Linux系统——进程控制(下)

💁‍♂️个人主页:进击的荆棘

👇作者其它专栏:

《数据结构与算法》《算法》《C++起始之路》《Linux》


目录

1.进程创建

2.进程终止

3.进程等待

4.进程程序替换

5.自主Shell命令行 解释器


4.进程程序替换

fork()之后,父子各自执行父进程代码的一部分若子进程只想执行一个全新的程序,就需要使用进程的程序替换。

程序替换是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载到调用进程的地址空间中。

4.1替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id未改变

4.2替换函数

有6中以exec开头的函数,通称exec函数:

cpp 复制代码
#include <unistd.h>

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

4.2.1函数解释

●这些函数若调用成功则加载新的程序从启动代码开始执行,不再返回。

●若调用出错则返回-1

●所以exec函数只有出错的返回值而没有成功的返回值

4.2.2命名解释

这些函数原型看起来很容易混,但掌握规律就很好记。

●l(list):表示参数采用列表

●v(vector):参数用数组

●p(path):有p自动搜索环境变量PATH

●e(env):表示自己维护环境变量

exec调用如下:

cpp 复制代码
#include <unistd.h>
int main()
{
    char *const argv[] = {"ps", "-ef", NULL};
    char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
    
    execl("/bin/ps", "ps", "-ef", NULL);

    // 带p的,可以使⽤环境变量PATH,⽆需写全路径
    execlp("ps", "ps", "-ef", NULL);

    // 带e的,需要⾃⼰组装环境变量
    execle("ps", "ps", "-ef", NULL, envp);

    execv("/bin/ps", argv);
    
    // 带p的,可以使⽤环境变量PATH,⽆需写全路径
    execvp("ps", argv);

    // 带e的,需要⾃⼰组装环境变量
    execve("/bin/ps", argv, envp);
    
    exit(0);
}

实际上,只有execve是真正的系统调用,其它五个函数最终都调用execve,所以execve在man手册第二节,其它函数在man手册第三节。函数间关系:

exec函数簇:

注:

●直接使用数组去替换envp会导致结果只有自己的环境变量,而没有系统的环境变量

●新增方式给子进程

▢直接putenv

▢exec*e,putenv,引入char ** environ

5.自主Shell命令行 解释器

5.1实现原理

下面是一个与shell典型的互动:

cpp 复制代码
[root@localhost epoll]# ls
client.cpp readme.md server.cpp utility.h
[root@localhost epoll]# ps
PID TTY TIME CMD
3451 pts/0 00:00:00 bash
3514 pts/0 00:00:00 ps

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell有标识为sh的方块代表,它随着时间的流逝从左向右一旦。shell从用户读入字符串"ls"。shell建立一个新的进程,然后再哪个进程中运行ls程序并等待那个进程结束。

然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序并等待这个程序结束。

所以要写一个shell,需要循环以下过程:

1.获取命令行

2.解析命令行

3.建立一个子进程(fork)

4.替换子进程(execvp)

5.父进程等待子进程退出(wait)

5.2实现源码

实现代码:

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

//shell的全局参数
#define MAXARGC 128
int g_argc;
char* g_argv[MAXARGC];

//环境变量参数表
#define MAX_ENV 100
char * g_env[MAX_ENV];
int g_envs;

//for test
char cwd[1024];
char cwdenv[1024];

//退出码
int lastcode=0;

//重定向需要的变量
#define NONE_REDIR 0   
#define INPUT_REDIR 1   //<
#define OUTPUT_REDIR 2  //>
#define APPEND_REDIR 3  //>>
int redir=NONE_REDIR;
std::string filename;

const char* GetUser(){
  const char * user=getenv("USER");
  return user==NULL?"none":user;
}

const char* GetHostname(){
  const char * hostname=getenv("HOSTNAME");
  return hostname==NULL?"none":hostname;
}

const char* GetPwd(){
  //const char * pwd=getenv("PWD");
  const char * pwd=getcwd(cwd,sizeof(cwd));
  if(pwd){
    snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",cwd);
    putenv(cwdenv);
  }
  return pwd==NULL?"none":pwd;
}

const char * GetHome(){
  const char *home=getenv("HOME");
  return home==NULL?"":home;
}

void InitEnv(){
  extern char ** environ;
  memset(g_env,0,sizeof(g_env));
  g_envs=0;
  //1.获取环境变量
  for(int i=0;environ[i];i++){
    //先为自己的环境变量参数表开辟空间
    g_env[i]=(char*)malloc(strlen(environ[i])+1);
    strcpy(g_env[i],environ[i]);
    g_envs++;
  }

  g_env[g_envs]=NULL;//最后一个值需要为空
  //2.导成环境变量
  for(int i=0;g_env[i];i++){
    putenv(g_env[i]);
  }
  environ=g_env;
}

std::string DirName(const char* pwd){
#define SLASH "/"
  std::string dir=pwd;
  if(dir==SLASH) return SLASH;
  auto pos=dir.rfind(SLASH);
  if(pos==std::string::npos) return "BUG?";
  return dir.substr(pos+1);
}
void MakeCommandLine(char prompt[],int size){
  snprintf(prompt,size,FORMAT,GetUser(),GetHostname(),DirName(GetPwd()).c_str());
}

void PrintCommandPrompt(){
  char prompt[COMMAND_SIZE];
  MakeCommandLine(prompt,sizeof(prompt));
  printf("%s",prompt);
  fflush(stdout);
}

bool GetCommand(char* command,int size){
  char *str=fgets(command,size,stdin);
  if(str==NULL) return false;
  command[strlen(command)-1]=0;//消除\n
  if(strlen(command)==0) return false;
  return true; 
}
bool CommandPrase(char *command){
#define SPA " "
  g_argc=0;
  g_argv[g_argc++]=strtok(command,SPA);
  while((bool)(g_argv[g_argc++]=strtok(nullptr,SPA)));
  //减掉多余的1
  g_argc--;
  return g_argc>0?true:false;
}
void Cd(){
  //四种清况 cd    cd -   cd ~   cd XXXNAME
  if(g_argc==1){
    std::string home=GetHome();
    if(home.empty()) return ;
    chdir(home.c_str());
  }
  else{
    std::string where=g_argv[1];
    if(where=="-"){
     // 
    }
    else if(where=="~"){
      chdir(getenv("HOME"));
    }
    else{
      chdir(where.c_str());
    }
  }
}
void Echo(){
  //三种清况 echo $?   ehco $PATH   echo "XXXXX"
  if(g_argc==2){
    std::string opt=g_argv[1];
    if(opt=="$?"){
      std::cout<<lastcode<<std::endl;
      lastcode=0;
    }
    else if(opt[0]=='$'){
      std::string env_name=opt.substr(1);
      const char* env_value=getenv(env_name.c_str());
      if(env_value)
          std::cout<<env_value<<std::endl;
    }
    else {
      std::cout<<opt<<std::endl;
    }
  } 
}

bool CheckAndExecIn(){
  std::string cmd=g_argv[0];
  if(cmd=="cd"){
    Cd();
    return true;
  }
  else if(cmd=="echo"){
    Echo();
    return true;
  }
  return false;
}

void Execte(){
  pid_t id=fork();
  if(id==0){
    int fd=-1;
    //判断是否是重定向
    if(redir==INPUT_REDIR){
      fd=open(filename.c_str(),O_RDONLY);
      if(fd<0) exit(1);
      dup2(fd,0);
      close(fd);
    }
    else if(redir==OUTPUT_REDIR){
      fd=open(filename.c_str(),O_CREAT|O_WRONLY|O_TRUNC,0666);
      if(fd<0) exit(2);
      dup2(fd,1);
      close(fd);
    }
    else if(redir==APPEND_REDIR){
      fd=open(filename.c_str(),O_CREAT|O_WRONLY|O_APPEND,0666);
      if(fd<0) exit(2);
      dup2(fd,1);
      close(fd);
    }
    else {

    }
    //child
    execvp(g_argv[0],g_argv);
    exit(1);
  }
  int status=0;
  //father
  pid_t rid=waitpid(id,&status,0);
  if(rid>0){
    lastcode=WEXITSTATUS(status);
  }
}

//void Print(char* command){
//  printf("%s\n",command);
//}

void RvSpace(char cmd[],int &end){
  while(isspace(cmd[end])){
    end++;
  }
}
void RedirCheck(char cmd[]){
  redir=NONE_REDIR;
  filename.clear();

  int start=0;
  int end=strlen(cmd)-1;
  while(end>start){ 
    if(cmd[end]=='<'){
      cmd[end++]=0;
      RvSpace(cmd,end);
      redir=INPUT_REDIR;
      filename=cmd+end;
      break;
    }
    else if(cmd[end]=='>'){
      if(cmd[end-1]=='>'){
        cmd[end-1]=0;
        //RvSpace(cmd,end);
        redir=APPEND_REDIR;
        //filename=cmd+end;
        //break;
      }
      else{ 
        //cmd[end++]=0;
        //RvSpace(cmd,end);
        redir=OUTPUT_REDIR;
        //filename=cmd+end;
        //break;
      }
      cmd[end++]=0;
      RvSpace(cmd,end);
      filename=cmd+end;
      break;
    }
    else{
      end--;
    }
  }
}

int main(){
  InitEnv();
  while(true){
     //1.输出命令行提示符
     PrintCommandPrompt();
     //2.获取命令行输入
     char command[COMMAND_SIZE];
     if(!GetCommand(command,sizeof(command)))
       continue;
     //Print(command);
    
     //3.重定向检查
     RedirCheck(command);
     //printf("redir:%d,filename:%s\n",redir,filename.c_str());

     //4.拆解命令行 "ls -a -l" -> "ls" "-a" "-l"
     if(!CommandPrase(command))
       continue;
     //5.检测并处理内建命令
     if(CheckAndExecIn())
       continue;
     //6.执行替换命令
     Execte();
  }
  return 0;
}

5.3总结

exec/exit就像call/return

一个C程序有很多函数组成。一个函数可以调换导另一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有它的局部变量,不同的函数通过call/return系统进行通信。

这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程序之内的模式扩展到程序之间。如下图:

一个C程序可以fork/exec另一个程序,并传递给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。

相关推荐
优化Henry1 小时前
学习笔记之关于MME不通问题归纳
运维·网络·学习·5g·信息与通信
考虑考虑8 小时前
数据库中的EXISTS
运维·数据库·后端
爱网络爱Linux8 小时前
【系列】每天一个 Linux 命令(53)startx 命令
linux·rhce·rhca·linux运维·linux命令大全·红帽认证
ZhengEnCi9 小时前
L2A-一个域名如何访问多台云服务器-子域名、路径前缀与Nginx反向代理选型指南
linux·人工智能
M78佐菲9 小时前
51单片机学习笔记:ds18b20要点整理
linux·笔记·嵌入式硬件·学习·51单片机
高亦真9 小时前
今天是学习嵌入式的第37天
linux·学习·算法
一个有温度的技术博主10 小时前
Linux 核心命令速查指南:从文件操作到系统运维
linux·服务器
小小的木头人10 小时前
Docker 环境下迁移 containerd 数据目录:从 `/var/lib/containerd` 到 `/data/containerd`
linux·docker
天远数科10 小时前
零信任架构实战:基于天远全能消金报告构建自动化信贷评估网关
运维·人工智能·架构·自动化