2026/2/23 2:29:25
网站建设
项目流程
出口贸易网站,GTA5房产网站建设中,wordpress怎么解绑域名,工商营业执照在线查询本系列主要旨在帮助初学者学习和巩固Linux系统。也是笔者自己学习Linux的心得体会。 个人主页#xff1a; 爱装代码的小瓶子 文章系列#xff1a; Linux 2. C 文章目录 1. 回忆上一篇文章我们所写的Shell#xff1a;2.前置知识点准备#xff1a;2-1.外部命令和内嵌命令…本系列主要旨在帮助初学者学习和巩固Linux系统。也是笔者自己学习Linux的心得体会。个人主页爱装代码的小瓶子文章系列Linux2.C文章目录1. 回忆上一篇文章我们所写的Shell2.前置知识点准备2-1.外部命令和内嵌命令 2-2getenv(PWD) 和 getcwd() 的区别2-3 环境变量表和命令行表的维护理由一“父传子”的继承机制 (为了子进程)理由二保持“逻辑状态”与“物理状态”的一致 (为了 cd)理由三配置的持久化 (为了 export)3. 开始行动 改进shell3-1 开始构建内嵌命令:3-2 重写GetPwd()4. 维护系统变量表总结1. 回忆上一篇文章我们所写的Shell【C与Linux 基础】进程篇 -还在怕进程控制带你用C手写一个简易Shell里面已经写过了简单的shell程序但是可惜的事情我们并没有完成对内嵌命令的支持。你可以尝试使用cd和echo你会发现他是不是支持的还要export命令他也是不支持的。这是因为我们没有在shell内部去实现。为了后面的Shell更容易理解这里我们还是先讲一些知识点当你看完这个就能知道外部命令 (External Command)和内嵌命令 (Built-in Command)。PWD vs getcwd两者有何区别环境变量表和命令行表的维护。2.前置知识点准备2-1.外部命令和内嵌命令 引入我们可以看一下这个场景当我们运行我们自己的程序的时候。输入cd 指定的地址结果查询pwd发现所处的路径没有发生改变。这并不是你的错误而是我们之前的程序没有实现这个错误。我们来想一想上面这个场景为什么会发生这个问题intExecute(){pid_t idfork();if(id0){//childexecvp(g_argv[0],g_argv);exit(1);}//父亲开始等待pid_t ridwaitpid(id,nullptr,0);(void)rid;return0;}当我们我们获取cd这个命令的时候我们利用子程序来完成这个命令的确子进程改变了自己的位置但是父进程并没有改变。当子程序执行被execvp里面的替换执行后他就自己退出了并不能影响或者改变shell这个进程。由此我们可以引入什么是内嵌命令什么是外部命令什么是内嵌命令也叫内建命令是指直接包含在 Shell 程序本身内部的指令而不是磁盘上的某个独立的可执行文件。外部命令 (External Command): 当你输入 ls 或 vim 时Shell 会去磁盘的 /bin 或 /usr/bin 目录下寻找对应的程序然后创建一个新的子进程 (fork) 来运行它。内嵌命令 (Built-in Command): 当你输入 cd 或 echo 时Shell 不会创建子进程而是直接调用 Shell 程序内部的一个函数来执行。我们再来说说为什么echo他也是内嵌命令其实很多像echo命令一样它既是内嵌命令也是外部命令他有两个实现但是其实主要还是内嵌命令虽然 /bin/echo 也是存在的但 Shell 通常默认使用内建的 echo。性能原因: 创建进程 (fork exec) 的开销很大。echo 通常只打印简短的字符串如果为了打印几个字就启动一个新进程属于“杀鸡用牛刀”效率极低。方便访问 Shell 变量: 内建 echo 可以更方便地处理 Shell 内部的特殊变量和转义字符。还要export这个必须导入我们自己实现的shell中如果导入子程序中后面他还是会结束的并不能作用在父进程上面同样也需要内嵌实现。2-2getenv(PWD)和getcwd()的区别在 C 程序中这两者都可以用来获取“当前工作目录”但来源、可靠性、实现方式和适用场景完全不同。我们先来说说getenv(PWD)这个就是我们在上一版在用的代码如下constchar*GetPwd(){char*namegetenv(PWD);returnnamenullptr?None:name;}来源:它是去查环境变量表读取名为PWD的那个变量的值。本质:这是一个字符串读取操作。缺点:它是不可靠的。环境变量是可以被用户或脚本随意修改的例如export PWD/tmp即使你人不在/tmp。如果你通过硬链接跳转或者程序崩溃导致环境变量没更新这个值可能就是错的。它只是 Shell 维护的一个“逻辑状态”。比如如果程序用chdir()改变了目录但没有更新环境变量 PWD它会返回旧值。提前剧透我们需要用chdir()这个函数。再来看看getcwd() (Get Current Working Directory)来源:这是一个系统调用 (System Call)。它直接询问操作系统内核“我现在到底在哪里”本质:内核会去查该进程的文件系统描述符根据 inode 信息反向推导出绝对路径。优点:它是绝对真实的物理状态。缺点:相比读取内存中的变量系统调用的开销稍大微乎其微这个就是准确的在后序的实现中我们需要实现并且使用。2-3 环境变量表和命令行表的维护我们来看看我们上一个程序其中有一行注释是错误的在上一行里面我们只维护了一个命令行表为了让我们的shell支持自己环境变量表这个也是需要我们维护的。为什么需要维护其实结合上面的你就可以理解了为什么需要维护环境变量表了理由一“父传子”的继承机制 (为了子进程)这是最重要的原因。Shell 本身不仅仅是用来运行的它是用来启动别人的。场景: 你在你的 Shell 里启动了vim。过程:fork()- 子进程继承父进程的环境变量 -exec。如果你不维护:假设用户改了PATH(export PATH/my/custom/bin:$PATH)但你没有更新环境变量表。然后用户输入mytool这个工具在/my/custom/bin下。结果: 你的 Shell 找不到它或者即便你找到了启动的子进程如果内部想调用同目录下的其他脚本也会失败。再比如: 你没维护TERM变量。用户启动vimvim发现环境变量里没有TERM它不知道屏幕多大不知道支持什么颜色直接报错退出或者变成黑白模式。理由二保持“逻辑状态”与“物理状态”的一致 (为了 cd)这是我们在上一个问题里讨论的PWD问题。物理状态: 内核里的cwd(Current Working Directory)。逻辑状态: 环境变量里的PWD。为什么不同步很危险:有些程序比如ls显示超链接或者一些构建工具make并不直接调系统调用getcwd而是为了快直接读$PWD。如果你用chdir走了却没改PWD变量这些工具就会以为你还在原来的地方导致逻辑错误比如生成的文件路径不对。理由三配置的持久化 (为了 export)用户在 Shell 里最常用的操作之一就是配置环境。用户操作:export JAVA_HOME/usr/local/java。你的任务: 用户希望从此以后在这个 Shell 里启动的所有 Java 程序都能读到这个路径。如果你不维护:用户敲了命令你没把它写进environ表。用户紧接着输入java -version。Java 程序启动了去读JAVA_HOME发现是空的。结果: 用户会觉得你的 Shell 是坏的“我明明设置了环境变量为什么不生效”3. 开始行动 改进shell3-1 开始构建内嵌命令:我们之前的主函数的执行顺序如下打印前置的格式获取用户输入的命令。分析用户输入的指令利用子进程来完成执行。我们只需在第三部和第四步骤之间加上检查是不是内嵌命令就可以了。我们先来完善内嵌函数命令的检查boolCheckBuildIn(){std::string cmdg_argv[0];if(cmdcd){cd();returntrue;}elseif(cmdecho){echo();returntrue;}//目前只完成这两个暂时做个示例returnfalse;}我这里只完成了者两个其他的类似于export可以自己尝试完善。我们这里只要是echo和cd都是由shell本身这个父进程来完成。我们先来看看什么cd命令是怎么实现的boolcd(){if(g_argc1){//说明就只有一个cdstd::string homeGetHome();if(home.empty())returntrue;chdir(home.c_str());}else{std::string whereg_argv[1];if(where-){//todo}elseif(where~){//todo}else{chdir(where.c_str());}}returnfalse;}其中chdir是改变目录的系统调用函数可以帮助进程改变位置。这样我们就可以完成执行cd命令。我们可以看到这个pwd是发生了改变。但是打印的这个还是没有变化。这个需要后面才能解决。接下来我们解决echo命令这个主要是打印和查询环境变量和退出码voidecho(){if(g_argc2){std::string optg_argv[1];if(opt$?){//查询最近一次的退出码std::coutlastcodestd::endl;lastcode0;//打印完成就设置为0}elseif(opt[1]$){std::string env_nameopt.substr(1);constchar*env_valuegetenv(env_name.c_str());std::coutenv_valuestd::endl;}else{std::coutoptstd::endl;}}}除了这个上面的代码我们还需要设置一个全局变量最近任务的退出码。注意的是我这里的退出码就只争对了子进程不是内嵌命令。那么对于执行函数也是需要发生改变的。intExecute(){pid_t idfork();if(id0){//childexecvp(g_argv[0],g_argv);exit(1);}//父亲开始等待intstatus0;pid_t ridwaitpid(id,status,0);if(rid0){lastcodeWEXITSTATUS(status);//运行程序的退出码。}return0;}这样就完成了查询最近的退出码看看结果是怎么样的可以看到还打印了一个$这是怎么回事这是主函数出了问题致命错误内建命令执行完后没有“拦住”外部命令。当你知道echo是内嵌命令但是后面还是利用子进程打印了这个$?.所以后面还是需要改变的重新改变之后我们可以看到是没有发生变化的。这是没有问题的。改变之后的主函数如下intmain(){while(true){//1.先打印出一个类似于[user 域名 当前pwd]://需要什么变量user 和 localhost 还有pwd这些都在环境变量表里面PrintCommand();//2. 获取用户命令charcommandline[COMMAND_SIZE];GetCommandLine(commandline,sizeof(commandline));//3.分割命令CommandParse(commandline);//4 检查是不是内嵌命令if(CheckBuildIn())continue;//5.利用子进程来执行Execute();}这样就问题不大了。3-2 重写GetPwd()我们在上面的图片其实也可以看到pwd一直不变化这里我们来尝试改写让他随着cd命令变化而变化。随后解释为什么之前不变化。新增两个暂时的表charcwd[1024];charcwd_env[1048];利用两个表来完成获取pwd当cd变化的时候get的值也会发生变化constchar*GetPwd(){char*cgetcwd(cwd,sizeof(cwd));if(c!nullptr){snprintf(cwd_env,sizeof(cwd_env),PWD%s,cwd);//把cwd里面的地址给环境表putenv(cwd_env);//环境表加入系统环境变量}returncnullptr?None:c;}这个“顺手”发生在你的 main 循环里。请看整个流程用户输入 cd /tmp你的 CheckBuildIn - Cd - chdir(“/tmp”) 被执行。此时此刻物理目录变成了 /tmp但环境变量表里的 PWD 还是 /home/wwh。状态是不一致的但是没关系程序继续往下跑。while(true) 循环进入下一轮代码执行到第一行PrintCommand()。打印提示符PrintCommand 想要打印 [wwhhost /tmp]#它必须知道当前在哪。于是它调用了 GetPwd()。触发同步Magic Happens HereGetPwd 一运行马上执行了 getcwd拿到 /tmp。紧接着它执行了 putenv。就在这一瞬间环境变量表里的 PWD 被修正成了 /tmp。只要你调用 GetPwd()比如在打印提示符的时候它就会顺手把环境变量 PWD 给修正了。保证了显示出来的路径和实际所在的路径永远是一致的。4. 维护系统变量表voidInitEnv(){externchar**environ;memset(g_env,0,sizeof(g_env));g_envs0;//本来要从配置文件来//1. 获取环境变量for(inti0;environ[i];i){// 1.1 申请空间g_env[i](char*)malloc(strlen(environ[i])1);strcpy(g_env[i],environ[i]);g_envs;}g_env[g_envs](char*)HAHAfor_test;//for_testg_env[g_envs]NULL;//2. 导成环境变量for(inti0;g_env[i];i){putenv(g_env[i]);}environg_env;}这个还是利用C语言的malloc来完成的我就不在我的shell里面实现了。总结我们今天认识了三个前置知识点完成对shell的小改进也许还是不太像真正的shell但是也是很辛苦各位了代码已近资源绑定。本文写的也不算很好但是我一进尽力了还希望大家能多点点赞。这段代码不仅仅是一个简易的 Shell 解释器更是一次向 Linux 内核深处进发的孤独探险。从最初面对空白光标的迷茫到亲手用fork刻画出父子进程的离合用exec完成灵魂的替换我们曾在字符串解析的碎片中迷失在野指针的边缘试探更在环境变量与物理路径的“精神分裂”中反复挣扎。这一路是为了让cd不再只是一个命令而是进程状态的真实跳动是为了让$?不再只是一个符号而是父进程对子进程最负责的守望。这几百行代码凝结的不仅是逻辑的闭环更是你终于读懂了操作系统那沉默而精密脉搏后的豁然开朗——原来每一个闪烁的提示符背后都站着一个不断修正自我、维护秩序的灵魂。感谢各位对本篇文章的支持。谢谢各位点个三连吧