1、创建进程
2、退出进程
3、fork() 和 vfork() 的区别
4、exit() 和 _exit() 的区别
5、exec() 函数族
6、回收进程
在操作系统中,进程是程序在执行过程中的一个实例,是计算机中最基本的执行单位。进程系统调用是操作系统提供给用户程序或其他进程使用的接口,通过这些系统调用,用户程序可以向操作系统请求服务或获取资源,实现进程的创建、撤销、通信和调度等功能。
进程:程序执行一次的过程
程序和进程的区别:
程序是静态的二进制有序集合,进程是动态的执行过程
程序只有文本和数据,进程除了文本和数据还有系统级别的数据
进程是资源分配的最小单位
pid_t fork(void);
功能:创建子进程
返回值:成功返回等于0或者大于0的数,失败返回-1子进程返回0,父进程返回子进程ID号
#include
#include
int main() {
int x = 10;
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return -1;
} else if (pid == 0) {
// 子进程
x += 5;
printf("Child: x = %d\n", x);
} else {
// 父进程
x -= 5;
printf("Parent: x = %d\n", x);
}
return 0;
}
//Parent: x = 5
//Child: x = 15
pid_t vfork(void);
#include
#include
int main() {
int x = 10;
pid_t pid = vfork();
if (pid < 0) {
perror("vfork");
return -1;
} else if (pid == 0) {
// 子进程
x += 5;
printf("Child: x = %d\n", x);
_exit(0); // 使用_vfork的子进程应该使用_exit而不是exit
} else {
// 父进程
x -= 5;
printf("Parent: x = %d\n", x);
}
return 0;
}
//Child: x = 15
//Parent: x = 5
功能:创建子进程
返回值:成功返回等于0或者大于0的数,失败返回-1,子进程返回0,父进程返回子进程ID号
注:只能先运行子进程,并且子进程结束了(exit或者exec)才能运行父进程
void exit(int status);
功能:让当前进程退出
参数说明:
status:进程退出时想要传递的值(传递给回收者)
void _exit(int status);
功能:让当前进程退出
参数说明:
status:进程退出时想要传递的值(传递给回收者)
_exit进程退出时不清空IO缓冲区,exit退出时要清空
①、进程创建方式:
fork: 创建一个新的进程,新进程是父进程的副本。父进程和子进程在执行fork之后的代码时是相互独立的,并行执行,但各自拥有各自的地址空间,互不干扰。
vfork: 创建一个新的进程,新进程与父进程共享地址空间。在子进程执行vfork之后的代码时,它会继续使用父进程的地址空间,直到调用exec函数族中的某个函数或者调用_exit函数为止。
②、性能:
vfork通常比fork更高效,因为它避免了复制整个地址空间的开销。在使用fork创建子进程时,需要复制父进程的地址空间,这对于大内存的父进程来说可能会耗费较多时间和资源。
③、安全性:
vfork的使用要求比较严格,子进程在调用vfork后不能修改地址空间中的内容,否则可能会导致不可预期的错误。而fork创建的子进程则不受这样的限制,它可以自由修改自己的地址空间。
④、返回值:
fork返回新创建的子进程的PID(进程ID),父进程中可以通过返回值来判断是父进程还是子进程。
vfork的返回值与fork相同,但在子进程中不能依赖返回值来区分父子进程,因为vfork在子进程中返回之前,父进程是被阻塞的,所以返回值并不代表是父进程还是子进程。
⑤、用途:
fork常用于一般的进程创建场景,其中子进程通常会执行一些独立的任务,或者进行进程间通信(IPC)。
vfork通常用于创建新进程,并在新进程中立即执行exec函数族中的某个函数,从而加载新的可执行程序,替换当前进程的映像。这样可以避免在创建新进程时复制大的地址空间。
总的来说,fork用于创建独立的进程,而vfork用于创建执行新程序的进程,并在加载新程序后立即替换当前进程。在使用vfork时需要格外小心,确保子进程不会对父进程的地址空间进行修改,以避免产生不确定的行为。
①、exit()函数是C标准库提供的函数,而_exit()函数是系统调用,直接由内核提供。
②、exit()函数在调用时会执行一系列清理工作,包括调用atexit()注册的函数,关闭打开的文件流等。而_exit()函数会立即终止进程,不进行任何清理工作。
③、exit()函数会刷新缓冲区,将缓冲区中的数据写入文件。而_exit()函数不会刷新缓冲区,可能会导致输出的数据不完整。
④、exit()函数可以返回一个整数值,作为进程的退出状态码,通常用于表示进程执行的结果。而_exit()函数没有返回值,直接终止进程。
exec函数族用于在当前进程中执行一个新的程序映像,取代当前进程的代码和数据。这样做的效果是当前进程的执行内容会被新的程序替换,从而使得原来的进程变成了新程序的进程。
exec函数族有多个不同的函数,它们可以根据需要提供更多的控制选项。以下是exec函数族的一些常见函数:
//头文件
#include
①:execl()
int execl(const char *path, const char *arg0, ... /* (char *) NULL */);
功能:从文件中加载并执行一个新的程序映像,置换当前的程序映像。
返回值:exec函数族中的函数没有返回值,因为它们在成功执行时会替换当前进程的代码和数据,导致当前进程的执行流被新程序替代,从而不会返回到exec函数调用的位置。如果执行失败,这些函数会返回-1,并设置相应的错误码,此时当前进程的代码和数据仍然保持不变。
参数说明:
path:指定了要执行的程序的路径。
arg0:是要执行的程序的名称(通常传递给argv[0]),之后的参数是要传递给新程序的命令行参数列表,最后以NULL结尾。
#include
#include
int main(int argc, char *argv[])
{
//原型int execl(const char *path, const char *arg0, ... /* (char *) NULL
//printf("父进程:我的PID是 %d\n", getpid());
execl("/bin/ls", "ls", "-l", NULL);
printf("我不会被打印出来\n");
return 0;
}
②execv()
int execv(const char *path, char *const argv[]);
功能:从文件中加载并执行一个新的程序映像。
参数说明:
path:指定了要执行的程序的路径。
argv:是一个字符串数组,用于传递给新程序的命令行参数列表,数组最后一个元素必须是NULL。
#include
#include
int main()
{
//原型:int execv(const char *path, char *const argv[]);
char *argv[] = {"ls", "-a", NULL};
execv("/bin/ls", argv);
printf("我都出来了,说明你的execv函数没有执行\n");
return 0;
}
③execle()
int execle(const char *path, const char *arg0, ... /* (char *) NULL, char *const envp[] */);
功能:从文件中加载并执行一个新的程序映像,并指定新程序的环境变量。
参数说明:
path:指定了要执行的程序的路径。
arg0:是要执行的程序的名称(通常传递给argv[0]),之后的参数是要传递给新程序的命令行参数列表,最后以NULL结尾。
envp:是一个字符串数组,用于传递新程序的环境变量列表,数组最后一个元素必须是NULL。
#include
#include
int main(int argc, char *argv[])
{
//原型:int execle(const char *path, const char *arg0, ... /* (char *) NULL, char *const envp[] */);
char *envp[] = {"MY_ENVP = hello", NULL};
execle("/bin/ls", "ls", "-l", NULL, envp);
printf("我不应该被打印出来!\n");
return 0;
}
④execvp()
int execvp(const char *file, char *const argv[]);
功能:从PATH环境变量指定的路径中查找并执行一个新的程序映像。
参数说明:
file:是要执行的程序的名称。
argv:是一个字符串数组,用于传递给新程序的命令行参数列表,数组最后一个元素必须是NULL。
#include
#include
int main()
{
//原型:int execvp(const char *file, char *const argv[]);
char *argv[] = {"ls", "-l", NULL};
execvp("ls", argv);
printf("我不应该被打印出来!\n");
return 0;
}
⑤execve()
int execve(const char *path, char *const argv[], char *const envp[]);
功能:从文件中加载并执行一个新的程序映像,并指定新程序的环境变量。
参数说明:
path:指定了要执行的程序的路径。
argv:是一个字符串数组,用于传递给新程序的命令行参数列表,数组最后一个元素必须是NULL。
envp:是一个字符串数组,用于传递新程序的环境变量列表,数组最后一个元素必须是NULL。
#include
#include
int main()
{
//原型:int execve(const char *path, char *const argv[], char *const envp[]);
char *argv[] = {"ls", "-l", NULL};
char *envp[] = {"MY_ENVP=hello", NULL};
execve("/bin/ls", argv, envp);
printf("我不会被打印出来!\b");
return 0;
}
这些exec函数族的函数调用成功时,不会返回,因为当前进程的代码和数据都被新程序替换了。如果函数调用失败,则会返回-1,并设置相应的错误码。执行失败时,应当根据错误码进行错误处理。
某个进程退出后会变成僵尸态,如果不回收这个进程那么会一直占用系统资源
①wait()
#include
pid_t wait(int *wstatus);
功能:阻塞等待回收子进程
返回值:成功返回回收到的进程ID,失败返回-1
参数说明:
wstatus:保存exit的值以及子进程退出状态是否正常
#include
#include
#include
#include
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("Child process: My PID is %d\n", getpid());
// 子进程退出,退出值为 42
_exit(42);
} else if (pid > 0) {
// 父进程
printf("Parent process: My PID is %d\n", getpid());
int status;
// 使用 wait 回收子进程
pid_t child_pid = wait(&status);
if (WIFEXITED(status)) {
// 子进程正常退出
int exit_status = WEXITSTATUS(status);
printf("Child process with PID %d exited normally with status %d\n", child_pid, exit_status);
} else if (WIFSIGNALED(status)) {
// 子进程因收到信号而退出
int signal_num = WTERMSIG(status);
printf("Child process with PID %d exited due to signal %d\n", child_pid, signal_num);
} else {
// 其他错误情况
printf("Child process with PID %d exited abnormally\n", child_pid);
}
} else {
// fork 失败
printf("Fork failed.\n");
}
return 0;
}
②waitpid()
pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:回收子进程(可以非阻塞,也可以指定要回收谁)
返回值:成功返回进程ID,失败返回-1
参数说明:
pid:用于指定要回收的进程号-1:表示回收任意子进程
0:表示回收特定的子进程号wstatus:保存进程退出值以及状态(与wait函数的wstatus参数相同)
options:阻塞或者非阻塞的方式WNOHANG:非阻塞
0:阻塞
#include
#include
#include
#include
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("Child process: My PID is %d\n", getpid());
// 子进程退出,退出值为 42
_exit(42);
} else if (pid > 0) {
// 父进程
printf("Parent process: My PID is %d\n", getpid());
int status;
// 使用 waitpid 回收子进程,这里使用 -1 表示回收任意子进程
pid_t child_pid = waitpid(-1, &status, 0);
if (WIFEXITED(status)) {
// 子进程正常退出
int exit_status = WEXITSTATUS(status);
printf("Child process with PID %d exited normally with status %d\n", child_pid, exit_status);
} else if (WIFSIGNALED(status)) {
// 子进程因收到信号而退出
int signal_num = WTERMSIG(status);
printf("Child process with PID %d exited due to signal %d\n", child_pid, signal_num);
} else {
// 其他错误情况
printf("Child process with PID %d exited abnormally\n", child_pid);
}
} else {
// fork 失败
printf("Fork failed.\n");
}
return 0;
}
wait函数是一个阻塞式的回收函数,它会一直等待直到有子进程退出。而waitpid函数可以选择回收某个特定的子进程,还可以使用WNOHANG选项进行非阻塞回收。