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
相关产品推荐
相关产品推荐

