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

操作系统之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;
    }
    

    相关文章:

  • Laravel 对接阿里云 OSS 说明文档
  • GPIO(通用输入输出端口)详细介绍
  • 【Qt】控件的理解 和 基础控件 QWidget 属性详解(通俗易懂+附源码+思维导图框架)
  • PyTorch卷积层填充(Padding)与步幅(Stride)详解及代码示例
  • 深入理解 Spring @Configuration 注解
  • PyTorch深度学习框架60天进阶学习计划 - 第48天:移动端模型优化(二)
  • 4.22tx视频后台开发一面
  • 【愚公系列】《Python网络爬虫从入门到精通》063-项目实战电商数据侦探(主窗体的数据展示)
  • 前端框架的“快闪“时代:我们该如何应对技术迭代的洪流?
  • 媒体关注:联易融聚焦AI+业务,重塑供应链金融生态
  • CAD在线查看免费,可以支持DWG/GLB/GLTF/doc/wps/pdf/psd/eml/zip, rar/MP3/MP4/svg/OBJ/FBX格式
  • 2025年数字媒体设计与文化交流国际会议 (DMACE 2025)
  • 【Redis】字符串类型List 常用命令详解
  • 基于 PaddleOCR对pdf文件中的文字提取
  • 分布式之易混淆概念
  • vue浅试(1)
  • EasyRTC音视频实时通话:打造高清低延迟的远程会议新生态
  • (51单片机)LCD显示温度(DS18B20教程)(LCD1602教程)(延时函数教程)(单总线教程)
  • 7. 深入Spring AI:刨析 Advisors 机制
  • C++中的算术转换、其他隐式类型转换和显示转换详解
  • 更好发挥汽车产业在扩投资促消费方面的带动作用!陈吉宁调研上海车展
  • 2025欧亚经济合作发展论坛在沪举办
  • 上海一小学百名学生齐聚图书馆:纸质书的浪漫AI无法取代
  • 广西三江通报“网约车司机加价”:对网约车平台进行约谈
  • 新任遂宁市委副书记王忠诚已任市政府党组书记
  • 新科世界冠军!雨果4比1战胜林诗栋,首夺世界杯男单冠军