应用--Minishell实现

支持后台运行的Minishell实现

一、具体代码

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    char cmd[100];
    char *args[10];
    
    // 回收已经退出的子进程(非阻塞)
    while (waitpid(-1, NULL, WNOHANG) > 0) {
        // 自动回收僵尸进程
    }
    
    while (1) {
        printf("> ");
        fflush(stdout);
        
        if (fgets(cmd, sizeof(cmd), stdin) == NULL) break;
        
        // 解析命令
        int i = 0;
        args[i] = strtok(cmd, " \t\n");
        while (args[i]) args[++i] = strtok(NULL, " \t\n");
        
        if (!args[0]) continue;
        
        if (strcmp(args[0], "quit") == 0) break;
        
        else if (strcmp(args[0], "cd") == 0) {
            if (args[1]) {
                if (chdir(args[1]) != 0) perror("cd");
            }
        }
        
        else {
            pid_t pid = fork();
            
            if (pid == 0) {
                // 子进程
                execvp(args[0], args);
                perror(args[0]);
                exit(1);
            } 
            else if (pid > 0) {
                // 父进程
                // 这里可以立即进行非阻塞回收
                int status;
                pid_t ret = waitpid(pid, &status, WNOHANG);
                
                if (ret == 0) {
                    printf("命令 %s 正在后台运行 (PID: %d)\n", args[0], pid);
                } 
                else if (ret == pid) {
                    if (WIFEXITED(status)) {
                        printf("命令执行完成,退出码: %d\n", WEXITSTATUS(status));
                    }
                }
            }
        }
    }
    
    // 等待所有子进程结束
    while (waitpid(-1, NULL, 0) > 0);
    
    return 0;
}  

主要特点

  1. 支持前台命令(阻塞等待)

  2. 支持后台命令(非阻塞执行)

  3. 自动回收僵尸进程

  4. 实现了基本的shell功能

二、逐行代码解释

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    char cmd[100];          // 存储用户输入的命令
    char *args[10];         // 存储解析后的参数
    
    // 回收已经退出的子进程(非阻塞)
    while (waitpid(-1, NULL, WNOHANG) > 0) {
        // 自动回收僵尸进程
    }

1. 启动时清理僵尸进程

  • waitpid(-1, NULL, WNOHANG):非阻塞回收所有子进程

  • > 0:有子进程被回收

  • 循环直到没有僵尸进程

复制代码
    while (1) {
        printf("> ");
        fflush(stdout);  // 立即刷新输出缓冲区
        
        // 读取用户输入
        if (fgets(cmd, sizeof(cmd), stdin) == NULL) break;

2. 显示提示符和读取命令

  • fgets():安全读取一行输入

  • 如果返回NULL(如按Ctrl+D),则退出循环

复制代码
        // 解析命令
        int i = 0;
        args[i] = strtok(cmd, " \t\n");  // 第一次调用
        while (args[i]) args[++i] = strtok(NULL, " \t\n");  // 后续调用
        
        if (!args[0]) continue;  // 空行,继续循环

3. 命令解析

  • strtok():按空格、制表符、换行符分割字符串

  • 结果存储在args数组中

  • args[0]是命令名,后续是参数

复制代码
        if (strcmp(args[0], "quit") == 0) break;
        
        else if (strcmp(args[0], "cd") == 0) {
            if (args[1]) {
                if (chdir(args[1]) != 0) perror("cd");
            }
        }

4. 内置命令处理

  • quit:直接退出循环

  • cd :使用chdir()改变当前工作目录

  • perror("cd"):如果chdir()失败,显示错误信息

复制代码
        else {
            pid_t pid = fork();  // 创建子进程
            
            if (pid == 0) {
                // 子进程
                execvp(args[0], args);  // 执行命令
                perror(args[0]);        // 如果execvp失败
                exit(1);                // 子进程异常退出
            } 
            else if (pid > 0) {
                // 父进程 - 关键:非阻塞回收
                int status;
                pid_t ret = waitpid(pid, &status, WNOHANG);

5. 执行外部命令

  • fork():创建子进程

  • 子进程:execvp()执行命令

  • 父进程:waitpid(pid, &status, WNOHANG) 非阻塞检查子进程状态

复制代码
                if (ret == 0) {
                    printf("命令 %s 正在后台运行 (PID: %d)\n", args[0], pid);
                } 
                else if (ret == pid) {
                    if (WIFEXITED(status)) {
                        printf("命令执行完成,退出码: %d\n", WEXITSTATUS(status));
                    }
                }
            }
        }
    }

6. 非阻塞回收的状态判断

  • ret == 0:子进程还在运行,转为后台运行

  • ret == pid:子进程已结束,立即回收

  • WIFEXITED(status):检查是否正常退出

  • WEXITSTATUS(status):获取退出码

复制代码
    // 等待所有子进程结束
    while (waitpid(-1, NULL, 0) > 0);
    
    return 0;
}

7. 程序退出前清理

  • 阻塞等待所有子进程结束

  • 防止留下僵尸进程

三、关键知识点整理

1. 非阻塞回收的三种情况

复制代码
pid_t ret = waitpid(pid, &status, WNOHANG);

// 情况1: ret > 0 (子进程已结束)
if (ret == pid) {
    // 子进程已结束,可以获取状态
}

// 情况2: ret == 0 (子进程仍在运行)
if (ret == 0) {
    // 子进程还在运行,可以做其他事情
}

// 情况3: ret == -1 (错误或没有子进程)
if (ret == -1) {
    // 处理错误
}

2. 后台运行与前台运行的区别

特性 前台命令 后台命令
wait方式 阻塞等待 非阻塞检查
用户交互 需要等待完成 立即返回提示符
示例 ls sleep 10 &(这里没有&符号但效果类似)
输出 直接显示 可能和用户输入交错

3. 僵尸进程处理策略

复制代码
// 策略1: 启动时清理(代码开头)
while (waitpid(-1, NULL, WNOHANG) > 0);

// 策略2: 命令执行后立即非阻塞回收
waitpid(pid, &status, WNOHANG);

// 策略3: 退出前阻塞清理(代码结尾)
while (waitpid(-1, NULL, 0) > 0);

4. 这个实现的限制

  • 没有真正的后台符号&

    1. 所有命令都尝试非阻塞回收

    2. 如果命令执行很快(如ls),可能立即完成

    3. 如果命令执行慢(如sleep 5),才显示"后台运行"

  • 输出可能混乱

    1. 后台命令的输出可能与用户输入交错

    2. 没有进行输出重定向

  • 不支持管道和重定向

5. 改进建议

复制代码
// 添加后台运行符号&支持
if (args[i-1] && strcmp(args[i-1], "&") == 0) {
    args[i-1] = NULL;  // 移除&符号
    // 使用非阻塞回收
    waitpid(pid, &status, WNOHANG);
} else {
    // 前台命令,阻塞等待
    waitpid(pid, &status, 0);
}

// 添加信号处理避免僵尸进程
signal(SIGCHLD, SIG_IGN);  // 忽略SIGCHLD,系统自动回收

四、测试示例

复制代码
# 编译运行
$ gcc -o minishell minishell.c
$ ./minishell

# 测试场景1: 快速命令(可能立即完成)
> ls
file1.txt file2.txt
命令执行完成,退出码: 0

# 测试场景2: 慢速命令(转为后台)
> sleep 5
命令 sleep 正在后台运行 (PID: 12345)
> # 立即显示新提示符,可以继续输入命令

# 测试场景3: 内置命令
> cd /tmp
> pwd
/tmp

五、学习要点

  1. 非阻塞回收的应用场景:实现后台任务、避免程序阻塞

  2. waitpid的灵活使用:通过选项控制等待行为

  3. 僵尸进程的预防:多种回收策略结合

  4. Shell的基本架构:读-解析-执行循环

相关推荐
m0_471199636 小时前
【vue】diff算法简介
前端·vue.js·算法
努力学算法的蒟蒻6 小时前
day34(12.15)——leetcode面试经典150
算法·leetcode·面试
星川皆无恙6 小时前
基于ARIMA 算法模型和NLP:社交媒体舆情分析在涉众型经济犯罪情报挖掘中的应用研究
人工智能·爬虫·python·算法·机器学习·自然语言处理·数据分析
zore_c6 小时前
【C语言】Win 32 API——一部分内容详解!!!
c语言·开发语言·c++·经验分享·笔记
郝学胜-神的一滴6 小时前
Linux线程编程:从原理到实践
linux·服务器·开发语言·c++·程序人生·设计模式·软件工程
_OP_CHEN6 小时前
【Linux系统编程】(十四)深入 Linux 内核:进程优先级调度与切换的底层逻辑全解析
linux·运维·linux内核·进程·进程切换·进程优先级·调度算法
Orange裴6 小时前
Kali linux2025.3 安装nessus(Mac M4芯片)
linux·运维·macos·kali linux
重生之我在番茄自学网安拯救世界6 小时前
网络安全中级阶段学习笔记(七):Web 安全之文件上传漏洞笔记1(包含upload-labs-master靶场前三关实战)
笔记·学习·web安全·文件上传漏洞·网安基础
走在路上的菜鸟6 小时前
Android学Dart学习笔记第十五节 类
android·笔记·学习·flutter