linux c 笔记 进程控制(三)

进程退出
    进程结束表示进程即将结束运行,在linux系统中进程的退出方法分为正常退出和异常退出两种.
exit函数
进程有三种正常终止法及两种异常终止法。
(1) 正常终止:
    (a) 在main函数内执行return语句。这等效于调用 exit。
    (b) 调用exit函数。此函数由ANSI C定义,其操作包括调用各终止处理程序(终止处理程序在调用atexit函数时登录),然后关闭所有标准I/O流等。因为ANSI C并不处理文件描述符、多进程(父、子进程)以及作业控制,所以这一定义对 UNIX系统而言是不完整的。
    (c) 调用_exit系统调用函数。此函数由 exit调用,它处理 UNIX特定的细节。
(2) 异常终止:
    (a) 调用abort。它产生SIGABRT信号,所以是下一种异常终止的一种特例。
    (b) 当进程接收到某个信号时,而该信号使程序终止。进程本身(例如调用abort函数)、其他进程和内核都能产生传送到某一进程的信号。例如,进程越出其地址空间访问存储单元,或者除以 0,内核就会为该进程产生相应的信号。
    不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等等。

对退出方式的比较:
    exit和return 的区别:exit 是一个函数,有参数,return 是函数执行完毕后的返回,exit把控制权交给系统,而return将控制权交给调用函数
    exit和about的区别:exit是正常终止程序,about 是异常终止程序
    exit( int exit_code ):exit中的参数exit_code 为0代表进程正常终止,若为其他值表示程序执行过程中有错误发生,比如:溢出,除数为0.
    exit()和_exit()的区别:exit在头文件 stdlib.h中声明,而_exit()声明在头文件unistd.h中,两个函数均能正常终止进程,但是_exit()会执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核.

    对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于exit和_exit,这是依靠传递给它们的退出状态( exit status)参数来实现的。在异常终止情况,内核(不是进程本身)产生一个指示其异常终止原因的终止状态( termination status)。在任意一种情况下,该终止进程的父进程都能用 wait或waitpid函数得其终止状态。
注意,这里使用了“退出状态”(它是传向exit或_exit的参数,或main的返回值)和“终止状态”两个术语,以表示有所区别。在最后调用 _exit时内核将其退出状态转换成终止状态。明了父进程检查子进程的终止状态的不同方法。如果子进程正常终止,则父进程可以获得子进程的退出状态。

在说明fork函数时,一定是一个父进程生成一个子进程。上面又说明了子进程将其终止状态返回给父进程。但是如果父进程在子进程之前终止,对于其父进程已经终止的所有进程,它们的父进程都改变为 init进程。我们称这些进程由 init进程领养。其操作过程大致是:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止的进程的子进程,如果是,则该进程的父进程 ID就更改为1 ( init进程的I D )。这种处理方法保证了每进程有一个父进程。
  
另一个我们关心的情况是如果子进程在父进程之前终止,那么父进程又如何能在做相应检查时得到子进程的终止状态呢?对此问题的回答是内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用 wait或 waitpid 时,可以得到有关信息。这种信息至少包括进程ID、该进程的终止状态、以反该进程使用的 CPU 时间总量。内核可以释放终止进程所使用的所有存储器,关闭其所有打开文件。在 UNIX 术语中,一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵死进程( zombie)。如果编写一个长期运行的程序,它fork了很多子进程,那么除非父进程等待取得子进程的终止状态,否则这些子进程就会变成僵死进程。


执行新程序:
exec函数
用fork函数创建子进程后,子进程往往要调用一种 exec函数以执行另一个程序。当进程调用一种 exec函数时,该进程完全由新程序代换,而新程序则从其 main函数开始执行。
因为调用exec并不创建新进程,所以前后的进程 ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。
有六种不同的exec函数可供使用,它们常常被统称为 exec函数。这些exec函数都是 UNIX 进程控制原语。用fork可以创建新进程,用 exec可以执行新的程序。 exit函数和两个wait函数处理
终止和等待终止。这些是我们需要的基本的进程控制原语。在后面各节中将使用这些原语构造另外一些如popen和system之类的函数。
#include <unistd.h>
int execl ( const char * pathname, const char  *arg 0, . . . / * ( char * ) 0 * / ) ;
int execv ( const char *pathname, char * const argv [] );
int execle ( const char * pathname, const char  * arg 0, ...);
int execve ( const char * pathname, char * const argv [ ] , char * const envp [] );
int execlp ( const char * filename, const char  *arg 0, . . . / * ( char * ) 0 * / ) ;
int execvp ( const charfile * name, char * const argv [] );
六个函数返回:若出错则为- 1,若成功则不返回


这些函数之间的第一个区别是前四个取路径名作为参数,后两个则取文件名作为参数。当
指定filename作为参数时:
• 如果filename中包含/,则就将其视为路径名。
• 否则就按PATH环境变量,在有关目录中搜寻可执行文件。
PATH变量包含了一张目录表 (称为路径前缀 ),目录之间用冒号 ( : )分隔。例如下列 name = value环境字符串:PATH=/bin:/usr/bin:/usr/local/bin :.
指定在四个目录中进行搜索。( 零长前缀也表示当前目录。在 value的开始处可用:表示,在行中间则要用::表示,在行尾以:表示。)

如果execlp和execvp中的任意一个使用路径前缀中的一个找到了一个可执行文件,但是该文件不是由连接编辑程序产生的机器可执行代码文件,则就认为该文件是一个 shell脚本,于是试着调用/bin/sh,并以该filename作为shell的输入。
第二个区别与参数表的传递有关 (  l表示表( list ),v表示矢量 ( vector )  )。函数execl、execlp和execle要求将新程序的每个命令行参数都说明为一个单独的参数。这种参数表以空指针结尾。
对于另外三个函数( execv, execvp和execve ),则应先构造一个指向各参数的指针数组,然后将该数组地址作为这三个函数的参数。
在使用ANSI C 原型之前,对execl , execle和execlp三个函数表示命令行参数的一般方法是:char * arg 0, char * arg 1, . . . , char  * arg n, ( char * ) 0
应当特别指出的是:在最后一个命令行参数之后跟了一个空指针。如果用常数 0来表示一个空指针,则必须将它强制转换为一个字符指针,否则它将被解释为整型参数。如果一个整型数的长度与char * 的长度不同,exec函数实际参数就将出错。
最后一个区别与向新程序传递环境表相关。以 e 结尾的两个函数( execle和 execve)可以传递一个指向环境字符串指针数组的指针。其他四个函数则使用调用进程中的
environ变量为新程序复制现存的环境。通常,一个进程允许将其环境传播给其子进程,但有时也有这种情况,进程想要为子进程指定一个确定的环境。例如,在初始化一个新登录的 shell时, login程序创建一个只定义少数几个变量的特殊环境,而在我们登录时,可以通过 shell起动文件,将其他变量加到环境中


环境变量
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,如:临时文件夹位置和系统文件夹位置等。
环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或者多个应用程序所将使用到的信息。例如Windows和DOS操作系统中的path环境变量,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下面寻找此程序外,还应到path中指定的路径去找。用户通过设置环境变量,来更好的运行进程。
如同前述,环境字符串的形式是:
name = value
UNIX内核并不关心这种字符串的意义,它们的解释完全取决于各个应用程序。例如, shell使用了大量的环境变量。其中某一些在登录时自动设置(如 HOME,USER等),有些则由用户设置。我们通常在一个 shell起动文件中设置环境变量以控制 shell的动作。例如,若设置了环境变量 MAILPATH,则它告诉Bourne shell和KornShell到哪里去查看邮件。
ANSI C定义了一个函数getenv,可以用其取环境变量值,但是该标准又称环境的内容是由实现定义的。
#include <stdlib.h>
char * getenv ( const char *  name) ;
返回:指向与 name关联的value的指针,若未找到则为 NULL
注意,此函数返回一个指针,它指向 name = value字符串中的value。我们应当使用 getenv 从环境中取一个环境变量的值,而不是直接存取 environ。


execv函数:execv函数是通过路径名的方式调用可执行文件作为新的进程映像.他的argv参数用来提供给main函数的argv参数.argv参数是一个以NULL结尾(最后一个元素必须是一个空指针)的字符串数组
execve函数:在该系统中,参数path是将要执行的程序的路径名,参数argv,envp与main函数的argv,envp对应.
execl函数:该函数与execl函数的用法相似,只是在传参数的时候(参数中使用"..."说明参数的个数是不确定的),要注意的是这些参数要以一个空指针作为结束
execl函数:该函数与execl函数的用法类似,只是要显式指定环境变量.环境变量位于命令行参数最后一个参数的后面,也就是空指针之后.
execvp函数:该函数和execv函数的用法类似,不同的是参数filename. 该参会素如果包含"/",就相当于路径名,如果不包含"/",函数就到PATH环境变量定义的目录中寻找可执行的文件,PATH环境变量的目录之间以冒号隔开.
execlp函数:该函数类似于execl函数,他们的区别和execvp 和execv的区别一样.

exec函数族的6个函数中只有execve是系统调用,其他五个都是库函数,它们实现时都调用了execve




执行了新程序后的进程除了保存了原有的进程ID,父进程ID,实际用户ID,和实际组ID之外,进程还保持了许多原有的特征,主要有:
    .当前的工作目录
    .根目录
    .创建文件是使用的屏蔽字
    .进程信号屏蔽字
    .未决警告
    .和进程相关的使用处理器的时间
    .控制终端
    .文件锁

 

等待进程结束
在子进程先于父进程退出时,如果父进程没有调用wait 和waitpid函数,子进程就会进入僵死状态.如果父进程调用了wait 和waitpid函数,就不会使子进程变成僵尸进程,这
当一个进程正常或异常终止时,内核就向其父进程发送 SIGCHLD信号。因为子进程终止是个异步事件 (这可以在父进程运行的任何时候发生 ),所以这种信号也是内核向父进程发的异步通知。父进程可以忽略该信号,或者提供一个该信号发生时即被调用执行的函数 (信号处理程序)。对于这种信号的系统默认动作是忽略它。调用wait或waitpid的进程可能会:
• 阻塞(如果其所有子进程都还在运行 )。
• 带子进程的终止状态立即返回 (如果一个子进程已终止,正等待父进程存取其终止状态 )。
• 出错立即返回(如果它没有任何子进程)。
如果进程由于接收到 SIGCHLD信号而调用 wait,则可期望wait会立即返回。但是如果在一任一时刻调用 wait,则进程可能会阻塞。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait ( int *statloc) ;
pid_t waitpid ( pid_t pid, int * statloc, int options) ;
两个函数返回:若成功则为进程 ID,若出错则为- 1

• 在一个子进程终止前, wait 使其调用者阻塞,而 waitpid 有一选择项,可使调用者不阻塞。
• waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的进程。

如果一个子进程已经终止,是一个僵死进程,则 wait立即返回并取得该子进程的状态,否则w a i t使其调用者阻塞直到一个子进程终止。如调用者阻塞而且它有多个子进程,则在其一个子进程终止时, wait就立即返回。因为 wait返回终止子进程的进程 ID,所以它总能了解是哪一个子进程终止了。

这两个函数的参数 statloc是一个整型指针。如果 statloc不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针。
依据传统,这两个函数返回的整型状态字是由实现定义的。其中某些位表示退出状态(正常返回),其他位则指示信号编号(异常返回),有一位指示是否产生了一个 core文件等等。







对于waitpid的pid参数的解释与其值有关:
• pid == -1 等待任一子进程。于是在这一功能方面 waitpid与wait等效。
• pid > 0 等待其进程ID与pid相等的子进程。
• pid == 0 等待其组ID等于调用进程的组I D的任一子进程。
• pid < -1 等待其组ID等于pid的绝对值的任一子进程。
 waitpid返回终止子进程的进程 ID,而该子进程的终止状态则通过 statloc返回。对于wait,其唯一的出错是调用进程没有子进程 (函数调用被一个信号中断时,也可能返回另一种出错。但是对于 waitpid,如果指定的进程或进程组不存在,或者调用进程没有子进程都能出错。
options参数使我们能进一步控制 waitpid的操作。此参数或者是 0,或者是表8 - 2中常数的逐
位或运算。

 

waitpid函数提供了wait函数没有提供的三个功能:
(1) waitpid等待一个特定的进程 (而wait则返回任一终止子进程的状态 )。
(2) waitpid提供了一个wait的非阻塞版本。有时希望取得一个子进程的状态,但不想阻塞。
(3) waitpid支持作业控制(以 WUNTRACED选择项)。

 

posted @ 2015-07-28 08:38  kaylee  阅读(275)  评论(0编辑  收藏  举报