Multi-process communication between linux (shared memory)

Jiangdong flower master Minming 2022-09-23 08:50:17 阅读数:468

multi-processmultiprocesscommunicationlinux

一、什么是进程间通信

进程间通信是指在不同进程之间传播或交换信息;每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信

二、linux进程通信的目的

Interprocess communication has the following purposes:
1、数据传输,一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M之间;
2、共享数据,多个进程想要操作共享数据,Modification of data by a process,Other processes should see it right away;
3、通知事件,一个进程需要向另一个或一组进程发送消息,Notify them that something has happened;
4、资源共享,多个进程之间共享同样的资源.为了做到这一点,需要内核提供锁和同步机制;
5、进程控制,有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变.

三、进程间通信的几种方式

  • 管道(匿名管道、命名管道)
  • 共享内存
  • 消息队列
  • 信号量

1、管道

本质:在内核中开辟一块缓冲区;If multiple processes get the same pipe(缓冲区)的操作句柄,can access the same buffer,就可以进行通信

读写特性:
若管道中没有数据,则调用read读取数据会阻塞
若管道中数据满了(约64k),则调用write写入数据会阻塞
If all read ends of the pipepipefd[0]被关闭,则继续调用write会产生异常导致进程退出 ----Both parent and child processes callclose(pipefd[0])
If all write ends of the pipepipefd[1]被关闭,则继续调用read,readNo longer blocks after reading the data in the pipe,而返回0 ----Both parent and child processes callclose(pipefd[1])

2、共享内存

特性:
共享内存是最快的进程间通信方式
The life cycle does not follow the process,随内核(There is no artificial case)
Shared memory does not have its own synchronization and mutual exclusion,There are security issues when multiple processes access

3、队列
内核中的一个优先级队列,多个进程通过访问同一个队列,To add nodes or get nodes to achieve communication

msgget 创建 msgsnd 获取 msgctl 删除

4、信号量
信号量是用于实现进程间的同步与互斥(如共享内存不提供同步与互斥,存在安全隐患,可以使用信号量搭配共享内存使用)

四、共享内存

操作流程

  • 获取共享内存对象的ID
  • 将共享内存映射至本进程虚拟空间的某个区域
  • 不同进程通过对这块共享内存进行读写、传输数据
  • 当进程不再使用这块共享内存时,解除映射关系
  • 当没有进程再需要这块共享内存时,删除它

使用共享内存
相关API

  • 获取共享内存对象的ID:int shmget(key_t key, size_t size, int shmflg);
  • 映射共享内存:void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 解除内存映射:int shmdt(const void *shmaddr);
  • 设置内存对象:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 查看IPC对象信息:ipcs -m

相关API

获取共享内存对象的ID
shmget函数负责创建或打开共享内存段

  • 函数原型:int shmget(key_t key, size_t size, int shmflg);
  • 函数功能:创建或打开一个共享内存对象
  • 函数参数:
    • key:IPC对象的键值,一般为IPC_PRIVATE或ftok返回的key值
    • Size:共享内存大小,一般为内存物理页的整数倍
    • shmflg:
      • IPC_CREAT:如果不存在与指定的key对应的段,那么就创建一个新段
      • IPC_EXCL:若key指定的内存存在且指定了IPC_CREAT,返回EEXIST错误
      • SHM_HUGETLB:使用巨页(huge page)
  • 返回值:共享内存的标识符ID

attach共享内存——使用共享内存
shmget函数,不过是在茫茫内存中创建了或找到了一块共享内存区域,但是这块内存和进程尚没有任何关系.要

想使用该共享内存,必须先把共享内存引入进程的地址空间,这就是attach操作.

  • 函数原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 函数功能:将shmid标识的共享内存引入到当前进程的虚拟地址空间
  • 函数参数
    • shmid:共享内存的IPC对象ID

    • shmaddr:

      • 若为NULL:共享内存会被attach到一个合适的虚拟地址空间,建议使用NULL
      • 不为NULL:系统会根据参数及地址边界对齐等分配一个合适的地址
    • shmflg:

      • IPC_RDONLY:附加只读权限,不指定的话默认是读写权限
      • IPC_REMAP: 替换位于shmaddr处的任意既有映射:共享内存段或内存映射
      • SHM_RND : 将shmaddr四舍五入为SHMMLBA字节的倍数

第二个参数是用来指定将共享内存放到虚拟地址空间的什么位置的.
Most ordinary youth will set the second parameter to NULL,表示用户并不在意,一切交由内核做主.

  • 返回值:共享内存段的地址

shmat如果调用成功,则返回进程虚拟地址空间内的一个地址.如果失败,就会返回(void*)-1

detach共享内存——分离共享内存

shmdt函数仅仅是使进程和共享内存脱离关系,并未删除共享内存.shmdtThe function of the function is to decrement the reference count of the shared memory1.如前所述,只有共享内存的引用计数为0时,调用shmctl函数的IPC_RMID命令才会真正地删除共享内存.

  • 函数原型:int shmdt(const void *shmaddr);
  • 函数功能:解除内存映射,将共享内存分离出当前进程的地址空间
  • 函数参数:
    • shmaddr:共享内存地址

TIPS: 通过fork创建的子进程会继承父进程所附加的共享内存段,父子进程可以通过共享内存进行IPC通信.
在exec系统调用中,所有附加的共享内存段都会被分离
函数shmdt仅仅是使进程和共享内存脱离关系,将共享内存的引用计数减1,并未删除共享内存.
当共享内存的引用计数为0时,调用shmctl的IPC_RMID命令才会删除共享内存

设置共享内存属性

  • 函数原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 函数功能:获取/设置共享内存对象属性
  • 当cmd为IPC_STAT和IPC_SET时,需要用到第三个参数
  • 函数参数:
    • shmid:共享内存对象ID
    • cmd:
      • 当cmd为IPC_STAT和IPC_SET时,需要用到第三个参数
      • IPC_STAT: 用于获取shmid对应的共享内存的信息.
      • IPC_SET:IPC_SET也只能修改shm_perm中的uid、gid及mode
      • IPC_RMID:Shared memory segments can be deleted as follows:如果共享内存的引用计数shm_nattch等于0,则可以立即删除共享内存.
  • buf:
    • 将该内存对象关联的shmid_ds数据结构拷贝到参数buf中

编程方式

  1. 创建
  2. 映射
  3. 读写
  4. 解除映射
  5. 销毁

共享内存share memory写端

int main(int argc, char *argv[])
{
key_t key = ftok(".", 588);
int shm_id = shmget(key, 4096, IPC_CREAT|0666 );
printf("shm_id:%d\n", shm_id);
/* Automatic allocation */
char *shm_p = shmat(shm_id, NULL, 0);
memset(shm_p, 0, sizeof(shm_p));
fgets(shm_p, 4096, stdin);
sleep(30);
shmctl(shm_id, IPC_RMID, NULL);
return 0;
}

共享内存share memory读端

 int main(int argc, char *argv[])
{
key_t key = ftok(".", 588);
int shm_id= shmget(key, 4096, 0666);
char *shm_p = shmat(shm_id, NULL, 0);
printf("from share memory:%s\n", shm_p);
shmdt(shm_p);
return 0;
}

共享内存的通信限制

  • SHMMNI:系统所能创建的共享内存的最大个数,IPCMIN:32768
  • SHMMIN:一个共享内存段的最小字节数4096
  • SHMMAX:一个共享内存段的最大字节数33554432
  • SHMALL:系统中共享内存的分页总数2097152
  • SHMSEG:一个进程允许attch的共享内存段的大小个数

共享内存通信特点

  • 共享内存抛弃了“内核代理人”角色,提升了系统性能
  • 需要进程本身维护共享内存的各种问题:同步、互斥…
  • 一般需要信号量、互斥锁、文件锁等配合使用,在各个进程之间在高效通信的同时,防止发送数据的践踏,破坏
copyright:author[Jiangdong flower master Minming],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/266/202209230842521175.html