Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1746006
  • 博文数量: 1493
  • 博客积分: 38
  • 博客等级: 民兵
  • 技术积分: 5834
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-19 17:28
文章分类

全部博文(1493)

文章存档

2016年(11)

2015年(38)

2014年(137)

2013年(253)

2012年(1054)

2011年(1)

分类: C/C++

2013-03-04 16:11:45

原文地址:内核对信号的处理机制 作者:djjsindy

      linux的信号处理机制就是在内核态中把信号的相关信息在写入到其他进程的task_struct(进程信号)或者signal_struct(线程组信号)中,然后内核在一定的时机去处理进程中未处理的信号,对于处理信号的时机有这么几个:系统调用后,系统中断或者异常,返回用户态之前,都会处理当前进程未处理的信号。

arch/x86/kernel/entry_32.S中,系统调用完成后会处理未处理的信号。

544     syscall_exit:

545     LOCKDEP_SYS_EXIT

546     DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt

547      # setting need_resched or sigpending

548      # between sampling and the iret

549      TRACE_IRQS_OFF

550      movl TI_flags(%ebp), %ecx

551      testl $_TIF_ALLWORK_MASK, %ecx # current->work

552      jne syscall_exit_work                         //退出系统调用之前,检查是否需要处理信号

    在work_notifysig中会处理挂起的信号。

656     work_notifysig: # deal with pending signals and

657      # notify-resume requests

658     #ifdef CONFIG_VM86

659      testl $X86_EFLAGS_VM, PT_EFLAGS(%esp)

660      movl %esp, %eax

661      jne work_notifysig_v86 # returning to kernel-space or

662      # vm86-space

663      xorl %edx, %edx

664      call do_notify_resume            //调用信号处理函数

665      jmp resume_userspace_sig   //返回用户空间

    在中断或者异常结束之后,返回用户态之前会检查是否有信号挂起,未处理。

362     ENTRY(resume_userspace)

363      LOCKDEP_SYS_EXIT

364      DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt

365      # setting need_resched or sigpending

366      # between sampling and the iret

367      TRACE_IRQS_OFF

368      movl TI_flags(%ebp), %ecx

369      andl $_TIF_WORK_MASK, %ecx # is there any work to be done on

370      # int/exception return?

371      jne work_pending                 //检查是否有信号未处理

372      jmp restore_all

373     END(ret_from_exception)

    从上面的代码中可以看出,在系统调用或者中断,异常返回到用户空间之前内核都会检查是否有信号在当前进程中挂起。如果有信号就去处理执行函数do_notify_resume

对于一个信号,如果这个信号没有在用户态声明处理函数,那么有以下几种默认行为:


  1. 进程被终止。
  2. dump core文件,同时进程被终止
  3. 信号被忽略
  4. 进程被暂停
  5. 进程被暂停后,又重新开始运行


    do_notify_resume函数中会判断thread_info_flags中是否设置了有信号未处理的标志,如果有的话就去处理

838     void

839     do_notify_resume(struct pt_regs *regs, void *unused, __u32 thread_info_flags)

840     {

841             #ifdef CONFIG_X86_MCE

842              /* notify userspace of pending MCEs */

843             if (thread_info_flags & _TIF_MCE_NOTIFY)

844                        mce_notify_process();

845             #endif /* CONFIG_X86_64 && CONFIG_X86_MCE */

846

847              /* deal with pending signal delivery */

848              if (thread_info_flags & _TIF_SIGPENDING) //如果设置了_TIF_SIGPENDING标志,就说明有信号未处理

849              do_signal(regs);                                       //就直接调用do_signal函数

      do_signal函数中,会去判断每个被挂起的信号,如果这个信号在用户态没有设置处理函数,那么内核会按照信号的默认处理行为处理信号,如果这个信号在用户态设置了处理函数,那么需要返回用户态去调用这个函数,在内核中会在用户态的栈中插入一个调用那个处理函数的栈针,调用完用户态处理函数后返回内核态,继续do_signal函数的处理。

791              signr = get_signal_to_deliver(&info, &ka, regs, NULL); //获得一个在用户态有处理函数的信号,没有设置用户态处理函数的信号,按照默认处理行为处理

792              if (signr > 0) {

793                      /* Whee! Actually deliver the signal. */

794                      if (handle_signal(signr, &info, &ka, oldset, regs) == 0) { //构造栈针返回用户态调用处理函数

这里我们关注 get_signal_to_deliver函数所作的事情。


     从上面的图中可以看出,处理信号首先要关注一下当前进程是否是收到了SIGCONT信号而导致了进程状态的变化,如果在group stop的过程中收到了SIGCONT信号,那么这里会默认group stop过程已经让进程组完全停止,然后通知trace进程或者是父进程,子进程状态发生了改变。如果是在group_stop后收到了SIGCONT,说明进程被唤醒了,同时也要通知trace进程或者父进程,子进程状态发生了改变。

     do_signal_stop的函数是判断是否是在group stop过程中,如果在group stop,那么当前进程会暂停。group stop的分析参考stop信号对线程组的影响

     后面的过程是从进程的私有信号队列或者公用信号队列中取出一个信号,然后调度调试器来跟踪当前进程,具体参考信号对调试状态的影响

     后面的细节是如果进程设置了信号处理函数,那么函数就会退出,具体的处理过程交给了用户态。后面会处理stop信号,在调用do_signal_stop之前,会判断一下当前的进程组是否是孤儿进程组,如果是的话就忽略那些非SIGSTOP的暂停信号。

     这里孤儿进程组的概念是:该组中每个成员的父进程要么是该组中的一个成员,要么不是该组所属会话(session)的成员。判断是否是孤儿进程的代码:

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) ||(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;

}

     对于孤儿进程组还不太理解,还有待研究。最后的处理过程就相对简单易懂了,stop信号处理完毕后,就剩下进程结束的那写信号了。如果当前信号应该去coredump,就去调用do_coredump,进程结束最后调用了do_group_exit函数。

总结:

     信号会在进程返回用户空间前,去处理在信号队列中的信号,如果该信号在用户态设置了信号处理函数,就返回到用户空间去调用信号处理函数,如果未设置,就采取默认的处理行为。

阅读(820) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~