进程间通信 IPC
线程之间通信简单,直接读写全局共享变量就行。但是进程之间内存完全隔离,互相看不见对方的数据,必须靠操作系统提供的IPC手段传递数据。
常见方式:管道、信号、消息队列、共享内存、信号量。
1.管道
无名管道 pipe()
只能给有血缘关系进程用,比如父子进程。
调用pipe(fd),得到两个文件描述符 fd0读端,fd1写端。
子进程fork之后,父子进程都持有这两个文件描述符。一个往fd1写数据,另一个从fd0读数据。
半双工:单向数据流,数据只能从写端流向读端。
管道行为特点:
-
管道里面有数据,read直接读;没数据,read阻塞睡觉。
-
如果所有写端全部关闭,read读完现有数据直接返回0,不会阻塞。
-
如果所有读端全部关闭,再往管道write写,直接触发管道破裂,进程收到信号直接退出。
有名管道 mkfifo
又叫FIFO文件,在磁盘生成一个特殊文件,没有血缘关系进程也能通信。
注意:open打开有名管道的时候,读和写必须两边都到位,open才会成功,否则程序阻塞卡住。
2.信号
信号就是操作系统发给进程的简短通知,用来告诉进程发生某个事件。比如按Ctrl+C就是发送SIGINT信号。
进程收到信号有三种处理方式:
-
默认处理:系统自带行为,大部分信号会直接杀死程序。
-
忽略信号,收到啥也不干。9号SIGKILL、19号SIGSTOP这俩信号不能忽略,强制杀死/暂停。
-
捕获信号:收到信号就执行我们自己写的信号处理函数。
相关函数:
-
signal():简单设置信号处理函数。
-
kill(pid,信号编号):给指定pid进程发送信号。
-
alarm(seconds):几秒之后,给自己进程发送SIGALRM闹钟信号。
-
pause():让进程睡觉,直到收到一个信号才唤醒继续跑。
3.消息队列
内核开辟的消息缓冲区,多个进程可以往队列丢消息包,也可以按类型读取消息包。
命令ipcs查看IPC对象,ipcrm删除。
ftok:根据文件路径+一个小数字,生成唯一key值,用这个key来创建消息队列。
msgget:根据key拿到消息队列ID。
msgsnd:发送消息。
msgrcv:接收消息,可以指定只读取某一类消息。
msgctl:删除消息队列,获取消息队列状态。
优点:可以按消息类型筛选读取;缺点:拷贝数据,有开销。
4.共享内存(IPC里面速度最快)
内核开辟一块物理内存,多个进程把这块内存映射到自己的虚拟地址空间。所有进程直接读写同一块内存,数据不需要在内核和用户空间来回拷贝,速度拉满。
注意!共享内存本身不带同步保护!多进程同时读写会乱,必须搭配信号量/互斥锁控制访问时序。
流程:
1.shmget:创建共享内存,拿到shmid;
2.shmat:把共享内存映射到当前进程虚拟地址,得到内存首地址,之后直接指针读写;
3.shmdt:解除映射,断开进程和这块内存的联系;
4.shmctl:销毁共享内存。
5.信号灯集(System V信号量)
跟线程信号量用途差不多,但是可以跨进程使用。semget创建信号灯集合,semop执行P/V操作(申请释放资源),semctl初始化值、删除信号灯。