(三)进程管理
- 内核调度的对象是线程,而不是进程;对Linux而言,线程只不过是一种特殊的进程罢了。
- 在现代操作系统中,进程提供两种虚拟机制:虚拟处理器和虚拟内存,让进程觉得自己在独享处理器和内存资源。有趣的是,在线程之间可以共享虚拟内存,但每个都拥有各自的虚拟处理器。
- 程序本身并不是进程,进程是处于执行期的程序以及相关的资源的总称。
- 内核把进程的列表存放在叫做任务队列的双向循环链表中,链表中的每一项都是类型为task_struct、称为进程描述符的结构,包含一个具体进程的所有信息。
- 进程描述符中包含的数据能完整地描述一个正在执行地程序:它打开的文件,进程的地址空间,挂起的信号,进程的状态,还有其他更多信息。
- Linux通过slab分配器分配task_struct结构,这样能达到对象复用和缓存着色的目的,通过预先分配和重复使用task_struct,可以避免动态分配和释放所带来的资源消耗。
- 用slab分配器动态生成task_struct,只需在栈底或栈顶创建一个新的结构struct thread_info,每个任务的thread_info结构在它的内核栈的尾端分配,结构中task域中存放的是指向该任务实际task_struct的指针。
- 内核通过一个唯一的进程标识值或PID来标识每个进程。为了与老版本的Unix和Linux兼容,PID的最大值默认设置为32768,这个值越小,转一圈就越快。
- 一般程序在用户空间执行,当一个程序执行了系统调用或者触发了某个异常,它就陷入了内核空间。系统调用和异常处理程序是对内核明确定义的接口,进程只有通过这些接口才能陷入内核执行。
- 在Linux系统中,所有进程都是PID为1的init进程的后代。内核在系统启动的最后阶段启动init进程,该进程读取系统的初始化脚本并执行其他的相关程序,最终完成系统启动的整个过程。
- 系统中的每个进程必有一个父进程,相应的,每个进程也可以拥有零个或多个子进程,拥有同一个父进程的所有进程被称为兄弟。进程间的关系存放在进程描述符中,每个task_struct都包含一个指向其父进程tast_struct、叫做parent的指针,还包含一个称为children的子进程链表。
- Linux的fork()使用写时拷贝页实现,写时拷贝是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝,只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。
- 从Linux内核的角度来说,它并没有线程这个概念,Linux把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程,相反,线程仅仅被视为一个与其他进程共享某些资源的进程,每个线程都拥有唯一隶属于自己的task_struct,所以在内核中它看起来就像是一个普通的进程。
- 内核经常需要在后台执行一些操作,这种任务可以通过内核线程完成–独立运行在内核空间的标准进程。内核线程和普通的进程间的区别在于内核线程没有独立的地址空间,它们只在内核空间运行,从来不切换到用户空间去。内核进程和普通进程一样,可以被调度,也可以被抢占。
- 如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则这些成为孤儿的进程就会在退出时永远处于僵死状态,白白地耗费内存。对于这个问题,解决方法是给予子进程在当前线程组内找一个线程作为父亲,如果不行,就让init做它们的父进程。