【嵌入式开发——Linux操作系统】8进程间通信IPC和内核同步
大家一定听过线程通信等误导词汇,介绍本章内容前,必须先理清一点:Linux中直接管理的任务叫做进程,不是线程(甚至Linux都没有线程的概念,创建一个线程本质仍是创建一个进程,只不过起访问资源有所限制而已),要不怎么会有一句:进程是资源分配的最小单元,线程是调度的最小单元呢。
一个进程通常会含有多个线程,线程间可以共享该进程的内存资源等(线程也会可以自己私有的内存),从这里可以看出,一个个进程间内存是割裂开的,所以在操作系统中需要通信的是进程,比如需要知道其他进程“活干的咋样了”,而线程间由于可以共享进程的资源,所以需要有同步手段,防止发生读写冲突。
1 进程间通信机制(inter-process communication)
(详细可参考:https://zhuanlan.zhihu.com/p/2266442934)
匿名管道(PIPE)
1.它是半双工的,即数据只能一个方向流动,具有固定的读端和写端;
2.它只能在具有亲缘关系的进程间通信(父子进程或兄弟进程)
命名管道(FIFO)
可以在无关进程之间交换数据
消息队列
是消息的链表,存在于内核之中,一个消息队列有一个标识符(即队列ID)来标识;
消息队列是面向记录的,其中消息具有特定的格式及优先级;
独立于发送和接收进程,进程被销毁时,消息队列及内容不会被删除;
可以实现消息的随机查询,不一定以先进先出的顺序读取,也可以按消息类型读取
信号
相当于Linux软中断,每个信号有一个名字和编号,名字以“SIG”开头,比如SIGIO,SIGCHLD等;
信号从1开始编号,不存在0号信号
共享内存
允许多个进程访问的同一个内存空间;
最快的一种IPC,因为进程直接堆内存进行存取;
多个进程可以同时操作,所以需要进行同步;
信号量+共享内存一般配合使用,信号量用于同步共享内存的访问
信号量
一个计数器,基于操作系统PV操作
套接字Socket
专门用于进程间网络通信的一种方式
2 内核同步
临界区
一个访问共用资源(如共用设备或共用存储器)的程序片段,该程序片段又无法同时被多个线程访问。
原子操作
不会被“线程调度”机制打断的操作,这种操作一旦开始必定运行结束,中间不会有Context Switch;
由编译器保障,一个线程对数据的操作不会被其他线程打断;
Linux内核提供的 automic_t 类型变量;
2类操作:原子整数操作和原子位操作
自旋锁
锁住一个临界区,进入临界区需要先获取自旋锁,离开临界区需要释放掉;
当一个进程/线程已经获取自旋锁,但还没有释放,则其他想要获取自旋锁的进程/线程需要等待(循环尝试获取该自旋锁,需要一直占用CPU,造成CPU处理时间浪费);
注意:
1.自旋锁不可递归,你试图获取你已经持有的自旋锁时,必须自旋,但你处于忙等待中,永远没有机会释放,自己会锁死自己。
2.获取自旋锁之前,要禁用本地中断。(因为中断处理程序可能也需要该锁);
读写锁
适用于读锁多于写锁情况;
写独占,读共享,写锁优先级高;
1.一个线程拥有写锁,其他线程不能读、写方式持有该锁;
2.一个线程拥有读锁,其他线程可以读方式持有该锁;
3.一个线程拥有读锁,其他线程既有读锁也有写锁,等该线程读完后,写锁优先抢到该锁
顺序锁
简称seq锁,读时候不加锁,写时候加锁;
该锁主要依靠一个序列计数器,读取数据前和读取数据后,都会读取该序列号,看是否相等,如果相等,说明读操作过程中没有写操作发生。
信号量
Semaphore,也是一种锁,当线程获取不到信号量时,不会循环试图获取该锁,而是进入睡眠。等有信号量释放时,线程被唤醒执行;
使用信号量时,线程会进入睡眠,不占用CPU时间,所以信号量适用于等待时间较长的临界区;分为计数信号量和二值信号量;
消耗CPU时间的地方在于使线程睡眠和唤醒线程;
如果(使线程睡眠+唤醒线程)CPU时间>线程自旋等待的CPU时间,可以考虑使用自旋锁
互斥体
可以睡眠的锁,相当于二值信号量,只是API更简单
RCU(Read-copy-update)
只适用于读多写少的情况;
读者不需要获得任何锁就可访问RCU保护的临界区;
写者访问临界区时,先拷贝一份副本,对副本进行写操作;
RCU机制在适当时机调用一个回调函数把指向原来临界区的指针重新指向被修改的临界区,锁机制中的垃圾收集器负责调用回调函数(适当时机:所有引用该共享临界区的CPU都退出对临界区的操作)