进程间通信(IPC:InterProcess Communication)
1 管道:
半双工管道:即数据只能在一个方向上流动(历史上管道是半双工的,现在的系统提供全双工管道,但是为了最佳的移植性,建议使用半双工管道)
管道只能在具有公共祖先的进程间使用:通常:一个管道由一个进程调用pipe创建,然后该进程调用fork,这样父子进程就可以应用该管道。
int fd[2] ;
读端:fd[0]
写端:fd[1]
创建管道:pipe(fd);
父进程:关闭读端close(fd[0]); 写入数据到写端:wtrte(fd[1], "hello world\n", 12);
子进程:关闭写端close(fd[1]); 读出数据从读端:read(fd[0], str, 12);
2 命名管道 FIFO
mkfifo创建FIFO文件,FIFO文件真实存在系统中,通过FIFO文件,不具有亲缘关系的进程可以实现通信。
3 消息队列
对消息队列有写权限的进程可以向中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息(创建的时候设置权限)。消息队列是随内核持续的,只有在内核重起或者显示删除一个消息队列时,该消息队列才会真正被删除。因此系统中记录消息队列的数据结构(struct ipc_ids msg_ids)位于内核中,系统中的所有消息队列都可以在结构msg_ids中找到访问入口。
这种消息的发送方式是:发送方不必等待接收方检查它所收到的消息就可以继续工作下去,而接收方如果没有收到消息也不需等待。
4 信号量
他是一个计数器,用于多进程对共享资源的访问,同时也用于进程同步。
一般说来,信号量的初值可以是任意正值,该值说明有多少个共享资源单位可以共享,为了获取共享资源,进程需要执行以下操作:
1 测试控制该资源的信号量
2 若此信号量为正,那么进程可以使用该资源。进程将信号量值减1,表示他使用了一个资源单位
3 若此信号量值为0,则进程进入休眠状态,直到信号量大于0,进程被唤醒,返回第1步
当进程不再使用由一个信号量控制的共享资源时,该信号量值加1,如果有进程正在休眠等待此信号量,唤醒他们
5 共享内存
多个进程对同一块内存空间进行存储的区域。是一种最快的IPC。信号>量通常被用来实现共享存储的同步。
一个终端的进程创建一块共享存储区域,往区域内写入hello world,然后退出(不能关>闭掉共享存储区); 另一个终端的进程链接到共享存储区,并且从共享存储区内读入hello world
线程间通信:所有进程间通信的方式
自旋锁实际上是忙等锁,当锁不可用时,CPU一直循环执行“测试并设置(test-and-set)”该锁直到可用而取得该锁,CPU在等待自旋锁时不做任何有用的工作,仅仅是等待。这说明只有在占用锁的时间极短的情况下,使用自旋锁是合理的,因为此时某个CPU可能正在等待这个自旋锁。当临界区较为短小时,如只是为了保证对数据修改的原子性,常用自旋锁;当临界区很大,或有共享设备的时候,需要较长时间占用锁,使用自旋锁就不是一个很好的选择,会降低CPU的效率。
自旋锁也存在死锁(deadlock)问题。引发这个问题最常见的情况是要求递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的CPU想第二次获得这个自旋锁,则该CPU将死锁。
只有当进程占用资源很长时间时,用信号量才是不错的选择。
当所要保护的临界区比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。但是CPU得不到自旋锁会在那里空转直到锁成功为止,所以要求锁不能在临界区里停留很长时间,否则会降低系统的效率
自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁,“自旋”就是“在原地打转”。而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,但CPU不会停,会接着运行其他的执行路径。 但是,无论是信号量,还是自旋锁,在任何时刻,最多只能有一个保持者,即在任何时刻最多只能有一个执行单元获得锁。