【Linux】进程通信

进程通信

进程间通信是指 两个或多个进程之间交换数据或同步操作的机制。由于每个进程有独立的地址空间,不能直接访问彼此的内存,所以需要通过操作系统提供的机制进行通信。

以下场景就需要进程通信:

数据传输:一个进程需要将它的数据发送给另一个进程

资源共享:多个进程之间共享同样资源

通知事件:一个进程向其他进程发送消息,告知发生了某个事件

主要通信机制分类:

管道、System V

管道

一个进程连接到另一个进程的数据流称为一个 "管道",也就是一个命令的标准输出直接作为下一个命令的标准输入,无需中间文件。

bash 复制代码
# 查看进程并筛选
ps aux | grep python

# 对文本去重、计数并取前 10 行。
cat data.txt | sort | uniq -c | head -n 10

匿名管道

没有名字,不能通过文件系统路径访问,数据只能从一端写入,另一端读出。

bash 复制代码
int pipe(int fd[2]);
fd: 文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码

数据流向:fd[1] → 内核缓冲区 → 从 fd[0] 读出

案例,父进程向子进程发送一条消息:

bash 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main()
{
	int fd[2];
	pid_t pid;
	char buffer[128];
	// 1.创建匿名管道
	if (pipe(fd) == -1)
	{
		perror("pipe");
		return 1;
	}
	// 2.创建子进程
	pid = fork();
	if (pid == -1)
	{
		perror("fork");
		return 1;
	}
	if (pid == 0)
	{
		// 子进程从管道读取
		close(fd[1]); // 关闭写端, 避免浪费
		read(fd[0], buffer, sizeof(buffer));
		printf("child received: %s\n", buffer);
		close(fd[0]);
	}
	else
	{
		// 父进程向管道写
		close(fd[0]); // 关闭读端
		const char *msg = "hello from parent";
		write(fd[1], msg, strlen(msg) + 1);
		close(fd[1]);
		wait(NULL); // 等待子进程结束
	}
	return 0;
}

只要还有进程持有写端fd[1],read() 就不会返回0(EOF)。只有当所有写端都关闭后,read才会返回0表示管道关闭。

命名管道

用于任意两个进程之间(不要求有亲缘关系)进行单向通信,使用 mkfifo 命令。

cpp 复制代码
int mkfifo(const char *filename,mode_t mode);

命名管道的行为类似普通文件,但有关键区别:

打开即阻塞:默认情况下,

若只有读端打开(open(..., O_RDONLY)),会阻塞,直到有写端打开。

若只有写端打开(open(..., O_WRONLY)),也会阻塞,直到有读端打开。

案例,两个独立程序通过 FIFO 通信:

写端程序 client.cpp:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    // 以只写方式打开名为"mypipe"的命名管道(FIFO文件)
    // 如果管道不存在或没有读取端,open()会阻塞
    int wfd = open("mypipe", O_WRONLY);
    // 检查管道是否成功打开
    if(wfd < 0)
    {
        perror("open");
    }
    char buf[1024]; // 定义缓冲区,用于存储用户输入
    while(1)
    {
        buf[0] = 0;
        printf("please enter-> ");
        fflush(stdout);     // 强制刷新输出缓冲区,确保提示信息立即显示

        // 从标准输入(键盘)读取用户输入,最多读取sizeof(buf)-1个字符
        // 保留一个位置用于字符串结束符'\0'
        ssize_t s = read(0, buf, sizeof(buf)-1);
        if(s > 0)
        {
            buf[s] = 0;      // 在读取的数据末尾添加字符串结束符
            write(wfd, buf, strlen(buf));   // 将输入写入管道
        }else if(s <= 0)
        {
            perror("read");
        }
    }
    close(wfd);  // 关闭管道文件描述符
    return 0;
}

读端程序server.cpp:

cpp 复制代码
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h> // 包含此头文件以使用mkfifo函数
#include <unistd.h>
#include <string.h>

int main()
{
	// 创建命名管道(FIFO文件)
    // 参数1: "mypipe" - 管道文件名
    // 参数2: 0644 - 管道文件权限(rw-r--r--)
    // mkfifo函数成功返回0,失败返回-1
	if(mkfifo("mypipe", 0644) < 0)
	{
		perror("mkfifo");
	}
	// 以只读方式打开命名管道, 如果没有任何写入端打开管道,open()会阻塞直到有写入端连接
	int rfd = open("mypipe", O_RDONLY);
	if(rfd < 0)
	{
		perror("open");
	}
	char buf[1024];	 // 缓冲区,用于存储从管道读取的数据
	while (1)
	{
		buf[0] = 0;
		printf("please wait...\n");
		// 从管道读取数据, 如果管道中没有数据且写入端未关闭,read()会阻塞等待
		ssize_t s = read(rfd, buf, sizeof(buf)-1);
		if(s > 0)
		{
			buf[s-1] = 0;
			printf("client say# %s\n", buf);
		}else if(s == 0)
		{
			printf("client quit!!!\n");
			break;
		}else{
			perror("read");
		}
	}
	close(rfd);
	return 0;
}

system V共享内存

共享内存是最快的进程间通信形式,允许多个不相关的进程访问同一块物理内存区域,从而实现高效的数据共享。进程不再通过执行进入内核的系统调用来传递彼此的数据。

cpp 复制代码
int shmget(ket_t key, size_t size, int shmflg);
功能:用来创建共享内存
key:共享内存段名字
size:共享内存大小
shmflg:权限位:如 0666(读写权限)创建标志:
	IPC_CREAT:若不存在则创建
    IPC_EXCL:与 IPC_CREAT 同用,若已存在则失败(类似 O_EXCL)
返回值:成功返回共享内存表示符,失败返回-1

cpp 复制代码
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:将共享内存段连接到进程空间地址空间
shmid:共享内存标识符
shmaddr:指定连接的地址
shmflg:标志位,0为可读可写,SHM_RDONLY:只读,SHM_RND:若 shmaddr 非 NULL,将其向下舍入到页边界
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

cpp 复制代码
int shmdt(const void *shmaddr);
功能:将共享内存段与当前进程分离离(只是取消映射,不是删除共享内存段)
shmaddr:由shmat所返回的指针
返回值:成功返回0,失败返回-1

cpp 复制代码
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:用于控制共享内存
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作:
	IPC_STAT:获取段信息到 buf
	IPC_SET:用 buf 设置权限/uid/gid
	IPC_RMID:立即标记删除(实际删除在引用计数归零后
buf:指向一个保存共享内存的模式状态和访问权限的数据结构
返回值:成功返回0,失败返回-1

案例:创建两个程序:

写入端(server):创建共享内存并写入数据

读取端(client):读取共享内存中的数据

公共头文件 shm_chat.h:

cpp 复制代码
#ifndef SHM_CHAT_H
#define SHM_CHAT_H

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

// 定义共享内存大小
#define SHM_SIZE 1024

// 定义共享内存的键值(任意唯一值)
#define SHM_KEY 0x1234

// 定义共享内存结构
typedef struct {
    int written;    // 0: 未写入新数据, 1: 已写入新数据
    char text[SHM_SIZE - sizeof(int)];  // 实际存储的数据
} ShmData;

// 函数声明
int create_shm(int key, int size, int flags);
void* attach_shm(int shmid);
int detach_shm(const void* shmaddr);
int destroy_shm(int shmid);

#endif

公共函数实现 shm_caht.c

cpp 复制代码
#include "shm_chat.h"

// 创建或获取共享内存段
int create_shm(int key, int size, int flags) {
    int shmid;
    
    // 使用shmget创建或获取共享内存
    // key: 共享内存的键值,不同进程使用相同的key访问同一块共享内存
    // size: 共享内存大小
    // flags: 权限标志,如 IPC_CREAT | 0666 表示创建并设置权限
    shmid = shmget(key, size, flags);
    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }
    
    printf("Shared memory created/retrieved, ID: %d\n", shmid);
    return shmid;
}

// 将共享内存附加到进程地址空间
void* attach_shm(int shmid) {
    void *shmaddr;
    
    // 使用shmat将共享内存附加到当前进程
    // shmid: 共享内存ID
    // NULL: 让系统选择附加地址
    // 0: 默认标志,可读写
    shmaddr = shmat(shmid, NULL, 0);
    if (shmaddr == (void*)-1) {
        perror("shmat failed");
        exit(1);
    }
    
    printf("Shared memory attached at address: %p\n", shmaddr);
    return shmaddr;
}

// 从进程地址空间分离共享内存
int detach_shm(const void* shmaddr) {
    int ret;
    
    // 使用shmdt分离共享内存
    // shmaddr: 共享内存的附加地址
    ret = shmdt(shmaddr);
    if (ret == -1) {
        perror("shmdt failed");
        return -1;
    }
    
    printf("Shared memory detached\n");
    return 0;
}

// 销毁共享内存段
int destroy_shm(int shmid) {
    int ret;
    
    // 使用shmctl控制共享内存
    // shmid: 共享内存ID
    // IPC_RMID: 命令,表示删除共享内存
    // NULL: 不需要缓冲区
    ret = shmctl(shmid, IPC_RMID, NULL);
    if (ret == -1) {
        perror("shmctl failed");
        return -1;
    }
    
    printf("Shared memory destroyed\n");
    return 0;
}

写入端程序 shm_write.c

cpp 复制代码
#include "shm_chat.h"

int main() {
    int shmid;
    ShmData *shm_data;
    char buffer[SHM_SIZE - sizeof(int)];
    
    // 1. 创建共享内存(如果已存在则获取)
    // IPC_CREAT: 如果不存在则创建
    // 0666: 权限,用户、组、其他都可读写
    shmid = create_shm(SHM_KEY, sizeof(ShmData), IPC_CREAT | 0666);
    
    // 2. 附加共享内存到进程地址空间
    shm_data = (ShmData*)attach_shm(shmid);
    
    // 初始化共享内存数据
    shm_data->written = 0;
    memset(shm_data->text, 0, sizeof(shm_data->text));
    
    printf("=== 共享内存聊天程序(写入端)===\n");
    printf("输入 'exit' 退出程序\n\n");
    
    while (1) {
        // 等待读取端读取上次的数据
        while (shm_data->written == 1) {
            printf("等待对方读取...\n");
            sleep(1);
        }
        
        // 获取用户输入
        printf("请输入消息: ");
        fflush(stdout);
        
        if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
            break;  // 读取失败或EOF
        }
        
        // 去掉换行符
        buffer[strcspn(buffer, "\n")] = '\0';
        
        // 检查是否退出
        if (strcmp(buffer, "exit") == 0) {
            // 通知读取端退出
            strcpy(shm_data->text, "EXIT_SIGNAL");
            shm_data->written = 1;
            break;
        }
        
        // 3. 写入数据到共享内存
        strncpy(shm_data->text, buffer, sizeof(shm_data->text) - 1);
        shm_data->text[sizeof(shm_data->text) - 1] = '\0';  // 确保字符串结束
        
        // 4. 设置标志,表示有新数据
        shm_data->written = 1;
        
        printf("消息已发送\n");
    }
    
    // 5. 分离共享内存
    detach_shm(shm_data);
    
    printf("写入端程序退出\n");
    return 0;
}

读取端程序 shm_read.c

cpp 复制代码
#include "shm_chat.h"

int main() {
    int shmid;
    ShmData *shm_data;
    
    // 1. 获取已存在的共享内存(不创建新的)
    shmid = create_shm(SHM_KEY, 0, 0666);
    
    // 2. 附加共享内存到进程地址空间
    shm_data = (ShmData*)attach_shm(shmid);
    
    printf("=== 共享内存聊天程序(读取端)===\n");
    printf("等待对方发送消息...\n\n");
    
    while (1) {
        // 等待新数据
        while (shm_data->written == 0) {
            sleep(1);  // 休眠等待,避免忙等待消耗CPU
        }
        
        // 3. 检查退出信号
        if (strcmp(shm_data->text, "EXIT_SIGNAL") == 0) {
            printf("对方已退出\n");
            break;
        }
        
        // 4. 读取并显示数据
        printf("收到消息: %s\n", shm_data->text);
        
        // 5. 重置标志,表示数据已读取
        shm_data->written = 0;
        
        printf("等待下一条消息...\n");
    }
    
    // 6. 分离共享内存
    detach_shm(shm_data);
    
    printf("读取端程序退出\n");
    return 0;
}
相关推荐
Solar20252 小时前
工程材料企业数据采集系统十大解决方案深度解析:从技术挑战到架构实践
java·大数据·运维·服务器·架构
UR的出不克2 小时前
基于 mitmproxy 的大众点评数据采集实战:自动化抓取景点与评论数据
运维·爬虫·python·自动化
viqjeee2 小时前
RK3288设备树介绍和配置
linux·设备树
chinesegf2 小时前
docker迁移镜像并运行
运维·docker·容器
末日汐2 小时前
Linux进程信号
linux·运维·服务器
winfredzhang2 小时前
构建自动化 Node.js 项目管理工具:从文件夹监控到一键联动运行
chrome·python·sqlite·node.js·端口·运行js
无垠的广袤3 小时前
【工业树莓派 CM0 NANO 单板计算机】YOLO26 部署方案
linux·python·opencv·yolo·树莓派·目标识别
宇钶宇夕3 小时前
CoDeSys入门实战一起学习(九):CoDeSys库文件实操指南——安装、调用与版本管理
运维·自动化·软件工程
皮蛋sol周3 小时前
嵌入式学习数据结构(二)双向链表 内核链表
linux·数据结构·学习·嵌入式·arm·双向链表