什么是进程替换
进程替换就是用新进程来替换当前进程. 在 bash 中, 进程替换其实就是命令替换和管道的组合.
替换原理:
我们知道子进程存在的意义不仅仅是它能帮助父进程进行压力分摊, 最主要的功能其实是让子进程去完成其他的工作, 也就是进程替换. 子进程通过调用 exec 系列函数时, 当前进程的虚拟地址空间上的各个数据段被磁盘上指定的新程序给替换掉, 本质上并没有创建新的进程, 进程的 ID 也没有发生变化.
替换函数:
#include<unistd.h>
int execl(const char *path, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
(1)l(list):表示参数采用列表。
(2)v(vector):参数用数组。
(3)p(path):自动搜索环境变量PATH。
(4)e(env):自己维护环境变量。
函数关系:
事实上, 只有 execve 是系统调用接口, 其他都是通过调用 execve 来实现的
进程替换的简单使用:
execl:
#include <stdio.h>
#include <unistd.h>
int main() {
execl("/bin/ls", "ls", "-l", NULL);
return 0;
}
execlp:
#include <stdio.h>
#include <unistd.h>
int main() {
execlp("ls", "ls", "-l", NULL);
return 0;
}
execle:
#include <stdio.h>
#include <unistd.h>
int main() {
char* const envp[] = {
"PATH=/bin:/usr/bin",
"TERM=console",
NULL
};
execle("/bin/ls", "ls", "-l", NULL, envp);
}
execv:
#include <stdio.h>
#include <unistd.h>
int main() {
char* const argv[] = {
"ls",
"-l",
NULL
};
execv("/bin/ls", argv);
}
execvp:
#include <stdio.h>
#include <unistd.h>
int main() {
char* const argv = {
"ls",
"-l",
NULL
};
execvp("ls", argv);
}
execve:
#include <stdio.h>
#include <unistd.h>
int main() {
char* const argv[] = {
"ls",
"-l",
NULL
};
char* const envp[] = {
"PATH=/bin:/usr/bin",
"TERM=console",
NULL
};
execve("/bin/ls", argv, envp);
}
效果图:
当然, 在实际应用中我们一般是将 exec 系列函数放到子进程中, 让子进程进行进程替换, 来完成其他任务.
minishell(迷你shell)
通过进程替换来实现一个简单的 minishell:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
// 命令行
char command[1024];
int do_face() {
// 初始化命令行缓冲区 --- 全局变量默认初始化为 NULL
memset(command, 0x00, 1024);
printf("[user@localhost]$ ");
// 刷新缓冲区 --- 将缓冲区中的内容输出到屏幕上
fflush(stdout);
// %[^\n]: 接收到 '\n' 就停止接收
// %*c 接收最后一个字符但是不赋予 command
if (scanf("%[^\n]%*c", command) == 0) {
// 如果只输入 '\n' 则将 '\n' 吃掉
getchar();
return -1;
}
return 0;
}
// 将命令行中的字符串切割成各个命令
char** do_parse(char* cmd) {
// 保存各个字符串命令
static char* argv[32];
// 下标
int argc = 0;
char* cur = cmd;
while (*cur != '\0') {
if (*cur != ' ') {
argv[argc++] = cur;
while (*cur != ' ' && *cur != '\0') {
++cur;
}
if (*cur != '\0') {
*cur = '\0';
++cur;
}
}
while (*cur != '\0' && *cur == ' ') {
++cur;
}
}
// 在最后一个命令后面加 NULL
argv[argc] = NULL;
return argv;
}
// 进程替换, 让子进程来完成shell调用各个命令的任务
void do_exec() {
pid_t pid = fork();
char** argv = {NULL};
if (pid < 0) {
perror("fork error");
exit(-1);
} else if (pid == 0) {
argv = do_parse(command);
// 如果命令行不为空, 则进程替换
if (argv[0] != NULL) {
execvp(argv[0], argv);
} else {
exit(0);
}
} else {
// 进程等待
waitpid(pid, NULL, 0);
}
}
int main() {
while (1) {
if (do_face() < 0) {
continue;
}
do_exec();
}
return 0;
}