内核版本: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内核笔记--内存管理之用户态进程内存分配的更多相关文章

  1. Linux内存管理 —— 内核态和用户态的内存分配方式

    1. 使用buddy系统管理ZONE我的这两篇文章buddy系统和slab分配器已经分析过buddy和slab的原理和源码,因此一些细节不再赘述.所有zone都是通过buddy系统管理的,buddy ...

  2. 【转载】linux内核笔记之高端内存映射

    原文:linux内核笔记之高端内存映射 在32位的系统上,内核使用第3GB~第4GB的线性地址空间,共1GB大小.内核将其中的前896MB与物理内存的0~896MB进行直接映射,即线性映射,将剩余的1 ...

  3. Linux内核笔记--网络子系统初探

    内核版本:linux-2.6.11 本文对Linux网络子系统的收发包的流程进行一个大致梳理,以流水账的形式记录从应用层write一个socket开始到这些数据被应用层read出来的这个过程中linu ...

  4. 【转载】linux内核笔记之进程地址空间

    原文:linux内核笔记之进程地址空间 进程的地址空间由允许进程使用的全部线性地址组成,在32位系统中为0~3GB,每个进程看到的线性地址集合是不同的. 内核通过线性区的资源(数据结构)来表示线性地址 ...

  5. linux内核笔记-内核同步

    linux内核就相当于不断对请求进行响应的服务器,这些请求可能来自CPU,可能来自发出中断的外部设备.我们将内核看作两种请求的侍者. (1)老板提出请求,侍者如果空闲,为老板服务.(系统调用或异常) ...

  6. Linux内核中的并发与竞态概述

    1.前言 众所周知,Linux系统是一个多任务的操作系统,当多个任务同时访问同一片内存区域的时候,这些任务可能会相互覆盖内存中数据,从而造成内存中的数据混乱,问题严重的话,还可能会导致系统崩溃. 2. ...

  7. LINUX内核分析第六周学习总结——进程的描述和进程的创建

    LINUX内核分析第六周学习总结——进程的描述和进程的创建 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...

  8. 使用gdb跟踪Linux内核启动过程(从start_kernel到init进程启动)

    本次实验过程如下: 1. 运行MenuOS系统 在实验楼的虚拟机环境里,打击打开shell,使用下面的命令 cd LinuxKernel/ qemu -kernel linux-/arch/x86/b ...

  9. LINUX内核分析第八周总结:进程的切换和系统的一般执行过程

    一.进程调度与进程切换 1.不同的进程有不同的调度需求 第一种分类: I/O密集型(I/O-bound) 频繁的进行I/O 通常会花费很多时间等待I/O操作的完成 CPU密集型(CPU-bound) ...

随机推荐

  1. 仅用aspx文件实现Ajax调用后台cs程序。(实例)

    仅用aspx文件实现Ajax调用后台cs无刷新程序.(实例) 两个文件:aaa.aspx 和aaa.aspx.cs 一.aaa.aspx <script type="text/java ...

  2. JS学习:第一周——NO.4继承

    1.[关于call] 作用:是用来改变this指向的,有两种参数 第一种:第一个参数,用来改变this指向 第二种:给call前面的函数传参,从第二个参数开始,给call前面的函数从左到右一个个的传参 ...

  3. dom 无法找到 body节点问题

    最近在学习html dom节点知识时候,对照代码自己敲了一边,始终获取不到文档中的body对象,代码如下(未修改前): <!doctype html> <html> <h ...

  4. iOS中assign,copy,retain之间的区别以及weak和strong的区别

    @property (nonatomic, assign) NSString *title; 什么是assign,copy,retain之间的区别? assign: 简单赋值,不更改索引计数(Refe ...

  5. ur c题练习

    ur的c果然sxbk啊 ur5:“三个莫比乌斯反演掷地有声"——摘自v(c)f(z)k(y)语录,无删改 ur2:有根树分治裸题,复杂度玄学$O(n\sqrt{n})$. 首先,转化为统计k ...

  6. Ubuntu上Grafana 监控 Docker的技巧

    导读 Grafana 是一个有着丰富指标的开源控制面板.在可视化大规模测量数据的时候是非常有用的.根据不同的指标数据,它提供了一个强大.优雅的来创建.分享和浏览数据的方式. 它提供了丰富多样.灵活的图 ...

  7. bootstrap 布局(收藏/摘抄)

    bootstrap 12栅格 布局

  8. 6 VC维

    1 VC维的定义 VC维其实就是第一个break point的之前的样本容量.标准定义是:对一个假设空间,如果存在N个样本能够被假设空间中的h按所有可能的2的N次方种形式分开,则称该假设空间能够把N个 ...

  9. JS 格式化当前时间

    Date.prototype.format = function(fmt) { var o = { "M+" : this.getMonth()+1, //月份 "d+& ...

  10. C#夯实基础之接口(《CLR via C#》读书笔记)

    一. 接口的类型 接口是引用类型.因此从值类型赋值给接口是需要装箱的.如下所示: class Program { static void Main(string[] args) { ISay catS ...