进程等待和进程终止

进程终止
1》进程退出场景

(a)程序运行完毕,结果正确
(b)程序运行完毕,结果不正确
(c)代码异常终止

2》进程退出方法
(1)正常退出

(a)在main函数内执行return语句
(b)调用exit
(c)调用_exit或_Exit函数

(2)异常退出

(a) 调用abort。
(b)当进程接收到某些信号时。
(c) 最后一个线程对“取消”做出相应。

不管进程如何终止,最后都会执行进程内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。

一般情况下,我们希望将终止进程的终止状态告知其父进程。对于以上exit,_exit,_Exit等函数,它将其退出状态作为参数传送给函数; 在异常终止情况,内核产生一个指示其异常终止原因的终止状态(在最后调用_exit时,内核将退出状态转换成终止状态。)

另一种情况,如果父进程在子进程之前终止,对于父进程已经终止的所有进程,它们的父进程都改变为init进程(孤儿状态之前已详述);如果子进程在父进程之前终止,内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。这些信息至少包含进程ID,该进程终止状态以及该进程使用的CPU时间总量。
3》退出函数
(1)_exit
_exit是linux系统调用,关闭所有文件描述符,然后退出进程。

#include<unistd.h>
void _exit(int status);

参数status定义了进程的终止状态,父进程通过wait来获取该值。

(2)exit
exit是C语言的库函数,他最终也调用_exit。

#include<unistd.h>
void exit(int status);

工作流程
1.执行用户通过alexit或on_exit定义的清理函数。
2.关闭所有打开的流,所有的缓存数据均被写入。
3.调用_exit

对比演示

[a@localhost ~]$ vim exit.c
[a@localhost ~]$ cat exit.c
#include<stdio.h>
int main()
{
    printf("hello");
    exit(0);
}
[a@localhost ~]$ ./a.out
hello[a@localhost ~]$ 

[a@localhost ~]$ vim _exit.c
[a@localhost ~]$ cat _exit.c
#include<stdio.h>
int main()
{
    printf("hello");
    _exit(0);
}

[a@localhost ~]$ ./a.out
[a@localhost ~]$ 

(3)return
return退出是一种更常见的进程退出方法,等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做exit的参数。

进程等待
目的:父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息,避免子进程进入僵尸状态。
调用wait或waitpid的进程可能会发生的情况:

(1)如果其所有进程都在运行,则阻塞。
(2)如果一个子进程已终止,正等待其父进程获取其终止状态,则取得该子进程的终止状态立即返回。
(3)如果没有任何子进程,则立即出错返回。

wait和waitpid函数

pid_t wait(int* status);

返回值:成功返回被等待进程pid,失败返回-1.
参数:status是一个整形指针,若不为空则终止进程的终止状态就存放在它所指向的单元,不关心则可以设置为NULL

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

返回值:正常返回的时候waitpid返回收集到的子进程的进程ID;调用中出错返回-1.
参数:

(1)pid pid=-1:等待任一个子进程,与wait等效。 pid>0:等待其进程ID与pid相等的子进程
(2)status WIFEXITED(status):查看进程是否是正常退出 WEXITSTATUS(status):查看进程的退出码
(3)options WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID.

waitpid函数相比wait函数的优势:
(1)waitpid可等待一个特定的进程,而wait则返回任一终止子进程的状态。
(2)waitpid提供了wait的非阻塞版本。
(3)waitpid通过WUNTRACED 和WCONTINUED选项支持作业控制。

进程的阻塞等待方式:

#include<unistd.h>
#include<stdio.h>
#include<sys/wait.h>
int main()
{
    pid_t pid;
    pid = fork();
    if(pid < 0)
    {
      printf("%s fork error\n");
    }
    else if(pid == 0)
    {
      printf("child is run,pid is:%d\n",getpid());
      sleep(5);
      exit(1);
    }
    else
    {
      int status = 0;
      pid_t ret = waitpid(-1,&status,0);//阻塞式等待,等待5printf("this is test for wait\n");
      if(WIFEXITED(status) && ret == pid)
      {
        printf("wait child 5s success,child return code is:%d.\n",WEXITSTATUS(status));
      }
      else
      {
         printf("wait child failed,return.\n");
             return 1;
           }
         } 
    return 0;
}
[a@localhost ~]$ ./a.out
child is run,pid is:4930
this is test for wait
wait child 5s success,child return code is:1.
[a@localhost ~]$ 

进程的非阻塞等待方式:

[a@localhost ~]$ cat test.c
#include<unistd.h>  
#include<stdlib.h>  
#include<sys/wait.h>  
int main()  
{  
    pid_t pid=fork();  
    if(pid<0){  
        printf("fork is error\n");  
        return 1;  
    }else if(pid==0){//child  
        printf("child is run,pid is:%d\n",getpid());  
        sleep(5);  
        exit(1);  
    }  
    else{  
        int status=0;  
        pid_t ret=0;  
        do{  
            ret=waitpid(-1,&status,WNOHANG); //非阻塞式等待
            if(ret==0){  
                printf("child is running\n");  
            }  
            sleep(1);  
        }while(ret==0);  
        if(WIFEXITED(status) && ret==pid){  
            printf("wait child 5s success,child return code is:%d\n",  
                    WEXITSTATUS(status));  
        }else{  
            printf("wait child failed,return.\n");  
            return 1;  
        }  
    }  
    return 0;  
}  
[a@localhost ~]$ ./a.out
child is running
child is run,pid is:4988
child is running
child is running
child is running
child is running
wait child 5s success,child return code is:1
[a@localhost ~]$ 

system/popen
1》system在其实现中调用了fork,exec和waitpid因此有三种返回值。
(1)fork失败或者waitpid返回除EINTR之外的出错,则system返回-1,并设置errno以指示错误类型。
(2)如果exec失败,则其返回值如同shell执行了exit(127)一样。
(3)否则所有3个函数都成功,那么system的返回值是shell的终止状态。
源码:

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<sys/wait.h>
int system(const char * cmdstring)
{
    pid_t pid;
    int status;
    if(cmdstring == NULL){         
         return (1);
    }

    if((pid = fork())<0){
            status = -1;//fork失败,返回-1
    }
    else if(pid = 0){
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127); //execl执行失败返回127
        }
    else{
            while(waitpid(pid, &status, 0) < 0){
                if(errno != EINTR){
                    status = -1;
                    break;
                }
            }
        }
        return status;
}

system的原理: 当system接受的命令为NULL时直接返回,否则fork出一个子进程,因为fork在两个进程:父进程和子进程中都返回,这里要检查返回的pid,fork在子进程中返回0,在父进程中返回子进程的pid,父进程使用waitpid等待子进程结束,子进程则是调用execl来启动一个程序代替自己,execl(“/bin/sh”, “sh”, “-c”, cmdstring, (char*)0)是调用shell,这个shell的路径是/bin/sh,后面的字符串都是参数,然后子进程就变成了一个shell进程,这个shell的参数是cmdstring,就是system接受的参数。
对比fork原理:当一个进程A调用fork时,系统内核创建一个新的进程B,并将A的内存映像复制到B的进程空间中,因为A和B是一样的,那么他们怎么知道自己是父进程还是子进程呢,看fork的返回值就知道,上面也说了fork在子进程中返回0,在父进程中返回子进程的pid。
popen

FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

2》popen() 函数用创建管道的方式启动一个 进程, 并调用 shell. 因为管道是被定义成单向的, 所以 type 参数只能定义成只读或者只写, 不能是两者同时, 结果流也相应的是只读或者只写. command 参数是一个字符串指针, 指向的是一个以 null 结束符结尾的字符串, 这个字符串包含一个 shell 命令. 这个命令被送到 /bin/sh 以 -c 参数执行, 即由 shell 来执行. type 参数也是一个指向以 null 结束符结尾的字符串的指针, 这个字符串必须是 ‘r’ 或者 ‘w’ 来指明是读还是写.

#include
int main(int argc, char *argv[])
{
         char buf[128];
         FILE *pp;

         if( (pp = popen("ls -l", "r")) == NULL )
         {
                 printf("popen() error!/n");
                 exit(1);
         }

         while(fgets(buf, sizeof buf, pp))
         {
                 printf("%s", buf);
         }
         pclose(pp);
         return 0;
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值