Linux内核笔记--内存管理之用户态进程内存分配
内核版本:linux-2.6.11
Linux在加载一个可执行程序的时候做了种种复杂的工作,内存分配是其中非常重要的一环,作为一个linux程序员必然会想要知道这个过程到底是怎么样的,内核源码会告诉你这一切。
线性区
一个可执行程序,是经过编译器处理后的遵守一定规则的数据、符号表和指令序列的组合,当linux加载一个可执行程序的时候,会为其创建一个新的进程,其对应的进程描述符task_struct中会保存许多资源的描述符,其中的mm_struct就是这个进程的内存描述符,用来管理该进程拥有的所有内存。
一个进程拥有的内存是动态变化的,比如栈的扩充、堆的扩充、新的文件映射等等,出于这个原因,需要一个更细粒度的单位来实现内存的动态增加减少,这个单位叫线性地址区间,简称线性区,用vm_area_struct描述。
线性地址空间
线性地址空间是基于单个进程的,暂时抛开写时复制机制不谈,不同进程之间的线性地址空间是彼此隔离的,这是由linux的多级分页机制实现。
一个进程拥有的线性地址空间的具体表示就是这个进程的内存描述符中存储的线性区的集合。
死程序,活进程
现在,我们知道了,进程是通过增加和减少线性区来管理自己拥有的内存,并通过逻辑地址加上某一个线性区的基地址来进行寻址操作,那么ok,这两点已经能够保证一个四肢头脑健全的进程正常运行,然而,一个可执行程序是存在硬盘上的,是一个死的东西,linux加载器需要把它变成活的,需要给她四肢给她头脑,即把她的代码、数据、栈、依赖库全部放到内存中。
这个过程,从do_fork开始。
do_fork和写时复制
Linux用do_fork来创建一个新的用户态进程,写时复制机制让新的子进程在不进行写操作的前提下会拥有父进程的所有页框,相当于父子进程拥有相同的线性区,当子进程对线性区写操作或者执行exec的时候,系统会将子进程的mm_struct重新初始化,
简单说下写时复制机制的实现:主要函数调用流程do_fork-->copy_process-->copy_mm-->dup_mmap-->copy_page_range,copy_page_range将父进程的多级页表结构整个复制一遍,此时,父子进程拥有彼此分离的多级页表结构,但在最后一级页表中存放的相同的页描述符,即子进程在进行写操作之前依然跟父进程共享相同的页,当子进程对某个共享页进行写操作时,系统会将执行流定向到do_wp_page,这个函数将复制一个新的页来替换要写的页。因此一个新的进程在初始的时候跟父进程共享相同的地址空间,但经过一段时间后,父子进程的地址空间将变得真正隔离开来。
分配线性区
然而运行一个新的程序会干掉所有旧的内存空间,并为新进程重新分配新的线性地址空间,从sys_execve()即exec的系统调用例程开始,调用流程依次是sys_execve-->do_execve-->mm_alloc-->mm_init-->mm_alloc_pgd-->pgd_alloc。最后这个pgd_alloc为这个进程分配了一个新的页全局目录(第一级页表)。
此时,该进程的线性地址空间依然为空,因为还未曾为其分配任何线性区。
sys_execve()会在最后会调用这个可执行程序对应格式的load_binary函数,这个函数完成了这种格式的可执行程序的加载,其中最主要的过程就是多次调用do_mmap为该进程分配一系列的线性区并存放不同的内容,分配顺序是,栈段->代码段->数据段->bss->依赖库,堆是在运行过程中动态分配的,由内核中brk和mmap函数实现,C库将其封装成我们熟知的malloc函数。
线性区的分配简单说就是扫描用户态线性地址空间(32位系统下通常是从0x40000000开始的低3G的空间),查找一个足够大的线性地址范围。
经过以上的过程,新进程拥有了自己的线性地址空间,但是别忘了,系统从未给这个进程分配任何可用的物理页,
仅仅只初始化了一个页全局目录,那么,当进程寻址的时候,MMU如何正确进行地址转换呢。
分配页框(填充页表)
Linux顺理成章的将新进程物理页的分配放在了缺页异常处理程序中,进程运行前期会频繁通过缺页异常来请求分页,缺页异常处理程序最终会调用伙伴系统的一个入口alloc_pages来分配新的页框并为缺页的线性地址填充页表,一段时间后,该进程的运行环境就会被完全载入内存。
至此,死程序变活进程。
插一段:sys_execve()第一步是调用getname()函数,获得程序名网上和一些书上说这个函数是用来得到一个新的页框并从用户空间拷贝程序名到这个页框中,然而,2.6的源码最终指向的一个函数是kmem_cache_alloc(cachep, flags),这个函数我在Linux内核笔记——内存管理之slab分配器里提到过,这是slab分配器的调用入口,所以从这里可以知道,getname其实是通过指定一个叫names_cachep的高速缓存描述符来分配一个这个类型的内存对象,这个names_cachep则是一个kmem_cache_t类型的指针,是一种高速缓存类型,所以这里说获得一个新的页框是欠妥的,实际上getname是获得了一个names_cachep这种高速缓存里注册的构造函数对应的一个指定的可用内存对象,然后再存入程序名到这个内存对象中。虽然这个对象可能就是一个普通页框,这依赖于这个注册的构造函数,
详细解释见Linux内核笔记——内存管理之slab分配器。
PS: 个人理解,错误难免,望能指出,万分感谢
Linux内核笔记--内存管理之用户态进程内存分配的更多相关文章
- Linux内存管理 —— 内核态和用户态的内存分配方式
1. 使用buddy系统管理ZONE我的这两篇文章buddy系统和slab分配器已经分析过buddy和slab的原理和源码,因此一些细节不再赘述.所有zone都是通过buddy系统管理的,buddy ...
- 【转载】linux内核笔记之高端内存映射
原文:linux内核笔记之高端内存映射 在32位的系统上,内核使用第3GB~第4GB的线性地址空间,共1GB大小.内核将其中的前896MB与物理内存的0~896MB进行直接映射,即线性映射,将剩余的1 ...
- Linux内核笔记--网络子系统初探
内核版本:linux-2.6.11 本文对Linux网络子系统的收发包的流程进行一个大致梳理,以流水账的形式记录从应用层write一个socket开始到这些数据被应用层read出来的这个过程中linu ...
- 【转载】linux内核笔记之进程地址空间
原文:linux内核笔记之进程地址空间 进程的地址空间由允许进程使用的全部线性地址组成,在32位系统中为0~3GB,每个进程看到的线性地址集合是不同的. 内核通过线性区的资源(数据结构)来表示线性地址 ...
- linux内核笔记-内核同步
linux内核就相当于不断对请求进行响应的服务器,这些请求可能来自CPU,可能来自发出中断的外部设备.我们将内核看作两种请求的侍者. (1)老板提出请求,侍者如果空闲,为老板服务.(系统调用或异常) ...
- Linux内核中的并发与竞态概述
1.前言 众所周知,Linux系统是一个多任务的操作系统,当多个任务同时访问同一片内存区域的时候,这些任务可能会相互覆盖内存中数据,从而造成内存中的数据混乱,问题严重的话,还可能会导致系统崩溃. 2. ...
- LINUX内核分析第六周学习总结——进程的描述和进程的创建
LINUX内核分析第六周学习总结——进程的描述和进程的创建 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...
- 使用gdb跟踪Linux内核启动过程(从start_kernel到init进程启动)
本次实验过程如下: 1. 运行MenuOS系统 在实验楼的虚拟机环境里,打击打开shell,使用下面的命令 cd LinuxKernel/ qemu -kernel linux-/arch/x86/b ...
- LINUX内核分析第八周总结:进程的切换和系统的一般执行过程
一.进程调度与进程切换 1.不同的进程有不同的调度需求 第一种分类: I/O密集型(I/O-bound) 频繁的进行I/O 通常会花费很多时间等待I/O操作的完成 CPU密集型(CPU-bound) ...
随机推荐
- .net MVC中异常日志
在日常工作中,我们有些项目可能进入了维护期,但是项目可能存在一些潜伏较深的bug导致我们在测试阶段并未发现,那么错误日志记录为我们的项目维护起着重要的作用.记录系统日志的方法如下 1.在系统根目录建立 ...
- JavaScript学习笔记(1))——————call,apply方法
学习前端也有一段时间了,但是效果甚微.利用时间不够充分,虽然是利用工作之余来学习.但是这不能成为我的借口. 今天学习了(其实看了很多遍)call apply方法. function abc(a,b){ ...
- 项目vue2.0仿外卖APP(四)
组件拆分 先把项目搭建时生成的代码给清了吧 现在static目录下引入reset.css 接着在index.html引入,并且设置<meta> 有时候呢,为了让代码符合我们平时的编码习惯, ...
- Cvim的安装与使用
一.安装cvim插件 第一步:下载cvim的安装包 在linux系统下的浏览器firefox.chrome浏览器中打开下面链接 www.vim.org/scripts/download_script. ...
- Java 动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- R语言常用函数
统计: mean:平均数sd:Standard Deviation 标准差var:方差median:中位数cov:协方差cor:相关系数 #环境ls/objectsrmhelp() library() ...
- nova instance出错:"message": "Proxy error: 502 Read from server failed
执行 $ nova resize instance1 时候出错: {, "details": " File \"/opt/stack/nova/nova/com ...
- 定时器管理:nginx的红黑树和libevent的堆
libevent 发生超时后, while循环一次从堆顶del timer——直到最新调整的最小堆顶不是超时事件为止,(实际是del event),但是会稍后把这个timeout的 event放到ac ...
- Windows 网络编程
网络编程 API ,失败返回 -,错误代码 WSASYSNOTREADY 表示基础网络子系统没有准备好网络通行,WSAVERNOTSUPPORTED 表示 Socket 版本不支持,WSAEINPRO ...
- mssqlserver 数据库一直提示“正在还原”
今天访问服务器,突然发现不知道数据库被谁给还原了,而且一直处于还原状态无法结束. 通过查询说是恢复进程被挂起了,最终通过命令: RESTORE database dbname with recov ...