浅谈Linux内存管理

来源:互联网 发布:肯德基糖醋酱淘宝 编辑:程序博客网 时间:2024/05/18 01:52

一. 引言

首先以应用程序开发者的角度审视Linux的进程内存管理,在吃基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法。

二. 基本知识

首先简单的介绍下理解linux内核编程的一些基本知识,当然也可以跳过这一节,你自认为都比较熟悉的。

(1)    AT&T 汇编

1.       基本语法

1)      赋值方向:movl %ebx,%eax ---- 方向从左到右。这个是与INTEL汇编醉倒的区别。

2)      指令前缀:movl $1,%eax ---- $立即数前需要加前缀”$”.

 Rep     

 Scasb

 --- rep 表示重复执行下一条指令。

3)      简介寻址:instr %segreg:disp(base,index,scale),foo

表示Segreg:[base+index*scale+disp]

4)      指令后缀:movw, movb,movl 表示赋值的长度。

 

2.       内嵌汇编

1)      简介:_asm_ _volatitle_(“hlt”);

 “_asm_”: 表示后面的代码为内嵌汇编

 “_volatitle_”: 表示编译器不要优化代码,后面的指令保留原样。

 请参考《Is32》指令

2)      语法:

 _asm_(

       汇编语句模板:

       输出部分:

       输入部分:

       破坏描述部分)

3)      例子: 取自linux内核源代码的内存管理中的memory.c

 #define invalidate() /

 _asm_(“movl %%eax, %%cr3”::”a”(0))

 输入部分没有,输入部分”a”表示eax,eax0,最后表示将eax的值付给cr3寄存器。

(2)    80x86保护模式

 

图片

16为实模式,物理内存=段地址*16+EIP.段地址存放的是线性地址的基地址。而32位保护模式,此时的段寄存器存放的不是线性地址的基地址,而是选端子,相当于描述表的索引,那描述表是什么的东西呢,它则相当于一个数组,存放做所有段的线性地址的基地址。具体请参考《80x86汇编程序设计》中的保护模式章节。

 

a.       根据任务结构中知道全局描述符号中该任务局部描述符的地址。

b.       根据程序中的代码段寄存器CS中的值,即在局部描述符表中的索引。知道代码段的线性基地址。

c.       最后,将eip,esi,edi等偏移值加上代码段基地址,就是所要的线性地址了。

d.       如果没有开启分页机制,线性地址就是物理地址。

图片

(3)    Linux 体系结构

 

图片

(4)    Linux 启动引导

 

图片

linux-0.12内核情景分析》的启动引导章节。当电脑加点的时候,启动线性地址4g的代码,这个地址其实映射在ram bios。这段代码会将引导扇区的代码考的0x7c00,并跳转到该地址处。Head.s初始化描述表,并开启页机制。次时开启了linux内核之旅。


三. 分页机制

开启分页机制后,线性地址需要经过页机制转换成物理内存。如图.

 

图片

Linux将页目录表的存放在物理内存0处。页表则依次存放在页表目录表的后面。Cr3寄存器存放着页目录表的地址。

图片

 

每个页表项和页目录项都是占4个字节。

图片

 

四. 内存管理

(1) 进程内存区别

逻辑地址:

程序的逻辑地址,在linux中即为任务号*64m64是一个页表的最大寻址空间。

 

图片

内核中的线性地址空间的内存跟物理地址一样。

线性地址:

Cpu能地址的地址空间。如果没有开启页机制,线性地址就是物理地址。

 

图片

  每个进程在线性地址中都是从nr*64M的地址位置开始(nr是任务号),占用线性地址空间的范围是64M。其中最后部分的环境参数数据快最长为128K,其左面起始堆栈指针。在进程创建时bss段的第一页被初始化为全0

物理地址:

真正在物理内存条上的内存。

       可以阅读下Mel GormanLinux Virtual Memory Manager

(2) linux内存管理

       Intel 80x86 CPU中,程序在寻址过程中使用的有段和偏移值构成的地址。该地址并不能直接用来寻址物理内存,称为虚拟地址。将虚拟地址映射到物理内存,这个地址变换机制就是内存管理的主要功能之一(内存管理的另外一个主要功能是内存的寻址保护机制)。虚拟地址通过段管理机制首先编程一种中间地址形式-cpu 32的线性地址。然后使用分页管理机制将线性地址映射到物理地址。

       图片

(3) 写时赋值(copy on wirte)机制

        当进程A使用系统调用fock创建一个子进程B时,由于子进程B实际上是父进程A的一个拷贝,因此会拥有与进程相同的物理页面。也即为了达到节约内存和加快创建速度的目标,fork函数会让子进程B以只读方式共享父进程A的物理页面。也同时将父进程A对这些物理页面的访问权限也设成只读。详见memory.c程序中的copy_page_tables函数。这样一来,当父进程A和子进程B任何一方对这些以共享的物理页面执行写操作时,都会产生出错异常(page_fault int14)中断,此时CPU会执行系统提供的异常函数do_wp_page函数试图解决这个异常。

        Do_wp_page会对这块导致写入异常中断的物理页进行取消共享操作(un_wp_page函数),为写进程复制一新的物理页面,使父进程A和子进程B各自拥有一块内容系统的物理页面。这时才真正地进行了复制操作(只复制这一块物理页面)。并且把将要执行写入操作的这块物理页面标记成可以写访问,将这块物理页面映射到相应的线性地址。最后,从异常处理函数中返回时,cpu就会重新执行刚才导致异常的写入操作指令,是进程继续执行。

        因此,对进程在自己的虚拟地址范围内进行些操作时,就会使用上面这种被动的写时复制操作:写操作-》页面异常中断-》处理写保护异常-》重新执行写操作

 

(4) 加载执行程序

      写操作-》页面缺页中断-》处理写保护异常-》重新执行写操作

       也就是说,当exec执行某个程序时,不会讲该程序的所有代码和数据整个加载到内存中的,只有执行到某个虚拟地址时,发现缺页时,cpu就会产生缺页中断,在page.S中执行do_no_page.

(5) 库函数malloc

        其没有真正意思上调用内核get_free_page获得物理内存,而是调整线性地址空间,只要当真正要访问的时候,产生缺页中断,才有系统的内存管理区分配物理内存,并映射到线性地址。

 

五. 小结

程序的虚拟地址-(段机制,选择符)-》线性地址-(页机制,内存管理)-》物理地址

原创粉丝点击