内核版本: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. C和指针 第十五章 文件I/O

    stdio.h中包含了声明FILE结构 struct _iobuf { char *_ptr; //文件输入的下一个位置 int _cnt; //当前缓冲区的相对位置 char *_base; //指 ...

  2. 有时打开myeclipse,部署报错解决方案

    1.首先关闭MyEclipse工作空间. 2.然后删除工作空间下的 "/.metadata/.plugins/org.eclipse.core.runtime/.settings/com.g ...

  3. 耿丹CS16-2班第六次作业汇总

    Deadline: 2016-11-13 11:59 作业内容 第六次作业总结 00.本次题目分值最高为**6分/题 × 7题 + 5分/篇 × 1篇 = 47分**,其中有新解法者每题加原创分**2 ...

  4. cd命令

    [cd]           切换目录     cd===>change directory 命令功能 : 切换目录到dirname 命令实例: 实例1:切换到跟目录下 命令: cd / 输出: ...

  5. webbrower判断页面是否加载完成的好方法

    public void WaitPageCompleted(int timeOutSec) { ; ; j < timeOutSec * ; j++) { _wb.Invoke(new Acti ...

  6. 55. 2种方法求字符串的组合[string combination]

    [本文链接] http://www.cnblogs.com/hellogiser/p/string-combination.html [题目] 题目:输入一个字符串,输出该字符串中字符的所有组合.举个 ...

  7. google tensorflow guide

    # For CPU-only version $ pip install https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow- ...

  8. JQuery实现无刷新下拉加载图片

          最近做的一个项目需要做页面无刷新下拉加载图片,调研了一番,大多都采用检测滚动条达到底部,然后利用ajax加载下一页数据对页面数据进行添加,根据这一逻辑,自己写了一个,具体代码如下: JQu ...

  9. mysql查询时强制区分大小写

    转载自:http://snowolf.iteye.com/blog/1681944 平时很少会考虑数据存储需要明确字符串类型字段的大小写,MySQL默认的查询也不区分大小写.但作为用户信息,一旦用户名 ...

  10. 【webGl】threejs实现一个简单的动画-弹跳的小球

    在这里,我们将动态画面简称为动画(animation).正如动画片的原理一样,动画的本质是利用了人眼的视觉暂留特性,快速地变换画面,从而产生物体在运动的假象.而对于Three.js程序而言,动画的实现 ...