文章目录
- 进程创建
- 又识fork函数
- fork函数返回值
- 写时拷贝
- fork常规用法
- fork调用失败的原因
- 进程终止
- 进程退出的三种场景
- 进程常见退出方法
- main函数return 0
- 使用exit函数
- 使用_exit函数
- 异常退出
进程创建
又识fork函数
目前我们知道在Linux下创建进程有两种方式:
- 命令行启动命令(自己写的程序、指令…)
- fork()函数
我们知道在linux中
fork函数
是非常重要的函数,它从一个已经存在进程中创建一个新进程。新进程为子进程,而原来的进程为父进程。
进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork返回,开始调度器调度
创建子进程的进程控制块task_struct;
创建子进程的进程地址空间mm_struct;
重新申请空间,进行拷贝,修改页表;
#include<stdio.h>
#include<unistd.h>int main()
{printf("Before:PID is %d\n", getpid());pid_t id = fork();if(id == -1){printf("fork error!!\n");}printf("After : PID is %d, fork return is %d\n", getgid(), id); sleep(1);return 0;
}
调用fork函数之前,Before输出一次,由父进程输出;
调用fork函数之后,After输出两次,由父、子进程分别输出一次。
所以fork之前父进程单独执行,fork之后,父子两个进程分别执行。且fork之后,谁先执行不确定,由调度器自行决定。
fork函数返回值
#include <unistd.h>
pid_t fork(void);
返回值:子进程返回0,父进程返回子进程pid,出错返回-1
写时拷贝
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。
- 为什么数据要进行写时拷贝?
使父、子进程之间是相互独立的;
节省内存和系统资源,提高 fork 的效率,减少 fork 失败的概率。 - 为什么不一开始就复制出来一份给子进程?
所有的数据,父、子进程并不是都要进行写入的,有的仅仅需要读取,而此时拷贝是没有意义的,而且还会浪费内存资源。
fork常规用法
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
fork调用失败的原因
- 系统中有太多的进程
- 实际用户的进程数超过了限制
进程终止
进程退出的三种场景
进程退出只有三种情况:
- 代码运行完毕,结果正确。
- 代码运行完毕,结果不正确。
- 代码异常终止。
进程常见退出方法
main函数return 0
我们之前在写C\C++代码的时候,总会在main函数的结尾写上一个return 0
但这是为什么呢??
首先我们需要明确知道,不一定非要return 0
,也可以return其他值。
main函数执行到return 语句时,表示该进程执行完毕;
其余函数执行到return 语句时,表示该函数执行完毕;
- 程序正常执行完毕并且结果正确时返回
0;
- 程序正常执行完毕但结果不正确时返回
!0;
那么程序既然正常执行完毕,但结果不正确时,会返回什么!=0
,那就是1 2 3 4 5,那么他们又有什么含义,我们看下面的一段代码
看来0代表的就是success, 并且在134号以后就是unknown error
查看最近进程的退出码:
指令:echo $?
第一次返回100是我们所写的进程返回值;
第二次返回0是我们第一次使用echo $?成功的返回值;
使用exit函数
该函数使用时需要包头文件#include<stdlib.h>
有一个int
类型的参数,其含义是错误码,和main函数的return值是一个意思
但exit函数和return是有一定的区别的
- exit用于结束正在运行的整个程序,它将参数返回给操作系统,把控制权交给操作系统;而return 是退出当前函数,返回函数值,把控制权交给调用函数;
- exit是系统调用函数,表示一个进程的结束;return 是语言级别的,表示调用堆栈的返回;
exit函数在程序任一地方使用都可以直接退出程序,并且返回错误码
任意地点调用exit函数
,表示进程退出,不进行后续操作
使用_exit函数
它和exit一样都是终止进程,并且参数都一样含义也一样但差别在哪??
我们观察下面的运行结果
OK了,我们好像发现了其中的奥秘
第一个打印出来了,而第二个没有打印。
对于printf函数,我们要么碰到\n刷新条件、要么手动fflush刷新、要么终止进程(main函数中的return 也可以做到)即将结束自己刷新。
对于printf函数来说,如果没有换行\n的话,那么其先会保存在缓冲区中,但我们使用exit函数的话退出进程时却被打印出来了,也就是说exit函数会对缓冲区进行刷新,而_exit函数并没有使其打印出来也就是说没有这个功能。
既然exit函数是C的库函数,而_exit函数是系统调用函数,那么我们不妨大胆猜想,缓冲区是由C来维护的
总结
- exit是库函数、_exit是系统调用函数
- exit终止进程的时候会自动刷新缓冲区、_exit终止进程时,不回刷新缓冲区
异常退出
数组越界
野指针访问
此时运行程序后,程序会退出。这时候再去使用指令: echo $?
就没有意义了!
就好比考试作弊后,无论你100,20分结果都是一样的。没有意义!
全局变量errno
用于显示错误信息的字符串。当程序运行时,errno宏被设置为0,一旦程序发生了系统级的错误,errno宏就会被设置为其它值。
perror函数
函数用于打印最近的库函数执行出错的消息
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>int main()
{FILE *file = fopen("non_existent_file.txt", "r");if (file == NULL) {// errno 现在已被 fopen 设置,描述错误的原因perror("Error opening file"); // 打印 "Error opening file: <系统的错误消息>"return EXIT_FAILURE;}// 正常处理文件fclose(file);return EXIT_SUCCESS;
}
输出
Error opening file: No such file or directory
尝试打开一个不存在的文件,由于文件不存在,fopen() 将返回 NULL 并设置全局变量 errno。然后我们调用 perror() 来打印错误消息,该消息将包括由 errno 表示的特定错误原因。