04年时维护的第一个商业服务就用了两次fork产生守护进程的做法,前两天在网上看到许多帖子以及一些unix书籍,认为一次fork后产生守护进程足够了,各有道理吧,不过多了一次fork到底是出于什么目的呢?
进程也就是task,看看内核里维护进程的数据结构task_struct,这里有两个成员:
struct task_struct {
volatile long state;
int exit_state;
...
}
看看include/linux/sched.h里的value取值:
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
/* in tsk->state again */
#define TASK_DEAD 64
#define TASK_WAKEKILL 128
#define TASK_WAKING 256
#define TASK_STATE_MAX 512
可以看到,进程状态里除了大家都理解的running/interuptible/uninterruptible/stop等状态外,还有一个ZOMBIE状态,这个状态是怎么回事呢?
这是因为linux里的进程都属于一颗树,树的根结点是linux系统初始化结束阶段时启动的init进程,这个进程的pid是1,所有的其他进程都是它的子孙。除了init,任何进程一定有他的父进程,而父进程会负责分配(fork)、回收(wait4)它申请的进程资源。这个树状关系也比较健壮,当某个进程还在运行时,它的父进程却退出了,这个进程却没有成为孤儿进程,因为linux有一个机制,init进程会接管它,成为它的父进程。这也是守护进程的由来了,因为守护进程的其中一个要求就是希望init成为守护进程的父进程。
如果某个进程自身终止了,在调用exit清理完相关的内容文件等资源后,它就会进入ZOMBIE状态,它的父进程会调用wait4来回收这个task_struct,但是,如果父进程一直没有调用wait4去释放子进程的task_struct,问题就来了,这个task_struct谁来回收呢?永远没有人,除非父进程终止后,被init进程接管这个ZOMBIE进程,然后调用wait4来回收进程描述符。如果父进程一直在运行着,这个ZOMBIE会永远的占用系统资源,用KILL发任何信号量也不能释放它。这是很可怕的,因为服务器上可能会出现无数ZOMBIE进程导致机器挂掉。
来看看内核代码吧。进程在退出时执行sys_exit(C程序里在main函数返回会执行到),而它会调用do_exit,do_exit首先清理进程使用的资源,然后调用exit_notify方法,将进程置为僵尸ZOMBIE状态,决定是否要以init进程做为当前进程的父进程,最后通知当前进程的父进程:
kernel/exit.c
static void exit_notify(struct task_struct *tsk)
{
int state;
struct task_struct *t;
struct list_head ptrace_dead, *_p, *_n;
if (signal_pending(tsk) && !tsk->signal->group_exit
&& !thread_group_empty(tsk)) {
/*
* This occurs when there was a race between our exit
* syscall and a group signal choosing us as the one to
* wake up. It could be that we are the only thread
* alerted to check for pending signals, but another thread
* should be woken now to take the signal since we will not.
* Now we'll wake all the threads in the group just to make
* sure someone gets all the pending signals.
*/
read_lock(&tasklist_lock);
spin_lock_irq(&tsk->sighand->siglock);
for (t = next_thread(tsk); t != tsk; t = next_thread(t))
if (!signal_pending(t) && !(t->flags & PF_EXITING)) {
recalc_sigpending_tsk(t);
if (signal_pending(t))
signal_wake_up(t, 0);
}
spin_unlock_irq(&tsk->sighand->siglock);
read_unlock(&tasklist_lock);
}
write_lock_irq(&tasklist_lock);
/*
* This does two things:
*
* A. Make init inherit all the child processes
* B. Check to see if any process groups have become orphaned
* as a result of our exiting, and if they have any stopped
* jobs, send them a SIGHUP and then a SIGCONT. (POSIX 3.2.2.2)
*/
INIT_LIST_HEAD(&ptrace_dead);
forget_original_parent(tsk, &ptrace_dead);
BUG_ON(!list_empty(&tsk->children));
BUG_ON(!list_empty(&tsk->ptrace_children));
/*
* Check to see if any process groups have become orphaned
* as a result of our exiting, and if they have any stopped
* jobs, send them a SIGHUP and then a SIGCONT. (POSIX 3.2.2.2)
*
* Case i: Our father is in a different pgrp than we are
* and we were the only connection outside, so our pgrp
* is about to become orphaned.
*/
t = tsk->real_parent;
if ((process_group(t) != process_group(tsk)) &&
(t->signal->session == tsk->signal->session) &&
will_become_orphaned_pgrp(process_group(tsk), tsk) &&
has_stopped_jobs(process_group(tsk))) {
__kill_pg_info(SIGHUP, (void *)1, process_group(tsk));
__kill_pg_info(SIGCONT, (void *)1, process_group(tsk));
}
/* Let father know we died
*
* Thread signals are configurable, but you aren't going to use
* that to send signals to arbitary processes.
* That stops right now.
*
* If the parent exec id doesn't match the exec id we saved
* when we started then we know the parent has changed security
* domain.
*
* If our self_exec id doesn't match our parent_exec_id then
* we have changed execution domain as these two values started
* the same after a fork.
*
*/
if (tsk->exit_signal != SIGCHLD && tsk->exit_signal != -1 &&
( tsk->parent_exec_id != t->self_exec_id ||
tsk->self_exec_id != tsk->parent_exec_id)
&& !capable(CAP_KILL))
tsk->exit_signal = SIGCHLD;
/* If something other than our normal parent is ptracing us, then
* send it a SIGCHLD instead of honoring exit_signal. exit_signal
* only has special meaning to our real parent.
*/
if (tsk->exit_signal != -1 && thread_group_empty(tsk)) {
int signal = tsk->parent == tsk->real_parent ? tsk->exit_signal : SIGCHLD;
do_notify_parent(tsk, signal);
} else if (tsk->ptrace) {
do_notify_parent(tsk, SIGCHLD);
}
state = EXIT_ZOMBIE;
if (tsk->exit_signal == -1 && tsk->ptrace == 0)
state = EXIT_DEAD;
tsk->exit_state = state;
/*
* Clear these here so that update_process_times() won't try to deliver
* itimer, profile or rlimit signals to this task while it is in late exit.
*/
tsk->it_virt_value = 0;
tsk->it_prof_value = 0;
write_unlock_irq(&tasklist_lock);
list_for_each_safe(_p, _n, &ptrace_dead) {
list_del_init(_p);
t = list_entry(_p,struct task_struct,ptrace_list);
release_task(t);
}
/* If the process is dead, release it - nobody will wait for it */
if (state == EXIT_DEAD)
release_task(tsk);
/* PF_DEAD causes final put_task_struct after we schedule. */
preempt_disable();
tsk->flags |= PF_DEAD;
}
大家可以看到这段内核代码的注释非常全。forget_original_parent这个函数还会把该进程的所有子孙进程重设父进程,交给init进程接管。
回过头来,看看为什么守护进程要fork两次。这里有一个假定,父进程生成守护进程后,还有自己的事要做,它的人生意义并不只是为了生成守护进程。这样,如果父进程fork一次创建了一个守护进程,然后继续做其它事时阻塞了,这时守护进程一直在运行,父进程却没有正常退出。如果守护进程因为正常或非正常原因退出了,就会变成ZOMBIE进程。
如果fork两次呢?父进程先fork出一个儿子进程,儿子进程再fork出孙子进程做为守护进程,然后儿子进程立刻退出,守护进程被init进程接管,这样无论父进程做什么事,无论怎么被阻塞,都与守护进程无关了。所以,fork两次的守护进程很安全,避免了僵尸进程出现的可能性。
分享到:
相关推荐
### 谈谈守护进程和僵尸进程 #### 守护进程与僵尸进程的概念及应用场景 在深入探讨守护进程和僵尸进程之前,我们先简要回顾一下这两种进程的基本概念及其应用场景。 - **守护进程**(Daemon Process):是在后台...
僵尸进程是计算机操作系统中的一种特殊状态,当一个进程执行完毕并退出,但其父进程尚未读取其退出状态时,这个已结束的进程就会成为僵尸进程。它的主要特征是进程已不再执行任何代码,但其进程描述符仍然存在于内存...
- 对于可能会创建子进程的守护进程来说,需要正确处理子进程结束后留下的僵尸进程问题。 - 示例代码: ```c signal(SIGCHLD, SIG_IGN); ``` 通过以上步骤,我们可以有效地编写出符合要求的Linux守护进程。这些...
僵尸进程是一种特定状态的进程,当一个进程执行完毕并退出,但其父进程尚未读取它的退出状态时,该进程就会成为僵尸进程。这种状态的进程已经没有在执行任何代码,但它在系统中的进程描述符仍然存在,占用了一定的...
在Linux操作系统中,守护进程(Daemon)是系统后台运行的重要组成部分,它们不与用户界面直接交互,而是持续运行以提供服务或执行特定任务。守护进程的生命周期长,通常在系统启动时启动,并在系统关闭时终止。由于...
守护进程在启动后会断开与控制终端的连接,避免因终端关闭而被强制结束。而在Windows系统中,类似的概念可以通过服务(Service)来实现,服务可以在用户登录与否的情况下独立运行。 为了使进程变得“杀不死”,...
在操作系统(OS)中,僵尸进程是一个已终止但其父进程尚未从子进程接收状态信息的进程。这种状态是进程生命周期中的一个不寻常阶段,它可能会占用系统资源并导致不必要的混乱。本文将深入探讨消除僵尸进程的两种常用...
#### 一、僵尸进程的概念与形成原因 在Linux系统中,程序的执行是以进程的形式存在的。每一个进程都有一个父进程(除非是init进程,它的PID为1,没有父进程),而它自身又可以作为其他进程的父进程。当一个子进程...
只有父进程发出了与被终止的进程相关的 wait() 类系统调用之后,才允许这样做。这就是引入僵死状态的原因:尽管从技术上来说进程已死,但必须保存它的描述符,直到父进程得到通知。 如果一个进程已经终止,但是它的...
7. **处理SIGCHLD信号**:对于生成子进程的守护进程,需要妥善处理SIGCHLD信号,以避免子进程成为僵尸进程。在Linux中,可以简单地忽略SIGCHLD信号。 ```c signal(SIGCHLD, SIG_IGN); ``` 以上步骤提供了构建...
守护进程(Daemon)是一种特殊的后台进程,它没有控制终端,也不与用户交互。守护进程通常用于提供一些服务,例如网络服务、数据库服务等。Linux系统下守护进程编程方法是指在Linux操作系统下编写和实现守护进程的...
有时候即使注销并重新登录系统,僵尸进程仍然可能存在,这可能是由于特定服务或守护进程的问题导致的。例如,在Ubuntu 12.04中,使用LightDM作为显示管理器时可能会遇到此类问题。在这种情况下,使用`kill -HUP`命令...
dumb-init 跨框架init初始化进程,有效回收僵尸进程
5. **创建子进程**:为了避免僵尸进程,守护进程通常会创建一个子进程,然后父进程退出,子进程继续执行。 6. **执行服务代码**:完成上述步骤后,守护进程就可以开始执行它的主要任务了。 在提供的`init.c`和`...
本文主要给大家介绍了关于Linux中僵尸进程和孤儿进程的相关内容,分享给出来供大家参考学习,下面来看看详细的介绍: 1、僵尸进程 一个子进程在其父进程没有调用wait()或waitpid()的情况下退出,这个子进程就是僵尸...
### 僵尸(zombie)进程详解与处理方法 #### 一、僵尸进程定义与状态 在Linux系统中,进程可以处于多种不同的状态,而其中一种特殊的状态被称为“僵尸”状态(Z)。当一个进程终止时,其父进程应当通过`wait()`或`...
在Linux系统中,僵尸进程(Zombie Process)是一种常见的系统问题,它们通常是由父进程创建的子进程在完成任务后未能被父进程正确回收而遗留下来的。这些僵尸进程虽然不再执行任何操作,但仍然占据着系统资源,如...
如果守护进程会产生子进程,那么处理 SIGCHLD 信号可以防止子进程成为僵尸进程。 ```php pcntl_signal(SIGCHLD, SIG_IGN); ``` #### 示例代码 接下来,我们来看一个简单的 PHP 守护进程类的示例代码: ```php ...
Linux避免僵尸进程