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。