示例代码
- github: https://github.com/lengjingzju/notes/tree/master/code/linux_ipc
- gitee: https://gitee.com/lengjingzju/notes/tree/master/code/linux_ipc
一、POSIX IPC概述
POSIX IPC(Inter-Process Communication)是IEEE POSIX标准中定义的一组进程间通信机制,提供了比System V IPC更现代、更简洁的接口。POSIX IPC包括:
- POSIX消息队列(Message Queues)
- POSIX信号量(Semaphores)
- POSIX共享内存(Shared Memory)
POSIX IPC的特点:
- 基于路径名:使用文件系统路径名来标识IPC对象,而不是System V的key_t键值
- 简洁的API:设计更符合UNIX哲学,使用文件描述符和类似文件操作的函数
- 更好的移植性:符合POSIX标准,在不同UNIX系统间移植性更好
- 支持非阻塞操作:大多数操作都支持非阻塞模式
- 更细粒度的控制:提供更多的属性和选项控制
POSIX IPC与System V IPC对比表:
| 特性 | System V IPC | POSIX IPC |
|---|---|---|
| 对象标识 | key_t键值(通过ftok生成) | 路径名(如"/myqueue") |
| API风格 | 专用的IPC函数 | 类似文件操作的函数 |
| 移植性 | 广泛支持,但接口较老 | 符合POSIX标准,移植性好 |
| 持久性 | 内核持久性 | 内核持久性 |
| 权限控制 | ipc_perm结构 | 文件系统权限 |
| 易用性 | 较复杂 | 较简单 |
POSIX IPC接口表
| 接口 | 消息队列 | 信号量 | 共享内存 |
|---|---|---|---|
| 头文件 | <mqueue.h> | <semaphore.h> | <mman.h> |
| 描述符 | mqd_t | sem_t* | int(文件描述符) |
| 创建/打开 | mq_open() | sem_open() | shm_open() |
| 关闭对象 | mq_close() | sem_close() | close() |
| 断开连接 | mq_unlink() | sem_unlink() | shm_unlink() |
| 执行IPC | mq_send() | sem_post() | 使用文件I/O函数 |
| mq_receive() | sem_wait() | ||
| sem_getvalue() | |||
| 其它操作 | mq_getattr() | sem_init() | |
| mq_setattr() | sem_destroy() | ||
| mq_notify() |
- 除非特别说明,完全可以套用文件I/O模型理解和使用POSIX IPC。
二、POSIX消息队列
1. 接口说明
c
/**
* @brief 创建或打开POSIX消息队列
* @param name [IN] 消息队列的路径名,以'/'开头,不含其他'/'
* @param oflag [IN] 打开标志:O_CREAT、O_EXCL、O_RDONLY、O_WRONLY、O_RDWR
* @param mode [IN] 权限模式(当oflag包含O_CREAT时有效)
* @param attr [IN] 消息队列属性指针,为NULL时使用默认属性
* @return 成功返回消息队列描述符,失败返回(mqd_t)-1
* @note 消息队列名字需要符合规范,在Linux上通常位于/dev/mqueue
*/
mqd_t mq_open(const char *name, int oflag, ... /* mode_t mode, struct mq_attr *attr */);
/**
* @brief 关闭消息队列
* @param mqdes [IN] 消息队列描述符
* @return 成功返回0,失败返回-1
* @note 关闭并不会删除消息队列,只是当前进程不再使用该描述符
*/
int mq_close(mqd_t mqdes);
/**
* @brief 删除消息队列
* @param name [IN] 消息队列的路径名
* @return 成功返回0,失败返回-1
* @note 删除后,已打开该队列的进程仍可使用,直到所有进程都关闭
*/
int mq_unlink(const char *name);
/**
* @brief 发送消息到消息队列
* @param mqdes [IN] 消息队列描述符
* @param msg_ptr [IN] 指向消息数据的指针
* @param msg_len [IN] 消息长度(字节)
* @param msg_prio [IN] 消息优先级(0最低,数值越大优先级越高)
* @return 成功返回0,失败返回-1
* @note 消息长度不能超过创建时指定的mq_msgsize
*/
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
/**
* @brief 从消息队列接收消息
* @param mqdes [IN] 消息队列描述符
* @param msg_ptr [OUT] 指向接收缓冲区的指针
* @param msg_len [IN] 缓冲区长度
* @param msg_prio [OUT] 接收到的消息优先级(可为NULL)
* @return 成功返回接收到的消息字节数,失败返回-1
* @note 默认接收优先级最高的最早消息,缓冲区长度必须≥mq_msgsize
*/
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);
/**
* @brief 获取消息队列属性
* @param mqdes [IN] 消息队列描述符
* @param attr [OUT] 存储属性的结构体指针
* @return 成功返回0,失败返回-1
*/
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
/**
* @brief 设置消息队列属性
* @param mqdes [IN] 消息队列描述符
* @param newattr [IN] 新的属性
* @param oldattr [OUT] 旧的属性(可为NULL)
* @return 成功返回0,失败返回-1
* @note 只能修改mq_flags(O_NONBLOCK),不能修改mq_maxmsg和mq_msgsize
*/
int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr);
2. 代码示例
以下是一个使用POSIX消息队列的例子,父进程从终端读取数据,发送到消息队列,子进程从消息队列接收数据,输出到终端。输入"q"退出。
代码链接:test_posix_mq.c
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <mqueue.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "jlog_core.h"
#define BUF_SIZE 128
#define TIP(str) write(STDOUT_FILENO, str, strlen(str))
#define POSIX_MQ_PATH "/test_posix_mq"
#define POSIX_MQ_PRIO 0
static int read_from_posix_mq(void)
{
struct mq_attr attr;
char *buf;
ssize_t num;
mqd_t mqid;
int ret = 0;
usleep(1000);
mqid = mq_open(POSIX_MQ_PATH, O_RDONLY);
if (mqid == -1) {
LLOG_ERRNO("mq_open(%s) failed!\n", POSIX_MQ_PATH);
return -1;
}
if (mq_getattr(mqid, &attr) == -1) {
mq_close(mqid);
LLOG_ERRNO("mq_getattr() failed!\n");
return -1;
}
buf = malloc(attr.mq_msgsize);
if (buf == NULL) {
mq_close(mqid);
LLOG_ERRNO("malloc(%ld) failed!\n", attr.mq_msgsize);
return -1;
}
for (;;) {
memset(buf, 0, attr.mq_msgsize);
num = mq_receive(mqid, buf, attr.mq_msgsize, NULL);
if (num == -1) {
LLOG_ERRNO("mq_receive() failed!\n");
ret = -1;
break;
}
if (num == 0)
continue;
if (num == 2 && buf[0] == 'q')
break;
TIP("mq_receive: ");
if (write(STDOUT_FILENO, buf, num) != num) {
LLOG_ERRNO("write STDOUT failed!\n");
ret = -1;
break;
}
}
free(buf);
mq_close(mqid);
return ret;
}
static int write_to_posix_mq(void)
{
char buf[BUF_SIZE];
ssize_t num;
mqd_t mqid;
int ret = 0;
mqid = mq_open(POSIX_MQ_PATH, O_CREAT | O_EXCL | O_WRONLY,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, NULL);
if (mqid == -1) {
LLOG_ERRNO("mq_open(%s) failed!\n", POSIX_MQ_PATH);
return -1;
}
for (;;) {
usleep(1000);
TIP("mq_send: ");
memset(buf, 0, BUF_SIZE);
num = read(STDIN_FILENO, buf, BUF_SIZE);
if (num == -1) {
LLOG_ERRNO("read STDIN failed!\n");
ret = -1;
break;
}
if (num == 0)
continue;
if (mq_send(mqid, buf, num, POSIX_MQ_PRIO) == -1) {
LLOG_ERRNO("mq_send() failed!\n");
ret = -1;
break;
}
if (num == 2 && buf[0] == 'q')
break;
}
mq_close(mqid);
mq_unlink(POSIX_MQ_PATH);
return ret;
}
int main(int argc, char *argv[])
{
int ret = 0;
SLOG_INFO("POSIX MQ: 父进程从终端写入获取数据发送消息,子进程接收消息将数据输出到终端。\n");
SLOG_INFO("[input q to exit]\n");
switch (fork()) {
case -1:
LLOG_ERRNO("fork() failed!\n");
exit(EXIT_FAILURE);
case 0: /* Child */
ret = read_from_posix_mq();
SLOG_INFO("child process exit!\n");
exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
default: /* Parent */
ret = write_to_posix_mq();
wait(NULL); /* wait for child process exit */
SLOG_INFO("parent process exit!\n");
exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
}
3. 详细讲解
工作原理:
- 使用
mq_open创建或打开消息队列,创建时需要指定路径名、标志、权限和属性。 - 父进程以只写方式创建消息队列,子进程以只读方式打开消息队列。
- 父进程从标准输入读取数据,使用
mq_send发送到消息队列。 - 子进程使用
mq_receive从消息队列接收数据,并输出到标准输出。 - 通信完成后,父进程关闭消息队列并使用
mq_unlink删除它。
关键点:
- 路径名规范 :必须以'/'开头,且不能包含其他'/'。在Linux上,POSIX消息队列通常位于
/dev/mqueue目录下(通过挂载)。 - 消息属性:创建时可以指定最大消息数(mq_maxmsg)和最大消息大小(mq_msgsize),创建后不能修改。
- 消息优先级 :支持0~
sysconf(_SC_MQ_PRIO_MAX)-1的优先级,0为最低,数值越大优先级越高。 - 阻塞与非阻塞 :默认是阻塞的,可以通过
mq_setattr设置O_NONBLOCK标志。 - 持久性:消息队列在内核中持久存在,直到被显式删除。
与System V消息队列对比:
- POSIX消息队列支持消息优先级,System V消息队列通过消息类型实现类似功能。
- POSIX消息队列有最大消息大小限制,System V消息队列没有硬性限制(但受内核参数影响)。
- POSIX消息队列API更简洁,使用路径名而不是键值。
三、POSIX信号量
POSIX信号量分为两种类型:
- 命名信号量(Named Semaphore):通过路径名标识,可用于不相关进程间同步
- 无名信号量(Unnamed Semaphore):通过内存地址标识,可用于线程间或相关进程间同步
1. 接口说明
c
/**
* @brief 创建或打开命名信号量
* @param name [IN] 信号量的路径名,以'/'开头
* @param oflag [IN] 打开标志:O_CREAT、O_EXCL
* @param mode [IN] 权限模式
* @param value [IN] 信号量的初始值(当oflag包含O_CREAT时有效)
* @return 成功返回信号量指针,失败返回SEM_FAILED
* @note 在Linux上,命名信号量在/dev/shm目录下创建sem.name文件
*/
sem_t *sem_open(const char *name, int oflag, ... /* mode_t mode, unsigned int value */);
/**
* @brief 关闭命名信号量
* @param sem [IN] 信号量指针
* @return 成功返回0,失败返回-1
* @note 关闭并不会删除信号量,只是当前进程不再使用该信号量
*/
int sem_close(sem_t *sem);
/**
* @brief 删除命名信号量
* @param name [IN] 信号量的路径名
* @return 成功返回0,失败返回-1
*/
int sem_unlink(const char *name);
/**
* @brief 等待信号量(P操作,减1操作)
* @param sem [IN] 信号量指针
* @return 成功返回0,失败返回-1
* @note 如果信号量值>0,则减1并立即返回;否则阻塞直到信号量值>0
*/
int sem_wait(sem_t *sem);
/**
* @brief 非阻塞等待信号量
* @param sem [IN] 信号量指针
* @return 成功返回0,失败返回-1(如果信号量值为0,设置errno为EAGAIN)
*/
int sem_trywait(sem_t *sem);
/**
* @brief 带超时的等待信号量
* @param sem [IN] 信号量指针
* @param abs_timeout [IN] 绝对超时时间
* @return 成功返回0,失败返回-1
*/
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
/**
* @brief 发布信号量(V操作,加1操作)
* @param sem [IN] 信号量指针
* @return 成功返回0,失败返回-1
* @note 将信号量值加1,并唤醒等待该信号量的线程/进程
*/
int sem_post(sem_t *sem);
/**
* @brief 获取信号量的当前值
* @param sem [IN] 信号量指针
* @param sval [OUT] 存储当前值的指针
* @return 成功返回0,失败返回-1
* @note 获取的值可能在你使用它时已经过时
*/
int sem_getvalue(sem_t *sem, int *sval);
对于无名信号量,还有以下两个专用函数:
c
/**
* @brief 初始化无名信号量
* @param sem [IN] 信号量指针
* @param pshared [IN] 共享标志:0表示线程间共享,非0表示进程间共享
* @param value [IN] 信号量的初始值
* @return 成功返回0,失败返回-1
* @note 进程间共享时,信号量必须位于共享内存区域
*/
int sem_init(sem_t *sem, int pshared, unsigned int value);
/**
* @brief 销毁无名信号量
* @param sem [IN] 信号量指针
* @return 成功返回0,失败返回-1
*/
int sem_destroy(sem_t *sem);
2. 详细讲解
命名信号量使用步骤:
- 使用
sem_open创建或打开命名信号量 - 使用
sem_wait/sem_post进行P/V操作 - 使用
sem_close关闭信号量 - 使用
sem_unlink删除信号量(可选)
无名信号量使用步骤:
- 为信号量分配内存(全局变量、堆内存或共享内存)
- 使用
sem_init初始化信号量 - 使用
sem_wait/sem_post进行P/V操作 - 使用
sem_destroy销毁信号量
关键点:
- 命名信号量路径名 :必须以'/'开头,在Linux上位于
/dev/shm目录(sem.name文件)。 - 无名信号量位置:线程间共享通常放在全局变量或堆上;进程间共享必须放在共享内存中。
- 信号量值范围:信号量值永远不会小于0,初始值可以设为任意非负整数。
- 阻塞行为 :
sem_wait在信号量值为0时会阻塞;sem_trywait立即返回;sem_timedwait支持超时。 - 原子性:所有操作都是原子的,多个进程/线程同时操作不会出现竞态条件。
与System V信号量对比:
- POSIX信号量更简单,只支持单个信号量值的增减;System V信号量支持信号量集和复杂操作。
- POSIX信号量API更简洁,使用路径名而不是键值。
- POSIX无名信号量可以用于线程间同步,System V信号量只能用于进程间同步。
四、POSIX共享内存
1. 接口说明
c
/**
* @brief 创建或打开POSIX共享内存对象
* @param name [IN] 共享内存的路径名,以'/'开头
* @param oflag [IN] 打开标志:O_CREAT、O_EXCL、O_RDONLY、O_RDWR
* @param mode [IN] 权限模式
* @return 成功返回文件描述符,失败返回-1
* @note 创建后共享内存大小为0,需要使用ftruncate设置大小
* 在Linux上,共享内存在/dev/shm目录下创建文件
*/
int shm_open(const char *name, int oflag, mode_t mode);
/**
* @brief 删除共享内存对象的名字
* @param name [IN] 共享内存的路径名
* @return 成功返回0,失败返回-1
* @note 删除名字后,已打开该对象的进程仍可继续使用,直到所有引用都关闭
*/
int shm_unlink(const char *name);
POSIX共享内存通常与内存映射函数一起使用:
c
/**
* @brief 将共享内存映射到进程地址空间
* @param addr [IN] 指定映射地址,通常为NULL(由系统选择)
* @param length [IN] 映射长度
* @param prot [IN] 保护标志:PROT_READ、PROT_WRITE等
* @param flags [IN] 映射标志:MAP_SHARED、MAP_PRIVATE
* @param fd [IN] 文件描述符(shm_open返回的)
* @param offset [IN] 偏移量,通常为0
* @return 成功返回映射地址,失败返回MAP_FAILED
*/
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
/**
* @brief 解除内存映射
* @param addr [IN] 映射地址
* @param length [IN] 映射长度
* @return 成功返回0,失败返回-1
*/
int munmap(void *addr, size_t length);
2. 代码示例
以下是一个使用POSIX共享内存和POSIX命名信号量的例子,父进程从终端读取数据写入共享内存,子进程从共享内存读取数据输出到终端。使用两个命名信号量同步读写。
代码链接:test_posix_shm.c
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "jlog_core.h"
#define BUF_SIZE 128
#define TIP(str) write(STDOUT_FILENO, str, strlen(str))
#define POSIX_SHM_SIZE (8192 * 1)
#define POSIX_SHM_PATH "/test_posix_shm"
#define POSIX_RSEM_PATH "/test_posix_rsem"
#define POSIX_WSEM_PATH "/test_posix_wsem"
static int read_from_posix_shm(void)
{
char buf[BUF_SIZE];
char *addr;
ssize_t num;
int shmid;
sem_t *rsemid, *wsemid;
int bflag = 0;
int ret = 0;
usleep(1000);
shmid = shm_open(POSIX_SHM_PATH, O_RDWR, 0);
if (shmid == -1) {
LLOG_ERRNO("shm_open(%s) failed!\n", POSIX_SHM_PATH);
ret = -1;
goto end0;
}
addr = mmap(NULL, POSIX_SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0);
if ((void*)addr == MAP_FAILED) {
LLOG_ERRNO("mmap(%s) failed!\n", POSIX_SHM_PATH);
ret = -1;
goto end1;
}
rsemid = sem_open(POSIX_RSEM_PATH, O_RDWR);
if ((void*)rsemid == (void*)-1) {
LLOG_ERRNO("sem_open(%s) failed!\n", POSIX_RSEM_PATH);
ret = -1;
goto end2;
}
wsemid = sem_open(POSIX_WSEM_PATH, O_RDWR);
if ((void*)wsemid == (void*)-1) {
LLOG_ERRNO("sem_open(%s) failed!\n", POSIX_WSEM_PATH);
ret = -1;
goto end3;
}
for (;;) {
sem_wait(rsemid);
memset(buf, 0, BUF_SIZE);
memcpy(buf, addr, BUF_SIZE);
num = strlen(buf);
if (num == 0) {
goto post;
}
if (num == 2 && buf[0] == 'q') {
bflag = 1;
goto post;
}
TIP("read SHM: ");
if (write(STDOUT_FILENO, buf, num) != num) {
LLOG_ERRNO("write STDOUT failed!\n");
bflag = 1;
ret = -1;
}
post:
sem_post(wsemid);
if (bflag)
break;
}
sem_close(wsemid);
end3:
sem_close(rsemid);
end2:
munmap(addr, POSIX_SHM_SIZE);
end1:
close(shmid);
end0:
return ret;
}
static int write_to_posix_shm(void)
{
char buf[BUF_SIZE];
char *addr;
ssize_t num;
int shmid;
sem_t *rsemid, *wsemid;
int bflag = 0;
int ret = 0;
shmid = shm_open(POSIX_SHM_PATH, O_CREAT | O_EXCL | O_RDWR,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
if (shmid == -1) {
LLOG_ERRNO("shm_open(%s) failed!\n", POSIX_SHM_PATH);
ret = -1;
goto end0;
}
if (ftruncate(shmid, POSIX_SHM_SIZE) == -1) {
LLOG_ERRNO("ftruncate(%d) failed!\n", POSIX_SHM_SIZE);
ret = -1;
goto end1;
}
addr = mmap(NULL, POSIX_SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0);
if ((void*)addr == MAP_FAILED) {
LLOG_ERRNO("mmap(%s) failed!\n", POSIX_SHM_PATH);
ret = -1;
goto end1;
}
rsemid = sem_open(POSIX_RSEM_PATH, O_CREAT | O_EXCL | O_RDWR,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, 0);
if ((void*)rsemid == (void*)-1) {
LLOG_ERRNO("sem_open(%s) failed!\n", POSIX_RSEM_PATH);
ret = -1;
goto end2;
}
wsemid = sem_open(POSIX_WSEM_PATH, O_CREAT | O_EXCL | O_RDWR,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, 1);
if ((void*)wsemid == (void*)-1) {
LLOG_ERRNO("sem_open(%s) failed!\n", POSIX_WSEM_PATH);
ret = -1;
goto end3;
}
for (;;) {
sem_wait(wsemid);
TIP("write SHM: ");
memset(buf, 0, BUF_SIZE);
num = read(STDIN_FILENO, buf, BUF_SIZE);
if (num == -1) {
LLOG_ERRNO("read STDIN failed!\n");
bflag = 1;
ret = -1;
goto post;
}
if (num == 0) {
goto post;
}
memset(addr, 0, BUF_SIZE);
memcpy(addr, buf, BUF_SIZE);
if (num == 2 && buf[0] == 'q') {
bflag = 1;
}
post:
sem_post(rsemid);
if (bflag)
break;
}
sem_close(wsemid);
sem_unlink(POSIX_WSEM_PATH);
end3:
sem_close(rsemid);
sem_unlink(POSIX_RSEM_PATH);
end2:
munmap(addr, POSIX_SHM_SIZE);
end1:
close(shmid);
shm_unlink(POSIX_SHM_PATH);
end0:
return ret;
}
int main(int argc, char *argv[])
{
int ret = 0;
SLOG_INFO("POSIX SHM: 父进程从终端写入获取数据发送消息,子进程接收消息将数据输出到终端。\n");
SLOG_INFO("[input q to exit]\n");
switch (fork()) {
case -1:
LLOG_ERRNO("fork() failed!\n");
exit(EXIT_FAILURE);
case 0: /* Child */
ret = read_from_posix_shm();
SLOG_INFO("child process exit!\n");
exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
default: /* Parent */
ret = write_to_posix_shm();
wait(NULL); /* wait for child process exit */
SLOG_INFO("parent process exit!\n");
exit(ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
}
3. 详细讲解
工作原理:
- 使用
shm_open创建或打开共享内存对象,创建后大小为0。 - 使用
ftruncate设置共享内存的大小。 - 使用
mmap将共享内存映射到进程地址空间。 - 使用
sem_open创建命名信号量用于同步。 - 父进程写入数据前等待写者信号量,写入后释放读者信号量。
- 子进程读取数据前等待读者信号量,读取后释放写者信号量。
- 通信完成后,父进程解除映射、关闭文件描述符、删除共享内存对象和信号量。
关键点:
- 路径名规范 :必须以'/'开头,在Linux上位于
/dev/shm目录。 - 初始大小 :
shm_open创建的共享内存大小为0,必须使用ftruncate设置大小。 - 内存映射 :使用
mmap将共享内存映射到进程地址空间后,可以像普通内存一样访问。 - 同步机制:共享内存没有内置同步,必须使用信号量或其他同步机制。
- 持久性:共享内存对象在内核中持久存在,直到被显式删除。
与System V共享内存对比:
- POSIX共享内存使用文件描述符,可以应用所有文件操作函数(如
fstat、fcntl)。 - POSIX共享内存可以方便地使用
mmap映射到进程地址空间。 - System V共享内存使用
shmat/shmdt进行映射/解除映射。 - POSIX共享内存API更简洁,使用路径名而不是键值。
五、内存映射高级功能
除了基本的mmap和munmap,POSIX还提供了一些高级内存映射功能:
c
/**
* @brief 同步内存映射到文件
* @param addr [IN] 映射地址
* @param length [IN] 同步长度
* @param flags [IN] 同步标志:MS_ASYNC(异步)、MS_SYNC(同步)
* @return 成功返回0,失败返回-1
* @note MS_SYNC会阻塞直到同步完成,MS_ASYNC立即返回
*/
int msync(void *addr, size_t length, int flags);
/**
* @brief 修改内存保护权限
* @param addr [IN] 内存地址
* @param length [IN] 修改长度
* @param prot [IN] 新的保护权限
* @return 成功返回0,失败返回-1
*/
int mprotect(void *addr, size_t length, int prot);
/**
* @brief 内存加锁(防止被交换出去)
* @param addr [IN] 内存地址
* @param length [IN] 锁定长度
* @return 成功返回0,失败返回-1
*/
int mlock(void *addr, size_t length);
int munlock(void *addr, size_t length);
/**
* @brief 进程所有内存加锁
* @param flags [IN] 标志:MCL_CURRENT(当前)、MCL_FUTURE(未来)
* @return 成功返回0,失败返回-1
*/
int mlockall(int flags);
int munlockall(void);
六、总结
POSIX IPC提供了现代、简洁、可移植的进程间通信机制:
1. 主要优势
- 标准化:符合POSIX标准,移植性好
- 简洁API:类似文件操作,学习成本低
- 路径名标识:使用文件系统路径名,更直观
- 细粒度控制:提供丰富的属性和选项
2. 三种机制对比
| 机制 | 适用场景 | 特点 | 同步需求 |
|---|---|---|---|
| 消息队列 | 进程间传递结构化消息 | 支持优先级,消息有边界 | 内置同步 |
| 信号量 | 进程间/线程间同步 | 分为命名和无名两种 | 本身就是同步机制 |
| 共享内存 | 进程间高效共享大量数据 | 最快的IPC机制 | 需要额外同步 |
3. 使用建议
- 新项目优先选择POSIX IPC:除非需要与旧System V代码兼容
- 根据需求选择机制 :
- 少量结构化数据:消息队列
- 同步控制:信号量
- 大量数据共享:共享内存+信号量
- 注意资源管理:POSIX IPC对象在内核中持久存在,需要显式删除
- 考虑可移植性:POSIX IPC在不同UNIX系统间移植性更好
4. 性能考虑
- 共享内存是最快的IPC机制,适合高性能场景
- 消息队列适合结构化数据传递,但性能低于共享内存
- 信号量同步开销很小,适合频繁的同步操作
5. 安全考虑
- 使用适当的权限模式(如0600)限制访问
- 验证输入数据,防止缓冲区溢出
- 使用同步机制防止竞态条件
POSIX IPC是现代UNIX/Linux系统推荐的进程间通信方式,提供了强大而简洁的接口。掌握这些机制对于开发高效、可移植的多进程应用至关重要。
在下一篇文章中,我们将介绍线程同步机制,包括互斥锁、条件变量、读写锁等,这些是构建多线程程序的基础。