fork函数:用来创建进程:
进程的概念:一个可执行程序(多个进程可以共享一个可执行程序)
进程:定义为一个可执行程序的实例
在一个进程中,可以用fork创建一个子进程,当该子进程创建时,它从fork指令的下一条开始执行与父进程相同的代码
说白了:fork函数产生了一个和当前进程完全一样的新进程, 并和当前进程一样从fork中返回
原来一条执行通路, 变成了两条通路
之后的代码谁执行不一定
fork函数简单范例:
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>void sig_usr(int signo)
{
printf("收到了SIGUSR1信号, 进程id为%d\n",getpid());
} int main()
{
pid_t pid;
if(signal(SIGUSR1, sig_usr) == SIG_ERR)
{
printf("无法捕捉");
exit(1);
}
pid =fork();
if(pid< 0)
{
printf("子进程创建失败\n");
exit(1);
}
for(;;)
{
sleep(1);
printf("休息一秒, 进程id=%d!\n", getpid());
}
printf("再见了!\n");
return 0;
}
在这个代码中,使用strace -e trace=signal -p 父进程pid,跟踪父进程 kill掉子进程,父进程会收到SIGCHILD,
子进程变成了僵尸进程。状态为Z
僵尸进程的产生, 解决, SIGCHILD
僵尸进程的产生:在Unix系统中,一个子进程结束了但是它的父进程还活着
但是它的父进程没有调用wait/waitpid函数进行额外的处理,那么这个子进程就会变成僵尸进程
僵尸进程:以及被终止,但没有被内核丢掉,因为内核认为父进程可能需要子进程信息。Z+
作为开发者, 坚决不允许僵尸进程。
解决僵尸进程:
a) kill 掉父进程
b)重启电脑
SIGCHILD信号:一个进程被终止或者停止时,这个信号被发送给父进程;
所以,对源码中有fork行为的进程,我们应该拦截SIGCHILD信号
解决范例:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>void sig_usr(int signo)
{
int status;
switch(signo)
{
case SIGUSR1:
printf("收到了SIGUSR1信号, 进程为%d\n", getpid());
break;
case SIGCHLD:
// waitpid 为了获取子进程的终止状态
//第一个参数为-1 表示等待任何子进程
//第二个参数 保存子进程的状态信息
//第三个参数 提供额外选项, WNOHANG表示不要阻塞, 立即返回
printf("收到了SIGCHILD信号, 进程id=%d\n", getpid());
pid_t pid = waitpid(-1, &status, WNOHANG);
if(pid==0)
return;
if(pid==-1)
return;
return;
break;
}
}int main()
{
pid_t pid;
if(signal(SIGUSR1, sig_usr) == SIG_ERR)
{
printf("无法捕捉");
exit(1);
}
pid =fork();
if(pid< 0)
{
printf("子进程创建失败\n");
exit(1);
}
for(;;)
{
sleep(1);
printf("休息一秒, 进程id=%d!\n", getpid());
}
printf("再见了!\n");
return 0;
}
fork函数进一步认识:
fork()产生新进程的速度非常快, fork()产生的新进程并不复制原进程的内存空间,而是和
原进程(父进程)一起共享一个内存空间,但这个内存空间的特性是“写时复制”也就是说:
原来的进程和fork() 出来的子进程可以同时,自由的读取内存, 但如果子进程(父进程)对内存
进行修改的话,那么这个内存就会复制一份给改进程单独使用,以免影响到共享这个内存空间的其他进程
fork代码:
fork返回两次, 父进程返回一次, 子进程返回一次, 子进程pid==0,
父进程返回值>0。