[Linux内核完全剖析]第五章Linux内核体系结构5.7总结 进程控制

PCB(Process Control Block 进程控制块)又称任务数据结构,位于include/linux/sched.h中:

struct task_struct{
    long state; //任务的运行状态(-1 不可运行,0 可运行(就绪),>0 已停止)。
    long counter; //任务运行时间计数(递减)(滴答数),运行时间片。
    long priority; //运行优先数。任务开始运行时counter = priority。
    long signal; //信号。是位图,每个比特位代表一种信号,信号值=位偏移值+1。
    struct sigaction sigaction[32]; //信号执行属性结构,对应信号将要执行的操作和标志信息。
    long blocked; //进程信号屏蔽码(对应信号位图)。
    int exit_code; //任务执行停止的退出码,其父进程会取。
    unsigned long start_code; //代码段地址(线性地址)。
    unsigned long end_code; //代码长度(字节数)。
    unsigned long end_data; //代码长度 + 数据长度(字节数)。
    unsigned long brk; //总长度(字节数)。
    unsigned long start_stack; //堆栈段地址。
    long pid; //进程标识号(进程号)。
    long father; //父进程号。
    long pgrp; //父进程组号。
    long session; //会话号。
    long leader; //会话首领。
    unsigned short uid; //用户标识号(用户id)。
    unsigned short euid; //有效用户id。
    unsigned short suid; //设置用户id。
    unsigned short gid; //组标识号(组id)。
    unsigned short egid; //有效组id。
    unsigned short sgid; //设置组id。
    long alarm; //报警定时值(滴答数)。
    long utime; //用户态运行时间(滴答数)。
    long stime; //系统态运行时间(滴答数)。
    long cutime; //子进程用户态运行时间。
    long cstime; //子进程系统态运行时间。
    long start_time; //进程开始运行时刻。
    unsigned short used_math; //标志:是否使用了协处理器。
    int tty; //进程使用tty 的子设备号。-1 表示没有使用。
    unsigned short umask; //文件创建属性屏蔽位。
    struct m_inode *pwd; //当前工作目录i节点结构。
    struct m_inode *root; //根目录i节点结构。
    struct m_inode *executable; //执行文件i节点结构。
    unsigned long close_on_exec; //执行时关闭文件句柄位图标志。
    struct file *filp[NR_OPEN]; //进程使用的文件表结构位。

    struct desc_struct ldt[3]; //本任务的局部表描述符。0-空,1-代码段cs,2-数据和堆栈段ds&ss。
    struct tss_struct tss; //本进程的任务状态段信息结构。
};

进程运行状态(task_struct中的state字段)
0:运行状态,当进程占用CPU时间或者已经就绪随时可由调度程序执行时(就绪态),均属于该状态。进程可以在用户态运行,也可以在内核态运行。在内核态执行的时候如果需要等待系统某个资源,则会切换到睡眠状态,也只有这种情况下内核才会进行进程切换操作。(但是在Linux2.4内核以后有了内核态的优先级相关的进程切换功能)为了避免进程切换造成的内核数据错误,内核在执行临界区代码时会禁止中断。
1:可中断的睡眠状态,进程处于该状态时,不占用CPU时间。当系统产生了一个中断或者释放了进程正在等待的资源,或者进程收到一个信号,可以唤醒从而转到就绪态。
2:不可中断的睡眠状态,除不会因为收到信号而唤醒外,其余行为均与可中断的睡眠状态类似。只有使用wake_up()函数明确唤醒时才会转到就绪态,通常是在进程需要不受干扰的等待某个事件的发生时使用。
3:暂停状态,当进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU时会进入该状态,调试期间的进程收到任何信号均会进入暂停状态,可使用SIGCONT信号使暂停的进程进入就绪态。Linux0.11中并未实现该状态,会当作进程终止来处理。
4:僵死状态,进程已经结束运行,但是父进程还没有通过wait()问询其状态,这种情况被称为僵死状态,当父进程通过wait()获取了子进程信息之后,就会彻底清除该进程在PCB中的全部信息。

进程初始化
进程0初始化的过程是系统初始化过程的一部分。
1、引导程序先把内核从磁盘加载到内存中,并开启保护模式。
2、执行系统初始化代码init/main.c。这段代码会确定物理内存的分配和使用方式。
3、分别使用初始化函数对内存管理、中断处理、设备管理、进程管理以及软硬盘硬件进行初始化。
4、系统自主切换到进程0中。
5、进程0 fork()出init进程
6、init进程进行应用环境初始化
7、执行shell登录程序
进程0在系统空闲时会被调度出来,但它会执行一个pause()使自己放弃CPU时间,然后调度程序又会去调度别的进程来执行。
上述第4步由move_to_user_mode宏来完成,它构造好栈信息,使用iret指令从内核态跳转到用户态执行。在此之前调度程序初始化函数sched_init()会对进程0的task_struct,在GDT中设置好所需TSS和LDT,并加载到tr和ldtr中。
当切换到进程0后,由于特权级发生变化,使得DS、ES、FS、GS变得无效,需要重新加载。而SS仍然是切换前的值,也就是说进程0的用户态栈是之前内核使用的栈,而内核态的栈则是在上一步被设置成了其task_struct所在页面的顶端(PAGE_SIZE+(long)&init_task)。由于创建新进程1时,需要复制进程0的task_struct,所以要保持栈的清洁。

创建新进程
fork()调用过程
1、在进程数组中找到一个还未被使用的空项,如果满了(64个进程在运行)则返回出错。
2、在主内存区申请一页来存放task_struct信息。复制当前进程的task_struct到新进程,并把新进程的state更改为不可中断的睡眠状态(防止调度进程在未新进程未初始化完成时调度它)。
3、更改新进程task_struct其他数据项。把当前进程设置为新进程的父进程,清除信号位图并复位进程统计数据,设置初始运行时间片位15。设置tss中各寄存器值,tss.eax=0(fork()后新进程的返回值),tss.esp0为新进程tss_struct所在页面的顶端,tss.ss0设置为内核数据段选择符。还有tss.ldt,以及如果使用协处理器,则许把协处理器状态保存到tss.i387。
4、设置新进程数据和代码段描述符,并复制当前进程的页表。注意,系统此时不为新进程分配实际的物理内存页面,而是共享父进程的。只有当父子进程中任一个写内存时,系统才为执行写操作的进程实际分配,这是写时复制技术(copy on write)。
5、父进程打开的文件的打开次数+1。
6、设置GDT中新任务的TSS和LDT描述符。
7、设置新任务状态位就绪态,并返回新进程号。

进程调度
Linux是优先级相关的抢占式任务调度,Linux2.4版本以后增加了内核态抢占的功能。Linux0.11采用优先级排队的调度策略:
调度函数schedule() 运行时,会先比较每个就绪的进程的counter值,选取最大的来运行。如果没有就绪进程,重新计算每个进程的counter=counter/2+priority。然后再重新扫描是否有就绪进程,如果有则运行,没有则调用进程0,进程0会调用pause()自睡眠导致schedule()函数再次调用。
schedule()函数内部会调用switch_to()宏进行进程切换,遵循以下步骤:
1、判断要切换的进程是否为当前进程,如果是则直接运行,否则进入第2 步。
2、把全局变量current指针指向新任务,然后长跳转到新任务状态段TSS。
3、跳转造成任务切换,CPU会进行保存旧的TSS,载入新TSS的动作。

进程终止步骤如下
1、中止进程调用exit()系统调用。进入内核函数do_exit()。
2、释放进程代码段和数据段所占用内存页面,关闭打开的文件,对当前工作目录,根目录和程序的i节点进行同步操作。
3、如果有子进程则让init进程接管,如果是会话首领并有控制终端,则释放控制终端并向所有会话进程发送SIGHUP信号。
4、把进程设置为僵死状态,并向父进程发送SIGCHLD信号提醒过来收尸。
5、do_exit()调用调度函数。

This entry was posted in 操作系统 and tagged . Bookmark the permalink.

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注