当前位置: 首页 > ds >正文

操作系统之shell实现(下)

🌟 各位看官好,我是maomi_9526

🌍 种一棵树最好是十年前,其次是现在!

🚀 今天来学习C语言的相关知识。

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦

目录

1. 进程程序替换

2.exec函数

2.1 execl 

2.2 execlp 

2.3 execle 

2.4 execv 

2.5 execvp 

2.6 execvpe 

 2.7execve

2.8命名理解

3.进程替换

3.1进程替换原理

4. 自主Shell命令行解释器

4.1获取当前环境信息

4.2输出命令行提示符

4.3获取命令行输入

4.4执行命令行

4.4.1执行内建命令 

4.4.2执行外部命令

4.5更新环境变量

3. Shell 实现完整代码


1. 进程程序替换

  • fork() 系统调用创建一个子进程,父子进程开始执行相同的程序代码。若子进程要执行一个不同的程序,可以使用 exec 系列函数来实现程序的替换。

  • 这些 exec 函数会加载一个全新的程序(包括代码和数据)到子进程的地址空间中,并从新程序的入口点开始执行,原有的程序代码被替换掉。exec 函数系列中最常用的是 execve,其他的 execl, execlp, execv, execvp, execle 等只是 execve 的不同封装。

2.exec函数

头文件:#include<unistd.h>

返回值:当失败时返回-1

2.1 execl 

int execl(const char *path, const char *arg, ...);

execl("/usr/bin/ls","ls","-l",NULL);
2.2 execlp 

int execlp(const char *file, const char *arg, ...);

execlp("ls","ls","-l",NULL);
2.3 execle 

int execle(const char *path, const char *arg, ..., char * const envp[]);

extern char**environ;//声明全局环境变量
execle("/usr/bin/ls","1s","-l","-a",NULL,environ};
2.4 execv 

int execv(const char *path, char *const argv[]);

char*argv[]={"1s","-l","-a",NULL};execv("/usr/bin/ls",argv);
2.5 execvp 

int execvp(const char *file, char *const argv[]);

char*argv[]={"1s","-l","-a",NULL};execvp("ls",argv);
2.6 execvpe 

int execvpe(const char *file, char *const argv[],char *const envp[]);


char*argv[]={"ls","-a","-l",NULL};
execvpe("ls",argv,environ);
     2.7execve

    系统调用函数execve

    上面的exec系列函数本质上都不是系统级别的调用,都是对execve的语言级别的封装

    int execve(const char *filename, char *const argv[], char *const envp[]);

     

    2.8命名理解
    • l(list) : 表示参数采用列表
    • v(vector) : 参数用数组
    • p(path) : 有p自动搜索环境变量PATH
    • e(env) : 表示自己维护环境变量
    函数级别函数名列表传参是否带路径是否使用当前环境变量
    语言级别execl列表
    execlp列表
    execle列表
    execv数组
    execvp数组
    execvpe数组
    系统级别execve数组

    3.进程替换

    3.1进程替换原理

    当进程执行了代码替换操作后,原先加载的代码会被新的代码所替换。

    此时,原有的代码不再存在于进程的地址空间中,执行流转向新的代码。具体来说,在进程替换时,原代码的内存空间被新的代码段覆盖,新的代码开始运行。此过程的本质是将进程的代码区域替换为新的内容,从而导致原有代码失效并不可再访问。

    所以原来代码我的进程执行完毕并不会出现。 

    4. 自主Shell命令行解释器

    • 通过实现一个自定义的 shell,可以处理命令行输入,并根据输入执行对应的命令。Shell 需要有以下功能:

    4.1获取当前环境信息

    getenv() 是一个 C 标准库函数,用于从环境变量中获取指定名称的值。环境变量是系统级的变量,它们存储了操作系统和程序运行时需要的配置信息,比如系统路径、用户设置等。getenv() 函数通过读取这些环境变量,允许程序动态地获取环境设置。

    头文件:#include<stdlib.h>

    函数:char *getenv(const char *name);

    返回值:

    • 成功:如果找到了指定名称的环境变量,getenv() 会返回该变量的值(一个指向字符数组的指针,代表该环境变量的值)。

    • 失败:如果未找到指定的环境变量,getenv() 返回 NULL

    代码实现:

    //获取当前环境信息
    const char* GETPWD()
    {char *pwd=getenv("PWD");return pwd==NULL?"None":pwd;
    }//获取用户信息
    const char*GETUSER()
    {char*user=getenv("USER");return user==NULL?"None":user;
    }//获取系统信息
    const char*GETHOSTNAME()
    {char*hostname=getenv("HOSTNAME");return hostname==NULL?"None":hostname;
    }
    
    4.2输出命令行提示符

    snprintf 是 C 语言标准库中的一个函数,属于 stdio.h 头文件。它的作用是将格式化的数据输出到一个字符数组中,并且保证不会发生缓冲区溢出。snprintf 函数是对 sprintf 的一种改进,主要是增加了一个最大字符数的限制,避免了 sprintf 在没有足够空间时造成内存溢出的风险。 

    头文件:#include<stdio.h>

    int snprintf(char *str, size_t size, const char *format, ...);

    返回值:

    • 成功:返回写入字节数(当被写入内容超过写入大小,发生截断)

    • 失败:返回负数 

    #define COMMAND_SIZE 1024
    #define FORMAT "[%s@%s %s]#"
    void MakeCMDPrompt(char cmdprompt[],size_t size)//制作命令行提示符
    {snprintf(cmdprompt,size,FORMAT,GETUSER(),GETHOSTNAME(),GETPWD());
    }
    void PrintCMDPrompt()//打印命令行提示符
    {char prompt[COMMAND_SIZE];MakeCMDPrompt(prompt,sizeof(prompt));printf("%s",prompt);
    }
    
    4.3获取命令行输入

    fgets 是 C 语言标准库中的一个函数,属于 stdio.h 头文件。它的作用是从指定的文件流中读取一行字符串,并将读取的内容存储到一个字符数组中。与 gets 不同,fgets 可以避免缓冲区溢出的问题,因为它会限制读取的字符数。 

    头文件:#include<stdio.h>

    char *fgets(char *s, int size, FILE *stream);

    返回值:

    • 成功 :返回写入的s的位置
    • 失败:返回NULL

     代码实现:

    //接受命令行
    bool MakeCMDLine(char*out,size_t size)
    {char*line=fgets(out,size,stdin);if(line==NULL) return false;//返回值为空,写入失败out[strlen(out)-1]=0;//去除输入的换行符if(strlen(out)==0) return false;return true;
    }

    4.2解析命令行

    将用户输入的命令解析成可执行的命令和参数。

    strtok 是 C 语言标准库中的一个函数,属于 string.h 头文件。它用于将一个字符串分割成一系列子字符串(tokens),根据指定的分隔符。该函数通常用于处理由空格、逗号、换行符等字符分隔的文本数据。

    char *strtok(char *str, const char *delim);

    • str:待分割的字符串。如果是第一次调用 strtok,该参数应为需要分割的字符串;如果是后续调用,应该传递 NULL,以继续分割上一次传入的字符串。

    • delim:分隔符字符串,定义了用于分割字符串的字符集合。可以是单个字符,也可以是多个字符,strtok 会将字符串中的任何一个分隔符都视为分隔点。

    //分割字符串
    bool CMDLinePrase(char *line)
    {
    #define ADC " "g_argc=0;//每次初始化为0,确保每个命令都是从首位开始g_argv[g_argc++]=strtok(line,ADC);while(g_argv[g_argc++]=strtok(nullptr,ADC));g_argc--;return true;
    }
    
    4.4执行命令行
    4.4.1执行内建命令 

    通过父进程本身来进行执行:(cd命令)

    头文件:#include<unistd.h>

     int chdir(const char *path);

    bool CheckBuiltIn()
    {std::string cmd=g_argv[0];if(cmd=="cd"){if(g_argc==1){chdir(GETHOME());return true;}else{std::string pwd=g_argv[1];chdir(pwd.c_str());}return true;}return false;
    }
    
    4.4.2执行外部命令

     通过子进程来进行执行:

    //子程序进行进程替换执行命令
    int Execute()
    {int id=fork();if(id==0){//chileexecvp(g_argv[0],g_argv);exit(1);}//fatherint idd=waitpid(id,NULL,0);//阻塞等待(void)idd;//使用避免报错return 0;
    }
    
    4.5更新环境变量

    getcwdunistd.h 头文件中的一个函数,用于获取当前工作目录。 

     #include<unistd.h>

    char *getcwd(char *buf, size_t size);

    • buf:一个字符数组的指针,用来存储获取的当前工作目录的路径。你需要在调用 getcwd 之前分配足够的内存空间来存储路径。

    • sizebuf 指针指向的字符数组的大小。它指定了 buf 能够存储的最大字符数。

    char g_env[1024];
    char g_cwd[1024];void ChangEnv()
    {const char*cwd=getcwd(g_cwd,sizeof(g_cwd));if(cwd!=nullptr){snprintf(g_env,sizeof(g_env),"PWD=%s",g_cwd);putenv(g_env);}
    }
    

    3. Shell 实现完整代码

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cstdlib>
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/wait.h>
    #define COMMAND_SIZE 1024
    #define FORMAT "[%s@%s %s]#"
    #define MAXARGC 128
    char g_env[1024];
    char g_cwd[1024];
    char* g_argv[MAXARGC];
    int g_argc=0;
    const char* GETPWD()
    {char *pwd=getenv("PWD");return pwd==NULL?"None":pwd;
    }
    const char*GETUSER()
    {char*user=getenv("USER");return user==NULL?"None":user;
    }
    const char*GETHOSTNAME()
    {char*hostname=getenv("HOSTNAME");return hostname==NULL?"None":hostname;
    }
    const char*GETHOME()
    {char*home=getenv("HOME");return home==NULL?"None":home;
    }
    void ChangEnv()
    {const char*cwd=getcwd(g_cwd,sizeof(g_cwd));if(cwd!=nullptr){snprintf(g_env,sizeof(g_env),"PWD=%s",g_cwd);putenv(g_env);}
    }
    bool CheckBuiltIn()
    {std::string cmd=g_argv[0];if(cmd=="cd"){if(g_argc==1){chdir(GETHOME());return true;}else{std::string pwd=g_argv[1];chdir(pwd.c_str());}ChangEnv();return true;}return false;
    }
    std::string DirName(const char* pwd)
    {
    #define SLASH "/"std::string dir=pwd;auto pose=dir.rfind(SLASH);if(pose==std::string::npos) return "BUG?";return dir.substr(pose+1);
    }
    void MakeCMDPrompt(char cmdprompt[],size_t size)
    {//snprintf(cmdprompt,size,FORMAT,GETUSER(),GETHOSTNAME(),GETPWD());snprintf(cmdprompt,size,FORMAT,GETUSER(),GETHOSTNAME(),DirName(GETPWD()).c_str());
    }
    void PrintCMDPrompt()
    {char prompt[COMMAND_SIZE];MakeCMDPrompt(prompt,sizeof(prompt));printf("%s",prompt);
    }
    bool MakeCMDLine(char*out,size_t size)
    {char*line=fgets(out,size,stdin);if(line==NULL) return false;out[strlen(out)-1]=0;if(strlen(out)==0) return false;return true;
    }
    bool CMDLinePrase(char *line)
    {
    #define ADC " "g_argc=0;g_argv[g_argc++]=strtok(line,ADC);while(g_argv[g_argc++]=strtok(nullptr,ADC));g_argc--;return g_argc==0?false:true;
    }
    void PrintCMDLinePrase()
    {for(int i=0;g_argv[i];i++){printf("argv[%d]->%s\n",i,g_argv[i]);}printf("argc :%d\n",g_argc);
    }
    void Print()
    {char cmdline[COMMAND_SIZE];if( MakeCMDLine(cmdline,sizeof(cmdline))){printf("%s",cmdline);}
    }
    int Execute()
    {int id=fork();if(id==0){//chileexecvp(g_argv[0],g_argv);exit(1);}//fatherint idd=waitpid(id,NULL,0);//阻塞等待(void)idd;//使用避免报错return 0;
    }
    int main()
    {while(true){PrintCMDPrompt();char cmdline[COMMAND_SIZE];if(! MakeCMDLine(cmdline,sizeof(cmdline))){continue;}if(!CMDLinePrase(cmdline)){continue;}if(CheckBuiltIn()){continue;}Execute();}return 0;
    }
    
    http://www.xdnf.cn/news/1058.html

    相关文章:

  • 传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确。此 RPC 请求中提供了过多的参数。最多应为 2100。
  • 常用第三方库:dio网络库使用与封装
  • PHP 爬虫如何获取 1688 商品详情(代码示例)
  • 【前端记事】关于electron的入门使用
  • 【C++】vector<bool>特化
  • 外商在国内宣传 活动|发布会|参展 邀请媒体
  • 软件测试之接口测试常见面试
  • 什么是负载均衡?NGINX是如何实现负载均衡的?
  • UML 通信图对象协作:共享汽车系统交互脉络
  • 为什么在TCP层(即传输层)没有解决半包、粘包的问题
  • 技术速递|Agent 模式:对所有用户开放,并支持 MCP
  • 【SF顺丰】顺丰开放平台API对接(注册、API测试篇)
  • V5验证官网滑块验证码WSS协议逆向算法分析
  • vue vite开发时保留console.log打包完后依然想保留某个文件夹下的console.log方便以后的观察
  • C语言实现堆(优先队列)详解
  • 【Easylive】手动实现分布式事务解决方案流程解析
  • Java转Go日记(四):socket编程
  • STM32之DHT11温湿度传感器---附代码
  • vue3性能优化
  • 【数据结构_12】优先级队列
  • 深度学习3.5 图像分类数据集
  • YOLO11改进 | 特征融合Neck篇之Lowlevel Feature Alignment机制:多尺度检测的革新性突破
  • C 语言开发问题:使用 <assert.h> 时,定义的 #define NDEBUG 不生效
  • vin码识别技术-车辆vin识别代码-Java接口集成
  • 《理解C++宏:从#define到条件编译》
  • 【工具】VS Code/Cursor 编辑器状态栏颜色自定义指南
  • 装饰模式:动态扩展对象功能的优雅设计
  • AI Agent开发第35课-揭秘RAG系统的致命漏洞与防御策略
  • 极刻AI搜v1.0 问一次问题 AI工具一起答
  • 城市客运安全员证适用岗位及要求