您的位置:首页 > 科技 > IT业 > 网推怎么推广_陕西疫情最新情况最新消息今天_拉新人拿奖励的app_12345微信公众号

网推怎么推广_陕西疫情最新情况最新消息今天_拉新人拿奖励的app_12345微信公众号

2025/3/4 11:25:25 来源:https://blog.csdn.net/wangchen_0/article/details/144019726  浏览:    关键词:网推怎么推广_陕西疫情最新情况最新消息今天_拉新人拿奖励的app_12345微信公众号
网推怎么推广_陕西疫情最新情况最新消息今天_拉新人拿奖励的app_12345微信公众号

1. 进程创建

1.1 fork()函数

fork() 函数创建一个新进程,新进程是调用它的父进程的副本。系统在内部为子进程分配一个新的进程 ID(PID),但子进程的内存和父进程的内存空间是分开的。调用 fork() 时,父进程和子进程的代码从 fork() 函数返回的地方开始分别执行

  • 作用: fork() 是用于创建新进程的系统调用,新进程称为子进程,复制父进程的地址空间。
  • 返回值:
    • 父进程:返回子进程的 PID。
    • 子进程:返回 0。
    • 错误:返回 -1

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main() {pid_t pid = fork();if (pid == 0) {printf("Child process: pid=%d, ppid=%d\n", getpid(), getppid());} else if (pid > 0) {printf("Parent process: pid=%d, child_pid=%d\n", getpid(), pid);} else {perror("fork failed");}return 0;
}

fork创建子进程有两种用法

(1)通过if/else父子分流,各自执行代码的一部分

(2)执行全新的程序

fork() 创建失败:

(1)内存空间不足

(2)实际用户进程数超过了限制

1.2 写时拷贝

fork() 只会在父进程和子进程修改内存时才进行内存拷贝。具体来说:

  • 父进程和子进程在 fork() 后共享相同的内存页,直到有一个进程试图修改内存。
  • 当某一进程尝试修改内存时,操作系统会为该进程分配一个新的内存页(即拷贝),从而确保父子进程不会互相影响。

这种机制可以显著提高性能,尤其是在创建大量进程时。它避免了不必要的内存复制,只有在进程修改数据时才会进行实际的拷贝。

为什么要有写时拷贝?

 - 减少创建子进程的时间

 - 减少内存浪费

2. 进程终止

在 Linux 系统中,进程的生命周期从创建到终止是一个完整的过程。进程终止时,操作系统会进行资源回收,释放进程占用的内存、文件描述符等资源。进程可以通过多种方式终止,常见的退出方法包括 main 函数的 return、exit函数等。

进程终止的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的数据和代码。

2.1 进程常见退出方法

2.1.1 main函数return

在 C 语言程序中,main 函数是程序的入口点。当程序执行到 main 函数的最后,main 函数的 return 语句会导致进程的终止。

main函数的返回值通常表明程序的执行情况。

在 main 函数中使用 return 时,实际上是调用了 exit(0)exit 的其他变体,因此 returnexit 具有相同的效果

- 通常,return 0; 表示程序成功结束。

- 非零返回值通常表示程序异常终止或错误退出,具体的值由程序员定义。

2.1.2 exit

exit 是标准库函数,专门用于终止程序并返回退出状态给操作系统。无论exit()被调用的位置在哪里,都会导致程序的终止,且会清理资源(如关闭文件、释放内存等)。

函数原型:

void exit(int status);
  • status: 退出状态码,通常用 0 表示成功,非零表示出错。
  • 在调用 exit 后,程序会立即终止,不会再执行任何后续代码。

注意事项:

  • exit() 不仅会终止进程,还会清理进程使用的资源,包括缓冲区数据、已打开的文件等。
  • 如果进程通过 exit() 退出,操作系统会收集子进程的退出状态信息(如果有的话)。这也与 waitpid() 函数相关,父进程可以使用它来获取子进程的退出状态。

echo $?:打印最近一个程序退出时的退出码。main函数的返回值我们称为进程退出码

2.1.3 _exit

_exit() 是一个系统调用,功能是立即终止当前进程。它会直接终止进程并清理资源,但不会执行 exit() 需要执行的标准库清理过程,如关闭流、执行注册的 atexit() 函数等。_exit() 主要用于在进程中断时需要立即退出的情况。

exit(库函数) vs _exit(系统调用)

 - exit退出进程的时候,会进行缓冲区的刷新

 - _exit退出进程的时候,不会进行缓冲区的刷新

exit()_exit() 的主要区别在于资源清理的行为,_exit() 更加快速、低级,适用于子进程或需要避免缓冲区刷新的场景。

2.2 退出码与strerror

退出码是操作系统用来表示进程终止状态的一个整数值。退出码通常由进程的最后一个返回值决定,表示进程的结束结果。退出码可以由程序员自定义,用于指示程序的执行情况。

  • 退出码值
    • 0:表示程序成功结束(即没有错误发生)。
    • 非零值:表示程序出现了某些错误或异常,非零的退出码通常用于区分不同的错误类型。

sterror() 一个标准库函数,用于根据错误码(errno)返回一个错误信息字符串。当程序遇到错误时,通常会设置 errno 变量,strerror()函数可以根据 errno 的值返回错误的描述信息,这对于调试和错误处理非常有帮助。 

通过下面的结果可以看到,退出码一共有134种

fopen打开失败时会返回错误码errno,表示打开失败的原因

此时失败的原因就是2:没有该文件

再来一个例子

errno 和 strerror()的关系

  • 当一个系统调用或库函数发生错误时,它会设置 errno 变量,errno 是一个全局变量,表示最后发生的错误类型。
  • strerror() 使用 errno 的值返回一个错误描述字符串,用于帮助程序员理解错误的具体原因。

常见的 errno 错误码

  • ENOMEM:内存不足
  • EINVAL:无效的参数
  • EAGAIN:资源暂时不可用
  • ENOENT:没有找到文件或目录
  • EACCES:权限不足
  • EBADF:坏的文件描述符

上图前两种情况退出码由return返回值决定可以自己设置

第三种情况退出码无意义

3. 进程等待

子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。 另外,进程⼀旦变成僵尸状态,那就刀枪不⼊,“杀⼈不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死⼀个已经死去的进程。 最后,关于父进程派给子进程的任务,我们需要知道子进程运行是否完成,结果对还是不对,或者是否正常退出。
父进程通过进程等待的父式,回收子进程资源,获取子进程退出信息。

3.1 wait 和 waitpid

在多进程程序中,父进程可能需要等待子进程执行完毕并回收其资源。Linux 提供了两个常用的系统调用:wait 和 waitpid,它们用于父进程等待子进程结束并获取其退出状态。通过这些调用,父进程能够管理子进程的生命周期,并处理子进程终止时的各种情况。

3.1.1 wait

函数原型: 

pid_t wait(int *status);
  • status: 指向一个整型变量的指针,用来存储子进程的退出状态。父进程可以通过该变量检查子进程的终止状态(是否正常退出、是否由于信号退出等)。
  • 返回值: 返回结束的子进程的 pid(进程ID),如果没有子进程或遇到错误,返回 -1

 使用方式wait() 一般会阻塞父进程,直到一个子进程退出。如果父进程有多个子进程,它会返回第一个结束的子进程的 pid

  • 前五秒正常运行:

    • 在前五秒内,父进程和子进程运行正常。子进程会输出 5 次自己的 PID 和父进程 PID,然后正常退出。
    • 父进程在这时并未调用 waitpid(),因此子进程在退出时没有立刻被父进程收养。此时,子进程还没有成为僵尸进程。

子进程成为僵尸进程: 

子进程执行 exit(0) 后会变成僵尸进程,原因是父进程没有及时调用 waitpid() 来回收它的资源。僵尸进程是已经退出但尚未被父进程回收的进程,它会保留在进程表中,直到父进程通过 waitpid() 获取它的退出状态。

为什么子进程变成僵尸进程:

在父进程调用 sleep(10) 后,它延迟了 10 秒才调用 waitpid(),这个时候子进程已经结束并变成僵尸进程。虽然子进程退出了,但父进程还未及时收尸,因此子进程保持在进程表中。

后续父进程回收子进程 

父进程在调用 waitpid() 后,成功回收了子进程的退出状态,从而清除了僵尸进程。waitpid() 返回值 rid 是子进程的 PID,表明子进程已经被父进程回收。

  • 前五秒:父子进程正常执行,子进程打印信息,父进程保持休眠。
  • 后五秒:子进程退出后变成僵尸进程,因为父进程没有及时调用 waitpid() 来回收子进程的退出状态。
  • 父进程等待回收:父进程通过 waitpid() 回收子进程,清除了僵尸进程。

3.1.2 waitpid

waitpid() 是一个更灵活的系统调用,它允许父进程等待指定的子进程,也可以通过额外的选项控制等待行为。与 wait() 不同,waitpid() 可以等待特定的子进程,或者在不阻塞父进程的情况下进行非阻塞等待。

函数原型:

pid_t waitpid(pid_t pid, int *status, int options);
  • pid: 可以指定要等待的子进程的 pid

    • pid > 0: 等待指定 pid 的子进程。
    • pid == 0: 等待同组的任何子进程。
    • pid == -1: 等待任何子进程(与 wait() 类似)。
    • pid < -1: 等待进程组中的某个子进程。
  • status: 用于存储子进程的退出状态,和 wait() 相同。

  • options: 控制行为的选项,常用的选项有:

    • WNOHANG:非阻塞模式,立即返回,若没有子进程终止则返回 0。
    • WUNTRACED:等待已停止的子进程。
    • WCONTINUED:等待已经继续运行的子进程。

返回值

  • 返回 pid(子进程的 pid)表示父进程已成功获得该子进程的终止信息。
  • 如果没有子进程或遇到错误,返回 -1

下面的waitpid作用与wait相同 

3.2 获取子进程status

在父进程等待子进程终止时,wait()waitpid() 会返回一个表示子进程状态的整数 status。通过宏函数,我们可以从 status 中提取子进程的退出信息:

  • WIFEXITED(status)
    判断子进程是否正常退出。如果子进程是由于调用 exit() 或正常返回而终止,WIFEXITED 返回 true,可以通过 WEXITSTATUS(status) 获取子进程的退出码。

  • WIFSIGNALED(status)
    判断子进程是否因为信号异常终止。如果子进程由于收到信号(例如 SIGKILLSIGSEGV)而退出,WIFSIGNALED 返回 true,可以通过 WTERMSIG(status) 获取终止信号的编号。

  • WIFSTOPPED(status)
    判断子进程是否被信号暂停。如果子进程被信号暂停(例如通过 SIGSTOP),WIFSTOPPED 返回 true,可以通过 WSTOPSIG(status) 获取导致子进程暂停的信号编号。

  • WIFCONTINUED(status)
    判断子进程是否继续运行。如果子进程在暂停后继续运行,WIFCONTINUED 返回 true

  • WEXITSTATUS(status)
    如果 WIFEXITED(status) 返回 true,则可以调用 WEXITSTATUS(status) 获取子进程的退出码。通常情况下,子进程会通过 exit() 函数传递一个退出码。

  • WTERMSIG(status)
    如果 WIFSIGNALED(status) 返回 true,则可以调用 WTERMSIG(status) 获取终止子进程的信号编号(例如,SIGSEGV 表示段错误)。

  • WSTOPSIG(status)
    如果 WIFSTOPPED(status) 返回 true,则可以调用 WSTOPSIG(status) 获取导致子进程暂停的信号编号。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main() {pid_t pid = fork();  // 创建子进程if (pid == 0) {// 子进程:模拟正常退出printf("Child process exiting normally\n");exit(42);  // 子进程正常退出并返回退出码 42} else if (pid > 0) {// 父进程int status;pid_t child_pid = waitpid(pid, &status, 0);  // 等待子进程结束if (child_pid > 0) {if (WIFEXITED(status)) {// 子进程正常退出int exit_code = WEXITSTATUS(status);printf("Child process exited with code %d\n", exit_code);} else if (WIFSIGNALED(status)) {// 子进程由于信号终止int signal_number = WTERMSIG(status);printf("Child process terminated by signal %d\n", signal_number);} else if (WIFSTOPPED(status)) {// 子进程被信号暂停int stop_signal = WSTOPSIG(status);printf("Child process stopped by signal %d\n", stop_signal);} else if (WIFCONTINUED(status)) {// 子进程继续执行printf("Child process continued\n");}}} else {perror("fork failed");exit(1);}return 0;
}
Child process exiting normally
Child process exited with code 42
  • 父进程调用 waitpid(pid, &status, 0) 等待特定的子进程。
  • 通过 WIFEXITED(status) 判断子进程是否正常退出,并使用 WEXITSTATUS(status) 获取退出码 42

8-15是退出状态,即退出码

退出码为1,因此8-15之间为00000001,0-7为0,所以为0000000100000000->256

那我们怎么让status和退出码一致呢?status>>8&0xFF

拿到退出信号

 

 waitppid:WEXITSTATUS(status)

非阻塞调用

4. 进程替换

在一个进程运行过程中,有时候需要执行另一个不同的程序。进程替换就是让当前进程放弃正在执行的程序,转而执行新的程序。这种替换是直接的,不会创建新的进程,而是复用当前进程的资源。一旦替换成功就去执行新的代码了,原始代码之后的部分就不存在了。

常见实现方式:在 C 语言中使用 exec 系列函数

常见的有 execlexecvexecleexecveexeclp 和 execvp。这些函数的功能基本相同,只是参数传递方式略有不同。exec*系列的函数只有失败会有返回值,没有成功返回值。

#include <unistd.h>int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

按参数传递方式分类

  • execlexeclpexecle:使用可变参数列表传递命令行参数,最后一个参数必须为 NULL,用于表示参数列表的结束。
  • execvexecvpexecve:使用字符指针数组 argv 传递命令行参数,数组的最后一个元素必须为 NULL

按查找路径分类

  • execlexecvexecleexecve:需要指定要执行程序的完整路径名 path
  • execlpexecvp:只需要指定程序名 file,系统会在环境变量 PATH 所指定的路径中查找该程序。

按是否自定义环境变量分类

  • execlexeclpexecvexecvp:使用调用进程的环境变量。
  • execleexecve:可以通过 envp 参数自定义环境变量。

4.1 execl

int execl(const char *path, const char *arg, ...);

功能:用新程序替换当前进程的映像,新程序的路径由 path 指定,命令行参数以可变参数列表的形式传递。

参数

path:要执行程序的完整路径名。

arg:可变参数列表,第一个参数通常是程序名,最后一个参数必须为 NULL

返回值:如果执行成功,不会返回;如果执行失败,返回 -1,并设置 errno

程序执行ls -l 命令 

4.2 execlp

int execlp(const char *file, const char *arg, ...);
  • 与 execl 类似,但不需要指定程序的完整路径,系统会在 PATH 环境变量指定的路径中查找该程序。
  • 参数
    • file:要执行程序的名称。
    • arg:可变参数列表,第一个参数通常是程序名,最后一个参数必须为 NULL
  • 返回值:如果执行成功,不会返回;如果执行失败,返回 -1,并设置 errno

第一个 ls 的作用是指定要执行的程序的名称。execlp 函数会在环境变量 PATH 所指定的路径中查找这个名称对应的可执行文件。 

第二个 ls 是传递给 第一个ls 程序的第一个命令行参数。后面跟着的 -l 则是 ls 命令的一个选项

4.3 execle

int execle(const char *path, const char *arg, ..., char * const envp[]);
  • 功能:用新程序替换当前进程的映像,新程序的路径由 path 指定,命令行参数以可变参数列表的形式传递,同时可以通过 envp 参数自定义环境变量。
  • 参数
    • path:要执行程序的完整路径名。
    • arg:可变参数列表,第一个参数通常是程序名,最后一个参数必须为 NULL
    • envp:自定义的环境变量数组,数组的最后一个元素必须为 NULL
  • 返回值:如果执行成功,不会返回;如果执行失败,返回 -1,并设置 errno

4.4 execv 

int execv(const char *path, char *const argv[]);
  • 用新程序替换当前进程的映像,新程序的路径由 path 指定,命令行参数以字符指针数组 argv 的形式传递。
  • 参数
    • path:要执行程序的完整路径名。
    • argv:命令行参数数组,数组的第一个元素通常是程序名,最后一个元素必须为 NULL
  • 返回值:如果执行成功,不会返回;如果执行失败,返回 -1,并设置 errno

4.5 execvp

int execvp(const char *file, char *const argv[]);
  • 功能:与 execv 类似,但不需要指定程序的完整路径,系统会在 PATH 环境变量指定的路径中查找该程序。
  • 参数
    • file:要执行程序的名称。
    • argv:命令行参数数组,数组的第一个元素通常是程序名,最后一个元素必须为 NULL
  • 返回值:如果执行成功,不会返回;如果执行失败,返回 -1,并设置 errno

4.6 execve

int execve(const char *path, char *const argv[], char *const envp[]);
  • 功能:用新程序替换当前进程的映像,新程序的路径由 path 指定,命令行参数以字符指针数组 argv 的形式传递,同时可以通过 envp 参数自定义环境变量。
  • 参数
    • path:要执行程序的完整路径名。
    • argv:命令行参数数组,数组的第一个元素通常是程序名,最后一个元素必须为 NULL
    • envp:自定义的环境变量数组,数组的最后一个元素必须为 NULL
  • 返回值:如果执行成功,不会返回;如果执行失败,返回 -1,并设置 errno

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com