进程

定义:进程是程序执行时的一个实例。

所有进程都有一个父进程。

当一个进程创建时,它几乎与父进程相同。她接受父进程地址空间的一个拷贝,并从进行创建系统调用的下一条指令开始执行与父进程相同的代码。尽管父子进程可以共享含有程序代码的页,但是他们各自有独立的数据拷贝。

 

进程描述符

  进程状态。进程状态是互斥的。

  可运行状态(TASK_RUNING),进程要么在CPU上执行,要么准备执行

  可中断的等待状态(TASK_INTERRUPTIBLE),进程被挂起(睡眠),直到某个条件变为真。产生一个硬件中断,释放进行正在等待的系统资源,或者传递一个信号都是可以唤醒进程的条件。

  不可中断的暂停状态(TASK_UINTERRUPTIBLE),与可中断的等待状态类似,但是有一个例外,把信号传递到睡眠进程不能改变它的状态。

  暂停状态(TASK_STOPPED),进程的执行被暂停。当进程接收到SIGSTOP,SIGTSTP,SIGTTIN或者SITTOU信号后,进入暂停状态。

  跟踪状态(TASK_TRACED),进程的执行已由debugger程序暂停。

  僵死状态(EXIT_ZOMBIE),进程的执行被终止,但是父进程还没有发布wait4()或waitpid()系统调用来返回有关死亡进程的信息。发布wait()类系统调用前,内核不能丢弃包含在进程描述符中的数据。

  僵死撤销状态(EXIT_DEAD),最终状态:由父进程刚发出wait()或waitpid()系统调用,因而进程由系统删除。为了防止其他执行线程在同一个进程上也执行wait()类系统调用(这是一种竞争条件),而把进程状态由僵死改为撤销状态。

  

  标识一个进程

  进程和进程描述符有非常严格的一一对应关系,这使得用32位进程描述符的地址标识进程称为一种方便的方式。另一方面,类Unix操作系统允许用户使用一个叫做进行标识符(process ID)的数来标识进程,PID存放在进行描述符的pid字段中。PID被顺序编号,PID有个上限,当不够用时必须开始循环已经闲杂的PID号。由于循环使用PID号,内核必须通过管理一个pidmap_array位图来表示当前已分配和闲置的PID。因为一个页框包含32768个位,所以在32位体系结构中pidmap_array位图存放在一个单独的页中。

 

  进程描述符处理

  内核把进程描述符存放在动态内存中。对于每个进程来说,内核都把两个不同的数据结构紧凑地存放在一个单独为进程分配的存储区域内:一个是与进程描述符相关的小数据结构thread_info,叫做线程描述符。另一个是内核态的进程堆栈。这个区域的大小通常是8K(两个页框)。考虑到效率问题,内核让这8K空间占据连续的两个页框并让第一页框的起始地址是213的倍数。

 

  创建进程

  传统的Unix操作系统以统一的方式对待所有的进程:子进程复制父进程的所有资源,效率低下。

  现代Unix内核通过引入三种不同的机制解决了这个问题:

  1.写时复制技术允许父子进程读相同的物理页。只有两者中有一个试图写一个物理页,内核就把这个页的内容拷贝到一个新的物理页,并把这个物理页分配给正在写的进程。

  2.轻量级进程允许父子进程共享在内核的很多数据结构。

  3.vfork()系统调用创建的进程能共享其父进程的内存地址空间。为了防止父进程重新子进程需要的数据,阻塞父进程的执行,一直到子进程退出或者执行一个新的程序为止。

 

  clone函数

  在Linux中,轻量级进程是由名为clone的函数创建的。实际上,clone是在C语言库中定义的一个封装函数。它负责建立新轻量级进程的堆栈并且调用对编程者隐藏clone系统调用。实现clone系统调用的sys_clone服务例程没有fn和arg参数。实际上,封装函数把fn指针放在子进程堆栈的某个个位置处,该位置就是该封装函数本身返回地址存放位置。arg指针刚好存放在子进程堆栈中fn下面。当封装函数结束时,CPU从堆栈中取出返回地址,然后执行fn(arg)函数。

  传统的fork系统调用在Linux中是用clone实现的。vfork也是。

posted @ 2014-05-19 17:38  Homura  阅读(287)  评论(0编辑  收藏  举报