基于c++实现的简易shell

news2024/11/15 18:32:54

代码逻辑

核心思想

  • 解析命令行,拆解命令及其选项
  • 创建子进程,在子进程中执行命令
  • 如果是前台执行命令,则父进程就阻塞等待子进程中命令执行结束后回收子进程的资源
  • 如果是后台执行命令,则父进程不进行阻塞等待,可继续向下运行,但为了防止僵尸进程的出现,需要设置一个信号函数,当后台子进程的命令执行结束后,会给父进程发送一个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.coloradmin.cn/o/2157160.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32 软件触发ADC采集

0.91寸OLED屏幕大小的音频频谱&#xff0c;炫酷&#xff01; STM32另一个很少人知道的的功能——时钟监测 晶振与软件的关系&#xff08;深度理解&#xff09; STM32单片机一种另类的IO初始化方法 ADC是一个十分重要的功能&#xff0c;几乎任何一款单片机都会包含这个功能&a…

记一次MySQL索引不当引发死锁问题

一、前言 在并发量很低的情况下&#xff0c;mysql 的响应时延一切正常&#xff0c;一旦并发量上去了&#xff0c;mysql就会出现死锁的情况&#xff0c;你有没有遇到过&#xff1f;到底是是什么原因导致的呢&#xff0c;让我们一起看看真实的案例。 二、遇到的问题 先介绍一下…

LabVIEW提高开发效率技巧----利用第三方库和工具

LabVIEW开发不仅依赖于自身强大的图形化编程能力&#xff0c;还得益于其庞大的用户社区和丰富的第三方库。这些工具和库能够帮助开发者快速解决问题&#xff0c;提升开发效率&#xff0c;避免从头开始编写代码。 1. LabVIEW工具网络&#xff08;NI Tools Network&#xff09; …

MateBook 16s 2023在Deepin下开启性能模式,调节风扇转速到最大,全网首发!

方法 在Deepin下按住Fnp快捷键&#xff0c;开启性能模式。 验证 首先去debian下载acpi-call-dkms https://packages.debian.org/sid/all/acpi-call-dkms/download 然后使用root用户执行&#xff1a; apt install --simulate ./acpi-call-dkms_1.2.2-2.1_all.deb apt inst…

LeetCode 面试经典150题 191.位1的个数

Java中的算术右移和逻辑右移的区别 题目&#xff1a;编写一个函数&#xff0c;获取一个正整数的二进制形式并返回其二进制表达式中设置位的个数&#xff08;也被称为汉明重量&#xff09;。 设置位的个数即二进制中1的个数。 思路&#xff1a;方法一&#xff1a;因为正数的原…

基于阿里云免费部署Qwen1-8B-chat模型并进行lora参数微调从0到1上手操作

文章目录 一、申请资源二、创建实例三、克隆微调数据四、部署Qwen1-8B-chat模型1、环境配置2、模型下载3、本地模型部署 五、模型微调1、拉取Qwen仓库源码2、微调配置3、合并微调参数4、本地部署微调模型 一、申请资源 阿里云账号申请PAI资源详细教程我已于部署ChatGLM3时写过…

Golang | Leetcode Golang题解之第430题扁平化多级双向链表

题目&#xff1a; 题解&#xff1a; func dfs(node *Node) (last *Node) {cur : nodefor cur ! nil {next : cur.Next// 如果有子节点&#xff0c;那么首先处理子节点if cur.Child ! nil {childLast : dfs(cur.Child)next cur.Next// 将 node 与 child 相连cur.Next cur.Chi…

遗传算法与深度学习实战(14)——进化策略详解与实现

遗传算法与深度学习实战&#xff08;14&#xff09;——进化策略详解与实现 0. 前言1. 进化策略1.1 进化策略原理1.2 将进化策略应用于函数逼近 2. 实现进化策略小结系列链接 0. 前言 进化策略 (Evolutionary Strategies, ES) 是进化计算和遗传方法的扩展&#xff0c;增加了控…

【Python学习手册(第四版)】学习笔记24-高级模块话题

个人总结难免疏漏&#xff0c;请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。 本来计划中秋发布几篇文章&#xff0c;结果阳了&#xff0c;发烧、头疼、咽疼&#xff0c;修养了近一周&#xff0c;还没好完。希望大家都能有个好身体&#xff0…

proteus仿真软件简体中文版网盘资源下载(附教程)

对于电子通信专业的小伙伴来说&#xff0c;今天文章的标题应该不会陌生。Proteus是一款具有广泛应用的仿真软件&#xff0c;它的功能非常强大&#xff0c;适用于所有单片机的仿真工作&#xff0c;能够从原理图、调试、到与电路的协同仿真一条龙全部搞定&#xff0c;受到所有用户…

自己开发了一个电脑上滚动背单词的软件

在这个快节奏的时代&#xff0c;我们每天都在忙碌中度过&#xff0c;手机虽然方便&#xff0c;但往往难以找到一整块时间来专心背单词。然而&#xff0c;你是否意识到&#xff0c;每天坐在电脑前的时间远比使用手机的时间要长&#xff1f;现在我们来介绍一个新型的学习软件灵思…

Fyne ( go跨平台GUI )中文文档-容器和布局 (四)

本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法 go代码展示为Go 1.16 及更高版本, ide为goland2021.2 这是一个系列文章&#xff1a; Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne ( go跨平台GUI…

XSS—xss-labs靶场通关

level 1 JS弹窗函数alert() <script>alert()</script> level 2 闭合绕过 "> <script>alert()</script> <" level 3 onfocus事件在元素获得焦点时触发&#xff0c;最常与 <input>、<select> 和 <a> 标签一起使用…

科研绘图系列:R语言多个AUC曲线图(multiple AUC curves)

文章目录 介绍加载R包导入数据数据预处理画图输出结果组图系统信息介绍 多个ROC曲线在同一张图上可以直观地展示和比较不同模型或方法的性能。这种图通常被称为ROC曲线图,它通过比较不同模型的ROC曲线下的面积(AUC)大小来比较模型的优劣。AUC值越大,模型的诊断或预测效果越…

生成自签名证书和私钥

可以使用 OpenSSL 来生成自签名证书&#xff08;linux上执行&#xff09;&#xff1a; openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes生成 key.pem&#xff08;私钥&#xff09;和 cert.pem&#xff08;证书&#xff09; 当执行这个 …

某集群管理系统存在任意文件读取漏洞

你为什么要拼命努力&#xff1f;父母的白发&#xff0c;想去的地方很远&#xff0c;想要的东西很贵&#xff0c;喜欢的人很优秀&#xff0c;周围人的嘲笑&#xff0c;以及&#xff0c;天生傲骨。 漏洞描述 利用漏洞&#xff0c;攻击者可以读取 Windows 或 Linux 服务器上的任…

ER论文阅读-Decoupled Multimodal Distilling for Emotion Recognition

基本介绍&#xff1a;CVPR, 2023, CCF-A 原文链接&#xff1a;https://openaccess.thecvf.com/content/CVPR2023/papers/Li_Decoupled_Multimodal_Distilling_for_Emotion_Recognition_CVPR_2023_paper.pdf Abstract 多模态情感识别&#xff08;MER&#xff09;旨在通过语言、…

基于STM32残疾人辅助行走系统

要么是家人陪伴&#xff0c;要么是类似导盲犬的动物辅助&#xff0c;家人还有事要做&#xff0c;不一定实时在场&#xff0c;而动物辅助也可能会出现新的问题&#xff0c;威胁残疾人身体安全。因此利用现代计算机技术、传感器检测设备和物联网技术设计这一款辅助残疾人行走的智…

.NET常见的5种项目架构模式

前言 项目架构模式在软件开发中扮演着至关重要的角色&#xff0c;它们为开发者提供了一套组织和管理代码的指导原则&#xff0c;以提高软件的可维护性、可扩展性、可重用性和可测试性。 假如你有其他的项目架构模式推荐&#xff0c;欢迎在文末留言&#x1f91e;&#xff01;&a…

基于微信小程序的家教信息管理系统的设计与实现(论文+源码)_kaic

摘 要 随着互联网时代的来临&#xff0c;使得传统的家教模式已不复存在&#xff0c;亟需一种方便、快捷的在线教学平台。因此&#xff0c;利用Java语言作为支撑和MySQL数据库存储数据&#xff0c;结合微信小程序的便利性&#xff0c;为用户开发出了一个更加人性化、方便的家庭…