基于c++实现的简易shell

代码逻辑

核心思想

  • 解析命令行,拆解命令及其选项
  • 创建子进程,在子进程中执行命令
  • 如果是前台执行命令,则父进程就阻塞等待子进程中命令执行结束后回收子进程的资源
  • 如果是后台执行命令,则父进程不进行阻塞等待,可继续向下运行,但为了防止僵尸进程的出现,需要设置一个信号函数,当后台子进程的命令执行结束后,会给父进程发送一个SIGCHLD信号,父进程的信号函数收到该信号后知道子进程执行结束了,于是执行waitpid系统函数回收子进程

小知识

僵尸进程

  • 每个进程都有一个父进程,当该进程执行结束后,其资源需要被父进程回收,否则会占用系统资源。
  • 父进程回收子进程的办法就是通过调用waitpid函数阻塞等待子进程的执行结束。
  • 僵尸进程就是指当一个进程结束之后,父进程由于一些原因迟迟不回收其子进程的资源,从而导致该进程一直占用着系统资源,这时该进程就成为僵尸进程

比如,当一个父进程由于业务原因需要一直运行,那么由他所创建的子进程结束之后,如果不进行回收就会成为一个僵尸进程,而该父进程由于业务原因也不可能阻塞等待子进程执行结束之后回收子进程,此时就需要设置信号函数来回收进程

孤儿进程

与僵尸进程对应的还有一个概念叫孤儿进程

前面我们说,每个进程都一个父进程,负责将其所创建的子进程执行结束之后回收子进程的资源,但前提是,父进程需要活到子进程执行结束的时候,因此如果该子进程执行结束之前,父进程先挂掉了,这种情况下的子进程就叫孤儿进程

但孤儿进程的危害其实并不大,尽管父进程挂掉了,但是还有祖宗收场,因此,系统会将孤儿进程挂到进程号为0,也就是init进程下,由他负责回收孤儿进程的资源

信号

  • 信号是进程间通信的一种机制,通常用来通知进程发生了某种事件,如中断、错误或特定条件的满足。
  • 信号可以被发送到进程以提醒其执行特定操作,例如终止、暂停或处理错误。
  • 常见的信号包括 SIGINT(中断信号)、SIGTERM(终止信号)等。通过信号,程序可以对系统事件进行响应,提高灵活性和控制能力。

本文中的shell代码中涉及到了SIGCHLD信号的使用,该信号用于子进程结束或停止时,通知父进程其子进程的状态改变。父进程可以通过捕获此信号来调用 wait()waitpid() 函数,以获取子进程的退出状态并回收资源。这有助于防止僵尸进程的产生,确保资源得到有效管理。

源码

main.cc

#include <iostream>
#include <string>
constexpr int CMDMAXSIZE = 1024;
void eval(const char *cmdline);
void shell() {
    char cmdline[CMDMAXSIZE] = {0};
    while (1) {
        std::cout << ">";
        // 读取stdin输入的命令行
        if (!fgets(cmdline, sizeof(cmdline), stdin)) {
            // 判断是否输入到文件尾
            if (feof(stdin))
                exit(0);
        }
        // 解析命令行并执行命令
        eval(cmdline);
    }
}

int main() {
    shell();
    return 0;
}

eval.cc

该文件用于解析命令后执行命令

#include <iostream>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
constexpr int ARGSIZE = 1024;

//解析命令并将其存储到argv,并返回该命令是一个后台命令
int parseline(const char *command, char **argv);
// 判断是否是一个shell内置命令,如果是就直接执行,否则返回false
int inlinecommand(char **argv);
// 后台回收进程
void backprogrom(int sig);

void eval(const char *cmdline) {
    if (!cmdline)
        return;
    char *argv[ARGSIZE] = {0};
    // 解析命令并判断是否是后台命令
    int bg = parseline(cmdline, argv);
    if (bg == -1)
        return;
    // 判断是否是shell内置命令,如果是就直接执行,并返回标志
    int flag = inlinecommand(argv);
    if (flag == -1)
        return;

    pid_t pid;
    // 判断是否是shell内置命令,如果是就处理内置命令,否则说明是一个可执行文件
    if (!flag) {
        // 无论是不是后台命令,都执行可执行文件
        if ((pid = fork()) == 0) {
            if (execve(argv[0], argv, environ) < 0) {
                std::cerr << "exec failed!" << std::endl;
                _exit(1);
            }
        } else if (pid > 0) {
            if (!bg) {
                // 如果不是后台命令,父进程需要阻塞等待子进程(也就是命令行)的执行
                wait(nullptr);
            } else if (bg > 0) { //如果是后台命令,父进行不需要阻塞等待子进程执行结束
                std::cout << "pid [" << pid << "] " << argv[0] << " running..."
                          << std::endl;
                signal(SIGCHLD, backprogrom);
            }
        }
    }
}

parseline.cc

该文件负责解析命令行及其选项,并返回判断是否为后台命令

#include <sstream>
#include <string>
#include <vector>
int parseline(const char *command, char **argv) {
    std::stringstream cmdline(command);
    std::vector<std::string> argv_vec;
    std::string cmd;
    while (cmdline >> cmd) {
        argv_vec.emplace_back(cmd);
    }
    if (argv_vec.empty())
        return -1;

    int argc = 0;
    for (auto &str : argv_vec) {
        argv[argc++] = const_cast<char *>(str.c_str());
    }

    if (argv_vec[argv_vec.size() - 1] == "&") {
        argv[argc - 1] = nullptr;
        return 1;
    }

    argv[argc] = nullptr;
    return 0;
}

inlinecommand.cc

该文件用于判断输入的命令是否是shell的内置命令,如果是就直接执行之

#include <iostream>
#include <stdlib.h>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <unordered_set>
constexpr int CMDMAXSIZE = 1024;

static std::unordered_set<std::string> inlinecmd = {"ls", "pwd", "clear"};

int inlinecommand(char **argv) {
    std::string cmd(argv[0]);
    if (cmd == "exit") {
        exit(1);
    }
    auto it = inlinecmd.find(cmd);
    if (it != inlinecmd.end()) {
        pid_t pid = fork();
        if (pid == 0) {
            if (execvp(cmd.c_str(), argv) < 0) {
                std::cout << argv[0] << " exec error!" << std::endl;
            }
        }
        return 1;
    }
    return 0;
}

backwait.cc

该文件用于回收后台命令所在的子进程的资源

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
/*
1. exit
库:exit 定义在 <stdlib.h> 中。
功能:exit 在程序终止时会执行以下操作:
    调用所有已注册的 atexit 函数。
    刷新标准 I/O 缓冲区,确保所有数据都被写入。
    返回控制给操作系统,返回值为 exit 的参数。
适用场景:通常用于正常退出程序。
2. _exit
库:_exit 定义在 <unistd.h> 中。
功能:_exit 直接终止进程,不执行任何清理操作:
    不调用 atexit 函数。
    不刷新 I/O 缓冲区。
    直接将控制权返回给操作系统,返回值为 _exit 的参数。
适用场景:通常在子进程中使用,如在
    fork后立即退出,以避免在父进程和子进程之间共享未刷新缓冲的数据。
*/
ssize_t sio_puts(const char s[]) { return write(STDOUT_FILENO, s, strlen(s)); }
void sio_error(const char s[]) {
    sio_puts(s);
    return _exit(1);
}

void backprogrom(int sig) {
    int olderrno = errno;
    while (waitpid(-1, nullptr, WNOHANG) > 0) {
        sio_puts("reaped child\n");
    }
    if (errno != ECHILD)
        sio_error("wait_pid error!");
    errno = olderrno;
}

测试效果

说明,本代码仅用于帮助理解shell执行命令的过程,但是对后台命令的处理稍显不足,并且对前台命令和后台命令的回收处理也仍旧需要改进,烦请大佬帮忙指正!

参考:《深入理解计算机系统》

【Linux】fork函数详解|多进程_多进程fork-CSDN博客

孤儿进程与僵尸进程[总结] - Rabbit_Dale - 博客园 (cnblogs.com)

 ​​​​​​孤儿进程与僵尸进程产生及其处理_孤儿进程的意义-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/882157.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【ArcGIS微课1000例】0123:数据库中要素类批量转为shapefile

除了ArcGIS之外的其他GIS平台,想要打开ArcGIS数据库,可能无法直接打开,为了便于使用shp,建议直接将数据库中要素类批量转为shapefile。 文章目录 一、连接至数据库二、要素批量转shp一、连接至数据库 打开ArcMap,或者打开ArcCatalog,找到数据库连接,如下图: 数据库为个…

Laravel邮件发送:从配置到发邮件的指南!

Laravel邮件发送功能如何实现&#xff1f;怎么使用Laravel发信&#xff1f; Laravel作为一款流行的PHP框架&#xff0c;提供了强大且易用的邮件发送功能。AokSend将详细介绍如何从配置到实际发送邮件的全过程&#xff0c;帮助你快速掌握Laravel邮件发送的技巧。 Laravel邮件发…

数据中台!企业的必备还是可有可无?(附数据中台构建完整脑图)

数据中台&#xff01;企业的必备还是可有可无&#xff1f;&#xff08;附数据中台构建完整脑图&#xff09; 前言数据中台 前言 在这个数据爆炸的时代&#xff0c;数据已经成为了企业最宝贵的资产之一。然而&#xff0c;如何有效地管理和利用这些数据&#xff0c;却是许多企业…

Linux驱动开发 ——架构体系

只读存储器&#xff08;ROM&#xff09; 1.作用 这是一种非易失性存储器&#xff0c;用于永久存储数据和程序。与随机存取存储器&#xff08;RAM&#xff09;不同&#xff0c;ROM中的数据在断电后不会丢失&#xff0c;通常用于存储固件和系统启动程序。它的内容在制造时或通过…

Blender软件三大渲染器Eevee、Cycles、Workbench对比解析

Blender 是一款强大的开源3D制作平台&#xff0c;提供了从建模、雕刻、动画到渲染、后期制作的一整套工具&#xff0c;广泛应用于电影、游戏、建筑、艺术等领域。 渲染101云渲染云渲6666 相比于其他平台&#xff0c;如 Autodesk Maya、3ds Max 或 Cinema 4D&#xff0c;Blende…

Error when custom data is added to Azure OpenAI Service Deployment

题意&#xff1a;在向 Azure OpenAI 服务部署添加自定义数据时出现错误。 问题背景&#xff1a; I receive the following error when adding my custom data which is a .txt file (it doesnt matter whether I add it via Azure Cognitive Search, Azure Blob Storage, or F…

药用植物的空间多组学:从生物合成途径到工业应用-文献精读51

Spatial multi-omics in medicinal plants: from biosynthesis pathways to industrial applications 药用植物的空间多组学&#xff1a;从生物合成途径到工业应用 摘要 随着分子测序和成像技术的快速发展&#xff0c;药用植物的多组学研究进入了单细胞时代。我们讨论了空间多…

振弦式渗压计常见故障有哪些?怎么解决?

振弦式渗压计是一种用于测量结构物或土体内部渗透水压力的仪器&#xff0c;广泛应用于土木工程、水利工程及环境监测领域。在使用过程中&#xff0c;可能会遇到一些常见的故障&#xff0c;以下是一些故障及其解决方法&#xff1a; 1. 读数不稳定&#xff1a; - 确保渗压计安装在…

React18入门教程

React介绍 React由Meta公司开发&#xff0c;是一个用于 构建Web和原生交互界面的库 React的优势 相较于传统基于DOM开发的优势 组件化的开发方式 不错的性能 相较于其它前端框架的优势 丰富的生态 跨平台支持 React的市场情况 全球最流行&#xff0c;大厂必备 开发环境…

2024年数学建模比赛题目及解题代码

目录 一、引言 1. 1竞赛背景介绍 1.1.1数学建模竞赛概述 1.1.2生产过程决策问题在竞赛中的重要性 1.2 解题前准备 1.2.2 工具与资源准备 1.2.3 心态调整与策略规划 二、问题理解与分析 三、模型构建与求解 3.1 模型选择与设计 3.1.1 根据问题特性选择合适的数学模型类…

线程池的执行流程和配置参数总结

一、线程池的执行流程总结 提交线程任务&#xff1b;如果线程池中存在空闲线程&#xff0c;则分配一个空闲线程给任务&#xff0c;执行线程任务&#xff1b;线程池中不存在空闲线程&#xff0c;则线程池会判断当前线程数是否超过核心线程数&#xff08;corePoolSize&#xff09…

【超详细】基于YOLOv8训练无人机视角Visdrone2019数据集

主要内容如下&#xff1a; 1、Visdrone2019数据集介绍 2、下载、制作YOLO格式训练集 3、模型训练及预测 4、Onnxruntime推理 运行环境&#xff1a;Python3.8&#xff08;要求>3.8&#xff09;&#xff0c;torch1.12.0cu113&#xff08;要求>1.8&#xff09;&#xff0c…

[Python]一、Python基础编程(2)

F:\BaiduNetdiskDownload\2023人工智能开发学习路线图\1、人工智能开发入门\1、零基础Python编程 1. 文件操作 把⼀些内容 ( 数据 )存储存放起来,可以让程序下⼀次执⾏的时候直接使⽤,⽽不必重新制作⼀份,省时省⼒ 。 1.1 文件的基本操作 1. 打开文件 2. 读写操作 3. 关闭…

【Go】Go语言切片(Slice)深度剖析与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

[PICO VR]Unity如何往PICO VR眼镜里写持久化数据txt/json文本

前言 最近在用PICO VR做用户实验&#xff0c;需要将用户实验的数据记录到PICO头盔的存储空间里&#xff0c;记录一下整个过程 流程 1.开启写入权限 首先开启写入权限&#xff1a;Unity->Edit->Player->安卓小机器人->Other Settings->Configuration->Wri…

Spring的任务调度

Spring的任务调度 1.概述 Spring框架为任务调度提供了专门的解决方案。在Spring框架的org.springframework.scheduling包中&#xff0c;通过对JDK 的ScheduledExecutorService接口的实例进行封装&#xff0c;对外提供了一些注解和接口&#xff0c;为开发者处理定时任务提供了…

力扣面试150 添加与搜索单词 - 数据结构设计 字典树

Problem: 211. 添加与搜索单词 - 数据结构设计 &#x1f469;‍&#x1f3eb; 参考题解 public class WordDictionary {// 定义一个内部类 Node&#xff0c;用于表示 Trie&#xff08;前缀树&#xff09;中的每个节点class Node{// 每个节点有一个大小为 26 的数组&#xff0c…

C#如何把写好的类编译成dll文件

1 新建一个类库项目 2 直接改写这个Class1.cs文件 3 记得要添加Windows.Forms引用 4 我直接把在别的项目中做好的cs文件搞到这里来&#xff0c;连文件名也改了&#xff08;FilesDirectory.cs&#xff09;&#xff0c;这里using System.Windows.Forms不会报错&#xff0c;因为前…

Spring Boot管理用户数据

目录 学习目标前言Thymeleaf 模板JSON 数据步骤 1: 创建 Spring Boot 项目使用 Spring Initializr 创建项目使用 IDE 创建项目 步骤 2: 添加依赖步骤 3: 创建 Controller步骤 4: 新建index页面步骤 5: 运行应用程序 表单提交步骤 1: 添加 Thymeleaf 依赖在 Maven 中添加依赖 步…

探索 ShellGPT:终端中的 AI 助手

文章目录 探索 ShellGPT&#xff1a;终端中的 AI 助手背景介绍ShellGPT 是什么&#xff1f;如何安装 ShellGPT&#xff1f;简单的库函数使用方法场景应用常见问题及解决方案总结 探索 ShellGPT&#xff1a;终端中的 AI 助手 背景介绍 在当今快速发展的技术领域&#xff0c;命…