《linux内核设计与实现》笔记:第三章进程管理

1、关于进程的一些事

1)进程就是正在执行的程序代码的实时结果。也就是内核为了执行相应的代码,而封装起来的一个东西,为了有效且透明地管理所以细节。
2)内核调度的对象是线程,而不是进程。Linux系统的线程实现非常特别,它对线程和进程不特别区别。对Linux而言,线程只不过是一种特殊的进程罢了,
3)程序本身并不是进程,进程是出于执行期的程序以及相关的资源的总和。可以将进程理解为一个大锅,里面放着各式各样的菜。
4)调用fork()的进程成为父进程,新产生的进程称为子进程。它返回两次,一次回到父进程,另一次回到新产生的子进程。
5)通常,创建新的进程都是为了立即执行新的、不同的程序,而接着调用exec()这组函数就可以创建新的地址空间,并把新的程序载入其中。在现代Linux内核中,fork()实际上是由clone()系统调用实现的。
6)程序退出后,被设置为僵死状态,直到它的父进程调用wait()或waitpid()为止。
7)Linux内核通常把进程叫做“任务”。

2、关于进程的描述符及任务结构

1)内核把进程的列表存放在叫做任务队列的双向循环链表中。
2)链表中的每一项都是类型为task_struct,称为进程描述符的结构。
3)进程描述符中包含的数据能完整地描述一个正在执行的程序:它打开的文件,进程的地址空间,挂起的信号,进程的状态,还有更多信息。
4)task_struct结构是由Linux通过slab分配器分配的,这样能达到对象复用和缓存着色。
5)在创建后的进程中,需要在栈底(对于向下增长的栈来说)或栈顶(对于向上增长的栈来说)创建一个新的结构体struct thread_info.
《linux内核设计与实现》笔记:第三章进程管理

3、关于进程描述符的存放

1)内核通过一个唯一的进程标识值或PID来标识每个进程。PID是一个数,表示为pid_t隐含类型,实际上是一个int类型。
2)在内核中,访问任务通常需要获得指向其task_struct的指针。实际上,内核中大部分处理进程的代码都是直接通过task_struct进程的。因此,通过current宏查找到当前正在运行进程的进程描述符的速度就显得尤为重要。

4、关于进程上下文

1)当一个程序调执行了系统调用或者出发了某个异常,它就陷入了内核空间,此时,我们称内核“代表进程执行”并处于进程上下文中。
2)系统调用和异常处理程序是对内核明确定义的接口。进程只有通过这些接口才能陷入内核执行——对内核的所有访问都必须通过这些接口。

5、关于进程家族树

1)所有的进程都是PID为1的init进程的后代。
2)内核在系统启动的最后阶段启动init进程。该进程读取系统的初始化脚本并执行其它的相关程序,最终完成系统启动的整个过程。
3)系统中的每个进程必有一个父进程,相应的,每个进程也可以拥有零个或多个子进程。拥有同一个父进程的所有进程被称为兄弟。
4)进程间的关系存放在进程描述符中(struct task_struct)。

6、关于fork的写时拷贝

1)传统的fork()系统调用直接把所有的资源复制给新的进程。这种实现过于简单并且效率低下,因为它拷贝的数据也许并不共享,更糟糕的情况时,如果新进程打算立即执行一个新的映像,那么所有的拷贝都将前功尽弃。
2)Linux的fork()使用“写时拷贝”技术。写时拷贝是一种可以推迟,甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享一个拷贝。只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。
3)比如,fork()后立即调用exec(),它们就无须复制了。

7、关于线程在Linux中的实现

1)从内核的角度来说,它并没有线程这个概念。
2)Linux把所有的线程都当作进程来实现。
3)内核并没有准备特别的调度算法或是定义特别的数据结构来表示线程。相反,线程仅仅被视为一个与其它进程共享某些资源的进程。
4)每个线程都拥有唯一隶属于自己的task_struct,所以在内核中,它看起来像是一个普通的进程(只是线程和其它一些进程共享某些资源,如地址空间)。
5)在其它的系统中,相较于重量级的进程,线程被抽象成一种耗费较少资源,运行迅速的执行单元。而对于Linux来说,它只是一种进程间共享资源的手段(Linux的进程本身就够轻量级了)。

8、关于内核线程

1)内核线程和普通的进程间的区别在于内核线程没有独立的地址空间(实际上指向地址空间的mm指针被设置为NULL)。它们只在内核空间运行,从来不切换到用户空间取。
2)内核进程和普通进程一样,可以被调度,也可以被抢占。

9、关于孤儿进程

1)如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父进程,否则这些称为孤儿的进程就会在退出时永远处于僵死状态,白白地浪费内存。
2)解决的方法使给子进程在当前线程组内找一个线程作为父亲,如果不行,就让init做它们的父进程。