05-进程控制

news2024/12/23 4:09:04

1. 学习目标

  • 了解进程相关的概念
  • 掌握fork/getpid/getppid函数的使用
  • 熟练掌握ps/kill命令的使用
  • 熟练掌握execl/execlp函数的使用
  • 说出什么是孤儿进程什么是僵尸进程
  • 熟练掌握wait函数的使用
  • 熟练掌握waitpid函数的使用

2 进程相关概念

2.1 程序和进程

  • 程序,是指编译好的二进制文件,在磁盘上,占用磁盘空间, 是一个静态的概念.
  • 进程,一个启动的程序, 进程占用的是系统资源,如:物理内存,CPU,终端等,是一个动态的概念
  • 程序 → 剧本(纸)
  • 进程 → 戏(舞台、演员、灯光、道具...)

同一个剧本可以在多个舞台同时上演。同样,同一个程序也可以加载为不同的进程(彼此之间互不影响)

2.2 并行和并发

  • 并发,在一个时间段内, 是在同一个cpu上, 同时运行多个程序。

如:若将CPU的1S的时间分成1000个时间片,每个进程执行完一个时间片必须无条件让出CPU的使用权,这样1S中就可以执行1000个进程。

  • 并行性指两个或两个以上的程序在同一时刻发生(需要有多颗)。

2.3 PCB-进程控制块

每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。

/usr/src/linux-headers-4.4.0-96/include/linux/sched.h文件的1390行处可以查看struct task_struct 结构体定义。其内部成员有很多,我们重点掌握以下部分即可:

  • 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
  • 进程的状态,有就绪、运行、挂起、停止等状态。
  • 进程切换时需要保存和恢复的一些CPU寄存器。
  • 描述虚拟地址空间的信息。
  • 描述控制终端的信息。
  • 当前工作目录(Current Working Directory)。
    • getcwd --pwd
  • umask掩码。
  • 文件描述符表,包含很多指向file结构体的指针。
  • 和信号相关的信息。
  • 用户id和组id。
  • 会话(Session)和进程组。
  • 进程可以使用的资源上限(Resource Limit)。
    • ulimit -a

2.4 进程状态(面试考)

  • 进程基本的状态有5种。分别为初始态,就绪态,运行态,挂起态与终止态。其中初始态为进程准备阶段,常与就绪态结合来看。

3 创建进程

3.1 fork函数

  • 函数作用:创建子进程
  • 原型: pid_t fork(void);

函数参数:无

返回值:调用成功:父进程返回子进程的PID,子进程返回0;(父子进程各返回一个值)

调用失败:返回-1,设置errno值。

  • fork函数代码片段实例

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

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

{

printf("before fork, pid:[%d]\n",getpid());

// pid_t fork(void);

pid_t pid = fork();

if(pid < 0)

{

perror("fork error");

return -1;

}

else if(pid > 0)

{

printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid());

// pid_t getpid(void);

// pid_t getppid(void);

sleep(1); //延迟1s,让子进程先退出

}

else

{

printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid());

}

printf("after fork, pid:[%d]\n",getpid());

return 0;

}

[holo@holocom 0406]$ ./fork

before fork, pid:[69024]

father:pid == [69024],fpid == [49354] //49354是当前shell

son:pid == [69025], fpid == [69024]

after fork, pid:[69025]

after fork, pid:[69024]

[holo@holocom ~]$ ps -ef | grep 49354

holo 49354 49349 0 16:57 pts/0 00:00:00 -bash

holo 75618 49355 0 17:45 pts/1 00:00:00 grep --color=auto 49354

上述代码注释掉sleep(1)后,父进程(有概率)先死,子进程就会变为孤儿进程

[holo@holocom 0406]$ make fork

cc fork.c -o fork

[holo@holocom 0406]$ ./fork

before fork, pid:[83311]

father:pid == [83311],fpid == [49354]

after fork, pid:[83311]

[holo@holocom 0406]$ son:pid == [83312], fpid == [1] //1号进程:init进程(很多进程的父进程)

after fork, pid:[83312]

[holo@holocom ~]$ ps -ef | grep init

root 6397 1 0 11:26 ? 00:00:00 /usr/sbin/alsactl -s -n 19 -c -E ALSA_CONFIG_PATH=/etc/alsa/alsactl.conf --initfile=/lib/alsa/init/00main rdaemon

holo 96489 49355 0 17:51 pts/1 00:00:00 grep --color=auto init

  • 调用fork函数的内核实现原理:

父进程调用Fork函数创建子进程,子进程的用户区和父进程的用户区完全一样,但是内核区不完全一样.如他俩的PID不一样

pid_t fork(void);

父子进程的执行逻辑:

父进程执行pid>0的值,子进程执行pid=0的值

  • fork函数总结

pid_t fork(void);

►fork函数的返回值?

父进程返回子进程的PID,是一个大于0数;

子进程返回0;

特别需要注意的是:不是fork函数在一个进程中返回2个值,而是在父子进程各自返回一个值。

►子进程创建成功后,代码的执行位置?

父进程执行到什么位置,子进程就从哪里执行

►如何区分父子进程

通过fork函数的返回值

►父子进程的执行顺序

不一定,哪个进程先抢到CPU,哪个进程就先执行

3.2 ps命令和kill命令

  • ps aux | grep "xxx"
  • ps ajx | grep "xxx"
  • -a:(all)当前系统所有用户的进程
  • -u:查看进程所有者及其他一些信息
  • -x:显示没有控制终端的进程 -- 不能与用户进行交互的进程【输入、输出】
  • -j: 列出与作业控制相关的信息
  • kill -l 查看系统有哪些信号
  • kill -9 pid 杀死某个线程

找pid 用 ps -ef就够了

3.3 getpid/getppid

  • getpid - 得到当前进程的PID

pid_t getpid(void);

  • getppid - 得到当前进程的父进程的PID

pid_t getppid(void);

3.3 练习题

  • 编写程序,循环创建多个子进程,要求如下:
  1. 多个子进程是兄弟关系。
  2. 判断子进程是第几个子进程

画图讲解创建多个子进程遇到的问题

注意:若让多个子进程都是兄弟进程,必须不能让子进程再去创建新的子进程。

循环创建n个子进程

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

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

{

int i = 0;

for(i = 0 ;i < 3; i++)

{

// pid_t fork(void);

pid_t pid = fork();

if(pid < 0)

{

perror("fork error");

return -1;

}

else if(pid > 0)

{

printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid());

// pid_t getpid(void);

// pid_t getppid(void);

// sleep(1);

}

else

{

printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid());

}

}

sleep(10);

return 0;

}

[holo@holocom 0406]$ ./fork1

father:pid == [25337],fpid == [8655]

father:pid == [25337],fpid == [8655]

father:pid == [25337],fpid == [8655]

son:pid == [25339], fpid == [25337]

father:pid == [25339],fpid == [25337]

son:pid == [25340], fpid == [25337]

son:pid == [25338], fpid == [25337]

father:pid == [25338],fpid == [25337]

father:pid == [25338],fpid == [25337]

son:pid == [25341], fpid == [25339]

son:pid == [25342], fpid == [25338]

father:pid == [25342],fpid == [25338]

son:pid == [25343], fpid == [25338]

son:pid == [25344], fpid == [25342]

分析:创建了几个son 就是有几个子进程

在子进程代码处, 添加break , 就会break,不会继续执行循环 ,子进程就不会创建子进程了.

这样就能保证父进程创建来的子进程全是兄弟关系

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

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

{

int i = 0;

for(i = 0 ;i < 3; i++)

{

// pid_t fork(void);

pid_t pid = fork();

if(pid < 0)

{

perror("fork error");

return -1;

}

else if(pid > 0)

{

printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid());

// pid_t getpid(void);

// pid_t getppid(void);

// sleep(1);

}

else

{

printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid());

break;

}

}

sleep(10);

return 0;

}

[holo@holocom 0406]$ ./fork1

father:pid == [26549],fpid == [8655]

father:pid == [26549],fpid == [8655]

father:pid == [26549],fpid == [8655]

son:pid == [26550], fpid == [26549]

son:pid == [26551], fpid == [26549]

son:pid == [26552], fpid == [26549]

打印出 是第几个子进程的pid

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

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

{

int i = 0;

for(i = 0 ;i < 3; i++)

{

// pid_t fork(void);

pid_t pid = fork();

if(pid < 0)

{

perror("fork error");

return -1;

}

else if(pid > 0)

{

printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid());

// pid_t getpid(void);

// pid_t getppid(void);

// sleep(1);

}

else

{

printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid());

break;

}

}

//第一个子进程

if(i==0)

{

printf("[%d]---[%d]:child\n",i,getpid()); //第i个进程的pid

}

//第二个子进程

if(i==1)

{

printf("[%d]---[%d]:child\n",i,getpid()); //第i个进程的pid

}

//第三子进程

if(i==2)

{

printf("[%d]---[%d]:child\n",i,getpid()); //第i个进程的pid

}

//父进程

if(i==3)

{

printf("[%d]---[%d]:child\n",i,getpid()); //第i个进程的pid

}

sleep(10);

return 0;

}

[holo@holocom 0406]$ ./fork1

father:pid == [39900],fpid == [8655]

father:pid == [39900],fpid == [8655]

father:pid == [39900],fpid == [8655]

[3]---[39900]:child

son:pid == [39902], fpid == [39900]

[1]---[39902]:child

son:pid == [39903], fpid == [39900]

[2]---[39903]:child

son:pid == [39901], fpid == [39900]

[0]---[39901]:child

  • 编写程序,测试父子进程是否能够共享全局变量

重点通过这个案例讲解 读时共享,写时复制

可以节省资源, 在某个进程即将修改时, 在物理内存中拷贝出一份资源,修改后再映射回去.

父子进程不可以使用全局变量进行通信.

由系统的内存管理单元MMU 执行: 从虚拟内存映射到物理内存, 再映射回去

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

int g_var = 99;

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

{

// pid_t fork(void);

pid_t pid = fork();

if(pid < 0)

{

perror("fork error");

return -1;

}

else if(pid > 0)

{

g_var++;

printf("add:[%p]",&g_var); //访问的是虚拟地址,因为访问不了物理地址

printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid());

// pid_t getpid(void);

// pid_t getppid(void);

// sleep(1);

}

else

{

printf("add:[%p]",&g_var);

sleep(1);

printf("child : g_var == [%d]\n",g_var);

printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid());

}

return 0;

}

[holo@holocom 0406]$ make fork2

cc fork2.c -o fork2

[holo@holocom 0406]$ ./fork2

add:[0x601054]father:pid == [8914],fpid == [8799]

[holo@holocom 0406]$ add:[0x601054]child : g_var == [99]

son:pid == [8915], fpid == [1]

4 exec函数族

4.1 函数作用和函数介绍

有的时候需要在一个进程内部执行系统命令或用户自定义的应用程序,用exec函数族可以实现 , 使用方法一般都是在父进程里面调用fork创建处子进程,然后在子进程里面调用exec函数。

pid = fork();

if(pid==0)

{

execl(...);

}

Arg参数最好写:程序的名字,以便自己知道在执行这个程序.除非让别人故意看不到采写别的名字

  • execl函数

" 换核不换壳 " : 堆, 栈 , .txt代码段都替换了

函数原型: int execl(const char *path, const char *arg, ... /* (char *) NULL */);

参数介绍:

  • path: 要执行的程序的绝对路径/相对路径
  • 变参arg: 要执行的程序的需要的参数
  • arg:占位,通常写应用程序的名字
  • arg后面的: 命令的参数
  • 参数写完之后, 以NULL结尾(告诉函数 完了)

返回值:若是成功,则不返回,不会再执行exec函数后面的代码;若是失败,会执行execl后面的代码,可以用perror打印错误原因。

execl函数一般执行自己写的程序。

  • execlp函数

函数原型: int execlp(const char *file, const char *arg, .../* (char *) NULL */);

参数介绍:

  • file: 执行命令的名字, 不带路径, 根据PATH环境变量来搜索该命令(eg:ls -l)
  • arg:占位
  • arg后面的: 命令的参数
  • 参数写完之后: NULL

返回值:若是成功,则不返回,不会再执行exec函数后面的代码;若是失败,会执行execlp后面的代码,可以用perror打印错误原因。

execlp函数一般是执行系统自带的程序或者是命令.

execl和execlp函数的区别: execl用于打开我们自己的应用程序 , execlp用于打开系统自带的应用程序或自己的应用程序 , execl必须加路径, execlp执行系统命令时不需要加路径

4.2 exec函数族原理介绍

exec族函数的实现原理图:

如:execlp(“ls”, “ls”, “-l”, NULL);

总结:

exec函数是用一个新程序替换了当前进程的代码段、数据段、堆和栈;原有的进程空间没有发生变化,并没有创建新的进程,进程PID没有发生变化。

4.3 exec函数练习

  • 使用execl函数执行一个用户自定义的应用程序
  • 使用execlp函数执行一个linux系统命令

注意:当execl和execlp函数执行成功后,不返回,并且不会执行execl后面的代码逻辑,原因是调用execl函数成功以后,exec函数指定的代码段已经将原有的代码段替换了。

test.c

#include <stdio.h>

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

{

int i=0;

for(i=0; i<argc; i++)

{

printf("[%d]:[%s]\n",i,argv[i]);

}

return 0;

}

execl.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

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

{

// pid_t fork(void);

pid_t pid = fork();

if(pid < 0)

{

perror("fork error");

return -1;

}

else if(pid > 0)

{

printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid());

}

else

{

printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid());

//execl("/usr/bin/ls" , "ls" , "-l" , NULL); //路径(which ls可查) , 命令名字(占位符) , 命令参数

//execl("./test","test","hello","world ","ni","hao","ya ", NULL);

//execlp("ls","ls","-l",NULL);

execlp("./test","test","hello","world","ni","hao","ya",NULL); //可加路径也可不加

//第二个参数占位符务必写上, 可以看出执行的是哪个exe, 如果不想让别人看见 可不写

printf("execl error");

}

return 0;

}

[holo@holocom 0406]$ ./execl

father:pid == [48819],fpid == [10309]

[holo@holocom 0406]$ son:pid == [48820], fpid == [1]

[0]:[hello]

[1]:[world]

[2]:[ni]

[3]:[hao]

[4]:[ya]

5 进程回收

5.1 为什么要进行进程资源的回收

当一个进程退出之后,进程能够回收自己的用户区的资源,但是不能回收内核空间的PCB资源,必须由它的父进程调用wait或者waitpid函数完成对子进程的回收,避免造成系统资源的浪费。

5.2 孤儿进程

  • 孤儿进程的概念:

若子进程的父进程已经死掉,而子进程还存活着,这个进程就成了孤儿进程。

  • 为了保证每个进程都有一个父进程,孤儿进程会被init进程领养,init进程成为了孤儿进程的养父进程,当孤儿进程退出之后,由init进程完成对孤儿进程的回收。
  • 模拟孤儿进程的案例

编写模拟孤儿进程的代码讲解孤儿进程,验证孤儿进程的父进程是否由原来的父进程变成了init进程。

如果进程只创建不回收,linux系统资源就会消耗殆尽,死机了.

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <string.h>

#include <unistd.h>

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

{

pid_t pid = fork();

while(1){

pid = fork();

if(pid < 0)

{

perror("fork error");

return -1;

}

else if(pid > 0)

{

printf("father:pid = [%d],fpid = [%d]\n",getpid(),getppid());

}

else if(pid == 0)

{

printf("son:pid = [%d],fpid = [%d]\n",getpid(),getppid());

}

}

return 0;

}

开始执行!

resource temporarily unavailable 资源暂时不可用

进程活着才可以被回收

//孤儿进程

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

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

{

pid_t pid = fork();

if(pid < 0)

{

perror("fork error");

return -1;

}

else if(pid > 0)

{

sleep(2);

printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid());

}

else

{

printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid());

sleep(20); //保证让父进程先死,子进程后死.

printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid());

}

return 0;

}

[holo@holocom 0406]$ make orphan

cc orphan.c -o orphan

[holo@holocom 0406]$ ./orphan

son:pid == [58529], fpid == [58525]

father:pid == [58525],fpid == [8050]

[holo@holocom 0406]$ son:pid == [58529], fpid == [1]

5.3 僵尸进程

  • 僵尸进程的概念:

若子进程死了,父进程还活着, 但是父进程没有调用wait或waitpid函数完成对子进程的回收(回收的是子进程的内核资源),则该子进程就成了僵尸进程。

  • 如何解决僵尸进程
  • 由于僵尸进程是一个已经死亡的进程,所以不能使用kill命令将其杀死
  • 通过杀死其父进程的方法可以消除僵尸进程。

杀死其父进程后,这个僵尸进程会被init进程领养,由init进程完成对僵尸进程的回收。

  • 模拟僵尸进程的案例

编写模拟僵尸进程的代码讲解僵尸进程, 验证若子进程先于父进程退出, 而父进程没有调用wait或者waitpid函数进行回收, 从而使子进程成为了僵尸进程.

//僵尸进程

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

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

{

pid_t pid = fork();

if(pid < 0)

{

perror("fork error");

return -1;

}

else if(pid > 0)

{

sleep(100);

printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid());

}

else

{

printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid());

}

return 0;

}

Defunct表明此进程是僵尸进程 , 不可以被kill杀死

holo 113269 8050 0 15:55 pts/0 00:00:00 ./zombie

holo 113270 113269 0 15:55 pts/0 00:00:00 [zombie] <defunct>

[省略其他进程描述]

[holo@holocom ~]$ kill -9 113270

[holo@holocom ~]$ ps –ef

holo 113269 8050 0 15:55 pts/0 00:00:00 ./zombie

holo 113270 113269 0 15:55 pts/0 00:00:00 [zombie] <defunct>

[holo@holocom ~]$ kill -9 113269

[holo@holocom ~]$ ps –ef

root 81821 2 0 15:46 ? 00:00:00 [kworker/0:1]

root 94623 2 0 15:50 ? 00:00:00 [kworker/u256:1]

root 98837 2 0 15:51 ? 00:00:00 [kworker/0:0]

root 109028 2 0 15:54 ? 00:00:00 [kworker/0:3]

root 112734 6542 0 15:55 ? 00:00:00 sleep 60

holo 114947 8043 0 15:56 ? 00:00:00 bash -c export LANG="en_US";export LANGUAGE="en_US";export LC_ALL="en_US";fre

holo 114954 8044 0 15:56 ? 00:00:00 bash -c export LANG="en_US";export LANGUAGE="en_US";export LC_ALL="en_US";fre

holo 114969 114947 0 15:56 ? 00:00:00 sleep 1

holo 114976 114954 0 15:56 ? 00:00:00 sleep 1

holo 114977 8049 0 15:56 pts/1 00:00:00 ps –ef

(此时僵尸进程的父进程被杀死了,所以僵尸进程也不复存在了)

[holo@holocom 0406]$ ./zombie

son:pid == [113270], fpid == [113269]

已杀死

5.4 进程回收函数

  • wait函数

  • 在父进程中调用,这是一个阻塞函数,子进程退出后,立即返回
  • 函数原型:

pid_t wait(int *status); //status可以传入NULL

  • 函数作用:
  • 阻塞并等待子进程退出
  • 回收子进程残留资源
  • 获取子进程结束状态(退出原因)。
  • 返回值:
  • 成功:清理掉的子进程ID;
  • 失败:-1 (没有子进程)
  • status参数:子进程的退出状态(是否正常退出) -- 传出参数

如果不关心状态,status可以设为NULL

如果关心状态,就定义一个status, 然后等待传出值(这个值不是子进程退出return的值, 使用下面的函数获取子进程退出的值)

status变量是通过wait()函数返回的子进程退出状态。它是一个整数类型的变量,包含了有关子进程退出的一些信息。

其中,低字节(最低8位)存储了子进程的退出状态码,即子进程调用exit()函数时传递的返回值。这个状态码可以通过WEXITSTATUS(status)宏来提取。

int exit_status = WEXITSTATUS(status);

printf("子进程的退出状态码: %d\n", exit_status);

  • WIFEXITED(status):为非0 true  → 子进程正常结束

WEXITSTATUS(status):获取子进程退出return的值

  • WIFSIGNALED(status):为非0 true→ 子进程异常终止

WTERMSIG(status):取得子进程终止的信号编号。

  • wait函数练习

在父进程中使用wait函数完成父进程对子进程的回收

//无退出状态 参数为NULL

//父进程调用wait函数,回收子进程

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/wait.h>

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

{

pid_t pid = fork();

if(pid < 0)

{

perror("fork error");

return -1;

}

else if(pid > 0)

{

printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid());

pid_t spid = wait(NULL);

printf("pid : [%d]\n ",spid);

}

else

{

printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid());

sleep(5);

}

return 0;

}

[holo@holocom 0406]$ ./wait

father:pid == [68367],fpid == [8050]

son:pid == [68368], fpid == [68367]

pid : [68368]

//参数status有退出状态

//父进程调用wait函数,回收子进程

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/wait.h>

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

{

pid_t pid = fork();

if(pid < 0)

{

perror("fork error");

return -1;

}

else if(pid > 0)

{

printf("father:pid == [%d],fpid == [%d]\n", getpid(),getppid());

int status;

pid_t spid = wait(&status); //spid存储 退出的子进程id

printf("pid : [%d]\n ",spid);

if(WIFEXITED(status)) //正常退出了

{

printf("son normal exit, status is [%d]\n", WEXITSTATUS(status));

}

else if(WIFSIGNALED(status)) //被信号杀死了

{

printf("son is killed by signal , signo is [%d]\n", WTERMSIG(status));

}

}

else

{

printf("son:pid == [%d], fpid == [%d]\n",getpid(),getppid());

sleep(20);

return 3; //子进程休眠20秒退出,返回3

}

return 0;

}

[holo@holocom 0406]$ ./wait

father:pid == [18092],fpid == [8050]

son:pid == [18093], fpid == [18092]

pid : [18093]

son normal exit, status is [3]

问题:执行./wait后, ps –ef 查不到正在执行的这个进程,所以无法杀掉这个进程,son is killed by signal 的情况无法测试,待解决.

解答:已经解决,原来这个进程pid,往上滑可以看到

补充 : ps -ef | grep 程序名也可以查到

[holo@holocom 0406]$ ./wait

father:pid == [34399],fpid == [8050]

son:pid == [34400], fpid == [34399]

pid : [34400]

son is killed by signal , signo is [9]

[holo@holocom ~]$ ps -ef

holo 34399 8050 0 16:48 pts/0 00:00:00 ./wait

holo 34400 34399 0 16:48 pts/0 00:00:00 ./wait

[holo@holocom ~]$ kill -9 34400

  • waitpid函数

  • 函数原型:

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

  • 函数作用

同wait函数

  • 函数参数

参数:

pid:

pid = -1 等待所有的子进程退出。与wait等效。

pid > 0 等待回收指定子进程。

pid = 0 等待回收同一个进程组里的子进程

pid < -1 等待回收其他组的子进程

status: 子进程的退出状态,用法同wait函数。

options:设置为WNOHANG,函数非阻塞; 即 Wait no hang : 等待不挂起

设置为0,函数阻塞。

  • 函数返回值

>0:返回回收掉的子进程ID;

-1:无子进程

=0:参3为WNOHANG,且子进程正在运行。

  • waitpid函数练习

使用waitpid函数完成对子进程的回收

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <string.h>

#include <unistd.h>

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

{

pid_t fork_pid = fork();

if(fork_pid < 0)

{

perror("fork error");

return -1;

}

else if(fork_pid > 0) //fork() : 父进程返回子进程的PID,子进程返回0

{

printf("father:pid == [%d],fpid == [%d]\n",getpid(),getppid()); //getpid得到当前进程的pid,getppid得到当前进程父进程的pid

int status;

//子进程还活着, 再回收一次,循环回收 直到子进程死去 停止回收

while(1) //由于非阻塞,需要保证父进程晚于子进程退出,不存在僵尸进程问题

{

pid_t child_pid = waitpid(-1,&status,WNOHANG); //waitpid() 返回清理掉的子进程pid -1:等待所有子进程 0:函数阻塞

//WNOHANG:函数不阻塞

// printf("child_pid:[%d]\n",child_pid);

if(child_pid > 0){ //>0说明回收了子进程

if(WIFEXITED(status))

{

printf("进程正常退出,值为:[%d]\n",WEXITSTATUS(status));

}

else if(WIFSIGNALED(status))

{

printf("进程异常终止,被[%d]杀死\n",WTERMSIG(status));

}

//break;能写到这里嘛

}

else if(child_pid == 0) //说明子进程还活着

{

// printf("子进程还活着,child_pid == [%d]\n",child_pid);

}

else if(child_pid == -1) //子进程全部退出了 waitpid返回值为-1说明没有子进程

{

printf("子进程全部退出,child_pid == [%d]\n",child_pid);

break; //没有子进程的时候结束while循环 父进程跳出

}

}

//僵尸进程:子进程死了,父进程还活着,但是父进程没有调用wait或waitpid函数完成对子进程内核资源的回收,所以子进程就变成了僵尸进程

// sleep(20); //让子进程变成僵尸进程,僵尸进程产生的原因是只做了一次waitpid

}

else if(fork_pid == 0) //fork():子进程返回0

{

printf("child:pid == [%d] , fpid == [%d]\n",getpid(),getppid()); //子进程PID 父进程PID

sleep(2);

return 3; //子进程如果正常退出,返回3

}

return 0;

}

[holo@holocom 0406]$ ./waitpid1

father:pid == [27661],fpid == [7666]

child:pid == [27662] , fpid == [27661]

进程正常退出,值为:[3]

子进程全部退出,child_pid == [-1]

6 作业

6.1 作业1

测试父子进程之间是否共享文件

#include <stdio.h>

#include <fcntl.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

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

{

        int fd = open(argv[1] , O_RDWR | O_TRUNC);

        if(fd < 0)

        {

                perror("open error");

        }

        pid_t pid = fork();   //open之后才fork, 这样子进程有了和父进程相同的文件描述符表

        if(pid<0)

        {

                perror("fork error");

        }

        else if(pid>0)  //父进程

        {

                printf("father:[%d]",getpid());

                write(fd , "nihaoa" , sizeof("nihaoa"));

                wait(0);        //肯定能回收成功吗

        }

        else    //子进程

        {

                char buf[64];

                memset(buf,0x00,sizeof(buf));

                sleep(3);

                lseek(fd , 0, SEEK_SET);

                read(fd, buf , sizeof(buf));

                printf("son:[%d] , buf:[%s]\n",getpid() , buf); 

        }

        

        close(fd);

        return 0;

}

holo@holo:~/test$ ./gongxiang 1.txt

son:[48608] , buf:[nihaoa]

6.2 作业2

父进程fork三个子进程:

其中一个调用ps命令;

一个调用自定义应用程序;

一个调用会出现段错误的程序。

父进程回收三个子进程(waitpid),并且打印三个子进程的退出状态。

=== 段错误 ===

1>. 访问了非法内存

2>. 访问了不可写的区域进行写操作

3>. 栈空间溢出

char* p = “hello,world”        

p【0】=‘a’;

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <string.h>

#include <unistd.h>

#include <string.h>

#include <errno.h>

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

{

int i;

for(i=0; i<3; i++)

{

pid_t pid = fork();

if(pid<0)

{

perror("fork error");

return -1;

}

else if(pid >0)  //父进程逻辑

{

printf("father:pid = [%d]\n",getpid());

sleep(1);

}

else  //子进程的逻辑

{  

if(i==0)  //调用ps命令

{

execlp("ls" , "ls" , "-l", NULL);

fprintf(stderr , "[%d],[%s]",i,strerror(errno));

return -1;

}

else if(i==1)  //调用自定义程序

{

execlp("./testt" , "testt" , "ni" , "hao", NULL);

fprintf(stderr , "[%d],[%s]",i,strerror(errno));

return -1;

}else if(i==2)  //调用会出现段错误的程序

{

execlp("./testt" , "testt" , "core" , "core", NULL);

fprintf(stderr , "[%d],[%s]",i,strerror(errno));

return -1;

}

break;

}

}

while(1) //只有父进程会走这里的代码 , 因为子进程execlp会替换掉后续代码

{

int status;

int wait_pid = waitpid(-1 , &status, 0);       

sleep(2);

if(wait_pid == -1) //==-1说明没子进程了

{

printf("子进程已经全部退出了");

break;

}

else if(wait_pid>0) //指定子进程退出了 返回pid

{

if(WIFEXITED(status))           //正常退出了

{

printf("son normal exit, status is [%d]\n", WEXITSTATUS(status));

}

else if(WIFSIGNALED(status))    //被信号杀死

printf("son is killed by signal , signo is [%d]\n", WTERMSIG(status));

}

}

else if(wait_pid==0)

{

continue;

}

}

return 66;

}

holo@holo:~/test$ ./gongxiang 1.txt

son:[48608] , buf:[nihaoa]

father:[48607]holo@holo:~/test$ ./waitpid

father:pid = [49054]

总用量 252

-r---w---T 1 holo holo   110 10月  4 04:07 11111.tvsc

-rwxrwxr-x 1 holo holo   110 10月  4 04:07 112.asda

-rwxrwxr-x 1 holo holo     0 10月  4 08:26 11.log

-rw-rw-r-- 1 holo holo     7 10月  7 22:06 1.txt

-rwxrwxr-x 1 holo holo   110 10月  4 04:07 1.txtad

-rwxrwxr-x 1 holo holo     0 10月  4 07:49 2.txt

-rwxrwxr-x 1 holo holo  8681 10月  4 05:55 a.out

---xrw---- 1 holo holo   121 10月  4 04:08 asdae

----r-S--- 1 holo holo   121 10月  4 04:08 asd.tt

-rwxrwxr-x 1 holo holo  9085 10月  4 13:14 dir

-rw-rw-r-- 1 holo holo  1120 10月  4 13:15 dir.c

-rwxrwxr-x 1 holo holo  8723 10月  4 08:36 dup2

-rw-rw-r-- 1 holo holo   456 10月  4 08:36 dup2.c

-rw-r--r-- 1 holo holo 19475 10月  4 06:49 eset nu

-rwxrwxr-x 1 holo holo  8726 10月  4 18:32 execl

-rw-rw-r-- 1 holo holo   389 10月  4 18:29 execl.c

-rwxrwxr-x 1 holo holo  9145 10月  7 22:05 gongxiang

-rw-rw-r-- 1 holo holo   743 10月  7 22:05 gongxiang.c

-rw-rw-r-- 1 holo holo   436 10月  4 04:08 lseek.c

drwxrwxr-x 2 holo holo  4096 10月  5 19:35 pipe

-rwxrwxr-x 1 holo holo  8825 10月  4 04:01 read

-rw-rw-r-- 1 holo holo   501 10月  4 04:07 read.c

lrwxrwxrwx 1 holo holo     6 10月  4 06:54 read.c.s -> read.c

-rwxrwxr-x 1 holo holo    14 10月  4 08:36 see.log

-rwxrwxr-x 1 holo holo  8633 10月  4 06:14 stat

-rw-rw-r-- 1 holo holo   287 10月  4 06:14 stat.c

-rwxrwxr-x 1 holo holo  8572 10月  4 18:31 testt

-rw-rw-r-- 1 holo holo   164 10月  4 18:31 testt.c

-rwxrwxr-x 1 holo holo  8834 10月  4 05:46 tty

-rw-rw-r-- 1 holo holo   427 10月  4 05:55 tty.c

-rwxrwxr-x 1 holo holo   110 10月  4 04:06 vim

-rwxrwxr-x 1 holo holo  8828 10月  4 19:23 wait

-rw-rw-r-- 1 holo holo   953 10月  4 19:22 wait.c

-rwxrwxr-x 1 holo holo  9046 10月  7 18:16 waitpid

-rwxrwxr-x 1 holo holo  9097 10月  7 17:48 waitpid1

-rwxrw-r-- 1 holo holo  2353 10月  7 17:47 waitpid1.c

-rwxrw-r-- 1 holo holo  1586 10月  7 18:16 waitpid.c

-r--rwS--- 1 holo holo   121 10月  4 05:28 xxx

father:pid = [49054]

[testt]

[ni]

[hao]

father:pid = [49054]

[testt]

[core]

[core]

son normal exit, status is [0]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1074026.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql数据库root密码忘记了,这里有一个简单的方法可以解决

mysql安装久了&#xff0c;就容易忘记root密码&#xff0c;那么下面这个找回密码的方法将解决你的问题&#xff1a; 特别注意事项&#xff1a; 本方法只适合mysql数据库密码遗忘&#xff08;忘记了&#xff09; 这个解决方案的前提是你的电脑里安装了navicat&#xff08;其他…

阿桂天山的技术小结:Sqlalchemy+pyodbc连接MSSQL server测试

话不多说,有图有源码 1)确保本机安装了sql server对应的odbc驱动 在控制面板的管理工具中可以查&#xff1a;数据源(ODBC) 我这里已经安装了,如果没有安装可以自行下载安装 2)连接MsSql Server代码 # -*- coding: utf-8 -*- __author__ "阿桂天山"#----------判…

【位图+布隆过滤器】

目录 一、位图1.1位图的概念1.2位图的实现 二、布隆过滤器2.1布隆过滤器的概念2.2布隆过滤器的实现 三、位图的扩展--找只出现一次的数 一、位图 1.1位图的概念 所谓位图&#xff0c;就是用每一位来存放某种状态&#xff0c;适用于海量数据&#xff0c;数据无重复的场景。通常…

IDEA 配置 云服务器远程部署

目录 参考资料远程部署与远程开发远程连接配置配置成功&#xff1a;同步文件自动更新文件配置自动更新文件参数调整正确运行问题1&#xff1a;运行mvn spring-boot:run之后一直卡在第一条下载问题2&#xff1a;运行成功后访问不到问题3&#xff1a;无法配置远程开发 参考资料 …

Vmware下载安装以及创建虚拟机

虚拟机有很多种&#xff0c;常见的有VMware Workstation、VirtualBox等。这里以VMware Workstation为例&#xff0c;可在官网下载并安装。 目录 一、下载 二、安装 三、创建虚拟机 四、Ubuntu安装 下载ISO镜像 Ubuntu 使用ISO镜像 一、下载 第一步那就是要下载一个工具&…

基于Java的在线问卷调查系统的设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技…

PTA 7-2 彩虹瓶(单调栈)

题目 彩虹瓶的制作过程&#xff08;并不&#xff09;是这样的&#xff1a;先把一大批空瓶铺放在装填场地上&#xff0c;然后按照一定的顺序将每种颜色的小球均匀撒到这批瓶子里。 假设彩虹瓶里要按顺序装 N 种颜色的小球&#xff08;不妨将顺序就编号为 1 到 N&#xff09;。…

一文3000字从0到1使用pytest-xdist实现分布式APP自动化测试

目录 01、分布式测试的原理 02、测试项目 03、环境准备 04、搭建步骤 05、分布式执行 06、测试报告 不知道大家有没有遇到这样一种情况&#xff0c;实际工作中&#xff0c;app自动化测试的用例可能是成百上千条的&#xff0c;如果放在一台机器上跑&#xff0c;消耗的时间…

数据结构--算法、数据结构的基本概念

&#x1f4d5;参考&#xff1a;王道 一、算法的基本概念 1.程序数据结构算法 2.算法的特性 &#xff08;1&#xff09;有穷性 执行有穷步之后结束&#xff0c;且每一步都可在有穷时间内完成。 &#xff08;2&#xff09;确定性 &#xff08;3&#xff09;可行性 可通过已经实…

深度学习基础知识 register_buffer 与 register_parameter用法分析

深度学习基础知识 register_buffer 与 register_parameter用法分析 1、问题引入2、register_parameter()2.1 作用2.2 用法 3、register_buffer()3.1 作用3.2 用法 1、问题引入 思考问题&#xff1a;定义的weight与bias是否会被保存到网络的参数中&#xff0c;可否在优化器的作用…

解决PlatformIO下载速度慢以及容易出错(解决vscode下载缓慢问题)

Content 问题描述&#xff1a;依赖下载缓慢问题解决&#xff1a;为vscode配置代理端口 问题描述&#xff1a;依赖下载缓慢 Arduino对于ESP32的开发提供了众多的库&#xff0c;但是Arduino IDE编译速度过于缓慢的问题属实让人难受。 为此我们使用vscode中的platformIO插件&…

斑馬打印機打印中文

创建项目 首先說一下&#xff0c;本文章是借鉴了其他大佬的文章&#xff0c;然后我记录一下的文章。 首先创建好一个.net framework的winform项目。 这里面主要用到两个库文件&#xff1a; Fnthex32.dll、LabelPrint.dll。 Fnthex32这个有8位参数和9位参数的&#xff0c;我这…

数据结构--》解锁数据结构中树与二叉树的奥秘(一)

数据结构中的树与二叉树&#xff0c;是在建立非线性数据结构方面极为重要的两个概念。它们不仅能够模拟出生活中各种实际问题的复杂关系&#xff0c;还常被用于实现搜索、排序、查找等算法&#xff0c;甚至成为一些大型软件和系统中的基础设施。 无论你是初学者还是进阶者&…

三角函数和角公式

该文章对三角函数和角公式做了多种证明&#xff1a;https://mp.weixin.qq.com/s?__bizMzI4ODYwNTM3Ng&mid2247484178&idx1&sn1f6e04c50ae30b63198201db3d9a4f05&chksmec3a96bddb4d1fab4baf8188ca6ba60d8d4364be4f08dc53e13b2e4cdfcec4fdb43928108001&scen…

10_9C++

X-mind #include <iostream> using namespace std; class Per { private:string name;int age;float *height;float *weight; public:Per()//无参构造函数{cout << "无参构造函数" << endl;}Per(string name,int age,float *height,float *weight)…

MySQL——单表与多表查询练习

MySQL 一、练习一二、练习二 一、练习一 这里首先将素材创建完毕&#xff0c;首先创建一个数据库并使用&#xff0c;这里我创建的数据库名为worker&#xff1a; 紧接着我们创建数据库表并创建表结构&#xff1a; 查看表结构&#xff1a; 接着我们导入数据&#xff1a; 这…

【LeetCode】剑指 Offer Ⅱ 第6章:栈(6道题) -- Java Version

题库链接&#xff1a;https://leetcode.cn/problem-list/e8X3pBZi/ 类型题目解决方案栈的应用剑指 Offer II 036. 后缀表达式模拟 栈 ⭐剑指 Offer II 037. 小行星碰撞分类讨论 栈 ⭐单调栈剑指 Offer II 038. 每日温度单调栈 ⭐剑指 Offer II 039. 直方图最大矩形面积单调栈…

Excel·VBA使用ADO合并工作簿

之前文章《ExcelVBA合并工作簿&#xff08;7&#xff0c;合并子文件夹同名工作簿中同名工作表&#xff0c;纵向汇总数据&#xff09;》处理合并工作簿问题&#xff0c;代码运行速度比较慢 而《ExcelVBA使用ADO读取工作簿工作表数据》读取数据非常快&#xff0c;那么是否可以使用…

vue3+elementui实现表格样式可配置

后端接口传回的数据格式如下图 需要依靠后端传回的数据控制表格样式 实现代码 <!-- 可视化配置-表格 --> <template><div class"tabulation_main" ref"myDiv"><!-- 尝试过在mounted中使用this.$refs.myDiv.offsetHeight,获取父元素…

Redis安装及key、string操作

安装 在官网下载的数据包上传到Linux家目录 Install Redis from Source | Redis wget https://download.redis.io/redis-stable.tar.gz tar -xzvf redis-stable.tar.gz cd redis-stable make 编译后出现以下提示后输入make install 出现以下提示则安装成功 输入redis-sever启…