LV05-05-进程通信-01-进程通信概述
本文主要是进程通信——进程通信概述的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
| Windows | windows11 |
| Ubuntu | Ubuntu16.04的64位版本 |
| VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
| SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
| 开发板 | 正点原子 i.MX6ULL Linux阿尔法开发板 |
| uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
| linux内核 | linux-4.15(NXP官方提供) |
| STM32开发板 | 正点原子战舰V3(STM32F103ZET6) |
点击查看本文参考资料
| 参考方向 | 参考原文 |
| --- | --- |
点击查看相关文件下载
| --- | --- |
一、进程通信的概念
进程间通信(interprocess communication,简称IPC)指两个进程之间的通信。系统中的每一个进程都有各自的地址空间,并且相互独立、隔离,每个进程都处于自己的地址空间中。所以同一个进程的不同模块之间进行通信都是很简单的,例如不同的函数就可以通过全局变量实现通信。
两个不同的进程之间要进行通信通常是比较困难的,因为这两个进程处于不同的地址空间中;通常情况下,大部分的程序是不要考虑进程间通信的,因为我们所接触绝大部分程序都是单进程程序(可以有多个线程),对于一些复杂、大型的应用程序,则会根据实际需要将其设计成多进程程序,例如GUI、服务区应用程序等。
二、通信机制简介
虽然不同的进程的用户地址空间都是独立的,一般是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信就需要通过内核才能完成。
Linux内核提供了多种IPC机制,基本是从UNIX系统继承而来,而对UNIX发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。前者对UNIX早期的进程间通信手段进行了系统的改进和扩充,形成了System V IPC,通信进程局限在单个计算机内;后者则跳过了该限制,形成了基于套接字(Socket,也就是网络)的进程间通信机制。Linux则把两者继承了下来:
所以在进程中的通信方式如下:
| UNIX IPC | 管道、FIFO、信号 |
| System V IPC | 信号量、消息队列、共享内存 |
| POSIX IPC | 信号量、消息队列、共享内存 |
| Socket IPC | 基于Socket进程间通信 |
将会在后边的几篇笔记中详细介绍常用的几种通信方式的实现。
三、管道简介
管道包括匿名管道(pipe)以及命名管道(FIFO)。匿名管道可用于具有亲属关系进程间的通信;命名管道克服了匿名管道没有名称的限制,从而允许无亲属关系进程间的通信。详细的使用会在后边的笔记中说明。
四、信号简介
信号是一种通知性的通信方式,当某种事件发生时,用于向接收进程发出通知,根据需要,接收进程可以对通知做出某种反应,也可以进行忽略或者执行默认操作。详细的使用会在后边的笔记中说明。
五、 System V IPC 简介
System V IPC包括了三种不同的进程通信机制:信号量、共享内存和消息队列。这三种会在后边的笔记中详细说明。
这里先来了解一下System V, 曾经也被称为 AT&T System V,是UNIX操作系统众多版本中的一支。它最初由 AT&T 开发,在1983年第一次发布。一共发行了4个System V的主要版本。其中System V Release 4,或者称为SVR4,是最成功的版本,成为一些UNIX共同特性的源头,例如 SysV 初始化脚本(/etc/init.d),用来控制系统启动和关闭,System V Interface Definition (SVID) 是一个System V 如何工作的标准定义。
【注意】
(1)每个IPC对象有唯一的ID 用Key关联。
(2)IPC对象创建后一直存在,直到被显式地删除。
1. 相关命令
1.1 ipcs
1.1.1 命令说明
查看信号量、共享内存和消息队列的信息。
1 | ipcs [options] |
| options | 说明 |
| -a | 显示全部可显示IPC对象的信息 |
| -q | 显示活动的消息队列信息 |
| -m | 显示活动的共享内存信息 |
| -s | 显示活动的信号量信息 |
| -l | 显示所有IPC对象的大小限制(小写的L) |
1.1.2 使用实例
我们使用ipcs -a命令,将会在终端显示如下信息:
1 | --------- 消息队列 ----------- |
1.2 ipcmk
1.2.1 命令说明
创建共享内存段、消息队列、信号量。
1 | ipcmk [options] |
| options | 说明 |
| -M | 创建大小为size字节的共享内存段。参数size之后可以是乘法后缀:KiB(=1024),MiB(=1024*1024),同理GiB等。(iB是可选的,例如,K与KiB同义)。或也可以后缀KB(=1000),MB(=1000*1000)等。 |
| -Q | 创建一个消息队列。 |
| -S | 创建一个包含多个元素的信号量数组。 |
1.2.2 使用实例
我们使用以下命令创建三种IPC对象:
1 | ipcmk -M 30 |
然后我们执行ipcs -a命令查看当前的IPC信息:
1 | --------- 消息队列 ----------- |
1.3 ipcrm
1.3.1 命令说明
移除一个消息对象,或共享内存段,或信号量,同时会将与ipc对象相关链的数据也一起移除。
1 | ipcrm [options] |
| options | 说明 |
| -M shmkey | 移除用shmkey创建的共享内存段 |
| -m shmid | 移除用shmid标识的共享内存段 |
| -Q msgkey | 移除用msgkey创建的消息队列 |
| -q msqid | 移除用msqid标识的消息队列 |
| -S semkey | 移除用semkey创建的信号 |
| -s semid | 移除用semid标识的信号 |
1.3.2 使用实例
暂无。
2. key 和 ID
2.1 一个实例
使用ipcs -M size命令创建了三块共享内存区域,显示的信息如下所示:
1 | --------- 消息队列 ----------- |
这里边包含了key值(键)、ID值(msqid、shmid、semid)、拥有者、权限等关键信息,实现两个进程之间通信的关键就在于key值和ID值。
2.2 ID 值
Linux系统为每个IPC机制都分配了唯一的ID,这个ID被称为IPC对象的标识符,所有针对IPC机制的操作都使用该ID值。ID值是在创建IPC对象的时候由系统分配的。
2.3 key 值
通信双方都需要通过某个方法来获取ID值。那怎么让两个进程获取到同一个ID值呢?IPC在实现时约定使用key值做为参数创建,如果在创建时使用相同的key值将得到同一个IPC对象的ID(即一方创建ID,另一方仅是获取ID),这样就保证了双方可以获取用于传递数据的同一个IPC机制ID值。这个key值被称为System V IPC外部标识符,又称为IPC键,它是一个32位的整型数据。
其实说到底,key的存在只是为了让两个进程可以对同一个IPC对象进行操作,key值有两种途径获取:
- 途径一
key值可以人为的指定,也就是key为IPC_PRIVATE,但这样可能会造成同一个IPC机制有相同的key值。使用IPC_PRIVATE创建的IPC对象的key值属性为0,和IPC对象的ID就没有了对应关系。这样毫无关系的进程,就无法通过key值来得到IPC对象的ID(因为这种方式创建的IPC对象的key值都是0)。因此,这种方式产生的IPC对象,和无名管道类似,不能用于毫无关系的进程间通信。但也不是一点用处都没有,仍然可以用于有亲缘关系的进程间通信。
- 途径二
ftok()可以创建key,该函数是Linux系统提供的系统调用。后边会有具体的说明。
3. key 和 ID 的创建
3.1 key 的创建
3.1.1 ftok()
在linux下可以使用man 3 ftok 命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于创建一个IPC对象的key值。
【函数参数】
pathname:char *类型,是指定的文件名(可以包含路径),这个文件必须是存在的而且可以访问的。proj_id:int类型,该参数是子序号,它是一个整数,但是只有8 bits被使用,即实际范围是0~255。
【返回值】key_t类型,当函数执行成功,则会返回key_t键值,失败返回-1。在一般的UNIX中,通常是将文件的索引节点(可以使用ls -i查看文件索引节点)取出,然后在前面加上子序号就得到key_t的值。
【使用格式】一般情况下基本使用格式如下:
1 | /* 需要包含的头文件 */ |
【注意事项】
(1)若pathname指向的文件不存在,则会报如下错误:
1 | No such file or directory |
(2)在使用ftok()函数时,里面有两个参数,即pathname和proj_id,pathname为指定的文件名,而proj_id为子序列号,这个函数的返回值就是key,它与指定的文件的索引节点号和子序列号有关,如果文件的路径,名称和子序列号不变,那么得到的key值是否也是不变的呢?
如果pathname指向的文件或者目录被删除而且又重新创建,那么文件系统会赋予这个同名文件新的节点信息,所以键值key不一定相同。要确保key值不变,可以确保ftok()的文件不被删除,或者不用ftok(),指定一个固定的key值。
3.1.2使用实例
点击查看实例
1 | /* 头文件 */ |
在终端执行以下命令编译程序:
1 | gcc test.c -Wall # 生成可执行文件 a.out |
然后,终端会有以下信息显示:
1 | key=643d0b38, sizeof(key)=4 |
3.2 ID 的创建
ID的创建都是在创建IPC对象的时候分配的,后边的笔记中会有IPC对象的创建及操作,那里会有说明的。
4. 相关函数汇总
| IPC对象名称 | 消息队列 | 信号量 | 共享内存 |
| 头文件 | <sys/msg.h> | <sys/sem.h> | <sys/shm.h> |
| 关联数据结构 | msqid_ds | semid_ds | shmid_ds |
| 创建或打开IPC | msgget() | semget() | shmget() |
| IPC控制 | msgctl() | semctl() | shmctl() |
| IPC操作 | msgsnd()、msgrcv() | semop() | shmat()、shmdt() |
六、POSIX IPC 简介
1. POSIX
POSIX(Portable Operating System Interface for Computing Systems)是由IEEE 和ISO/IEC 开发的一簇标准。该标准是基于现有的UNIX 实践和经验,描述了操作系统的调用服务接口,用于保证编制的应用程序可以在源代码一级上在多种操作系统上移植运行。
它是在1980 年早期一个UNIX 用户组(usr/group)的早期工作的基础上取得的。该UNIX 用户组原来试图将AT&T 的系统V 和Berkeley CSRG的BSD 系统的调用接口之间的区别重新调和集成,从而于1984 年产生了/usr/group 标准。
1985 年,IEEE操作系统技术委员会标准小组委员会(TCOS-SS)开始在ANSI 的支持下责成(指定)IEEE 标准委员会制定有关程序源代码可移植性操作系统服务接口正式标准。
到了1986 年4 月,IEEE 就制定出了试用标准。第一个正式标准是在1988 年9 月份批准的(IEEE 1003.1-1988),也就经常提到的POSIX.1 标准。
2. POSIX IPC
POSIX IPC也包含了消息队列、信号量和共享内存三种,但是POSIX的语法相对于System V会更加的简单些,使用名字代替键来标识IPC对象。
3. 相关函数汇总
| IPC对象 | 消息队列 | 信号量 | 共享内存 |
| 头文件 | <mqueue.h> | <semaphore.h> | <sys/mman.h> |
| 对象句柄 | mqd_t | sem_t * | int(文件描述符) |
| 创建或打开IPC | mq_open() | sem_open() | shm_open()、mmap() |
| 关闭IPC | mq_close() | sem_close() | munmap() |
| 断开与IPC的链接 | mq_unlink() | sem__unlink() | shm__unlink() |
| IPC操作 | mq_send()、mq_receive() | sem_post()、sem_wait()、sem_getvalue() | 在共享区域中的位置上操作 |
| 其他操作 | mq_setattr()、mq_getattr()、mq_notify() | sem_init()、sem_destroy() | --- |
七、 Socket简介
socket我们一般翻译为套接字,它主要是针对于网络编程时,实现进程的通信,后边网络编程部分的笔记会有详细的说明。