一张图片先了解大概其原理: 单进程替换
这张图描述了操作系统在进程替换过程中如何通过 PCB 保存进程的状态、如何管理进程的内存(如代码段、数据段和页表)、以及如何将这些信息存储到磁盘中,并在需要时进行恢复。通过这个过程,操作系统能够实现多任务处理,并确保每个进程在切换后能够从正确的地方继续执行。 多进程替换
即根据父进程创建出子进程(拷贝父进程)
然后程序替换子进程
使用man手册之后可以看到其详细的内容:
上面这些函数接口(由语言封装)把一个文件(可执行程序)替换程当前进程。 他们的作用都是进行 进程替换而准备的,接口的详细说明下文见。
单进程其替换的过程示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
// 模拟进程的状态
typedef enum {
RUNNING,
WAITING,
COMPLETED
} ProcessState;
// 进程结构体
typedef struct {
ProcessState state; // 当前进程的状态
ProcessState saved_state; // 保存的进程状态
} Process;
// 模拟进程运行
void run_process(Process* process) {
printf("进程开始运行...\n");
sleep(1); // 模拟一些处理(例如计算)
// 模拟 I/O 等待
io_wait(process);
}
// 模拟 I/O 等待过程
void io_wait(Process* process) {
printf("进程进入 I/O 等待...\n");
save_state(process); // 保存当前进程的状态
// 模拟 I/O 操作,例如磁盘读取,等待 3 秒
sleep(3);
printf("磁盘读取完成,恢复进程...\n");
restore_state(process); // 恢复进程的状态
// 继续执行
finalize_process(process);
}
// 保存进程状态
void save_state(Process* process) {
printf("保存进程状态...\n");
process->saved_state = process->state; // 保存当前进程的状态
process->state = WAITING; // 将当前进程设置为等待状态
}
// 恢复进程状态
void restore_state(Process* process) {
printf("恢复进程状态...\n");
process->state = process->saved_state; // 恢复到之前的状态
process->saved_state = RUNNING; // 清空保存的状态
}
// 结束进程
void finalize_process(Process* process) {
printf("进程继续执行并结束。\n");
process->state = COMPLETED; // 进程执行完毕,设置为完成状态
}
int main() {
// 创建并初始化进程
Process process = {RUNNING, RUNNING};
// 执行进程
run_process(&process);
return 0;
}
进程开始运行...
进程进入 I/O 等待...
保存进程状态...
磁盘读取完成,恢复进程...
恢复进程状态...
进程继续执行并结束。
这个 C 语言程序模拟了一个单进程环境中的“进程替换”过程。尽管系统只有一个进程,但通过保存和恢复状态,我们可以模拟进程被暂停(例如等待 I/O 完成)并恢复执行的过程。虽然这种机制在实际多进程环境中更为常见,但在单进程系统中,操作系统仍然会通过这种方式管理进程的执行。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
// 模拟进程的状态
typedef enum {
RUNNING,
WAITING,
COMPLETED
} ProcessState;
// 进程结构体
typedef struct {
ProcessState state; // 当前进程的状态
ProcessState saved_state; // 保存的进程状态
} Process;
// 模拟进程运行
void run_process(Process* process) {
printf("进程开始运行...\n");
sleep(1); // 模拟一些处理(例如计算)
// 模拟 I/O 等待
io_wait(process);
}
// 模拟 I/O 等待过程
void io_wait(Process* process) {
printf("进程进入 I/O 等待...\n");
save_state(process); // 保存当前进程的状态
// 模拟 I/O 操作,例如磁盘读取,等待 3 秒
sleep(3);
printf("磁盘读取完成,恢复进程...\n");
restore_state(process); // 恢复进程的状态
// 继续执行
finalize_process(process);
}
// 保存进程状态
void save_state(Process* process) {
printf("保存进程状态...\n");
process->saved_state = process->state; // 保存当前进程的状态
process->state = WAITING; // 将当前进程设置为等待状态
}
// 恢复进程状态
void restore_state(Process* process) {
printf("恢复进程状态...\n");
process->state = process->saved_state; // 恢复到之前的状态
process->saved_state = RUNNING; // 清空保存的状态
}
// 结束进程
void finalize_process(Process* process) {
printf("进程继续执行并结束。\n");
process->state = COMPLETED; // 进程执行完毕,设置为完成状态
}
int main() {
pid_t pid;
Process process = {RUNNING, RUNNING};
// 创建一个子进程
pid = fork();
if (pid == -1) {
// 创建进程失败
perror("fork失败");
exit(1);
}
else if (pid == 0) {
// 子进程部分
printf("子进程开始运行...\n");
run_process(&process); // 运行进程
exit(0); // 子进程结束
}
else {
// 父进程部分
wait(NULL); // 等待子进程结束
printf("父进程继续运行...\n");
run_process(&process); // 父进程继续运行
// 等待所有子进程结束
wait(NULL);
}
return 0;
}
子进程开始运行...
进程开始运行...
进程进入 I/O 等待...
保存进程状态...
磁盘读取完成,恢复进程...
恢复进程状态...
进程继续执行并结束。
父进程继续运行...
进程开始运行...
进程进入 I/O 等待...
保存进程状态...
磁盘读取完成,恢复进程...
恢复进程状态...
进程继续执行并结束。
再看这些接口
下面这个图片作为示例:
在执行一个程序时,第一件事确实是要找到这个程序。因此,execl 函数的第一个参数就是该程序的路径,通常是一个绝对路径(当然,相对路径也可以,但是在某些情况下执行命令时可能会受到路径的限制)。当操作系统定位到这个程序后,它需要确定以什么方式执行该程序以及传递哪些参数。因此,execl 中的第二个及后续参数就是传递给程序的选项或参数。
这些参数的形式类似于一个链表,每个选项或参数都通过一个指针传递,而链表的结尾通过一个特殊的 NULL 值来标识,以此来区分链表的末尾。这是因为,execl 必须知道何时停止读取选项,从而确保不会读取到无效或不必要的内容。因此,最后一个选项或参数必须显式设置为 NULL,这是标准的约定。
execlp 函数与 execl 函数非常相似,但有一个重要的不同点:它会在指定路径的基础上搜索系统的 PATH 环境变量。这意味着,execlp 允许我们只提供程序的名称(不需要完整的路径),操作系统会自动在 PATH 中查找该程序。
#include <stdio.h>
#include <unistd.h>
int main() {
// 使用 execlp 执行程序 ls
execlp("ls", "ls", "-l", "/home", NULL);
// 如果 execlp 执行失败,则打印错误信息
perror("execlp failed");
return 0;
}
解释:
在执行一个程序时,第一步仍然是要找到这个程序。对于 execlp,与 execl 的不同之处在于,execlp 并不需要我们指定程序的绝对路径(虽然可以提供),而是只需要提供程序的名称。操作系统会根据系统的 PATH 环境变量,自动搜索并找到该程序。
因此,execlp 函数的第一个参数是要执行的程序名称(或者路径),操作系统会在 PATH 环境变量中依次查找该程序。如果提供的是相对路径或者只提供文件名,操作系统会按照 PATH 中定义的搜索路径进行查找。
在执行一个程序时,第一步仍然是要找到这个程序。与 execlv 相似,execv 需要我们提供程序的 路径 和 参数数组。execv 的第一个参数是要执行的程序的完整路径或相对路径,后续参数是传递给程序的选项和参数,这些选项和参数通过一个数组来传递。数组的最后一个元素必须是 NULL,用来标识参数的结束。
#include <stdio.h>
#include <unistd.h>
int main() {
// 创建一个参数数组
char *args[] = {"ls", "-l", "/home", NULL};
// 使用 execv 执行程序 /bin/ls
execv("/bin/ls", args);
// 如果 execv 执行失败,则打印错误信息
perror("execv failed");
return 0;
}
解释: