linux内核情景分析之信号实现

信号在进程间通信是异步的,每个进程的task_struct结构有一个sig指针,指向一个signal_struct结构
定义如下
  1. struct signal_struct {
  2. atomic_t count;
  3. struct k_sigaction action[_NSIG];//类似一个信号向量表,每个元素就是一个函数指针
  4. spinlock_t siglock;
  5. };
  1. struct k_sigaction {
  2. struct sigaction sa;
  3. };
  1. struct sigaction {
  2. unsigned int sa_flags;
  3. __sighandler_t sa_handler;
  4. sigset_t sa_mask;
  5. void (*sa_restorer)(void);
  6. int sa_resv[1]; /* reserved */
  7. };
由此可见,每个信号向量除了指向一个信号处理函数,他还可以是2个其他特殊的常数
  1. /* Fake signal functions */
  2. #define SIG_DFL ((__sighandler_t)0) /* default signal handling */
  3. #define SIG_IGN ((__sighandler_t)1) /* ignore signal */
  4. #define SIG_ERR ((__sighandler_t)-1) /* error return from signal */
由于SIG_DFL得数值为0,当向量表为空白时,所有信号向量对被视为SIG_DFL
信号向量与中断向量不同的是:中断向量表在系统空间,每个中断向量所指向中断响应程序也在系统空间,但虽然信号向量表在系统空间,但这些向量指向的信号处理程序缺在用户空间.
对于信号的检测和响应总是发生在系统空间,发生情况:当前进程系统调用,中断,异常而进入系统调用,从系统调用返回到用户空间的前夕.第二,当前进程在内核中进入睡眠后刚被唤醒的时候,由于信号的存在(处于未决状态)提前返回到用户空间.
具体来分析下sigaction结构体
  1. struct sigaction {
  2. unsigned int sa_flags;
  3. __sighandler_t sa_handler;
  4. sigset_t sa_mask;//屏蔽,一个64位的类型
  5. void (*sa_restorer)(void);
  6. int sa_resv[1]; /* reserved */
  7. };
这里_sa_handler与_sa_sigaction都是函数指针,数据类型__sighandler_t定义为void(*)(int)指针,可见二者只是调用时的参数表不同,ra_restorer弃用.但sa_mask与sa_flags两个字段扮演着重要的角色.
sa_mask是一个位图,每一位对应一种信号,如果位图某一位设置为1,就表示执行当前信号的处理程序期间,要将相应的信号暂时屏蔽,使得执行过程中不会嵌套响应那种信号,特不是不管图的相应位设置为1,当前信号总是屏蔽自己,使得对同一种信号不会嵌套发生,除非sa_flags设置为SA_NODEFER或者SA_NOMASK标志为1
task_struct的信号队列与信号位图合并到一起了
  1. struct sigqueue {
  2. struct sigqueue *next;
  3. siginfo_t info;//挂载信号相关信息
  4. };
  1. struct sigpending {
  2. struct sigqueue *head, **tail;//挂载队列
  3. sigset_t signal;//位图,信号要处理时,设置为1
  4. };

  1. struct task_struct {
  2. int sigpending; //表示该进程是否有信号需要处理
  3. int exit_code, exit_signal;
  4. /* Protects signal and blocked */
  5. struct signal_struct *sig; //指向具体的信号处理动作
  6. sigset_t blocked; //阻塞
  7. struct sigpending pending;//位图+信号队列
  8. }
结构分析完了,接下来看看信号的安装
  1. int
  2. do_sigaction(int sig, const struct k_sigaction *act, struct k_sigaction *oact)
  3. {
  4. struct k_sigaction *k;
  5. if (sig < 1 || sig > _NSIG ||
  6. (act && (sig == SIGKILL || sig == SIGSTOP)))//不允许对此信号安装信号处理函数
  7. return -EINVAL;
  8. k = &current->sig->action[sig-1];//获取该信号的旧信号处理函数
  9. spin_lock(&current->sig->siglock);
  10. if (oact)
  11. *oact = *k;//获取老的信号具体操作
  12. if (act) {
  13. *k = *act;//获取新的信号处理操作,将阻塞位sigkill跟sigstop删除(不允许屏蔽)
  14. sigdelsetmask(&k->sa.sa_mask, sigmask(SIGKILL) | sigmask(SIGSTOP));
  15. /*
  16. * POSIX 3.3.1.3:
  17. * "Setting a signal action to SIG_IGN for a signal that is
  18. * pending shall cause the pending signal to be discarded,
  19. * whether or not it is blocked."
  20. *
  21. * "Setting a signal action to SIG_DFL for a signal that is
  22. * pending and whose default action is to ignore the signal
  23. * (for example, SIGCHLD), shall cause the pending signal to
  24. * be discarded, whether or not it is blocked"
  25. *
  26. * Note the silly behaviour of SIGCHLD: SIG_IGN means that the
  27. * signal isn't actually ignored, but does automatic child
  28. * reaping, while SIG_DFL is explicitly said by POSIX to force
  29. * the signal to be ignored.
  30. */
  31. if (k->sa.sa_handler == SIG_IGN
  32. || (k->sa.sa_handler == SIG_DFL
  33. && (sig == SIGCONT ||
  34. sig == SIGCHLD ||
  35. sig == SIGWINCH))) {
  36. spin_lock_irq(&current->sigmask_lock);
  37. if (rm_sig_from_queue(sig, current))//如果设置的处理模式是sig_ign或者sig_del
  38. //而涉及的信号是上面3个,那就直接丢弃已到达信号
  39. recalc_sigpending(current);//是否有信号等待处理总标志位也需要再计算
  40. spin_unlock_irq(&current->sigmask_lock);
  41. }
  42. }
  43. spin_unlock(&current->sig->siglock);
  44. return 0;
  45. }

rm_sig_from_queue丢弃已到达信号实现
  1. /*
  2. * Remove signal sig from t->pending.
  3. * Returns 1 if sig was found.
  4. *
  5. * All callers must be holding t->sigmask_lock.
  6. */
  7. static int rm_sig_from_queue(int sig, struct task_struct *t)
  8. {
  9. return rm_from_queue(sig, &t->pending);
  10. }
其主体实现是rm_from_queue
  1. static int rm_from_queue(int sig, struct sigpending *s)
  2. {
  3. struct sigqueue *q, **pp;
  4. if (!sigismember(&s->signal, sig))//表示该位是否已经置1,一般是已经,否则直接return
  5. return 0;
  6. sigdelset(&s->signal, sig);//清除该标志位
  7. pp = &s->head;
  8. while ((q = *pp) != NULL) {
  9. if (q->info.si_signo == sig) {//如果队列中的信号等于sig(针对新类型信号)
  10. if ((*pp = q->next) == NULL)
  11. s->tail = pp;
  12. kmem_cache_free(sigqueue_cachep,q);//删除
  13. atomic_dec(&nr_queued_signals);//-1
  14. continue;
  15. }
  16. pp = &q->next;
  17. }
  18. return 1;
  19. }
查看发送信号处理
  1. asmlinkage long
  2. sys_kill(int pid, int sig)
  3. {
  4. struct siginfo info;
  5. //收集相关信息
  6. info.si_signo = sig;//信号类型
  7. info.si_errno = 0;
  8. info.si_code = SI_USER;//用户信号
  9. info.si_pid = current->pid;//进程号
  10. info.si_uid = current->uid;//uid
  11. return kill_something_info(sig, &info, pid);
  12. }

  1. /*
  2. * kill_something_info() interprets pid in interesting ways just like kill(2).
  3. *
  4. * POSIX specifies that kill(-1,sig) is unspecified, but what we have
  5. * is probably wrong. Should make it like BSD or SYSV.
  6. */
  7. static int kill_something_info(int sig, struct siginfo *info, int pid)
  8. {
  9. if (!pid) {//pid==0发送给当前进程的所有进程
  10. return kill_pg_info(sig, info, current->pgrp);
  11. } else if (pid == -1) { //发送到除当前进程以外的进程
  12. int retval = 0, count = 0;
  13. struct task_struct * p;
  14. read_lock(&tasklist_lock);
  15. for_each_task(p) {
  16. if (p->pid > 1 && p != current) {
  17. int err = send_sig_info(sig, info, p);
  18. ++count;
  19. if (err != -EPERM)
  20. retval = err;
  21. }
  22. }
  23. read_unlock(&tasklist_lock);
  24. return count ? retval : -ESRCH;
  25. } else if (pid < 0) {//小于值,则发送给-pid的进程组
  26. return kill_pg_info(sig, info, -pid);
  27. } else {
  28. return kill_proc_info(sig, info, pid);//发送给具体的进程
  29. }
  30. }

  1. inline int
  2. kill_proc_info(int sig, struct siginfo *info, pid_t pid)
  3. {
  4. int error;
  5. struct task_struct *p;
  6. read_lock(&tasklist_lock);
  7. p = find_task_by_pid(pid);//找到pid对应的task_struct结构
  8. error = -ESRCH;
  9. if (p)
  10. error = send_sig_info(sig, info, p);//发送信号
  11. read_unlock(&tasklist_lock);
  12. return error;
  13. }
最重要部分send_sig_info

  1. int //信号 信号相关信息 接收信号进程的pcb
  2. send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
  3. {
  4. unsigned long flags;
  5. int ret;
  6. #if DEBUG_SIG
  7. printk("SIG queue (%s:%d): %d ", t->comm, t->pid, sig);
  8. #endif
  9. ret = -EINVAL;
  10. if (sig < 0 || sig > _NSIG)//不在信号范围出错
  11. goto out_nolock;
  12. /* The somewhat baroque permissions check... */
  13. ret = -EPERM;
  14. if (bad_signal(sig, info, t))//进行错误检查
  15. goto out_nolock;
  16. /* The null signal is a permissions and process existance probe.
  17. No signal is actually delivered. Same goes for zombies. */
  18. ret = 0;
  19. if (!sig || !t->sig)//如果信号为0或者,不存在对应操作
  20. goto out_nolock;
  21. spin_lock_irqsave(&t->sigmask_lock, flags);
  22. handle_stop_signal(sig, t);//收到某些特定的信号,不可屏蔽一些其他后续信号,这个负责处理
  23. /* Optimize away the signal, if it's a signal that can be
  24. handled immediately (ie non-blocked and untraced) and
  25. that is ignored (either explicitly or by default). */
  26. if (ignored_signal(sig, t))//如果响应是忽略,而且不用跟踪,没有屏蔽,那就不用投递信号
  27. goto out;
  28. /* Support queueing exactly one non-rt signal, so that we
  29. can get more detailed information about the cause of
  30. the signal. */
  31. if (sig < SIGRTMIN && sigismember(&t->pending.signal, sig))
  32. goto out;//老编制的信号,投递,就是把接收信号位图设置为1,不用将信号挂到队列
  33. ret = deliver_signal(sig, info, t);
  34. out:
  35. spin_unlock_irqrestore(&t->sigmask_lock, flags);
  36. if ((t->state & TASK_INTERRUPTIBLE) && signal_pending(t))//如果接收的进程处于这个状态,
  37. //而且有信号要处理,则唤醒进程
  38. wake_up_process(t);
  39. out_nolock:
  40. #if DEBUG_SIG
  41. printk(" %d -> %d\n", signal_pending(t), ret);
  42. #endif
  43. return ret;
  44. }

  1. static int deliver_signal(int sig, struct siginfo *info, struct task_struct *t)
  2. {
  3. int retval = send_signal(sig, info, &t->pending);
  4. if (!retval && !sigismember(&t->blocked, sig))
  5. signal_wake_up(t);
  6. return retval;
  7. }


  1. static int send_signal(int sig, struct siginfo *info, struct sigpending *signals)
  2. {
  3. struct sigqueue * q = NULL;//创建一个相关嘟列
  4. /* Real-time signals must be queued if sent by sigqueue, or
  5. some other real-time mechanism. It is implementation
  6. defined whether kill() does so. We attempt to do so, on
  7. the principle of least surprise, but since kill is not
  8. allowed to fail with EAGAIN when low on memory we just
  9. make sure at least one signal gets delivered and don't
  10. pass on the info struct. */
  11. if (atomic_read(&nr_queued_signals) < max_queued_signals) {
  12. q = kmem_cache_alloc(sigqueue_cachep, GFP_ATOMIC);
  13. }
  14. if (q) {
  15. atomic_inc(&nr_queued_signals);
  16. q->next = NULL;//新创建的,设置为null
  17. *signals->tail = q;//指向尾部
  18. signals->tail = &q->next;//
  19. switch ((unsigned long) info) {
  20. case 0:
  21. q->info.si_signo = sig;
  22. q->info.si_errno = 0;
  23. q->info.si_code = SI_USER;
  24. q->info.si_pid = current->pid;
  25. q->info.si_uid = current->uid;
  26. break;
  27. case 1:
  28. q->info.si_signo = sig;
  29. q->info.si_errno = 0;
  30. q->info.si_code = SI_KERNEL;
  31. q->info.si_pid = 0;
  32. q->info.si_uid = 0;
  33. break;
  34. default:
  35. copy_siginfo(&q->info, info);//拷贝相关信息,到队列
  36. break;
  37. }
  38. } else if (sig >= SIGRTMIN && info && (unsigned long)info != 1
  39. && info->si_code != SI_USER) {
  40. /*
  41. * Queue overflow, abort. We may abort if the signal was rt
  42. * and sent by user using something other than kill().
  43. */
  44. return -EAGAIN;
  45. }
  46. sigaddset(&signals->signal, sig);//将接收信号,相应位图设置为1
  47. return 0;
  48. }

  1. /*
  2. * Note that 'init' is a special process: it doesn't get signals it doesn't
  3. * want to handle. Thus you cannot kill init even with a SIGKILL even by
  4. * mistake.
  5. */
  6. int do_signal(struct pt_regs *regs, sigset_t *oldset)
  7. {
  8. siginfo_t info;
  9. struct k_sigaction *ka;
  10. /*
  11. * We want the common case to go fast, which
  12. * is why we may in certain cases get here from
  13. * kernel mode. Just return without doing anything
  14. * if so.
  15. */
  16. if ((regs->xcs & 3) != 3)
  17. return 1;
  18. if (!oldset)//如果为0
  19. oldset = &current->blocked;//获取其屏蔽信号
  20. for (;;) {
  21. unsigned long signr;
  22. spin_lock_irq(&current->sigmask_lock);
  23. signr = dequeue_signal(&current->blocked, &info);//取出一个没屏蔽的信号
  24. spin_unlock_irq(&current->sigmask_lock);
  25. if (!signr)//如果为0则跳出(因为表示当前没有这样的信号了)
  26. break;
  27. //如果当前
  28. if ((current->ptrace & PT_PTRACED) && signr != SIGKILL) {
  29. /* Let the debugger run. */
  30. current->exit_code = signr;
  31. current->state = TASK_STOPPED;
  32. notify_parent(current, SIGCHLD);
  33. schedule();
  34. /* We're back. Did the debugger cancel the sig? */
  35. if (!(signr = current->exit_code))
  36. continue;
  37. current->exit_code = 0;
  38. /* The debugger continued. Ignore SIGSTOP. */
  39. if (signr == SIGSTOP)
  40. continue;
  41. /* Update the siginfo structure. Is this good? */
  42. if (signr != info.si_signo) {
  43. info.si_signo = signr;
  44. info.si_errno = 0;
  45. info.si_code = SI_USER;
  46. info.si_pid = current->p_pptr->pid;
  47. info.si_uid = current->p_pptr->uid;
  48. }
  49. /* If the (new) signal is now blocked, requeue it. */
  50. if (sigismember(&current->blocked, signr)) {
  51. send_sig_info(signr, &info, current);
  52. continue;
  53. }
  54. }
  55. ka = &current->sig->action[signr-1];//获取信号向量
  56. if (ka->sa.sa_handler == SIG_IGN) {//如果对应的处理是SIG_IGN
  57. if (signr != SIGCHLD)//忽略则继续取没屏蔽信号,处理
  58. continue;
  59. //是儿子信号则等待所有子进程退出
  60. /* Check for SIGCHLD: it's special. */
  61. while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0)
  62. /* nothing */;
  63. continue;
  64. }
  65. //信号向量设置为SIG_DFL
  66. if (ka->sa.sa_handler == SIG_DFL) {
  67. int exit_code = signr;
  68. /* Init gets no signals it doesn't want. */
  69. if (current->pid == 1)//不可以对init进程发送相关信号
  70. continue;
  71. switch (signr) {
  72. case SIGCONT: case SIGCHLD: case SIGWINCH:
  73. continue;//如果是SIGCONT之类,continue继续处理信号
  74. case SIGTSTP: case SIGTTIN: case SIGTTOU:
  75. if (is_orphaned_pgrp(current->pgrp))
  76. continue;
  77. /* FALLTHRU */
  78. case SIGSTOP://如果是sigstop信号
  79. current->state = TASK_STOPPED;//设置标志位
  80. current->exit_code = signr;
  81. if (!(current->p_pptr->sig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP))
  82. notify_parent(current, SIGCHLD);
  83. schedule();
  84. continue;
  85. case SIGQUIT: case SIGILL: case SIGTRAP:
  86. case SIGABRT: case SIGFPE: case SIGSEGV:
  87. case SIGBUS: case SIGSYS: case SIGXCPU: case SIGXFSZ:
  88. if (do_coredump(signr, regs))//这类信号默认处理
  89. exit_code |= 0x80;
  90. /* FALLTHRU */
  91. default://多数信号的默认处理
  92. sigaddset(&current->pending.signal, signr);//猜想设置此位表示默认处理,留给父进程查看信息
  93. recalc_sigpending(current);//检查进程是否有非阻塞的挂起信号
  94. current->flags |= PF_SIGNALED;
  95. do_exit(exit_code);//结束接收信号的进程
  96. /* NOTREACHED */
  97. }
  98. }
  99. /* Reenable any watchpoints before delivering the
  100. * signal to user space. The processor register will
  101. * have been cleared if the watchpoint triggered
  102. * inside the kernel.
  103. */
  104. __asm__("movl %0,%%db7" : : "r" (current->thread.debugreg[7]));
  105. /* Whee! Actually deliver the signal. */
  106. handle_signal(signr, ka, &info, oldset, regs);
  107. return 1;
  108. }
  109. /* Did we come from a system call? */
  110. if (regs->orig_eax >= 0) {
  111. /* Restart the system call - no handlers present */
  112. if (regs->eax == -ERESTARTNOHAND ||
  113. regs->eax == -ERESTARTSYS ||
  114. regs->eax == -ERESTARTNOINTR) {
  115. regs->eax = regs->orig_eax;
  116. regs->eip -= 2;
  117. }
  118. }
  119. return 0;
  120. }


信号安装过程
获取旧的信号处理方法
同时安装新的信号处理方法,将信号处理函数掩码的阻塞位sigkill跟sigstop删除,返回旧信号处理方法

信号发送过程
siginfo收集发送信号当前信息(进程号,uid号,信号的对应数值)
如果响应是忽略,而且不用跟踪,没有屏蔽,那就不用投递信号
老编制的信号,投递,就是把接收信号位图设置为1,如果是第一次则挂载到队列,不是第一次则不用将信号挂到队列
接着创建一个队列结构,拷贝siginfo信号,同时将sigpending的signal位图设置为1,完成传递

处理过程(for循环,不断的获取已挂载但没阻塞的信号,直到没有为止)
获取信号向量,如果对应位是SIG_IGN,那就直接continue,是儿子信号则调用wait等待所有子进程退出
默认处理,一般直接退出
否则将调用用户自定义的函数


来自为知笔记(Wiz)


posted on 2016-10-27 10:50  笨拙的菜鸟  阅读(1131)  评论(0编辑  收藏  举报

导航