IO进、线程——进程的创建、退出、回收、fork()和vfork的区别、exit()和_exit()区别、exec函数族

目录

1、创建进程
2、退出进程
3、fork() 和 vfork() 的区别
4、exit() 和 _exit() 的区别
5、exec() 函数族
6、回收进程

进程系统调用

在操作系统中,进程是程序在执行过程中的一个实例,是计算机中最基本的执行单位。进程系统调用是操作系统提供给用户程序或其他进程使用的接口,通过这些系统调用,用户程序可以向操作系统请求服务或获取资源,实现进程的创建撤销通信调度等功能。

进程:程序执行一次的过程

程序和进程的区别:

程序是静态的二进制有序集合,进程是动态的执行过程
程序只有文本和数据,进程除了文本和数据还有系统级别的数据

进程是资源分配的最小单位

1. 创建进程——fork() 、 vfork()

①fork()

pid_t fork(void);

功能:创建子进程
返回值:成功返回等于0或者大于0的数,失败返回-1子进程返回0,父进程返回子进程ID号

fork创建出来的子进程,和父进程相比运行的先后顺序都是随机,但是一般情况下时父进程先运行。

代码示例
#include <stdio.h>
#include <unistd.h>

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

②vfork()

pid_t vfork(void);

功能:创建子进程
返回值:成功返回等于0或者大于0的数,失败返回-1,子进程返回0,父进程返回子进程ID号
:只能先运行子进程,并且子进程结束了(exit或者exec)才能运行父进程

vfork创建出来的子进程首先一定是先运行子进程,然后再运行父进程。

代码示例
#include <stdio.h>
#include <unistd.h>

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

2. 进程退出——exit() 、 _exit()

void exit(int status);

功能:让当前进程退出
参数说明
status:进程退出时想要传递的值(传递给回收者)

void _exit(int status);

功能:让当前进程退出
参数说明
status:进程退出时想要传递的值(传递给回收者)
_exit进程退出时不清空IO缓冲区,exit退出时要清空

3、fork和vfork都是创建新进程的系统调用,但它们有一些重要的区别:

①、进程创建方式:

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时需要格外小心,确保子进程不会对父进程的地址空间进行修改,以避免产生不确定的行为。

4、exit和_exit的区别

①、exit()函数是C标准库提供的函数,而_exit()函数是系统调用,直接由内核提供。
②、exit()函数在调用时会执行一系列清理工作,包括调用atexit()注册的函数,关闭打开的文件流等。而_exit()函数会立即终止进程,不进行任何清理工作。
③、exit()函数会刷新缓冲区,将缓冲区中的数据写入文件。而_exit()函数不会刷新缓冲区,可能会导致输出的数据不完整。
④、exit()函数可以返回一个整数值,作为进程的退出状态码,通常用于表示进程执行的结果。而_exit()函数没有返回值,直接终止进程。

5、exec()函数族

exec函数族用于在当前进程中执行一个新的程序映像,取代当前进程的代码和数据。这样做的效果是当前进程的执行内容会被新的程序替换,从而使得原来的进程变成了新程序的进程。

exec函数族有多个不同的函数,它们可以根据需要提供更多的控制选项。以下是exec函数族的一些常见函数:

//头文件
#include <unistd.h>

①:execl()

int execl(const char *path, const char *arg0, ... /* (char *) NULL */);

功能:从文件中加载并执行一个新的程序映像,置换当前的程序映像。
返回值:exec函数族中的函数没有返回值,因为它们在成功执行时会替换当前进程的代码和数据,导致当前进程的执行流被新程序替代,从而不会返回到exec函数调用的位置。如果执行失败,这些函数会返回-1,并设置相应的错误码,此时当前进程的代码和数据仍然保持不变。
参数说明:
path:指定了要执行的程序的路径。
arg0:是要执行的程序的名称(通常传递给argv[0]),之后的参数是要传递给新程序的命令行参数列表,最后以NULL结尾。

#include <stdio.h>
#include <unistd.h>
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 <stdio.h>
#include <unistd.h>
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 <stdio.h>
#include <unistd.h>
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 <stdio.h>
#include <unistd.h>
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 <stdio.h>
#include <unistd.h>
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,并设置相应的错误码。执行失败时,应当根据错误码进行错误处理。

6、回收进程——wait() 、waitpid()

某个进程退出后会变成僵尸态,如果不回收这个进程那么会一直占用系统资源
①wait()

#include <sys/wait.h>

pid_t wait(int *wstatus);

功能:阻塞等待回收子进程
返回值:成功返回回收到的进程ID,失败返回-1
参数说明
wstatus:保存exit的值以及子进程退出状态是否正常

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

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 <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

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()最大区别

wait函数是一个阻塞式的回收函数,它会一直等待直到有子进程退出。而waitpid函数可以选择回收某个特定的子进程,还可以使用WNOHANG选项进行非阻塞回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小羊客栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值