在这篇博客中,我们将深入探索如何在 C++ 程序中实现子进程的创建与执行,以及父子进程间的管道通信。核心代码提供了一个框架,用于接收用户命令、创建子进程并利用 execvp 系统调用执行这些命令。此外,我们通过创建管道(pipe),展示了如何在父子进程间安全地传递数据。
本文重点介绍 fork, pipe, 和 execvp 的使用方法,并解释了如何将标准输出和标准错误从子进程重定向到父进程。这一过程涉及对 Unix 系统调用的深入理解,尤其是进程间通信(IPC)的概念。我们的目标是提供一个清晰的指南,帮助读者理解和实现在现代操作系统中广泛应用的进程创建、执行和通信机制。
该教程适合对操作系统、进程管理以及 Unix/Linux 系统编程感兴趣的读者。无论是系统编程新手还是有经验的开发者,都可以通过这个实例加深对进程间通信和命令行界面(CLI)应用编程的理解。
一共有六个分三组记忆
execlp -> execvp
execl -> execv
execle -> execve
记忆口诀如下:
0、exec是通用前缀
1、 p 代表第一个参数是file 也就是程序的名字比如 ls, 如果名字没带p, 代表第一个参数是path, 比如 /bin/ls
2、 l 代表参数传递的是list, 参数是一个个传递的,最后一个参数为NULL
3、 v 代表argv, 参数是用char* 数组传递的
4、 e 代表环境变量
1、execlp l->list,p-> file 参数就是(file, a1, a2, a3,..., NULL);
2、execvp v->argv,p->file. 参数就是file, aegv)
3、execl l->list,没p -> path 参数就是(path, a1, a2, a3, ..., NULL);
4、execv v->argv,没p->path 参数(path, argv);
5、execle l->list,没p->path, e->env 参数(path, a0, a1, ..., NULL, env);
6、execve v->aegv,没p->path, e->env 参数(path, argv, env);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <iostream>
#include <string>
bool isCommandSupport(std::string command) {
if (command == "ls" || command == "pwd" || command == "ps" || command == "cat" || command == "grep" ||
command == "wc" || command == "exit") {
return true;
}
return false;
}
void childTask() {
while (true) {
// 获得一整行输入
std::string line;
size_t len = 0;
std::getline(std::cin, line);
// 进行分割
char *p = strtok((char *) line.c_str(), " ");
char *argv[100];
int argc = 0;
while (p != NULL) {
argv[argc++] = p;
p = strtok(NULL, " ");
}
argv[argc] = NULL;
if (argc == 0) {
continue;
}
if (isCommandSupport(argv[0])) {
if (strcmp(argv[0], "exit") == 0) {
exit(0);
}
} else {
printf("command not support\n");
continue;
}
int pipefd[2];
pid_t pid;
char buf;
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { /* 子进程 */
close(pipefd[0]); // 关闭读端
dup2(pipefd[1], STDOUT_FILENO); // 将标准输出重定向到管道写端
dup2(pipefd[1], STDERR_FILENO); // 将标准错误也重定向到管道写端
close(pipefd[1]); // 关闭原始写端
execvp(argv[0], argv);
_exit(EXIT_FAILURE);
} else { /* 父进程 */
close(pipefd[1]); // 关闭写端
while (read(pipefd[0], &buf, 1) > 0) {
write(STDOUT_FILENO, &buf, 1);
}
close(pipefd[0]); // 关闭读端
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
} else if (WIFSIGNALED(status)) {
printf("child terminated abnormally, signal %d\n", WTERMSIG(status));
}
}
}
}
int main() {
childTask();
}