当前位置: 首页 > news >正文

Linux进程间通信

Linux进程间通信

  • 进程间通信(IPC)详解
    • 进程间通信介绍
      • 进程间通信的概念
      • 进程间通信的目的
      • 进程间通信的本质
      • 进程间通信的分类
    • 管道
      • 什么是管道
    • 匿名管道
      • 匿名管道的原理
      • `pipe` 函数
      • 匿名管道使用步骤
      • 管道读写规则
      • 管道的特点
      • 管道的四种特殊情况
      • 管道的大小
    • 命名管道
      • 命名管道的原理
      • 使用命令创建命名管道
      • 创建命名管道(程序)
      • 命名管道的打开规则
      • 用命名管道实现 Server & Client 通信
      • 用命名管道实现派发计算任务
      • 用命名管道实现进程遥控
      • 用命名管道实现文件拷贝
      • 命名管道与匿名管道的区别
    • 命令行中的管道
  • System V 进程间通信详解
    • System V 进程间通信概述
    • System V 共享内存
      • 基本原理
      • 数据结构
      • 创建与释放
        • 创建共享内存
        • 释放共享内存
      • 关联与去关联
        • 关联共享内存
        • 去关联共享内存
      • 用共享内存实现 Server & Client 通信
      • 与管道的对比
    • System V 消息队列
      • 基本原理
      • 数据结构
      • 创建与释放
        • 创建消息队列
        • 释放消息队列
      • 发送与接收数据
        • 发送数据
        • 接收数据
    • System V 信号量
      • 相关概念
      • 数据结构
      • 相关函数
        • 创建信号量集
        • 删除信号量集
        • 操作信号量
    • System V IPC 的联系
    • 总结


进程间通信(IPC)详解

进程间通信介绍

进程间通信的概念

进程间通信(Interprocess Communication,简称IPC)是指在不同进程之间传播或交换信息的过程。由于进程之间具有独立性(尤其在数据层面),实现通信需要借助操作系统提供的机制。

进程间通信的目的

  • 数据传输:一个进程将数据发送给另一个进程。
  • 资源共享:多个进程共享同一资源。
  • 通知事件:一个进程向另一个或一组进程发送消息,通知某种事件发生(如进程终止时通知父进程)。
  • 进程控制:某些进程需要完全控制另一个进程的执行(如调试进程),包括拦截其异常并实时监控状态变化。

进程间通信的本质

进程间通信的本质是让不同进程访问同一份资源。这份资源通常由操作系统提供,可以是内存区域、文件内核缓冲区等。由于进程的数据独立性,通信必须依赖第三方资源,通过读写该资源实现数据交换。

进程间通信的分类

  • 管道
    • 匿名管道
    • 命名管道
  • System V IPC
    • 消息队列
    • 共享内存
    • 信号量
  • POSIX IPC
    • 消息队列
    • 共享内存
    • 信号量
    • 互斥量
    • 条件变量
    • 读写锁

管道

什么是管道

管道是Unix中最古老的进程间通信形式,指从一个进程到另一个进程的数据流。例如,使用who | wc -l命令统计云服务器登录用户数,其中who进程通过管道将数据传递给wc进程,完成数据传输和处理。


匿名管道

匿名管道的原理

匿名管道适用于本地父子进程间的通信。其核心原理是让父子进程共享同一份文件资源(由操作系统维护),通过对该文件的读写实现通信。
注意

  • 该文件资源是内存文件,不会将数据刷新到磁盘(避免IO开销)。
  • 父子进程操作该文件时,缓冲区数据不会触发写时拷贝。

pipe 函数

pipe 函数用于创建匿名管道,原型如下:

int pipe(int pipefd[2]);
  • 参数pipefd 是一个数组,返回两个文件描述符:
    • pipefd[0]:读端文件描述符
    • pipefd[1]:写端文件描述符
  • 返回值:成功返回0,失败返回-1。

匿名管道使用步骤

  1. 父进程调用 pipe 创建管道。
  2. 父进程通过 fork 创建子进程。
  3. 根据通信方向,关闭不必要的读写端(如父进程关闭写端,子进程关闭读端)。

示例代码(子进程写,父进程读):

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    int fd[2];
    if (pipe(fd) < 0) {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if (id == 0) { // 子进程
        close(fd[0]); // 关闭读端
        const char* msg = "hello father, I am child...\n";
        int count = 10;
        while (count--) {
            write(fd[1], msg, strlen(msg));
            sleep(1);
        }
        close(fd[1]);
        exit(0);
    }
    // 父进程
    close(fd[1]); // 关闭写端
    char buff[64];
    while (1) {
        ssize_t s = read(fd[0], buff, sizeof(buff) - 1);
        if (s > 0) {
            buff[s] = '\0';
            printf("child send to father: %s", buff);
        } else if (s == 0) {
            printf("read file end\n");
            break;
        } else {
            printf("read error\n");
            break;
        }
    }
    close(fd[0]);
    waitpid(id, NULL, 0);
    return 0;
}

管道读写规则

  • 无数据可读
    • 默认(阻塞):read 阻塞直到有数据。
    • O_NONBLOCKread 返回-1,errnoEAGAIN
  • 管道已满
    • 默认(阻塞):write 阻塞直到数据被读取。
    • O_NONBLOCKwrite 返回-1,errnoEAGAIN
  • 写端全关闭read 返回0。
  • 读端全关闭write 触发 SIGPIPE 信号,可能导致进程退出。
  • 写入数据量
    • 小于 PIPE_BUF:保证原子性。
    • 大于 PIPE_BUF:不保证原子性。

管道的特点

  1. 自带同步与互斥
    • 管道是临界资源,同一时刻只允许一个进程操作。
    • 同步:读写操作按序协调(如写后读)。
    • 互斥:多个进程不能同时访问。
  2. 生命周期随进程:所有相关进程退出后,管道资源被释放。
  3. 流式服务:数据无明确分段,读取长度任意。
  4. 半双工通信:数据单向流动,双向通信需创建两个管道。

管道的四种特殊情况

  1. 写端不写,读端一直读:读端挂起,直到管道有数据。
  2. 读端不读,写端一直写:管道满时写端挂起,直到数据被读取。
  3. 写端关闭,读端读完:读端继续执行后续逻辑,不挂起。
  4. 读端关闭,写端继续写:写端进程被操作系统杀掉,收到 SIGPIPE 信号。

验证第四种情况(代码略,结果显示收到信号13,即 SIGPIPE)。

管道的大小

管道容量有限,写满时会阻塞。测试方法:

  1. man 手册:Linux 2.6.11 后,默认容量为 65536 字节。
  2. ulimit -a:显示 4096 字节(512 × 8)。
  3. 代码测试:写端持续写入,读端不读,实测容量为 65536 字节。

命名管道

命名管道的原理

命名管道(FIFO)适用于无亲缘关系的进程间通信。它是一个特殊文件类型,通过文件名让不同进程访问同一资源。
注意

  • 命名管道是内存文件,磁盘映像大小恒为0。
  • 与普通文件不同,命名管道专为通信设计,安全性更高。

使用命令创建命名管道

使用 mkfifo 命令:

mkfifo fifo

创建后文件类型为 p,可用作通信媒介。

创建命名管道(程序)

使用 mkfifo 函数:

int mkfifo(const char *pathname, mode_t mode);
  • 参数
    • pathname:管道文件路径或名称。
    • mode:权限(如 0666,受 umask 影响)。
  • 返回值:成功返回0,失败返回-1。

示例

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FILE_NAME "myfifo"

int main() {
    umask(0);
    if (mkfifo(FILE_NAME, 0666) < 0) {
        perror("mkfifo");
        return 1;
    }
    return 0;
}

命名管道的打开规则

  • 读打开
    • 默认(阻塞):等待写端打开。
    • O_NONBLOCK:立即返回成功。
  • 写打开
    • 默认(阻塞):等待读端打开。
    • O_NONBLOCK:立即返回失败(ENXIO)。

用命名管道实现 Server & Client 通信

服务端(创建并读取管道):

#include "comm.h"
int main() {
    umask(0);
    if (mkfifo(FILE_NAME, 0666) < 0) {
        perror("mkfifo");
        return 1;
    }
    int fd = open(FILE_NAME, O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 2;
    }
    char msg[128];
    while (1) {
        ssize_t s = read(fd, msg, sizeof(msg) - 1);
        if (s > 0) {
            msg[s] = '\0';
            printf("client# %s\n", msg);
        } else if (s == 0) {
            printf("client quit!\n");
            break;
        } else {
            printf("read error!\n");
            break;
        }
    }
    close(fd);
    return 0;
}

客户端(写入管道):

#include "comm.h"
int main() {
    int fd = open(FILE_NAME, O_WRONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    char msg[128];
    while (1) {
        printf("Please Enter# ");
        fflush(stdout);
        ssize_t s = read(0, msg, sizeof(msg) - 1);
        if (s > 0) {
            msg[s - 1] = '\0';
            write(fd, msg, strlen(msg));
        }
    }
    close(fd);
    return 0;
}

头文件comm.h):

#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define FILE_NAME "myfifo"

运行:先启动服务端,再启动客户端,验证通信成功。

用命名管道实现派发计算任务

服务端接收客户端发送的计算请求(如 3+5),计算并输出结果:

#include "comm.h"
int main() {
    umask(0);
    if (mkfifo(FILE_NAME, 0666) < 0) {
        perror("mkfifo");
        return 1;
    }
    int fd = open(FILE_NAME, O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 2;
    }
    char msg[128];
    while (1) {
        ssize_t s = read(fd, msg, sizeof(msg) - 1);
        if (s > 0) {
            msg[s] = '\0';
            printf("client# %s\n", msg);
            char* lable = "+-*/%";
            int flag = 0;
            for (char* p = msg; *p; p++) {
                if (*p == '+') flag = 0;
                else if (*p == '-') flag = 1;
                else if (*p == '*') flag = 2;
                else if (*p == '/') flag = 3;
                else if (*p == '%') flag = 4;
            }
            char* data1 = strtok(msg, "+-*/%");
            char* data2 = strtok(NULL, "+-*/%");
            int num1 = atoi(data1), num2 = atoi(data2), ret = 0;
            switch (flag) {
                case 0: ret = num1 + num2; break;
                case 1: ret = num1 - num2; break;
                case 2: ret = num1 * num2; break;
                case 3: ret = num1 / num2; break;
                case 4: ret = num1 % num2; break;
            }
            printf("%d %c %d = %d\n", num1, lable[flag], num2, ret);
        } else if (s == 0) {
            printf("client quit!\n");
            break;
        } else {
            printf("read error!\n");
            break;
        }
    }
    close(fd);
    return 0;
}

用命名管道实现进程遥控

客户端发送命令(如 ls),服务端执行:

#include "comm.h"
int main() {
    umask(0);
    if (mkfifo(FILE_NAME, 0666) < 0) {
        perror("mkfifo");
        return 1;
    }
    int fd = open(FILE_NAME, O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 2;
    }
    char msg[128];
    while (1) {
        ssize_t s = read(fd, msg, sizeof(msg) - 1);
        if (s > 0) {
            msg[s] = '\0';
            printf("client# %s\n", msg);
            if (fork() == 0) {
                execlp(msg, msg, NULL);
                exit(1);
            }
            waitpid(-1, NULL, 0);
        } else if (s == 0) {
            printf("client quit!\n");
            break;
        } else {
            printf("read error!\n");
            break;
        }
    }
    close(fd);
    return 0;
}

用命名管道实现文件拷贝

服务端(读取管道,写入新文件):

#include "comm.h"
int main() {
    umask(0);
    if (mkfifo(FILE_NAME, 0666) < 0) {
        perror("mkfifo");
        return 1;
    }
    int fd = open(FILE_NAME, O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 2;
    }
    int fdout = open("file-bat.txt", O_CREAT | O_WRONLY, 0666);
    if (fdout < 0) {
        perror("open");
        return 3;
    }
    char msg[128];
    while (1) {
        ssize_t s = read(fd, msg, sizeof(msg) - 1);
        if (s > 0) {
            write(fdout, msg, s);
        } else if (s == 0) {
            printf("client quit!\n");
            break;
        } else {
            printf("read error!\n");
            break;
        }
    }
    close(fd);
    close(fdout);
    return 0;
}

客户端(读取文件,写入管道):

#include "comm.h"
int main() {
    int fd = open(FILE_NAME, O_WRONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    int fdin = open("file.txt", O_RDONLY);
    if (fdin < 0) {
        perror("open");
        return 2;
    }
    char msg[128];
    while (1) {
        ssize_t s = read(fdin, msg, sizeof(msg));
        if (s > 0) {
            write(fd, msg, s);
        } else if (s == 0) {
            printf("read end of file!\n");
            break;
        } else {
            printf("read error!\n");
            break;
        }
    }
    close(fd);
    close(fdin);
    return 0;
}

意义:本地拷贝看似简单,但若将管道视为网络,可实现文件上传/下载功能。

命名管道与匿名管道的区别

  • 创建与打开
    • 匿名管道:pipe 函数创建并打开。
    • 命名管道:mkfifo 创建,open 打开。
  • 语义:创建与打开方式不同,其余操作一致。

命令行中的管道

命令行管道(|)如 cat data.txt | grep dragon,连接的进程(如 catgrep)由同一父进程(bash)创建,互为兄弟进程,具有亲缘关系,且无磁盘上的命名管道文件,因此是匿名管道


System V 进程间通信详解

我们详细探讨了进程间通信(IPC)的概念、目的、本质以及基于管道的实现方式,包括匿名管道和命名管道。从本质上看,管道通信依赖于文件系统,操作系统并未对其进行过多专门设计。而 System V IPC 则是操作系统针对进程间通信特别设计的一套机制,尽管其本质仍是通过让不同进程看到同一份资源来实现通信,但它提供了更高效、更灵活的方式。本篇博客将深入剖析 System V IPC 的三种主要形式:共享内存消息队列信号量,并探讨它们之间的联系与应用。


System V 进程间通信概述

System V IPC 是 Unix System V 体系中定义的一组进程间通信机制,包括以下三种核心方式:

  1. System V 共享内存:允许不同进程直接访问同一块物理内存,实现高效的数据共享。
  2. System V 消息队列:通过队列结构在进程间传递带有类型的数据块。
  3. System V 信号量:用于协调进程间的同步与互斥,确保临界资源的安全访问。

其中,共享内存和消息队列以数据传输为主要目的,而信号量则专注于进程间的协调。虽然信号量看似与通信无直接关联,但它是保障共享资源访问秩序的重要工具,因此也被归入 IPC 范畴。

我们可以将这三者比喻为现实生活中的场景:

  • 共享内存和消息队列 好比手机,用于直接传递信息。
  • 信号量 则像下棋时的棋钟,确保双方按规则轮流行动。

与管道通信不同,System V IPC 的生命周期随内核而非进程。这意味着,即使创建 IPC 资源的进程退出,资源也不会自动释放,必须显式删除或等待系统重启。这种特性体现了 IPC 资源由内核管理和维护的特点。


System V 共享内存

基本原理

共享内存是 System V IPC 中最直接高效的通信方式。其核心思想是:在物理内存中分配一块空间,并通过操作系统的映射机制,将这块内存分别映射到多个进程的虚拟地址空间。进程通过虚拟地址访问这块共享内存,从而实现数据共享。

具体过程如下:

  1. 操作系统在物理内存中开辟一块共享内存区域。
  2. 通过页表映射,将共享内存挂接到各个进程的地址空间。
  3. 进程通过虚拟地址操作共享内存,实际访问的是同一块物理内存。

这种方式避免了管道通信中频繁的数据拷贝,使得共享内存成为所有 IPC 方式中速度最快的。

数据结构

系统中可能存在多个共享内存实例,操作系统通过内核数据结构对其进行管理。共享内存的核心数据结构是 shmid_ds,定义如下(位于 /usr/include/linux/shm.h):

struct shmid_ds {
    struct ipc_perm shm_perm;   /* 操作权限 */
    int shm_segsz;              /* 共享内存大小(字节) */
    __kernel_time_t shm_atime;  /* 最后关联时间 */
    __kernel_time_t shm_dtime;  /* 最后去关联时间 */
    __kernel_time_t shm_ctime;  /* 最后修改时间 */
    __kernel_ipc_pid_t shm_cpid; /* 创建者 PID */
    __kernel_ipc_pid_t shm_lpid; /* 最后操作者 PID */
    unsigned short shm_nattch;  /* 当前关联进程数 */
    /* 其他未使用字段略 */
};

其中,shm_permipc_perm 类型结构体,用于存储共享内存的唯一标识 key 值,定义如下(位于 /usr/include/linux/ipc.h):

struct ipc_perm {
    __kernel_key_t key;     /* IPC 键值 */
    __kernel_uid_t uid;     /* 拥有者 UID */
    __kernel_gid_t gid;     /* 拥有者 GID */
    __kernel_uid_t cuid;    /* 创建者 UID */
    __kernel_gid_t cgid;    /* 创建者 GID */
    __kernel_mode_t mode;   /* 权限模式 */
    unsigned short seq;     /* 序列号 */
};

key 值是共享内存的全局唯一标识,确保不同进程能找到同一块共享内存。

创建与释放

创建共享内存

创建共享内存使用 shmget 函数:

int shmget(key_t key, size_t size, int shmflg);
  • key:共享内存的唯一标识,通常由 ftok 函数生成。
  • size:共享内存的大小(字节)。
  • shmflg:创建标志,常用组合包括:
    • IPC_CREAT:若不存在指定 key 的共享内存则创建,否则返回已有句柄。
    • IPC_CREAT | IPC_EXCL:确保创建全新共享内存,若已存在则报错。
  • 返回值:成功返回共享内存句柄(shmid),失败返回 -1。

ftok 函数用于生成 key 值:

key_t ftok(const char *pathname, int proj_id);
  • pathname:已有文件的路径名。
  • proj_id:整数标识符。
  • 注意:不同进程需使用相同参数以生成相同 key

示例代码:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

#define PATHNAME "/tmp"
#define PROJ_ID 0x6666
#define SIZE 4096

int main() {
    key_t key = ftok(PATHNAME, PROJ_ID);
    if (key < 0) {
        perror("ftok");
        return 1;
    }
    int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0) {
        perror("shmget");
        return 2;
    }
    printf("key: %x, shmid: %d\n", key, shmid);
    return 0;
}

运行后,可用 ipcs -m 查看共享内存信息。

释放共享内存

释放共享内存使用 shmctl 函数:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • shmid:共享内存句柄。
  • cmd:控制命令,常用 IPC_RMID 表示删除。
  • buf:通常设为 NULL
  • 返回值:成功返回 0,失败返回 -1。

示例:在创建后释放共享内存:

shmctl(shmid, IPC_RMID, NULL);

或使用命令行:ipcrm -m shmid

关联与去关联

关联共享内存

将共享内存映射到进程地址空间使用 shmat 函数:

void *shmat(int shmid, const void *shmaddr, int shmflg);
  • shmid:共享内存句柄。
  • shmaddr:映射地址,通常设为 NULL 由内核自动选择。
  • shmflg:标志,如 SHM_RDONLY(只读)。
  • 返回值:成功返回映射地址,失败返回 (void*)-1
去关联共享内存

取消映射使用 shmdt 函数:

int shmdt(const void *shmaddr);
  • shmaddrshmat 返回的地址。
  • 返回值:成功返回 0,失败返回 -1。

示例:关联并去关联共享内存:

char *mem = shmat(shmid, NULL, 0);
if (mem == (void*)-1) {
    perror("shmat");
    return 1;
}
printf("Attached at %p\n", mem);
sleep(2);
shmdt(mem);

用共享内存实现 Server & Client 通信

以下是一个简单的 Server 和 Client 通过共享内存通信的实现:

头文件 comm.h

#ifndef _COMM_H_
#define _COMM_H_

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

#define PATHNAME "/tmp"
#define PROJ_ID 0x6666
#define SIZE 4096

#endif

服务端 server.c

#include "comm.h"

int main() {
    key_t key = ftok(PATHNAME, PROJ_ID);
    int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0) {
        perror("shmget");
        return 1;
    }
    char *mem = shmat(shmid, NULL, 0);
    while (1) {
        printf("Client sent: %s\n", mem);
        sleep(1);
    }
    shmdt(mem);
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

客户端 client.c

#include "comm.h"

int main() {
    key_t key = ftok(PATHNAME, PROJ_ID);
    int shmid = shmget(key, SIZE, IPC_CREAT);
    char *mem = shmat(shmid, NULL, 0);
    int i = 0;
    while (1) {
        mem[i] = 'A' + i;
        i++;
        mem[i] = '\0';
        sleep(1);
    }
    shmdt(mem);
    return 0;
}

运行结果:服务端持续输出客户端写入的字符串,证明通信成功。

与管道的对比

管道通信需要多次数据拷贝(通常四次:用户态到内核态再到用户态),而共享内存只需两次拷贝(输入到共享内存,共享内存到输出),因此速度更快。然而,管道自带同步与互斥机制,而共享内存缺乏保护机制,需配合信号量使用。


System V 消息队列

基本原理

消息队列是一个由操作系统维护的队列,每个队列元素是一个数据块,包含类型(mtype)和数据(mtext)。进程通过队列尾添加数据块,从队列头读取数据块。数据块的类型决定其接收者。

特点:

  • 支持按类型接收数据。
  • 生命周期随内核,需手动删除。

数据结构

消息队列的核心数据结构是 msqid_ds(位于 /usr/include/linux/msg.h):

struct msqid_ds {
    struct ipc_perm msg_perm;   /* 权限 */
    struct msg *msg_first;      /* 队列首消息 */
    struct msg *msg_last;       /* 队列尾消息 */
    __kernel_time_t msg_stime;  /* 最后发送时间 */
    __kernel_time_t msg_rtime;  /* 最后接收时间 */
    __kernel_time_t msg_ctime;  /* 最后修改时间 */
    unsigned short msg_qnum;    /* 队列中消息数 */
    unsigned short msg_qbytes;  /* 队列最大字节数 */
    __kernel_ipc_pid_t msg_lspid; /* 最后发送者 PID */
    __kernel_ipc_pid_t msg_lrpid; /* 最后接收者 PID */
};

msg_perm 同为 ipc_perm 类型,包含 key 值。

创建与释放

创建消息队列

使用 msgget 函数:

int msgget(key_t key, int msgflg);
  • 参数与 shmget 类似。
  • 返回值:成功返回消息队列句柄(msqid)。
释放消息队列

使用 msgctl 函数:

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • cmd:如 IPC_RMID

发送与接收数据

发送数据

使用 msgsnd 函数:

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • msgp:指向 msgbuf 结构:
struct msgbuf {
    long mtype;    /* 消息类型,大于 0 */
    char mtext[1]; /* 消息数据,可自定义大小 */
};
接收数据

使用 msgrcv 函数:

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  • msgtyp:指定接收的消息类型。

示例:略(可参考管道通信模式实现)。


System V 信号量

相关概念

信号量是一个计数器,用于保护临界资源,确保进程互斥或同步访问。分为:

  • 二元信号量:值为 0 或 1,用于互斥。
  • 多元信号量:值大于 1,表示资源份数。

操作包括:

  • P 操作:申请资源(计数器减 1)。
  • V 操作:释放资源(计数器加 1)。

数据结构

信号量数据结构为 semid_ds(位于 /usr/include/linux/sem.h):

struct semid_ds {
    struct ipc_perm sem_perm;   /* 权限 */
    __kernel_time_t sem_otime;  /* 最后操作时间 */
    __kernel_time_t sem_ctime;  /* 最后修改时间 */
    struct sem *sem_base;       /* 信号量数组指针 */
    struct sem_queue *sem_pending; /* 待处理操作 */
    unsigned short sem_nsems;   /* 信号量个数 */
};

相关函数

创建信号量集
int semget(key_t key, int nsems, int semflg);
  • nsems:信号量个数。
删除信号量集
int semctl(int semid, int semnum, int cmd, ...);
操作信号量
int semop(int semid, struct sembuf *sops, unsigned nsops);
  • sops:操作结构体数组。

System V IPC 的联系

共享内存、消息队列和信号量的数据结构都以 ipc_perm 作为首个成员。这种设计便于内核统一管理所有 IPC 资源。通过一个 ipc_perm 数组,内核可以快速定位并操作任意 IPC 资源。这种切片式管理提高了效率和一致性。


总结

System V IPC 提供了比管道更灵活和高效的通信方式:

  • 共享内存 速度最快,但需自行实现同步。
  • 消息队列 支持类型化数据传输,适合结构化通信。
  • 信号量 保障资源访问秩序,是其他 IPC 方式的必要补充。

相关文章:

  • 《Java到Go的平滑转型指南》
  • HTML CSS JS官方文档
  • Camera2 实现重力感应四个方向调试相机预览
  • [学习笔记] VM虚拟机安装Ubuntu系统
  • SpringMVC_day02
  • 【清华大学】AIGC发展研究(3.0版)
  • ROS导航工具包Navigation
  • Scikit-learn模型评估全流程解析:从数据划分到交叉验证优化
  • Java-模块二-2
  • 【MySQL】从零开始:掌握MySQL数据库的核心概念(三)
  • 音视频学习(三十):fmp4
  • 深入浅出JVM性能优化:从理论到实践
  • string kmp java
  • 高频SQL50题 第四天 | 1251. 平均售价、620. 有趣的电影、1075. 项目员工 I、1633. 各赛事的用户注册率
  • Ubuntu修改Swap交换空间大小
  • 2.创建Collection、添加索引、加载内存、预览和搜索数据
  • 【动态规划】按摩师
  • 蓝桥杯嵌入式备赛记录—CubeMX配置
  • 传统行业的思维惯性之困:评论列表
  • Linux系统中Crontab的用法详解
  • 中国首艘海洋级智能科考船“同济”号试航成功,可搭载水下遥控机器人
  • 女子七年后才知银行卡被盗刷18万元,警方抓获其前男友
  • 马上评|清理“滥竽充数者”,为医者正名
  • 中日东三省问题的源起——《1905年东三省事宜谈判笔记》解题
  • 秘鲁总统任命前司法部长阿拉纳为新总理
  • 山东:小伙为救同学耽误考试属实,启用副题安排考试