IPC机制 通信
通信概念说明
进程间通信目的
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
进程间通信的本质
让不同的进程之间,看到同一份“资源”——即能都访问特定形式的内存空间。
由于进程之间存在独立性,因此用于通信的“资源”不能由进程创建,而有操作系统提供。
访问这段“资源”,本质上就是访问“操作系统”。因此我们需要使用系统调用接口,通信模块(IPC)。
共享的资源类似于父子进程fork()之后不发生“写实拷贝”的情况。
总结:管道的本质就是内核在内存中开辟了一个缓冲区,这个缓冲区与管道文件相关联,对管道文件的操作,被内核转换成对这块缓冲区的操作。
通信手段
匿名管道
1 |
|
- 其中
|
被称为管道符。
管道
从一个进程连接到另一个进程的一个数据流称为一个“管道”(单向通信)。也就是半双工通信,如果想实现相互通信(全双工通信),我们需要创建两个管道才行。
但一般程序fork之后,子进程会继承父进程的文件描述符表,也就继承了其读写权限。 管道则不同
- 通过文件描述符控制达到单向通信——管道的原理。
管道的特征
具有血缘关系的进程才能进行进程间通信。
管道只能单向通信。
父子进程是会进行协同,同步和互斥的——主要是为了保护管道文件的数据安全。
管道只能传输无格式的字节流。
管道资源
是有固定大小的。(PIPE_BUF)
管道的情况
读写端正常,管道如果为空,那么读端就要阻塞。
读写端正常,管道如果被写满,写端就要被阻塞。
如果读端正常读,写端关闭,读端就会读到
0
,表明读到了文件(pipe)结尾,不会被阻塞。如果写端正常写,读端关闭。那么操作系统就会
kill
正在写入的进程。
共享内存
共享内存如何创建的?
需要使用共享内存的进程,通过系统调用,从物理内存中申请内存。
将这申请的这部分空间的首地址通过页表映射到**进程地址空间的
共享区
**中。共享内存机制来说,仅在建立共享内存区域时需要系统调用,一旦建立共享内存,所有的访问都可作为常规内存访问,无需借助内核。
因此,数据就不需要在进程之间来回拷贝,所以这是最快的一种进程通信方式。
大小一般是
4096
(假设页
的大小是4kb)的整数倍。
共享内存的特点
共享内存申请完毕之后,当作一般的内存使用即可,不需要使用系统调用,共享内存内的数据,由用户自己维护。
共享内存是所有进程间通信中,速度最快的。
什么是共享内存标识符
shmid
——共享内存标识符。可以通过
int shmget(key_t key, size_t size, int shmflg)
返回值得到。
形参 | 含义 |
---|---|
int shmflg | 权限 |
如何让不同进程看到同一个共享内存?
不同的共享内存之间有不同的
key
值——key
值的唯一性,我们可以使用ftok()
去创建一个key
。只有有相同的
pathname
和proj_id
,ftok()
就可以生成相同的key
。第一个进程创建共享内存之后,会产生
key
,第二个之后的进程,只需要拿着同一个key
,就可以和第一个进程看到同一个共享内存了。对于一个已经创建好的共享内存,它的
key
就存在于共享内存的描述对象中。
ipcs -m
可以查看共享内存信息。
key
和shmid
区别
Key:用来表示操作系统内唯一性,即内核层使用。
Shmid:共享内存标识符只在你的进程内,用来表示资源的唯一性,即在用户层使用。
共享内存的生命周期
共享内存的生命周期是随内核的,除非用户主动关闭(释放)或者内核重启。
进程与共享内存之间的连接
void *shmat(int shmid, const void *shmaddr, int shmflg);
其中
shmaddr
代表的是共享内存在内存中的位置,一般给空(nullptr
),会自动记录到进程的共享区。通信时独立的一个模块,模型内也会记录如缓冲区大小之类的信息
常用共享内存函数
函数 | 原型 | 说明 |
---|---|---|
shmget |
int shmget(key_t key, size_t size, int shmflg); |
创建或获取一个共享内存段。 |
shmat |
void *shmat(int shmid, const void *shmaddr, int shmflg); |
将共享内存段附加到进程的地址空间。 |
shmdt |
int shmdt(const void *shmaddr); |
将共享内存段从进程的地址空间分离。 |
shmctl |
int shmctl(int shmid, int cmd, struct shmid_ds *buf); |
控制共享内存段,例如删除共享内存段。 |
消息队列
- 管道这种进程通信方式虽然使用简单,但是效率比较低,不适合进程间频繁地交换数据,并且管道只能传输无格式的字节流。为此,消息传递机制(Linux 中称消息队列)应用而生。比如,A 进程要给 B 进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返回了,B 进程在需要的时候自行去消息队列中读取数据就可以了。同样的,B 进程要给 A 进程发送消息也是如此。
消息队列有关函数
函数 | 原型 | 说明 |
---|---|---|
msgget |
int msgget(key_t key, int msgflg); |
创建或获取一个消息队列。 |
msgsnd |
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); |
将消息发送到消息队列。 |
msgrcv |
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); |
从消息队列接收消息。 |
msgctl |
int msgctl(int msqid, int cmd, struct msqid_ds *buf); |
控制消息队列,例如删除消息队列。 |
- msqid:消息队列标识符。
共享内存,消息队列,信号量的结构体信息
shmid_ds
结构体
成员 | 类型 | 说明 |
---|---|---|
struct ipc_perm shm_perm |
struct ipc_perm |
权限结构体,包含所有者、组和模式信息。 |
size_t shm_segsz |
size_t |
共享内存段的大小(字节)。 |
time_t shm_atime |
time_t |
最后一次附加操作的时间。 |
time_t shm_dtime |
time_t |
最后一次分离操作的时间。 |
time_t shm_ctime |
time_t |
最后一次改变操作的时间。 |
pid_t shm_cpid |
pid_t |
创建共享内存段的进程 ID。 |
pid_t shm_lpid |
pid_t |
最后一个操作共享内存段的进程 ID。 |
shmatt_t shm_nattch |
shmatt_t |
当前附加到该段的进程数量。 |
msqid_ds
结构体
成员 | 类型 | 说明 |
---|---|---|
struct ipc_perm msg_perm |
struct ipc_perm |
权限结构体,包含所有者、组和模式信息。 |
time_t msg_stime |
time_t |
最后一次消息发送操作的时间。 |
time_t msg_rtime |
time_t |
最后一次消息接收操作的时间。 |
time_t msg_ctime |
time_t |
最后一次改变操作的时间。 |
unsigned long msg_cbytes |
unsigned long |
当前队列中的字节数。 |
msgqnum_t msg_qnum |
msgqnum_t |
当前队列中的消息数。 |
msglen_t msg_qbytes |
msglen_t |
队列中允许的最大字节数。 |
pid_t msg_lspid |
pid_t |
最后一个发送消息的进程 ID。 |
pid_t msg_lrpid |
pid_t |
最后一个接收消息的进程 ID。 |
semid_ds
结构体
成员 | 类型 | 说明 |
---|---|---|
struct ipc_perm sem_perm |
struct ipc_perm |
权限结构体,包含所有者、组和模式信息。 |
time_t sem_otime |
time_t |
最后一次 semop 操作的时间。 |
time_t sem_ctime |
time_t |
最后一次改变操作的时间。 |
unsigned long sem_nsems |
unsigned long |
信号量集中的信号量个数。 |
总结
以上
IPC
对象,他们都具有struct ipc_perm sem_perm
结构体。ipc_perm* array[]
是一个指向ipc_perm
结构体的指针数组。其中ipc_perm
结构体,被嵌入到其他用于描述具体IPC对象的数据结构中,方便操作系统进行管理。
如何通过IPC对象存储的下标找到ipc_perm
信息
当我们通过下标找到
ipc_perm* array[]
对应的地址时,这个地址可以理解为是IPC
对象的首地址(他们都具有struct ipc_perm sem_perm
结构体)。操作系统区分指针指向的对象的类型。
3.根据此时访问的IPC对象,来对地址进行强制类型转换,转化之后就可以访问当前地址对应的IPC对象内的成员了。
ipc_perm
结构体
成员 | 类型 | 说明 |
---|---|---|
key_t key |
key_t |
IPC 结构的键。 |
uid_t uid |
uid_t |
所有者的用户 ID。 |
gid_t gid |
gid_t |
所有者的组 ID。 |
uid_t cuid |
uid_t |
创建者的用户 ID。 |
gid_t cgid |
gid_t |
创建者的组 ID。 |
mode_t mode |
mode_t |
访问权限。 |
unsigned short seq |
unsigned short |
序列号。 |