【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?

简介: 【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?

1. 前言

本篇文章是进程控制的最后一篇文章
有时我们遇见这种场景:子进程被创建
出来后并不想执行父进程的代码,而是
想去执行其他程序的代码来完成任务,
于是在这种场景下,程序替换显得很重要!

本章重点:

本篇文章着重讲解进程程序替换
的exec系列函数的用法(一共六个),
并且自主实现一个bash解释器.
最后拓展如何使用C调用其他语言的程序


2. exec系列函数的认识

在fork之后如果子进程想要执行一个

全新的程序,就需要用到此系列函数!

这里一共有六个函数,但是它们都有
一定的规律,并不难记忆!它们都是失败
返回-1首先先介绍一个最简单的函数:
execl

它的参数分别代表:要执行的程序的路径
以及名字,和如何执行此程序

先使用再解释:

int main()    
{    
    printf("我要进行程序替换了...\n");    
    int n = execl("/usr/bin/ls","ls","-a","-l",NULL);                                                                                                                   
    if(n==-1)    
    {    
        perror("execl");    
    }    
    printf("程序替换完毕!\n");    
    return 0;    
}

现象:

可以发现,在打印完:我要进行程序替换

后,就去执行了ls程序了,并且执行完后

并没有打印"程序替换完成"!

表面execl后,会将当前进程的代码和数据
进行替换,包括还没执行的代码!


3. execl系列函数

首先,execl隶属于exec系列,加上l,

l就是list,相当于要把执行的程序的路径

给列举出来,execl系列中还有

execlp和execle,现在来讲解这两个

execlp函数解析:

int main()    
{    
    printf("我要进行程序替换了...\n");    
    int n = execlp("ls","-l",NULL);                                                                                                                                     
    if(n==-1)    
    {    
        perror("execl");    
    }    
    printf("程序替换完毕!\n");    
    return 0;    
}

可以发现,使用execlp函数即使不加上
路径也可以找到程序,并且允许它,但是
这是为什么呢?带上p,p也就是PATH
环境变量,所以系统会去环境变量PATH
中找路劲,若找到就直接执行它!

execle函数解析:

int main()    
{    
    const char* _env[]={"MY_ENV=666",NULL};    
    printf("我要进行程序替换了...\n");    
    int n = execle("/usr/bin/ls","ls","-l",NULL,_env);//自己定义一个环境变量MY_ENV=666传递给要去执行的程序                                                              
    if(n==-1)    
    {    
        perror("execl");    
    }    
    printf("程序替换完毕!\n");    
    return 0;    
}

execle可以在执行其他程序前,
传入自己定义的环境变量,方便后续
程序的执行!e也就是env的简写


4. execv系列函数

execv系列函数即为将l换成了v,

v就是vector,数组,也就是利用

数组来传参

execv函数解析:

int main()    
  {    
    char* const set[]={"ls","-a","-l",NULL};  
      printf("我要进行程序替换了...\n");    
      int n = execv("/usr/bin/ls",set);                                                                             
      if(n==-1)    
      {    
          perror("execl");    
      }    
      printf("程序替换完毕!\n");    
      return 0;    
  }

将我们要执行程序的方法用数组存起来
如何再把数组传过去!后面的execvp和
execvpe函数也就很好理解了,加上p无非
就是去环境变量PATH中找路径,加上e也
就是给要去执行的程序传入环境变量,仅此而已!


5. 程序替换的使用场景

其实一般情况下,程序替换都不是将
自己替换掉,而是创建子进程去替换,
让子进程去干活,而父进程当"监工"

在这种场景下,我们可以很自然的想到
bash解释器的工作原理可能就是创建
子进程去执行任务,而bash父进程本身
就需要获取指令,并传达命令即可!

首先,bash解释器一定是一个while

死循环,因为它会不断给我们打印信息:

当然这个消息你可以自定义处理

int main()
{
    while(1)
    {
        //打印提示信息
        printf("[kwy@localhost myshell]# ");
        fflush(stdout);
        ......
    }
    return 0;
}

我们先把bash的整体结构分析一下,

然后在一步一步的实现它:

  1. 首先我们需要定义两个数组A和B
    A用来存放用户输入的所有字符串
    B用来存放以空格打散后的字符串

  2. 第二步,获取用户输入的字符串后,
    将字符串以空格为分割打散

  3. 第三步,创建子进程使用exec系列
    函数去执行用户输入的指令
    而bash本身充当监工的角色等待子进程死亡


6. 自我实现一个bash解释器

首先先创建两个数组备用

然后再接收用户的输入

#define NUM 1000
#define SIZE 16
char cmd_line[NUM];//保存完整的命令行字符串
char* my_argv[SIZE];//保存打散后的字符串
if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)//用fgets将标准输入输入到数组中
    continue;
cmd_line[strlen(cmd_line)-1] = '\0';//将输入的换行符给清除掉

接下来就是将字符串以空格为分割打散了

在C语言的学习时有strtok函数可以帮助

我们解决这个问题,它的功能如下:

这里默认大家知道这个函数的用法了
所以我直接将分割字符串的代码写出来:

//命令行字符串解析:以空格为分割打散
my_argv[0]=strtok(cmd_line," ");//提出第一部分
int index=1;
while(my_argv[index++] = strtok(NULL," "));//第二次调用strtok时若还想解析第一次调用的字符串,则传NULL

这段代码写完后,字符串就已经被我们
分割成了几个小字符串了,比如用户输入
“ls -a -l"就转换成了"ls”,“-a”,"-l"了,接下来
只需创建子进程完成任务即可!

//shell运行原理:通过子进程执行命令,父进程等待&&解析命令
//命令行解释器是一个常驻程序
#define NUM 1000
#define SIZE 16
char cmd_line[NUM];//保存完整的命令行字符串
char* my_argv[SIZE];//保存打散后的字符串
int main()
{
    while(1)
    {
        //打印提示信息
        printf("[kwy@localhost myshell]# ");
        fflush(stdout);
        memset(cmd_line,'\0',sizeof cmd_line);
        //获取用户的键盘输入
        if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)
            continue;
        cmd_line[strlen(cmd_line)-1] = '\0';//将输入的换行符给清除掉
        //命令行字符串解析:以空格为分割打散
        my_argv[0]=strtok(cmd_line," ");//提出第一部分
        int index=1;
        while(my_argv[index++] = strtok(NULL," "));//第二次调用strtok时若还想解析第一次调用的字符串,则传NULL
        //fork后子进程去完成任务
        pid_t id=fork();
        if(id == 0)//子进程
        {
            printf("下面的功能让子进程执行\n");
            //当执行cd等命令时,改变的是子进程的路径,而父进程的路径没变
            execvp(my_argv[0],my_argv);
            exit(1);//执行失败就返回1
        }
        //父进程的代码,当监工
        int status = 0;
        pid_t ret = waitpid(-1,&status,0);
        if(ret>0) printf("exit code: %d\n",WEXITSTATUS(status));
    }
    return 0;
}

关于代码的解释都在注释中
如果你还有哪个地方不懂,欢迎私信


7. 内建命令的特殊性

在实现bash时,可能会遇见一个问题:

就是cd指令进入某个文件夹似乎没用

这一点其实很好理解,因为指令cd是
进入某个文件夹,而进入此文件夹当然
是当前进程进入了,如果创建了子进程
去进去文件夹,由于写时拷贝的原因,父
进程并不会进去,所以对于像cd这样的
指令我们称为内建命令,也就是不能让
子进程来完成的命令,只能父进程亲自动手!

if(strcmp(my_argv[0],"cd")==0)
{
   if(my_argv[1]!=NULL)
       chdir(my_argv[1]);
   continue;//直接跳到while(1)处
}

chdir即为切换当前的工作目录

内建命令不止cd,像export,kill

和history等等也是内建命令!


8. 总结以及拓展

进程程序替换可以帮助我们完成很多

任务,制作一个简易的bash解释器只是

众多应用中的一个,随着我们学习的深入

你还会发现新大陆!

对于程序替换的拓展:

在Linux下,C语言程序不仅可以替换成
C语言程序去执行,还可以替换成python
或Java甚至是bash等程序去执行它们
语言的代码:

比如python脚本:

#! /usr/bin/python3.6
print("hello Python/n")

运行命令: python test.py

将进程替换为Python程序:execlp("python", "python", "test.py", NULL);

这样就可以直接在C程序上执行python代码了!


🔎 下期预告:基础IO 🔍


相关文章
|
3月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
1月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
150 67
|
2月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
2月前
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
83 4
|
2月前
|
Linux
Linux:守护进程(进程组、会话和守护进程)
守护进程在 Linux 系统中扮演着重要角色,通过后台执行关键任务和服务,确保系统的稳定运行。理解进程组和会话的概念,是正确创建和管理守护进程的基础。使用现代的 `systemd` 或传统的 `init.d` 方法,可以有效地管理守护进程,提升系统的可靠性和可维护性。希望本文能帮助读者深入理解并掌握 Linux 守护进程的相关知识。
90 7
|
2月前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
126 5
|
2月前
|
Linux 应用服务中间件 nginx
Linux 进程管理基础
Linux 进程是操作系统中运行程序的实例,彼此隔离以确保安全性和稳定性。常用命令查看和管理进程:`ps` 显示当前终端会话相关进程;`ps aux` 和 `ps -ef` 显示所有进程信息;`ps -u username` 查看特定用户进程;`ps -e | grep <进程名>` 查找特定进程;`ps -p <PID>` 查看指定 PID 的进程详情。终止进程可用 `kill <PID>` 或 `pkill <进程名>`,强制终止加 `-9` 选项。
51 3
|
监控 安全 Shell
防止员工泄密的措施:在Linux环境下使用Bash脚本实现日志监控
在Linux环境下,为防止员工泄密,本文提出使用Bash脚本进行日志监控。脚本会定期检查系统日志文件,搜索敏感关键词(如"password"、"confidential"、"secret"),并将匹配项记录到临时日志文件。当检测到可疑活动时,脚本通过curl自动将数据POST到公司内部网站进行分析处理,增强信息安全防护。
263 0
|
Linux Shell Windows
4:Bash shell命令-步入Linux的现代方法
4:Bash shell命令-步入Linux的现代方法
150 0
|
Ubuntu 安全 Linux
不用安装虚拟机,直接在Windows上面运行Linux Bash Shell,嗯!真香!!!
不用安装虚拟机,直接在Windows上面运行Linux Bash Shell,嗯!真香!!!
499 0