linux 信号signal实现
信号是发送给进程或进程组的非常小的消息,通常只包含信号编号;现在的系统可以附带siginfo信息,见sigaction的SA_SIGINFO标识。信号主要有以下两个目的:1.让进程知道出现某异步事件2.出现异步事件,让进程能做出相应的处理(通过提供信号处理函数,由内核自动调用) I.信号生命周期i.信号生命周期1.信号产生:内核更新目标进程数据结构,表示出现某异步事件
信号是发送给进程或进程组的非常小的消息,通常只包含信号编号;现在的系统可以附带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,信号处理完成后要能够恢复进程的原执行流程。
更多推荐
所有评论(0)