Linux进程
Linux进程管理是Linux操作系统中一个重要的概念,涉及到进程的创建、调度、同步、通信等多个方面。
Linux进程是操作系统进行资源分配和调度的基本单位。每个进程都有自己的独立内存空间、数据、代码和状态。
进程的基本概念
- 进程定义 :进程是正在运行的程序的实例,包括代码、数据和状态信息。
- 进程与程序的区别 :程序是存储在磁盘上的指令集合,而进程是程序在处理器上执行时的实体。
- 进程的状态 :新建、就绪、运行、等待、终止。
进程的创建
- 系统调用fork() :创建一个新的进程,通常用于进程克隆。
- 系统调用vfork() :快速创建一个新进程,用于避免复制父进程的数据。
- 系统调用exec() :在当前进程的上下文中加载一个新程序。
fork()
在Linux中,创建进程最常见的是使用 fork()
系统调用,它会创建一个与当前进程几乎完全相同的副本,这个副本称为子进程。子进程开始时会执行 fork()
调用之后的代码。
fork()
是一个基本的系统调用,用于创建一个新的进程,称为子进程。当一个进程调用 fork()
时,它会返回两次:一次在父进程中,返回子进程的PID;一次在子进程中,返回0。子进程是父进程的精确副本,包括内存映像、文件描述符、环境变量等。子进程从 fork()
调用处开始执行代码。
fork()
的典型用途包括:
- 创建并行执行的任务。
- 在父进程和子进程之间建立通信机制,如管道。
- 利用子进程来执行不同的程序或任务。
vfork()
vfork()
是一个在Unix和类Unix操作系统中用于创建进程的系统调用。与 fork()
系统调用相比,vfork()
在创建子进程时的行为略有不同,特别是在子进程执行前,它会暂停父进程的执行,并且子进程会继承父进程的数据和堆栈内存,这使得 vfork()
在某些情况下比 fork()
更高效。
需要注意的是,由于 vfork()
会复制父进程的地址空间给子进程,包括堆栈和数据段,因此在使用 vfork()
时需要特别小心。在子进程中不应该执行任何可能改变父进程地址空间的操作,如写入父进程的堆栈或数据段。一旦子进程执行完任务并退出,父进程的地址空间会恢复到 vfork()
调用前的状态。
exec()
exec()
系列函数用于在当前进程的上下文中加载并运行一个新的程序。当一个进程调用 exec()
时,它会被新程序替换掉,原有的内存空间、代码和数据被新程序的内容覆盖。exec()
不会创建新的进程,而是替换当前进程的映像。也就是说,当一个进程调用 exec
函数时,它放弃自己的代码、数据和堆栈,取而代之的是新程序的代码和数据。这意味着,一旦 exec
调用成功,原来的进程就不再存在,取而代之的是新加载的程序。
exec
函数的主要作用是在当前进程的基础上加载并运行一个新的程序,这样可以避免创建新进程的开销,同时还能保持现有的进程ID(PID)和环境信息。这种方式在进程需要改变其执行的程序时非常有用,例如在脚本中启动不同的程序,或者在程序中动态加载新的功能模块。在 exec
调用之后,新程序会从其主函数(main
)开始执行,就像它是一个全新的进程一样,执行完后不会返回调用 exec
语句之后的地方。如果 exec
调用失败,当前进程保持不变,并返回错误指示,返回到调用 exec
语句之后的地方。
exec()
的典型用途包括:
- 运行不同的程序或命令。
- 改变当前进程的执行流。
- 动态链接程序,加载共享库或模块。
exec()
系列函数包括:
execl()
:加载程序,但不提供命令行参数。execle()
:加载程序,清除环境变量。execlp()
:搜索PATH环境变量指定的目录,加载程序。execv()
:使用给定的文件描述符数组作为文件描述符。execve()
:提供完整的文件描述符表和环境变量。
fork()
和 exec()
的结合使用
fork()
和 exec()
经常结合使用来创建新的进程并运行不同的程序。典型的步骤如下:
- 父进程调用
fork()
创建子进程。 - 子进程调用
exec()
加载并运行新的程序。 - 父进程可以继续执行其他任务,或者等待子进程完成。
这种结合使用允许父进程创建多个子进程,每个子进程可以执行不同的任务或程序,从而实现并行处理和复杂的程序逻辑。同时,父进程可以通过子进程的PID来管理和监控它们的执行状态。
进程创建的实验:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> // 用于waitpid等函数
int test_fork() {
pid_t pid_tmp;
pid_t pid_child;
printf("%s() enter\n", __func__);
pid_tmp = getpid(); // 获取当前进程的PID
printf("[%s: %d]: pid_tmp = %d\n", __func__, __LINE__, pid_tmp);
// 调用fork()
pid_child = fork();
if (pid_child < 0) {
// fork失败
printf("fork failed");
} else if (pid_child == 0) {
pid_tmp = getpid(); // 获取当前进程的PID
printf("[%s: %d]: pid_tmp = %d\n", __func__, __LINE__, pid_tmp);
// 子进程
// 调用execlp()执行'ps'命令
// execlp()会替换当前进程的映像,包括代码、数据和堆栈
// 第一个参数ps是程序名称,后续参数是传递给程序的参数
// 第二个参数ps是传递给ps命令的第一个参数,通常它是命令的名称
// 第三个参数-a是传递给ps命令的第一个参数,如果ps有多个参数可以继续往后加如"-l"
// (char *)NULL: 这是execlp调用的最后一个参数,它是一个指向NULL的指针,表示参数列表的结束。在C语言中,字符串数组或参数列表通常以NULL结尾
// 如果exec成功,程序不会返回,如果失败,返回-a
execlp("ps", "ps", "-a", "-l", (char *)NULL);
// 如果exec失败,打印错误并退出
printf("execlp failed\n");
} else {
pid_tmp = getpid(); // 获取当前进程的PID
printf("[%s: %d]: pid_tmp = %d, pid_child=%d\n", __func__, __LINE__, pid_tmp, pid_child);
// 父进程
// 等待子进程结束
int status;
waitpid(pid_child, &status, 0); // waitpid()等待特定的子进程pid结束
if (WIFEXITED(status)) { // 如果子进程正常退出,则返回非零值
printf("Child process exited with success, WEXITSTATUS(status)=%d, status=%d\n", WEXITSTATUS(status), status);
} else {
printf("Child process exited with fail, WEXITSTATUS(status)=%d, status=%d\n", WEXITSTATUS(status), status);
}
}
printf("%s() exit\n\n", __func__);
return 0;
}
int test_vfork() {
pid_t pid;
printf("%s() enter\n", __func__);
// 调用vfork()创建子进程
pid = vfork();
if (pid == -1) {
// vfork()失败,输出错误信息并退出
perror("vfork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
sleep(2); // 为了更好的验证父进程会等待子进程结束
// 子进程的代码
printf("I am the child process with PID: %d\n", getpid());
// 执行完子进程的任务后退出
exit(EXIT_SUCCESS);
} else {
// 父进程的代码
printf("I am the parent process with PID: %d\n", getpid());
// 等待子进程结束
int status;
waitpid(pid, &status, 0);
// 检查子进程是否成功退出
if (WIFEXITED(status)) {
int exit_status = WEXITSTATUS(status);
printf("Child exited with status %d\n", exit_status);
}
// 继续父进程的任务
}
printf("%s() exit\n\n", __func__);
return 0;
}
int test_exec() {
pid_t pid_tmp;
pid_t pid_child;
printf("%s() enter\n", __func__);
pid_tmp = getpid(); // 获取当前进程的PID
printf("[%s: %d]: pid_tmp = %d\n", __func__, __LINE__, pid_tmp);
// 调用fork()
pid_child = fork();
if (pid_child < 0) {
// fork失败
printf("fork failed");
} else if (pid_child == 0) {
pid_tmp = getpid(); // 获取当前进程的PID
printf("[%s: %d]: pid_tmp = %d\n", __func__, __LINE__, pid_tmp);
// 子进程
// 调用execlp()执行'psxx'命令,使其调用失败,原有的内存空间不会被覆盖
// 如果exec成功,程序不会返回,如果失败,返回-a
execlp("psxx", "psxx", "-a", "-l", (char *)NULL);
// 如果exec失败,打印错误并退出
printf("execlp failed\n");
pid_tmp = getpid(); // 获取当前进程的PID
printf("[%s: %d]: pid_tmp = %d\n", __func__, __LINE__, pid_tmp);
} else {
pid_tmp = getpid(); // 获取当前进程的PID
printf("[%s: %d]: pid_tmp = %d, pid_child=%d\n", __func__, __LINE__, pid_tmp, pid_child);
// 父进程
// 等待子进程结束
int status;
waitpid(pid_child, &status, 0); // waitpid()等待特定的子进程pid结束
if (WIFEXITED(status)) { // 如果子进程正常退出,则返回非零值
printf("Child process exited with success, WEXITSTATUS(status)=%d, status=%d\n", WEXITSTATUS(status), status);
} else {
printf("Child process exited with fail, WEXITSTATUS(status)=%d, status=%d\n", WEXITSTATUS(status), status);
}
}
printf("[%s: %d]: end pid_tmp = %d\n", __func__, __LINE__, pid_tmp);
printf("%s() exit\n\n", __func__);
return 0;
}
int main()
{
test_fork();
test_vfork();
test_exec();
return 0;
}
实验结果:
eon@ubuntu:/mnt/d/ubuntu/code/test/test_process$ gcc -o test_fork test_fork.c
eon@ubuntu:/mnt/d/ubuntu/code/test/test_process$ ./test_fork
test_fork() enter
[test_fork: 13]: pid_tmp = 3469
[test_fork: 38]: pid_tmp = 3469, pid_child=3470
[test_fork: 23]: pid_tmp = 3470
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 12 11 0 80 0 - 2733 - tty1 00:00:00 sh
0 S 1000 13 12 0 80 0 - 2733 - tty1 00:00:00 sh
0 S 1000 18 13 0 80 0 - 2733 - tty1 00:00:00 sh
0 S 1000 22 18 0 80 0 - 239914 ? tty1 00:00:02 node
0 S 1000 34 33 0 80 0 - 152427 ? tty2 00:00:00 node
0 S 1000 42 41 0 80 0 - 151300 ? tty3 00:00:00 node
0 R 1000 43 22 0 80 0 - 164057 ? tty1 00:00:01 node
0 S 1000 58 22 5 80 0 - 265791 ? tty1 00:01:38 node
0 S 1000 118 22 1 80 0 - 247420 ? tty1 00:00:24 node
0 S 1000 3469 129 0 80 0 - 2704 - pts/0 00:00:00 test_fork
0 R 1000 3470 3469 0 80 0 - 3881 - pts/0 00:00:00 ps
Child process exited with success, WEXITSTATUS(status)=0, status=0
test_fork() exit
test_vfork() enter
I am the child process with PID: 3471
I am the parent process with PID: 3469
Child exited with status 0
test_vfork() exit
test_exec() enter
[test_exec: 99]: pid_tmp = 3469
[test_exec: 121]: pid_tmp = 3469, pid_child=3486
[test_exec: 109]: pid_tmp = 3486
execlp failed
[test_exec: 118]: pid_tmp = 3486
[test_exec: 133]: end pid_tmp = 3486
test_exec() exit
Child process exited with success, WEXITSTATUS(status)=0, status=0
[test_exec: 133]: end pid_tmp = 3469
test_exec() exit
eon@ubuntu:/mnt/d/ubuntu/code/test/test_process$