欢迎来到Cefler的博客
博客主页:那个传说中的man的主页
个人专栏:题目解析
推荐文章:题目大解析(3)
之前我们所创建的所有的子进程,执行的代码,都是父进程代码的一部分,如果我们想让子进程执行新的程序,不再和父进程有瓜葛了。这时我们就引入—— 程序替换 程序替换 程序替换
概念
在Linux中,进程控制的程序替换是指一个进程用另一个新程序来替换自己的执行映像。这个新程序完全取代了原来的程序,包括代码、数据和堆栈等信息,成为了新的运行中的进程。这种程序替换的操作通常是通过调用exec
函数来完成的。
程序替换常用于创建新的进程或者更新正在运行的进程的执行映像。例如,在一个Shell中,当你输入一个命令时,Shell就会创建一个新的进程来执行相应的程序。在这种情况下,Shell就会调用fork
创建一个新的子进程,然后在子进程中调用exec
来加载并执行新的程序。
程序替换的一个重要特点是原有的进程ID(PID)不会改变,这意味着虽然进程的执行映像已经被替换,但是它仍然保持着原来的PID。这样做的好处是可以保持一些与PID相关的状态,比如父子进程关系、进程的权限等。
总之,Linux中进程控制的程序替换是通过exec
函数来实现的,它允许一个进程用另一个程序来替换自己的执行映像,从而创建新的进程或者更新正在运行的进程的执行映像。
在Linux中,有多个替换函数可用于执行其他程序。下面是对这些函数的介绍:
execl
:该函数用于在当前进程中执行一个可执行文件。它接受可执行文件的路径和一系列的参数作为参数,并将当前进程替换为新的可执行文件。这个函数的原型如下:#include
int execl(const char *path, const char *arg, ...);
这个函数在执行成功后不会返回,如果调用失败,则会设置errno并返回-1[1]。execlp
:与execl
类似,但是它可以在系统的PATH
环境变量指定的路径中搜索可执行文件。这个函数的原型如下:
#include
int execlp(const char *file, const char *arg, ...);
execle
:这个函数与execl
类似,但是它还接受一个额外的参数envp
,用于指定新程序的环境变量。这个函数的原型如下:
#include
int execle(const char *path, const char *arg, ..., char * const envp[]);
同样地,这个函数在执行成功后不会返回,如果调用失败,则会设置errno并返回-1[1]。
execv
:与execl
类似,但是它接受一个参数数组来传递可执行文件的路径和参数。这个函数的原型如下:
#include
int execv(const char *path, char *const argv[]);
同样地,这个函数在执行成功后不会返回,如果调用失败,则会设置errno并返回-1[1]。
execvp
:与execv
类似,但是它可以在系统的PATH
环境变量指定的路径中搜索可执行文件。这个函数的原型如下:#include
int execvp(const char *file, char *const argv[]);
同样地,这个函数在执行成功后不会返回,如果调用失败,则会设置errno并返回-1[1]。execve
:与execv
类似,但是它还接受一个额外的参数envp
,用于指定新程序的环境变量。这个函数的原型如下:#include
int execve(const char *path, char *const argv[], char *const envp[]);
同样地,这个函数在执行成功后不会返回,如果调用失败,则会设置errno并返回-1[1]。这些以exec开头的函数,统称exec函数
这些替换函数可以在创建新进程后,用于在新进程中执行另一个程序。当调用这些函数时,当前进程会被完全替换为新程序。这些函数的区别在于参数的传递方式和对环境变量的处理方式。通过这些函数,我们可以方便地在Linux中执行其他程序并传递参数[1]。
exe函数
的本质就是加载器,将磁盘中的程序加载到内存中!
代码:
#include
#include
int main()
{
printf("pid:%d,exe command begin\n", getpid());
execl("/usr/bin/pwd", "pwd", NULL);
printf("pid:%d,exe command end\n", getpid());
return 0;
}
首先在Makefile文件中使得make能一次性创建两个可执行程序
创建mytest.cc:
myprocess.c:
the result:
当谈论Linux的Shell脚本文件时,我们实际上指的是一种文本文件,其中包含了一系列的Shell命令,这些命令按照特定的顺序被解释器(比如bash、sh等)逐行执行,就像在命令行中一行行输入命令一样。
下面是一个简单的Shell脚本例子,它将会创建一个包含当前日期和时间的文件,并显示一条消息:
#!/usr/bin/bash
# 这是一个简单的Shell脚本
# 显示一条消息
echo "hello shell"
让我们来解释一下这个例子:
#!/usr/bin/bash
:这是脚本文件的第一行,称为shebang,用来告诉系统使用哪个解释器来执行脚本。在这个例子中,#!/usr/bin/bash
告诉系统使用Bash来解释执行脚本。
#
开头的行是注释,用来对脚本进行说明,不会被解释器执行。
echo "hello shell"
:这一行使用echo
命令在终端上显示一条消息。
要注意的是,为了使一个Shell脚本文件可以被执行,你需要先将其设置为可执行文件。你可以使用chmod +x script.sh
命令来赋予这个Shell脚本文件执行权限。
总的来说,Linux的Shell脚本文件是一种包含了一系列Shell命令的文本文件,它可以用来自动化执行一系列任务,非常适合用于批处理、系统管理和自动化部署等场景。
script.sh:
当我们进行程序替换时,子进程对应的环境变量,是可以直接从父进程来的(这就可以解释环境变量的全局性)
而子进程的环境变量,并不是因为程序替换来的,而是在进程继承中,继承进程地址空间里面的数据(数据中包含父进程的环境变量),而程序替换:只会替换新程序的代码和数据,环境变量不会被替换!
我们在进程状态、类型、优先级、命令行参数概念、环境变量(重要)、程序地址空间中知道main函数有argc,argv,env三个关于命令行的参数。实际上我们上面学的替换函数,就是将命令打包成char* argv[],environ打包成char* env[]在程序替换中进行传递
关于上图中的environ参数,我们可以直接传,也可以不显示传,它都会默认传递的。
execle程序替换传递环境变量时,是原地覆盖,而不是新增。
若要新增的话,可以用到函数putenv()
#include
#include
#include
#include
#include
#include
#define NUM 1024
#define SIZE 64
#define SEP " "
char cwd[1024];
char enval[1024];
int lastcode = 0;
//#define Debug 1
const char* getUsername()
{
const char*name = getenv("USER");
if(name) return name;
else return "none";
}
const char* getHostname()
{
const char* hostname = getenv("HOSTNAME");
if(hostname) return hostname;
else return "none";
}
const char* getCwd()
{
const char* cwd = getenv("PWD");
if(cwd) return cwd;
else return "none";
}
int getUserCommand(char* usercommand,int num)
{
printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());
char* r = fgets(usercommand,num,stdin);
if(r==NULL) return 1;
usercommand[strlen(usercommand)-1] = '\0';//消除最后一个输入的\n
return strlen(usercommand);
}
void commandSplit(char* in,char* out[])
{
int argc = 0;
out[argc++] = strtok(in,SEP);//第一次分隔需要传入源字符串
while(out[argc++] = strtok(NULL,SEP));//当strtok传回NULL时就结束
#ifdef Debug
for(int i = 0;out[i];i++)
{
printf("%d:%s\n",i,out[i]);
}
#endif
}
int execute(char* argv[])
{
pid_t id = fork();
if(id<0) return -1;
else if(id==0)//child
{
//exec command
execvp(argv[0],argv);//argv[0]第一个就是执行命令
exit(1);
}
else
{
//father
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid>0)
{
lastcode = WEXITSTATUS(status);//
}
}
return 0;
}
void cd(const char* path)
{
chdir(path);
char tmp[1024];//创建临时变量存储
getcwd(tmp,sizeof(tmp));//getcwd获取当前路径
sprintf(cwd,"PWD=%s",tmp);//tmp读入到cwd中
putenv(cwd);//将新的PWD环境变量导出
}
int doBuildin(char* argv[])
{
if(strcmp(argv[0],"cd")==0)
{
char* path = NULL;
if(argv[1] == NULL) path = ".";
else path = argv[1];
cd(path);
return 1;
}
else if(strcmp(argv[0], "export") == 0)
{
if(argv[1] == NULL) return 1;
strcpy(enval, argv[1]);
putenv(enval); // ???
return 1;
}
else if(strcmp(argv[0], "echo") == 0)
{
char *val = argv[1]+1; // $PATH $?
if(strcmp(val, "?") == 0)
{
printf("%d\n", lastcode);
lastcode = 0;
}
else{
printf("%s\n", getenv(val));
}
return 1;
}
else if(0){}
return 0;
}
int main()
{
while(1){
char usercommand[NUM];
//1.打印提示符&&获取用户命令符字符串获取成功
int n = getUserCommand(usercommand,sizeof(usercommand));
if(n<=0) continue;//如果长度小于等于0就没必要执行相关命令了
char* argv[SIZE];
//2.分割字符串
commandSplit(usercommand,argv);
//3.检查是否为内建命令,内建命令不能让子进程进行,让父进程自己执行
n = doBuildin(argv);
if(n) continue;
//4.执行相对应的命令
execute(argv);
}
return 0;
}
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注❤️ ,学海无涯苦作舟,愿与君一起共勉成长