思维导图
学习内容
在这一篇博客的主要内容是学习fork函数,了解fork函数的功能、返回值等。我们需要学会使用fork函数创建子进程。
学习目标
- 进程的概念
- fork函数的初始
- fork函数的返回值
- 写时拷贝
- fork函数的常规用法
- fork函数调用失败的原因
零、进程的概念
进程:内核的相关管理数据结构(task_struct等)+ 代码 + 数据,代码是父子共享的,数据是具有写时拷贝。
如何理解进程的独立性??因为父子进程是用两个互不相关的内核数据结构进行创建的,所以当一个进程崩溃时,是不会影响其他进程的。
一、fork函数初始
在Linux操作系统中,fork函数是非常重要的!!!
函数的原型:
#include <unisted.h> pid_t fork(void);
函数的作用:从已存在的进程创建一个新进程,新进程是子进程,原进程是父进程。
函数的返回值:子进程中返回0,父进程返回子进程id,出错返回-1
进程多用fork函数,当程序进入fork函数后,控制转移到内核中的fork代码,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程的部分数据结构内容拷贝到子进程
- 添加子进程到系统进程列表中
- fork函数返回,开始进行调度器调度
所以,fork 之前父进程独立执行, fork 之后,父子两个执行流分别执行。注意, fork 之后,谁先执行完全由调度器决定。
#include <stdio.h> #include <unistd.h> int main() { printf("Before: pid is %d\n", getpid()); pid_t id = fork(); if(id < 0) perror("fork:"), exit(1); printf("After: pid is %d, return : %d\n", getpid(), getppid()); sleep(1); return 0; }
二、fork函数的返回值
- 子进程返回的是0
- 父进程返回的是子进程的pid
为什么给父进程返回子进程的pid,而子进程返回的是0??
因为父进程要方便对子进程进行标识,进而进行管理;而子进程是为了进行判断是否创建成功。
三、写时拷贝
3.1 写时拷贝的概念
写时拷贝的概念:就是等到修改数据时,才真正分配内存空间,这是对程序性能的优化,可以延迟甚至避免内存拷贝,当然目的就是避免不必要的拷贝。
3.2 写时拷贝的原理
写时拷贝实际上是运用了一个“引用计数”的概念来实现的,在开辟的空间中多维护了四个字节来存储引用计数。
有两种方式来存储引用计数:
- 多开辟四个字节(pCount)的空间,用来记录有多少个指针指向这片空间。
- 在开辟空间的头部预留四个字节的空间来记录有多少个指针指向这片空间。
当我们多开辟一个空间时,让引用计数 + 1,如果有释放空间,那么就让引用计数 - 1,但是此时不是真正的释放,是假释放,等到引用计数为0时,才是真正的释放空间。如果有修改或写的操作,那么也让原空间的引用计数-1,并且真正开辟新的空间。
四、fork函数的常规用法
- 一个父进程希望复制自己,使父子进程执行不同的代码段。例如,父进程等待客户端的请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如子进程从fork函数返回后,调用exec函数。
五、fork函数调用失败的原因
- 系统中有太多的进程;
- 实际用户的进程数超过了限制。