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

Linux下将C++中fork()替换为Pthread的多线程Shell改造咨询

把基于fork的Linux简易Shell改造成多线程版本

核心注意点

线程和进程的核心差异在于:线程共享进程的地址空间,直接在线程里调用execv会替换整个进程的代码段,导致所有线程(包括Shell主线程)被销毁。所以正确的做法是:在线程内部创建子进程来执行命令,线程只负责管理这个子进程的生命周期。

另外,主线程会循环复用command_t结构体,所以需要给每个线程单独复制一份命令参数和路径,避免线程读取到被主线程覆盖的数据。

具体改造步骤

1. 新增线程参数结构体

定义结构体封装线程需要的命令路径和参数列表:

// 线程函数的参数结构体
typedef struct {
    char *cmd_path;
    char **cmd_args;
} thread_args_t;

2. 实现线程执行函数

线程函数负责接收参数、创建子进程执行命令、等待子进程结束后释放资源:

void* execute_command(void *args) {
    thread_args_t *cmd_args = (thread_args_t*)args;
    
    int child_pid = fork();
    if (child_pid == 0) {
        // 子进程执行命令,失败则打印错误并退出
        execv(cmd_args->cmd_path, cmd_args->cmd_args);
        perror("execv failed");
        exit(EXIT_FAILURE);
    } else if (child_pid > 0) {
        // 线程等待子进程结束
        waitpid(child_pid, NULL, 0);
    } else {
        perror("fork failed");
    }
    
    // 释放线程参数资源
    free(cmd_args->cmd_path);
    for (int i = 0; cmd_args->cmd_args[i] != NULL; i++) {
        free(cmd_args->cmd_args[i]);
    }
    free(cmd_args->cmd_args);
    free(cmd_args);
    
    pthread_exit(NULL);
}

3. 修改主线程执行逻辑

替换原有的fork代码块为线程创建逻辑,同时复制命令数据避免资源竞争:

// 替换原fork代码段
else {
    // 1. 构建完整命令路径
    char *full_path = NULL;
    if (*command.name == '/') {
        full_path = strdup(command.name);
    } else {
        char temp[LINE_LEN];
        strcpy(temp, "/bin/");
        strcat(temp, command.name);
        full_path = strdup(temp);
    }
    
    // 2. 复制命令参数列表(execv要求参数以NULL结尾)
    char **args_copy = malloc(sizeof(char*) * (command.argcount + 1));
    for (int i = 0; i < command.argcount; i++) {
        args_copy[i] = strdup(command.argvalue[i]);
    }
    args_copy[command.argcount] = NULL;
    
    // 3. 封装线程参数
    thread_args_t *thread_args = malloc(sizeof(thread_args_t));
    thread_args->cmd_path = full_path;
    thread_args->cmd_args = args_copy;
    
    // 4. 创建线程并分离(避免主线程阻塞)
    pthread_t tid;
    if (pthread_create(&tid, NULL, execute_command, (void*)thread_args) != 0) {
        perror("pthread_create failed");
        // 创建失败时释放资源
        free(full_path);
        for (int i = 0; args_copy[i] != NULL; i++) free(args_copy[i]);
        free(args_copy);
        free(thread_args);
    }
    pthread_detach(tid);
    
    // 释放原command的资源
    for (int i = 0; i < command.argcount; i++) {
        free(command.argvalue[i]);
        command.argvalue[i] = NULL;
    }
    free(command.name);
    command.name = NULL;
    command.argcount = 0;
    
    printPrompt();
}

4. 修正原代码中的Bug

原代码存在多处会导致崩溃或功能异常的问题,必须修复:

  • lookupPath循环变量错误:for (x = 0; a < MAX_PATHS; x++) 中的a改为x,同时返回完整路径而非命令名
  • parsePath数组索引错误:dirs[i] = NULL; 中的i改为x
  • readCommand缓冲区大小错误:fgets(buffer, sizeof(buffer), stdin) 改为fgets(buffer, LINE_LEN, stdin),并清除换行符
  • parseCommand逻辑错误:删除错误的strcat拼接代码,修正参数拆分逻辑,确保参数列表以NULL结尾

完整修改后的代码

// Header files
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <pthread.h>
///////////////////////////////////////////////////////////////////////////////////////////////////
#define LINE_LEN 80            // 命令行长度
#define MAX_ARGS 64            // 最大参数数量
#define MAX_ARG_LEN 16         // 单个参数长度
#define MAX_PATHS 64           // 最大路径数量
#define MAX_PATH_LEN 96        // 单个路径长度
#define WHITESPACE " .,''\t\n" // 命令行分隔符
#define TRUE 1                 // 布尔真
#ifndef NULL                   // 定义NULL
#define NULL 0
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
// 命令结构体
typedef struct {
    char *name;                 // 命令名
    int argcount;               // 参数数量
    char *argvalue[MAX_ARGS];   // 参数列表
} command_t;

// 线程参数结构体
typedef struct {
    char *cmd_path;
    char **cmd_args;
} thread_args_t;

// 函数声明
char *lookupPath(char **, char **);
int parseCommand(char *, command_t *);
int parsePath(char **);
void printPrompt();
void readCommand(char *);
void* execute_command(void *args);

/***************************************************************
 * 主函数:处理Shell的核心逻辑
 **************************************************************/
int main(int argcount, char *argvalue[]) {
    char *pathv[MAX_PATHS];
    command_t command;
    char *commandLine = (char *)malloc(LINE_LEN);
    
    // 初始化命令结构体
    command.name = NULL;
    for (int i = 0; i < MAX_ARGS; i++) {
        command.argvalue[i] = NULL;
    }
    command.argcount = 0;
    
    parsePath(pathv);
    printPrompt();
    
    while (TRUE) {
        readCommand(commandLine);
        
        // 处理退出命令
        if (strcasecmp(commandLine, "stop") == 0 || 
            strcasecmp(commandLine, "exit") == 0 || 
            strcasecmp(commandLine, "quit") == 0) {
            free(commandLine);
            if (command.name != NULL) free(command.name);
            for (int i = 0; i < command.argcount; i++) {
                if (command.argvalue[i] != NULL) free(command.argvalue[i]);
            }
            break;
        }
        
        // 解析命令
        if (!parseCommand(commandLine, &command)) {
            printPrompt();
            continue;
        }
        
        // 查找命令路径
        command.name = lookupPath(command.argvalue, pathv);
        if (command.name == NULL) {
            printf("Command not found\n");
            // 释放资源
            for (int i = 0; i < command.argcount; i++) free(command.argvalue[i]);
            free(command.name);
            command.name = NULL;
            command.argcount = 0;
            printPrompt();
            continue;
        }
        
        // 创建线程执行命令
        char *full_path = strdup(command.name);
        char **args_copy = malloc(sizeof(char*) * (command.argcount + 1));
        for (int i = 0; i < command.argcount; i++) {
            args_copy[i] = strdup(command.argvalue[i]);
        }
        args_copy[command.argcount] = NULL;
        
        thread_args_t *thread_args = malloc(sizeof(thread_args_t));
        thread_args->cmd_path = full_path;
        thread_args->cmd_args = args_copy;
        
        pthread_t tid;
        if (pthread_create(&tid, NULL, execute_command, (void*)thread_args) != 0) {
            perror("Failed to create thread");
            free(full_path);
            for (int i = 0; args_copy[i] != NULL; i++) free(args_copy[i]);
            free(args_copy);
            free(thread_args);
        }
        pthread_detach(tid);
        
        // 释放原命令资源
        for (int i = 0; i < command.argcount; i++) {
            free(command.argvalue[i]);
            command.argvalue[i] = NULL;
        }
        free(command.name);
        command.name = NULL;
        command.argcount = 0;
        
        printPrompt();
    }
    
    printf("Exiting now...\n");
    return 0;
}

/**********************************************************
 * 线程执行函数:创建子进程执行命令
 *********************************************************/
void* execute_command(void *args) {
    thread_args_t *cmd_args = (thread_args_t*)args;
    
    int child_pid = fork();
    if (child_pid == 0) {
        execv(cmd_args->cmd_path, cmd_args->cmd_args);
        perror("Failed to execute command");
        exit(EXIT_FAILURE);
    } else if (child_pid > 0) {
        waitpid(child_pid, NULL, 0);
    } else {
        perror("Failed to fork child process");
    }
    
    free(cmd_args->cmd_path);
    for (int i = 0; cmd_args->cmd_args[i] != NULL; i++) {
        free(cmd_args->cmd_args[i]);
    }
    free(cmd_args->cmd_args);
    free(cmd_args);
    
    pthread_exit(NULL);
}

/**********************************************************
 * 查找命令的完整路径
 *********************************************************/
char *lookupPath(char **argvalue, char **dir) {
    char pName[MAX_PATH_LEN];
    int x;
    
    // 处理绝对路径
    if (*argvalue[0] == '/') {
        if (access(argvalue[0], X_OK) == 0) {
            return strdup(argvalue[0]);
        }
        return NULL;
    }
    
    // 遍历PATH路径查找
    for (x = 0; x < MAX_PATHS; x++) {
        if (dir[x] == NULL) break;
        
        char tpath[MAX_PATH_LEN];
        strcpy(tpath, dir[x]);
        strcat(tpath, "/");
        strcat(tpath, argvalue[0]);
        
        if (access(tpath, X_OK) == 0) {
            return strdup(tpath);
        }
    }
    return NULL;
}

/**********************************************************
 * 解析命令行参数
 *********************************************************/
int parseCommand(char *cLine, command_t *cmd) {
    int argcount = 0;
    char **clPtr = &cLine;
    
    // 重置命令结构体
    if (cmd->name != NULL) {
        free(cmd->name);
        cmd->name = NULL;
    }
    for (int i = 0; i < cmd->argcount; i++) {
        if (cmd->argvalue[i] != NULL) {
            free(cmd->argvalue[i]);
            cmd->argvalue[i] = NULL;
        }
    }
    cmd->argcount = 0;
    
    // 跳过开头空白字符
    while (isspace((unsigned char)*cLine)) {
        cLine++;
    }
    
    // 拆分参数
    while ((cmd->argvalue[argcount] = strsep(clPtr, WHITESPACE)) != NULL) {
        if (strlen(cmd->argvalue[argcount]) == 0) {
            free(cmd->argvalue[argcount]);
            cmd->argvalue[argcount] = NULL;
            continue;
        }
        argcount++;
        if (argcount >= MAX_ARGS) break;
        cmd->argvalue[argcount] = malloc(MAX_ARG_LEN);
    }
    
    cmd->argcount = argcount;
    if (argcount > 0) {
        cmd->name = strdup(cmd->argvalue[0]);
    } else {
        cmd->name = NULL;
    }
    cmd->argvalue[argcount] = NULL;
    
    return argcount > 0 ? 1 : 0;
}

/**********************************************************
 * 解析环境变量PATH
 *********************************************************/
int parsePath(char *dirs[]) {
    char *pathEnvVar = getenv("PATH");
    char *thePath;
    int x;
    
    // 初始化路径数组
    for (x = 0; x < MAX_PATHS; x++) {
        dirs[x] = NULL;
    }
    
    if (pathEnvVar == NULL) {
        dirs[0] = strdup("/bin");
        return 0;
    }
    
    thePath = strdup(pathEnvVar);
    for (x = 0; x < MAX_PATHS; x++) {
        dirs[x] = strsep(&thePath, ":");
        if (dirs[x] == NULL) break;
        dirs[x] = strdup(dirs[x]);
    }
    
    free(thePath);
    return 0;
}

/**********************************************************
 * 打印命令提示符
 *********************************************************/
void printPrompt() {
    const char *promptString = ">>> ";
    printf("%s", promptString);
    fflush(stdout);
}

/**********************************************************
 * 读取用户输入的命令
 *********************************************************/
void readCommand(char *buffer) {
    memset(buffer, 0, LINE_LEN);
    fgets(buffer, LINE_LEN, stdin);
    
    // 移除换行符
    size_t len = strlen(buffer);
    if (len > 0 && buffer[len-1] == '\n') {
        buffer[len-1] = '\0';
    }
}

编译与运行

编译时需要链接pthread库:

gcc shell_thread.c -o shell_thread -pthread

运行编译后的程序:

./shell_thread

内容的提问来源于stack exchange,提问作者Pez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 19:31:03