IPC机制 通信

通信概念说明

进程间通信目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程

  • 资源共享:多个进程之间共享同样的资源。

  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

进程间通信的本质

  1. 让不同的进程之间,看到同一份“资源”——即能都访问特定形式的内存空间。

  2. 由于进程之间存在独立性,因此用于通信的“资源”不能由进程创建,而有操作系统提供。

  3. 访问这段“资源”,本质上就是访问“操作系统”。因此我们需要使用系统调用接口,通信模块(IPC)。

  4. 共享的资源类似于父子进程fork()之后不发生“写实拷贝”的情况

总结:管道的本质就是内核在内存中开辟了一个缓冲区,这个缓冲区与管道文件相关联,对管道文件的操作,被内核转换成对这块缓冲区的操作。

通信手段

匿名管道

1
$ command1 | command2
  • 其中|被称为管道符。

管道

管道通信

  • 从一个进程连接到另一个进程的一个数据流称为一个“管道”(单向通信)。也就是半双工通信,如果想实现相互通信(全双工通信),我们需要创建两个管道才行。

  • 一般程序fork之后,子进程会继承父进程的文件描述符表,也就继承了其读写权限。 管道则不同

管道如何控制读写的抽象图

  • 通过文件描述符控制达到单向通信——管道的原理。

通道的文件描述符

管道的特征

  1. 具有血缘关系的进程才能进行进程间通信。

  2. 管道只能单向通信。

  3. 父子进程是会进行协同,同步和互斥的——主要是为了保护管道文件的数据安全。

  4. 管道只能传输无格式的字节流。

  5. 管道资源是有固定大小的。(PIPE_BUF)

管道的情况

  • 读写端正常,管道如果为空,那么读端就要阻塞。

  • 读写端正常,管道如果被写满,写端就要被阻塞。

  • 如果读端正常读,写端关闭,读端就会读到0,表明读到了文件(pipe)结尾,不会被阻塞。

  • 如果写端正常写,读端关闭。那么操作系统就会kill正在写入的进程。

共享内存

共享内存

  • 共享内存如何创建的?

    1. 需要使用共享内存的进程,通过系统调用,从物理内存中申请内存

    2. 将这申请的这部分空间的首地址通过页表映射到**进程地址空间的共享区**中。

    3. 共享内存机制来说,仅在建立共享内存区域时需要系统调用,一旦建立共享内存,所有的访问都可作为常规内存访问,无需借助内核。

    4. 因此,数据就不需要在进程之间来回拷贝,所以这是最快的一种进程通信方式。

    5. 大小一般是4096(假设的大小是4kb)的整数倍。

共享内存的特点

  1. 共享内存申请完毕之后,当作一般的内存使用即可,不需要使用系统调用,共享内存内的数据,由用户自己维护。

  2. 共享内存是所有进程间通信中,速度最快的。

什么是共享内存标识符

  • shmid——共享内存标识符。

  • 可以通过int shmget(key_t key, size_t size, int shmflg)返回值得到。

形参 含义
int shmflg 权限

如何让不同进程看到同一个共享内存?

  • 不同的共享内存之间有不同的key值——key值的唯一性,我们可以使用ftok()去创建一个key

  • 只有有相同的pathnameproj_idftok()就可以生成相同的key

  • 第一个进程创建共享内存之后,会产生key,第二个之后的进程,只需要拿着同一个key,就可以和第一个进程看到同一个共享内存了。

  • 对于一个已经创建好的共享内存,它的key就存在于共享内存的描述对象中。

ipcs -m可以查看共享内存信息。

keyshmid区别

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信息

  1. 当我们通过下标找到ipc_perm* array[]对应的地址时,这个地址可以理解为是IPC对象的首地址(他们都具有struct ipc_perm sem_perm结构体)。

  2. 操作系统区分指针指向的对象的类型

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 序列号。

引用资料

Linux内核六大进程通信机制原理


IPC机制 通信
https://weihehe.top/2024/07/30/通信方法/
作者
weihehe
发布于
2024年7月30日
许可协议