八. Linux 的进程管理(一)Linux 的进程结构// 在文件 include/linux/task.h 中#define NR_TASKS 512//在文件 /kernal/ seched.c中struct task_struct *task [ NR_TASKS ]= { &init_task };//在文件 /include/linux / seched.h 中struct task_struct { … } //Linux的PCB内容Linux的PCB: struct task_struct { …} 结构[ 见补充材料 p13](1) 进程状态(2) 进程调度信息(3) 进程标识(4) 内部通信信息(5) 进程链接指针(6) 时间和定时器信息(7) 文件系统信息(8) 虚拟内存信息(9) 进程上下文1.Linux进程状态 long state;2.进程调度信息:进程普通进程——优先级调度实时进程——先来先服务调度FCFS时间片轮转调度 RR(1) long counter ; 进程剩余时间片(2) long priority; 优先级(同时还表示进程时间片)当counter=0时,priority =〉counter(3) unsigned long policy;SCHED_ OTHER 普通进程调度策略 SCHED_ FIFO 实时进程SCHED_ RR 实时进程(4)unsigned long rt_priority;实时优先级(调度程序求权重时用到它)3.标识号(1) int pid ; //进程标识号(2) unsigned short uid,euid,suid,fsuid;//用户标识,有效标识,备份标识,文件系统标识(3) unsigned short gid,egid,sgid,fsgid;//用户组标识,有效组标识,备份组标识,文件系统用户组标识4. 内部通信信息(IPC —— Inter Process Communication)为使在同一任务上协调工作,进程间必须作通信即:交流数据信号Linux 支持通信机制管道共享内存信号量消息队列(1) long signal; //进程接收到的信号(共有32 种类型号:0~ 31表示)(2) long blocked; //信号掩码(3) struct signal_struct *sig;// 信号处理函数结构指针(4) struct sem_queue *semsleeping;//与信号量相关的等待队列指针(5) struct sem_undo * semundo;//为避免死锁在信号量上设置取消操作5.进程指针可运行队列进程链接信息队列指针各种等待队列进程树(家属关系)(1) struct task_struct *next_run.. .. *prev_run//可运行队列(2) struct task_struct *next_task.. .. *prev_task(3) struct task_struct *p_opptr;// ( original parent 祖先) .. .. *p_pptr//( parent 双亲).. .. *p_cptr// ( child 孩子).. .. *p_osptr//( older sibling 左兄弟) .. .. *p_ysptr//( younger sibling 右兄弟)6.时间和定时器信息(1) long start_time; //进程创建时间(2) long utime;// 进程在用户态下耗费时间stimr;// 进程在系统态下耗费时间cutime;// 子进程在用户态下耗费时间 cstime;// 子进程在系统态下耗费时间(3) long counter; // 进程剩余时间片(4) unsigned long timeout;//进程申请延迟时间Linux 允许进程创建三种定时器实时定时器虚拟定时器概况定时器每种定时器包含三项内容:(5) unsigned long it_real_value,it_virt_value, it_prof_value;// 到期时间unsigned long it_real_incr,it_virt_ incr, it_prof_incr; //定时间隔,当定时间隔〉0,循环置定时值第三项仅对实时定时器存在:struct timer_list real_timer;//实时定时器到期要触发的事件及到期时间结构[若有时,按到期时间,将所有进程此结构,挂入系统实时钟例程任务队列中 ]7. 文件系统信息(1)struct fs_struct *fs;本进程可执行映像文件所在根目录(root)本进程可执行映像文件所在当前目录(pwd)(2) struct files_struct *files;进程打开文件描述符表8.虚拟内存信息【除了内核线程(kernel thread) 或守护进程(daemon)外,每个进程都拥有自己的虚拟内存空间。
Linux 内核负责进程的虚拟内存和物理内存映射】(1) struct mm_struct *mm;// 描述进程虚拟内存(2) struct desc_struct *ldt;// 局部(进程)描述符表9.处理器与相关环境(上下文)信息执行环境:寄存器、堆栈、…(1) long debugreg [ 8];//8个调试寄存器(2) struct thread_struct *tss;//任务切换状态(二) Linux 进程控制1.进程创建即:构造进程:PCB 、程序与数据区进程映像系统调用fork( ),创建一个子进程,使子进程成为一个处于“可运行状态”(入可运行队列)、竞争CPU 单位。
子进程映像除pid进程标识外,即基本复制父进程映像格式:int fork( )0 ——子进程从fork后调度到时返回值为0返回值 >0 ——父进程fork后调度到时返回值(子pid)-1 ——创建子进程失败.例(1) : 父子 C语言程序# include <stdio.h># include <sys/types.h> #include <unistd.h>int main(void){ pid_t pid;pid =fork();//①系统存在二个进程,父、子,其执行映象程序数据//都相同.但各有一个备份!if( pid <0) { printf(“forkerror\n”);//②fork失败exit(0);} //③系统调用,终止本进程//执行,此为父进程else if( pid = = 0){ printf(“This is child process!”);} //④子进程从fork返回时,执行printf else{ printf(“This is parent process!”); } //⑤父进程从fork返回时,执行printf exit(0); // ⑥父,或子正常终止}分析:执行顺序 (1)fork创子进程失败①②③结果:fork error(2)fork 后父进程先调度到: ①判pid<0 pid≠0⑤输出: this is parent process!⑥再调子进程判pid<0 pid= = 0④⑥输出:This is child process!(3)fork 后子进程先调度列: ①判pid<0 pid==0 ④⑥后再调度父:判pid<0 pid≠0 ⑤⑥输出结果:This is child process!This is parent process!处理过程:fork()创建一子进程 (调用do_fork()完成)调用exec()使子进程运行2. 进程执行3. 父等子进程终止___wait()父与子同步父阻塞,入等待队列,等待子进程终止时,唤醒父#include <sys/type.h>#include <sys/wait.h>Pid_t wait (int *statloc)子进程ID__正确返回子终止时返回给父状态参数0 ___其他-1 __出错返回功能:父等子终止并获子状态参数return返回4. 进程终止 exit() 或exit()返回使子进程处于TASK_ZOMB 终止状态,仅保留task_struct,唤醒父进程(若父调用了wait),向父发送信号,通知父,子要求终止处理.父消亡____改父为1#init作子的善后处理.子终止前父未亡____由父进程作子善后处理:◆读task_struct信息:PID,终止状态信息,CPU时间信息(用机时间)作记帐用task_struct格式: #include <stdio.h>void exit ( int status )传给父状态信息例3.fork() 、wait()、exit() 举例综合#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>#include <stdio.h>int main(){ pid_t pid1,pid2; int stat;if((pid1 =fork())< 0){ printf(“fork1 error!”); //创子进程出错失败! exit(1); }else if( pid1== 0 ) //创子进程成功//子又创”孙进程”“fork2 error!”) //孙未成功创建 }exit(0); //子创孙成功则子结束. else {//入睡2秒printf(“this is grandson, parentis:%d,”getppid());//孙结束(可能子已消亡,则1//#init代替孙的父亲(子进程)//善后处理孙进程}}wait ( &stat); // 父成功创子后阻塞,等待子消亡 exit(0); // 子消亡后,父消亡}孙入睡2秒,唤醒后再输出.此时孙之父(子)已消亡,则1#代其父(子)若成功创建结果:当孙进程运行时输出:This is grandson, parent is 1孙 (孙的父为1号进程)( Ⅲ ) Linux核心进程调度(一)内核机制中断分割中断处理(分割)调度程序schedule() { …do_bottom_ 延迟到half }系统调用返回时:ret_from_syscall ( ) (尽可能快执行){…}1.内核机制――数据结构(1)b otton_half―内核例程处理程序(<=32个)全局变量unsigned long bh_active=0 是否激活内核例程处理程序位=0 未激活位=1 已激活unsigned long bh_mask=0 是否安装了内核例程处理程序位=0未安装位=1已安装void ( *bh_base[32] ) ( viod ) 内核例程控制程序数组即:一组指向函数的指针每一元素为一指向例程(函数)指针bh_mask是否安装void(*bh_base[32])(viod)0 实时定时Void timer_bh(void)1 控制台0-未安装 2 TTY消息队列1-已安装 3(按安装例程置位)8bh_active是否激活0-未激活1-已激活何时置位:每发生一次相应设备中断(如时钟中断)则设备在bh_active中相应位置1(时钟为第0位)在bottom half中处理结束复位(置0)每个bottom_half例程干什么?处理一个任务队列(2)任务队列①结点结构:struct tq_struct //(一般任务结点){ struct tq_struct *next; // 指向下一个结点int sync;// 初始为0void ( *rountune ) ( void * ); // 调用例程指针void *data; // 例程参数}②定时器任务结点结构(与一般队列结点不同)Linux同时支持两种定时机制(兼顾老机制)(a) 老机制:定时器表结构:struct timer_struct{ unsigned long expires; // 到期时间void (*fn)(void); // 要触发例程}timer_active timer_table数组timer_struct31 i 1 0 0i31初始化时:unsigned long timer_active = 0;示无定时器任务(b) 新机制:定时器队列结构:struct timer_list{ struct timer_list *next;struct timer_list *prev;unsigned long expires;//到期时间(定义定时器: 当前jiffies +// it_real_value )unsigned long *data; // 数据void (*function) (unsigned long );// 该结点触发一个例程指针}timer_list timer_list timer_list (P88)到期时间升序排队!!!初始化时:struct timer_listtimer_head={&timer_head, &timer_head, ~0 ,0, NULL}队列头指针头尾指向自己最大数2.d o_bottom_half――处理所有(32个)内核例程(P内核书78)void do_bottom_half ( void ){ unsigned long active; // 定义无符号长整型工作变量unsigned long mask, left;void ( **bh ) (void) , (*fn ) (void );// 定义fn为指向函数的指针, bh指向函数指针的指针sti ( ); // 开中断bh=bh_base; // 取出函数型指针数组首址=> bh active = bh_active & bh_mask;// 看是否有激活内核例程for (mask=1, left= ~0; left & active;// e1 e2// 置00…01, 全1 视:32位及还有否激活例//程需处理bh++ , mask<<1, left<<1 )// e3 mask形如:00….10…..0用于判断当前位// left 形如:11…110….0用于判32位是否都处理完成了!if ( mask & active ) // 判当前位,有否要动作?{bh_active &= ~mask // 有:清当前位上的bh_active位fn=*bh;bh_baseif ( ! fn ) bh→0 例程函数go to bad_bh; bh++fn ( );31// 执行例程程序(函数)处理一个任务队列}return;bad_bh: printf( “irqc:bad bottom half entry %08lx \n ”,mask );}3. 特殊任务队列――tq_scheduler【因为】内核bh_base[32]数组仅32个项目tq_scheduler目的扩充【所以】该队列中每个例程也是一个内核例程处理程序(bottom half_handler )调度程序中被运行处理____ tq_scheduler队列void schedule (void) // Linux调度程序!{ ……run_task_queue (& tq_scheduler );……..}1.timer bottom half handler――定时器内核例程处理程序即:timer_bh()(1)struct task_struct// 有关实时定时器工作单元{ ……unsigned long it_real_value; //到期时间unsigned long it_real_incr; //定时间隔:(是否循环定时!=0是循环)struct timer_list real_timer; //定时器队列结构……….} 此结构被链入(双向勾链)系统中的timer_list 结构队列(到期时间是时钟单位jiffies)(依it_real_value及it_real_incr置)(2)void timer_bh(void) 定时器内核例程处理程序{ run_old_timers (); // 处理定时器表(老定时器机制)cli(); // 临界段处理(关锁)(关中)run_timer_list (); // 处理定时器队列(新机制)sti();//(开锁)(开中)…….if ( - - current-> counter<=0 ){ current-> counte r =0; // 用完时间片(配额)need_resched=1; // 置再调度标志}do_process_times(current,user,system);// 记录当前进程使用CPU时间do_it_virt();// 更新当前进程(用户态)虚拟定时器,若到期:发//SIGVTALRM信号do_it_prof();//更新当前进程概况定时器,若到期,发SIGPROF//信号(占CPU不论用户态,核心态)}( 3 ) void run_timer_list(void) 定时器队列(新机制)处理函数{struct timer_list *timer; // 工作指针,指向当前任务队列中一个结点// 循环直到timer-> timer_head为止timer-> expires<=jiffies )本队列结点到期时间//{ /* 将结点:定时器中一个例程从定时器中移走*/void ( *fn (unsigned long) =timer-> function; // 定义函数指针fnunsigned long data = timer-> data; // 取结点中参数及函数指针(地址)timer->next ->prev = timer->prev;timer->prov->next = timer->next;timer->next = timer->prev = NULL; //当前结点双向指针置空sti();fn(data);// 执行该结点的定时器例程(函数)cli();}}即:本函数遍历定时器队列,处理所有到期定时器,然后将定时器从队列中移走。