linux signal framework
linux signal framework
信号产生&处理流程
信号产生(signal generate)
信号的产生可以是user space通过kill等系统调用给某个线程(目标线程)发送某个signal,此时signal generate的流程如下:
1. 根据pid确定对应的task_struct;
2. 此时pid_type是PIDTYPE_TGID,所以sigpending是task_struct.signal.shared_pending而不是task_struct.pending,这表示目标线程所在的进程的所有线程都可以处理这个signal。只要有一个线程处理了该signal,将会把此signal从sigpending中移除,这样就保证一个pending signal只会被处理一次;
3. 分配一个sigqueue,它表示目前正在发送的signal,并把此sigqueue插入到上述sigpending.list链表,同时将sigpending.signal pending信号bitmap中当前signal对应bit置1;
4. 调用complete_signal()选择一个thread,将这个thread的TIF_SIGPENDING flag设置上(这样此thread在系统调用结束return to user时发现此flag被设置上了,就会去处理pending signal),并wake此thread;
这里在选择thread时,如果此signal没有被此thread block,则选择的thread一般就是目标线程。如果此signal被目标线程block了,则检查目标线程所在的进程内的其它线程是否有block此signal,如果都block了,则会直接return,所以设置TIF_SIGPENDING和thread wake动作都不会执行。从这里可以看出,进程内的线程blocked signal集合可以不一样,这个从sigprocmask系统调用也可以看出来,这个系统调用设置signal block是以thread为单位进行的。另外如果是给非主线程发送signal,如果这个thread将这个signal block了,则在complete_signal()会直接return,因为非主线程的task_struct.thread_group链表是空的。
信号处理(signal deliver)
1. 在线程系统调用完毕准备ret_to_user时,检查本线程的TIF_SIGPENDING flag是否有设置上,如果有设置上,则进行信号处理;
2. 根据本线程task_struct.blocked从task_struct.pending或者task_struct.signal.shared_pending中取出没有被block的pending signal,取出时会将此pending signal的sigqueue从sigpending.list链表上移除,并将此signal在sigpending.signal的对应bit clear;
3. 根据2中得到的pending signal number,得到此signal对应的sa_handler,在sa_handler不为SIG_IGN和SIG_DFL的情况下,即进程本身安装了该signal对应的signal handler,get_signal()返回这个signal,后面将会调用这个signal对应的signal handler
关于blocked signal
如果线程将某个signal block了,则在给这个线程发送signal时还是会分配一个sigqueue并把这个sigqueue插入到的pending list,同时sigpending.signal信号集合里也加入这个signal,但是在complete_signal()应该不会call signal_wake_up()去设置目标线程的TIF_SIGPENDING以及thread wake;同时线程在处理pending signal时,会将pending signal集合里的当前线程已经block的signal过滤掉,只关心pending signal集合里没有被本线程block的其它signal,所以如果一个线程block了某个signal,同时后面一直没有unblock它,在此线程收到了此signal后,此signal将一直在sigpending里。
sa_handler类型
sa_handler类型有SIG_IGN/SIG_DFL/customized3种
进程对信号的处理方式默认是SIG_DFL,除非进程自己有给信号安装信号处理函数或者设置为SIG_IGN了。
1. 信号处理方式SIG_DFL
信号处理方式如果是SIG_DFL,同时信号是SIG_KERNEL_IGNORE_MASK/SIG_KERNEL_STOP_MASK这两类signal之外的其它signal,则sig_fatal()条件成立,则这个signal一般会转化为SIGKILL,即将这个进程中的每个线程的task_struct.pending.signal集合上加上SIGKILL,同时将这个进程的signal_struct.flags设置为SIGNAL_GROUP_EXIT,此后因为有这个flag,进程里的每个线程在处理pending signal时会直接call do_group_exit()将本线程exit。如果signal是kernel ignore/kernel stop两类信号其中之一,则不会call do_group_exit(),其中如果是kernel ignore类其中之一的signal,则直接ignore,不做任何处理。
2. 信号处理方式SIG_IGN
信号处理方式如果是SIG_IGN,则在send signal时(__send_signal())直接取消信号发送,这样这个信号将不会被发给目标进程;如果是在处理signal时(信号处理不是同步的,有可能在send signal时这个signal没有被ignore,后面马上又将它ignore了),发现当前signal进程是ignore它的,则也会直接ignore这个signal,不做任何处理
3. 信号处理方式customized
信号处理sa_handler如果既不是SIG_IGN,也不是SIG_DFL,则是进程有安装自己的信号处理函数,则会call这个进程安装的信号处理函数来处理这个signal
关于给非主线程发送signal
1. 应该是可以给进程里的任意线程发送signal的,而不是只能给进程(主线程)发送signal,给非主线程发送signal时,指定非主线程id(task_struct.pid)即可给此非主线程发送某一signal,此时设置TIF_SIGPENDING和wake的thread将是此非主线程(在此非主线程没有block该signal的前提下)。只是即使在user space给非主线程发送signal,这个signal也会被记录到task_struct.signal.shared_pending,此时进程内的所有线程均可以去处理这个signal,而非只有指定的非主线程才能去处理。在user space,一般是给主线程(进程)发送signal。
2. 如果给非主线程发送的signal没有被非主线程blocked,并且该signal对于sig_fatal()条件满足,此时不会像主线程那样将进程内的所有线程的task_struct.pending.signal SIGKILL bit置1,此时只会将它自己的task_struct.pending.signal SIGKILL bit置1,同时设置TIF_SIGPENDING、wake的thread只有该非主线程
进程内所有线程共享同一个signal_struct/sighand_struct结构体
signal_struct以及sighand_struct只会在主线程创建时分配,非主线程创建时不再分配,所以一个继承只会有一个signal_struct/sighand_struct,进程内所有线程的task_struct.signal/sighand指针都指向这个signal_struct/sighand_struct。
这个在copy_signal()里可以看出来,如果clone_flags中有CLONE_THREAD flag(创建非主线程时,一般会带这个flag,比如pthread_create()里call clone时其clone_flags里就会带有CLONE_THREAD)时,将不会给当前的线程分配signal_struct,而是通过dup_task_struct()从而这个线程的task_struct.signal_struct指针和current的一致,signal_struct只会在主线程创建时分配,即fork一个新进程时分配。sighand_struct类似,在pthread_create调用clone时会带有CLONE_SIGHAND flag,所以非主线程创建时并不会去alloc新的sighand_struct(copy_sighand())
另外一篇关于signal处理的blog链接(fatal signal的处理,fatal signal时进程退出):
https://www.cnblogs.com/aspirs/p/15850713.html