当前位置:首页>Linux>Linux IPC实战:用共享内存+信号量实现高效进程间大数据传输

Linux IPC实战:用共享内存+信号量实现高效进程间大数据传输

  • 2026-09-03 03:08:47
Linux IPC实战:用共享内存+信号量实现高效进程间大数据传输

大家好,我是一个爱分享的牛马程序员,工作中碰到,加上自己理解,很高兴给大家分享。

-begin-

题目:在Linux系统中,编写一个程序,使用共享内存(Shared Memory)和信号量(Semaphore)实现两个进程间的大数据传输:进程A循环读取一个文本文件的内容(每次读取1024字节),写入共享内存;进程B从共享内存中读取数据并写入另一个文件,直到文件传输完成(进程A读取到文件末尾),两个进程均退出并清理资源。

详细分析过程:

1.共享内存设计:
◦共享内存是内核中的一块连续内存区域,由进程A和进程B映射到各自的地址空间,实现数据的直接访问(无需内核拷贝,速度快)。
◦此处设置共享内存大小为1024字节(与每次读取的文件块大小一致),确保一次能容纳一个数据块。
2.同步机制设计:
◦需两个信号量实现进程间的同步:
▪sem_empty:初始值为1,表示共享内存为空(进程A可写入数据)。
▪sem_full:初始值为0,表示共享内存中有数据(进程B可读取数据)。
◦进程A写入数据前需等待sem_empty(确保内存为空),写入后释放sem_full(通知进程B有数据);进程B读取前需等待sem_full(确保内存有数据),读取后释放sem_empty(通知进程A可写入新数据)。
3.流程设计:
◦进程A(发送方):打开源文件,循环读取数据块(1024字节),若读取到数据,等待sem_empty,将数据写入共享内存,释放sem_full;若读取到文件末尾(返回0字节),向共享内存写入结束标志(如-1),释放sem_full后退出。
◦进程B(接收方):打开目标文件,循环等待sem_full,从共享内存读取数据,若读到结束标志则退出,否则将数据写入目标文件,释放sem_empty;最后关闭文件并清理资源。
4.资源清理:
◦进程B检测到结束标志后,先删除共享内存和信号量,再退出;进程A等待进程B处理完最后数据后退出,避免资源残留。

关键代码:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/sem.h>

#include <sys/wait.h>

#include <fcntl.h>

#include <sys/stat.h>

#define SHM_SIZE 1024       // 共享内存大小(字节)

#define BUFFER_SIZE 1024    // 每次读写的缓冲区大小

#define FILE_NAME_SRC "source.txt"   // 源文件

#define FILE_NAME_DST "dest.txt"     // 目标文件

// 信号量操作:P操作(等待)

void sem_p(int semid, int index) {

   struct sembuf sb;

   sb.sem_num = index;

   sb.sem_op = -1;

   sb.sem_flg = 0;

   semop(semid, &sb, 1);

}

// 信号量操作:V操作(释放)

void sem_v(int semid, int index) {

   struct sembuf sb;

   sb.sem_num = index;

   sb.sem_op = 1;

   sb.sem_flg = 0;

   semop(semid, &sb, 1);

}

int main() {

   key_t shm_key, sem_key;

   int shmid, semid;

   char *shm_ptr;

   pid_t pid;

   int fd_src, fd_dst;

   ssize_t read_len;

   // 生成共享内存和信号量的关键字

   shm_key = ftok(".", 's');

   sem_key = ftok(".", 'e');

   if (shm_key == -1 || sem_key == -1) {

       perror("ftok failed");

       exit(EXIT_FAILURE);

   }

   // 创建共享内存(大小SHM_SIZE,权限0666)

   shmid = shmget(shm_key, SHM_SIZE, IPC_CREAT | 0666);

   if (shmid == -1) {

       perror("shmget failed");

       exit(EXIT_FAILURE);

   }

   // 映射共享内存到当前进程地址空间

   shm_ptr = (char *)shmat(shmid, NULL, 0);

   if (shm_ptr == (char *)-1) {

       perror("shmat failed");

       shmctl(shmid, IPC_RMID, NULL);

       exit(EXIT_FAILURE);

   }

   // 创建2个信号量:[0]sem_empty(初始1),[1]sem_full(初始0)

   semid = semget(sem_key, 2, IPC_CREAT | 0666);

   if (semid == -1) {

       perror("semget failed");

       shmdt(shm_ptr);

       shmctl(shmid, IPC_RMID, NULL);

       exit(EXIT_FAILURE);

   }

   semctl(semid, 0, SETVAL, 1);  // sem_empty = 1

   semctl(semid, 1, SETVAL, 0);  // sem_full = 0

   // 创建子进程(进程B:接收方)

   pid = fork();

   if (pid == -1) {

       perror("fork failed");

       goto cleanup;

   }

   if (pid == 0) {  // 进程B:读取共享内存并写入目标文件

       // 打开目标文件(若不存在则创建,权限0644)

       fd_dst = open(FILE_NAME_DST, O_WRONLY | O_CREAT | O_TRUNC, 0644);

       if (fd_dst == -1) {

           perror("open dest failed");

           exit(EXIT_FAILURE);

       }

       while (1) {

           sem_p(semid, 1);  // 等待sem_full(共享内存有数据)

           // 检查是否为结束标志(用首字节存储-1表示结束)

           if (*(int *)shm_ptr == -1) {

               break;  // 退出循环

           }

           // 写入目标文件

           if (write(fd_dst, shm_ptr, BUFFER_SIZE) != BUFFER_SIZE) {

               perror("write dest failed");

               close(fd_dst);

               exit(EXIT_FAILURE);

           }

           sem_v(semid, 0);  // 释放sem_empty(共享内存已空)

       }

       // 关闭目标文件

       close(fd_dst);

       printf("进程B:文件传输完成\n");

       // 退出前清理资源

       shmdt(shm_ptr);

       shmctl(shmid, IPC_RMID, NULL);  // 删除共享内存

       semctl(semid, 0, IPC_RMID);     // 删除信号量

       exit(EXIT_SUCCESS);

   } else {  // 进程A:读取源文件并写入共享内存

       // 打开源文件(只读)

       fd_src = open(FILE_NAME_SRC, O_RDONLY);

       if (fd_src == -1) {

           perror("open source failed");

           waitpid(pid, NULL, 0);

           goto cleanup;

       }

       // 循环读取源文件

       while ((read_len = read(fd_src, shm_ptr, BUFFER_SIZE)) > 0) {

           // 若读取不足BUFFER_SIZE,用0填充剩余空间(避免残留数据)

           if (read_len < BUFFER_SIZE) {

               memset(shm_ptr + read_len, 0, BUFFER_SIZE - read_len);

           }

           sem_p(semid, 0);  // 等待sem_empty(共享内存为空)

           sem_v(semid, 1);  // 释放sem_full(共享内存有数据)

       }

       if (read_len == -1) {  // 读取错误

           perror("read source failed");

           close(fd_src);

           waitpid(pid, NULL, 0);

           goto cleanup;

       }

       // 读取到文件末尾,写入结束标志

       sem_p(semid, 0);  // 等待sem_empty

       *(int *)shm_ptr = -1;  // 结束标志

       sem_v(semid, 1);  // 释放sem_full,通知进程B

       // 关闭源文件

       close(fd_src);

       printf("进程A:文件读取完成\n");

       // 等待进程B处理完毕

       waitpid(pid, NULL, 0);

   }

cleanup:

   // 清理资源

   shmdt(shm_ptr);

   shmctl(shmid, IPC_RMID, NULL);

   semctl(semid, 0, IPC_RMID);

   return 0;

}

代码说明:

•共享内存操作:进程A和进程B通过shm_ptr直接访问同一块内存,数据传输无需经过内核缓冲区,效率远高于管道或消息队列,适合大数据传输。
•信号量同步:sem_empty和sem_full形成“握手”机制,确保进程A写入数据后进程B才能读取,避免“写覆盖”或“读空”问题。例如,进程A写入数据前必须等待内存为空(sem_empty),写入后立即通知进程B(sem_full);进程B读取后释放sem_empty,允许进程A写入下一块数据。
•结束标志处理:进程A读取到文件末尾后,向共享内存写入-1(存储在首字节)作为结束标志,进程B检测到该标志后退出并清理资源,确保传输完整。
•数据对齐:每次读写固定为1024字节,若源文件大小不是1024的整数倍,最后一块用0填充,避免残留数据干扰(实际应用中可额外传递有效长度,此处简化处理)。

运行结果示例:

假设source.txt内容为“Hello, this is a test file for shared memory transfer!”(大小约50字节),运行程序后:

进程A:文件读取完成

进程B:文件传输完成

此时dest.txt的内容与source.txt完全一致,实现了文件的完整传输。

常见考点:

•共享内存的创建(shmget)、映射(shmat)、解除映射(shmdt)和删除(shmctl)流程;
•信号量在共享内存同步中的作用(避免数据竞争);
•共享内存与其他IPC机制(管道、消息队列)的对比(共享内存速度最快,但需手动同步);
•大数据传输的分块处理策略(固定块大小,处理文件末尾的不完整块)。
-end-

如果文章对你有提升,帮忙点赞,分享,关注。十分感谢

最新文章

随机文章