本文接这上一篇【服务器编程】Linux多进程编程(一)的内容介绍Linux多进程编程中常用的两种方式:共享内存和消息队列。
本文的内容是阅读总结游双的《Linux高性能服务器编程》第13章“多进程编程“而来
共享内存
共享内存是最高效的IPC机制,因为它不涉及进程之间的任何数据传输。这种高效率带来的问题是,我们必须用其他辅助手段来同步进程对共享内存的访问,否则会产生竞态条件。Linux共享内存的API都定义在sys/shm.h头文件中,包括4个系统调用:shmget、shmat、shmdt和shmctl。
shmget系统调用
shmget
系统调用创建一段新的共享内存,或者获取一段已经存在的共享内存。其定义如下:
#include <sys/shm.h>
int shmget(key_t key,size_t size, int shmflg);
- key:用来标识一段全局唯一的共享内存;
- size:指定共享内存的大小,如果是获取已经存在的共享内存,可以把size设置为0;
- shmflg:权限标志符;
shmat和shmdt系统调用
共享内存被创建/获取之后,我们不能立即访问它,二是需要先将它关联到进程的地址空间中。使用完共享内存之后,我们也需要将它从进程地址空间中分离。这两项任务分别由如下两个系统调用实现:
#include <sys/shm.h>
void* shmat(int shm_id,const void* shm_addr, int shmflg);
int shmdt(const void* shm_addr);
- shm_id:有shmget调用返回的共享内存标识符
- shm_addr:指定共享内存关联到进程的哪块地址空间,推荐设置为NULL,表示被关联的地址由操作系统选择
- shmflg:常见的有
SHM_RDONLY
(进程仅能读取共享内存中的内容),SHM_REMAP
(如果地址shmaddr已经被关联到一段共享内存上,则重新关联),SHM_EXEC
(指定对共享内存段的执行权限)
shmat
成功时返回共享内存被关联到的地址,失败则返回(void*)-1
并设置errno
。
shmdt
函数将关联到shm_addr
处的共享内存从进程中分离。它成功时返回0,失败则返回-1并设置errno
。
shmctl系统调用
shmctl
系统调用控制共享内存的某些属性。其定义如下:
#include <sys/shm.h>
int shmctl(int shm_id, int command, struct shmid_ds* buf);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nCfL9voM-1593871396806)(C:\Users\11754\Desktop\技术\总结\img\shmctl支持的命令.png)]
共享内存的POSIX方法
Linux提供了另外一种利用mmap
在无关进程之间共享内存的方式。这种方式无须任何文件的支持,但它需要先使用如下函数来创建或打开一个POSIX
共享内存对象:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int shm_open(const char* name, int oflag, mode_t mode);
- name参数指定要创建/打开的共享内存,格式为“/somename”
- oflag参数指定创建方式。它可以是下列标志中的一个或者多个的按位或:
- O_RDONLY:以只读方式打开共享内存对象
- O_RDWR:以可读、可写方式打开共享内存对象
- O_CREAT:如果共享内存对象不存在,则创建它
- O_EXCL:和O_CREAT一起使用,如果由name指定的共享内存对象已经存在,则shm_open调用返回错误,否则就创建一个新的共享内存对象。
由shm_open
创建的共享内存对象使用完之后也需要被删除。这个过程是通过如下函数实现的:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int shm_unlink(const char* name);
该函数将name参数指定的共享内存对象标记为等待删除。当所有使用该共享内存对象的进程都使用ummap
将它从进程中分离之后,系统将销毁这个共享内存对象所占据的资源。
如果代码中使用了上述POSIX
共享内存函数,则编译的时候需要指定链接选线-lrt
。
消息队列
消息队列是在两个进程之间传递二进制块数据的有一种简单有效的方式。每个数据块都有一个特定的类型,接收方可以根据类型来有选择地接收数据,而不一定像管道和命名管道那样必须以先进先出的方式接收数据。
Linux消息队列的API定义在sys/msg.h头文件中,包括:msgget、msgsnd、msgrcv和msgctl。
msgget
msgget
用于创建一个消息队列,或者获取一个已有的消息队列。其定义如下:
#include <sys/msg.h>
int msgget(key_t key,int msgflg);
key
参数是一个键值,用来标识一个全局唯一的消息队列。msgflg
参数是权限控制符,如:IPC_CREAT
表示如果消息队列不存在,则创建,否则打开;
msgsnd
msgsnd
把一条消息添加到消息队列中。定义如下:
#include <sys/msg.h>
int msgsnd(int msqid,const void* msg_ptr,size_t msg_sz,int msgflg);
msqid
参数是由msgget
调用返回的消息队列标识符;msg_ptr
参数指向一个准备发送的消息,消息必须被定义为如下类型:
struct msgbuf
{
long mtype; /* 消息类型 */
char mtext[512]; /* 消息数据 */
}
msg_sz
是消息的数据部分的长度msgflg
参数控制msgsnd
的行为。它通常仅支持IPC_NOWAIT
标志,即以非阻塞的方式发送消息。默认情况下,发送消息时如果消息队列满了,则msgsnd
将阻塞。如IPC_NOWAIT
标志被指定,则msgsnd
将立即返回并设置errno
为EAGAIN
。
msgrcv
msgrcv
系统调用从消息队列中获取消息。其定义如下:
#include <sys/msg.h>
int msgrcv(int msqid, void* msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
msqid
是msgget
调用返回的消息队列标识符;msg_ptr
用于存储接收消息的类型,msg_sz
指的是消息数据部分的长度;msgtype
参数指定接收何种类型的消息:- 等于0:读取消息队列中的第一个消息;
- 大于0:读取消息队列中第一个类型值为
msgtype
的消息; - 小于0:读取消息队列中第一个类型值比
msgtype
的绝对值小的消息;
msgflg
控制msgrcv
函数的行为:IPC_NOWAIT
:如果消息队列中没有消息,则msgrcv
调用立即返回并设置errno
为ENOMSG
;MSG_EXCEPT
:如果msgtype
大于0,则接收消息队列中第一个非msgtype
类型的消息;MSG_NOERROR
:如果消息数据部分的长度超过msg_sz
,就将它截断;
msgctl系统调用
msgctl
系统调用控制消息队列的某些属性。其定义如下:
#include <sys/msg.h>
int msgctl(int msqid, int command, struct msqid_ds* buf);
msqid
参数是由msgget
调用返回的共享内存标识符。command
参数指定要执行的命令。