Linux(九)fork复制进程与写时拷贝技术

server/2025/3/31 6:23:04/

一、printf隐藏的缓冲区

首先,大家一起思考一个问题:为什么会有缓冲区的存在呢?

        因为屏幕是一个硬件设备,是由操作系统来管理的,因此printf打印的时候需要调用操作系统的接口才能完成,这个时候我们需要从用户态切换到内核态,这个开销是比较大的。所以先刷新到缓冲区,当缓冲区满时,打印到屏幕上,这样在不需要时回到缓冲区即可,减少开销;程序结束就再次刷新缓冲区。

        那我们为什么感觉不到缓冲区的存在呢?

        大家可以在虚拟机中编写上面的main.c程序,运行后就会发现结果会先睡眠3秒,然后再打印hello,这就是因为printf先进入了缓冲区。

那么,有伙伴可能就会想缓冲区刷新到界面(屏幕)上的条件是什么呢?

(1)缓冲区放满
(2)缓冲区未满,强制刷新缓冲区到屏幕;
(3)程序结束时,自动刷新缓冲区:exit方法;

那我们就在介绍一下强制刷新缓冲区的两种方法:

(1)遇到\n自动刷新

 在这里添加了‘\n’,运行结果就是先hello,后睡眠

(2)手动刷新

fflush(stdout);

std:标准库,out输出

运行结果也是先hello,后睡眠 

小编在额外说一下exit(0)

exit是先刷新缓冲区,然后再调用_exit(真正的退出)。

_exit直接退出,不会刷新缓冲区。

大家可以试着运行一下下面这个代码,就会发现,不输出hello

#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){printf("hello");sleep (3);_exit (0);}

 

大家可以看一下,我连续运行了三次./main都是没有打印出东西的,只是睡眠了3秒,然后结束运行。

二、fork复制进程

1、shell

        在计算机科学中,Shell俗称壳(用来区别于核),是指“为使用者提供操作界面”的软件(command interpreter,命令解析器)。它类似于DOS下的COMMAND.COM和后来的cmd.exe。它接收用户命令,然后调用相应的应用程序。

        我们就是通过命令解释器(称为shell)(bash是命令解释器中的一种)和内核和系统进行交互的
(Windows通过图形界面进行交互的);例如我们把Is交给bash,bash帮我们运行Is,然后把结果给用
户;

大家可以ps -f一下就可以发现: 一个终端一个bash,因为PID不同

2、fork如何复制进程

        fork是把已有的进程复制一份,当然把PCB也复制了一份,然后申请一个PID,子进程的PID=父进程的PID+1;

        父进程的返回值是子进程的PID,子进程的PID是父进程的PID+1;子进程的返回值是0;

 

 如果父子进程想要做不同的事情,那么我们通过返回值来判断。

我们现在想要使用fork(),但是我们既不知道参数类型也不知道返回值,这个时候就要用到

man fork     这个命令调用fork的帮助手册

我们通过执行下面的程序可以来感受一下父子进程: 

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>int main(){char *s=NULL;int n=0;//控制父子进程执行的次数;pid_t id=fork();assert(id !=- 1);if(id == 0){s="child";n=3;}//子进程else{s="parent";n=7;}//父进程//父子进程int i=0;for(;i<n;i++){printf("s=%s\n",s);sleep(1);//是因为两个进程同时打印太快了,一下就出来感受不到,sleep后就感觉到同时}exit(0);
}

        通过上面的代码和运行结果,我们知道了父子进程是两个独立的进程,各自执行各自的代码;如果父子进程要做不一样的事情,就通过if else返回值来操作。

3、fork的时机

        有的朋友可能会想,我一直调用fork,不就一直在复制进程,没有终止了吗?这就涉及到了fork的时机。

        fork产生的这个子进程不是从头开始执行的,而是从fork之后开始执行的,就是说fork下面的代码
子进程才开始执行,具体的是说从返回值这里子进程开始执行,子进程不会再fork了,所以不会出现
子进程再去fork产生一个子进程的问题.也就是说:从返回值这里开始,父进程返回子进程的PID,子进程返回0;

4、getppid与getpid

getppid:得到一个进程的父进程的PID;
getpid:得到当前进程的PID;

同样的,我们使用命令:man getpid;   man getppid  来查看如何使用getpid,getppid

 然后将我们刚刚的代码做一下修改,使我们能看到pid和ppid

三、写时拷贝技术

当我们不采用写时拷贝技术,使用fork:

如上图,父进程的所有,子进程都要复制过来;

就造成了两个问题:复制开销较大,占用内存空间

所以我们对fork复制进程的过程做了一个优化——写时拷贝技术(修改,写入的时候才拷贝)

        综上,就是fork的时候,子进程直接把父进程的页表复制过来,子进程发生写入(修改)的时候才分配内存复制,然后进行相应的页表修改。写时拷贝是一种可以推迟甚至免除拷贝数据的技术

四、进程的逻辑地址与物理地址

首先,我们一起思考一个问题:我们在进程中看到的地址是进程的物理地址还是逻辑地址?

通过下面的代码打印看看

printf("s=%s,pid=%d,ppid=%d,n的地址为:%p\n",s,getpid(),getppid(),&n);//打印n的地址

 大家会发现,打印出的地址是相同的

父子进程中n的值都不一样,那么我们为什么看到n的地址是相同的呢?

我们在进程中看到的地址就是进程的逻辑地址(进程的4G空间,从0开始,一直往上增长);

32位系统上,都有一个0-4G的地址空间:
在Linux系统上,最上面这1G由内核使用,下面3G是用户在使用;
为什么是4G呢?在32位系统上,能够寻址的范围就是2^32=4294967296字
节/1000=4294976K/1000=4294M/1000=4.29G约等于4G;

而我们把所有的地址都编号,1K = 2^10,4K = 2^12;物理页面能有2^20个页面

大家通过对上图进行梳理,就可以发现父子进程逻辑地址一样,但是物理地址是不一样的;

        以前我们的程序都是只有一个进程,我们逻辑地址相同,那么我们的逻辑地址映射过去的物理地址肯定也是相同的一块空间,只有一个进程,就不用刻意去理解逻辑地址和物理地址的差异;对于同一进程,逻辑地址相同,物理地址肯定相同。
        现在,我们的程序都是多进程的,逻辑地址相同,对应的物理地址就不一定相同了;也就是说A进程和B进程的逻辑地址相同,就不能说明物理地址一定相同,我们还需要看各自的页表,看看页表是否相同。
        不同进程的逻辑地址是没有比较的意义的;

那为什么在程序中不直接使用物理地址呢?

        因为我们无法预知哪些物理地址是空闲的,同时空闲的也是动态变化的,程序在不断的申请释放空间中。

【小编有话说】

        从这篇文章开始,我们就进入Linux更深一点的学习了,后面的内容会越来越难,小伙伴们一定要反复思考学习

        还是老话,看的开心的话,不要忘记点赞关注收藏哦,有了大家的鼓励小编会更有动力呢!!!


http://www.ppmy.cn/server/179719.html

相关文章

【NLP 48、大语言模型的神秘力量 —— ICL:in context learning】

目录 一、ICL的优势 1.传统做法 2.ICL做法 二、ICL的发展 三、ICL成因的两种看法 1.meta learning 2.Bayesian Inference 四、ICL要点 ① 语言模型的规模 ② 提示词prompt中提供的examples数量和顺序 ③ 提示词prompt的形式&#xff08;format&#xff09; 五、fine-tune VS I…

网络安全之开放式系统互联参考模型

对于HCIA HCIP的相关了解 1.1什么是HCIA&#xff1f; &#xff08;Huawei Certified ICT Associate&#xff09;是华为认证体系中的初级认证&#xff0c;全称为“华为认证ICT工程师”。该认证旨在帮助初学者和初级IT专业人员建立并验证在信息通信技术&#xff08;ICT&#xf…

【一起学Rust | Tauri2.0框架】深入浅出 Tauri 2.0 应用调试:从新手到专家的蜕变

前言 Tauri 是一款备受瞩目的跨平台桌面应用开发框架&#xff0c;它允许开发者使用 Web 技术栈&#xff08;HTML、CSS、JavaScript&#xff09;构建高性能、安全的原生应用。Tauri 2.0 的发布带来了诸多令人兴奋的新特性和改进&#xff0c;进一步提升了开发体验和应用性能。然…

23种设计模式-创建型模式-单例

文章目录 简介问题1. 确保一个类只有一个实例2. 为该实例提供全局访问点 解决方案示例重构前&#xff1a;重构后&#xff1a; 拓展volatile 在单例模式中的双重作用 总结 简介 单例是一种创建型设计模式&#xff0c;它可以确保一个类只有一个实例&#xff0c;同时为该实例提供…

webscoket爬虫之某旺(1)分析篇

文章仅供学习与交流!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!! 什么是websocket爬虫 WebSocket爬虫是一种利用WebSocket协议进行数据抓取的工具或程序。WebSocket是一种在单个TCP连接上进行全双工通信的协议,常用于实时应用程序,如聊天应用、在…

Qt在模块依靠情况下资源文件名称和资源名称的使用限制

概述 在Qt中使用添加资源文件的时候&#xff0c;对于资源文件名称的定义&#xff0c;往往是较为随意的。 但是当涉及到Qt库依赖的时候&#xff0c;则可能需要遵守一定的规则&#xff0c;否则可能出现文件找不到或者错误加载的问题。 环境 环境名称Qt 版本系统版本LinuxQt 5.…

python 游戏开发cocos2d库安装与使用

Cocos2d-x 是一个广泛使用的开源游戏开发框架&#xff0c;支持多种编程语言&#xff0c;包括 Python。对于 Python 开发者来说&#xff0c;通常使用的是 Cocos2d-py 或者更现代的 Cocos2d-x 的 Python 绑定版本。这里我将指导你如何安装和开始使用 Cocos2d-py。 安装步骤 安装…

分布式爬虫框架Scrapy-Redis实战指南

引言 在当今数字化的时代背景下&#xff0c;互联网技术的蓬勃兴起极大地改变了旅游酒店业的运营模式与市场格局。作为旅游产业链中的关键一环&#xff0c;酒店业的兴衰与互联网技术的应用程度紧密相连。分布式爬虫技术&#xff0c;尤其是基于 Scrapy 框架的 Scrapy-Redis 扩展…