系统编程之进程,父子进程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()函数的使用
- 系统编程之进程,父子进程fork()函数
- fork 之 父子进程
- fork父子进程共享
- fork()----父子进程共享
- 5.8fork父子进程
- linux系统编程之进程(四):wait/waitpid函数与僵尸进程、fork 2 times
- Linux系统编程(8)—— 进程之进程控制函数fork
- linux系统编程之进程(四):wait/waitpid函数与僵尸进程、fork 2 times
- Linux高编之进程--------fork函数的同步与异步(兄弟子进程和父子孙进程示列)
- Linux系统编程-----进程fork()
- linux系统编程之进程(三):进程复制fork,孤儿进程,僵尸进程
- linux系统编程之进程(三):进程复制fork,孤儿进程,僵尸进程
- linux系统编程之进程(三):进程复制fork,孤儿进程,僵尸进程
- 十、Linux系统编程-进程(三)父子进程共享文件、fork和vfork、exit和_exit、atexit注册退出事件
- Linux_父子进程与fork
- linux系统编程之进程(二):fork函数相关总结
- linux系统编程之进程(二):fork函数相关总结
- linux系统编程之进程(二):fork函数相关总结
- SSH整合其他方式(没有hibernate的核心配置文件)
- POJ1260-Pearls(dp)
- HBase源码分析之用户
- VSCode代码修改延迟的问题
- 在结构化的程序设计中,模块划分的原则是()
- 系统编程之进程,父子进程fork()函数
- centos 7配置系统调度isolcpus(软中断绑定)
- JavaScript高级程序设计--基本概念
- 51Nod-1073 约瑟夫环
- 【JAVA基础】equal和hashcode的区别
- Android给打开商店给好评代码
- POJ 2054
- LED显示屏花屏问题
- 选择排序