逻辑书写
请写下你的逻辑,与参考逻辑对比,如有缺失,可细看本文
完整代码示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
// 线程函数:计算平方
void *square(void *arg) {
int n = *(int *)arg;
printf("线程1: %d的平方是%d\n", n, n * n);
return NULL;
}
// 线程函数:计算立方
void *cube(void *arg) {
int n = *(int *)arg;
printf("线程2: %d的立方是%d\n", n, n * n * n);
return NULL;
}
int main() {
pid_t pid = fork();
if (pid == -1) {
perror("fork失败");
exit(EXIT_FAILURE);
} else if (pid == 0) { // 子进程
printf("子进程启动,执行命令:ls -l\n");
execl("/bin/ls", "ls", "-l", NULL); // 通过execl执行命令
perror("execl失败"); // 若execl执行失败才会执行到这里
exit(EXIT_FAILURE);
} else { // 父进程
printf("父进程创建子进程,子进程PID=%d\n", pid);
// 创建两个线程
pthread_t t1, t2;
int num = 5; // 传递给线程的参数
pthread_create(&t1, NULL, square, &num);
pthread_create(&t2, NULL, cube, &num);
// 等待线程结束
pthread_join(t1, NULL);
pthread_join(t2, NULL);
// 等待子进程结束
waitpid(pid, NULL, 0);
printf("父进程结束\n");
}
return 0;
}
代码解析
-
进程创建(
fork
):fork()
创建子进程。父进程获取子进程的 PID,子进程的pid
为 0。- 子进程调用
execl
执行/bin/ls -l
命令,替换自身代码为ls
的代码。 - 如果
execl
失败,子进程会打印错误并退出。
-
线程操作(
pthread
):- 父进程创建两个线程
t1
和t2
,分别执行square
和cube
函数。 - 线程函数通过指针接收参数(这里是整数
num = 5
)。 pthread_join
确保父进程等待线程结束再继续。
- 父进程创建两个线程
-
进程同步(
waitpid
):- 父进程调用
waitpid
等待子进程结束,避免僵尸进程。
- 父进程调用
知识回顾
waitpid
回顾之前对fork讲解过程中,涉及到的PCB
(近程控制块)。在这里再次补充并巩固,PCB
中包含了该进程的各种状态信息,如是否正在运行、是否已结束、退出状态等。
在本次训练中 waitpid
便是利用这些状态,完成等待判断!
execl
关于execl,我并没有找到很好的资料解读,因此我想通过本次训练来阐述execl的具体用法。
execl 函数是 UNIX 和类 UNIX 操作系统中用于执行一个新程序的系统调用。当你调用 execl 时,当前进程的镜像(即代码、数据和堆栈等)会被新程序的镜像所替换,但进程 ID(PID)保持不变。这意味着,从外部看,这个进程似乎运行了一个新的程序,但实际上它只是被新的程序覆盖了。
下面是 execl 函数的一个基本解释,特别是针对代码 execl(“/bin/ls”, “ls”, “-l”, NULL);:
函数原型
int execl(const char *path, const char *arg, ..., (char *) NULL);
参数解释
path:要执行的程序的路径。在这个例子中,它是 “/bin/ls”,即列出目录内容的 ls 命令的绝对路径。
arg:传递给新程序的命令行参数。第一个参数 arg 通常是程序的名称(在这个例子中为 “ls”),它会被存储在新程序的 argv[0] 中。随后的参数是传递给程序的其他命令行参数(在这个例子中为 “-l”),最后一个参数必须是 (char *) NULL,用来标记参数列表的结束。
代码解释
execl(“/bin/ls”, “ls”, “-l”, NULL); 这行代码的作用是:
- 替换当前进程的镜像为 “/bin/ls” 程序。
- 将 “ls” 作为新程序的 argv[0],将 “-l” 作为 argv[1] 传递给新程序。
- NULL 标记了参数列表的结束。
当这行代码执行时,如果 execl 调用成功,那么当前进程将被 ls 程序替换,并且 ls 程序将以 -l 选项(长格式列出目录内容)运行。由于进程镜像被替换,因此当前进程中的后续代码将不会被执行。
如果 execl 调用失败,它将返回 -1,并且 errno 将被设置为一个错误码,指示失败的原因。但是,在大多数情况下,如果 execl 调用失败,程序将立即终止,因为当前进程的镜像已经被部分或全部销毁,而且通常没有后续的代码来检查 execl 的返回值或处理错误。
需要注意的是,由于 execl 会替换当前进程的镜像,因此它通常用于创建新进程后立即执行新程序的场景(例如,在 fork 调用之后)。
运行结果
父进程创建子进程,子进程PID=12345
子进程启动,执行命令:ls -l
总用量 8
-rwxr-xr-x 1 user group 8760 May 10 10:00 demo
-rw-r--r-- 1 user group 678 May 10 10:00 demo.c
线程1: 5的平方是25
线程2: 5的立方是125
父进程结束
- 子进程调用
ls -l
列出当前目录内容。 - 父进程的两个线程分别计算平方和立方。
- 所有线程和子进程结束后,父进程退出。
关键点总结
fork()
:创建子进程,父子进程并行执行不同代码。exec
族函数:子进程通过execl
加载外部命令,替换原有程序。pthread
:父进程通过多线程并行处理任务,共享进程资源(如变量num
)。- 同步机制:
waitpid
和pthread_join
确保进程和线程按顺序结束。
通过这个案例,你可以清晰看到进程、线程和外部命令执行的协作模式。
进程之间的代码执行顺序并不是固定的。
参考逻辑
父进程通过 fork
创建 子进程。
子进程调用 execl
执行外部命令(如 ls -l
)。
父进程创建 两个线程,分别计算数值的平方和立方。
父进程等待子进程和所有线程结束。