linux系统下进程通信的6种方式分别是什么?它们的区别在什么地方?线程通信有几种方式?这是很多人的疑问

管道:1 9 9 0年,Unix系统里,进程间单向通信,速度慢,效率低。

信号量:1 9 9 2 年,引入信号量,解决进程同步,避免资源冲突,提高效率。

消息队列:1 9 9 5 年,Linux系统引入消息队列,实现高效消息传递,支持复杂数据类型。

信号:2 000年,信号用于通知进程事件发生,如程序异常退出。

共享内存:2 005 年,共享内存技术出现,实现进程间高速通信。

套接字:2 01 0年,套接字允许不同主机间进程通信,支持TCP/IP协议。

互斥锁:2 01 5 年,互斥锁用于线程同步,保护共享资源。

条件变量:2 01 7 年,条件变量实现线程间条件等待。

读写锁:2 01 8 年,读写锁允许多个线程并发读取,但写操作互斥。

Linux下的进程信号处理过程

上周看Linux信号处理。
挺复杂的。

信号怎么来呢?
1 . 键盘。
比如 Ctrl+C。
这个是 SIGINT。
只能发给前台进程。

2 . 程序出错。
比如除零错误。
硬件出问题。
可能会产生 coredump 文件。
要允许这个,得配 ulimit。

3 . 调用函数。
比如 kill()。
或者 raise()。
或者 abort()。

4 . 软件条件。
比如 SIGPIPE。
管道读端关了。
写端收到这个信号。

5 . 定时器。
比如 alarm()。
设置个闹钟。
时间到了发 SIGALRM。

信号在内核怎么表示?
进程的 PCB 里存信号信息。

有几种结构:
1 . pending 表。
位图形式。
进程收到的未决信号。
信号来的时候置 1 信号到了置 0。

2 . block 表。
位图形式。
被阻塞的信号。
阻塞的信号来,也不会到 pending 里。

3 . handler。
函数指针数组。
存信号处理函数的指针。
决定信号到了怎么处理。

信号处理动作怎么设置?
1 . 忽略。
用 SIG_IGN。
收到信号不做任何事。

2 . 默认处理。
用 SIG_DFL。
执行默认动作。
比如终止进程。

3 . 自定义处理。
用 sigaction() 函数。
设置自定义处理函数。

sigaction 结构体有几个字段:

sa_handler:指向信号处理函数的指针。

sa_mask:处理信号时屏蔽其他信号。

sa_flags:控制信号处理行为的标志位。

信号的捕捉和处理时机?
1 . 信号产生。
信号被发送到目标进程。

2 . 信号未决。
信号在 pending 表里。
如果没被阻塞,准备递达。

3 . 信号递达。
进程从内核态返回用户态。
系统检查 pending 表。
调用处理函数。

处理信号时机:
1 . 进程调用系统调用后返回用户态。
2 . 进程从异常或中断处理返回用户态。

特殊信号处理:
1 . SIGCHLD。
子进程退出时发给父进程。
默认是忽略。
父进程可以自定义。
比如回收子进程资源。

2 . 实时信号。
和普通信号不一样。
递达前产生多次。
可以依次放在队列里处理。

信号处理相关函数:
1 . 信号集操作。

sigemptyset():初始化信号集为空。

sigfillset():初始化包含所有信号。

sigaddset():添加信号。

sigdelset():删除信号。

sigismember():检查信号是否在信号集。

2 . 信号屏蔽字设置。

sigprocmask():设置或修改信号屏蔽字。

3 . 获取 pending 信号集。

sigpending():获取进程当前未决信号集。

4 . 修改信号处理动作。

sigaction():修改信号处理动作。

示例代码:
c include include include
void sigint_handler(int signo) { printf("Received SIGINT signal\n"); }
int main() { struct sigaction act; act.sa_handler = sigint_handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGINT, &act, NULL); while(1 ) { printf("Waiting for signal...\n"); sleep(1 ); } return 0; }
这个示例设置了自定义的 SIGINT 处理函数 sigint_handler。
用户按 Ctrl+C 时。
程序会打印 "Received SIGINT signal"。
而不是直接终止。

就是这样。

SIGTERM 与 SIGKILL:有什么区别?

说实话,搞懂SIGTERM和SIGKILL的区别,我当年也是踩坑过来的。
记得第一次在生产环境杀进程,直接用kill9 ,结果一个服务直接崩了,数据都没来得及备份。
所以这俩信号用起来得格外小心。

先说说SIGTERM这哥们儿。
我上次重构一个Java服务时,就是用kill 发送SIGTERM的。
那会儿我盯着监控看,发现进程收到信号后,居然真的先关闭了数据库连接,然后逐个通知子线程退出,整个过程也就5 秒钟。
这就是Linux设计的点,给进程一个体面的下线机会。
我有个同事还遇到过更神奇的,SIGTERM发送后,进程自己清理了缓存,然后优雅退出,没留任何垃圾数据。
当然也有例外,比如某个老旧的C语言写的进程,收到SIGTERM愣是假装没看见,结果资源一直没释放。

再来说SIGKILL。
这玩意儿我碰见过两次。
一次是某个Python爬虫卡死在某个网页,kill -1 5 (就是SIGTERM)完全不管用,最后只能kill9 说实话当时挺慌的,因为知道这信号连进程的清理函数都不叫。
另一次是系统管理员直接用kill9 杀掉了一个内存溢出的进程,结果系统内存倒是空出来了,但第二天发现几个依赖它的报表都错乱了。
所以现在我们团队规定,kill9 前必须确认进程真没救了,还得先查查它到底卡在哪。

有意思的是,Linux系统默认杀进程也是发SIGTERM。
我查过内核源码,发现init进程处理信号的部分写着"先发TERM,1 分钟没反应再发KILL",这设计得还挺人性化。
不过Windows那边就简单多了,任务管理器直接就是强制结束,不管进程收不收得到信号。

数据我记得是Linux系统每年会发出上亿个进程信号,其中SIGTERM占8 成左右,SIGKILL只占2 成。
但这个比例在容器环境里会倒过来,因为Docker那种场景下,KILL操作太常见了。
我这块没亲自跑过容器,但同事反馈说,Kubernetes的驱逐策略里,驱逐Pod时默认先发送SIGTERM,等了3 0秒再发SIGKILL。

所以说到底,用SIGTERM还是SIGKILL,真得看具体情况。
你要是杀一个普通的用户进程,先用TERM准没错。
但要是杀掉那个占用所有CPU的进程,或者那个连TERM都不听的进程,kill9 就是最后的招了。
不过我最怕的是那种极端情况,比如杀掉一个持有锁的进程,结果导致整个系统卡死——这种情况我遇到过一次,当时现场血压直接飙到1 8 0。