僵死进程&进程替换&信号

僵死进程

一个进程执行结束,但是进程的PCB没有被系统释放。进程结束后,在PCB中还要保存进程退出码,以备其父进程获取其退出码。

父进程未结束,子进程结束,并且父进程没有获取子进程的退出码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
#include<sys/stat.h>
int main()
{
pid_t pid=fork();
assert(pid!=-1);
if(pid==0)
{
printf("child starting \n");
sleep(10);
printf("child over\n");
}
else
{
printf("father starting \n");
sleep(20);
printf("father over");
}

}

如何处理僵死进程:

父进程获取子进程的退出状态:

1
2
3
pid_t wait(int *result);	本身会阻塞,直到任意一个子进程退出
返回处理的进程的pid
result是获取进程的退出码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
#include<sys/stat.h>
int main()
{
pid_t pid=fork();
assert(pid!=-1);
if(pid==0)
{
printf("child starting:%d \n",getpid());
sleep(10);
printf("child over\n");
}
else
{
pid_t id=wait(NULL);
printf("id=%d\n",id);
printf("father starting \n");
sleep(20);
printf("father over");
}

}

进程替换

创建子进程以后,子进程执行的还是父进程的指令。

一个进程能够去执行另一份程序,fork()之后,子进程进行进程替换,父进程继续执行其指令,子进程就可以执行另一份指令。

main.c代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
#include<sys/stat.h>
int main()
{
pid_t pid=fork();
assert(pid!=-1);
if(pid==0)
{
printf("i am child:my pid=%d\n",getpid());
//进程替换
int res=execl("./test","./test","hello","world",(char *)0);

int i=0;
for(;i<5;++i)
{
printf("i am child\n");
sleep(1);
}

printf("exec error\n");
}
else
{
int i=0;
for(;i<10;++i)
{
printf("i am father\n");
sleep(1);
}
}
exit(0);
}

test.c代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>

int main(int argc,char *argv[])
{
printf("i am test:my pid=%d\n",getpid());
int i=0;
for(;i<argc;++i)
{
printf("argv[%d]=%s\n",i,argv[i]);
}
exit(0);
}

由结果可以看出来,执行进程替换,仅仅是替换进程所执行的指令集,并没有创建新的进程。

写时拷贝技术:fork之后,往往子进程会执行exec

fork之前打开的文件,在子进程执行了exec之后,能不能通过此文件描述符访问该文件?

1
2
3
4
5
6
7
8
9
1、fork后子进程将复制父进程的数据段、BSS段、代码段、堆空间、栈空间和文件描述符。
2、在执行exec系列函数时,默认情况下,新代码可以使用在父进程中fork前打开的文件描述符,即执行exec系列函数时,并不关闭进程原来打开的文件。
所以,使用exec执行新的程序时,仍然可以使用原来的文件。那么有没有什么办法关闭呢?答案是有的:
1、如果子进程执行的新程序为自己编译后的程序,可以在子进程程序中用循环关闭从0到NOFILE的文件描述符。NOFILE是一个系统宏定义,不同的系统有不同的值。
2、在父进程中使用fcntl函数设置文件描述符的CLOEXEC项:fcntl(fd,F_SETFD,FD_CLOEXEC)。FD_CLOEXEC是fd close on exec的缩写,设置了此项后在执行exec系列函数时,将自动关闭设置了此项的fd。
作者:知乎用户
链接:https://www.zhihu.com/question/49609592/answer/314041691
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

exec函数

fork函数是用于创建一个子进程,该子进程几乎是父进程的副本,而有时我们希望子进程去执行另外的程序,exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行脚本文件。

1
2
3
4
5
6
int execl(char *pathname,char *argv0,char *argv1,...,(char *)0);
int execv(char *pathname,char *argv[]);
int execle(char *pathname,char *argv0,...,(char *)0,char envp[]);//加了envp,可以传递环境变量
int execlp(char *filename,char *argv0,...,(char *)0);
int execvp(char *filename,char *argv[]);
5个都是库函数,都是对execve的封装
1
int execve(char *pathname,char *argv[],char *envp[]);这一个是系统调用

信号

信号是系统预先定义好的某些特定的事件,信号可以被产生,也可以被接收,产生和接收的实体都是进程。信号的作用就是一个进程向另一个进程通知某一事件的发生。

1、当进程收到信号时,他所处理的:默认 忽略 捕获(自定义)

编写程序实现,当进程在键盘输入Ctrl+c时,当前进程输出一个hello world,第二次收到结束进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<signal.h>
//fun函数会在第一次收到信号时执行,他的执行肯定是在第二次收到信号之前执行的
void fun(int sign)
{
printf("hello world:%d\n",sign);
signal(SIGINT,SIG_DFL);
}
int main()
{
signal(SIGINT,fun);//signal仅仅完成信号相应方式的修改
while(1)
{
sleep(2);
printf("i am main,running\n");
}
}

2、在程序中如何给一个进程发送一个信号

1
2
3
4
int kill(pid_t pid,int sigtype);
pid指定发送给那个进程
sigtype指定发送的信号类型
kill(getpid(),SIGINT);<==>raise(SIGINT);进程给自己发送一个信号

通过kill实现kill命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>

/*
kill 默认 15
-9 9
-stop 19
*/
int main(int argc,char *argv[])
{
if(argc<2)
{
printf("please input process's pid.again\n");
exit(0);
}
int sigtype=15;
int i=1;
for(;i<argc;++i)
{
if(i==1)
{
if(strncmp(argv[1],"-9",2)==0)
{
sigtype=9;
continue;
}
if(strncmp(argv[1],"-stop",5)==0)
{
sigtype=19;
continue;
}
}
int pid=0;
sscanf(argv[i],"%d",&pid);
if(-1==kill(pid,sigtype))
{
perror("kill error");
}
}
exit(0);
}

利用信号解决僵死进程的方法

父进程需要调用wait方法,子进程结束会给父进程发送SIGCHLD,当父进程接收到SIGCHLD信号时,再调用wait方法,父进程不会阻塞。

父进程那些代码能够保证是在接收到信号之后执行,给SIGCHLD信号绑定一个信号处理函数(次函数会在接收到SIGCHLD信号之后才会被调用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<signal.h>
#include<fcntl.h>
void fun(int sign)
{
pid_t pid=wait(NULL);
printf("fun :pid=%d\n",pid);
}
int main()
{
signal(SIGCHLD,fun);
pid_t pid=fork();
assert(pid!=-1);

if(pid==0)
{
printf("child start\n");
sleep(5);
printf("child end\n");
}
else
{
printf("father start\n");
sleep(10);
printf("father end\n");
}

}