第三章-进程(1)
最后更新于:2022-04-01 20:24:22
### 进程、轻量级进程和线程:
从内核观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的实体。
进程是资源管理的最小单位,线程是程序执行的最小单位。在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销。
最初的进程定义都包含程序、资源及其执行三部分,其中程序通常指代码,资源在操作系统层面上通常包括内存资源、IO资源、信号处理等部分,而程序的执行通常理解为执行上下文,包括对CPU的占用,后来发展为线程。在线程概念出现以前,为了减小进程切换的开销,操作系统设计者逐渐修正进程的概念,逐渐允许将进程所占有的资源从其主体剥离出来,允许某些进程共享一部分资源,例如文件、信号,数据内存,甚至代码,这就发展出轻量进程的概念。Linux内核在2.0.x版本就已经实现了轻量进程,应用程序可以通过一个统一的clone()系统调用接口,用不同的参数指定创建轻量进程还是普通进程。
当一个进程创建时,它几乎与父进程相同。它接受父进程地址空间的一个(逻辑)拷贝,并从进程创建系统调用的下一条指令开始执行与父进程相同的代码。尽管父子进程可以共巷含有程序代码的页,但是它们各自有独立的数据拷贝(栈和堆),因此子进程对一个内存单元的修改对父进程是不可见的。
Linux使用轻量级进程,对多线程应用程序提供更好的支持。两个轻量级进程基本上可以共享一些资源,诸如地址空间、打开的文件等等。只要其中一个修改共享资源,另一个就立即查看这种修改。
在Linux中,一个线程组基本上就是实现了多线程应用的一组轻量级进程。
### 进程描述符:
为了管理进程,内核必须对每个进程所做的事情进行清楚的描述。例如:内核必须知道进程的优先级,它是正在CPU上运行还是因为某些事件而被阻塞,给它分配了什么样的地址空间,允许它访问哪个文件等等,这正是进程描述符的作用。
### 进程状态:
可运行状态:进程要么在CPU上执行,要么准备执行。
可中断的等待状态:进程被挂起,直到某个条件变为真。
不可中断的等待状态:与可中断等待状态类似,但有一个例外:把信号传递到睡眠进程不能改变它的状态。
暂停状态:进程的执行被暂停。
跟踪状态:进程的执行已由Debugger程序暂停。当一个进程被另一个进程监控时,任何信号都可以把个进程置于跟踪状态。
僵死状态:进程的执行被终止,但父进程还没有发布wait4()或waitpid()系统调用来返回有关死忘进程的信息。在发布wait()类系统调用前,内核不能丢弃包含在死进程描述符中的数据,因此父进程可能还需要它。
僵死撤消状态:最终状态,由于父进程刚发出wait4()或waitpid()系统调用,因而进程由系统删除。为了防止其他执行线程在同一个进程上也执行wait()类系统调用,而把进程的状态由僵死状态改为僵死撤消状态。
### 标识一个进程:
内核对进程的大部分引用都是通过进程描述符指针进行的。
类Unix操作系统允许用户使用一个叫做进程标识符(PID)的数来标识进程,PID存放在进程描述符的pid字段中,PID被顺序编号,新创建进程的PID通常是前一个进程的PID加1。不过,PID的值有一个上限,当内核使用的PID达到这个上限值的时候必须开始循环使用已闲置的小PID号。缺省情况下,最大的PID号是32767。
由于循环使用PID号,内核必须通过管理一个pidmap_array位图来表示当前已分配的PID号和闲置的PID号。因为一个页框包含32768个位,所以32位体系结构中pidmap_array位置存放在一个单独的页中。
Linux把不同的PID与系统中每个进程或轻量级进程相关联。对于线程组,一个线程组中的所有线程使用和该线程组的领头线程相同的PID,即该组中第一个轻量级进程的PID,它被存入进程描述符的tgid字段。getpid()系统调用返回当前进程的tgid值而不是pid值。
### 进程间的关系:
进程0和进程1是由内核创建的,进程1(init)是所有进程的祖先。
进程描述符中表示进程亲属关系的字段的描述如下表:
进程之间还存在其它关系:一个进程可能是一个进程组或登陆会话的领头进程,也可能是一个线程组的领头进程,它还可能跟踪其他进程的执行。
### 如何组织进程:
运行队列链表把处于TASK_RUNNING状态的所有进程组织在一起,但由于对处于暂停、僵死、死亡状态进程的访问比较简单,Linux并没有为处于TASK_RSTOPPED、EXIT_ZOMBILE或者EXIT_DEAD状态的进程建立专门的链表。
对于处于TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE状态的进程,根据不同的特殊事件被细分为许多类,每一类都对应某个特殊事件,引入等待队列。
等待队列表示一组睡眠的进程,当某一条件为真时,由内核唤醒它们。
等待队列由双向链表实现。因为等待队列是由中断处理程序和主要内核函数修改的,因此必须对其双向链表进行保护以免对其进行同时访问,因为同时访问会导致不可预测的后果。同步是通过等待队列头中的lock自旋锁来达到的。
如果有两个或者多个进程在等待互斥地访问某一资源时,由内核有选择地唤醒,而非互斥进程总是由内核在事件发生时唤醒。
因为所有的非互斥进程总是在双向链表的开始位置,而所有的互斥进程在双向链表的尾部,所以内核总是先唤醒非互斥进程然后再唤醒互斥进程。一个等待队列中同时包含互斥进程和非互斥进程的情况是非常罕见的。
';
字段名 | 说明 |
real_parent | 指向创建了P的进程的描述符,如果P的父进程不再存在,就指向进程1的描述符 |
Parent | 指向P的当前父进程(这种进程的子进程终止时,必须向父进程发信号)。它的值通常与real_parent一致,但也可以不同 |
Children | 链表的头部,链表中的所有元素都是P创建的子进程 |
Sibling | 指向兄弟进程链表中的下一个元素或前一个元素的指针,这些兄弟进程的父进程都是P |