第三章进程管理
应专业选修Linux程序设计老师的要求,记录读书笔记
3.1进程
**1)进程:**处于执行期的程序(目标码存放在某种介质上),包含代码段还包含其他资源(打开的文件、内核内部数据、存放全局变量的数据段),是正在执行的程序代码的实时结果,Linux通常叫进程也叫任务。
**2)线程:**拥有独立的程序计数器、进程栈和一组进程寄存器。内核调度的是线程不是进程,对于 linux线程是一种特殊进程。
3**)实际上,**许多进程分享一个处理器,但是虚拟处理器为每个进程提供自己独享的处理器。虚拟内存为每个进程提供独享整个系统的内存资源
**4)linux通过父进程调用fork()**进行复制一个现有进程来创建一个全新进程——子进程。fork()函数实际上是由clone()系统调用实现的,exec()创建新的地址空间。fork()系统调用从内核返回两次:一次回到父进程,另一次回到新产生的子进程。
3.2进程描述符及任务结构
1)进程的列表存放在任务队列(双向循环链表)中,链表的每一项都是类型为task_struct称为进程描述符的结构,其包含一个具体进程的所有信息(进程的地址空间,挂起的信号,进程的状态,还有其他更多的信息)。
**2)分配进程描述符。**linux通过slab分配器分配task_struct结构(动态生成),task_struct存放在栈底或栈顶。每个任务的thread_info结构在它的内核栈的尾端分配,结构中的task域中存放的指向该任务实际task_struct的指针
3)进程描述符的存放。pid或者唯一的进程标识值来标识每个进程。内核把每个进程的pid存放在他们各自的进程描述符中。
4)进程状态:系统中的每个进程都必然处于五种进程状态中的一种。该域的值也比为:TASK_RUNNUNG(运行)、TASK_INTERRUPTIBLE(可中断)、TASK_UNINTERRUPTIBLE(不可中断)、_TASK_TRACED(被跟踪)_TASK_STOPPED(停止)
5)设置当前进程状态:set_task_state(task,state)函数将任务task的状态设置为state
6)进程上下文:current宏在进程上下文中使有效的,除非在此间隙有更高优先级的进程需要执行并由调度器做出了相应调整,否则退出内核进入用户态后会继续运行
7)进程家族树,进程之间存在明显的继承关系,都是PID为1的init进程的后代。任务队列是双向循环链表。获取前一个进程的方法list_entry(task->tasks.prev,struct task_struct,tasks)获取后一个进程的方法list_entry(task->tasks.prev,struct task_struct,tasks)依次访问整个任务队列for_each_process(task)
3.3进程创建
1)先用fork()拷贝当前进程创建子进程,exec()函数负责读取可执行文件并将其载入地址空间开始运行。
2)写时拷贝:fork()拷贝进程的时候使用写时拷贝页实现,让父进程和子进程共享同一个拷贝,不复制整个进程地址空间,使地址空间上的页(数据)的拷贝推迟到实际发生写入的时候才进行。
3)fork()调用clone(),clone()调用do_fork(),do_fork()完成创建中的大部分工作。
4)vfork()除了不拷贝父进程的页表项外,功能与fork()相同
3.4线程在linux中的实现
1)把所有线程都当做进程来实现,被视为一个与其他进程共享某些资源的进程。耗时更少,资源消耗更少,执行更快
2)创建线程:与创建进程不同的是在调用clone()函数的时候需要传递一些参数标志来指明需要共享的资源
3)内核线程:后台执行的一些操作,独立运行在内核空间的标准进程,没有独立的地址空间(指向地址空间mm指针被设置为NULL)。只在内核空间运行,可以被调度,可以被抢占。ps -ef查看内核线程,内核线程只能由其他内核线程创建(kthreadd内核进程)
3.5进程终结
1)do_exit()定义于kernel/exit.c:进程终结的大部分任务靠do_exit()。
2)do_exit()做的工作:
a.将tast_struct中的标志成员设置为PF_EXITING
b.调用del_timer_sync()删除任一内核定时器根据返回的结果判断定时器处理程序是否在运行
c.若BSD进程的记账功能是开启的,do_exit()调用acct_uodate_integrals()来输出记账信息
d.调用exit_mm()函数释放进程调用的mm_struct,如果没有别的进程使用它们(地址空间没有被共享)就彻底释放
e.调用sem_exit()函数,如果进程排队等候IPC信号,它则离开队列
f.调用exit_files()和exit_fs(),以分别递减文件描述符、文件系统数据的引用计数,如果引用计数为零则释放
g.把存放在task_struct的exit_code成员中的退出代码置为由exit()提供的退出代码,退出代码放在这里供父进程随时检索
h.调用exit_notify()向父进程发送信号,给子进程重新找养父,并把进程状态设置为EXIT_ZOMBLE
i.do_exit()调用schedule()切换到新的进程,处于EXIT_ZOMBLE状态的进程不会在被调度,所以这是进程所执行的最后一段代码,do_exit()永不返回。
3)删除进程描述符:调用do_exit()之后,线程僵死,但为了让系统有办法在进程终结后还能获得他的信息,进程描述符还保留,所以删除进程描述符在进程终结之后。
a.wait()这一族函数唯一的系统调用wait4()来实现调用它的进程,知道其中的一个子进程退出
b.释放进程描述符是,release_task()会被调用:
c.他调用_exit_signal(),该函数调用_unhash_process(),后者又调用detach_pid()从pidhash上删除该进程,同时从任务列表删除该进程
d._exit_signal()释放目前释放僵死进程所使用的所有剩余资源,并进行最终统计和记录
e.若果这个进程是线程组最后一个进程,并且领头进程已经死掉,那么release_task()就要通知僵死的领头进程的父进程
f.release_task()调用put_task_struct()释放进程内核栈和thread_info结构所占的页,并释放task_struct所占的slab高速缓存
4)进程描述符和所有进程独享资源全部被释放了
5)父进程在子进程之前退出,子进程会变成孤儿,永远处于僵死状态:给子进程在当前进程组找一个线程作为父亲,或者让init做他们的父进程。