系统编程之进程,父子进程fork()函数

来源:互联网 发布:get it与got it口语 编辑:程序博客网 时间:2024/05/02 00:01
并发
通过处理多个任务,即同时执行多条指令流

1.进程
进程是一个动态的概念,程序是一个静态的概念。

相关命令和软件
ps
top
htop
pstree
kill


进程ID

PID用于操作系统唯一标识的一个进程。
//获取当前进程的ID
pid_t getpid(void);

//获取当前父进程(创建者)id
pid_t getppid();


进程创建

system
创建一个子进程执行指定的shell命令,完成之后,子进程退出.

int system(const char *command);

fork()
通过对当前进程复制,产生一个子进程。
pid_t fork(void);

练习:使用fork()函数,创建一个子进程,在创建一个孙进程,各自打印自身的PID值

程序代码如下:

在这个程序里面为了验证刚开始的时候一段公共程序,父进程子进程都会执行,他们是两者的公共程序,所以我们在fork()后面添加了一句话

,即打印本进程id,所以程序程序中打印了两次。

#include <stdio.h>#include <unistd.h>#include <errno.h>int main(int argc,char *argv[]){   pid_t pid = fork();         //子进程返回的是0,父进程返回子进程的id   printf("parent pid3:%d\n",getpid());//这句话既属于父进程有属于子进程   if(pid == 0)   {      //获取子进程的id      printf("child pid:%d\n",getpid());      //创建孙进程      pid_t pid1=fork();      if(pid1==0)      {          // 获取孙进程的pid号           printf("the sonpid is :%d",getpid());      }   }   else if(pid>0)   {    //获取子进程的id     printf("the childpid  getfrom parent process:%d\n",pid);    //获取父进程的id     printf("the parentpid getfrom parent process:%d\n",getpid());   }      else   {     fprintf(stderr,"can't fork.error %d\n",errno);        }        return 0;}

 程序的讲解已经体现在注释里面,我们可以通过注释解析程序;

执行的结果如下:

@lE431:$ ./a.out

parent pid3:9605                                                      这里是公共程序的运行结果                               
the childpid  getfrom parent process:9606           这里是父进程运行的结果
the parentpid getfrom parent process:9605         这里是父进程运行的结果
parent pid3:9606                                                      这里是公共程序的运行结果
child pid:9606                                                           这里是子进程运行的结果

the sonpid is :9607                                                   治理是孙进程运行的结果

@lE431:$//                                                                 此处也是一个进程(这是系统的shell进程),这个进程打印的结果有可能出现在前面,

                                                                                     那是因为这几个程序的执行先后顺序不一致




注意:
    fork()通过复制父进程产生子进程
    父子进程执行顺序是异步的(无序的)
    同一个进程内的指令按照指令的前后顺序从前向后执行
    为了保证fork()之后父子进程分别执行各自的代码,fork()后的代码需要通过判断fork()返回值的不同进行条件执行


    进程是linux操作系统的一种并发机制,即同时处理多个任务

   

exec 函数族、

       int execl(const char *path, const char *arg, ... /* (char  *) NULL */);       int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);       int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);       int execv(const char *path, char *const argv[]);       int execvp(const char *file, char *const argv[]);       int execvpe(const char *file, char *const argv[],char *const envp[]);

总结:

1.exec函数族均通过替换当前进程空间来执行指定的可执行程序。

2.exec函数中各个函数的使用差异性主要表现在传命令行参数的方式,传递搜索路径的方式上。

3.函数名第五个参数为i的,表示以列表的形式传递命令行参数,类似于arg1,arg2,arg3,arg,....NULL。

4.函数名第五个字符为v的,表示以数组的方式传递命令行参数,即讲明领航参数存在一个如一个字符指针数组中,并且以NULL结尾。

5.函数名第6个字符为p的,表示去PATH路径下查找该可执行的文件。

6.函数名第6个字符为e的,表示去最后一个参数指定的路径下查找可执行文件。

7.exec函数族中所有函数的第一个参数均为可执行文件名;



练习:使用fork()和exec()函数族模拟完成一个简单的shell解释器。

1.在该shell解释器中,输入exit命令,退出shell;

2.可以执行带选项和参数的命令。

while(1){    //1.打印命令提示符    //2.读入待执行命令(必须是内置的sheel命令或者是已存在的某个可执行文件)     //3.fork()一个子进程去执行该命令}


    父子进程的执行顺序

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

pid_t   wait(int   *status);

//等待指定的子进程的退出

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


 

我们知道在unix/linux中正常情况下,子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程什么时候结束。当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态;

   孤儿进程

简单来说就是:父进程先于子进程退出,此时该子进程变为孤儿进程,孤儿进程会自动被1号进程即init进程收养

孤儿进程:一个父进程退出,而它的一个或者多个子进程还在运行,那么那些子进程将会称为孤儿进程。孤儿进程将被init进程(进程号为1的进程收养)

并有init进程对他们完成状态收集工作

下面的是孤儿进程的实例,先强行杀死父进程,此时就没有父进程来收养子进程,子进程变成孤儿进程,

此时的孤儿进程陷入死循环,孤儿进程的结束不能用  (ctrl+c) ,只能用kill  + (子进程的进程号)来杀死孤儿进程,

之前可以用ps -aux | grep (可执行文件名),来查看进程的进程号




#include <stdio.h>#include <string.h>#include <unistd.h>#include <errno.h>#include <stdlib.h>int main(int argc,char *argv[]){  pid_t  pid = fork();  if(pid==0)       {     while(1)     {         printf("i am child!\n");         sleep(1);     }     }else if(pid>0)   {       printf("i am parent!\n");       exit(0);   }else{    fprintf(stderr,"can't fork error %d\n",errno);}    return 0;}





   僵尸进程

简单来说就是:子进程退出后,没有被父进程wait()的进程,便会变为僵尸进程

一个进程使用fork()创建子进程,如果子进程退出,而父进程并没有调用wait()或者waitpid()获取子进程的状态信息,那么子进程的描述符仍然存在与系统中

这种进程称之为僵死进程。





#include <stdio.h>#include <string.h>#include <unistd.h>#include <errno.h>#include <stdlib.h>int main(int argc,char *argv[]){  pid_t  pid = fork();  if(pid==0)       {         printf("i am child!\n");         exit(0);                   //子进程只要死掉就会变成僵死状态。       }else if(pid>0)   {       printf("i am parent!\n");    //父进程休眠,父进程只要醒过来,就会在此回收子进程。       sleep(60);   }else{    fprintf(stderr,"can't fork error %d\n",errno);}    return 0;}

注意:此处的僵死进程只是暂时的,如果父进程的从休眠中醒过来,那么子进程会被再次收养,则子进程从退出时的僵死状态,被回收,编程正常的程序,并由睡醒的父进程收养。


总结: 子进程只要是活的(父进程提前退出),便有人愿意去收养,无论是init进程,还是父进程;

             但是如果子进程死了(子进程提前退出),只有父亲愿意收留它。

看来,这个父子进程之间貌似真的存在类似于真实的血缘关系;



僵尸进程的危害场景:

        例如有一个进程,它定期的产生一个子进程,这给子进程需要做的事情很少,做完它该做的事情之后就退出了,因此,这个子进程的生命周期很短,

但是,如果,父进程只管生成新的子进程,至于子进程退出的之后的事情,则一概不闻,这样,系统上运行上一段时间之后,系统中就会存在很多的僵死

进程,若用ps命令查看的话,就会看到很多状态为Z的进程,严格的来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,

当我们需求如何消灭系统中的大量僵死进程的时候,答案就是产生大量的僵死进程的元凶除掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元

凶进程,他产生的僵死进程就变成里孤儿进程,这些孤儿进程将会被 (init进程) 接管。init进程会wait()这些孤儿进程,释放他们的占用的系统进程表中的资源,

这样,这些已经僵死的孤儿进程,就能被init收拾了;



父进程循环创建子进程,子进程退出,造成多个僵尸进程,程序如下:

#include <stdio.h>#include <string.h>#include <unistd.h>#include <errno.h>#include <stdlib.h>int main(int argc,char *argv[]){  pid_t  pid;  while(1)  {  pid=fork();  if(pid==0)       {         printf("i am child!\n");         exit(0);                   //子进程只要死掉就会变成僵死状态。       }      else if(pid>0)   {       printf("i am parent!\n");    //父进程休眠,父进程只要醒过来,就会在此回收子进程。       sleep(5);       continue;   }   else   {    fprintf(stderr,"can't fork error %d\n",errno);    exit(1);    }  }    return 0;}


大量的僵尸占用着资源,我们该怎么解决这样的问题呢?

(1)通过信号机制

子进程退出时,向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait()进行处理僵尸进程

。测试程序如下:

#include <stdio.h>#include <string.h>#include <unistd.h>#include <errno.h>#include <stdlib.h>#include <signal.h>#include <sys/types.h>#include <sys/wait.h>static void sig_child(int signo);int main(int argc,char *argv[]){  pid_t  pid;  //创建捕捉子进程退出信号  signal(SIGCHLD,sig_child);  pid=fork();  if(pid==0)       {         printf("i am child %d!\n",getpid());         exit(0);                   //子进程只要死掉就会变成僵死状态。       }      else if(pid>0)   {       printf("i am parent!\n");    //父进程休眠,//等待子进程先退出父进程只要醒过来,就会在此回收子进程。       sleep(5);       printf("parent is coming back\n");   }   else   {    fprintf(stderr,"can't fork error %d\n",errno);    exit(1);    }      return 0;}static void sig_child(int signo){  pid_t pid;  int stat;  //处理僵尸进程  while((pid=waitpid(-1,&stat,WNOHANG)>0))    {      printf("child %d terminated.\n",pid);    }}

测试结果如下:

i am parent!
i am child 7008!
child 1 terminated.
parent is coming back


(2)fork()

《Unix环境高级编程》8.6节说的非常详细,原理是将子进程编程孤儿进程,从而其父进程变为init进程,通过init进程可以处理僵尸进程

#include <stdio.h>#include <string.h>#include <unistd.h>#include <errno.h>#include <stdlib.h>#include <sys/types.h>#include <sys/wait.h>int main(int argc,char *argv[]){  pid_t  pid; //创建子进程   pid=fork();  //进入子进程  if(pid==0)       {                 printf("i am child 1:%d my father is %d!\n",getpid(),getppid());                 //子进程再创建孙进程           pid=fork();           if(pid>0)           {            printf("i am child 2:%d my father is %d!\n",getpid(),getppid());               printf("child is exited\n");             exit(0);           }           else if(pid==0)           {//son process            //孙进程睡眠3s钟,确保子进程已经退出,这样,孙进程就变成了孤儿,孙进程的父亲就是init进程了              sleep(3);              printf("i am son process:%d,my father is %d",getpid(),getppid());              exit(0);           }             }     // 父进程等待第子进程退出   if(pid != waitpid(pid,NULL,0))   {    printf("i am parent!\n");        fprintf(stderr,"can't fork error %d\n",errno);    exit(1);    }      return 0;}




注意:孤儿进程被1号进程即init进程收养后,不会变成僵尸进程,1号进程为所有的子进程收拾

    信号



总结:编写更加完善的shell命令解释器。自学waitpid()函数的使用












阅读全文
1 0