一.僵尸进程
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵死进程。
R:运行
S:后台运行
Z:僵尸进程
#include
using namespace std;
#include
#include
int main()
{
pid_t pid;
pid = fork();
if(pid == 0)
{
cout<<"child"< 0)
{
for(;;)
{
sleep(1);
cout<<"parent"<
子进程执行完后退出,父进程死循环不退出.
g++ -o zom zombie.c
./zom
使用ps命令查看:ps -ef 找到defunct
UID PID PPID C STIME TTY TIME CMD
//........
txp 14301 11835 0 10:54 pts/2 00:00:00 ./zombie
txp 14302 14301 0 10:54 pts/2 00:00:00 [zombie]
#include
#include
#include
#include
#include
#include
#include
//当只有一个子进程的时候
void deal_child(int sig_no)
{
wait(NULL);
}
//存在多个子进程时
void deal_child(int sig_no)
{
for (;;) {
if (waitpid(-1, NULL, WNOHANG) == 0)
break;
}
}
int main(int argc, char **argv)
{
signal(SIGCHLD, deal_child);
//省略
return 0;
}
父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起。
执行wait()或waitpid()系统调用,则子进程在终止后会立即把它在进程表中的数据返回给父进程,
此时系统会立即删除该进入点。在这种情形下就不会产生defunct进程。
实际上父进程执行wait()与多线程中主线程执行pthread_join()是类似的,也会将主线程阻塞挂起。
学校学习时整理的关于wait()笔记:
方案一:
pid_t wait (int * status) 的三个作用:
1.父进程阻塞,然后等待子进程退出(注意这里是阻塞,如果子进程没死,父进程阻塞)
2.回收子进程残留资源 (残留的资源是子进程的PCB)
3.获取子进程结束原因(正常的退出、异常终止)
失败返回-1。
wait() 调用一次,只回收一个子进程,对于多个子进程,需要调用多次wait();
pid_t wpid,pid;
if(pid > 0)
{
wpid = wait( NULL );
}
循环的等待多个子进程的结束:
if ( pid == 0)
{
while(wait(NULL));
}
进一步获取子进程结束原因:利用下面的宏函数
1.
WIFEXITED(status) 为非0,表示进程正常的结束
WEXITSTATUS(status)如果上面的宏为真
用这个宏获取进程的退出码(实际上,正常退出的退出码是exit(n)中的n)
2.
SIFSIGNALED(status)为非0,表示进程异常终止
WTERMSIG(status)如上面的宏为真,使用该宏函数,取得使进程终止的那个信号的编码
linux中的进程异常退出都是由于 信号 产生的,进程收到了一个信号,所以异常终止回收信号编号
pid_t wpid,pid;
int status;
if(pid == 0)
{
return 100;
}
if( pid > 0 )
{
wpid = wait(&status);
}
if( WIFEXITED(status) )
{
pfint("退出码是%d",WEXITSTATUS(status));
}
//子进程trturn 100,所以这里打印出来也是100
//异常退出类似,只不过打印出来的退出码是收到异常信号的编号
方案二:
pid_t waitpid(pid_t pid , int *status , int options); 可以不阻塞父进程
成功,返回清理掉的所有子进程的ID;失败返回-1(无子进程)
参数pid:
>0 回收直到ID的子进程
-1 回收任意一个子进程(此时相当于wait)
0 回收和当前调用waitpid一个组的所有子进程
<-1 回收指定进程组内的任意子进程
参数:status
与wait()的作用的和用法一样,结合宏函数实现
参数: options
以轮询的方式,查看子进程是否死了,指定为宏 WNOHANG,就为非阻塞的
二.孤儿进程
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作。
PID = 1 的进程,所有进程的起源/sbin/init
孤儿进程对于系统的危害微乎其微,一般情况下,大可不必担心。
三.守护进程
不与任何终端关联的进程,通常情况下守护进程在系统启动时就在运行,它们以root用户或者其他特殊用户(apache和postfix)运行,并能处理一些系统级的任务。守护进程脱离于终端,是为了避免进程在执行过程中的信息在任何终端上显示,并且进程也不会被任何终端所产生的终端信息所打断(比如关闭终端等)
如何创建一个守护进程
简单来说,给子进程成为进程组组长和会话组组长做铺垫,因为它要独立出去
成为独立的一个组,自然要脱离父进程和终端
子进程会继承父进程的一些信息,就比如会话信息,所以,子进程要创建属于自己的会话
setsid()调用成功后,进程成为新的会话组长和新的进程组长,
并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
防止僵尸进程的出现
子进程继承了父进程的工作目录,既然要独立出去,那么肯定得有自己的工作目录
同样的子进程会继承父进程的文件描述符
守护进程是非交互式程序,没有控制终端,所以任何输出,
无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。
一般我们将这些文件描述符重定向到/dev/null/ 者相当于是一个黑洞或者是一个垃圾桶
创建文件是,文件创建掩码会被屏蔽一些权限相对应的位,所以需要重建文件创建掩码
示例:
#include
#include
#include "daemon.h"
int daemon(int nochdir, int noclose)
{
int fd;
switch (fork()) {
case -1:
return (-1);
case 0:
break;
default:
_exit(0);
}
if (setsid() == -1)
return (-1);
if (!nochdir)
(void)chdir("/");
if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
(void)dup2(fd, STDIN_FILENO);
(void)dup2(fd, STDOUT_FILENO);
(void)dup2(fd, STDERR_FILENO);
if (fd > 2)
(void)close (fd);
}
return (0);
}
对于守护进程中出现的几个名词,做一个解释: