Unix下C语言----进程控制

来源:互联网 发布:node中res.query 编辑:程序博客网 时间:2024/06/03 22:38

 一、进程基本环境

  进程是程序的一次运行,是运行在自己的虚拟地址空间的一个具有独立功能的程序。进程是分配和释放资源的基本单位,当程序执行时,系统创建进程,分配内存和CPU等资源;进程结束时,系统回收这些资源。

 

 

  1进程概念

     进程通常由程序、数据和进程控制块(PCB)三个部分组成的。其中,程序部分描述了进程锁要完成的功能;数据部分为进程提供了运行所需的堆栈和私有数据;进程控制块则含有进程的描述和控制信息,几种反应进程的动态特性,是系统识别和控制进程的依据。

 

  1)进程与程序

     进程是程序的一次执行,故程序是一个静态的概念,本身可以作为一种软件资源长期保存;而进程是程序的执行过程,是动态概念,有一定的生命周期,具有产生、发展和消亡的过程。

     程序是一个规划,它计划了所要执行的代码和所要完成的动作;进程是一位执行者,它依照程序的设计,申请资源,计算数据,并完成预定的任务。一个程序可以同时由多个进程执行;一个进程也可以有顺序地执行若干程序。

    进程不能脱离具体程序而存在,程序规定了相应进程所要完成的动作;程序不能脱离进程而应用,进程将程序的设想变成可能。

 

  2)进程与线程

    线程又名轻负荷进程,它是在进程基础上程序的一次执行,一个进程可以拥有多个线程。

    线程没有独立的资源,它共享进程的ID,共享进程的资源。

    线程是UNIX最小的调度单位,目前有系统级调度和进程级调度两种线程调度实行方式:系统级调度的操作系统以线程为单位进程调度;进程级调度的操作系统以仍以进程为单位进行调度,进程再为其上运行的线程提供调度控制。

 

  3)前台进程和后台进程

    用户在Shell提示符处键入命令,创建进程,此后该进程接管终端,Shell中不能再输入其它的命令,知道进程执行完毕,才将终端控制权返回Shell并显示用户提示符。这样子的进程是前台进程。

   如果用户在输入Shell命令时,在命令串上加上"&",于是就可以同时运行进程和执行Shell操作。这样子的进程就是后台进程,后台进程并不接管终端,因此必须是非交互式的。

 

  4)守护进程

    守护进程是与终端无关,常住后台执行的特殊进程。UNIX中最著名的2个守护进程是:

sysproc进程:UNIX的第一个进程,标志号为0,能合理地调度系统中运行的进程,负责将进程从硬件交换区调入内存(换入)或将进程从内存调到硬盘交换区(换出)。

init进程:系统初始化进程,进程标志号为1,是除sysproc外所有进程的祖先。

 

 

  5)父子进程

    进程采用树型结构管理。当一个进程启动另一个进程时,被启动的进程就是子进程,原进程就是父进程。在UNIX中,使用系统调用fork创建进程。fork复制了父进程的数据、堆栈段和进程环境,因此,子进程继承了父进程的某些环境,并且共享父进程的代码段。但子进程也拥有自己的环境,父子进程以并行的执行同一程序的不同分支。

 

  6)进程状态

    进程在运行过程中具有运行状态、就绪状态和睡眠状态。

    运行态:进程已经分配到CPU,正在处理机上执行时的装填。同一时刻处于运行态的进程不能大于CPU的数目。运行态又可以细分用户态和和心态两种。用户态:如果进程正在运行用户的代码,进程就处于用户态。核心态:如果进程出现系统调用或系统中断,运行操作系统的内部代码,进程就处于和心态。

    就绪态:进程已具备运行条件,但是其它进程正占用CPU,所以暂时不能运行而等待分配CPU的状态。处于就绪态的进程一旦获得了CPU,就能立刻运行。

    睡眠态:进程正在等待某种时间的发生而暂时不能运行的状态。处于睡眠态的进程尚不具备运行条件,即使CPU空闲,它也无法使用。

 

  7)进程调度

    UNIX系统的机场南横调度属于多级反馈循环调度,它的每个进程都有一个优先级,级别高的进程优先执行,级别低的进程延后执行,相同级别的进程循环调度。默认情况下,系统可以动态地调整正在运行中进程的优先级,以提供最佳相应时间;同时也允许用户自主更改进程的优先级别。

 

 

2.进程的标识

 1)进程的标识号

    UNIX中的每个进程都具有唯一的标志号,操作系统采用一个非负整数标识每个进程。进程启动时,系统为进程分配标志号(进程ID);进程终中止后,标志号可以重新使用。但任意时刻,一个标志号只对应一个进程。比如0代表sysproc进程,1代表init进程。

    UNIX中读取进程ID号的函数如下:

   

 

  2)进程的用户标识号

    当进程由用户启动时,该用户的标识号就是进程的实际用户标识,该用户的组标志号就是进程的实际组标识号。

    进程在操作文件时,都要进程权限检查,但进程的实际用户标识号和实际组标识号仅用于系统记账,参与权限检查的是进程的有效用户标识号和有效组标识号。一般情况下,实际标识号和有效标准号相一致,但系统允许更改进程的有效标识号。

    UNIX中读取进程用户ID号的函数如下:

   

 

 

  3.进程命令行

    每个完整的C程序都是从main函数开始执行的,其原型如下:

    int main();

    int main(int argc,char  *argv[]);

    其中命令行参数是在启动程序执行时,在Shell中以空白分开的字符串。main函数的参数传递运行程序的命令行参数,其中整型argc记载了命令行的参数个数,指针数组argv[]给出了命令行参数的内容,它的每一个元素指向一个iecunchu命令行参数的字符串。

ex:

编译和运行arg1.c

 

 

4.环境变量

  环境变量中记载了很多可供程序共享或者不经常改变的信息。UNIX下C程序中有2中获取环境变量值的方法:全局变量法和函数调用法。

  1)全局变量法

  UNIX系统中采用了一个指针数组来存储全部环境值:

  extern char **environ;

  ex:

 

  2)函数调用法

  UNIX中操作环境变量的函数如下:

  #include<stdlib.h>

  char *getenv(char *name);

  int putenv(const char *string);

  getenv以字符串形式返回环境变量name的值,如果参数name为NULL或者环境列表中无name变量,返回NULL。

  函数putenv增加、修改或删除环境变量。参数string指向一个字符串,格式为:“name=value”。

二、进程的生命周期

  1.进程的创建

    在UNIX中,系统调用fork是创建新进程的方法,它的原型为:

    #include<unistd.h>

    pid_t fork();

    调用fork创建的子进程,将共享父进程的代码空间,复制父进程数据空间,如堆栈等。此时子进程会获得父进程中的所有变量的一份拷贝,尽管父子进程中的变量名相同,但却存储在内存中的不同地方,因此不能奢望通过内存变量完成父子进程之间的消息传递,此变量非彼变量。

    如果fork调用失败,返回-1,如果调用成功,则在父进程中返回子进程标识号,在子进程返回0

 

 【注:】子进程可以在通过getpid得到自身的进程标志号,通过getppid得到父进程标志号。但父进程无法获取子进程的标志号,故fork成功时在父进程中返回创建的子进程的标志号,可以弥补这一缺陷。

 

  以下设了一个创建子进程的例子:

 

 

  【注】创建子进程主要应用两个方面:

   (1)复制代码,父子进程执行不同的代码段。这种情况在网络服务程序中最常见:父进程侦听,当socket收到连接请求后创建子进程,然后继续侦听。子进程负责建立socket连接、接收通信内容并执行处理过程。

   (2)执行新程序。子进程被创建后,离开调用exec命令执行别的程序。

 

 2.新程序的执行

   在shell中如何执行程序?下面以ls命令的执行为例说明:

   --shell进程接收用户输入的命令"ls"

   --shelle创建子进程,并进入等待状态。

   --子进程读取程序"ls"的代码,替换从父进程继承的执行代码

   --子进程执行"ls"程序

   --进程"ls"执行完毕,子进程退出。

   --shell被激活,重新接管终端,处理用户交互式输入

 

  1)exec函数族

  UNIX提供一组替换进程执行代码的函数,它们的原型如下:

 

exec函数组调用成功时,系统把一个新程序的地址空间代替调用进程的地址空间并装入新程序的内容,同时忽略原exec函数的返回,不执行exec之后的所有语句,系统将从新程序的main函数处开始执行。如果exec函数族调用失败,将返回-1。

2)fork-exec 模型

函数族exec经常应用在子进程中,因此常与函数fork连用,如下:

 

 

 3)vfork-exec 模型

  在大多数情况下,fork-exec模型在创建子进程后立即执行新程序,并不是用父进程的数据,因此UNIX提供了一个更好的vfork,它的原型如下:

#include<unistd.h>

pid_t vfork();

函数vfork与fork在功能和调用形式上类似,也能创建子进程,但二者有如下区别:

(1)Vfork创建的紫禁城并不复制父进程的数据,在随后的exec调用中系统会复制新程序的数据到内存中,继而避免了一次数据复制过程。

(2)父进程以vfork方式创建子进程后将被阻塞,直到子进程退出或执行exec调用才继续运行。

当子进程只有来执行新程序时,vfork-exec模型比fork-exec模型具有更高的效率,这种方法也正是shell创建新进程的方式。

 

(4)system模型

在UNIX中,我们也可以使用system函数完成新程序的执行,其原型如下:

#include<stdlib.h>

int system(char *string);

函数system阻塞调用它的进程,并执行字符串string的shell命令,它可以理解为vfork-exec模型的交易表达方式:

 

 

if(vfork()==0)

{

  execl("/bin/ls","-C",string,0);

  exit(0);

}

 

system调用成功,返回执行命令的shell状态,或者返回-1或其他错误。

 

3.进程的休眠

函数sleep使调用进程进入休眠状态,其原型如下:

#include<unistd.h>

unsigned int sleep(unsigned int seconds);

进程调用函数sleep后休眠seconds妙,直到时间结束或收到不可忽略信号为止。

 

4.进程的终止

  UNIX中,终止进程的函数如下:

  #include<unistd.h>

  void exit(int status);

  进程调用函数exit终止自己的运行,并且释放所占的系统资源。参数status的低8位记载了进程的终止状态,进程终止后将这个状态返回它的父进程。

 

5.进程的同步

系统调用wait可以实现父子进程之间的同步,其原型如下:

#include<sys/types.h>

#include<sys/wait.h>

pid_t wait(int *status);

pid_t waitpid(pid_t pid,int *status, int options);

函数wait挂起调用它的进程,知道它的任一子进程退出或者收到一个不能忽略的的信号位置,如果父进程执行wait调用前就已经有子进程退出,则立即返回。函数成功调用后返回结束运行子进程的ID,否则返回-1。参数status返回子进程退出的状态。

本处设计了一个进程同步的例子,父进程fork一个子进程后进入wait阻塞,等待子进程运行结束并打印子进程结束状态。如下:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

原创粉丝点击