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

摘要:
信号在进程间通信是异步的,每个进程的task_struct结构有一个sig指针,指向一个signal_struct结构定义如下 structsignal_struct { atomic_tcount; structk_sigaction action[_NSIG];//类似一个信号向量表,每个元素就是一个函数指针 spinlock_tsiglock; };
信号在进程间通信是异步的,每个进程的task_struct结构有一个sig指针,指向一个signal_struct结构
定义如下
  1. structsignal_struct {
  2. atomic_tcount;
  3. structk_sigaction action[_NSIG];//类似一个信号向量表,每个元素就是一个函数指针
  4. spinlock_tsiglock;
  5. };
  1. structk_sigaction {
  2. structsigaction sa;
  3. };
  1. structsigaction {
  2. unsignedintsa_flags;
  3. __sighandler_tsa_handler;
  4. sigset_tsa_mask;
  5. void(*sa_restorer)(void);
  6. intsa_resv[1];/* reserved */
  7. };
由此可见,每个信号向量除了指向一个信号处理函数,他还可以是2个其他特殊的常数
  1. /* Fake signal functions */
  2. #defineSIG_DFL ((__sighandler_t)0)/* default signal handling */
  3. #defineSIG_IGN ((__sighandler_t)1)/* ignore signal */
  4. #defineSIG_ERR ((__sighandler_t)-1)/* error return from signal */
由于SIG_DFL得数值为0,当向量表为空白时,所有信号向量对被视为SIG_DFL
信号向量与中断向量不同的是:中断向量表在系统空间,每个中断向量所指向中断响应程序也在系统空间,但虽然信号向量表在系统空间,但这些向量指向的信号处理程序缺在用户空间.
对于信号的检测和响应总是发生在系统空间,发生情况:当前进程系统调用,中断,异常而进入系统调用,从系统调用返回到用户空间的前夕.第二,当前进程在内核中进入睡眠后刚被唤醒的时候,由于信号的存在(处于未决状态)提前返回到用户空间.
具体来分析下sigaction结构体
  1. structsigaction {
  2. unsignedintsa_flags;
  3. __sighandler_tsa_handler;
  4. sigset_tsa_mask;//屏蔽,一个64位的类型
  5. void(*sa_restorer)(void);
  6. intsa_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
linux内核情景分析之信号实现第1张
task_struct的信号队列与信号位图合并到一起了
  1. structsigqueue {
  2. structsigqueue *next;
  3. siginfo_tinfo;//挂载信号相关信息
  4. };
  1. structsigpending {
  2. structsigqueue *head,**tail;//挂载队列
  3. sigset_tsignal;//位图,信号要处理时,设置为1
  4. };

  1. structtask_struct {
  2. intsigpending;//表示该进程是否有信号需要处理
  3. intexit_code,exit_signal;
  4. /* Protects signal and blocked */
  5. structsignal_struct *sig;//指向具体的信号处理动作
  6. sigset_tblocked;//阻塞
  7. structsigpending pending;//位图+信号队列
  8. }
结构分析完了,接下来看看信号的安装
  1. int
  2. do_sigaction(intsig,conststructk_sigaction *act,structk_sigaction *oact)
  3. {
  4. structk_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. return0;
  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. staticintrm_sig_from_queue(intsig,structtask_struct *t)
  8. {
  9. returnrm_from_queue(sig,&t->pending);
  10. }
其主体实现是rm_from_queue
  1. staticintrm_from_queue(intsig,structsigpending *s)
  2. {
  3. structsigqueue *q,**pp;
  4. if(!sigismember(&s->signal,sig))//表示该位是否已经置1,一般是已经,否则直接return
  5. return0;
  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. return1;
  19. }
查看发送信号处理
  1. asmlinkage long
  2. sys_kill(intpid,intsig)
  3. {
  4. structsiginfo 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. returnkill_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. staticintkill_something_info(intsig,structsiginfo *info,intpid)
  8. {
  9. if(!pid){//pid==0发送给当前进程的所有进程
  10. returnkill_pg_info(sig,info,current->pgrp);
  11. }elseif(pid ==-1){//发送到除当前进程以外的进程
  12. intretval =0,count =0;
  13. structtask_struct *p;
  14. read_lock(&tasklist_lock);
  15. for_each_task(p){
  16. if(p->pid >1&&p !=current){
  17. interr =send_sig_info(sig,info,p);
  18. ++count;
  19. if(err !=-EPERM)
  20. retval =err;
  21. }
  22. }
  23. read_unlock(&tasklist_lock);
  24. returncount ?retval :-ESRCH;
  25. }elseif(pid <0){//小于值,则发送给-pid的进程组
  26. returnkill_pg_info(sig,info,-pid);
  27. }else{
  28. returnkill_proc_info(sig,info,pid);//发送给具体的进程
  29. }
  30. }

  1. inlineint
  2. kill_proc_info(intsig,structsiginfo *info,pid_tpid)
  3. {
  4. interror;
  5. structtask_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. returnerror;
  13. }
最重要部分send_sig_info

  1. int//信号 信号相关信息 接收信号进程的pcb
  2. send_sig_info(intsig,structsiginfo *info,structtask_struct *t)
  3. {
  4. unsignedlongflags;
  5. intret;
  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. gotoout_nolock;
  12. /* The somewhat baroque permissions check... */
  13. ret =-EPERM;
  14. if(bad_signal(sig,info,t))//进行错误检查
  15. gotoout_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. gotoout_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. gotoout;
  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. gotoout;//老编制的信号,投递,就是把接收信号位图设置为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 ",signal_pending(t),ret);
  42. #endif
  43. returnret;
  44. }

  1. staticintdeliver_signal(intsig,structsiginfo *info,structtask_struct *t)
  2. {
  3. intretval =send_signal(sig,info,&t->pending);
  4. if(!retval &&!sigismember(&t->blocked,sig))
  5. signal_wake_up(t);
  6. returnretval;
  7. }


  1. staticintsend_signal(intsig,structsiginfo *info,structsigpending *signals)
  2. {
  3. structsigqueue *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((unsignedlong)info){
  20. case0:
  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. case1:
  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. }elseif(sig >=SIGRTMIN &&info &&(unsignedlong)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. return0;
  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. intdo_signal(structpt_regs *regs,sigset_t*oldset)
  7. {
  8. siginfo_tinfo;
  9. structk_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. return1;
  18. if(!oldset)//如果为0
  19. oldset =&current->blocked;//获取其屏蔽信号
  20. for(;;){
  21. unsignedlongsignr;
  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. intexit_code =signr;
  68. /* Init gets no signals it doesn't want. */
  69. if(current->pid ==1)//不可以对init进程发送相关信号
  70. continue;
  71. switch(signr){
  72. caseSIGCONT:caseSIGCHLD:caseSIGWINCH:
  73. continue;//如果是SIGCONT之类,continue继续处理信号
  74. caseSIGTSTP:caseSIGTTIN:caseSIGTTOU:
  75. if(is_orphaned_pgrp(current->pgrp))
  76. continue;
  77. /* FALLTHRU */
  78. caseSIGSTOP://如果是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. caseSIGQUIT:caseSIGILL:caseSIGTRAP:
  86. caseSIGABRT:caseSIGFPE:caseSIGSEGV:
  87. caseSIGBUS:caseSIGSYS:caseSIGXCPU:caseSIGXFSZ:
  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. return1;
  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. return0;
  120. }
linux内核情景分析之信号实现第2张

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

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

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

来自为知笔记(Wiz)

免责声明:文章转载自《linux内核情景分析之信号实现》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇excel中使用统计列中的值在其他列出现的次数java~jackson实现接口的反序列化下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

AIR串口通信

最近公司的项目中需要用到串口通信,项目是用基于AIR的,AIR本身是不支持串口通信的,本想用 c#或java另写一个负责串口通信的模块,又感觉很烦不想那么弄,就想到了ANE。可惜以前也没弄过 ANE,现研究也感觉麻烦,主要也是因为自己很懒就想在网上找看看有没有现成的ANE,结果还真找到了。   废话说的有点多。 先放上 ANE地址 http://code....

深入浅出全面解析RDMA

https://blog.csdn.net/qq_21125183/article/details/80563463     RDMA(RemoteDirect Memory Access)技术全称远程直接内存访问,就是为了解决网络传输中客户端与服务器端数据处理的延迟而产生的。它将数据直接从一台计算机的内存传输到另一台计算机,无需双方操作系统的介入。这允许...

操作系统笔记-1

第一章 操作系统引论 ★计算机操作系统是方便用户、管理和控制计算机软硬件资源的系统软件(或程序集合)。 1.OS的目标:有效性、方便性、可扩充性、开放性 2.OS的作用:1) OS作为用户与计算机硬件系统之间的接口;             ★(用户使用计算机的三种方式:命令、系统调用、图标窗口) 2)OS作为计算机系统资源的管理者;           ...

Arduino---HC-05 蓝牙模块

蓝牙基础知识回顾: (一)Arduino和HC-05连接 注意:Arduino通过TX与HC-05进行通信,而Arduino的电压为5V,HC-05的允许电压为3.3V。短时间通信无妨(长时间可能烧毁HC-05)<接分压电路> U = 5*20/30 = 3.3V I = 5V/(10K+20K) = 0.16mA (二)Arduino和...

进程间通信 Queue队列使用 生产者消费者模型 线程理论 创建及对象属性方法 线程互斥锁 守护线程

进程补充 进程通信 要想实现进程间通信,可以用管道或者队列 队列比管道更好用(队列自带管道和锁) 管道和队列的共同特点:数据只有一份,取完就没了 无法重复获取用一份数据 队列特点:先进先出 堆栈特点:先进后出 我们采用队列来实现进程间数据通信,下面先介绍一下队列 Queue队列的基本使用 基本方法:q.put(元素) q.get() q.get_nowai...

HTTPS通信原理-证书交换

TLS握手过程 握手简述(以RSA为例): client hello:客户端给出TLS协议版本号,支持的加密算法、随机数Client random、扩展字段 server hello:服务端确认双方可支持的加密算法,并把数字证书下发给客户端。同时也会生成一个随机数Server random 客户端验证证书的有效性,并重新生成一个随机数Pre-main s...