1.fork()函数与vfork()函数
fork()函数
作用:
用于创建一个新的子进程。它会复制调用它的进程的整个地址空间(内存、文件描述符等)。
返回值
>0,表示执行的父进程,返回值是子进程的PID(进程ID)。
=0,表示执行的子进程。
<0,表示创建子进程失败。
地址空间
父进程和子进程的地址空间是相互独立的。也就是说,父子进程各自有自己的变量副本,对一个进程变量的修改不会影响另一个进程。
相关代码
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程
printf("I am the child process with PID %d\n", getpid());
} else {
// 父进程
printf("I am the parent process with PID %d, child PID is %d\n", getpid(), pid);
}
return 0;
}
vfork()函数
作用
创建一个新的子进程,但它不会复制父进程的地址空间。子进程与父进程共享一个地址空间,知道子进程调用_exit()或者exec()结束时才分离。
返回值
>0,表示执行的父进程,返回值是子进程的PID(进程ID)。
=0,表示执行的子进程。
<0,表示创建子进程失败。
地址空间
子进程与父进程共享地址空间,因此在子进程中修改变量会影响父进程中的变量。在子进程调用_exit()或者exec()之前,父进程会被阻塞,无法继续执行。
相关代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid = vfork();
if (pid < 0) {
perror("vfork failed");
return 1;
} else if (pid == 0) {
// 子进程
printf("I am the child process with PID %d\n", getpid());
_exit(0); // 子进程立即退出或调用 exec() 系列函数
} else {
// 父进程在子进程调用 _exit() 或 exec() 之前被阻塞
printf("I am the parent process with PID %d, child PID is %d\n", getpid(), pid);
}
return 0;
}
2.getpid()函数与getppid()函数
getpid()函数
作用
返回当前进程的进程ID(PID)。
用途
用于唯一标识一个进程。每个正在运行的进程都有一个唯一的进程 ID,可以用来区分不同的进程。
getppid()函数
作用
返回当前进程的父进程的进程ID。
用途
用于了解当前进程的父进程是谁。父进程是启动或创建当前进程的进程。
相关代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork error!\n");
return -1;
}
else if(pid > 0)
{
//父进程
printf("father process\n");
printf("子进程的Pid = %d\n",pid);
printf("父进程的pid = %d\n",getpid());
sleep(5);
}
else
{
//子进程
printf("child process\n");
printf("子进程的pid =%d\n",getpid());
printf("父进程的pid =%d\n",getppid());
sleep(10);
}
return 0;
}
3.exit()函数与_exit()函数
exit()函数
作用
用于终止当前进程的执行,并向操作系统返回一个状态码。它不仅会终止进程,还会执行一些必要的清理操作,例如关闭文件描述符、刷新 I/O 缓冲区等。
参数
0表示正常退出。
非零值表示异常退出。具体值可以自定义,也可以使用标准定义的宏,例如 EXIT_SUCCESS
(一般为 0)和 EXIT_FAILURE
(一般为 1)。
_exit()函数
作用
立即终止进程,不进行任何清理操作(不刷新缓冲区、不调用 atexit()
函数),直接返回状态码给操作系统。
总结
exit()
是一种安全终止程序的方法,它会完成所有必要的清理工作。对于普通的进程终止,exit()
是首选;而对于需要立即退出、避免影响父进程的情况(如子进程中的错误处理),则使用 _exit()
。
4.waitpid
作用
用于使父进程等待特定的子进程结束,并获取其退出状态。它是进程间通信和管理的重要工具,通常用于防止子进程变成僵尸进程。
函数原型
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
参数
pid:指定要等待的子进程的进程ID。
status:指向整型变量的指针,用于存放子进程的退出状态。如果不需要获取状态,可以传递NULL。
options:该参数可用于设置等待行为的选项。
重要宏
-
WIFEXITED(status)
: 检查子进程是否正常退出。 -
WEXITSTATUS(status)
: 获取子进程的退出状态码(仅当WIFEXITED
为真时有效)。 -
WIFSIGNALED(status)
: 检查子进程是否因信号而终止。 -
WTERMSIG(status)
: 获取导致子进程终止的信号(如果是因信号而终止)。
5.system()函数
函数原型
#include <stdlib.h>
int system(const char *command);
参数
command:这是一个指向 C 风格字符串的指针,该字符串包含要执行的命令。该命令会被传递给系统的命令行(Shell)。
注意事项
跨平台差异
system()函数所执行的命令取决于操作系统,例如CLS是Windows的清屏命令,而clear是Linux和MacOS的清屏命令。
安全问题
如果command的内容是用户输入的,则可能会带来安全风险(命令注入)。尽可能避免直接使用用户输入的内容作为system()的参数。
5.exec()族函数
exec()族函数的主要作用是在一个进程内启动另外一个程序。这些函数用于取代当前进程的映射,换句话说就是将当前运行的程序替换为新的程序。
作用
1.进程管理
替换进程映象。exec()函数允许当前进程用新程序的映象替换自己,这意味着可以在同一进程上下文中执行不同的程序,而不需要创建新的进程。
2.执行命令
命令执行。在Shell(是一种命令行界面程序,允许用户与操作系统进行交互)和其他应用程序中,exec()函数用于执行用户输入的命令。通过结婚fork()函数,Shell可以创建子进程并在其中执行命令,然后再返回父进程。
3.灵活的参数传递
多种参数形式。exec()函数族提供多种形式(execl()、execv()等),允许开发者以不同方式传递参数和环境变量,使其适应各种需求。
4.安全性和资源管理
进程隔离。使用exec()进行进程替换可以确保新程序在干净的环境中运行,从而降低了安全风险,并有效管理系统资源。
避免资源泄露。调用exec()后,远进程的资源(如文件描述符等)会被清理,确保资源得到合理管理。
5.适应多任务环境
支持多任务处理。在现代多任务操作系统中,exec()使的程序能够灵活切换和执行不同的任务,提高了系统的灵活性和响应能力。