1. fork()
1.1 函数原型
- fork() 用于创建一个子进程,使用时需要包含头文件 <unistd.h> 。
- <unistd.h> 定义了一些符合 POSIX 标准的系统调用接口,fork() 本质是一个系统调用,fork() 的函数原型如下:
pid_t fork (void)
返回值定义:- -1 : 创建子程序失败
- 0 : 在子进程中中返回0
- >0 : 在父进程中返回子进程的pid
1.2 fork()示例
- 运行如下程序:
#include <unistd.h> #include <stdio.h> int main (void) { int fork_pid = fork(); if(fork_pid) { /* 父进程,返回子进程pid */ printf("parent process ...\n\r"); printf(" fork_pid = %d\n\r", fork_pid); printf(" pid = %d\n\r", getpid()); } else { /* 子进程, 返回0 */ printf("child process ...\n\r"); printf(" fork_pid = %d\n\r", fork_pid); printf(" pid = %d\n\r", getpid()); } }
- 运行结果如下,就结果而言 if 和 else 分支的程序都被执行了, 为什么?直接说答案:原因是if 和 else 是在不同的进程中执行的。
wsl_dmf@DESKTOP-7SAFD5K:~/tmp$ ./a.out parent process ... fork_pid = 2059 pid = 2058 child process ... fork_pid = 0 pid = 2059
1.3 fork()总结
- fork() 是一个系统调用,所以当程序执行 fork() 时,程序会由 用户态 进入 内核态。
- fork() 会创建一个子进程,子进程是父进程的拷贝:
- 子进程 进程控制块(task_struct) 会完全复制自 父进程 ,所以子进程中程序运行的上下文和父进程是完全一致的。例如 PC(程序计数指针/程序运行的位置) 和 SP(栈指针)。
- 父进程的整个代码段和数据段都会被复制,子进程和父进程的 虚拟内存 是完全一致的。
- 从物理内存 的角度来说,父进程 和 子进程的 物理内存是完全一样且单独存储的,子进程 的物理内存是 父进程 的复制,父子进程 的 页表 分别指向不同的物理空间
- 子进程 不会抢占父进程,创建好的子进程会被加入到就绪列表中等待调度。
2 execvp()
2.1 execvp()函数原型
- execvp() 将当前进程的空间全部由file 所指定的程序覆盖,并从头开始运行,execvp()不会创建进程,使用时需要包含头文件 <unistd.h> 。
- <unistd.h> 定义了一些符合 POSIX 标准的系统调用接口,execvp() 本质是一个系统调用,execvp() 的函数原型如下:
int execvp(const char *file, char *const argv[]);
- 功能说明:将当前进程的空间全部由file 所指定的程序覆盖,并从头开始运行;
- 参数说明:
- file:要执行的程序名。
- argv: 要执行程序的参数列表(本文暂不展开)
- 返回值:
- 0:成功
- -1:失败
2.2 execvp()示例
-
示例程序1
#include <unistd.h> #include <stdio.h> int main (void) { char* argvs[] = {"ls", "-l", "-a", NULL}; execvp("ls", argvs); /* 执行execvp() 时当前进程空间的内容会被全部替换,所以下面代码不会被执行 */ printf("Hello, world"); return 0; }
-
示例程序1执行结果
wsl_dmf@DESKTOP-7SAFD5K:~/tmp$ ./a.out total 32 drwxr-xr-x 3 wsl_dmf wsl_dmf 4096 May 6 23:21 . drwxr-x--- 10 wsl_dmf wsl_dmf 4096 May 6 23:08 .. drwxr-xr-x 2 wsl_dmf wsl_dmf 4096 Apr 29 23:53 .vscode -rwxr-xr-x 1 wsl_dmf wsl_dmf 16016 May 6 23:21 a.out -rw-r--r-- 1 wsl_dmf wsl_dmf 144 May 6 23:21 main.c
-
示例程序2:
/* 编译目标文件 : main.out */ #include <unistd.h> #include <stdio.h> int main (void) { char* argvs[] = {NULL}; printf("main_pid = %d\n\r", getpid()); execvp("./exec.out", argvs); /* 执行execvp() 时当前进程空间的内容会被全部替换,所以下面代码不会被执行 */ printf("Hello, world"); return 0; }
/* 编译目标文件 : ecec.out */ #include <unistd.h> #include <stdio.h> int main (void) { printf("exec_pid = %d\n\r", getpid()); return 0; }
- 示例程序2执行结果
wsl_dmf@DESKTOP-7SAFD5K:~/tmp$ ./main.out main_pid = 8581 exec_pid = 8581 wsl_dmf@DESKTOP-7SAFD5K:
2.3 execvp()总结
- execvp() 是一个系统调用,所以当程序执行 execvp() 时,程序会由 用户态 进入 内核态。
- execvp()并不会创建进程(使用同一个pid),而是将指定的程序覆盖当前进程空间,并从头开始执行。
- execvp() 通常会和 fork() 一起使用,使用 fork() 创建子进程,并在子进程中执行 execvp() 加载对应的程序执行。