1.进程间通信的核心分类
首先明确 Linux 支持的主流 IPC 方式,按使用场景和特性分类:
  1. 管道(匿名管道 / 命名管道):简单易用、单向 / 双向、基于文件描述符,适合简单数据传递的场景;匿名管道适用于父子进程 / 有亲缘关系进程间的通信;命名管道适用于无亲缘关系进程间的通信。
  2. 消息队列(System V/Posix):按消息类型传递、异步通信、可持久化,适合无亲缘关系进程、需要分类传递数据的场景。
  3. 共享内存(System V/Posix):最高效(直接访问内存,无数据拷贝)、需同步机制配合,适合高并发、大数据量传递场景(如视频、大数据处理)。
  4. 信号量(System V/Posix):实现进程间同步、互斥,保护共享资源;配合共享内存、多个进程竞争同一资源的场景(如临界区保护)。
  5. 信号(Linux 标准信号 / 实时信号):异步通知、简单高效、携带信息少;适合进程异常处理、简单通知场景(如终止进程、定时器通知)。
  6. Socket(AF_UNIX/AF_INET):支持本地进程(AF_UNIX)、跨主机进程(AF_INET)、通用性强;适合本地无亲缘关系进程、跨服务器分布式进程通信。
2.管道(Pipe)—— 最简单的字节流通信
管道是 Linux 最基础的 IPC 机制,本质是「内核维护的一块缓存」,对外表现为一对文件描述符,进程通过读写文件描述符实现数据传递。分为匿名管道和命名管道两种。
2.1.匿名管道(Anonymous Pipe)
  • 核心特性:
    • 「匿名」:无实际文件路径,仅存在于内核中,进程退出后管道消失。
    • 单向通信:数据只能从一端写入,另一端读出(读端、写端固定)。
    • 亲缘关系限制:仅支持父子进程、兄弟进程等有亲缘关系的进程通信(由 fork() 继承文件描述符)。
    • 基于字节流:无消息边界,读取数据时按字节流读取,需自行处理数据格式。
    • 阻塞特性:读端无数据时阻塞,写端管道满时阻塞;写端关闭后,读端读取到 EOF(0);读端关闭后,写端会收到 SIGPIPE 信号终止。
  • 函数原型:
// 成功返回 0,失败返回 -1,设置 errno
int pipe(int pipefd[2]);
  • 参数:
    • pipefd[2]:输出参数,返回两个文件描述符:
      • pipefd[0]:管道读端,用于读取数据。
      • pipefd[1]:管道写端,用于写入数据。
  • 注意:创建管道后,通常需要配合 fork() 创建子进程,子进程继承管道文件描述符,再关闭无用的端(如父进程关闭读端,子进程关闭写端,实现单向通信)。
  • 实操示例:父子进程通过匿名管道通信
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {
    int pipefd[2];
    char buf[1024];
    const char *msg = "Hello, 子进程!这是父进程通过匿名管道发送的消息\n";

    // 第一步:创建匿名管道
    if (pipe(pipefd) == -1) {
        perror("pipe failed");
        exit(EXIT_FAILURE);
    }

    // 第二步:创建子进程
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // 子进程:关闭写端,读取管道数据
        close(pipefd[1]);  // 子进程仅读取,关闭无用的写端

        // 阻塞读取管道数据
        ssize_t read_len = read(pipefd[0], buf, sizeof(buf) - 1);
        if (read_len > 0) {
            buf[read_len] = '\0';  // 字符串结束符
            printf("【子进程】读取到管道数据:%s", buf);
        } else if (read_len == 0) {
            printf("【子进程】管道写端已关闭,无更多数据\n");
        } else {
            perror("read failed");
        }

        close(pipefd[0]);  // 读取完成,关闭读端
        exit(EXIT_SUCCESS);
    } else {
        // 父进程:关闭读端,写入管道数据
        close(pipefd[0]);  // 父进程仅写入,关闭无用的读端

        // 向管道写入数据
        ssize_t write_len = write(pipefd[1], msg, strlen(msg));
        if (write_len == -1) {
            perror("write failed");
        } else {
            printf("【父进程】成功向管道写入 %zd 字节数据\n", write_len);
        }

        close(pipefd[1]);  // 写入完成,关闭写端(触发子进程 EOF)

        // 等待子进程执行完毕
        wait(NULL);
        exit(EXIT_SUCCESS);
    }
}
  • 编译运行结果:
gcc anonymous_pipe_demo.c -o anonymous_pipe_demo
./anonymous_pipe_demo

# 输出:
【父进程】成功向管道写入 66 字节数据
【子进程】读取到管道数据:Hello, 子进程!这是父进程通过匿名管道发送的消息
2.2.命名管道(Named Pipe/FIFO)
  • 核心特性:
    • 「命名」:有实际的文件路径(如 /tmp/my_fifo),存在于文件系统中,进程退出后管道文件仍存在(需手动删除)。
    • 双向 / 单向通信:默认单向,可通过打开方式配置为双向。
    • 无亲缘关系限制:支持任意两个无亲缘关系的进程通信(只要能访问该管道文件)。
    • 其他特性:与匿名管道一致(基于字节流、阻塞特性、依赖文件描述符)。
  • 函数原型:
// 成功返回 0,失败返回 -1,设置 errno
int mkfifo(const char *pathname, mode_t mode);
  • 参数:
    • pathname:管道文件的路径(如 /tmp/my_fifo)。
    • mode:管道文件的权限(如 0664,与 chmod 权限一致,受 umask 影响)。
  • 注意:命名管道创建后,需要通过 open() 打开(读端用 O_RDONLY,写端用 O_WRONLY),之后通过 read()/write() 读写数据,与普通文件操作一致。
  • 实操示例:两个无亲缘关系进程通过命名管道通信

第一步:写进程(fifo_writer.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

#define FIFO_PATH "/tmp/my_fifo"  // 命名管道路径
#define MODE 0664  // 管道文件权限

int main() {
    const char *msg = "Hello, 读进程!这是写进程通过命名管道发送的消息\n";

    // 第一步:创建命名管道(若已存在,忽略 EEXIST 错误)
    if (mkfifo(FIFO_PATH, MODE) == -1) {
        if (errno != EEXIST) {  // 排除文件已存在的错误
            perror("mkfifo failed");
            exit(EXIT_FAILURE);
        }
        printf("【写进程】命名管道已存在,无需重复创建\n");
    } else {
        printf("【写进程】成功创建命名管道:%s\n", FIFO_PATH);
    }

    // 第二步:以只写模式打开命名管道
    int fd = open(FIFO_PATH, O_WRONLY);
    if (fd == -1) {
        perror("open failed");
        exit(EXIT_FAILURE);
    }

    // 第三步:向管道写入数据
    ssize_t write_len = write(fd, msg, strlen(msg));
    if (write_len == -1) {
        perror("write failed");
    } else {
        printf("【写进程】成功向管道写入 %zd 字节数据\n", write_len);
    }

    // 第四步:关闭文件描述符,删除管道文件(可选)
    close(fd);
    unlink(FIFO_PATH);  // 删除管道文件
    printf("【写进程】操作完成,已删除命名管道\n");

    exit(EXIT_SUCCESS);
}

第二步:读进程(fifo_reader.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

#define FIFO_PATH "/tmp/my_fifo"  // 与写进程一致的管道路径

int main() {
    char buf[1024];

    // 第一步:以只读模式打开命名管道(若不存在,会阻塞等待管道被创建)
    printf("【读进程】等待命名管道创建...\n");
    int fd = open(FIFO_PATH, O_RDONLY);
    if (fd == -1) {
        perror("open failed");
        exit(EXIT_FAILURE);
    }
    printf("【读进程】成功打开命名管道:%s\n", FIFO_PATH);

    // 第二步:阻塞读取管道数据
    ssize_t read_len = read(fd, buf, sizeof(buf) - 1);
    if (read_len > 0) {
        buf[read_len] = '\0';
        printf("【读进程】读取到管道数据:%s", buf);
    } else if (read_len == 0) {
        printf("【读进程】管道写端已关闭,无更多数据\n");
    } else {
        perror("read failed");
    }

    // 第三步:关闭文件描述符
    close(fd);
    printf("【读进程】操作完成\n");

    exit(EXIT_SUCCESS);
}

第三步:编译运行

# 1. 编译两个进程
gcc fifo_writer.c -o fifo_writer
gcc fifo_reader.c -o fifo_reader

# 2. 先启动读进程(后台运行)
./fifo_reader &

# 3. 再启动写进程
./fifo_writer

# 输出:
【读进程】等待命名管道创建...
【写进程】成功创建命名管道:/tmp/my_fifo
【写进程】成功向管道写入 66 字节数据
【写进程】操作完成,已删除命名管道
【读进程】成功打开命名管道:/tmp/my_fifo
【读进程】读取到管道数据:Hello, 读进程!这是写进程通过命名管道发送的消息
【读进程】操作完成
3.消息队列(Message Queue)—— 按类型传递的异步通信
消息队列是 System V IPC 三大组件之一(另外两个是共享内存、信号量),本质是「内核维护的一个消息链表」,进程可以向队列中写入不同类型的消息,也可以从队列中读取指定类型的消息,实现异步通信。
  • 核心特性:
    • 异步通信:发送方写入消息后无需等待接收方读取,消息持久化在内核中,接收方按需读取。
    • 按类型传递:每个消息都有一个类型标识,接收方可按类型筛选读取,无需按写入顺序读取。
    • 无亲缘关系限制:支持任意进程通信(只要拥有相同的键值 key)。
    • 局限性:消息大小和队列总长度有内核限制,进程退出后消息队列仍存在(需手动删除,避免资源泄漏)。
  • 函数原型:
  • 获取一个当前未用的IPC的key

  • 获取消息队列ID

  • 发送和接受消息

  • 设置或者获取消息队列的相关属性

// 1. 创建/获取消息队列(成功返回消息队列 ID,失败返回 -1)
int msgget(key_t key, int msgflg);

// 2. 向消息队列发送消息(成功返回 0,失败返回 -1)
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

// 3. 从消息队列接收消息(成功返回读取的消息长度,失败返回 -1)
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

// 4. 控制消息队列(删除、获取状态,成功返回 0,失败返回 -1)
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • 关键说明:
    • key_t key:用于标识消息队列的唯一键值,通常通过 ftok() 生成。
    • 消息结构需自定义,必须以 long msg_type 开头(消息类型),后续为消息数据。
    • msgctl() 的 cmd 参数传入 IPC_RMID 可删除消息队列。
  • 实操示例:两个进程通过消息队列传递消息

自定义消息结构:

struct msg_buf {
    long msg_type;  // 消息类型(必须 >= 1)
    char msg_data[1024];  // 消息数据
};

发送进程:

  • 用 ftok() 生成 key。
  • 用 msgget() 创建 / 获取消息队列。
  • 填充消息结构(设置类型和数据),用 msgsnd() 发送消息。

接收进程:

  • 用相同的 key 获取消息队列。
  • 用 msgrcv() 按指定类型读取消息。

最后用 msgctl() 删除消息队列。

  • 示例:
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <sys/ipc.h>
#include <sys/msg.h>
 
 
//定义消息结构体
typedef struct _msg{
    long mtype; //接收者类型
    char mtext[100];//消息内容,可以有多个成员
}MSG;
long msg_type=10;
 
 
int main()
{
    //获取KEY值
    key_t key=ftok("./",1);
    
    //创建消息队列
    int msg_id=msgget(key,IPC_CREAT | 0777);
    
    pid_t son=fork();
    if(son==0){
        printf("子进程,ID为%d\n",getpid());
        while(1){
            MSG new_msg;
            new_msg.mtype=msg_type;
            scanf("%s",new_msg.mtext);
            msgsnd(msg_id,&new_msg,sizeof(MSG)-sizeof(long),0);
            if(strcmp(new_msg.mtext,"exit")==0){
                break;
            }
        }
    }
    else if(son>0){
        printf("父进程,ID为%d\n",getpid());
        while(1){
            MSG new_msg;
            msgrcv(msg_id,&new_msg,sizeof(MSG)-sizeof(long),msg_type,0);
            printf("from son:%s\n",new_msg.mtext);
            if(strcmp(new_msg.mtext,"exit")==0){
                break;
            }
        }
        wait(NULL);
    }
    else if(son<0){
        perror("fork failed\n");
    }
    
    return 0;
}
4.共享内存(Shared Memory)—— 最高效的大数据通信
共享内存是所有 IPC 方式中效率最高的一种,本质是「内核分配一块物理内存,映射到多个进程的虚拟地址空间」,进程直接访问该内存区域,无需数据拷贝(其他 IPC 方式均需内核中转,存在数据拷贝)。
  • 核心特性:
    • 高效性:无数据拷贝,进程直接读写内存,适合大数据量、高并发场景。
    • 无同步机制:共享内存本身不提供同步和互斥(多个进程同时读写会导致数据错乱),需配合信号量或互斥锁使用。
    • 无亲缘关系限制:支持任意进程通信(通过相同 key 关联)。
    • 局限性:进程退出后共享内存仍存在(需手动删除),内存大小受系统限制。
  • 函数原型:
  • 获取共享内存ID

  • 对共享内存进行映射,或者解除映射

  • 获取或者设置共享内存的相关属性

// 1. 创建/获取共享内存(成功返回共享内存 ID,失败返回 -1)
int shmget(key_t key, size_t size, int shmflg);

// 2. 将共享内存映射到进程虚拟地址空间(成功返回映射地址,失败返回 (void*)-1)
void *shmat(int shmid, const void *shmaddr, int shmflg);

// 3. 解除共享内存与进程的映射(成功返回 0,失败返回 -1)
int shmdt(const void *shmaddr);

// 4. 控制共享内存(删除、获取状态,成功返回 0,失败返回 -1)
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 核心流程:
  1. 进程 A 用 shmget() 创建共享内存(指定大小和权限)。
  2. 进程 A 用 shmat() 将共享内存映射到自身虚拟地址空间。
  3. 进程 B 用相同 key 获取共享内存,并用 shmat() 映射到自身地址空间。
  4. 进程 A/B 直接读写映射地址(共享内存),配合信号量实现同步。
  5. 通信完成后,用 shmdt() 解除映射,用 shmctl() 删除共享内存
  • 示例:
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
 
#define SCHEME_SIZE 1024
 
 
int main()
{
    //获取KEY值
    key_t key=ftok("./",1);
    
    //创建共享内存
    int shm_id=shmget(key,SCHEME_SIZE,IPC_CREAT | 0777);
    
    pid_t son=fork();
    if(son==0){
        printf("子进程,ID为%d\n",getpid());
        char *buf=(char *)shmat(shm_id,NULL,0);
        while(1){
            scanf("%s",buf);
            if(strcmp(buf,"exit")==0){
                break;
            }
        }
        shmdt(buf);
        shmctl(shm_id,IPC_RMID,NULL);
    }
    else if(son>0){
        printf("父进程,ID为%d\n",getpid());
        char *buf=(char *)shmat(shm_id,NULL,0);
        while(1){
            if(strlen(buf)>0){
                printf("from son:%s\n",buf);
                if(strcmp(buf,"exit")==0){
                    break;
                }
            }
            sleep(1);
        }
        shmdt(buf);
        shmctl(shm_id,IPC_RMID,NULL);
        wait(NULL);
    }
    else if(son<0){
        perror("fork failed\n");
        shmctl(shm_id,IPC_RMID,NULL);
    }
    
    return 0;
}
5.信号(Signal)—— 最简单的异步通知通信
信号是 Linux 进程间最基础的异步通知机制,用于「通知进程发生了某个事件」,进程可以对信号做出三种响应:默认处理、忽略、自定义处理。
  • 核心特性:
    • 异步性:信号可随时发送给进程,进程无需主动轮询,收到信号后中断当前执行流程,处理信号函数。
    • 携带信息少:仅传递信号编号,无法传递复杂数据(部分实时信号支持携带少量数据)。
    • 通用性强:支持所有进程通信,还可用于内核向进程发送通知(如段错误 SIGSEGV)。
  • 常用标准信号(共 31 个,无实时性):

  • 函数原型:
  • 向指定进程发送信号

  • 捕捉一个特定信号

  • 自己给自己发送一个指定信号

  • 将本进程挂起,直至收到一个信号

  • 信号集操作函数

  • 阻塞或者解除阻塞一个或多个信号

  • 给某进程发送一个指定的信号,同时携带一些数据

  • 捕捉一个指定信号,且可以通过扩展响应函数来获取信号携带的额外数据

// 1. 注册信号处理函数(设置信号的响应方式)
void (*signal(int sig, void (*handler)(int)))(int);

// 2. 向指定进程发送信号
int kill(pid_t pid, int sig);

// 3. 自定义信号处理函数(用于捕获信号后执行自定义逻辑)
void handler(int sig) {
    printf("收到信号:%d\n", sig);
}
  • 实操示例:父进程向子进程发送信号
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

// 子进程的信号处理函数
void sig_handler(int sig) {
    if (sig == SIGUSR1) {  // 自定义用户信号 1
        printf("【子进程】收到父进程发送的 SIGUSR1 信号(%d),即将退出\n", sig);
        exit(EXIT_SUCCESS);
    }
}

int main() {
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // 子进程:注册 SIGUSR1 信号的处理函数
        signal(SIGUSR1, sig_handler);
        printf("【子进程】PID:%d,等待父进程发送信号...\n", getpid());

        // 无限循环,等待信号
        while (1) {
            sleep(1);
        }
    } else {
        // 父进程:睡眠 3 秒后,向子进程发送 SIGUSR1 信号
        printf("【父进程】PID:%d,睡眠 3 秒后发送信号...\n", getpid());
        sleep(3);

        // 向子进程发送 SIGUSR1 信号
        if (kill(pid, SIGUSR1) == -1) {
            perror("kill failed");
        } else {
            printf("【父进程】成功向子进程 %d 发送 SIGUSR1 信号\n", pid);
        }

        // 等待子进程退出
        wait(NULL);
        printf("【父进程】子进程已退出\n");
    }

    return 0;
}
6.本地 Socket(AF_UNIX)—— 最通用的本地进程通信
Socket 不仅支持跨主机的网络通信(AF_INET),还支持本地进程间通信(AF_UNIX,也叫 AF_LOCAL),本质是「基于文件系统的套接字文件」,具有通用性强、支持双向通信、无数据大小限制等优点,是本地无亲缘关系进程通信的首选之一。
  • 核心特性:
    • 通用性强:API 与网络 Socket 一致,便于扩展为跨主机通信。
    • 双向通信:支持全双工通信,进程可同时读写。
    • 无亲缘关系限制:支持任意本地进程通信(通过套接字文件关联)。
    • 基于字节流 / 数据报:支持两种通信模式,灵活适配不同场景。
  • 核心流程(AF_UNIX 字节流模式):
    • 服务端:
      • 创建套接字:socket(AF_UNIX, SOCK_STREAM, 0)。
      • 绑定套接字文件:bind()(指定套接字文件路径)。
      • 监听连接:listen()。
      • 接受连接:accept()。
      • 读写数据:read()/write()。
    • 客户端:
      • 创建套接字:socket(AF_UNIX, SOCK_STREAM, 0)。
      • 连接服务端:connect()(指定相同的套接字文件路径)。
      • 读写数据:read()/write()。
    • 通信完成后,关闭套接字,删除套接字文件。
  • 示例:
  • 服务端:
void init_server()
{
    struct sockaddr_un addr_server, addr_client;
    int addrlen = sizeof(struct sockaddr_un);
 
 
    server_fd = socket( AF_UNIX, SOCK_STREAM, 0);
    if(server_fd<0)
        return;
    unlink("/var/run/unixsock");
    memset( &addr_server, 0, sizeof( addr_server ) );
    addr_server.sun_family = AF_UNIX;
    strncpy( addr_server.sun_path, "/var/run/unixsock", sizeof( addr_server.sun_path ) - 1 );
    if(bind( server_fd, ( struct sockaddr * )&addr_server, addrlen ) < 0)
        return;
    if(listen( server_fd, 10 ) < 0)
        return;
}
 
void recv_data()
{
    int clientfd;
    struct sockaddr_un addrclient;
    int addrlen = sizeof(struct sockaddr_un);
    char buf[1024]={0};
    int readlen;
    bool flag;
    while(1)
    {
        clientfd=accept( server_fd, ( struct sockaddr * )&addrclient,(socklen_t *)&addrlen );
        if(clientfd<0)
            return;
        flag=true;
        while(flag)
        {
            readlen=read(clientfd,buf,sizeof(buf));
            if(readlen<=0)
                break;
            qDebug().noquote()<<QString(buf);
            memset(buf,0,sizeof (buf));
        }
        close(clientfd);
    }
}
  • 客户端:
void init_client(void)
{
    struct stat buf;
    
    if(access(FILE_CLIENT,F_OK) < 0){
        printf("%s is not access\n",FILE_CLIENT);
        return;
    }
    
    if(stat(FILE_CLIENT,&buf) != 0){
        return;
    }
    if(!S_ISSOCK(buf.st_mode)){
        printf("%s is not a socket file\n",FILE_CLIENT);
        return;
    }
 
    client_fd = socket(AF_UNIX,SOCK_STREAM,0);
    if(client_fd < 0){
        printf("create socket failed\n");
        return;
    }
 
    struct sockaddr_un addr_server;
    int addrlen = sizeof(struct sockaddr_un);
    memset( &addr_server, 0, sizeof( addr_server ) );
    addr_server.sun_family = AF_UNIX;
    strncpy( addr_server.sun_path, FILE_CLIENT, sizeof( addr_server.sun_path ) - 1 );
    if(connect( client_fd, ( struct sockaddr * )&addr_server, addrlen ) < 0){
        printf("connect failed\n");
        return;
    }
    client_connect_flag = 1;
}
7.信号量(System V/Posix)
(1)获取信号量ID
(2)对信号量进行P/V操作,或者等零操作
信号量操作结构体的定义如下:
struct sembuf
{
    unsigned short sem_num; /*信号量元素序号(数组下标)*/
    short sem_op;/*操作参数*/
    short sem_flg;/*操作选项*/
};
请注意:信号量元素的序号从0开始,实际上就是数组下标。
根据sem_op的数值,信号量操作分成3种情况:

1.当sem_op大于0时:进行V操作,即信号量元素的值(semval)将会被加上sem_op 的值。如果SEM_UNDO被设置了,那么该V操作将会被系统记录。V操作永远不会导致进程阻塞。

2.当sem_op等于0时:进行等零操作,如果此时semval恰好为0,则semop()立即成功返回,否则如果IPC_NOWAIT被设置,则立即出错返回并将errno设置为EAGAIN,否则将使得进程进入睡眠,直到以下情况发生:

  • semval变为0。
  • 信号量被删除。(将导致semop()出错退出,错误码为EIDRM)
  • 收到信号。(将导致semop()出错退出,错误码为EINTR)

3.当sem_op小于0时:进行P操作,即信号量元素的值(semval)将会被减去sem_op 的绝对值。如果semval大于或等于sem_op的绝对值,则semop()立即成功返回,semval的值将减去sem_op的绝对值,并且如果SEM_UNDO被设置了,那么该P操作将会被系统记录。如果semval小于sem_ op的绝对值并且设置了IPC_NOWAIT,那么semop()将会出错返回且将错误码置为EAGAIN,否则将使得进程进入睡眠,直到以下情况发生:

  • semval的值变得大于或者等于sem_op的绝对值。
  • 信号量被删除。(将导致semop()出错退出,错误码为EIDRM)
  • 收到信号。(将导致semop()出错退出,错误码为EINTR)
(3)获取或者设置信号量的相关属性
8.各类 IPC 方式对比与选型建议
8.1.核心对比总结

 

8.2.选型建议
  • 简单父子进程通信:优先匿名管道(最简单)。
  • 无亲缘关系进程简单数据传递:优先命名管道(无需复杂 API)。
  • 无亲缘关系进程分类传递数据:优先消息队列(异步按类型传递)。
  • 高并发、大数据量传递(如视频、大数据):优先共享内存 + 信号量(最高效)。
  • 简单异步通知(如进程终止、异常通知):优先信号(最轻量)。
  • 本地进程复杂通信、需兼容跨主机扩展:优先本地 Socket(通用性最强)。
9.核心总结
  • Linux 进程间通信的核心是依赖内核中转或共享内核资源,突破进程地址空间的独立性限制。
  • 主流 IPC 分为管道类、消息类、共享内存类、信号类、Socket 类,各有优劣,需按场景选型。
  • 效率优先级:共享内存 > 信号 > 管道 / 命名管道 > 消息队列 > 本地 Socket。
  • 易用性优先级:匿名管道 > 信号 > 命名管道 > 消息队列 > 本地 Socket > 共享内存。
  • 生产环境中,共享内存必须配合信号量实现同步,避免数据竞争;所有 System V IPC 资源(消息队列、共享内存、信号量)使用后必须手动删除,避免资源泄漏。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐