进程程序替换

本文详细介绍了Linux系统中的进程替换原理,包括各种exec函数的使用,如execl、execv、execlp等。此外,还展示了简易Shell的制作过程,探讨了如何利用execvp函数执行命令以及如何处理环境变量。在Shell中,通过chdir函数实现路径切换,而putenv接口用于在Shell内部添加新的环境变量。

目录

一.替换原理

二.替换函数

 三.简易shell的制作


一.替换原理

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

二.替换函数

一共有6中替换函数 ,统称为exec函数。

#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 cha *file,char *const argv[]);

这些函数如果调用成功,则会加载新的程序从启动代码处开始执行。如果调用失败了会返回-1。

要执行一个全新的程序,首先要找到该程序的位置,以及怎么去执行,可以认为这些就是参数。

举例子看一下: 

1.execl:第一个参数是执行参数的路径,第二个参数是可变参数列表,表示你如何执行这个程序,并以NULL结尾。

int main()
{
  pid_t id=fork();
  if(id==0)
  {
      printf("我是子进程,我的id=%d\n",getpid());
      execl("/usr/bin/ls","ls","-a","-l",NULL);
      //execl("./T","T",NULL); //也可运行自己写的程序

      printf("我已经执行完毕\n");
  }
  else
  {
    int status=0;
    int ret=waitpid(id,&status,0);
    sleep(2);
   if(ret>0)
    {
       printf("退出信号是%d,退出码是%d\n",(status>>8)&0xFF,status&0xFF); 
       printf("等待成功\n");

    }
  
  }
  return 0;
}

[LF@ecs-100710 lesson7]$ ./test
我是子进程,我的id=20167
total 80
drwxrwxr-x  3 LF LF 4096 Oct 19 15:04 .
drwxrwxr-x 11 LF LF 4096 Oct 10 19:16 ..
-rwxrwxr-x  1 LF LF 8664 Oct 19 15:03 a.out
-rw-rw-r--  1 LF LF   65 Oct 19 14:40 makefile
-rwxrwxr-x  1 LF LF 8464 Oct  6 10:25 path
-rw-rw-r--  1 LF LF  186 Oct  6 10:25 path.c
drwxrwxr-x  2 LF LF 4096 Oct  6 11:14 shell
-rwxrwxr-x  1 LF LF 8352 Oct  6 09:38 T
-rw-rw-r--  1 LF LF  100 Oct  6 09:38 t.c
-rwxrwxr-x  1 LF LF 8664 Oct 19 15:04 test
-rw-rw-r--  1 LF LF  520 Oct 19 15:04 test.c
-rw-rw-r--  1 LF LF  763 Oct 19 15:03 tt.c
退出信号是0,退出码是0
等待成功

2.execv:第一个参数是执行参数的路径,第二个参数是一个指针数组,这些指针指向了那些参数(与上面类似)。

 char* argv_[]={"ls","-a","-l",NULL};
 execv("/usr/bin/ls",argv_);

execlp(const char *file, const char *arg, ...):第一个参数表示要执行参数的名字,第二个参数是可变参数列表,表示如何执行这个程序,以NULL结尾。

例如:

execlp("ls","ls","-l","-a",NULL);

int execvp(const char *file,char * const argv[]):与上面类似,第二个参数是指针数组。

int execle(const char *path, const char *arg, ...,char *const envp[]):第一个参数是执行程序的路径,第二个参数表示如何执行这个程序,以NULL结尾,第三个参数是环境变量。

例如:先看下传递系统的环境变量:

int main()
{
  extern**environ;
  pid_t id=fork();
  if(id==0)
  {
    printf("我是子进程\n");

   char* const env[]={"PATHMY=hello world",NULL};
   execle("./path","path",NULL,environ);
   //execle("./path","path",NULL,env);
    exit(9);
  }
  else
  {
    int status=0;
    int ret=waitpid(id,&status,0);
    sleep(2);
   if(ret>0)
    {
       printf("退出信号是%d,退出码是%d\n",(status>>8)&0xFF,status&0xFF); 
       printf("等待成功\n");
    }
  }
  return 0;
}
[LF@ecs-100710 lesson7]$ cat path.c
#include<stdio.h>
#include<stdlib.h>
int main()
{
  printf("PATH:%s\n",getenv("PATH"));
  printf(".........................\n");

  printf("PATHMY:%s\n",getenv("PATHMY"));
  return 0;
}

结果:

 传递自己写的环境变量:execle("./path","path",NULL,env);环境变量是传递了,但是被覆盖了。

 在之前已经学习过了,环境变量是可以被子进程传递下去的,可以用execl函数验证下,

 execl("./path","path",NULL);,用这段代码运行,可以得到一样的结果

事实上 , 只有 execve 是真正的系统调用 , 其它五个函数最终都调用 execve。

 三.简易shell的制作

shell:一个命令行解释器,当有命令要执行时,shell会创建一个子进程,让子进程执行命令,而shell只需等待其退出即可。

过程:

1. 获取命令行
2. 解析命令行
3. 建立一个子进程( fork
4. 替换子进程( execvp
5. 父进程等待子进程退出( wait

选用execvp(const char *file,char * const argv[])函数更加方便,传递程序名,一个指针数组。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<iostream>
using namespace std;

#define size 100
#define UU " "

int main()
{
  char arr1[size];//存储程序名字
  char* arr2[size];//程序如何执行
  while(true)
  {
    cout<<"[LF@ecs-100710]#:";
    fgets(arr1,size,stdin);//会读取\n,\n后面还有一个\0;
    arr1[strlen(arr1)-1]='\0';//将\n置为\0
    //printf("%s\n",arr1);

   arr2[0]=strtok(arr1," ");//若arr1是 "ls -l -a",arr2[0]="ls",arr2[1]="-l",arr2[2]="-a"
    int index=1;             //arr2[3]=NULL;          
    if(strcmp(arr2[0],"ls")==0)
    {
      arr2[index++]=(char*)"--color=auto";//添加颜色
    }
    while(arr2[index]=strtok(NULL,UU))
    {
       index++;
    }
    //进行程序替换
    pid_t id=fork();
    if(id==0)
    {
       execvp(arr2[0],arr2);
       exit(-1);
    }
    int status=0;
   pid_t ret= waitpid(id,&status,0);
   if(ret>0)
   {
     printf("等待子进程成功: sig: %d, code: %d\n", status&0x7F, (status>>8)&0xFF);
   }

  }
  return 0;
}


结果:

 

 执行cd ..命令或者切换路经的命令时,只是让创建的子进程进行了路径切换,而子进程是运行完毕就结束了,我们希望的是执行cd ..等命令时,是对父进程shell进行操作。由父进程shell执行的命令,叫做内建命令。

chdir:改变当前进程的路径

 if(strcmp(arr2[0],"cd")==0&&arr2[1]!=NULL)
  {
     chdir(arr2[1]);//更改当前进程的路径
     continue;
  }

结果:

环境变量:

1.环境变量会被子进程继承下去,所以他会有全局属性
2.当我们进行程序替换的时候,当前进程的环境变量非但不会被替换,而且是继承父进程的! !因为环境变量是系统的数据

在shell内部新增自己的环境变量- putenv 接口(自己写的shell里面的环境变量是继承父进程的,在自己写的shell里面增加环境变量,不会影响父进程中的环境变量)


if(strcmp(arr2[0],"export")==0&&arr2[1]!=NULL) 
  {
     putenv(arr2[1]);
      continue;
   }

演示:先创建一个程序打印不存在的环境变量

[LF@ecs-100710 lesson2]$ cat test.c
#include<stdio.h>
#include<stdlib.h>
extern char** environ;
int main()
{
  printf("%s\n",getenv("AABB"));
  return 0;
}

结果:

 后面运行自己的shell,在自己的shell里面增加环境变量AABB,并运行test程序打印该环境变量。

结果:

 出错了,并且在自己的shell里面查看环境变量AABB也不存在。

原因:因为把环境变量放入到 putenv(arr2[1]);,而arr2的内容每次都会重新赋值,所以要将新导入的环境放入一个固定的地方。

例如:

void putline(char* p)
{
  putenv(p);
} 
char arr3[size];//保存环境变量
 if(strcmp(arr2[0],"export")==0&&arr2[1]!=NULL) 
  {
      strcpy(arr3,arr2[1]);
      putline(arr3);//将环境变量导入这个进程中。父进程创建的子进程会继承父进程的环境变量
      continue;
  }

 结果:

 

完整代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<iostream>
using namespace std;

#define size 100
#define UU " "

void putline(char* p)
{
  putenv(p);
}
int main()
{
  char arr1[size];//存储程序名字
  char* arr2[size];//程序如何执行
  char arr3[size];//保存环境变量
  while(true)
  {
    cout<<"[LF@ecs-100710]#:";
    fgets(arr1,size,stdin);//会读取\n,\n后面有一个\0;
    arr1[strlen(arr1)-1]='\0';
    //printf("%s\n",arr1);

    arr2[0]=strtok(arr1," ");
    int index=1;
    if(strcmp(arr2[0],"ls")==0)
    {
      arr2[index++]=(char*)"--color=auto";
    }
    while(arr2[index]=strtok(NULL,UU))
    {
       index++;
    }
    //进行程序替换
    
       if(strcmp(arr2[0],"cd")==0&&arr2[1]!=NULL)
       {
             chdir(arr2[1]);//更改当前进程的路径
             continue;
       }
       if(strcmp(arr2[0],"export")==0&&arr2[1]!=NULL) 
       {
             strcpy(arr3,arr2[1]);
             putline(arr3);//将环境变量导入这个进程中。父进程创建的子进程会继承父进程的环境变量
             continue;
       }
    pid_t id=fork();
    if(id==0)
    {
       execvp(arr2[0],arr2);
       exit(-1);
    }
    int status=0;
   pid_t ret= waitpid(id,&status,0);
   if(ret>0)
   {
     printf("等待子进程成功: sig: %d, code: %d\n", status&0x7F, (status>>8)&0xFF);
   }

  }
  return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值