Linux操作系统--进程程序替换and做一个简单的shell

目录

1.替换原理:

2.替换函数

3.综合前面的知识,做一个简易的shell

1.替换原理:

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

环境变量也是数据,创建子进程的时候,环境变量就已经被子进程继承下去了

2.替换函数

int execve(const char *path, char *const argv[], char *const envp[]);

execve系统调用接口:上图中的函数其实最终都是调用execve接口exec

函数解释

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。因为当调用exec系列函数时,操作系统会完全替换当前进程的代码段和数据段,【但保留进程控制块(PCB)中的部分元数据(如PID、文件描述符表等)】所以不再返回。
  • 如果调用出错则返回-1

总之,exec函数只有出错的返回值而没有成功的返回值

命名理解:

  • l(list):表示参数采用列表
  • v(vector):参数用数组
  • p(path):有p自动搜索环境变量PATH
  • e(env):表示自己维护环境变量

exec调用举例如下:

#include<unistd.h>

int main()
{
    char *const argv[] = {"ps","aux",NULL};
    char *const envp[] = {"PATH=/bin:/usr/bin","TERM=console",NULL};

    execl("/bin/ps","ps","aux",NULL);

    //带p的,可以使用环境变量PATH,无需写全路径
    execlp("ps",// 查找PATH中的ps
    "ps", // argv[0]
    "aux",// argv[1]
    NULL);// 必须的终止符

    //带e的,需要自己组装环境变量
    execle("ps","ps","aux",NULL,envp);

    execv("/bin/ps",argv);

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

    //带e的,需要自己组装环境变量
    execve("/bin/ps",argv,envp);

    return 0;
}

只有execve是真正的系统调用,其他五个函数最终都调用execve,所以execve在man手册第二节,其他函数在man手册第三节。这些函数之间的关系如下图:

3.综合前面的知识,做一个简易的shell

shell从用户读入字符串“ls”。shell建立一个新的进程,然后在那个进程中运行ls程序并等待哪个进程结束。然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序,并等待这个进程结束。所以写一个shell,需要循环以下过程:

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork)
  4. 替换子进程(execvp)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44

int lastcode = 0;
int quit = 0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];
char pwd[LINE_SIZE];

//自定义环境变量表
char myenv[LINE_SIZE];

const char *getusername()
{
    return getenv("USER");
}

const char *gethostname()
{
    return getenv("HOSTNAME");
}

void getpwd()
{
    getcwd(pwd,sizeof(pwd));
}

void interact(char *cline, int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(), gethostname(),pwd);
    char *s = fgets(cline,size,stdin);
    assert(s);
    (void)s;
    //"abcd\n\0"
    cline[strlen(cline)-1] = '\0';
}

//ls -a -l | wc -l | head
int splitstring(char cline[],char *_argv[])
{
    int i = 0;
    argv[i++] = strtok(cline, DELIM);
    while(_argv[i++] = strtok(NULL,DELIM));
    return i - 1;
}

void NormalExcute(char *_argv[])
{
    pid_t id = fork();
    if(id < 0){
        perror("fork");
        return;
    }
    else if(id == 0){
        //让子进程执行命令
        //execvpe(_argv[0],_argv,environ);
        execvp(_argv[0],_argv);
        exit(EXIT_CODE);
    }
    else{
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid == id)
        {
            lastcode = WEXITSTATUS(status);
        }
    }
}

int buildCommand(char *_argv[], int _argc)
{
    if(_argc == 2 && strcmp(_argv[0], "cd") == 0)
    {
        chdir(argv[1]);
        getpwd();
        sprintf(getenv("PWD"), "%s",pwd);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"export")==0){
        strcpy(myenv,_argv[1]);
        putenv(myenv);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"echo") == 0){
        if(strcmp(_argv[1],"$?") == 0)
        {
            printf("%d\n", lastcode);
            lastcode = 0;
        }
        else if(*_argv[1] == '$')
        {
            char *val = getenv(_argv[1] + 1);
            if(val) printf("%s\n", val);
        }
        else{
            printf("%s\n",_argv[1]);
        }
        return 1;
    }

    //特殊处理一下ls
    if(strcmp(_argv[0],"ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}

int main()
{
    while(!quit){
        //交互问题,获取命令行,ls -a -l > myfile / ls -a -l >> myfile /cat < file.txt
        interact(commandline,sizeof(commandline));

        //commandline -> "ls -a -l -n\0" -> "ls" "-a" "-l" "-n"
        //子串分割的问题,解析命令行
        int argc = splitstring(commandline,argv);
        if(argc == 0)continue;

        //指令的判断
        //debug
        //for(int i = 0;argv[i];i++)printf("[%d]: %s\n", i , argv[i]);
        //内键命令,本质就是一个shell内部的一个函数
        int n = buildCommand(argv, argc);
        
        //普通命令执行
        if(!n) NormalExcute(argv);
        //
    }
}

4.本章知识补充:

  1. 程序替换成功之后,exec*后续的代码不会被执行
  2. Linux中形成的可执行程序,是由格式的,ELF,可执行程序的表头,可执行程序的入口地址就在表中
  3. 子进程独立:进程替换会在后台启动子进程来执行指定的命令。子进程有自己独立的环境,这意味着在子进程中对环境变量的修改不会影响到主进程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值