30天速通Linux 第七章进程间通信

学习资料https://pan.baidu.com/s/1NZpC4NyH0OHGOPPbO9J10g?pwd=h9m2 提取码: h9m2

一. 进程间通信概述

1.1 五大作用

数据传输/共享数据/通知事件/资源共享/进程控制(如 GDB 控制被调试进程)。

1.2 六种方式

方式 特点
管道 无名管道限亲缘进程;命名管道(FIFO)可任意进程
信号 只通知事件,不能传数据
消息队列 有格式 + 有类型,可无亲缘关系
共享内存 最快,直接访问内存无复制;需配合信号量
信号量 同步与互斥,不传数据
套接字 可跨机器

来源:AT&T → System V IPC (单机);BSD → socket;另有 POSIX IPC。Linux 全部继承。

二. 管道通信

  • 逻辑上一段管道:一端写、一端读,按写入顺序读出(FIFO 字节流)。
  • 单工单向 :方向一旦确定不能改,双向通信需两个管道。
  • 管道存在内存 中(不是磁盘);ps -aux | grep httpd 就是管道实例。
  • 无名管道 vs 命名管道:前者只能在父子进程间、无名字;后者有名字、任意进程可用、进程退出后仍存在。

2.1 无名管道 pipe

c 复制代码
#include <unistd.h>
int pipe(int pipe[2]);    // pipe[0] 读端,pipe[1] 写端;成功 0,失败 -1
c 复制代码
int mypipe[2], fd;
if (pipe(mypipe) < 0) { perror("pipe failed"); exit(0); }
if ((fd = fork()) < 0) { perror("fork failed"); exit(0); }

if (fd == 0) {                                  // 子进程:读 → 倒序 → 写回
    read(mypipe[0], recbuf, strlen(sendbuf));
    reverse(recbuf);
    write(mypipe[1], recbuf, strlen(recbuf));
}
if (fd > 0) {                                   // 父进程:写 → 等 → 读
    write(mypipe[1], sendbuf, strlen(sendbuf));
    sleep(10);                                  // 等子进程把数据取走
    read(mypipe[0], parrecbuf, strlen(sendbuf));
    wait();
}
  • fd 按最低可用 分配:0/1/2 已占 → pipe[0]=3、pipe[1]=4。
  • pipe 后两端都连父进程;fork 后父子共享该管道。

2.2 重定向 dup / dup2

c 复制代码
newfd = dup(oldfd);            // 复制描述符(按最低可用返回新号)
newfd = dup2(oldfd, newfd);    // 等价 close(newfd); dup(oldfd);

实现 ps -aux | grep init

c 复制代码
pipe(mypipe);
pid = fork();
if (pid == 0) {                       // 子进程:标准输入 ← 管道读端
    close(mypipe[1]);
    dup2(mypipe[0], 0);
    close(mypipe[0]);
    execlp("grep", "grep", "init", NULL);
} else {                              // 父进程:标准输出 → 管道写端
    close(mypipe[0]);
    dup2(mypipe[1], 1);
    close(mypipe[1]);
    execlp("ps", "ps", "aux", NULL);
}

分析技巧:oldfd1=3, oldfd2=4,dup(oldfd1)=5;dup2(oldfd1, 0) 先关 0 再复制 → newfd2 = 0(不是 6)。

2.3 popen

c 复制代码
FILE *popen(const char *command, const char *mode);   // mode: "r" 读结果 / "w" 写数据
int   pclose(FILE *stream);
c 复制代码
readfp  = popen("ps aux", "r");
writefp = popen("grep init", "w");
while (fgets(buf, 99, readfp) != NULL)
    fputs(buf, writefp);
pclose(readfp);  pclose(writefp);   // ⚠ 必须 pclose,否则产生僵尸进程

内部即:创建管道 → fork → dup2+close → 子进程 exec。

2.4 命名管道 FIFO

c 复制代码
int mkfifo(const char *filename, mode_t mode);        // 命令:mkfifo mypipe;ls -l 显示为 p
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);

打开规则

打开方式 默认(阻塞) O_NONBLOCK
以读打开 阻塞到有进程以写打开 立刻成功
以写打开 阻塞到有进程以读打开 立刻失败,ENXIO
  • 一般不同时读写 (不用 O_RDWR);双向传数据用一对 FIFO。
  • 写端在管道满时阻塞,读端在管道空时阻塞。
  • 典型用法:读写两个独立程序 open("/home/mypipe", O_RDONLY/O_WRONLY),先启动的阻塞在 open,另一个打开后双方同时返回。

三. System V 信号量

3.1 四个步骤

semget 创建/获取 → semctl(SETVAL) 初始化 → semop 做 P/V → semctl(IPC_RMID) 删除。

函数 原型 要点
semget int semget(key_t key, int nsems, int semflg); key 多进程共用同一信号量(IPC_PRIVATE 为私有);semflg 用 `0666
semctl int semctl(int semid, int semnum, int cmd, union semun arg); cmd:SETVAL 设初值/GETVAL 取值/IPC_STAT/IPC_RMID 删除
semop int semop(int semid, struct sembuf *sops, size_t nsops); sem_op = -1 是 P ,+1 是 V ;sem_flg 用 SEM_UNDO
c 复制代码
union semun { int val; struct semid_ds *buf; unsigned short *array; };  // 常需自己定义

struct sembuf { short sem_num; short sem_op; short sem_flg; };

临界资源 = 同时只允许有限个进程访问的资源;访问它的代码叫临界区。

3.2 封装好的 P/V 库

c 复制代码
int sem_p(int sem_id) {   // P 操作
    struct sembuf sem_b = {0, -1, SEM_UNDO};
    if (semop(sem_id, &sem_b, 1) == -1) { perror("p operation failed"); return -1; }
    return 0;
}
int sem_v(int sem_id) {   // V 操作
    struct sembuf sem_b = {0, 1, SEM_UNDO};
    if (semop(sem_id, &sem_b, 1) == -1) { perror("v operation failed"); return -1; }
    return 0;
}

控制父子进程执行顺序(信号量初值 0)

c 复制代码
sem_id = semget(ftok(".", 'a'), 1, 0666 | IPC_CREAT);   // ftok 由文件 i 节点生成 key
init_sem(sem_id, 0);
result = fork();
if (result == 0) {                 // 子进程先跑
    sleep(DELAY_TIME);
    printf("BBBBBBBBBBBB\n");
    sem_v(sem_id);                 // V:值 0 → 唤醒父进程
} else {
    sem_p(sem_id);                 // P:值变 -1,父进程阻塞
    printf("AAAAAAAAAAA\n");
    del_sem(sem_id);
}

四. POSIX 有名信号量

把信号量值保存在文件 里(通常在 /dev/shm),可用于毫无关系的进程 间同步。头文件 <semaphore.h>,编译加 -lrt 或 -lpthread。

函数 作用
sem_t *sem_open(const char *name, int oflag, mode_t mode, int value); 创建/打开,O_CREAT(已存在则报错用 `
int sem_wait(sem_t *sem); P:值为 0 则阻塞,否则减 1
int sem_trywait(sem_t *sem); P 的非阻塞版,值为 0 返回 EAGAIN
int sem_post(sem_t *sem); V:加 1 并唤醒一个等待者(原子操作)
int sem_close(sem_t *sem); / int sem_unlink(const char *name); 关闭/删除(引用数为 0 才真正删)
c 复制代码
mutex  = sem_open("process1", O_CREAT, 0777, 0);   // A 进程先等
mutex2 = sem_open("process2", O_CREAT, 0777, 1);   // B 进程先跑

// A 进程(写 ABC)              // B 进程(写 DEF)
sem_wait(mutex);                  sem_wait(mutex2);
write(fd, "ABC\n", 3);            write(fd, "DEF\n", 3);
sleep(1);                         sleep(1);
sem_post(mutex2);                 sem_post(mutex);

五. 共享内存

最快的 IPC :直接映射到进程虚拟地址空间,省去用户态/内核态之间的数据复制。

  • 三步:shmget 创建 → shmat 映射 → shmdt 撤消映射(+ shmctl 控制)。
  • ⚠ 本身不提供同步保护 ,必须配信号量。fork 后子进程继承父进程的共享内存。
函数 原型 要点
shmget int shmget(key_t key, int size, int shmflg); IPC_PRIVATE 私有;返回共享内存 id
shmat char *shmat(int shmid, const void *shmaddr, int shmflg); shmaddr=0 由系统分配;SHM_RDONLY 只读;返回映射地址
shmdt int shmdt(const void *shmaddr); 解除映射
shmctl int shmctl(int shmid, int cmd, struct shmid_ds *buf); IPC_RMID 删除/IPC_STAT/IPC_SET

父子进程通过共享内存通信

c 复制代码
shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT | 0600);
pid = fork();
if (pid == 0) {                                    // 子写
    shmaddr = (char *)shmat(shmid, NULL, 0);
    strcpy(shmaddr, "Hello World!\n");
    shmdt(shmaddr);
} else if (pid > 0) {                              // 父读
    sleep(10);
    shmaddr = (char *)shmat(shmid, NULL, 0);
    strcpy(buff, shmaddr);
    printf("I have got from shared memory :%s\n", buff);
    shmdt(shmaddr);
    shmctl(shmid, IPC_RMID, NULL);
}

5.1 mmap 共享内存

映射同一个普通文件 实现共享,之后像访问内存一样访问文件,不必 read/write。

c 复制代码
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
int   munmap(void *addr, size_t len);
int   msync(void *addr, size_t len, int flags);
参数 取值
prot PROT_READ/PROT_WRITE/PROT_EXEC/PROT_NONE(可或)
flags MAP_SHARED(改动写回文件)/MAP_PRIVATE/MAP_FIXED;fd=-1 + MAP_ANON 为匿名映射(只限亲缘进程)
msync 的 flags MS_ASYNC 异步/MS_SYNC 同步/MS_INVALIDATE 使其他映射失效
c 复制代码
fd = open("stu.txt", O_CREAT | O_RDWR | O_TRUNC, 00777);
lseek(fd, sizeof(student) * 5 - 1, SEEK_SET);  write(fd, "", 1);   // 先把文件撑大
student *p_map = (student *)mmap(NULL, sizeof(student) * 10,
                    PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
(*(p_map + i)).age = 20 + i;                  // 像数组一样访问
munmap(p_map, sizeof(student) * 10);

六. 消息队列

消息的链表,消息有格式 和类型(管道没有这两点),可无亲缘关系通信。

  • 特性:发送方不必等待 接收方(松耦合);接收可按类型从中间取 ,不必从头;随内核持续(重启或命令删除才消失)。
  • 流程:msgget 创建 → msgsnd 发送 → msgrcv 接收 → msgctl(IPC_RMID) 删除。
函数 原型 要点
msgget int msgget(key_t key, int msgflg); IPC_CREAT/IPC_EXCL;返回队列 id
msgsnd int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg); msgsz 不含 4 字节消息类型;msgflg=0 队满挂起,IPC_NOWAIT 立即返回
msgrcv int msgrcv(int msgid, void *msgp, size_t msgsz, long mtype, int msgflg); mtype=0 取驻留最久的那条;读完该消息出队
msgctl int msgctl(int msgid, int cmd, struct msqid_ds *buf); IPC_RMID(等价 ipcrm -q id)/IPC_STAT/IPC_SET
c 复制代码
struct my_msg_st {
    long my_msg_type;      // 消息类型 1/2/3,必须 long 开头
    char text[512];        // 消息内容
};

some_data.my_msg_type = rand() % 3 + 1;                  // 随机类型
msgsnd(msgid, (void *)&some_data, sizeof(some_data), 0);

msgrcv(msgid, (void *)my_data, sizeof(struct my_msg_st), (long)type, IPC_NOWAIT);

ipcs -q 查看队列,ipcrm -q <id> 删除。

七. 几种方式怎么选

方式 适用
共享内存 数据量最大、要求最快;耦合性强
信号量 只做同步/互斥,不传数据
消息队列 无亲缘关系、要传多种类型的大量数据
无名管道 仅父子进程
命名管道 任意进程传数据(特殊文件)

常组合使用:共享内存 + 信号量,实现既快又安全的并发访问。

相关推荐
emo_2b1 小时前
Pycharm配置conda解释器时遇到的问题及解决方法
linux·pycharm·conda
科技研学社1 小时前
中小服装工装厂自动化改造:产线节拍平衡与工位减负实施方案
运维·自动化
极光通讯1 小时前
服务器内存替代料选型方法论:主料号缺货时如何找到Pin-to-Pin兼容型号
运维·服务器
草根大哥1 小时前
08-横向扩容与突发高并发:加机器到底买到了什么
运维·webrtc·srt·横向扩容·whip·自建cdn
人生百态,人生如梦2 小时前
Ubuntu在低版本下使用ccswitch+claudecode(CCSwitch+Docker+Claude)连调教程
linux·ubuntu·docker·claudecode·ccswitch
oicola2 小时前
【运维】CI/CD是什么,合格的规范应该怎么设置
运维·ci/cd
Dachui_11222 小时前
内网穿透如何限制访问地区?ZeroNews Geo Location 区域访问控制实践
运维·网络安全·内网穿透·访问控制·远程办公·api安全·ip白名单
byte轻骑兵2 小时前
【BlueZ】hciconfig 工具:蓝牙适配器管理实操 + 源码实现基础
linux·bluez·电脑蓝牙·嵌入式蓝牙
SKH.2 小时前
Linux驱动-系统移植
linux·服务器