内核版本: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. 编译安装PHP7并安装Redis扩展Swoole扩展

    编译安装PHP7并安装Redis扩展Swoole扩展 在编译php7的机器上已经有编译安装过php5.3以上的版本,从而依赖库都有了 本php7是编译成fpm-php 使用的, 如果是apache那么 ...

  2. Web干货存档

    今天看了某乎,介绍了web一些基本东西,讲的很好,随手留下https://www.zhihu.com/question/22689579 web开发者文档,纯干货   https://develope ...

  3. git服务器搭建总结

    1.软件选择 服务端软件:由于我对linux还不熟悉,而且公司用的都是windows,于是找到了bonobo,这是一个基于.net framework 4.5和.net mvc4的开源软件,iis7. ...

  4. highcharts使用笔记

    1.legend取消点击事件: 饼图:plotOptions.pie.point.events.legendItemClick = function() {return false;} 其他,如:pl ...

  5. Coding List

    决定还是用回.net吧,一个人瞎搞比较快,在这里把进展做个简单的记录.

  6. ASP中Lable控件的定位问题

    问题:Lable控件的定位问题:找了好久都没找到可以将Lable控件定位的办法,网上说可以将修改position这个属性来实现定位,可是我始终没找到这个属性. (1)首先,在源代码中添加 style ...

  7. 安装yum

    RedHat 安装配置 YUM 删除 1 . 查询系统是否安装 yum : rpm – qa|grep yum 2删除原有 yum rpm -qa|grep yum|xargs rpm -e – no ...

  8. Cocoapods - pod install 成功后找不到头文件解决

    问题描述:使用Cocoapods时,import 找不到头文件. 问题原因:这是因为还没设置头文件的目录. 解决办法:在项目的Target的里设置一下,添加cocoapods头文件目录:目录路径直接写 ...

  9. iOS设计 - 一款APP从设计稿到切图过程概述

    这篇文章站在GUI设计师的角度概述了APP从项目启动到切片输出的过程,相当于工作流程的介绍.这里写的不是一种规范,只是一种工作方法,加上技术的更新是非常快的,大家在具体工作中,一定要灵活运用. 这里我 ...

  10. 何为HDFS?

    该文来自百度百科,自我收藏. Hadoop分布式文件系统(HDFS)被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统.它和现有的分布式文件系统有很多共同点.但同时, ...