下面是Android进程创建关系图
POSIX.1 将孤儿进程组(orphaned process group) 定义为:
该组的成员的父进程要么是该组的成员,要么不是该组所属session 的成员, 要么父进程是init.
反过来说,一个进程组不是孤儿进程组的条件是,该组中有一个进程,其父进程属于同一个会话的另外一个组,父进程为init 除外.
在父进程终止,进程组成为孤儿进程组时, 如果进程组中有stop 状态(t/T) 的process/thread, POSIX.1 要求向新的孤儿进程组中每一个进程发送SIGHUP, 接着又向其发送SIGCONT. (以保证进程要么退出,要么继续进行,而非stop 状态)
假如此时有process 处于STOP (T) 状态,机器必然打到孤儿进程组SIG 1 -> SIG 18 case, 杀掉整个zygote Process Group, 机器重启,原理图如下所示:
孤儿进程组的条件是进程组中进程的父进程都是当前进程组中的进程,或者是其他session中的进程。当孤儿进程组产生的时候,如果孤儿进程组中有TASK_STOP的进程,那么就发送SIGHUP和SIGCONT信号给这个进程组,这个顺序是不能变的,我们知道进程在进程在TASK_STOP的时候是不能响应信号的,只有当进程继续运行的时候,才能响应之前的信号。如果先发送SIGCONT信号再发送SIGHUP信号,那么SIGCONT信号后,进程就开始重新进入运行态,这个和马上响应SIGHUP信号的用意相悖。所以这个时候需要在进程stop的过程中首先发送SIGHUP信号,为的是让进程运行之后马上执行SIGHUP信号。
这两个信号是发送给有处于TASK_STOP状态的进程的进程组的,所以进程组中正在运行的进程,如果没有建立SIGHUP信号处理函数,那么运行的进程就会因为SIGHUP退出。
在进程退出的时候,在线程组都退出了,就会判断当前进程是否是孤儿进程组,如果是孤儿进程组就发送SIGHUP和SIGCONT信号。
代码:kernel/exit.c
static void exit_notify(struct task_struct *tsk, int group_dead)
{
int signal;
void *cookie;
/*
* 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)
*/
forget_original_parent(tsk);
exit_task_namespaces(tsk);
write_lock_irq(&tasklist_lock);
if (group_dead)
//判断是否是孤儿进程组,tsk如果是线程,那么group_leader就是线程组首进程
kill_orphaned_pgrp(tsk->group_leader, NULL);
kill_orphaned_pgrp函数就是查看进程退出后,是否变为了孤儿进程,如果是孤儿进程,并且有stop的进程,那么就向整个进程组发送SIGHUP,SIGCONT。
函数kill_orphaned_pgrp的一段代码:
if (task_pgrp(parent) != pgrp && //tsk和parent是同一session下的不同进程组
task_session(parent) == task_session(tsk) &&//
will_become_orphaned_pgrp(pgrp, ignored_task) &&//判断是否是孤儿进程组
has_stopped_jobs(pgrp)) { //如果进程组中有处于TASK_STOP状态的进程
__kill_pgrp_info(SIGHUP, SEND_SIG_PRIV, pgrp); //先发送SIGHUP在发送SIGCONT
__kill_pgrp_info(SIGCONT, SEND_SIG_PRIV, pgrp);
}
判断pgrp进程组是孤儿进程组,通俗的的说就是进程组首进程的退出会导致孤儿进程组的产生。
代码:kernel/exit.c
static int will_become_orphaned_pgrp(struct pid *pgrp, struct task_struct *ignored_task)
{
struct task_struct *p;
do_each_pid_task(pgrp, PIDTYPE_PGID, p) { //递归进程组中的每一个进程
if ((p == ignored_task) || //ignored_task就是将要退出的进程,所以不需要考虑
(p->exit_state && thread_group_empty(p)) || //进程退出并且这个线程组中没有其他的线程了
is_global_init(p->real_parent))
continue;
if (task_pgrp(p->real_parent) != pgrp && //如果进程组中有进程和父进程不是同一个进程组,并且这个两个进程属于同一个会话,那个进程组肯定不是孤儿进程组
task_session(p->real_parent) == task_session(p))
return 0;
} while_each_pid_task(pgrp, PIDTYPE_PGID, p);
return 1;
}
在判断如果是孤儿进程组的时候,如果是同时这个进程组有处于TASK_STOP的进程,那么就向这个进程组发送SIGHUP和SIGCONT信号,首先进程在STOP的过程中是不能响应SIGHUP信号,这样SIGCONT信号处理完这个进程会处于运行态,会去处理SIGHUP信号。信号在kill函数的最后要去看下进程是否需要唤醒。如果进程处于stop状态并且kill的SIGCONT信号需要被唤醒,还有就是SIGKILL信号,需要被唤醒,及时响应。kill函数最后回调用这个函数signal_wake_up, 看下signal_wake_up的实现
void signal_wake_up(struct task_struct *t, int resume)
{
unsigned int mask;
set_tsk_thread_flag(t, TIF_SIGPENDING); //标志这个进程有信号需要处理
/*
* For SIGKILL, we want to wake it up in the stopped/traced/killable
* case. We don't check t->state here because there is a race with it
* executing another processor and just now entering stopped state.
* By using wake_up_state, we ensure the process will wake up and
* handle its death signal.
*/
mask = TASK_INTERRUPTIBLE; //进程处于TASK_INTERRUPTIBLE得进程可以被唤醒
if (resume)
mask |= TASK_WAKEKILL;
if (!wake_up_state(t, mask))
//如果是运行态,并且运行在其他cpu得进程,那么kick_process的作用就是让进程没有延迟的进入内核态,快速响应信号
kick_process(t);
}
这里需要说的一点 TASK_INTERRUPTIBLE状态就是进程处于睡眠状态,但是这种睡眠状态可以被信号打断,但是如果进程处于TASK_UNINTERRUPTIBLE深度睡眠,那么这时候信号是不能唤醒这种进程的,即使是SIGKILL信号也不行.对于TASK_UNINTERRUPTIBLE的状态的还不是不理解,不理解的点有这么几点:
1. 在计算cpuload的时候,为什么要算上这个TASK_UNINTERRUPTIBLE的进程。
2. 如果进程在处于TASK_UNINTERRUPTIBLE状态,那么是不响应信号的,那么是通过什么机制转换到running状态的
对于TASK_WAKEKILL状态的用法还没时间看懂。
这里能看到的就是在函数wake_up_state 中会判断进程t的状态不是TASK_INTERRUPTIBLE和TASK_WAKEKILL的就不唤醒了。所以这里处于TASK_STOP的进程是不能被SIGHUP信号唤醒的。
函数try_to_wake_up
static int try_to_wake_up(struct task_struct *p, unsigned int state,
int wake_flags)
{
int cpu, orig_cpu, this_cpu, success = 0;
unsigned long flags;
unsigned long en_flags = ENQUEUE_WAKEUP;
struct rq *rq;
//禁止内核抢占调度
this_cpu = get_cpu();
smp_wmb();
rq = task_rq_lock(p, &flags);
//判断进程状态,如果不是TASK_INTERRUPTIBLE或者TASK_WAKEKILL状态,就直接退出了
if (!(p->state & state))
goto out;
那么SIGCONT信号如何唤醒进程状态TASK_STOP的进程,这里在kill函数的prepare_signal函数中,会判断如果是SIGCONT信号,那么会在那个进程的状态上加上TASK_INTERRUPTIBLE,这样SIGCONT信号就能唤醒这个进程了。
else if (sig == SIGCONT) {
unsigned int why;
/*
* Remove all stop signals from all queues,
* and wake all threads.
*/
rm_from_queue(SIG_KERNEL_STOP_MASK, &signal->shared_pending);//从进程共用信号队列中移除stop类信号
t = p;
do { //对于进程组而言,让每一个线程都继续执行
unsigned int state;
rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending); //从线程私有信号队列中移除stop类信号
state = __TASK_STOPPED;
if (sig_user_defined(t, SIGCONT) && !sigismember(&t->blocked, SIGCONT)) {
set_tsk_thread_flag(t, TIF_SIGPENDING);
state |= TASK_INTERRUPTIBLE; //设置 TASK_INTERRUPTIBLE,为了使进程wakeup
}
wake_up_state(t, state); //唤醒进程
} while_each_thread(p, t);