进程的程序替换exec*函数和shell实现

进程程序替换函数:execl - - - 让进程使用 exec 函数把自己得到代码和程序替换掉,执行新的程序 *。不会直接更改原进程PCB页表等,只会把原进程PCB原本所映射页表映射到的物理内存的代码和数据覆盖成 execl 所填写的程序数据。只要替换成功,原本代码之后的就不会运行,替换失败了才会向后运行。

也就是 利用老程序的壳子,覆盖成新程序的内容。exec* 类似于一种 linux 中的加载函数。

使用如下:当运行自己写的进程时,跑出来的时 ls 命令,而且test end... 也没有运行。

当程序被替换,自己的后面的代码不能运行,就可以利用fork函数创建子进程,让子进程去替换,父进程wait等待子进程就可以全部运行了。

创建子进程目的是让子进程完成任务:1.子进程执行代码的一部分 2.让子进程完成一个全新的程序,只要进程程序替换成功,代码和数据都会写时拷贝一个空间让子进程去覆盖重新开空间去让子进程执行。

exec* 函数出错,返回值为-1,错误码(errno)被设置。成功没有返回值。

execl(想执行程序的路径,怎样执行这个程序,... ,NULL结尾)

l:在命令行怎样调程序,则传参数怎样传。

execv(想执行程序的路径,需要执行的命令以Vector传入)

execvp(不用传路径 传程序名 系统在PATH路径中查找这个程序,需要执行的命令以Vector传入),execlp同理。

C的子程序调用执行程序,不是创建新的进程,而是改变代码和数据,进程pid不会变。下面的C调用C++就是例子。

execvpe(直接传文件名,传数组里面存放需要调用的程序名,传环境变量)

e:传入环境变量后有三种情景

1.直接全部替换自己写的环境变量

2.传入父进程的父进程的环境变量,也就是bash给的环境变量

3.把bash给的环境变量改一改传入,putenv(XXX) - - - 把XXX环境变量添加到bash的环境变量中,然后传bash环境变量时就会有XXX这个环境变量。

eg:在testexec中调用mytest.cc函数,使用execvpe传的三个参数就给到了.cc函数的main函数的参数列表中,分别就是argc,argv,env三个参数。

真正的系统调用函数是 execve,而上面的进程替换不是系统调用,而是3号手册的C函数取调用 execve 函数。

自己写一个shell:

char * getenv("XXX") - - - 得到环境变量中名字是XXX的环境变量对应的值。

snprintf(A,B,"C") - - - 把C中的东西写进A中,A的大小为B,B就是sizeof(A)

fflush(stdout) - - - 标准输出,冲刷缓冲区显示到显示器。

fgets(A,B,FILE* stream) - - - 按行从特定的文件流中获取内容填到A中,A的大小为B。为了得到一行内容空格不做分离符。

eg:char* s=fgets(A, sizeof(A), stdin) - - - 从标准输入中读取一行类容填写到A中。

strtok(str, const char* "b") - - - 把字符串str,按照b字符去分割,返回值是从左向右分割的第一个子串,只需要第一次调用时传str,最后传NULL,就可以分割完。一般是先赋值再分割,只有这样可以让str最后一个元素无法分割时,把值先给出去了,而不是返回的NULL。

chdir(const char*path) - - - 更改当前的工作路径

putenv() - - - 导入环境变量

getcwd - - - 获取当前的绝对路径

#define SkipPath§ do{p += (strlen§-1); while(*p !='/') p--;}while(0)

do{ ... }while(0) - - - 在宏函数中,这个东西可以让宏函数在调用的时候更像是一个函数,调用宏函数时可以随意在后面加 ; ,不会带来影响。

linux代码如下:

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>

#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{p += (strlen(p)-1); while(*p !='/') p--;}while(0)

const char *GetUserName() //获取用户名
{
  const char*name = getenv("USER");//getenv("XXX") 得到XXX对应的环境变量
  if(name == NULL) return "None";
  return name;
}
const char *GetHostName()//获取主机名
{
  const char*hostname = getenv("HOSTNAME");
  if(hostname == NULL) return "None";
  return hostname;
}
const char *GetCwd()//获取路径名
{
  const char*cwd=getenv("PWD");
  if(cwd == NULL) return "None";
  return cwd;
}

// 1
void MakeCommandLineAndPrient()//得到命令行并打到屏幕
{
  char line[SIZE];
  const char*username=GetUserName();
  const char*hostname=GetHostName();
  const char*cwd=GetCwd();
  SkipPath(cwd);
  snprintf(line,sizeof(line),"[%s@%s %s]>",username,hostname,strlen(cwd) == 1 ? "/" : cwd+1);
  printf("%s",line);
  fflush(stdout);
}

//2
char *gArgv[NUM]; //存放分割出来的命令字符串
void SplitCommand(char command[],int size)
{
  // "ls -a -l"-> "ls" "-a" "-l"
  gArgv[0] = strtok(command,SEP);
  int index=1;
  while((gArgv[index++]=strtok(NULL,SEP)));
}
int GetUserCommand(char command[], int n)
{
  char *s = fgets(command, n, stdin);
  if(s == NULL) return 1;
  command[strlen(command)-1]=ZERO;
  return strlen(command);
}

int lastcode =0;
// 4
char cwd[SIZE*2];
const char* GetHome()
{
  const char *home = getenv("HOME");
  return home;
}
void Cd()
{
  const char* path = gArgv[1];
  if(path == NULL) path = GetHome();
  // cd 后面一定有东西
  chdir(path); //走到这里说明是cd命令,路径会变化因此得更新pwd

  // 刷新环境变量
  char temp[SIZE*2];
  getcwd(temp, sizeof(temp));

  snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
  putenv(cwd);

}
int CheckBuildin()
{
  int yes = 0;
  const char* enter_cmd=gArgv[0];
  if(strcmp(enter_cmd, "cd") == 0)
  {
    yes = 1;
    Cd();
  }
  else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1],"$?") == 0)
  {
    yes = 1;
    printf("%d\n", lastcode);
    lastcode = 0;
  }
  return yes; 
}

//5 
void ExecuteCommand()//运行程序
{
  pid_t id = fork();
  if(id < 0)
  {
    exit(-1);
  }
  else if(id == 0) //子进程
  {
    execvp(gArgv[0],gArgv);
    exit(errno); //如果程序替换失败就会有返回码
  }
  else 
  {
    int status =0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
      lastcode = WEXITSTATUS(status);
      if(lastcode != 0) printf("%s:%s:%d\n",gArgv[0],strerror(lastcode),lastcode);
    }
  }
}
int main()
{
  int quit=0;
  while(!quit)
  {
    //1.输出一个命令行,需要得到用户名,主机名,当前目录
    MakeCommandLineAndPrient();

    //2.输入命令,获取命令字符串存到usercommand
    char usercommand[SIZE];
    int n = GetUserCommand(usercommand, sizeof(usercommand));
    (void)n;
    if(n<=0) return 1;

    //3.命令行字符串分割 
    SplitCommand(usercommand,sizeof(usercommand));

    //4.检测命令是否是内建命令
    n = CheckBuildin();
    if(n) continue; //如果n是1 为真 说明是内建命令,则在Cd已经执行过了

    //5.执行命令 
    ExecuteCommand(); 
  }
  
  return 0;
}

基本分析如下图

相关推荐
π大星星️13 分钟前
基于LNMP架构的个人博客系统部署
服务器·架构
孙克旭_4 小时前
PXE_Kickstart_无人值守自动化安装系统
linux·运维·自动化
皓月盈江5 小时前
Linux电脑本机使用小皮面板集成环境开发调试WEB项目
linux·php·web开发·phpstudy·小皮面板·集成环境·www.xp.cn
深井冰水5 小时前
mac M2能安装的虚拟机和linux系统系统
linux·macos
leoufung5 小时前
内核内存锁定机制与用户空间内存锁定的交互分析
linux·kernel
菜菜why6 小时前
AutoDL租用服务器教程
服务器
IT专业服务商6 小时前
联想 SR550 服务器,配置 RAID 5教程!
运维·服务器·windows·microsoft·硬件架构
忧虑的乌龟蛋7 小时前
嵌入式Linux I2C驱动开发详解
linux·驱动开发·嵌入式·iic·i2c·读数据·写数据
I_Scholar7 小时前
OPENSSL-1.1.1的使用及注意事项
linux·ssl
Johny_Zhao8 小时前
K8S+nginx+MYSQL+TOMCAT高可用架构企业自建网站
linux·网络·mysql·nginx·网络安全·信息安全·tomcat·云计算·shell·yum源·系统运维·itsm