1. fork函数
之前我们对fork函数以及有了初步的理解了,它在应该以及存在的进程中创建应该新的进程,子进程。

进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork返回,开始调度器调度
fork 函数的作用是在当前 进程 下,创建一个 子进程,子进程 创建后,会为其分配新的内存块和内核数据结构(PCB), 将 父进程中的数据结构内容拷贝给子进程,同时还会继承父进程中的环境变量表。

cpp
int main( void )
{
pid_t pid;
printf("Before: pid is %d\n", getpid());
if ( (pid=fork()) == -1 )perror("fork()"),exit(1);
printf("After:pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;
}
运行结果:

这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after消息有43677打印的。注意到进程43677没有打印before,为什么呢?如下图所示
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
fork返回值
子进程返回0
父进程返回子进程的pid
2.进程终止
进程的终止是在做什么?
1.释放曾经的代码和数据所占用的空间。
- 释放内核数据结构。
2.1 进程的常见退出方法
进程退出分为3种情况
1.代码运行完毕,结果正确
2.代码运行完毕,结果不正确
3.代码异常终止
进程的退出方法:
正常退出
1.从mian返回
2.调用exit系统调用
3._exit函数
异常退出
ctrl+c 发送信号,进程异常退出
获取进程的退出码可以通过echo $?来获取
echo $?
echo $?表示打印上一个进程的退出码。
2.2 退出码
退出码是进程终止时返回给操作系统的状态码。在类 Unix 操作系统中,退出码通常是一个整数,表示程序的执行结果。程序通过 exit() 函数或 return 语句返回退出码。
1.退出码为0,表示正常退出
2.非零表示程序发送了错误或者异常。
cpp
1 #include<stdio.h>
2 int main()
3 {
4 printf("hello linux\n");
5 return 0;//正常退出,退出码为0
6 }

代码正常运行结果,返回值为0。
cpp
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
int* p = NULL;
printf("I am a process,pid:%d,ppid:%d\n",getpid(),getppid());
*p = 100;// 对空指针进行解引用
return 0;
}

由于对于空指针进行了解引用(野指针其实相当于在页表中没有创建映射关系或者建立了但是被设置为了只读),进程收到了信号而异常退出,我们平常使用的ctrl+c也就是给进程发送了2号信号,让进程退出,上面退出码是139,实际是向进程发送了139-128=11号信号。
2.3 errno和strerror
errno 是一个全局变量,用于记录系统调用或库函数失败时的错误码。每当系统调用或标准库函数失败时,errno 会被设置为相应的错误码。例如,当你尝试打开一个不存在的文件时,fopen() 会返回 NULL,并将 errno 设置为 ENOENT(表示文件不存在)。

errno:保存系统调用失败的错误码(例如文件打开失败、内存分配失败等)。
strerror():是一个函数,它接受 errno 的值并返回一个指向描述该错误的字符串的指针。你可以通过 strerror(errno) 来打印出系统调用失败的原因。
cpp
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
FILE *file = fopen("nonexistent_file.txt", "r");
if (file == NULL) {
printf("Error: %s\n", strerror(errno)); // 打印错误信息
}
return 0;
}
2.4 return,exit和_exit
return和exit都表示函数返回,调用的数字都表示函数的退出码,有什么区别?
cpp
#include<stdio.h>
#include<stdlib.h>
void show()
{
printf("hello\n");
return;
exit(13);
printf("xxx");
}
int main()
{
show();
return 12;
}
return和exit在任意地方被调用都表示进程直接退出,设置退出码。
return只表示在当前函数返回,所以上面的代码退出码是12,没有show函数里面的return退出码就变成了13。
exit和_exit
exit() 是 C 标准库函数,用于终止程序并返回一个退出状态码。程序通过 exit() 来向操作系统报告它的退出状态。exit() 可以传递一个整数作为参数,这个整数就是退出码。
cpp
31 #include<stdio.h>
32 #include<stdlib.h>
33 #include<unistd.h>
34 int main()
35 {
36 printf("xxx");
37 printf("yyy");
38 // exit(12);
39 _exit(12);
40
41 }


上面的代码中,明显可以看到使用exit函数会把"xxx""yyy"都打印出来,_exit就不会。

因为exit回去把缓冲区的数据冲刷出来,打印出来,所以会打印出来,而_exit不会。
exit是应该库函数,而_exit则是系统调用。
3. 它们中间的关系
退出码与 exit():程序通过 exit() 或 return 来传递退出码,表示程序的终止状态。
退出码与 echo ?:Shell 使用 echo ? 显示上一个命令的退出状态码(退出码表示命令的成功与否)。例如,ls 命令成功执行时,退出码为 0;如果命令失败,退出码为非零值。
退出信号与 exit() 的退出码:如果进程因信号(如 SIGKILL、SIGSEGV 等)而终止,退出码是由信号编号表示的,等于信号编号+128。此时退出码(退出状态)将会被设置为信号的编号+128。
errno (错误码)和退出码的区别:errno 用于表示系统调用的错误(如文件操作失败、内存分配失败等)。它不与进程退出状态直接相关。exit() 的退出码是程序本身设定的,而 errno 是在系统调用或库函数失败时由操作系统设置的。
strerror() 和 errno:strerror() 用来根据 errno 错误码返回相应的错误信息描述。它与程序退出码无关,而是用于获取和显示系统调用失败的具体错误原因。