专做it招聘的网站,重庆设计集团有限公司,虚拟资源交易平台Wordpress源码,c 网站开发案例详解下载文章目录 前言Ⅰ. 重温fork函数一、fork()的概念二、如何理解fork()有两个返回值 Ⅱ.fork的常规用法Ⅲ. fork调用失败的原因Ⅳ. 写时拷贝为什么存在写时拷贝❓❓❓ 前言
现阶段我们知道进程创建有如下两种方式#xff0c;其实包括在以后的学习中这两种方式也是最常见的#… 文章目录 前言Ⅰ. 重温fork函数一、fork()的概念二、如何理解fork()有两个返回值 Ⅱ.fork的常规用法Ⅲ. fork调用失败的原因Ⅳ. 写时拷贝为什么存在写时拷贝❓❓❓ 前言
现阶段我们知道进程创建有如下两种方式其实包括在以后的学习中这两种方式也是最常见的
命令行启动命令 (程序、指令等)通过程序自身 fork() 后产生的子进程
Ⅰ. 重温fork函数
一、fork()的概念
在 linux 中 fork函数 是非常重要的 系统函数它从已存在进程中创建一个新进程。新进程为子进程而原进程为父进程。之前我们通过查 man 手册知道了运用 fork() 函数并且调用它需要包含 头文件 unistd.h。
如下是函数的声明和返回值
#include unistd.h
pid_t fork(void); // 返回值子进程中返回0父进程返回子进程id出错返回-1 但是之前我们一直有个问题没有解决那么就是**为什么一个函数一个返回两个值**有了之前的进程概念的知识这里我们就可以来解释一下这个现象了
二、如何理解fork()有两个返回值
父进程 fork 时子进程是以父进程为模板简单地说就是子进程的大部分属性和属性值是拷贝父进程的而小部分是指子进程的调度时间要重置、子进程的 pid、ppid 以及兄弟的要重置。其中上面的 PCB、地址空间、页表都在内核里由操作系统维护的这也就意味着我们只需要调用操作系统提供的接口 fork而具体工作细节由操作系统完成。
那其中 为什么 fork() 给父进程返回 子进程的pid给子进程返回 0 呢因为我们知道父进程和子进程的关系就是一对多的关系每个子进程只能有一个孩子而每个父进程可以有多个子进程这个 返回值的意义就是为了标识它们的关系
我们还要知道 fork() 函数是在用户空间中被我们调用的但是其实现是在内核空间中由操作系统实现的
fork() 的实现思路大致如下
1、给子进程分配新的内存块和内核数据结构PCB、进程地址空间、页表等并构建对应的映射关系
2、将父进程的部分数据结构内容拷贝至父进程
3、把子进程添加到系统进程列表中
4、fork 返回调度器开始调度。 从上图我们知道 既然在 fork 函数 return 之前就已经有了父子两个进程父子两个执行流分别执行所以会给父进程返回子进程的PID给子进程返回0失败则返回-1。
让我们看看下面的代码程序
#include stdio.h
#include unistd.h
#include stdlib.h
int main()
{ 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;
} 调用结果
[lirenVM-8-2-centos process]$ ./mypro
Before: pid is 20052
After:pid is 20052, fork return 20053
After:pid is 20053, fork return 0 从上述结果可以看到原来只有父进程一个也就是 20052但是 fork 之后又产生了子进程 20053
注意fork 之后谁先执行完全由调度器决定。 调度器是CPU中央处理器的管理员主要负责完成做两件事情 选择某些就绪进程来执行 打断某些执行的进程让它们变为就绪状态。 利用 fork 返回值的这个特性我们可以用变量 id 接收返回值根据 fork 返回值不同让父子进程执行不同的代码这个我们之前也讲过啦简单过一下就好
#include stdio.h
#include unistd.h
int grobal_val 100;
int main()
{pid_t id fork();if(id 0){printf(子进程:pid %d,ppid %d | grobal_val %d, grobal_val %p\n,getpid(), getppid(), grobal_val, grobal_val);}else if(id 0){printf(父进程:pid %d,ppid %d | grobal_val %d, grobal_val %p\n, getpid(), getppid(), grobal_val, grobal_val);sleep(1);}else {printf(fork error\n);return 1;}return 0;
}调用结果
[lirenVM-8-2-centos process]$ ./mypro
父进程:pid 18199,ppid 16903 | grobal_val 100, grobal_val 0x60105c
子进程:pid 18200,ppid 18199 | grobal_val 100, grobal_val 0x60105cⅡ.fork的常规用法
一个父进程希望复制自己使得 父子进程同时执行不同的代码段。例如父进程等待客户端请求生成子进程来处理请求。让一个进程执行一个不同的程序。例如子进程从 fork 返回后调用 exec 函数。这个会在进程替换中学习
Ⅲ. fork调用失败的原因
fork 是操作系统级别的接口所以失败的原因一定是系统级别的原因。
系统中已经存在太多的进程了。实际用户创建的进程超过了限制。
这段代码是测试你的用户能跑好多个进程但是不建议跑。因为跑了之后就会影响 bash会导致系统出错代码如下
#include stdio.h
#include unistd.h
int main()
{int cnt 0;while(1){int ret fork();if(ret 0){printf(fork error!, cnt: %d\n, cnt);break;}else if(ret 0){// 子进程不断循环while(1) {printf(子进程:pid %d,ppid %d\n,getpid(), getppid());sleep(1);}}// 父进程不断循环不断产生子进程cnt;}return 0;
}调用结果
子进程:pid 1706,ppid 27353
子进程:pid 32351,ppid 27353
子进程:pid 32348,ppid 27353
子进程:pid 32347,ppid 27353
子进程:pid 32331,ppid 27353
............
^C
[lirenVM-8-2-centos process]$ 解决方法 用命令 kill -9 -1 将进程全部杀死 重新增加一个用户使用
Ⅳ. 写时拷贝
当父子代码只读时父子的代码和数据是共享的。但是任意一方试图写入时便以写时拷贝的方式各自一份副本。
写时拷贝是一种机制或者策略好比打仗时的敌退我打敌进我撤它根据实时情况来完成既定规则。同理写时拷贝是根据父和子谁先写入的实时情况来完成拷贝的它是一种延时操作的策略。
具体步骤其实我们在讲进程地址空间的时候已经讲过了细节见下图: 这里要强调的是这里的写时拷贝是针对数据的写时拷贝这里留一个疑问 ~~ 代码会发生类似的写时拷贝的问题吗答案是会的后面我们讲进程程序替换时候会讲到
为什么存在写时拷贝❓❓❓
写时拷贝是为了保证父子进程的独立性。节省内存和系统资源提高 fork 的效率减少 fork 失败的概率。
父子进程创建时所有数据直接各自拷贝一份不行吗 很明显不使用写时拷贝也可以保证父子进程的独立性为啥还要费劲使用写时拷贝。其根本原因是
所有的数据父进程和子进程并不是都必须写入数据有可能它们仅仅需要读取而此时的各自拷贝是没有意义的而且会浪费内存和系统资源。fork 时创建数据结构如果还要将数据拷贝一份那么 fork 的效率一定会降低。fork 本质就是向系统申请更多的内存资源资源申请多了fork 有可能就会失败。