一网打尽Linux IPC(四):POSIX 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的特点:

  1. 基于路径名:使用文件系统路径名来标识IPC对象,而不是System V的key_t键值
  2. 简洁的API:设计更符合UNIX哲学,使用文件描述符和类似文件操作的函数
  3. 更好的移植性:符合POSIX标准,在不同UNIX系统间移植性更好
  4. 支持非阻塞操作:大多数操作都支持非阻塞模式
  5. 更细粒度的控制:提供更多的属性和选项控制

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. 详细讲解

工作原理

  1. 使用mq_open创建或打开消息队列,创建时需要指定路径名、标志、权限和属性。
  2. 父进程以只写方式创建消息队列,子进程以只读方式打开消息队列。
  3. 父进程从标准输入读取数据,使用mq_send发送到消息队列。
  4. 子进程使用mq_receive从消息队列接收数据,并输出到标准输出。
  5. 通信完成后,父进程关闭消息队列并使用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. 详细讲解

命名信号量使用步骤

  1. 使用sem_open创建或打开命名信号量
  2. 使用sem_wait/sem_post进行P/V操作
  3. 使用sem_close关闭信号量
  4. 使用sem_unlink删除信号量(可选)

无名信号量使用步骤

  1. 为信号量分配内存(全局变量、堆内存或共享内存)
  2. 使用sem_init初始化信号量
  3. 使用sem_wait/sem_post进行P/V操作
  4. 使用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. 详细讲解

工作原理

  1. 使用shm_open创建或打开共享内存对象,创建后大小为0。
  2. 使用ftruncate设置共享内存的大小。
  3. 使用mmap将共享内存映射到进程地址空间。
  4. 使用sem_open创建命名信号量用于同步。
  5. 父进程写入数据前等待写者信号量,写入后释放读者信号量。
  6. 子进程读取数据前等待读者信号量,读取后释放写者信号量。
  7. 通信完成后,父进程解除映射、关闭文件描述符、删除共享内存对象和信号量。

关键点

  • 路径名规范 :必须以'/'开头,在Linux上位于/dev/shm目录。
  • 初始大小shm_open创建的共享内存大小为0,必须使用ftruncate设置大小。
  • 内存映射 :使用mmap将共享内存映射到进程地址空间后,可以像普通内存一样访问。
  • 同步机制:共享内存没有内置同步,必须使用信号量或其他同步机制。
  • 持久性:共享内存对象在内核中持久存在,直到被显式删除。

与System V共享内存对比

  • POSIX共享内存使用文件描述符,可以应用所有文件操作函数(如fstatfcntl)。
  • POSIX共享内存可以方便地使用mmap映射到进程地址空间。
  • System V共享内存使用shmat/shmdt进行映射/解除映射。
  • POSIX共享内存API更简洁,使用路径名而不是键值。

五、内存映射高级功能

除了基本的mmapmunmap,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系统推荐的进程间通信方式,提供了强大而简洁的接口。掌握这些机制对于开发高效、可移植的多进程应用至关重要。


在下一篇文章中,我们将介绍线程同步机制,包括互斥锁、条件变量、读写锁等,这些是构建多线程程序的基础。

相关推荐
取加若则_2 小时前
Linux进程状态解析:僵尸与孤儿进程揭秘
linux
活蹦乱跳酸菜鱼2 小时前
Linux开发板使用AI-通义千问
linux·运维·服务器
Xの哲學2 小时前
Linux IPC机制深度剖析:从设计哲学到内核实现
linux·服务器·网络·算法·边缘计算
lihui_cbdd2 小时前
[故障排查] NFS 存储集群卡顿的完整排查记录:谁在深夜疯狂读写?
linux·运维
掘根2 小时前
【消息队列项目】客户端搭建与测试
运维·服务器·中间件
代码游侠2 小时前
应用——Linux Socket编程
运维·服务器·开发语言·笔记·网络协议·学习
张火火isgudi2 小时前
VMware Debian 挂载 Windows 文件夹至 Debian 目录
linux·运维·windows·debian
自信150413057592 小时前
数据结构初阶之单链表
c语言·数据结构
石榴花上2 小时前
银河麒麟上的9090端口被占用问题处理
linux