linux下无论是进程还是线程都是用task_struct来表示,task_struct中有与信号相关的字段,如下:
signal中有一个shared_pending字段,表示共享的待处理的信号, sighand表示信号的处理函数,blocked表示被屏蔽的信号,pending表示待处理的信号。 这是不是就是说每个线程都可以单独处理信号呢?不是。
用户空间创建线程通常调用pthread_create,创建进程通常调用fork,这两个函数最终都会调用系统调用clone, 只是传递的参数不一样,pthread_create带有CLONE_SIGHAND标记,而fork没有。
clone--> ... --> kernel_clone-->copy_process,copy_process中有几处与信号有关的代码:
p = dup_task_struct(current, node);
init_sigpending(&p->pending);
retval = copy_sighand(clone_flags, p);
retval = copy_signal(clone_flags, p);
dup_task_struct创建一个新的task_struct,这个task_struct与原来的task_struct几乎一样(某些字段进行了初始化);
init_sigpending将新的task_struct的pending进行了初始化,说明新task不会继承待处理的信号;
copy_sighand中如果有CLONE_SIGHAND标志则仅仅增加记数,新task_struct中的sighand不变,而sighand是一个指指针,说明新线程中的sighand指向父线程中的sighand,与父线程共享一个sighand。如果没有CLONESIGHAND标记则会为新task_struct创建一个新的sighand_struct,并且将父task_struct中的信号的处理函数复制过去,说明新进程不与父进程共享sighand,但处理函数从父进程继承。
copy_signal中如果没有CLONE_THREAD标记则为新的task_struct创建一个新的signal,线程的signal(字段)是共享的,而进程的signal是独立的。
新的task_struct的blocked字段没有改变直接从父task_struct继承过来,说明新task会继承被屏蔽的信号;
由此可以得出:
- 信号处理函数在线程间是共享的,而(父子)进程间是独立的,但新进程会从父进程继承处理函数
- 待处理信号线程和进程间都是独立的,且不会从父亲继承,初始化是空的
- 屏蔽信号线程和进程间都是独立的,且会从父亲继承
kill函数发送信号到进程,内核更新task_struct->signal->shared_pending字段,当进程中的某个线程返回到用户态时,首先检查shared_pending字段,如果有待处理的信号再检查其task_struct->blocked,如果被屏蔽了则不处理,否则执行其处理函数并将信号从shared_pending中移除。
pthread_kill函数发送信号到线程,内核更新task_struct->pending字段,当线程返回到用户态时首先检查pending字段,如果有待处理的信号再检查其task_struct->blocked,如果被屏蔽了则不处理,否则执行其处理函数并将信号从pending中移除。
为什么发送给进程的信号要先保存到shared_pending而不是直接找一个线程来处理? 有可能信号到来时所有的线程都屏蔽了该信号,这样就会造成信号的丢失。(还有其他原因)