第三章 进程管理笔记
20135109 高艺桐
3、1进程
1、程序本身并不是进程,进程是处于执行期的程序以及相关资源的总称。
2、执行线程,简称线程,是进程中活动的对象。每个线程都拥有一个独立的计数器、进程栈和一组进程计数器。
3、内核调度的对象是线程而不是进程,对Linux而言,线程是一种特殊的进程。
4、进程在创建它时开始存活,系统调用通过复制一个现有的进程来创建一个全新的进程,调用fork()的进程为父进程,新产生的进程为子进程。
5、fork()系统调用从内核返回两次:一次回到父进程,一次回到新产生的子进程。
3、2进程描述符及任务结构
1、内核把进程的列表存放叫做任务队列的双向循环列表,链表中的每一项都是类型为task_struck,成为进程描述符的结构,定义在<Linux/sched.h>文件中。
2、进程描述符包含一个进程的所有信息,包括:进程地址空间、挂起的信号、进程的状态等。
3、Linux通过slab分配器分配task_struck结构,这样能达到对象复用和缓存着色的目的。由于slab分配器动态生成task_struck,所以只需要在栈底(对于向下增长的栈来说)或栈顶(对于向上增长的栈来说)创建一个新的结构struck thread_info。
4、每个任务的thread_info结构在它的内核栈的低端分配,结构中task域中存放的是指向该任务实际task_struck的指针。
5、内核通过唯一的进程标识值或PID来标识每一个进程,PID实际上就是一个int类型。
6、在内核中,访问任务通常需要获得指向其task_struct的指针,实质上内核中大部分处理进程的代码都是直接通过task_struct进行的。有的硬件可以拿出一个专门的寄存器来存放当前进程的task_struct指针,加快访问速度,而想x86这样的体系结构,只能在栈的尾端创建thread_info结构,通过计算偏移间接的查找task_struct结构。
7、进程描述符中的state域描述了进程的当前状态,系统中的每个进程都必定处于这5种状态中的一种:
TASK_RUNNING(运行)
TASK_INTERRUPTIBLE(可中断)
TASK_UNINTERRUPTIBLE(不可中断)
_TASK_TRACED——被其他进程跟踪
_TASK_STOPPED——(停止)进程停止执行
8、内核经常需要调整某个进程的状态,这时最好使用set_task state(task,state)函数,这函数将指定的进程设置为指定的状态。
8、可执行程序代码是进程的重要部分,这些代码从一个可执行文件载入到进程的地址空间执行,一般程序在用户空间执行。我们称内核“代表进程”执行并处于进程中下文中。
9、所有的进程都是PID为1的init进程的后代,内核在系统启动的最后阶段启动init进程。
系统中的每一个进程都必有一个父进程,相应的每个进程也可以拥有0个或多个子进程,每个task_struct都包含一个指向其父进程的task_struct、叫做parent指针,还包含一个children的字进程链表。
3、3进程创建
1、进程首先在新的地址空间里创建进程,读入可执行文件,最后开始执行,fork()通过拷贝当前进程的一个子进程,子进程与父进程的区别在于PID、PPID等;exec()函数负责读取可执行文件并将其载入地址空间开始运行。
2、写时拷贝:
Linux的fork()使用写时拷贝(copy-on-weite)页实现,fork()的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述。
3、fork():
(1)Linux通过clone()系统调用fork(),然后由clone()去调度do_fork()
(2)do_fork()创建了大部分的工作,定义在kernel/fork.c的文件中,调用copy_process()函数,让进程开始运行。
(3)copy_process函数的工作流程:
1)调用dup_tast_struct()为进程创建内核栈、thread_info结构和task_struck等,这些值父进程与子进程完全一致。
2)子进程着手于父进程区别开。
3)子进程将状态设置为TASK_UNINTERRUPTIBLE,保证不会投入运行。
4)copy_process()调用copy_flags()更新task_struck的flags成员。
5)调用alloc_pid为新进程分配PID
6)根据传递给clone的参数标志,copy_process()拷贝或共享打开文件夹等。
7)copy_process()做扫尾工作,并返回子进程指针
8)回到do_fork()函数。
4、vfork():
(1)除了不拷贝父进程的页表外,vfork()系统调用和fork()系统调用基本相同。
(2)vfork()系统调用的实现是通过向clone()系统调用传递一个特殊的标志来进行的:
1)在调用copy_process()时,task_struck的vfor_done成员被设置成NULL;
2)执行do_fork时,vfor_done会指向一个特定的地址
3)子进程执行后,父进程不是马上执行而是继续等待,直到子进程的vfor_done向它发送信号
4)调用mm_release时进程退出内存地址空间,检查vfor_done是否为空,不为空则向父进程发送信号。
5)回到do_fork(),父进程继续工作。
3、4线程在Linux中的实现
1、线程机制是现代编程机制中一种常用的抽象概念,Linux把所有的线程都当做进程来实现。每一个线程都拥有自己的task_struck。
2、线程的创建和普通的进程创建类似,只不过在调用clone()的时候需要传递一些参数标志来指明需要共享的资源,传递给clone()的参数标志决定了新创建进程的方式和父子进程之间共享的资源类型。
3、内核线程和普通的进程间区别在于内核线程没有独立的地址空间(指向地址空间的mm指针被指向为NULL),他们只在内核空间运行,从来不切换到用户态去。
4、内核进程和普通进程一样,可以被调度也可以被抢占。
5、Linux会把一些任务交给内核线程去做,像flush和ksofirqd等。
3、5进程终结
1、进程终结时,内核必须释放它所占有的资源,终结依靠do_exit()。
2、在父进程获得子进程终结的信息后,子进程的task_struck结构才被释放。
3、wait()的标准动作是挂起调用它的进程,直到其中的一个子进程退出,此时函数会返回子进程的PID。
4、wait()用来检查子进程,清除所有与其相关的僵死进程。
5、如果父进程在子进程之前退出,必须有机制能保证子进程能找到一个新父亲,如果不行就让init做他们的父进程。在exit()中调用exit_notify(),该函数会调用forget_original_parent(),而后会调用find()_new_reaper()来寻找父进程。