You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 23:57:16