进程控制---进程等待

1.进程等待

1.进程等待必要性---为什么要等待?

• 之前讲过,子进程退出,父进程如果不管不顾,就可能造成'僵尸进程'的问题,进而造成内存泄漏

• 另外,进程一旦变成僵尸状态,那就刀枪不入,"杀人不眨眼"的kill -9 也无能为力,因为谁也 没有办法杀死一个已经死去的进程。

• 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是 不对,或者是否正常退出。

• 父进程通过进程等待的方式,回收子进程资源获取子进程退出信息

最重要的 可选的

2.是什么?

复制代码
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

pid_t waitpid(pid_t pid, int *status, int options);
             
这种用wait或者waitpid的方式就叫做进程等待

先写一个僵尸进程

复制代码
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ cat Makefile
proc:proc.c
	gcc -o $@ $^ 
.PHONY:clean
clean:
	rm -f proc
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ cat proc.c
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        int cnt = 5;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        exit(0);
    }

    //父进程
    sleep(100);

    return 0;
}

[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ make
gcc -o proc proc.c 
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ ./proc
我是一个子进程,pid:30693,ppid:30692
我是一个子进程,pid:30693,ppid:30692
我是一个子进程,pid:30693,ppid:30692
我是一个子进程,pid:30693,ppid:30692
我是一个子进程,pid:30693,ppid:30692
^C

[user1@iZ5waahoxw3q2bZ ~]$ ps ajx | grep proc
30669 30691 30690 30669 pts/1    30690 S+    1001   0:00 grep --color=auto proc
[user1@iZ5waahoxw3q2bZ ~]$ ps ajx | grep proc
30528 30692 30692 30528 pts/0    30692 S+    1001   0:00 ./proc
30692 30693 30692 30528 pts/0    30692 S+    1001   0:00 ./proc
30669 30695 30694 30669 pts/1    30694 S+    1001   0:00 grep --color=auto proc
[user1@iZ5waahoxw3q2bZ ~]$ ps ajx | grep proc
30528 30692 30692 30528 pts/0    30692 S+    1001   0:00 ./proc
30692 30693 30692 30528 pts/0    30692 S+    1001   0:00 ./proc
30669 30697 30696 30669 pts/1    30696 S+    1001   0:00 grep --color=auto proc
[user1@iZ5waahoxw3q2bZ ~]$ ps ajx | grep proc
30528 30692 30692 30528 pts/0    30692 S+    1001   0:00 ./proc
30692 30693 30692 30528 pts/0    30692 S+    1001   0:00 ./proc
30669 30699 30698 30669 pts/1    30698 S+    1001   0:00 grep --color=auto proc
[user1@iZ5waahoxw3q2bZ ~]$ ps ajx | grep proc
30528 30692 30692 30528 pts/0    30692 S+    1001   0:00 ./proc
30692 30693 30692 30528 pts/0    30692 S+    1001   0:00 ./proc
30669 30701 30700 30669 pts/1    30700 S+    1001   0:00 grep --color=auto proc
[user1@iZ5waahoxw3q2bZ ~]$ ps ajx | grep proc
30528 30692 30692 30528 pts/0    30692 S+    1001   0:00 ./proc
30692 30693 30692 30528 pts/0    30692 Z+    1001   0:00 [proc] <defunct>
30669 30704 30703 30669 pts/1    30703 S+    1001   0:00 grep --color=auto proc

可以发现变成僵尸进程了。

当前我们要用进程等待的方式把僵尸问题解决 wait

复制代码
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);  父进程创建出子进程,父进程通过接口等待子进程
目标z进程的pid    输出型参数  

int *status输出型参数,需要传一个整型变量的地址。

调成功这个函数之后,这个进程会把子进程的退出信息放在status,让上层即父进程拿到。

wait等待任意一个退出的子进程!

pid_t 目标Z进程的pid,成功回收了哪个僵尸进程的pid就返回哪个,失败了往往返回值是-1

子进程退出父进程是可以拿到对应的pid,但是如果子进程没退出呢?

如果等待子进程,子进程没有退出,父进程会阻塞在wait调用处(eg.scanf)

复制代码
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ cat proc.c
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        int cnt = 5;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        exit(0);
    }

    
    //父进程 
    sleep(10);
    pid_t rid = wait(NULL);
    if(rid > 0)
    {
        printf("wait success,rid:%d\n",rid);//rid
    }
    sleep(10);

    return 0;
}

[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ make
gcc -o proc proc.c 
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ ./proc
我是一个子进程,pid:31251,ppid:31250
我是一个子进程,pid:31251,ppid:31250
我是一个子进程,pid:31251,ppid:31250
我是一个子进程,pid:31251,ppid:31250
我是一个子进程,pid:31251,ppid:31250
wait success,rid:31251

子进程跑五秒,五秒以内父进程不运行。五秒之后,子进程变僵尸,父进程运行。

再过上五秒之后,父进程wait把子进程回收,父进程还在运行,子进程僵尸没有了。父进程再休眠十秒,就退出了。

复制代码
[user1@iZ5waahoxw3q2bZ ~]$ while :; do ps ajx | head -1 && ps ajx | grep proc; sleep 1; done
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30669 31409 31408 30669 pts/1    31408 S+    1001   0:00 grep --color=auto proc
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30881 31250 31250 30881 pts/0    31250 S+    1001   0:00 ./proc
31250 31251 31250 30881 pts/0    31250 S+    1001   0:00 ./proc
30669 31271 31270 30669 pts/1    31270 S+    1001   0:00 grep --color=auto proc
。。。。。。。。。。。。。。。。
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30881 31250 31250 30881 pts/0    31250 S+    1001   0:00 ./proc
31250 31251 31250 30881 pts/0    31250 Z+    1001   0:00 [proc] <defunct>
30669 31281 31280 30669 pts/1    31280 S+    1001   0:00 grep --color=auto proc
。。。。。。。。。。。。。。。。。
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30881 31250 31250 30881 pts/0    31250 S+    1001   0:00 ./proc
30669 31313 31312 30669 pts/1    31312 S+    1001   0:00 grep --color=auto proc
。。。。。。。。。。。。。。。。。
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30669 31378 31377 30669 pts/1    31377 S+    1001   0:00 grep --color=auto proc
。。。。。。。。。。。。。。。。。

时间线详解

时间 事件 子进程状态 父进程状态
0s fork执行,父子进程开始并行 运行中 运行中,但立即进入 sleep(10)
0-5s 子进程循环打印5次,每次间隔1秒 运行中 睡眠(不运行)
5s 子进程打印第5次后 exit(0) 僵尸(已终止,父未回收) 仍在睡眠(还剩5秒)
10s 父进程 sleep(10) 结束 僵尸 唤醒,执行 wait(NULL)
10s 父进程调用 wait,回收子进程 被回收,消失 继续执行 printf("wait success")
10s 父进程进入第二个 sleep(10) - 睡眠10秒
20s 父进程 sleep 结束,return 0 - 正常退出

从而验证了wait确实能解决僵尸问题

waitpid

复制代码
pid_t waitpid(pid_t pid, int *status, int options);
                            输出型参数      阻塞控制
              pid传错了就会等待失败  

开头pid_t跟wait一样

int *status父进程获取子进程的信息

options阻塞控制

The value of pid can be:

< -1 meaning wait for any child process whose process group ID is equal to the

absolute value of pid.

-1 meaning wait for any child process.

0 meaning wait for any child process whose process group ID is equal to that

of the calling process.

> 0 meaning wait for the child whose process ID is equal to the value of pid.
pid:指定要等待的子进程。

  • pid > 0:等待进程 ID 等于 pid 的特定子进程。

  • pid == -1:等待任意 子进程(等同于 wait)。

  • pid == 0:等待与调用进程同进程组的任意子进程。

  • pid < -1:等待进程组 ID 等于 |pid| 的任意子进程。

  • 如果 pid 不存在或不是调用进程的子进程,waitpid 可能会失败(返回 -1errno 设为 ECHILD)。

第一个参数pid_t pid

pid_t rid = wait(NULL);

pid_t rid = waitpid(-1,NULL,0); 这两种写法是一样的

复制代码
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ cat proc.c
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        int cnt = 5;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        exit(0);
    }

    
    //父进程 
    sleep(10);
    //pid_t rid = wait(NULL);
    //pid_t rid = waitpid(-1,NULL,0);
    pid_t rid = waitpid(id,NULL,0);
    if(rid > 0)
    {
        printf("wait success,rid:%d\n",rid);//rid
    }
    sleep(10);

    return 0;
}
第二个参数int *status

父进程要通过waitpid,通过status获得子进程的退出信息。

父进程怎么知道子进程结果怎么样呢?

• 代码运行完毕,结果正确

• 代码运行完毕,结果不正确

• 代码异常终止

还是通过上篇的 进程退出码、main返回值来判定当前子进程退出结果是否正确。

复制代码
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ cat proc.c
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        int cnt = 3;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }

    
    //父进程 
    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        printf("wait success,rid:%d,status:%d\n",rid,status);//rid
    }
    else
    {
        printf("wait failed:%d:%s\n",errno,strerror(errno));
    }
    return 0;
}

[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ make
gcc -o proc proc.c 
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ ./proc
我是一个子进程,pid:31502,ppid:31501
我是一个子进程,pid:31502,ppid:31501
我是一个子进程,pid:31502,ppid:31501
wait success,rid:31502,status:256

我们发现**status 整数值并不会得到子进程的退出码(exit(1) 中的 1)**

status中一定不是只有进程退出码

获取子进程status

•wait和waitpid,都有⼀个status参数,该参数是⼀个输出型参数,由操作系统填充。

•如果传递NULL,表示不关心子进程的退出状态信息。

•否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

•status不能简单的当作整形来看待,这个status是被划分成若干区域的可以当作位图来看待,具体细节下图(只研究status低16比特位):

总共32位,高16位不考虑。低16位中的首个八位表示退出状态即退出码

最后八位一位是core dump标志位,还有低7比特位表示进程退出时的推出信号。如果进程默认属于前两种情况,后八位全是0。就变成了1后面跟8个0也就是256了。

那么又该如何获取退出状态呢?把status整型变量右移8位,把8位全部清除掉,就可以拿到对应的1了。

printf("wait success,rid:%d,exit code:%d\n",rid,(status>>8)&0xFF);//rid

复制代码
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ cat proc.c
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        int cnt = 3;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }

    
    //父进程 
    //pid_t rid = wait(NULL);
    //pid_t rid = waitpid(-1,NULL,0);
    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        printf("wait success,rid:%d,exit code:%d\n",rid,(status>>8)&0xFF);//rid
    }
    else
    {
        printf("wait failed:%d:%s\n",errno,strerror(errno));
    }
    return 0;
}

[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ make
gcc -o proc proc.c 
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ ./proc
我是一个子进程,pid:31524,ppid:31523
我是一个子进程,pid:31524,ppid:31523
我是一个子进程,pid:31524,ppid:31523
wait success,rid:31524,exit code:1

那么为什么不定义一个全局变量呢?

子进程的退出信息也属于子进程的数据,父进程这样就拿不到子进程退出信息。那样会发生写时拷贝,拿不到。

必须通过前面那样的系统调用才能拿到。

3.异常

kill -l 命令就是用来查看系统中的所有信号的。

信号本质就是宏

复制代码
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

一个进程如果出现异常终止,那么它(status)的低七个比特位会保存退出异常时对应的信号编号

如果没有异常?

1.低七个比特位,信号值为0,表示没有收到过信号

2.一旦低七个比特位 !0 ,异常退出的,退出码无意义!

printf("wait success,rid:%d,exit code:%d,exit signal:%d\n",rid,(status>>8)&0xFF,status&0x7F);

复制代码
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ cat proc.c
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        int cnt = 3;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }

    
    //父进程 
    //pid_t rid = wait(NULL);
    //pid_t rid = waitpid(-1,NULL,0);
    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        //printf("wait success,rid:%d,exit code:%d\n",rid,(status>>8)&0xFF);//rid
        printf("wait success,rid:%d,exit code:%d,exit signal:%d\n",rid,(status>>8)&0xFF,status&0x7F);
    }
    else
    {
        printf("wait failed:%d:%s\n",errno,strerror(errno));
    }
    return 0;
}

[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ make
gcc -o proc proc.c 
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ ./proc
我是一个子进程,pid:31544,ppid:31543
我是一个子进程,pid:31544,ppid:31543
我是一个子进程,pid:31544,ppid:31543
wait success,rid:31544,exit code:1,exit signal:0

写一个错误的(把代码中while(cnt)改成while(1)),使用kill -9 杀掉进程

复制代码
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ make
gcc -o proc proc.c 
[user1@iZ5waahoxw3q2bZ 26-4-25.2]$ ./proc
我是一个子进程,pid:31555,ppid:31554
我是一个子进程,pid:31555,ppid:31554
我是一个子进程,pid:31555,ppid:31554
wait success,rid:31555,exit code:0,exit signal:9

4.怎么做到的?

wait跟wait_pid那些子进程信息是从哪里拿的?---应该存到哪里

处于僵尸状态的进程内部肯定有一种类似的int exit_code int exit_signal的整形数据

所以当我们这个子进程退出时,signal会把我们进程退出时main函数的返回值保存到进程的PCB里面。退出为0如果有也写进去。那么这个不就是进程退出的信息吗?

父进程waitpid调用对应的接口找到子进程的退出信息,把code跟signal通过status给到父进程的地址空间上

源码中

相关推荐
cen__y2 小时前
Linux05(管道)
linux·运维·服务器·c语言·开发语言·文件流
IDO读书2 小时前
CentOS 7 安装 jprofiler_linux64_7_2_3.tar.gz 详细步骤(解压、配置、远程连接)
linux
IMPYLH2 小时前
Linux 的 split 命令
linux·运维·python·bash·运维开发·unix
cyber_两只龙宝2 小时前
【Oracle】Oracle之使用DML语言管理表
linux·运维·服务器·数据库·云原生·oracle
sghuter2 小时前
数字资源分发的技术架构与未来趋势
c语言·开发语言·后端·青少年编程
惊鸿若梦一书生2 小时前
《Python 高阶教程》016|偏函数与柯里化:把复杂调用拆成更简单的组合
linux·网络·python
senijusene2 小时前
基于 Linux SPI 子系统的 ADXL345 加速度传感器驱动开发
linux·运维·驱动开发
顺风尿一寸2 小时前
深入Linux内核启动:从kernel_init到第一个用户进程的完整旅程
linux
Sakuyu434682 小时前
C语言基础(三)
c语言·开发语言