信号是发送给进程或进程组的非常小的消息,通常只包含信号编号;现在的系统可以附带siginfo信息,见sigaction的SA_SIGINFO标识。

信号主要有以下两个目的:
1.让进程知道出现某异步事件
2.出现异步事件,让进程能做出相应的处理(通过提供信号处理函数,由内核自动调用)

 

I.信号生命周期
i.信号生命周期
1.信号产生:内核更新目标进程数据结构,表示出现某异步事件
2.信号pending:信号产生但是未被传递出去,叫信号pending;主要是因为信号产生、信号传递是分为两个阶段的(信号在内核态返回用户态时传递,信号产生是在任意时间),所以之间存在延迟;同时信号阻塞会大大加长信号pending的时间
3.信号传递:内核通过改变目标进程运行状态并执行信号的handler,来让目标进程对信号标识的异步事件做相应的处理

 

ii.信号产生
主要有以下4种情况会产生signal:
1.硬件异常:当硬件检测到异常时;如SIGFPE(除0异常等),SIGSEGV(非法内存访问)
2.软件通知:当某事发生需要通知用户进程时;如信号SIGURG(网络连接中出现out-of-band数据,发送该信号),SIGPIPE(当往没有读者的管道中写数据时,发送该信号)
3.终端信号:当按下某些终端键时;如SIGINT(ctrl+c)
4.kill系统调用:当通过kill系统调用向其他进程发送信号时;如kill SIGUSR1 pid(kill命令调用kill系统调用)

 

iii.信号pending
主要有以下因素会影响信号pending的时间:
1.内核通常只传递当前进程的信号;当CPU从内核态切换到用户态时,内核会传递当前进程(占用该CPU的进程)的信号,而不会传递其它进程的信号。其它进程占用CPU后内核才会传递信号给该进程。
2.某信号可以被阻塞,直到该信号取消阻塞后,该信号才会被传递
3.当在执行某信号的处理handler时,内核会自动阻塞该信号直到该信号的处理完成;所以信号被处理时,该信号的再次出现不会打断信号处理,所以信号的处理handler不需要可重入。

 

iv.信号传递
当内核传递信号时,进程可以有如下action:
1.显式的忽略该信号
2.执行内核预定的默认动作,Terminate、Dump、Ignore、Stop、Continue
3.执行进程提供的自定义信号处理handler

 


II.数据结构
i.sigpending
siginfo用于记录信号信息

/* include/asm-generic/siginfo.h */
 40 typedef struct siginfo {
 41         int si_signo;
 42         int si_errno;
 43         int si_code;
 44 
 45         union {
 46                 int _pad[SI_PAD_SIZE];
 47 
 48                 /* kill() */
 49                 struct {
 50                         __kernel_pid_t _pid;    /* sender's pid */
 51                         __ARCH_SI_UID_T _uid;   /* sender's uid */
 52                 } _kill;
 53 
 54                 /* POSIX.1b timers */
 55                 struct {
 56                         __kernel_timer_t _tid;  /* timer id */
 57                         int _overrun;           /* overrun count */
 58                         char _pad[sizeof( __ARCH_SI_UID_T) - sizeof(int)];
 59                         sigval_t _sigval;       /* same as below */
 60                         int _sys_private;       /* not to be passed to user */
 61                 } _timer;
 62 
 63                 /* POSIX.1b signals */
 64                 struct {
 65                         __kernel_pid_t _pid;    /* sender's pid */
 66                         __ARCH_SI_UID_T _uid;   /* sender's uid */
 67                         sigval_t _sigval;
 68                 } _rt;
 69 
 70                 /* SIGCHLD */
 71                 struct {
 72                         __kernel_pid_t _pid;    /* which child */
 73                         __ARCH_SI_UID_T _uid;   /* sender's uid */
 74                         int _status;            /* exit code */
 75                         __kernel_clock_t _utime;
 76                         __kernel_clock_t _stime;
 77                 } _sigchld;
 78 
 79                 /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */
 80                 struct {
 81                         void __user *_addr; /* faulting insn/memory ref. */
 82 #ifdef __ARCH_SI_TRAPNO
 83                         int _trapno;    /* TRAP # which caused the signal */
 84 #endif
 85                         short _addr_lsb; /* LSB of the reported address */
 86                 } _sigfault;
 87 
 88                 /* SIGPOLL */
 89                 struct {
 90                         __ARCH_SI_BAND_T _band; /* POLL_IN, POLL_OUT, POLL_MSG */
 91                         int _fd;
 92                 } _sigpoll;
 93         } _sifields;
 94 } siginfo_t;

si_signo:信号编号
si_errno:产生信号的错误码,如果没错则为0
si_code:标识谁发出的信号;如SI_USER表示用户进程通过kill发出,SI_KERNEL表示内核发出等
_sifields:根据信号类型记录相应类型信号的信息;

 

sigqueue表示pending信号队列结点

/* include/linux/signal.h */
 10 /*
 11  * Real Time signals may be queued.
 12  */
 13 
 14 struct sigqueue {
 15         struct list_head list;
 16         int flags;
 17         siginfo_t info;
 18         struct user_struct *user;
 19 };

list:链接信号队列
flags:sigqueue标识
info:表示信号信息
user:指向用户信息

 

sigpending表示pending信号队列;
每个进程通常有两条信号pending队列,一条是共享信号pending队列(signal_struct.shared_pending),记录线程组pending的信号;
一条是私有信号pending队列(task_struct.pending),记录(轻量级)进程pending的信号。

 24 struct sigpending {
 25         struct list_head list;
 26         sigset_t signal;
 27 };

list:指向信号队列首节点
signal:pending的信号位图

 

ii.sighand_struct
sigaction表示信号传递时执行的action

/* arch/x86/include/asm/signal.h */
137 struct sigaction {
138         __sighandler_t sa_handler;
139         unsigned long sa_flags;
140         __sigrestore_t sa_restorer;
141         sigset_t sa_mask;               /* mask last for extensibility */
142 };
sa_handler:信号传递时执行的handler;可以是SIG_DFL,SIG_IGN或用户进程自定的handler
sa_flags:信号处理方式标识;如SA_SIGINFO表示信号处理时内核提供信号信息siginfo,SA_RESTART表示信号处理完成后自动重启被中断的系统调用
sa_restorer:信号恢复处理
sa_mask:表示信号处理过程中,被阻塞的信号

 480 struct sighand_struct {
 481         atomic_t                count;
 482         struct k_sigaction      action[_NSIG];
 483         spinlock_t              siglock;
 484         wait_queue_head_t       signalfd_wqh;
 485 };

count:引用计数器
action:信号处理数据,每个信号都有一个对应的确action
siglock:spinlock,保护sighand_struct和signal_struct
signalfd_wqh:等待队列
 

iii.signal_struct
signal_struct记录线程组共享的信号信息(不只有信号信息还有其它线程组相关的信息,如rlimit),如pending的信号等;sighand_struct与signal_struct都是线程组级别的信息且是共同存在的,所以可以只用一个锁,即放在sighand_struct中的siglock

/* include/linux/sched.h */
 555 /*
 556  * NOTE! "signal_struct" does not have it's own
 557  * locking, because a shared signal_struct always
 558  * implies a shared sighand_struct, so locking
 559  * sighand_struct is always a proper superset of
 560  * the locking of signal_struct.
 561  */
 562 struct signal_struct {
 563         atomic_t                count;
 564         atomic_t                live;
 565 
 566         wait_queue_head_t       wait_chldexit;  /* for wait4() */
 567 
 568         /* current thread group signal load-balancing target: */
 569         struct task_struct      *curr_target;
 570 
 571         /* shared signal handling: */
 572         struct sigpending       shared_pending;
 573 
 574         /* thread group exit support */
 575         int                     group_exit_code;
 576         /* overloaded:
 577          * - notify group_exit_task when ->count is equal to notify_count
 578          * - everyone except group_exit_task is stopped during signal delivery
 579          *   of fatal signals, group_exit_task processes the signal.
 580          */
 581         int                     notify_count;
 582         struct task_struct      *group_exit_task;
 583 
 584         /* thread group stop support, overloads group_exit_code too */
 585         int                     group_stop_count;
 586         unsigned int            flags; /* see SIGNAL_* flags below */
 587 
 588         /* POSIX.1b Interval Timers */
 589         struct list_head posix_timers;
 590 
 591         /* ITIMER_REAL timer for the process */
 592         struct hrtimer real_timer;
 593         struct pid *leader_pid;
 594         ktime_t it_real_incr;
 595 
 596         /*
 597          * ITIMER_PROF and ITIMER_VIRTUAL timers for the process, we use
 598          * CPUCLOCK_PROF and CPUCLOCK_VIRT for indexing array as these
 599          * values are defined to 0 and 1 respectively
 600          */
 601         struct cpu_itimer it[2];
 602 
 603         /*
 604          * Thread group totals for process CPU timers.
 605          * See thread_group_cputimer(), et al, for details.
 606          */
 607         struct thread_group_cputimer cputimer;
 608 
 609         /* Earliest-expiration cache. */
 610         struct task_cputime cputime_expires;
 611 
 612         struct list_head cpu_timers[3];
 613 
 614         struct pid *tty_old_pgrp;
 615 
 616         /* boolean value for session group leader */
 617         int leader;
 618 
 619         struct tty_struct *tty; /* NULL if no tty */
 620 
 621         /*
 622          * Cumulative resource counters for dead threads in the group,
 623          * and for reaped dead child processes forked by this group.
 624          * Live threads maintain their own counters and add to these
 625          * in __exit_signal, except for the group leader.
 626          */
 627         cputime_t utime, stime, cutime, cstime;
 628         cputime_t gtime;
 629         cputime_t cgtime;
 630 #ifndef CONFIG_VIRT_CPU_ACCOUNTING
 631         cputime_t prev_utime, prev_stime;
 632 #endif
 633         unsigned long nvcsw, nivcsw, cnvcsw, cnivcsw;
 634         unsigned long min_flt, maj_flt, cmin_flt, cmaj_flt;
 635         unsigned long inblock, oublock, cinblock, coublock;
 636         unsigned long maxrss, cmaxrss;
 637         struct task_io_accounting ioac;
 638 
 639         /*
 640          * Cumulative ns of schedule CPU time fo dead threads in the
 641          * group, not including a zombie group leader, (This only differs
 642          * from jiffies_to_ns(utime + stime) if sched_clock uses something
 643          * other than jiffies.)
 644          */
 645         unsigned long long sum_sched_runtime;
 646 
 647         /*
 648          * We don't bother to synchronize most readers of this at all,
 649          * because there is no reader checking a limit that actually needs
 650          * to get both rlim_cur and rlim_max atomically, and either one
 651          * alone is a single word that can safely be read normally.
 652          * getrlimit/setrlimit use task_lock(current->group_leader) to
 653          * protect this instead of the siglock, because they really
 654          * have no need to disable irqs.
 655          */
 656         struct rlimit rlim[RLIM_NLIMITS];
 657 
 658 #ifdef CONFIG_BSD_PROCESS_ACCT
 659         struct pacct_struct pacct;      /* per-process accounting information */
 660 #endif
 661 #ifdef CONFIG_TASKSTATS
 662         struct taskstats *stats;
 663 #endif
 664 #ifdef CONFIG_AUDIT
 665         unsigned audit_tty;
 666         struct tty_audit_buf *tty_audit_buf;
 667 #endif
 668 
 669         int oom_adj;    /* OOM kill score adjustment (bit shift) */
 670 };

count:引用计数器
live:线程组中的当前进程数
wait_chldexit:等待线程组中(轻量级)进程退出(wait4)的等待队列
curr_target:线程组中最近接收到信号的(轻量级)进程
shared_pending:共享信号pending队列
group_exit_code:进程组结束码
group_exit_task:killing整个线程组时使用
notify_count:killing整个线程组时使用
group_stop_count:暂停整个线程组时使用
flags:传递改变进程状态的信号时使用

 

iv.结构关系图
以上结构之间的关系如下图所示:



III.实现
内核实现信号:
1.记住进程哪些信号被阻塞
2.当由内核态切换到用户态时,检查进程是否有信号产生;每个时钟中断都会被触发,几乎在每微秒都会触发一次(一微秒触发一次是最坏的情况,因为在系统调用返回、硬件中断处理完成后返回、异常处理完成后返回都会触发信号检测)
3.判定信号是否被忽略,以下条件都满足时表示信号被忽略:
  a.进程未被追踪,task_struct中ptrace的PT_PTRACED标识为0
  b.进程未阻塞该信号
  c.进程忽略该信号,不论是显示的忽略还是默认行为是忽略
4.处理信号;进程执行的任意点时,信号传递时能去执行信号处理handler,信号处理完成后要能够恢复进程的原执行流程。

Logo

更多推荐