Linux进程控制 (下)

                             

去年的6月4日,我写了第一篇博客。回想起这一年来的点点滴滴,感觉现在的我比一年前自己提高了好多。真希望在秋招里和大家一起能够有很好的表现,在暑假里我们接着卷~

目录

进程程序替换

替换原理

替换函数

int execl(const char *path, const char *arg, ...)

int execlp(const char *file, const char *arg, ...)

int execv(const char *path, char *const argv[])

int execvp(const char *file, char *const argv[])

int execle(const char *path, const char *arg, ...,char *const envp[])

int execve(const char *path, char *const argv[], char *const envp[])

 int execvpe(const char *file, char *const argv[],char *const envp[])

替换函数之间的关系

SHELL简易制作 


进程程序替换

替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支)。

如何让子进程执行一个"全新的程序"呢?

子进程往往要调用一种exec*函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动进程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

        程序替换的本质就是把程序的代码和数据加载到进程特定的上下文中!!C/C++程序要运行,必须先加载到内存中!

如何加载?加载器!加载器的底层实现就是exec*程序替换函数! 

替换函数

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[]);

函数解释

1、这些函数如果调用成功则加载新的程序(替换的新程序)从启动代码开始执行,不再返回。
2、如果调用出错则返回-1。
3、所以exec函数只有出错的返回值(-1)而没有成功的返回值。

命名理解

l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量

int execl(const char *path, const char *arg, ...)

const char*path:要执行目标程序所在的路径(全路径:所在路径+文件名 )--- 你要执行谁?

const char* arg:在命令行上怎么执行,在这里参数就怎么一个一个的传递进去。 --- 你要怎么执行。

...:可变参数列表,传参必须以NULL作为传递的结束。

举个栗子:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main()
{
    pid_t fd = fork();
    if(fd == 0)
    {
        //child
        printf("command begin...\n");
        execl("/usr/bin/ls", "ls", "-a", "-l","-i", NULL);
        printf("command end...\n");
        exit(1);
    }
    //parent
    waitpid(-1, NULL, 0);
    printf("wait child success\n");
    return 0;
}

运行结果:

我们来和命令行上的指令作对比:

 

我们发现前后的结果都是一样的。 同时我们发现子进程发生程序替换之后,后面的代码就没有再执行了。

 

int execlp(const char *file, const char *arg, ...)

const char* file:要执行的文件名 --- 不需要加路径(在环境变量里会自动搜索)。 

const char* arg:在命令行上怎么执行,在这里参数就怎么一个一个的传递进去。 --- 你要怎么执行。

...:可变参数列表,传参必须以NULL作为传递的结束。

举个栗子:

int main()
{
    pid_t fd = fork();
    if(fd == 0)
    {
        //child
        printf("command begin...\n");
        execlp("ls", "ls", "-a", "-l","-i", NULL);
        printf("command end...\n");
        exit(1);
    }
    //parent
    waitpid(-1, NULL, 0);
    printf("wait child success\n");
    return 0;
}

运行结果和命令行上的结果作对比:

execlp("ls", "ls", "-a", "-l","-i", NULL):和上面的execl有些相似,我们可以理解p是PATH,结合前面讲的环境变量的知识,可以知道这种函数OS会在环境变量中自动查找执行命令的路径。不要认为第一个和第二个"ls"是重复的,它们代表的含义是不一样的!

第一个"ls":你要执行谁?

第二个"ls":你要怎么执行?

int execv(const char *path, char *const argv[])

const char*path:要执行目标程序所在的路径(全路径:所在路径+文件名 )--- 你要执行谁?

char* const argv[]:把要怎么执行的方法写在一个指针数组里面,然后传数组名即可。

举个栗子:

int main()
{
    pid_t fd = fork();
    if(fd == 0)
    {
        //child
        printf("command begin...\n");
        char* argv[] = {"ls", "-a", "-l", "-i", NULL};
        execv("/usr/bin/ls", argv);
        printf("command end...\n");
        exit(1);
    }
    //parent
    waitpid(-1, NULL, 0);
    printf("wait child success\n");
    return 0;
}

运行结果和命令行上的结果对比:

char* argv[] = {"ls", "-a", "-l", "-i", NULL};

execv("/usr/bin/ls", argv);

int execvp(const char *file, char *const argv[])

const char* file:要执行的文件名 --- 不需要加路径(在环境变量里会自动搜索)。

char* const argv[]:把要怎么执行的方法写在一个指针数组里面,然后传数组名即可。

举个栗子:

int main()
{
    pid_t fd = fork();
    if(fd == 0)
    {
        //child
        printf("command begin...\n");
        char* argv[] = {"ls", "-a", "-l", "-i", NULL};
        execvp("ls", argv);
        printf("command end...\n");
        exit(1);
    }
    //parent
    waitpid(-1, NULL, 0);
    printf("wait child success\n");
    return 0;
}

运行结果和命令行上的结果对比:

char* argv[] = {"ls", "-a", "-l", "-i", NULL};

execvp("ls", argv);

int execle(const char *path, const char *arg, ...,char *const envp[])

const char*path:要执行目标程序所在的路径(全路径:所在路径+文件名 )--- 你要执行谁?

char* const argv[]:把要怎么执行的方法写在一个指针数组里面,然后传数组名即可。

char* const envp[]:针对执行的程序使用自己的环境变量,不使用系统默认的。还记得那个环境变量的数组结构吗?在之前进程概念的博客里有讲解。

我们通过这个函数就可以用一个可执行程序来调用另一个可执行程序了:

mytext.c

int main()
{
    extern char** environ;
    for(int i = 0; environ[i]; i++)
    {
        printf("%s\n",environ[i]);
    }
    printf("my mytext running ... done\n");
    return 0;
}

test.c

int main()
{
    pid_t fd = fork();
    if(fd == 0)
    {
        //child
        printf("command begin...\n");
        char* env[] = {
            "hello world",
            "hello wmm",
            "hello cyq",
            NULL
        };
        execle("./mytext", "mytext", NULL, env);
        printf("command end...\n");
        _exit(1);
    }
    //parent
    waitpid(-1, NULL, 0);
    printf("wait child success\n");
    return 0;
}

运行结果:

我们可以看到./test程序执行了./mytext里面的代码了。

char* env[] = {"hello world","hello wmm", "hello cyq",NULL };

execle("./mytext", "mytext", NULL, env);

 ./mytext:指明当前路径下的mytext可执行程序。

mytext:执行该程序,这里可以不用加./,因为在前面搜索路径已经匹配过了。

env:给要运行mytext程序传过去的环境变量。

int execve(const char *path, char *const argv[], char *const envp[])

const char* filename:显示写全路径。

char* const argv[]:把要怎么执行的方法写在一个指针数组里面,然后传数组名即可。

char* const envp[]:针对执行的程序使用自己的环境变量,不使用系统默认的。

举个栗子:

mytext.c

int main()
{
    extern char** environ;
    for(int i = 0; environ[i]; i++)
    {
        printf("%s\n",environ[i]);
    }
    printf("my mytext running ... done\n");
    return 0;
}

test.c

int main()
{
    pid_t fd = fork();
    if(fd == 0)
    {
        //child
        printf("command begin...\n");
        char* env[] = {
            "hello world",
            "hello wmm",
            "love",
            "hello cyq",
            NULL
        };
        char* argv[] = {"mytext", NULL};
        execve("./mytext", argv, env);
        printf("command end...\n");
        _exit(1);
    }
    //parent
    waitpid(-1, NULL, 0);
    printf("wait child success\n");
    return 0;
}

运行结果:

execve("./mytext", argv, env):./mytext是全路径,你要执行谁? argv是执行方法,写在了指针数组里面,以NULL结尾。表示我们传的env表示环境变量,以NULL结尾。

 int execvpe(const char *file, char *const argv[],char *const envp[])

const char* file:要执行的文件名 --- 不需要加路径(在环境变量里会自动搜索)。

char* const argv[]:把要怎么执行的方法写在一个指针数组里面,然后传数组名即可。

char* const envp[]:针对执行的程序使用自己的环境变量,不使用系统默认的。

这个栗子就不举了~

替换函数之间的关系

事实上,只有execve是真正的系统调用,其它五个函数底层最终都调用execve,所以execve在man手册第2节,其它函数在man手册第3节。

这些函数之间的关系如下图所示:

SHELL简易制作 

我们根据之前讲的知识可以制作一个简易的SHELL,来帮助我们更好去理解进程。

代码如下:

在这里我就不去分析这个代码了,如果老铁们有看不懂的,联系博主,博主会热情帮助的~

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>

#define NUM 128
#define CMD_NUM 64

int main()
{
    char command[NUM];
    while(1)
    {
        char* argv[CMD_NUM] = {NULL};
        //1、打印提示符
        command[0] = 0; //用这种方式,可以做到以O(1)时间复杂度,清空字符串
        printf("[cyq@MYhome mydir]# ");
        fflush(stdout); //冲刷缓冲区
        
        //2、获取命令字符串
        fgets(command, NUM, stdin);//不能使用scanf,因为它不能读取空格!
        command[strlen(command) - 1] = '\0';//"ls\n\0" 把\n置'\0'

        //3、解析命令字符串,char* argv[]
        //strtok
        //"ls -a -b -c\0"
        const char* step = " "; //查找空格,分割
        argv[0] = strtok(command, step);
        int i = 1;
        while(argv[i] = strtok(NULL,step))
        {
            i++;
        }

        //4、检测命令是否需要shell本身执行的,内建命令
        //cd 是要让bash进程改变工作目录的,
        //而让bash创建的子进程去cd是不可以的!
        if(strcmp(argv[0], "cd") == 0)
        {
            if(argv[1] != NULL)
            {
                chdir(argv[1]);
                continue;
            }
        }

        //5、执行第三方命令
        if(fork() == 0)
        {
            //child
            execvp(argv[0], argv);
            exit(1);
        }

        int status = 0;
        waitpid(-1, &status, 0);
        printf("exit code: %d\n",(status>>8)&0xff);
    }
    return 0;
}

实验效果:

 看到这里,给博主点个赞吧~

                        

猜你喜欢

转载自blog.csdn.net/qq_58724706/article/details/125132844