【Linux】进程等待&程序替换

        一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着。        

        内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常退出则保存着导致该进程终止的信息是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量 $? 查看,因为Shell是它的父进程,当它终止时Shell调用 wait 或 waitpid得到它的退出状态同时彻底清除掉这个进程。

      当一个进程正常或异常终止时,内核就向其父进程发送一个SIGCHLD信号。因为子进程终止是一个异步事件,所以发生这种信号也是内核向父进程发的异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数。对于这种信号的系统默认动作是忽略它。

上述叙述中提到了文件描述符、wait或waitpid,下面进行说明

1、文件描述符

     三种标准输出流:stdin,stdout和stderr

         #include<stdio.h>

         extern FILE* stdin;  extern FILE* stdout; externa FILE* stderr;

         FILE*这种方式是由c语言(标准库)提供的;系统中任何一个程序跑起来都会默认得与这个程序关联3个标准输入输出流,分别是键盘、显示           器和显示器。

    1)文件标识符:0表示标准输入,1表示标准输出,2表示标准错误。

        下面程序进行说明

          

       2)文件描述符是从0开始的一连串的小整数,程序运行起来后形成一个进程,通过进程找到文件,默认打开3个文件0,1,2。之后打开的文件,文件             描述符从3开始进行递增。如图所示

         

       一旦关闭了某个文件描述符,则之后的文件描述符先为那个关闭文件描述符,接着从3开始递增。关闭0,即关闭标准输入,如图:

           

      3)系统调用在库函数的下层,库函数调用系统函数。

2、wait()或waitpid()函数

 头文件: 

            #include<sys/types.h>

            #include<sys/wait.h>

等待函数:

       1)pid_t wait(int *status);

           返回值:成功返回被等待进程pid,失败返回-1

           参数:输出型参数,获取进程退出状态,不关心则可以设置为NULL

           如果进程由于接收到SIGCHLD而调用wait,则可期望wait会立即返回;但如果在任意时刻调用wait,则进程可能阻塞。

           在一个子进程终止前, wait使其调用者堵塞,而waitpid有一个选项,可使调用者不堵塞。如果status不是空指针,则终止进程的终止状态就存放在它所指的单元内。如果不关心终止状态,则可将参数设置为空指针(waitpid同样适用)。

            

       2)pid_t waitpid(int *status);

            返回值:

                 a、当正常返回的时候waitpid返回收集到的子进程的进程id

                 b、如果设置了选项WNOHANG,而调用中waipid发现没有已退出的子进程可收集,则返回0

                 c、如果调用者出错,则返回-1,这是errno会被设置成相应的值以指示错误所在

                 d、当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD

            参数:

                 a、pid(进程id)

                       pid=-1;等待任一个子进程,与wait等效。

                       pid >0;等待其进程ID与pid相等的子进程。

                       pid == 0;等待其组id等于进程组id的任一个子进程。

                       pid <-1;等待其组ID等于pid绝对值的任一个子进程。

                 b、status(int型)

                       status的高8位(WIFEXITED(status)) :若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出
                       status的低8位 (WEXITSTATUS(status)) : 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

  

                 c、options(等待方式)

                      options为0:表示以阻塞方式进行等待,这种叫做阻塞式等待。

                      options为WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0(没有错误,也没有读取成功)不予以等待。若正常结                   束,则返回该子进程的ID。

  1 /************************************************************************* 
  2     > File Name: wait.c 
  3     > Author: ma6174 
  4     > Mail: ma6174@163.com  
  5     > Created Time: 2016年07月24日 星期日 19时57分23秒 
  6  ************************************************************************/ 
  7  
  8 #include<stdio.h> 
  9 #include<sys/types.h> 
 10 #include<sys/wait.h> 
 11 #include<stdlib.h> 
 12      
 13 int main() 
 14 { 
 15     pid_t id = fork(); 
 16     if(id < 0){ 
 17         printf("fork failure\n"); 
 18     } 
 19     else if(id == 0){ 
 20         printf("child, pid:%d\n",getpid()); 
 21         sleep(3); 
 22         //while(1);//会一直在等待子进程结束 
 23         printf("child deading...\n"); 
 24         exit(0); 
 25         //exit(123);//子进程的退出码 
 26         //exit(257);//异常退出,发生溢出 
 27     } 
 28     else{ 
 29         printf("father,pid:%d\n",getpid()); 
 30         //pid_t ret = wait(NULL);//不关心进程退出状态 
 31         //int status = 0; 
 32         //pid_t ret = waitpid(id, &status,0);//0表示默认以阻塞方式进行等待 
 33         while(1){ 
 34             pid_t ret = waitpid(id, NULL,WNOHANG); 
 35             printf("father wait return...\n"); 
 36             sleep(1); 
 37             if(ret == 0){//进程没有结束 
 38                 printf("child is running,Please wait\n"); 
 39             }//等待成功或失败就退出循环 
 40             else if(ret = id){//wait child success 
 41                 printf("wait success\n"); 
 42                 break; 
 43                 //printf("code is done? %d\n",status&0xff);//查看进程是否正常退出 
 44                //printf("exit code? %d\n",(status>>8)&0xff);//查看退出码 
 45             }else{ 
 46                 printf("wait failed\n"); 
 47                 break; 
 48             } 
 49             sleep(2);//休眠2秒,不会一直等待 
 50         } 
 51     } 
 52  
 53 }<span style="color:#cc0000;">

</span>

运行结果如下:


     3)检查wait和waitpid所返回的终止状态:

           a、利用按位与,得到static的前8位和后8位

           b、利用系统中的宏

              WIFEXITED(status) : 若为正常终止子进程返回的状态,则为真。

              WEXITSTATUS(status) : 若WIFEXITED非零,返回子进程退出码,提取进程退出返回值,如果子进程调exit(7),WEXITSTATUS(status就会返        回7请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫无意义。

       说明:status 并不简简单单是一个整形变量,父进程和子进程之间所有的状态交互都要通过这个int来表示,所以这个int的若干bit位都是有特殊的含义的,那么这个“int”如何编码就比较重要了,和IP地址一样,它是比较紧凑的,或者说是比较拥挤的。

       status指出了子进程是正常退出还是被非正常结束的(一个进程也可以被其他进程用信号结束),以及正常结束时的返回值,或被哪一个信号结束或进程的退出码是多少等信息,这些信息都被放在整数的不同二进制位中,所以用常规的方法读取会非常麻烦,所以开发者就设计了一套专门的宏(macro)来完成这项工作。

        4)wait和waitpid的作用:

           a、等待目的:子进程读取到子进程退出的一个状态信息

           b、让系统释放掉子进程占有的僵尸状态的资源

           c、运行时不保证父子进程谁先运行,可保证子进程先退出,等待使父子进程的退出同步起来(即退出产生一定的顺序)

        5)waitpid提供了wait没有提供的功能:

           a、waitpid可等待一个特定的进程

           b、waitpid提供了一个wait的非阻塞版本

           c、waitpid支持作业控制

3、进程程序替换

        用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

1)其实有6种以exec开头的函数,统称为exec函数:

#incldue<unistd.h>

int execl(const char* path,const char* arg,....);

int execlp(const char* file,const char* arg,....);

int execle(const char* path,const char* arg,..., char* const emp[]);

int execv(const char* path,char* const argv[]);

int execp(const char* file,char* const argv[]);

int execve(const char* path,char* const argv[],char* const emp[]);

这些函数如果调用成功,则加载新的程序从启动代码开始执行,不再返回。

如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。

2)这些函数的规则:

      不带字母p (表示path)的exec函数 第一个参数必须是程序的相对路径或绝对路径,例如"/bin/ls"或"./a.out",而不能 是"ls"或"a.out"。对于带字母p的函数: 如果参数中包含/,则将其视为路径名。 否则视为不带路径的程序名,在PATH环境变量的目录列表中搜索这个程序。
      带有字母l( 表示list)的exec函数要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,因此函数原型中有...,...中的最后一个可变参数应该是NULL, 起sentinel的作用。
      带有字母v( 表示vector)的函数,则应该先构造一个指向各参数的指针数 组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是NULL,就像main函数的argv参数或者环境变量表一样。
      对于以e (表示environment)结尾的exec函数,可以把一份新的环境变量表传给它,其他exec函数仍使用当前的环境变量表执行新程序。


上述进程用程序替换执行如下:



Linux下的open()、close()、write()和read()函数

 1. open()函数
功能描述:用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。
所需头文件:#include <sys/types.h>,#include <sys/stat.h>,#include <fcntl.h>
函数原型:int open(const char *pathname,int flags,int perms)
参数:
pathname:被打开的文件名(可包括路径名如"dev/ttyS0")
flags:文件打开方式,
O_RDONLY:以只读方式打开文件
O_WRONLY:以只写方式打开文件
O_RDWR:以读写方式打开文件
O_CREAT:如果改文件不存在,就创建一个新的文件,并用第三个参数为其设置权限
O_EXCL:如果使用O_CREAT时文件存在,则返回错误消息。这一参数可测试文件是否存在。此时open是原子操作,防止多个进程同时创建同一个文件
O_NOCTTY:使用本参数时,若文件为终端,那么该终端不会成为调用open()的那个进程的控制终端
O_TRUNC:若文件已经存在,那么会删除文件中的全部原有数据,并且设置文件大小为0
O_APPEND:以添加方式打开文件,在打开文件的同时,文件指针指向文件的末尾,即将写入的数据添加到文件的末尾
O_NONBLOCK: 如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I/O操作设置非阻塞方式。
O_SYNC:使每次write都等到物理I/O操作完成。
O_RSYNC:read 等待所有写入同一区域的写操作完成后再进行
在open()函数中,falgs参数可以通过“|”组合构成,但前3个标准常量(O_RDONLY,O_WRONLY,和O_RDWR)不能互相组合。
perms:被打开文件的存取权限,可以用两种方法表示,可以用一组宏定义:S_I(R/W/X)(USR/GRP/OTH),其中R/W/X表示读写执行权限,
USR/GRP/OTH分别表示文件的所有者/文件所属组/其他用户,如S_IRUUR|S_IWUUR|S_IXUUR,(-rex------),也可用八进制800表示同样的权限
返回值:
成功:返回文件描述符
失败:返回-1
 
2. close()函数
功能描述:用于关闭一个被打开的的文件
所需头文件: #include <unistd.h>
函数原型:int close(int fd)
参数:fd文件描述符
函数返回值:0成功,-1出错 
 
3. read()函数
功能描述: 从文件读取数据。
所需头文件: #include <unistd.h>
函数原型:ssize_t read(int fd, void *buf, size_t count);
参数:  
fd: 将要读取数据的文件描述词。
buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。
count: 表示调用一次read操作,应该读多少数量的字符。
返回值:返回所读取的字节数;0(读到EOF);-1(出错)。
以下几种情况会导致读取到的字节数小于 count :
    A. 读取普通文件时,读到文件末尾还不够 count 字节。例如:如果文件只有 30 字节,而我们想读取 100
字节,那么实际读到的只有 30 字节,read 函数返回 30 。此时再使用 read 函数作用于这个文件会导致 read 返回 0 。
    B. 从终端设备(terminal device)读取时,一般情况下每次只能读取一行。
    C. 从网络读取时,网络缓存可能导致读取的字节数小于 count字节。
    D. 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 count 。
    E. 从面向记录(record-oriented)的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。
    F. 在读取了部分数据时被信号中断。
读操作始于 cfo 。在成功返回之前,cfo 增加,增量为实际读取到的字节数。
 
4. write()函数
功能描述: 向文件写入数据。
所需头文件: #include <unistd.h>
函数原型:ssize_t write(int fd, void *buf, size_t count);
返回值:写入文件的字节数(成功);-1(出错)
功能:write 函数向 filedes 中写入 count 字节数据,数据来源为 buf 。返回值一般总是等于 count,否则就是出错了。常见的出错原因是磁盘空间满了或者超过了文件大小限制。
对于普通文件,写操作始于 cfo 。如果打开文件时使用了 O_APPEND,则每次写操作都将数据写入文件末尾。成功写入后,cfo 增加,增量为实际写入的字节数。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值