雪泉的专栏

:-)工作了,很少来逛了

Linux进程编程介绍(一)
Linux进程编程介绍(一)
2006-11-19 16:15

转自:计算机基础教程网(ITWEN.com)

  摘要:本节将介绍进程的定义。进程作为构成系统的基本细胞,不仅是系统内部独立运行的实体,而且是独立竞争资源的基本实体。了解进程的本质,对于理解、描述和设计操作系统有着极为重要的意义。了解进程的活动、状态,也有利于编制复杂程序。
  

1.进程的基本概念
  
  首先我们先看看进程的定义,进程是一个具有独立功能的程序关于某个数据集合的一次可以并发 执行的运行活动,是处于活动状态的计算机程序。进程作为构成系统的基本细胞,不仅是系统内部独立运行的实体,而且是独立竞争资源的基本实体。了解进程的本 质,对于理解、描述和设计操作系统有着极为重要的意义。了解进程的活动、状态,也有利于编制复杂程序。
  
1.1 进程状态和状态转换
  
  现在我们来看看,进程在生存周期中的各种状态及状态的转换。下面是LINUX系统的进程状态模型的各种状态:
  
  用户状态:进程在用户状态下运行的状态。
  内核状态:进程在内核状态下运行的状态。
  内存中就绪:进程没有执行,但处于就绪状态,只要内核调度它,就可以执行。
  内存中睡眠:进程正在睡眠并且进程存储在内存中,没有被交换到SWAP设备。
  就绪且换出:进程处于就绪状态,但是必须把它换入内存,内核才能再次调度它进行运行。
  睡眠且换出:进程正在睡眠,且被换出内存。
  被抢先:进程从内核状态返回用户状态时,内核抢先于它,做了上下文切换,调度了另一个进程。原先这个进程就处于被抢先状态。
  创建状态:进程刚被创建。该进程存在,但既不是就绪状态,也不是睡眠状态。这个状态是除了进程0以外的所有进程的最初状态。
  僵死状态(zombie):进程调用exit结束,进程不再存在,但在进程表项中仍有纪录,该纪录可由父进程收集。
  现在我们从进程的创建到退出来看看进程的状态转化。需要说明的是,进程在它的生命周期里并不一定要经历所有的状态。
  
  首先父进程通过系统调用fork来创建子进程,调用fork时,子进程首先处于创建态,fork调用为子进程配置好内核数据结构和子进程私有数据结构后,子进程就要进入就绪态3或5,即在内存中就绪,或者因为内存不够,而导致在SWAP设备中就绪。
  
   假设进程在内存中就绪,这时子进程就可以被内核调度程序调度上CPU运行。内核调度该进程进入内核状态,再由内核状态返回用户状态执行。该进程在用户状 态运行一定时间后,又会被调度程序所调度而进入内核状态,由此转入就绪态。有时进程在用户状态运行时,也会因为需要内核服务,使用系统调用而进入内核状 态,服务完毕,会由内核状态转回用户状态。要注意的是,进程在从内核状态向用户状态返回时可能被抢占,进入状态7,这是由于有优先级更高的进程急需使用 CPU,不能等到下一次调度时机,从而造成抢占。
  
  进程还会因为请求的资源不能得到满足,进入睡眠状态,直到它请求的资源被释放, 才会被内核唤醒而进入就绪态。如果进程在内存中睡眠时,内存不足,当进程睡眠时间达到一个阀值,进程会被SWAP出内存,使得进程在SWAP设备上睡眠。 这种状况同样可能发生在就绪的进程上。
  
  进程调用exit系统调用,将使得进程进入内核状态,执行exit调用,进入僵死状态而结束。以上就是进程状态转换的简单描述。
  
   进程的上下文是由用户级上下文、寄存器上下文以及系统级上下文组成。主要内容是该进程用户空间内容、寄存器内容以及与该进程有关的内核数据结构。当系统 收到一个中断、执行系统调用或内核做上下文切换时,就会保存进程的上下文。一个进程是它的上下文中运行的,若要调度进程,就要进行上下文切换。内核在四种 情况下允许发生上下文切换:
  
  当进程自己进入睡眠时;
  当进程执行完系统调用要返回用户状态,但发现该进程不是最有资格运行的进程时;
  当内核完成中断处理后要返回用户状态,但发现该进程不是最有资格运行的进程时;
  当进程退出(执行系统调用exit后)时。
   有时内核要求必须终止当前的执行,立即从先前保存的上下文处执行。这可由setjmp和longjmp实现,setjmp将保存的上下文存入进程自身的 数据空间(u区)中,并继续在当前的上下文中执行,一旦碰到了longjmp,内核就从该进程的u区,取出先前保存的上下文,并恢复该进程的上下文为原先 保存的。这时内核将使得进程从setjmp处执行,并给setjmp返回1。
  
  进程因等待资源或其他原因,进入睡眠态是通过内核的 sleep算法。该算法与本章后面要讲到的sleep函数是两个概念。算法sleep记录进程原先的处理机优先级,置进程为睡眠态,将进程放入睡眠队列, 记录睡眠的原因,给该进程进行上下文切换。内核通过算法wakeup来唤醒进程,如某资源被释放,则唤醒所有因等待该资源而进入睡眠的进程。如果进程睡眠 在一个可以接收软中断信号(signal)的级别上,则进程的睡眠可由软中断信号的到来而被唤醒。

1.2 进程控制
  
  现在我们开始讲述一下进程的控制,主要介绍内核对fork、exec、wait、exit的处理过程,为下一节学习这些调用打下概念上的基础,并介绍系统启动(boot)的过程以及进程init的作用。
  
   在Linux系统中,用户创建一个进程的唯一方法就是使用系统调用fork。内核为完成系统调用fork要进行几步操作第一步,为新进程在进程表中分配 一个表项。系统对一个用户可以同时运行的进程数是有限制的,对超级用户没有该限制,但也不能超过进程表的最大表项的数目。第二步,给子进程一个唯一的进程 标识号(PID)。该进程标识号其实就是该表项在进程表中的索引号。第三步,复制一个父进程的进程表项的副本给子进程。内核初始化子进程的进程表项时,是 从父进程处拷贝的。所以子进程拥有与父进程一样的uid、euid、gid、用于计算优先权的nice值、当前目录、当前根、用户文件描述符表等。第四 步,把与父进程相连的文件表和索引节点表的引用数加1。这些文件自动地与该子进程相连。第五步,内核为子进程创建用户级上下文。内核为子进程的u区及辅助 页表分配内存,并复制父进程的区内容。这样生成的是进程的静态部分。第六步,生成进程的动态部分,内核复制父进程的上下文的第一层,即寄存器上下文和内核 栈,内核再为子进程虚设一个上下文层,这是为了子进程能“恢复”它的上下文。这时,该调用会对父进程返回子进程的pid,对子进程返回0。
  
   Linux系统的系统调用exit,是进程用来终止执行时调用的。进程发出该调用,内核就会释放该进程所占的资源,释放进程上下文所占的内存空间,保留 进程表项,将进程表项中纪录进程状态的字段设为僵死状态。内核在进程收到不可捕捉的信号时,会从内核内部调用exit,使得进程退出。父进程通过 wait得到其子进程的进程表项中纪录的计时数据,并释放进程表项。最后,内核使得进程1(init进程)接收终止执行的进程的所有子进程。如果有子进程 僵死,就向init进程发出一个SIGCHLD的软中断信号.
  
  一个进程通过调用wait来与它的子进程同步,如果发出调用的进程 没有子进程则返回一个错误,如果找到一个僵死的子进程就取子进程的PID及退出时提供给父进程的参数。如果有子进程,但没有僵死的子进程,发出调用的进程 就睡眠在一个可中断的级别上,直到收到一个子进程僵死(SIGCLD)的信号或其他信号。

  进程控制的另一个主要内容就是对其他程序引 用。该功能是通过系统调用exec来实现的,该调用将一个可执行的程序文件读入,代替发出调用的进程执行。内核读入程序文件的正文,清除原先进程的数据 区,清除原先用户软中断信号处理函数的地址,当exec调用返回时,进程执行新的正文。
  
  一个系统启动的过程,也称作是自举的过 程。该过程因机器的不同而有所差异。但该过程的目的对所有机器都相同:将操作系统装入内存并开始执行。计算机先由硬件将引导块的内容读到内存并执行,自举 块的程序将内核从文件系统中装入内存,并将控制转入内核的入口,内核开始运行。内核首先初始化它的数据结构,并将根文件系统安装到根“/”,为进程0形成 执行环境。设置好进程0的环境后,内核便作为进程0开始执行,并调用系统调用fork。因为这时进程0运行在内核状态,所以新的进程也运行在内核状态。新 的进程(进程1)创建自己的用户级上下文,设置并保存好用户寄存器上下文。这时,进程1就从内核状态返回用户状态执行从内核拷贝的代码(exec),并调 用exec执行/sbin/init程序。进程1通常称为初始化进程,它负责初始化新的进程。
  
  进程init除了产生新的进程外, 还负责一些使用户在系统上注册的进程。例如,进程init一般要产生一些getty的子进程来监视终端。如果一个终端被打开,getty子进程就要求在这 个终端上执行一个注册的过程,当成功注册后,执行一个shell程序,来使得用户与系统交互。同时,进程init 执行系统调用wait来监视子进程的死亡,以及由于父进程的退出而产生的孤儿进程的移交。以上是系统启动和进程init的一个粗略的模型。
  

1.3 进程调度的概念
  
  Linux系统是一个分时系统,内核给每个进程分一个时间片,该进程的时间片用完就会 调度另一个进程执行。LINUX系统上的调度程序属于多级反馈循环调度。该调度方法是,给一个进程分一个时间片,抢先一个运行超过时间片的进程,并把进程 反馈到若干优先级队列中的一个队列。进程在执行完之前,要经过这样多次反馈循环。
  
  进程调度分成两个部分,一个是调度的时机,即什 么时候调度;一个是调度的算法,即如何调度和调度哪个进程。我们先来看看调度的算法,假设目前内核要求进行调度,调度程序从“在内存中就绪”和“被抢先” 状态的进程中选择一个优先权最高的进程,如果有若干优先权一样高的进程,则在其中选择等待时间最长的进程。切换进程上下文,继续执行该进程。如果没有选择 到进程,则不做操作,等待下一次调度时机的到来。

  每一个进程都有一个用于调度的优先权域。进程的优先权由低到高粗略地分为用户优先权和内核优先权。每种优先权有若干优先权值(优先数)与其对 应。每个优先权都有一个逻辑上与其相连的进程队列。进程从内核状态返回用户状态时被抢先,从而得到用户优先权。进程在内核算法sleep中得到内核优先 权。内核优先权高于用户优先权,即内核优先权和用户优先权之间存在一个阀值,所有用户优先权低于该阀值,而内核优先权高于该阀值。内核优先权中又划分为可 中断和不可中断,即进程在收到一个软中断信号时,低内核优先权的进程可被唤醒,而有高内核优先权的进程继续睡眠。
  
  计算一个进程优 先权的时机是:内核将一个优先权值赋给一个将进入睡眠的进程,这个优先权值是固定的,且与睡眠原因相联系;另一个时机是,时钟处理程序每隔一定时间(如每 隔1秒)调整用户状态下的所有进程的优先权,并使内核运行调度算法。时钟处理程序还根据一个衰减函数,每秒一次的调整每个进程的最近CPU使用时间。例如 可按如下公式调整:
  
  decay(CPU) = CPU/2;
  
  再根据公式重新计算在“就绪”和“被抢先”状态下的每个进程的优先权。
  
  Priority = (“recent CPU usage”/constant) + (base priority) + (nice value);
  
   其中constant是个系统常量(一般取值为“2”)。base priority值也是系统的一个常量,一般base priority取值为60。最后,nice的值是由进程发出nice调用时给出的值,这样就可以使得用户通过降低优先权而让出一些执行时间。只有超级用 户才能指定提高优先权的nice值。

 
阅读更多
想对作者说点什么? 我来说一句

linux进程编程介绍

2008年10月13日 1.03MB 下载

linux socket、驱动、进程编程

2011年04月28日 12.5MB 下载

Linux进程编程介绍.rar

2009年04月12日 861KB 下载

Linux 进程编程介绍.zip

2008年12月30日 868KB 下载

Linux进程编程介绍

2007年12月22日 987KB 下载

linux 进程控制阅读笔记

2009年03月11日 18KB 下载

linux 初级 c编程

2010年02月12日 537KB 下载

没有更多推荐了,返回首页

不良信息举报

Linux进程编程介绍(一)

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭