操作系统之shell实现(下)

🌟 各位看官好,我是 maomi_9526

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

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

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

目录

[1. 进程程序替换](#1. 进程程序替换)

2.exec函数

[2.1 execl](#2.1 execl)

[2.2 execlp](#2.2 execlp)

[2.3 execle](#2.3 execle)

[2.4 execv](#2.4 execv)

[2.5 execvp](#2.5 execvp)

[2.6 execvpe](#2.6 execvpe)

2.7execve

2.8命名理解

3.进程替换

3.1进程替换原理

[4. 自主Shell命令行解释器](#4. 自主Shell命令行解释器)

4.1获取当前环境信息

4.2输出命令行提示符

4.3获取命令行输入

4.4执行命令行

4.4.1执行内建命令

4.4.2执行外部命令

4.5更新环境变量

[3. Shell 实现完整代码](#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

代码实现:

cpp 复制代码
//获取当前环境信息
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, ...);

返回值:

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

  • 失败:返回负数

cpp 复制代码
#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

代码实现:

cpp 复制代码
//接受命令行
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 会将字符串中的任何一个分隔符都视为分隔点。

cpp 复制代码
//分割字符串
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);

cpp 复制代码
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执行外部命令

通过子进程来进行执行:

cpp 复制代码
//子程序进行进程替换执行命令
int Execute()
{
    int id=fork();
    if(id==0)
    {
      //chile
      
      execvp(g_argv[0],g_argv);
      exit(1);
    }
    //father
    int 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 能够存储的最大字符数。

cpp 复制代码
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 实现完整代码

cpp 复制代码
#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)
    {
      //chile
      
      execvp(g_argv[0],g_argv);
      exit(1);
    }
    //father
    int 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;
}
相关推荐
-21 分钟前
万字解析TCP
服务器·网络·tcp/ip
YuSun_WK30 分钟前
conda和pip的区别
linux·运维·服务器
YKPG30 分钟前
c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第七式】程序的编译
linux·c语言·开发语言
华纳云IDC服务商39 分钟前
Debian服务器环境下env变量丢失怎么办
运维·服务器·debian
抓饼先生40 分钟前
关于创建UNIX/Linux daemon进程的笔记
linux·笔记·unix
Yang三少喜欢撸铁1 小时前
【通过Docker快速部署Tomcat9.0】
linux·运维·服务器·docker·容器·tomcat
心随_风动1 小时前
华为openEuler操作系统全解析:起源、特性与生态对比
linux·华为·openeuler
风巽·剑染春水1 小时前
【安装部署】Linux下最简单的 pytorch3d 安装
linux·pytorch3d
无敌 喵星人1 小时前
ubuntu的各种工具配置
linux·运维·windows
颇有几分姿色1 小时前
Nacos 2.0.2 在 CentOS 7 上开启权限认证(含 Docker Compose 配置与接口示例)
linux·docker·centos