进程间通信通常有几种方式。
1.管道
分为匿名管道、命名管道。
匿名管道就是命令行中常用的 xxx | xxx。
管道以文件的形式存在,但是使用方式类似于开发的瀑布模型,当下一个环节没有开始时,上一个环节就始终处在阻塞状态。
虽然管道在内核中以文件的形式存在,但是它不是真的写在磁盘上的文件,而只是对应到内核中的一段缓存而已。
管道工作流程如下
image.png
2.消息队列
制定一个文件路径,进程间交互就以文件路径生成唯一的key,进程间send/recv都以该key即可。
并且,发送的消息结构体,前四字节一定为表示消息类型的Long,其后的位数不定,由用户自定义。
定义了以后,就可以一端发送,一端接收指定类型了。
但是现在这种方式不太常用,因为有很多功能强大的用户级别的消息队列。
3.共享内存+信号量
实质上是每个进程拿出一块虚拟地址空间来,映射到相同的物理内存中,这样一个进程写入到内存的数据,另一个进程马上就能看到,并且省去了传递信息的步骤。
但是为了避免同时操作内存,所以还需要使用信号量来加锁。
4.socket
通过系统提供的socket接口,也可以实现进程间通信。
5.信号
系统异常时抛出的信号,当异常发生的时候,会先尝试直接执行进程实现的与这个信号相关的方法,如果没有实现该方法,则走系统默认实现(通常是杀进程返回对应errorCode。
下图为注册信号处理函数+信号发送处理流程
image.png
Linux 信号通信主要由如下几个步骤组成
信号处理函数的注册
信号处理函数的注册, 定义在用户空间
注册最终通过 rt_sigaction 系统调用发起
将用户空间定义的信号处理函数保存到 task_struct 中 sighand 的 action 数组中
信号的发送
信号的发送通过 kill/tkill/tgkill/rt_sigqueueinfo 函数执行
最终通过 __send_signal, 将这个信号添加到对应 进程/线程 的信号待处理链表中
< 32 为不可靠信号, 待处理列表中存在该信号, 则会自动忽略
= 32 为可靠信号, 同一个信号会被添加到信号队列中
信号的处理
信号的处理会在系统调用或中断处理结束返回用户空间的时机通过 exit_to_usermode_loop 中的 do_signal 执行
修改用户函数栈, 插入我们构建的信号处理函数的栈帧 rt_sigframe, 并且将原来的函数栈信息保存在 uc_mcontext 中
信号处理函数执行结束之后, 会通过系统调用 rt_sigreturn 恢复之前用户态栈