Linux环境C语言共享内存信号量程序输出空文件问题求助
共享内存大小写转换程序输出空文件问题修复
已定位的代码错误
- 同步逻辑缺失,子进程提前执行
现有代码仅用1个初始值为1的信号量实现临界区互斥,没有控制进程执行顺序:fork()创建子进程后,子进程一旦获得CPU就会直接启动读缓冲区的循环,此时父进程可能还没开始往共享内存写文件内容。 - 长度计数变量未共享
记录写入字符数的变量i是栈上局部变量,fork()后父、子进程各自持有独立的i副本。子进程的i始终是fork时刻的初始值0,因此读缓冲区的for循环判定条件j < i/k < i永远不成立,循环体一次都不会执行,自然输出空文件。 - 信号量创建权限错误
semget()调用传入的权限参数为0100,仅赋予所有者执行权限,无读写权限,会导致后续semop()信号量操作直接失败。由于代码未做任何系统调用返回值校验,该错误无法被感知。 - 资源回收逻辑错误
三个进程(父+2个子进程)的代码路径末尾都会执行shmctl(shmid, IPC_RMID, NULL)删除共享内存,若子进程先执行到该行,会直接删除仍在使用的共享内存段,导致父进程写入失败。同时父进程没有等待子进程执行结束的逻辑,可能出现父进程提前退出、子进程变成孤儿进程的问题。 - 共享内存写入不规范
父进程写完文件内容后没有在缓冲区末尾追加字符串结束符\0,后续按字符串打印缓冲区时会出现越界读的问题。
修复要点
- 将记录写入长度的计数变量存入共享内存,保证三个进程访问同一个计数值
- 调整信号量逻辑:除了1个用于临界区互斥的信号量外,新增2个初始值为0的同步信号量,父进程写完所有内容后再通过信号量通知两个子进程开始读取,避免子进程提前运行
- 将信号量创建权限修改为
0666(所有用户可读写),和共享内存权限保持一致 - 父进程写完所有内容后调用
wait()等待两个子进程执行完毕,再统一执行共享内存、信号量的删除操作 - 父进程写入完成后在共享缓冲区末尾追加
\0结束符 - 为所有系统调用(
fopen/shmget/semget/shmat等)增加返回值检查,调用失败时直接打印错误信息并退出
修正后可运行代码
#include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <unistd.h> #include <sys/wait.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> // 信号量编号定义 #define SEM_MUTEX 0 #define SEM_CHILD1 1 #define SEM_CHILD2 2 // P操作(获取信号量) void sem_p(int sem_id, int sem_num) { struct sembuf op = { .sem_num = sem_num, .sem_op = -1, .sem_flg = 0 }; if (semop(sem_id, &op, 1) == -1) { perror("semop P failed"); exit(EXIT_FAILURE); } } // V操作(释放信号量) void sem_v(int sem_id, int sem_num) { struct sembuf op = { .sem_num = sem_num, .sem_op = 1, .sem_flg = 0 }; if (semop(sem_id, &op, 1) == -1) { perror("semop V failed"); exit(EXIT_FAILURE); } } int main() { FILE *archivo, *salida1, *salida2; char actual; // 1. 初始化共享内存:512字节缓冲区 + 1个int类型的长度计数器 key_t claveMC = ftok("UCAB.txt", 5); if (claveMC == -1) { perror("ftok shm failed"); exit(EXIT_FAILURE); } int shmid = shmget(claveMC, 512 + sizeof(int), 0666 | IPC_CREAT); if (shmid == -1) { perror("shmget failed"); exit(EXIT_FAILURE); } char *shm_addr = (char*)shmat(shmid, NULL, 0); if (shm_addr == (void*)-1) { perror("shmat failed"); exit(EXIT_FAILURE); } char *buffer = shm_addr; int *write_len = (int*)(shm_addr + 512); // 共享内存后半段存长度 *write_len = 0; // 2. 初始化3个信号量:互斥、子进程1等待、子进程2等待 key_t claveSem = ftok("UCAB.txt", 7); if (claveSem == -1) { perror("ftok sem failed"); exit(EXIT_FAILURE); } int idsem = semget(claveSem, 3, IPC_CREAT | 0666); if (idsem == -1) { perror("semget failed"); exit(EXIT_FAILURE); } // 设置信号量初始值 semctl(idsem, SEM_MUTEX, SETVAL, 1); semctl(idsem, SEM_CHILD1, SETVAL, 0); semctl(idsem, SEM_CHILD2, SETVAL, 0); // 3. 打开输入文件 archivo = fopen("UCAB.txt", "r"); if (archivo == NULL) { perror("open input file failed"); exit(EXIT_FAILURE); } // 4. 创建第一个子进程(输出大写) pid_t id1 = fork(); if (id1 == -1) { perror("fork 1 failed"); exit(EXIT_FAILURE); } if (id1 == 0) { // 子进程1:等父进程写完信号,再转大写输出 sem_p(idsem, SEM_CHILD1); salida1 = fopen("MAYUSCULAS.txt", "w"); if (salida1 == NULL) { perror("open MAYUSCULAS.txt failed"); exit(EXIT_FAILURE); } for (int k = 0; k < *write_len; k++) { sem_p(idsem, SEM_MUTEX); putc(toupper(buffer[k]), salida1); sem_v(idsem, SEM_MUTEX); } fclose(salida1); shmdt(shm_addr); exit(EXIT_SUCCESS); } // 父进程继续创建第二个子进程(输出小写) pid_t id2 = fork(); if (id2 == -1) { perror("fork 2 failed"); exit(EXIT_FAILURE); } if (id2 == 0) { // 子进程2:等父进程写完信号,再转小写输出 sem_p(idsem, SEM_CHILD2); salida2 = fopen("minusculas.txt", "w"); if (salida2 == NULL) { perror("open minusculas.txt failed"); exit(EXIT_FAILURE); } for (int j = 0; j < *write_len; j++) { sem_p(idsem, SEM_MUTEX); putc(tolower(buffer[j]), salida2); sem_v(idsem, SEM_MUTEX); } fclose(salida2); shmdt(shm_addr); exit(EXIT_SUCCESS); } // 父进程逻辑:读文件写共享内存 while (((actual = getc(archivo)) != EOF) && (*write_len < 511)) { // 留1字节存\0 sem_p(idsem, SEM_MUTEX); buffer[*write_len] = actual; (*write_len)++; sem_v(idsem, SEM_MUTEX); } // 补字符串结束符 sem_p(idsem, SEM_MUTEX); buffer[*write_len] = '\0'; sem_v(idsem, SEM_MUTEX); fclose(archivo); // 通知两个子进程可以开始读 sem_v(idsem, SEM_CHILD1); sem_v(idsem, SEM_CHILD2); // 等待两个子进程退出 waitpid(id1, NULL, 0); waitpid(id2, NULL, 0); // 清理IPC资源 shmdt(shm_addr); shmctl(shmid, IPC_RMID, NULL); semctl(idsem, 0, IPC_RMID); return 0; }
注:运行前需要确保当前目录下存在
UCAB.txt输入文件,否则程序会打印文件打开失败的错误信息。
内容的提问来源于stack exchange,提问作者Nigu
相关产品推荐
相关产品推荐

