进程控制3——进程终止和等待
来源:互联网 发布:聚划算淘宝商城特价区 编辑:程序博客网 时间:2024/05/19 23:55
一个进程不可能永远的存在,必然存在着结束,我们需要进行一些操作来终止这些进程。而父进程创建子进程后,如何知道子进程什么时候终止?如何知道子进程怎么终止?所以又带来了进程等待的问题。
下面先将一些关于进程终止 的函数,
exit,_exit都是用于终止进程的函数
区别:
_exit: 直接使进程停止,清除其使用的内存,并清除缓冲区中内容
exit:在停止进程之前,要检查文件的打开情况,并把文件缓冲区中的内容写回文件才停止进程。
exit()
表头文件: #include <stdlib.h>定义函数: void exit(int status);
函数说明:exit()用来正常终结目前进程的执行,并把参数status返回给父进程,而进程所有的缓冲区数据会自动写回并关闭未关闭的文件。
_exit()
表头文件: #include<unistd.h>定义函数: void _exit(int status);
函数说明
此函数调用后不会返回,并且会传递SIGCHLD信号给父进程,父进程可以由wait函数取得子进程结束状态。
这两种退出方式没有太多好说的,自己简单试一试就可以发现一些区别了。
子进程比父进程先退出:僵尸进程
僵尸进程指的是那些虽然已经终止的进程,但仍然保留一些信息,等待其父进程为其收尸。
僵尸进程产生的过程:
1.父进程调用fork创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中(进程描述符占有极少的内存空间)。
2.子进程的状态变成EXIT_ZOMBIE,并且向父进程发送SIGCHLD 信号,父进程此时应该调用 wait() 系统调用来获取子进程的退出状态以及其它的信息。在 wait 调用之后,僵尸进程就完全从内存中移除。
3.因此一个僵尸存在于其终止到父进程调用 wait 等函数这个时间的间隙,一般很快就消失,但如果编程不合理,父进程从不调用 wait 等系统调用来收集僵尸进程,那么这些进程会一直存在内存中。
pid_t pid = fork(); switch (pid){ case -1: perror ("fork"); break; case 0: // 子进程 printf ("我是子进程,我的Id 是%d\n", getpid()); exit(0); default: // 父进程 printf ("我是父进程,Id = %d\n", getpid()); while (1); break;}
我们继续用那条指令抓一下进程,会发现在子进程先结束后留下了,它的ID号还在,后面加上了一个<defunct>
,这个单词的意思是死者,很明显,这个进程就是我们所说的僵尸进程了。如果不进行一定处理的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
子进程先于父进程结束,如果不做适当处理会产生僵尸进程,那父进程先于子进程结束呢?
若父进程比子进程先终止,则该父进程的所有子进程的父进程都改变为init进程。我们称这些进程由init进程领养。其执行顺序大致如下:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止的进程的子进程,如果是,则该进程的父进程ID就更改为1(init进程的ID); 有init领养的进程不会称为僵死进程,因为只要init的子进程终止,init就会调用一个wait函数取得其终止状态。这样也就防止了在系统中有很多僵死进程。
下面是一个父进程提前结束的案例:
pid_t pid = fork(); switch (pid) { case -1: perror ("fork"); break; case 0: // 子进程 printf ("我是子进程,我的Id 是%d\n", getpid()); while (1) break; default: // 父进程 printf ("我是父进程,Id = %d\n", getpid()); printf ("我走啦\n"); // while (1); break; }
从图中不难看出,父进程已经结束了,而子进程依旧存在,但此时接手它的已经成为了1,1是谁?没错1就是init进程,我们通常所说的祖父进程,从此这个子进程不再受出了1之外的任何人控制了。
很明显父进程先于子进程结束,产生的危害就小多了,甚至还给我们一个思路,一个让程序运行在后台的思路,就让我们产生了一个关于守护进程的想法。
守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。
下面是一个创建守护进程的思路:
int daemonize(int nochdir, int noclose){ // 1、创建子进程,关闭父进程 pid_t pid = fork(); if (pid > 0) { exit(0); } else if (pid < 0) { return -1; } // 2、设置文件的掩码, mode & ~umask umask(0); // 3、设置新的会话: 脱离当前会话和终端的控制 if (setsid() < 0) { return -1; } if (nochdir == 0 ) { // 4、改变当前的工作目录 if (chdir("/") < 0) { return -1; } } // 标准输入、关闭标准输出、标准错误 close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); if (noclose == 0) { // 重定向标准输入、关闭标准输出、标准错误 open("/dev/null", O_RDONLY); // 0 open("/dev/null", O_RDWR); // 1 open("/dev/null", O_RDWR); // 2 } return 0;}
创建一个守护进程需要经历这么几步,创建子进程并退出父进程,设置文件掩码,设置新会话,改变工作目录,关闭并重定向标准输入,标准输出和标准错误。虽然有这样一个函数daemon()可以帮助我们完成这一些,但一些原理我们最好还是需要知道的。
上文我们提到了僵尸进程,知道了它的危害性,既然危害这么大,我们是不是该采取一些措施呢?让他的父进程做一些处理呢?下面我们就来讲如何处理僵尸进程。
我们先讲两个函数wait()和waitpid()
#include <sys/wait.h>pid_t wait(int *status);pid_t waitpid(pid_t pid, int *status, int options);
调用wait或waitpid的进程可能发生的情况有:
如果所有子进程都还在运行,则阻塞(Block)。
如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
如果它没有任何子进程,则立即出错返回。
在一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。
waitpid并不等待在其调用之后的第一个终止的子进程。它有若干个选项,可以控制它所等待的进程。
如果一个子进程已经终止,并且是一个僵尸进程,wait立即返回并取得该子进程的状态,否则wait使其调用者阻塞直到一个子进程终止。如果调用者阻塞并且它有多个子进程,则在其一个子进程终止时,wait就立即返回。因为wait返回终止子进程的ID,所以总能了解到是哪一个子进程终止了。
现在我们就使用wait()来处理之前的僵尸进程,
pid_t pid = fork();switch(pid){ case -1: perror("fork"); break; case 0: printf("子进程,ID是%d\n",getpid()); sleep(5); exit(0); break; default: printf("等待子进程结束\n"); sleep(20); pid_t childId = wait(NULL); printf("成功处理掉一个子进程,该子进程是%d\n",childId); sleep(5); break;}
在程序执行5s后,子进程离开了,这时候产生了一个僵尸进程8623
20s之后我们在终端发现提示8623已经被处理了,
再抓取一下进程发现子进程已经不见了
除了解决僵尸进程的问题,我们还可以通过几个宏来了解一下终止的状态:
WIFEXITED(status)
若子进程正常终止,该宏返回true。
此时,可以通过WEXITSTATUS(status)获取子进程的退出状态(exit status)。
WIFSIGNALED(status)
若子进程由信号杀死,该宏返回true。
此时,可以通过WTERMSIG(status)获取使子进程终止的信号值。
WIFSTOPPED(status)
若子进程被信号暂停(stopped),该宏返回true。
此时,可以通过WSTOPSIG(status)获取使子进程暂停的信号值。
WIFCONTINUED(status)
若子进程通过SIGCONT恢复,该宏返回true。
有兴趣可以测试一下。
waitpid()
pid_t waitpid (pid_t pid, int * status, int options)
功能:会暂时停止目前进程的执行,直到有信号来到或子进程结束
参数:如果不在意结束状态值,则参数status可以设成NULL。
参数pid为欲等待的子进程识别码:
pid<-1 等待进程组识别码为pid绝对值的任何子进程。
pid=-1 等待任何子进程,相当于wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为pid的子进程。
参数option可以为0 或下面的OR 组合
WNOHANG: 如果没有任何已经结束的子进程则马上返回,不予以等待。
WUNTRACED :如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
返回值:如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。
waitpid()拥有wait的一部分功能,同时能根据ID号处理僵尸进程,有兴趣的可以研究一下,在这就不多说了。
- 进程控制3——进程终止和等待
- 进程控制(上):进程创建,进程等待,进程终止
- Linux C编程--进程介绍3--进程终止和等待
- Linux C编程--进程介绍3--进程终止和等待
- Linux C编程--进程介绍3--进程终止和等待
- 等待进程终止WaitForSingleObject()
- 进程等待与终止
- 《unix高级环境编程》进程控制——进程等待
- 《unix高级环境编程》进程控制——进程等待
- 进程终止及等待相关
- 【linux】进程创建、等待、终止
- 进程控制:进程的创建、终止、阻塞、唤醒和切换
- 进程—异常控制流之故障、终止篇
- Linux系统编程——进程的控制:结束进程、等待进程结束
- Linux系统编程——进程的控制:结束进程、等待进程结束
- Linux系统编程——进程的控制:结束进程、等待进程结束
- Linux系统编程——进程的控制:结束进程、等待进程结束
- Linux系统编程——进程的控制:结束进程、等待进程结束
- EditText防止输入空格
- android图片选择及获取处理相关资料链接
- OpenJudge百炼-2744-子串-C语言-字符串处理
- 再识Java泛型
- 2:正常血压(程序设计与算法(一)第四周测验(2017夏季)
- 进程控制3——进程终止和等待
- scala中的map和tuple
- Semaphore的简单理解
- 低版本的Xcode如何连接高版本的真机进行调试
- Android Studio编绎流程
- 对线性回归、逻辑回归、各种回归的概念学习
- monkey基本命令参数详解示例
- Lambda表达式
- laravel 'Data Missing'