1.替换原理:通过fork
创建子进程后,子进程会开始执行与父进程相同的程序,但它们可能会执行不同的代码路径
2.通常,子进程随后会调用某种exec
函数来运行一个全新的程序。这个调用导致子进程的用户空间内的代码和数据被新程序完全替代,并从新程序的入口点开始执行
3.调用exec
并不会创建一个新的进程;因此,该进程的标识符(PID)在调用exec
之前和之后保持不变
exec 系列函数(包括 execl, execv, execle, execlp, execvp)在 Unix/Linux 操作系统中用于在当前进程上下文中启动一个新的程序。与 fork 和 spawn 类似的功能不同,exec 不会创建新的进程,而是替换当前进程的用户空间映像,即它将当前进程的代码段、数据段和堆栈替换为新程序的代码和数据,同时保留进程的内核资源,如打开的文件描述符、环境变量等。这一过程被称为“执行替换”或简称为“执行”
4.exec的工作原理
1.调用exec:当一个进程调用 exec
函数时,该进程的执行流将被暂停,操作系统内核将介入
2.替换用户空间:内核将卸载当前进程的用户空间映像,包括代码段、数据段和堆栈,然后加载新程序的映像到相同的虚拟地址空间。这意味着调用 exec
的进程将开始执行新程序的代码,而不是原先的代码
3.参数和环境变量:exec
函数允许传递参数和环境变量给新程序。这些参数和变量被复制到新程序的执行环境中,使得新程序可以访问它们
4.进程id保持不变:执行 exec
的进程的进程 ID(PID)不会改变。这意味着父进程可以通过先前的 PID 来等待和收集子进程的信息,即使子进程已经通过 exec
替换了其执行的程序
5.内核资源保持:除了用户空间的替换外,进程的内核资源,如打开的文件描述符、信号处理器和环境变量等,通常会被保留。这意味着新程序可以继承这些资源,除非明确关闭或替换它们
5.exec的优势和限制:
1.优势:
1.节省资源:由于没有创建新进程,exec
相对于 fork
和 spawn
更节省资源,因为它避免了复制进程上下文的开销
2.快速启动:exec
的执行速度通常比创建新进程更快,因为它不需要初始化一个新的进程上下文
2.缺点:不可逆:一旦 exec
成功执行,原来的程序将不再存在。这意味着不能在执行 exec
后回到原来程序的执行状态
6.替换函数:在Linux中,exec
函数用于执行其他程序,它会将当前进程的地址空间替换为新程序的地址空间,从而实现程序的替换执行。六种以exec开头的函数,统称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 char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
7.命名理解:
l(参数采用列表),v(参数用数组),p(环境变量PATH),e(自己维护环境变量)
8.返回值:
1.这一类函数,如果执行成功,都不会返回;它们会替换当前进程的映像,并从新程序的入口开始执行
2.如果有错误发生,这些函数会返回-1,不会发生替换
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
printf("进程开始\n");
execl("/usr/bin/lss","lss","-a","-l",NULL);
printf("进程结束\n");
return 0;
}
[hbr@VM-16-9-centos 6.replace]$ ./myproc
进程开始
进程结束
这些区别主要在于参数传递方式的不同(列表vs数组)以及是否自动搜索PATH
环境变量或手动指定环境变量
9. execl(const char *path, const char *arg, ...):通过
指定的路径执行程序,参数以逗号分隔的列表形式直接传递,列表必须以NULL
结束
path:要执行程序的路径
arg:要传给程序的第一个参数(通常为程序名称)
......:后续参数,以NULL
终止
特点:参数以参数列表的形式传递
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
pid_t id=fork();
if(id==0)
{
printf("子进程开始运行,pid:%d\n",getpid());
sleep(2);
execl("/usr/bin/ls","ls","-a","-l",NULL);
exit(1);
}
else
{
printf("父进程开始运行,pid:%d\n",getpid());
int status=0;
pid_t id=waitpid(-1,&status,0);
if(id>0)
{
printf("wait success,exit code:%d\n",WEXITSTATUS(status));
}
}
return 0;
}
[hbr@VM-16-9-centos 6.replace]$ ./myproc
父进程开始运行,pid:7607
子进程开始运行,pid:7608
//sleep两秒后执行
total 36
789092 drwxrwxr-x 3 hbr hbr 4096 Mar 14 16:59 .
788707 drwxrwxr-x 8 hbr hbr 4096 Mar 13 12:42 ..
789097 -rw-rw-r-- 1 hbr hbr 289 Mar 14 14:55 execl1.c
789093 -rw-rw-r-- 1 hbr hbr 83 Mar 13 12:30 Makefile
789028 -rwxrwxr-x 1 hbr hbr 8664 Mar 14 16:59 myproc
789136 -rw-rw-r-- 1 hbr hbr 765 Mar 14 16:59 myproc.c
789090 drwxrwxr-x 2 hbr hbr 4096 Mar 13 16:16 myshell
wait success,exit code:0
10.execlp(const char *file, const char *arg, ...):
功能与execl
相似,但它在环境变量PATH
中搜索file
,不需要完整路径
file:要执行的程序名称
arg:第一个参数,通常是程序名称
......:后续参数,以NULL
终止
特点:在PATH
搜索文件,参数以列表形式传递
execlp("ls","ls","-a","-l","-i",NULL);
[hbr@VM-16-9-centos 6.replace]$ ./myproc
父进程开始运行,pid:9463
子进程开始运行,pid:9464
total 36
789092 drwxrwxr-x 3 hbr hbr 4096 Mar 14 17:05 .
788707 drwxrwxr-x 8 hbr hbr 4096 Mar 13 12:42 ..
789097 -rw-rw-r-- 1 hbr hbr 289 Mar 14 14:55 execl1.c
789093 -rw-rw-r-- 1 hbr hbr 83 Mar 13 12:30 Makefile
789028 -rwxrwxr-x 1 hbr hbr 8704 Mar 14 17:05 myproc
789136 -rw-rw-r-- 1 hbr hbr 814 Mar 14 17:05 myproc.c
789090 drwxrwxr-x 2 hbr hbr 4096 Mar 13 16:16 myshell
wait success,exit code:0
11.execv(const char *path, char *const argv[]):
通过指定的路径执行程序,参数通过argv
数组传递
path:程序的路径
argv[]:一个指针数组,每个指针指向一个参数字符串,数组必须以NULL
结束
特点:参数以数组形式传递
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
pid_t id=fork();
if(id==0)
{
printf("子进程开始运行,pid:%d\n",getpid());
sleep(2);
char *const _argv[10]={
(char*)"ls",
(char*)"-l",
(char*)"-a",
(char*)"-i",
NULL };
execv("/usr/bin/ls",_argv);
exit(1);
}
else
{
printf("父进程开始运行,pid:%d\n",getpid());
int status=0;
pid_t id=waitpid(-1,&status,0);
if(id>0)
{
printf("wait success,exit code:%d\n",WEXITSTATUS(status));
}
}
return 0;
}
[hbr@VM-16-9-centos 6.replace]$ ./myproc
父进程开始运行,pid:7607
子进程开始运行,pid:7608//sleep两秒后执行
total 36
789092 drwxrwxr-x 3 hbr hbr 4096 Mar 14 16:59 .
788707 drwxrwxr-x 8 hbr hbr 4096 Mar 13 12:42 ..
789097 -rw-rw-r-- 1 hbr hbr 289 Mar 14 14:55 execl1.c
789093 -rw-rw-r-- 1 hbr hbr 83 Mar 13 12:30 Makefile
789028 -rwxrwxr-x 1 hbr hbr 8664 Mar 14 16:59 myproc
789136 -rw-rw-r-- 1 hbr hbr 765 Mar 14 16:59 myproc.c
789090 drwxrwxr-x 2 hbr hbr 4096 Mar 13 16:16 myshell
wait success,exit code:0
12.execvp(const char *file, char *const argv[]):
功能与execv
相似,但它在环境变量PATH
中搜索file
file:要执行的程序名称
argv[]:参数数组,以NULL
结束
特点:在PATH
搜索文件,参数以数组形式传递
char *const _argv[10]={
(char*)"ls",
(char*)"-l",
(char*)"-a",
(char*)"-i",
NULL };
execvp("ls",_argv);
[hbr@VM-16-9-centos 6.replace]$ ./myproc
父进程开始运行,pid:10531
子进程开始运行,pid:10532
total 36
789092 drwxrwxr-x 3 hbr hbr 4096 Mar 14 17:08 .
788707 drwxrwxr-x 8 hbr hbr 4096 Mar 13 12:42 ..
789097 -rw-rw-r-- 1 hbr hbr 289 Mar 14 14:55 execl1.c
789093 -rw-rw-r-- 1 hbr hbr 83 Mar 13 12:30 Makefile
789028 -rwxrwxr-x 1 hbr hbr 8672 Mar 14 17:08 myproc
789136 -rw-rw-r-- 1 hbr hbr 844 Mar 14 17:08 myproc.c
789090 drwxrwxr-x 2 hbr hbr 4096 Mar 13 16:16 myshell
wait success,exit code:0
13.Makefile编译多个文件
1..PHONY:all
声明了一个伪目标all
,意味着all
不是一个真正的文件名,它的作用主要是作为一个便捷的方式来列出所有的默认构建目标
2.all:exec mycmd
定义了一个规则,它指出要构建all
时,需要构建exec
和mycmd
这两个目标
3.-o $@
指定输出的可执行文件名,其中$@
是自动变量,代表当前规则的目标名(exec
或mycmd
)
4.$^是另一个自动变量,代表所有的依赖文件列表(这里分别是exec.c
和mycmd.c
)
[hbr@VM-16-9-centos 6.replace]$ cat Makefile
.PHONY:all
all:exec mycmd
exec:exec.c
gcc -std=c99 -o $@ $^
mycmd:mycmd.c
gcc -std=c99 -o $@ $^
.PHONY:clean
clean:
rm -f exec mycmd
[hbr@VM-16-9-centos 6.replace]$ make
gcc -std=c99 -o mycmd mycmd.c
[hbr@VM-16-9-centos 6.replace]$ ll
total 44
-rwxrwxr-x 1 hbr hbr 8664 Mar 14 17:25 exec
-rw-rw-r-- 1 hbr hbr 844 Mar 14 17:08 exec.c
-rw-rw-r-- 1 hbr hbr 289 Mar 14 14:55 execl.c
-rw-rw-r-- 1 hbr hbr 138 Mar 14 17:24 Makefile
-rwxrwxr-x 1 hbr hbr 8456 Mar 14 17:26 mycmd
-rw-rw-r-- 1 hbr hbr 364 Mar 14 17:26 mycmd.c
drwxrwxr-x 2 hbr hbr 4096 Mar 13 16:16 myshell
14.命令行程序mycmd.c
1.首先它检查是否有恰好两个参数(argc!=2
),即程序名和一个额外的参数。如果不是,它会打印 "can not execute!" 并退出
2.接下来,它比较第二个参数(argv[1]
)与字符串 "-a" 和 "-b"。如果是 "-a",它打印 "command a";如果是 "-b",它打印 "command b";否则,它打印 "default!"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char *argv[])
{
if(argc!=2)
{
printf("can not execute!\n");
exit(1);
}
if(strcmp(argv[1],"-a")==0)
printf("command a\n");
else if(strcmp(argv[1],"-b")==0)
printf("command b\n");
else
printf("default!\n");
return 0;
}
[hbr@VM-16-9-centos 6.replace]$ ./mycmd
can not execute!
[hbr@VM-16-9-centos 6.replace]$ ./mycmd -a
command a
[hbr@VM-16-9-centos 6.replace]$ ./mycmd -b
command b
[hbr@VM-16-9-centos 6.replace]$ ./mycmd -c
default!
15.传入自己的可执行文件
1.在这个程序中使用的 execl
函数,是用来在当前进程中加载并执行一个新程序的。这个函数需要可执行文件的路径作为参数,而不是源代码文件
2.源代码文件不能直接被 execl
执行,因为它们是文本文件,需要先编译成机器可以理解和执行的二进制格式
3.当代码中使用 execl(myfile, "mycmd", "-b", NULL);
时,操作系统会查找路径 /home/hbr/linux/process/6.replace/mycmd
指向的可执行文件,加载它到当前进程的内存空间中,并开始执行
4.如果 myfile
变量指向的是一个 C 语言源代码文件,操作系统无法执行它,因为它不是二进制的可执行格式
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
//绝对路径和相对路径均可
//const char *myfile="/home/hbr/linux/process/6.replace/mycmd";
const char *myfile="./mycmd";
int main()
{
pid_t id=fork();
if(id==0)
{
printf("子进程开始运行,pid:%d\n",getpid());
sleep(2);
execl(myfile,"mycmd","-b",NULL);
exit(1);
}
else
{
printf("父进程开始运行,pid:%d\n",getpid());
int status=0;
pid_t id=waitpid(-1,&status,0);
if(id>0)
{
printf("wait success,exit code:%d\n",WEXITSTATUS(status));
}
}
return 0;
}
16.const char *myfile="./mycmd"; 这行代码定义了一个指向字符串的指针myfile,这个字符串包含了要执行的程序的路径。这里使用的是相对路径,意味着mycmd程序位于当前工作目录下。如果程序位于其他位置,可以通过修改这个字符串来指定正确的路径,无论是绝对路径还是相对路径
17.execl(myfile,"mycmd","-b",NULL); 这行代码实际上是在调用execl函数,用于在当前进程(这里是子进 程)中执行一个新的程序。execl函数的第一个参数是要执行的程序的路径,这里通过myfile变量传递。第二个参数是程序名,这里是"mycmd",它是传递给新程序的argv[0]的值。接下来的参数是传递给mycmd程序的命令行参数,在这个例子中只有一个"-b"。最后一个参数必须是NULL,标志着参数列表的结束
[hbr@VM-16-9-centos 6.replace]$ make
gcc -std=c99 -o exec exec.c
gcc -std=c99 -o mycmd mycmd.c
[hbr@VM-16-9-centos 6.replace]$ ./exec
父进程开始运行,pid:20549
子进程开始运行,pid:20550
command b
wait success,exit code:0
tips:进程等待的使用场景(子进程结束而父进程仍然存在时使用,获取子进程的退出信息)