Linux里有进程,线程,基于此有进程组,线程组的概念。Linux内核把进程,线程做一样
的实现,都用一个task_struct表示, 这里我们统一用linux内核的概念来看待以上的概念。
内核include/linux/pid.h中对PID的种类有如下的定义:
1 | enum pid_type |
这里PID指的是一个内核线程ID,对应到用户态可以是一个进程或是一个线程。TGID指线程组
ID一个用户态进程和他创建的所有线程组成一个线程组。PGID是指进程组ID,这个要调用linux
系统函数创建,把多个用户态进程组成一个进程组。SID是指会话ID。
信号可以从一个用户态进程发到一个用户态进程,也可以从一个用户态线程发到一个用户态
线程。当然信号还可以从内核发到用户态,这里就涉及到上面PID的类型,通过指定不同的
PID类型, 内核可以把信号发到单个线程(进程)、线程组、进程组等。
信号是一种进程的系统资源, 而且传递时携带的信息很少。这样的性质决定,使用信号最好
由整个系统的顶层设计规划,不然如果底层设计中使用了信号, 很容易和其他的软件部件
相互冲突。因为信号传递的信息很少,必然要再加入其他的逻辑才可以完成整个业务逻辑,
这就会带来系统的复杂度。信号执行是打断原有进程(线程)执行流的,编写信号处理函数
要使用可重入函数,而且为了防止死锁,信号处理函数里不能使用锁,这些限制都使得信号
处理函数的编写很容易出错。
可以把信号处理从使用信号处理函数转变到使用线程,在线程中等待信号到来,然后处理。
这样可以把之前异步的处理放在线程里处理,避免上面说的信号处理函数里不能加锁的限制,
(fix me: 是否可以使用可重入函数)。像这里介绍的那样:
https://www.ibm.com/developerworks/cn/linux/l-cn-signalsec/index.html.
但是这样处理在架构上也是有代价的,他要求你的业务线程和信号处理的线程在一个线程
组里,这样才可以在信号处理线程里即使的做处理。还有一个要求是,需要在这个线程组
主线程里就设置屏蔽信号处理线程要处理的信号,这样在随后的新线程里才可以继承这个
信号屏蔽,然后单独在信号处理线程里不去屏蔽这个信号。可以看到这种方案适用于自己
构建的方案,每个部分是自己可以控制的。但是,当你的方案要嵌入到更高一层的方案里
时, 用一个线程单独处理信号会带来非常多的麻烦。
内核驱动可以使用fasync发信号给一个fd绑定的进程。具体可以参考这里。
以上内核向用户态进程发信号需要用户态先执行fd和进程绑定的fcntl操作。实际上内核
可以直接类似send_sig_info的函数给一个PID发信号。