进程的程序替换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;
}

基本分析如下图

相关推荐
秦jh_33 分钟前
【Linux】多线程(概念,控制)
linux·运维·前端
扣得君2 小时前
C++20 Coroutine Echo Server
运维·服务器·c++20
keep__go2 小时前
Linux 批量配置互信
linux·运维·服务器·数据库·shell
矛取矛求2 小时前
Linux中给普通账户一次性提权
linux·运维·服务器
Fanstay9852 小时前
在Linux中使用Nginx和Docker进行项目部署
linux·nginx·docker
大熊程序猿2 小时前
ubuntu 安装kafka-eagle
linux·ubuntu·kafka
jieshenai2 小时前
使用VSCode远程连接服务器并解决Neo4j无法登陆问题
服务器·vscode·neo4j
渗透测试老鸟-九青3 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss
Gentle5863 小时前
labview连接sql server数据库
服务器·数据库·labview
co0t3 小时前
计算机网络(11)和流量控制补充
服务器·网络·计算机网络