漫话linux:进程替换

#如何看待IBM中国研发部裁员?#

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时,需要构建execmycmd这两个目标

        3.-o $@指定输出的可执行文件名,其中$@是自动变量,代表当前规则的目标名(execmycmd

        4.$^是另一个自动变量,代表所有的依赖文件列表(这里分别是exec.cmycmd.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:进程等待的使用场景(子进程结束而父进程仍然存在时使用,获取子进程的退出信息)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值