linux信号实现浅析2--信号发送内核源码解析

linux系统为用户态进程提供了发送信号的系统调用函数int kill(pid_t pid, int sig);
该系统调用可以用来向任何进程或进程组发送任何信号。如果参数pid是正数,那么该调用将信号sig发送到进程号为pid的进程。如果pid等于0,那么信 号sig将发送给当前进程所属进程组里的所有进程。如果参数pid等于-1,信号sig将发送给除了进程1和自身以外的所有进程。如果参数pid小于- 1,信号sig将发送给属于进程组-pid的所有进程。如果参数sig为0,将不发送信号。该调用执行成功时,返回值为0;错误时,返回-1,并设置相应 的错误代码errno。

通过该系统调用,有可能是把信号发送给一个进程或者一个线程组中的一个轻量级进程,在内核代码中,要是把一个信号发送到一个进程,最终会调用到:
static int specific_send_sig_info (int sig, struct siginfo *info, struct task_struct *t);
要把一个进程发送给一个进程组,最终会调用到:
int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p);
在这两个函数中,sig用来记录发送的信号的id,p记录了要发送给哪个进程或者线程组中的进程,info或者是sig_info表的地址,或者是0(信号由普通用户态进程发送),1(信号由内核发送),2(由内核发送的SIGSTOP或SIGKILL信号)
1先来说一下发送给单个进程的信号的代码流程:


static int specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
int ret = 0;
assert_spin_locked(&t->sighand->siglock);
//检查信号是否可以被忽略,具体看后面的sig_ignored()函数解析
if (sig_ignored(t, sig))
goto out;



//非实时信号,且该信号已经在进程的私有信号挂起队列之中,无需进行信号发送了
if (LEGACY_QUEUE(&t->pending, sig))
goto out;


//把信号添加到进程的私有挂起队列里面,具体见后面的send_signal()函数解析
ret = send_signal(sig, info, t, &t->pending);
//添加成功且信号不被阻塞的话,通知进程有新的信号需要进行处理,在可能的情况下
唤醒该进程,具体见后面的signal_wake_up的注释
if (!ret && !sigismember(&t->blocked, sig))
signal_wake_up(t, sig == SIGKILL);
out:
return ret;
}

下面来介绍上面提到的几个辅助性的函数:


static int sig_ignored(struct task_struct *t, int sig)
{
void __user * handler;


//进程被跟踪
if (t->ptrace & PT_PTRACED)
return 0;


//该信号不在进程的阻塞信号集里面
if (sigismember(&t->blocked, sig))
return 0;


//对应的信号处理函数为sig_ign,即为忽略的//信号处理函数
handler = t->sighand->action[sig-1].sa.sa_handler;
return   handler == SIG_IGN ||
(handler == SIG_DFL && sig_kernel_ignore(sig));
}


static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
struct sigpending *signals)
{
struct sigqueue * q = NULL;
int ret = 0;
 
//sigstop or sigkill signal,直接把信号添加到signal mask 里面即可
if (info == SEND_SIG_FORCED)
goto out_set;



//分配一个信号描述符
q = __sigqueue_alloc(t, GFP_ATOMIC, (sig < SIGRTMIN &&
    (is_si_special(info) ||
     info->si_code >= 0)));
if (q) {
//将该信号描述符挂入到信号挂起队列中
list_add_tail(&q->list, &signals->list);
//填写信号描述符中的info字段
switch ((unsigned long) info) {
//由普通进程发来的信号
case (unsigned long) SEND_SIG_NOINFO:
q->info.si_signo = sig;
q->info.si_errno = 0;
q->info.si_code = SI_USER;
q->info.si_pid = current->pid;
q->info.si_uid = current->uid;
break;
//由内核发来的信号
case (unsigned long) SEND_SIG_PRIV:
q->info.si_signo = sig;
q->info.si_errno = 0;
q->info.si_code = SI_KERNEL;
q->info.si_pid = 0;
q->info.si_uid = 0;
break;
//带有sig_info内容,比如段错误引发的信号等。
default:
copy_siginfo(&q->info, info);
break;
}
} else if (!is_si_special(info)) {
//信号队列已经满了,如果该信号是实时信号,并且是由内核发来的,返回错误码
if (sig >= SIGRTMIN && info->si_code != SI_USER)
return -EAGAIN;
}


out_set:
//设置信号集的掩码,记录有相应信号
sigaddset(&signals->signal, sig);
return ret;
}
要注意在该函数里面即使无法给普通信号分配信号描述符,要需要将其设置在信号集中的mask上面的。


void signal_wake_up(struct task_struct *t, int resume)
{
unsigned int mask;


//置task_struct中thread_info的flag字段为tif_sigpending,以便该进程从内核态返回到用户态时能够处理相应挂起的信号
set_tsk_thread_flag(t, TIF_SIGPENDING);
mask = TASK_INTERRUPTIBLE;
if (resume)
mask |= TASK_STOPPED | TASK_TRACED;
//进程状态为task_interruptible的话,唤醒该进程
if (!wake_up_state(t, mask))
//当进程被唤醒后,若在别的cpu上运行了的话,发送一个处理器间中断,使进程进入内核态,以在返回用户态时注意到信号的存在,缩短对信号的反应时间
kick_process(t);
}


2再来描述一下发送给线程组的信号发送流程,这个稍微复杂了一些,了解个大概也就差不多了。
int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
unsigned long flags;
int ret;
//检查发送信号的权限
ret = check_kill_permission(sig, info, p);


if (!ret && sig) {
ret = -ESRCH;
//使用sighand lock进行加锁
if (lock_task_sighand(p, &flags)) {
//主要的处理函数,见下面
ret = __group_send_sig_info(sig, info, p);
unlock_task_sighand(p, &flags);
}
}


return ret;
}


int   __group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
int ret = 0;


assert_spin_locked(&p->sighand->siglock);
//检查信号的某些类型,可能会使其他挂起的信号无效,比如说信号挂起队列中有个SIG_STOP信号,这时候要发送的是SIG_CONT信号,那么挂起队列中的SIG_STOP就没有执行的必要了,见后面的handle_stop_signal解析
handle_stop_signal(sig, p);


//检查是否可以忽略信号
if (sig_ignored(p, sig))
return ret;
//信号为非实时信号的话,检查该信号是否已经在挂起队列中
if (LEGACY_QUEUE(&p->signal->shared_pending, sig))
return ret;


//将信号挂入到共享挂起队列中
ret = send_signal(sig, info, p, &p->signal->shared_pending);
if (unlikely(ret))
return ret;
//唤醒线程组中的一个轻量级线程来处理该信号
__group_complete_signal(sig, p);
return 0;
}
__group_send_sig_info()函数的处理流程总体来说和发送给单个进程的处理流程是差不多的,最大的不同是其执行了handle_stop_signal(sig, p);操作以及__group_complete_signal(sig, p);操作:


static void handle_stop_signal(int sig, struct task_struct *p)
{
struct task_struct *t;
//该线程组正在被杀死,不用再发送信号了
if (p->signal->flags & SIGNAL_GROUP_EXIT)
return;
//若信号为sigstop,sigtstp,sigttin,sigttou信号,将sigcont信号从挂起队列中移除,没有必要再做这些操作了
if (sig_kernel_stop(sig)) {
//将SIGCONT从共享挂起队列中移除
rm_from_queue(sigmask(SIGCONT), &p->signal->shared_pending);
t = p;
do {
//将SIGCONT从私有挂起队列中移除
rm_from_queue(sigmask(SIGCONT), &t->pending);
t = next_thread(t);
} while (t != p);//遍历线程组中的各个轻量级进程
} else if (sig == SIGCONT) {
//若为sigcont信号,则将sigstop,sigtstp,sigttin,sigttout从信号挂起队列中移除
if (unlikely(p->signal->group_stop_count > 0)) {
p->signal->group_stop_count = 0;
p->signal->flags = SIGNAL_STOP_CONTINUED;
spin_unlock(&p->sighand->siglock);
do_notify_parent_cldstop(p, CLD_STOPPED);
spin_lock(&p->sighand->siglock);
}
//从共享信号队列中移除四种信号
rm_from_queue(SIG_KERNEL_STOP_MASK, &p->signal->shared_pending);
t = p;
do {
unsigned int state;
//从私有信号队列中移除四种信号
rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending);
state = TASK_STOPPED;
if (sig_user_defined(t, SIGCONT) && !sigismember(&t->blocked, SIGCONT)) {
set_tsk_thread_flag(t, TIF_SIGPENDING);
state |= TASK_INTERRUPTIBLE;
}
//唤醒已经为stop状态的线程
wake_up_state(t, state);


t = next_thread(t);
} while (t != p);


if (p->signal->flags & SIGNAL_STOP_STOPPED) {
p->signal->flags = SIGNAL_STOP_CONTINUED;
p->signal->group_exit_code = 0;
spin_unlock(&p->sighand->siglock);
do_notify_parent_cldstop(p, CLD_CONTINUED);
spin_lock(&p->sighand->siglock);
} else {
p->signal->flags = 0;
}
} else if (sig == SIGKILL) {
p->signal->flags = 0;
}
}


//下面这个比较复杂,用来挑选一个线程组中的线程来执行该信号,首选的线程是参数p中传递进去的线程,其次从p->signal->curr_target中开始进行遍历,curr_target记录的是上一次处理线程组信号的线程组中的线程
static void   __group_complete_signal(int sig, struct task_struct *p)
{
struct task_struct *t;


//wants_signal用来判断一个进程是否可以被选为要唤醒的
//进程,需要满足条件:1不阻塞该信号,2进程不是EXIT_ZOMBIE,
//EXIT_DEAD等状态,3进程没正在被杀死,即PF_EXITING未被置位,
//4信号为sigkill,5进程正在运行,或者进程的TIF_SIGPENDING没有被置位
if (wants_signal(sig, p))//首选函数参数传递进来的线程
t = p;
else if (thread_group_empty(p))
return;
else {//首选要发送信号的进程,次选上次接收最后一个信号的进程

t = p->signal->curr_target;
if (t == NULL)
/* restart balancing at this thread */
t = p->signal->curr_target = p;
//从curr_target线程遍历线程组,选择合适的执行信号处理的线程
while (!wants_signal(sig, t)) {
t = next_thread(t);
if (t == p->signal->curr_target)
return;
}
p->signal->curr_target = t;
}
//致命性的信号,引起线程退出,coredump的信号,需要把线程组所有的线程都杀死
if (sig_fatal(p, sig) && !(p->signal->flags & SIGNAL_GROUP_EXIT) &&
   !sigismember(&t->real_blocked, sig) &&
   (sig == SIGKILL || !(t->ptrace & PT_PTRACED))) {

//该信号允许进行coredump
if (!sig_kernel_coredump(sig)) {
//将p的状态signal状态置位SIGNAL_GROUP_EXIT,表明线程组因为信号开始 退出
p->signal->flags = SIGNAL_GROUP_EXIT;
p->signal->group_exit_code = sig;
p->signal->group_stop_count = 0;
t = p;
//对线程组所有的线程都添加SIGKILL信号,使每个进程处理该sigkill信号
do {
sigaddset(&t->pending.signal, SIGKILL);
signal_wake_up(t, 1);
t = next_thread(t);
} while (t != p);
return;
}


//因为信号要将所有的线程组线程杀死,需要各个线程运行,将stop相关的信号从挂起队列移除,我也没太看懂这部分。。。。。。
rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending);
rm_from_queue(SIG_KERNEL_STOP_MASK, &p->signal->shared_pending);
p->signal->group_stop_count = 0;
p->signal->group_exit_task = t;
t = p;
 //唤醒线程组的各个线程
do {
p->signal->group_stop_count++;
signal_wake_up(t, 0);
t = next_thread(t);
} while (t != p);
wake_up_process(p->signal->group_exit_task);
return;
}
signal_wake_up(t, sig == SIGKILL);
return;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值