• 进程间通信
  • 管道
  • 共享内存
  • 消息队列
  • 信号量

进程间通信

https://blog.csdn.net/qq_35423154/article/details/105294963
在之前的一篇博客中讲过,因为每个进程都通过自己的页表构建物理地址和虚拟地址的映射关系,使每个进程都拥有自己的虚拟地址空间,并通过这个独立的虚拟地址空间来对物理内存进行操作,所有的进程都只能访问自己的虚拟地址,而不能直接访问物理内存,所以多个进程无法访问同一块区域,无法实现通信。

因为这种独立性,进程之间无法直接进行通信,操作系统就为了解决这种问题,提出了多重适用于不同情境下的通信方式。

数据传输:管道、消息队列
数据共享:共享内存
进程控制:信号量


管道

原理:管道的本质其实就是内核中的一块缓冲区,多个进程通过访问同一个缓冲区就可以实现进程间的通信

管道分为两种:匿名管道、命名管道

匿名管道

匿名 管道是内核中的一块缓冲区,因为没有具体的文件描述符,所以匿名管道只能适用于具有亲缘关系的进程间通信父进程在创建管道的时候操作系统会返回管道的文件描述符,然后生成子进程时子进程会通过拷贝父进程的pcb来获取到这个管道的描述符,所以他们可以通过这个文件描述符来访问同一个管道,来实现进程间的通信。而不具备亲缘关系的进程则无法通过这个文件描述符来访问同一个管道。

接口

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回-1

一开始父进程创建管道

父进程fork创建子进程

关闭多余描述符

就这样,子进程通过写入端fd[1]向管道写入数据,父进程通过读入端fd[0]从管道读取数据,来实现进程间的通信。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>int main()
{int pipefd[2];pipe(pipefd);int pid = fork();if(pid == 0){close(pipefd[0]);while(1){write(pipefd[1], "hello world", 12);sleep(3);}close(pipefd[1]);exit(0);}else if(pid > 0){close(pipefd[1]);char buff[1024];while(1){read(pipefd[0], buff, 12);printf("%s\n", buff);}close(pipefd[0]);}return 0;

这是一个简单的管道


运行后每三秒写端会写入数据,然后读端立即读入数据。

因为父子进程究竟是谁先执行这一点我们无法知道,假设如果子进程还没写入,父进程却已经开始读了,这时候应该是会读不到东西的,但是这种情况并没有发生,这里就牵扯到了管道的读写特性。

管道的读写特性:
  1. 如果管道中没有数据,则调用read读取数据会阻塞
  2. 如果管道中数据满了,则调用write写入数据会阻塞
  3. 如果管道的所有读端pipefd[0]被关闭,则继续调用write会因为无法读出而产生异常导致进程退出
  4. 如果管道的所有写端pipefd[1]被关闭,则继续调用read,因为无法再次写入,read读完管道中的所有数据后不再阻塞,返回0退出

命名管道

匿名管道的限制就是只能在亲缘关系的进程间通信,如果我们想为不相关的进程交换数据,就可以使用命名管道。

原理:命名管道也是内核中的一块缓冲区,但是它具有标识符。这个标识符是一个可见于文件系统的管道文件,能够被其他进程找到并打开管道文件来获取管道的操作句柄,多个进程可以通过打开这个管道文件来访问同一块缓冲区来实现通信

接口:

int mkfifo(const char *filename,mode_t mode);
mode:权限掩码
filename:管道的标识符,通过这个标识符来访问管道
返回值:若成功则返回0,否则返回-1

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>int main()
{mkfifo("test", 0664);int pid = fork();if(pid > 0){int read_fd = open("test", O_RDONLY);char buff[1024];read(read_fd, buff, 12);printf("buff:%s\n", buff);close(read_fd);}else if(pid == 0){int write_fd = open("test", O_WRONLY);write(write_fd, "hello world", 12);close(write_fd);exit(0);}return 0;
}


试验一下

open打开命名管道的特性:

1. 若文件以只读打开,则会阻塞,直到文件被以写的方式打开
2. 若文件以只写打开,则会阻塞,直到文件被以读的方式打开

管道的特性:

1. 管道是半双工通信(可以选择方向的单向传输),这个可以从上面的示意图看出来
2. 管道的读写特性(无论命名匿名都一样)
若管道中没有数据则读操作堵塞,如果管道中数据满了写操作堵塞。
如果管道中所有读端关闭则写端触发异常,如果所有写端关闭则读端读完数据后不堵塞返回0
3. 管道声明周期随进程,打开管道的所有进程退出后管道就会被释放。
4. 管道提供字节流传输服务
5. 命名管道额外有一个打开特性,只读打开会阻塞直到被以写打开,只写打开会阻塞直到被以读打开
6. 管道自带同步和互斥


共享内存

共享内存即在物理内存上开辟一块空间,然后多个进程通过页表将这同一个物理内存映射到自己的虚拟地址空间中,通过自己的虚拟地址空间来访问这块物理内存,达到了数据共享的目的。
如图:


也正是因为这种特性,使得共享内存成为了最快的进程间通信的方式因为它直接通过虚拟地址来访问物理内存,比前面的管道和后面的消息队列少了内核态和用户态的几次数据拷贝和交互。

共享内存的使用流程:

1. 创建共享内存
2. 将共享内存映射到虚拟地址空间
3. 进行操作
4. 解除映射关系
5. 释放共享内存

接口:

  1. 创建共享内存
    int shmget(key_t key, size_t size, int shmflg)

key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回共享内存标识符,失败返回-1
头文件:
#include <sys/ipc.h>
#include <sys/shm.h>

  1. 将共享内存映射到虚拟地址空间
    void *shmat(int shmid, const void *shmaddr, int shmflg)

shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:权限标志
返回值:成功返回指向共享内存映射在虚拟地址空间的指针(即首地址),失败返回-1
头文件:
#include <sys/types.h>
#include <sys/shm.h>

  1. 共享内存管理
    int shmctl(int shmid, int cmd, struct shmid_ds *buf)

shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0,失败返回-1
头文件:
#include <sys/types.h>
#include <sys/shm.h>

  1. 解除映射关系
    int shmdt(const void *shmaddr)

shmaddr: 由shmat所返回的指针
返回值:成功返回0,失败返回-1
头文件:
#include <sys/types.h>
#include <sys/shm.h>

特性:

1. 共享内存是最快的进程间通信方式
2. 生命周期随内核
3. 不自带同步与互斥,但可以借助信号量来实现同步与互斥


消息队列

消息队列是内核中的一个优先级队列,多个进程通过访问同一个队列,进行添加节点或者获取节点来实现通信。

接口:

  1. 创建消息队列
    int msgget(key_t key, int msgflg);

key:消息队列对象的关键字
msgflg:消息队列的建立标志和存取权限
返回值:成功执行时,返回消息队列标识值。失败返回-1
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

  1. 进程可以向队列中添加/获取节点
    添加节点:
    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    获取节点:
    ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
    int msgflg);

msqid:消息队列对象的标识符
msgp:消息缓冲区指针
msgsz:消息数据的长度
msgtyp:决定从队列中返回哪条消息
msgflg:消息队列状态
返回值:成功执行时,返回0。失败返回-1
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

  1. 删除消息队列
    int msgctl(int msqid, int cmd, struct msqid_ds *buf)

msqid:消息队列对象的标识符
cmd:函数要对消息队列进行的操作
buf:取出系统保存的消息队列的msqid_ds 数据,并将其存入参数buf 指向的msqid_ds 结构中
返回值:成功执行时,返回0。失败返回-1
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

特性:

1. 自带同步与互斥
2. 生命周期随内核


信号量

信号量其实是内核中的一个计数器和阻塞队列,通过信号量来对临界资源的访问进行控制,来实现进程间的同步与互斥

例如有一个能容纳n人的餐厅,则用一个计数器表示n,如果有人进入则n - 1,如果有人出来则n + 1,只有n > 0时才能进入,如果n <= 0时,则说明没有位置,需要将进程挂起并放入阻塞队列中,直到有人出来使资源释放时,才能将后续进程从阻塞队列中唤醒获取资源

同步:
通过条件判断实现临界资源访问的合理性

互斥:
通过同一时间的唯一访问来实现临界资源访问的安全性

POSIX信号量
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步

#include <semaphore.h>//初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
/*
参数:pshared:0表示线程间共享,非零表示进程间共享value:信号量初值
*///销毁信号量
int sem_destroy(sem_t *sem);//等待信号量
int sem_wait(sem_t *sem);
//功能:等待信号量,会将信号量的值减1//发布信号量
int sem_post(sem_t *sem);
//功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。

Linux 进程间通信:管道、共享内存、消息队列、信号量相关推荐

  1. 【Linux学习】进程间通信——system V(共享内存 | 消息队列 | 信号量)

  2. 【Linux】Linux进程间通信——共享内存/消息队列/守护进程

    文章目录 进程间通信--共享内存/守护进程 一, 共享内存 1. 共享内存概念 2. 共享内存使用 1. 共享内存使用步骤 2. 共享内存操作函数 3. 共享内存常用操作命令 4. 共享内存使用示例: ...

  3. Linux进程间通信:共享内存与管道

    references: [1] IPC through shared memory [2] Inter Process Communication (IPC) [3] https://www.geek ...

  4. [转]Linux 进程间通信:共享内存

    (上) 级别: 初级 郑彦兴 (mlinux@163.com), 国防科大攻读博士学位 2003 年 5 月 01 日 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式.两个不同进程A.B ...

  5. Linux进程间通信(四) - 共享内存

    共享内存的优势 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝.对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只 ...

  6. Linux进程间通信:共享内存mmap、xsi和posix

    前言 本文主要说明在Linux环境上如何使用共享内存.阅读本文可以帮你解决以下问题: 什么是共享内存和为什么要有共享内存? 如何使用mmap进行共享内存? 如何使用XSI共享内存? 如何使用POSIX ...

  7. Linux——进程间通信(共享内存【mmap实现+系统V】)

    共享内存 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式,两个不同的进程A.B共享内存的意思就是:同一块物理内存被映射到进程A.B各自的进程地址空间,进程A可以同时看到进程B对共享内存中 ...

  8. Linux进程间通信——使用共享内存

    下面将讲解进程间通信的另一种方式,使用共享内存. 一.什么是共享内存 顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存.共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式 ...

  9. Linux进程间通信(七)---共享内存之shmget()、shmat()、shmdt()及其基础实验

    /********************************************************************* * 2019年6月26日声明:本博客资源下载积分最开始为免 ...

  10. Linux进程间通信:共享内存(shm)

    目录 ★ key值说明 ★ shmget函数 ★ shmat函数 ★ shmdt函数 ★ shmctl函数 ★ 操作说明 ★ IPC相关指令 简介:共享内存指 (shared memory)在多处理器 ...

最新文章

  1. 【微软出品】AI-神经网络基本原理简明教程
  2. 结构化综合布线系统中的干线子系统是指(33)。【答案】D
  3. JavaScript 浮点数陷阱及解法
  4. 哈,又一款超级简单的队列(MQ)实现方案来了~
  5. Docker(五)如何构建Dockerfile
  6. spring boot系列 1: 第一个spring boot: Hello prj
  7. Struts项目中,检测用户名是否被占用/查询账户名称是否被占用/查询账户名称是否已被注册/检查用户名是否被注册
  8. 记录每个用户的操作记录(命令)
  9. Unable to open /dev/sda的原因之一
  10. 平板电脑有什么用_除了盖泡面,平板电脑没什么用了
  11. jquery中html()、text()、val()的区别与使用
  12. 实时查询(otoci)
  13. tableau的下载安装及简单使用
  14. 软件任我行 10步菜鸟快易通上手教程
  15. 世纪联华开了家新零售门店,网易严选也要入驻
  16. 360浏览器如何改html5,360安全浏览器如何设置为默认浏览器
  17. 各个编程语言都有哪些「黑点」?
  18. 玩转肺癌目标检测数据集Lung-PET-CT-Dx ——④转换成PASCAL VOC格式数据集
  19. Scala中Seq转Map的方法(:_*)
  20. 高德地图定位失败_高德开放平台

热门文章

  1. 由控制台输入年龄-不同类型不能直接比较
  2. FastDFS在项目中的应用
  3. 构造函数和实例对象之间的关系 构造函数创建对象带来的问题 原型
  4. 启动和退出mysql的三种方法_Oracle数据库几种启动和关闭方式
  5. 找不到文件、主类名和文件名不一致、缺少分号的解决方法
  6. 在阿里云的ubuntu上部署个人服务
  7. 20172304 《程序设计与数据结构》第四周学习总结
  8. php编译安装与配置
  9. Expected an Objective directive after @ 的解决办法
  10. mysql三学习sql声明学习