linux 中的内存管理

43
1 Linux Linux 中中中中中中 中中中中中中

Upload: ketan

Post on 19-Mar-2016

135 views

Category:

Documents


12 download

DESCRIPTION

Linux 中的内存管理. 从机器加电启动开始,处理器就在不断的取指令、执行指令。内存分配和内存寻址是关系系统运行很重要的问题。 要更深入理解系统底层的管理机制,难免要涉及内核实现机制的分析。本章将先分析内存管理,然后再通过具体实例进行编程体验。. 关于内核源码学习. 系统实现的具体细节最终落到源代码上。从理解运行机制出发,分析各种数据结构。在理论明了、有一定的程序基础后再阅读梳理代码,这是需要反复且花功夫的一件事。 阅读工具 帮助追踪复制调用,数据结构定义等 windows 环境下利用 Source Insight - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: Linux 中的内存管理

1

LinuxLinux 中的内存管中的内存管理理

Page 2: Linux 中的内存管理

2

从机器加电启动开始,处理器就在不断的取指令、执行指令。内存分配和内存寻址是关系系统运行很重要的问题。要更深入理解系统底层的管理机制,难免要涉及内核实现机制的分析。本章将先分析内存管理,然后再通过具体实例进行编程体验。

Page 3: Linux 中的内存管理

3

关于内核源码学习系统实现的具体细节最终落到源代码上。从理解运行机制出发,分析各种数据结构。在理论明了、有一定的程序基础后再阅读梳理代码,这是需要反复且花功夫的一件事。

阅读工具帮助追踪复制调用,数据结构定义等 windows环境下利用 Source Insight linux环境下利用 lxr(linux cross reference)或 glimpse等

阅读顺序一般按顺序阅读启动代码;然后进行专题阅读,如进程部分,内存管理部分等。在每个功能函数内部应该一步步来。 OK,感兴趣的话,反复读。 纵向:顺着程序的执行顺序逐步进行。 横向:分模块进行划分不是绝对的,而是经常结合在一起进行 Linux的启动:顺着 linux的启动顺序读,大致流程如下(以 X86平台为例):

./arch/x86/boot/bootSect.S

./arch/x86/boot/setup.S

./arch/x86/kernel/head.S

./init/main.c中的 start_kernel() 对于内存管理等部分,可以单独拿出来按模块进行阅读分析。

参考书——《 LINUX内核源代码情景分析》

Page 4: Linux 中的内存管理

4

主要内容

内存空间1 内存寻址2

内存分配和回收3

Page 5: Linux 中的内存管理

5

内存寻址1

Page 6: Linux 中的内存管理

6

简单的说系统运行就是两步:①装入内存、②执行;从装入内核、运行内核,到装入用户程序、运行用户程序。 怎么从内存中执行第 1 条指令?

① 系统初始化时装入 OS 内核程序:这意谓着要构建操作系统进程,最关键的还包括给内核分配空间并构建内核占用空间的页表——记录内核在内存的哪里。(内存分配)② 初始化完成后,内核程序已放进内存。执行阶段,处理器只需按规定的机制查询内核的页表找到要执行的指令然后依次执行即可,当然难免遇到程序跳转,可利用栈进行相应的跳转和返回。(内存寻址)

Page 7: Linux 中的内存管理

7

如何运行用户程序的第 1 条指令?① 由内核装入用户程序:同样要构建用户程序的进程,给用户进程分配空间并构建页表——记录下来进程在内存的哪里。(内存分配)② 用户程序装入后,执行阶段处理器也是按规定的机制查询用户进程的页表找到要执行的指令然后依次执行即可,难免遇到程序跳转,可利用栈进行相应的跳转和返回。(内存寻址)

实际上内存分配和内存寻址方式是密切联系的,先假设用户程序的内存已经分配,看看指令寻址过程具体是怎样的

Page 8: Linux 中的内存管理

8

4 、物理地址、虚拟地址及线性地址 几个地址名词 物理地址:将主板上的物理内存条所提供的内存空间定义为物理内存空间,其中每个内存单元的实际地址就是物理地址。 虚拟地址:将应用程序员看到的内存空间定义为虚拟地址空间 ( 或地址空间 ) ,其中的地址就叫虚拟地址 ( 或虚地址 ) ,分段机制下,一般用“段:偏移量”的形式来描述。 线性地址:指一段连续的,范围为 0 到 n 的地址空间,一个线性地址就是线性地址空间的一个绝对地址。

Page 9: Linux 中的内存管理

9

linux 是什么样的地址映射机制?内存管理有两种,一种是段式管理,另一种是页式管理,而页式管理更为先进。从 80 年代中期开始,页式内存管理进入了各种操作系统 ( 以 Unix 为主 ) 的内核,一时成为操作系统的一个热点。

硬件对系统寻址方式的制约: Intel平台:其 80286 开始实现保护模式的分段机制,但是很快就发现,光有段式内存管理而没有页式内存管理会使它的 X86 系列逐渐失去竞争力以及作为主流 CPU 产品的地位。于是,从 80386到后来的 IA32中实现了对页式内存管理的支持。但为了兼容旧的系统结构,它的页式存储管理只能建立在段式存储管理的基础上。 其他平台:多数硬件不支持分段机制,而是线性地址分页机制

linux设计地址映射机制要考虑:① Intel IA32的分段机制是不可禁止的, linux适应 IA32就必须支持分段机制;② 但若要方便移植到其他平台,还要方便去除分段机制;

Page 10: Linux 中的内存管理

10

① 何谓“支持硬件分段机制”:在要求分段的硬件下,地址处理必须的流程是通过段寄存器找到段处理的相关信息:段基址、段界限等。面对 intel这种强硬需求, linux必须提供这些信息,并提供程序流程进行分段处理。但 Linux没有妥协,它做了特殊的段机制设计: linux 上的程序文件面向硬件只描述代码段和数据段(各有内核态和用户态)共 4 个段,且段基址都为 0 ,段界限为

4GB 。 段机制前后地址实际上没有变化,然后进入分页机制② 方便移植: 段机制前后地址实际上没有变化——意谓着在不要求分段的其他硬件平台上,上述的段处理程序就可以很容易的裁掉而不影响分页地址处理。

Page 11: Linux 中的内存管理

11

段 a

段 b 段 b

思考: linux 下使每个段基址为 0 ,段界限为 4GB 。① 比较 a 、 b 段的第一条指令地址,若按段思维,段基址都是 0 ,段内指令从偏移 0 开始,两个段指令地址不就相同了,即使有分页机制,也无法区别开他们映射的内存地址啊?

段 a

地址映射

段机制 内存 页机制

页表

Page 12: Linux 中的内存管理

12

例: i386 体系结构的机器上,对 c 程序 helloworld反汇编后观察指令地址——逻辑地址(虚拟地址)。 linux 下常用 ELF格式可执行文件, ld总是从地址 0x8000000 开始安排程序代码段。 观察下图可以看到实际上 linux 下整个虚拟地址空间是线性的。

Page 13: Linux 中的内存管理

13

段 a

段 b

段 b

思考: linux 下使每个段基址为 0 ,段界限为 4GB 。①linux 的段机制下 a 、 b 段的第一条指令地址偏移不是 0 ,所以即使段基址都是 0 ,地址也不会重叠;② 每个段界限最大到 4G ,内存不够用怎么办?虚拟内存的关键——只装入部分不足时置换,具体由页机制处理。

段 a

地址映射

段机制 内存 页机制

页表

外存

Page 14: Linux 中的内存管理

14

总之 linux 下针对 intel 硬件设计了段机制,但所有段基址都是 0 ,长度不超过 4G 。这个段机制前后地址不发生变化。 配合 linux 这种特殊的段设计,最关键的是生成的虚拟地址,虽然看似段:偏移的二维形式,但实际上是一维平坦的地址空间。

在这样的设计下内存分配和内存寻址过程实际上有下面几步系统段表是为了应付硬件写好的固定内容,用户程序不需要管①形成虚拟地址②分配内存—构造页表③执行——开始段页机制寻址装入程序时完成

Page 15: Linux 中的内存管理

a.o

b.o

c.o

源代码

编译 链接

ELF可执行文件windows下是exe格式

逻辑地址 段:偏移

内存 分页 分段

内核----------------堆栈-----------------

-----------------数据段-----------------代码段

地址映射

物理地址 虚拟内存地址 线性地址实际上没有变化

只装入一部分

栈-----------------

-----------------数据段-----------------代码段

内核栈-----------------

-----------------内核数据段-----------------内核代码段

2 )开始执行读程序代码段第 1 条虚拟地址,利用页表进行地址映射即可找到指令在物理内存的位置并执行。

装入

页表

1 ) elf 文件装入,各种信息生成并记录:进程相关: pcb ( task_struct )内存相关:

通过读可执行程序中的内容形成虚拟地址相关信息(mm_struct) :代码段“从 **到 **” ,数据段“从**到 **”等等——形成虚拟内存空间。分配一定的内存,伴随着分配内存就要记录占用的内存信息 ,页表就构造出来了。

Page 16: Linux 中的内存管理

16

一个程序编译连接后形成的地址空间是一个虚拟地址空间 ,但mm_struct 形成时才意谓着进程的虚拟地址空间描述完成。 页表有填入的实际映射信息,才真正有物理空间,然后随着内存的请页与交换页表内容动态变化。所以

4G 的虚拟内存不一定非要 4G 的物理内存。 内存寻址过程总结:

① 取代码段的第 1 条指令,此时指令是虚拟地址;② 虚拟地址经过段机制处理后我们称为线性地址,实际上地址没有任何变化;③ 利用页表得到指令实际所在的物理内存地址;

在内存就执行,不在就调入。

Page 17: Linux 中的内存管理

物理内存

mm_struct :对整个虚存空间描述vm_area_struct :逻辑区域方面组织

从数据结构角度理解进程创建过程的内存处理(选看)

Page 18: Linux 中的内存管理

四个地址、两个地址空间

虚拟地址空间的好处对程序员屏蔽底层地址,更好的支持虚拟处理

段标识 : 偏移逻辑地址

虚拟地址空间物理地址空间

装入

Page 19: Linux 中的内存管理

19

内存空间2

Page 20: Linux 中的内存管理

20

关于虚拟地址空间的理解① 程序编译链接后,可执行文件里描述了进程指令和数据在哪里——虚拟地址。② 程序装入时,这些信息被记录到 mm_struct里以备给操作系统查看。可以说通过

mm_struct 进程的线性虚拟地址空间被描述出来。③ 程序执行时, CPU 要从 mm_struct 里记录的代码段开始执行,这里读到第 1 条指令的地址在哪,而这个地址是虚拟地址。④ 然后就是内存寻址机制,从虚拟地址映射到物理地址。虚拟地址形成的虚拟地址空间就是虚拟内存空间,最大 4G 。

Page 21: Linux 中的内存管理

虚拟内存、内核空间和用户空间

内核空间( 1GB )

进 程 1的 用 户空 间(3GB)

进 程 2的 用 户空 间(3GB)

进 程 n的 用 户空 间(3GB)

虚拟地址空间

32位寻址最大可支持 4G 的内存(在 32位的 x86 机器, 2^32 =4G ),而实际上 linux 用户面对的不是物理内存,是 4G 的虚拟内存。虚拟内存分两部分: 内核空间( 3G-4G ,虚地址 0xC0000000 到 0xFFFFFFFF )

编译链接时内核相关程序和数据被编址到这个部分 用户空间(较低的 3G字节,虚地址 0x00000000 到 0xBFFFFFFF )

编译链接时进程的相关代码和数据被编址到这个部分从具体进程的角度看,每个进程都拥有各自独立的 4GB 虚拟地址空间 ( 虚拟内存 ) 。

进程最大拥有 3G字节私有虚存空间,这些私有空间映射在内存哪里由各进程的页表决定。

Linux 内核空间对系统内的所有进程是共享的——每个进程都可以通过系统调用进入内核,所以是共享的;虽然内核空间占据了每个虚拟空间中的最高 1GB字节,但映射到物理内存却总是从最低地址( 0x00000000 )开始的,它的映射机制和用户的不一样

程序编译连接后形成的虚拟地址空间,装入时形成 mm_struct意谓着进程的虚拟地址空间描述完成。

Page 22: Linux 中的内存管理

22

进程的空间始终从进程虚拟地址空间的角度看

Page 23: Linux 中的内存管理

23

malloc 、 vmalloc等申请内存的操作函数实际上也都是从 4G 的虚拟内存中申请空间。进程通信中提到的共享内存通过将同一块物理内存(常为一种文件)映射到进程 A 、 B各自的内存,两个进程可快速感知对方的修改,这里的映射就是在进程虚拟内存的用户空间上映射的。也就是说内存分配是①申请虚拟内存②再映射到物理内存,至于这些申请的空间具体映射到内存哪里由系统具体分配和构建的页表决定。

Page 24: Linux 中的内存管理

24

区分用户空间和内核空间为什么共享内存是最快的 IPC:

消息队列和信号量、管道等对象,经由它们的数据需要在内核和用户空间进行额外的数据拷贝;而共享内存和访问它的所有进程都处于用户空间,进程通过地址映射的方式直接读写内存,从而获得非常高的通信效率。当然,由于被映射的物理内存被多个进程共享,共享内存往往需要搭配某种同步机制如信号量。

Page 25: Linux 中的内存管理

25

linux支持多种共享内存方式(选看) ① POSIX共享内存对象 : shm_open创建一个名称为 tmp的共享内存区对象后,在 /dev/shm/下可以看到对应的文件( tmpfs的文件系统 可以看成是直接对内存操作,速度非常快 )cat可以看到内容。进程重启共享内存中数据不会丢失,内核自举或显示调用shm_unlink 或 rm掉文件删除后丢失

② POSIX文件映射:通过映射一个普通文件(匿名文件或一个打开的命名文件)实现共享内存—— mmap()。该方式接口简单,较通用。可利用 cat查看映射的文件,要注意考虑进程终止对通信的影响。 ③ systemV共享内存:通过映射特殊存储块 shm中的文件实现进程间的共享内存通信——主要有以下几个 API :shmget() 、 shmat() 、 shmdt() 及 shmctl()。 本方式无法看到文件实体。进程重启共享内存中数据不会丢失,内核自举或显示调用shmdt或使用 ipcrm删除后丢失。

Page 26: Linux 中的内存管理

26

举例:在进程用户空间映射一个共享匿名虚存区实现共享内存 父子进程共享一个 4 字节的匿名区,利用内存区传递信息#include <sys/mman.h>#include <stdio.h>#include <unistd.h>#define N 10int main(){int i,sum,fd;int *result= mmap (0,4,PROT_READ|PROT_WRITE,MAP_SHARED|

MAP_ANONYMOUS,0,0);int pid=fork();if (pid==0){ //子进程

for(sum=0,i=1;i<N;i++) sum+=i;*result=sum;printf(“child %d write: result =%d,sum=%d\n”,getpid(),*result,sum);}else{ //父进程wait(0);printf(“farther %d : result =%d,sum=%d\n”,getpid(),*result,sum);}printf(“we’re going to sleep 20 seconds,see us in /proc \n”);sleep(20);if (pid>0) munmap(result,4);sleep(20);

}执行结果: farther: result=45 , sum=134518632父子进程 result 指向共享的虚存区,有相同值;各自 sum 变量不同程序执行后父子进程都会睡眠 20秒,到 proc 的相应 pid目录下观察maps 文件,可以看到新建的匿名虚存区

Page 27: Linux 中的内存管理

27

共享的虚存区

运行上页程序,观察 proc 下进程的虚存映射信息

Page 28: Linux 中的内存管理

28

不同版本虚拟段的处理或许不同, ubuntu 下抓图如下:

虽然申请 4B,但是实际上分配了a000-b000之间的 4K( 212 )大小的空间,因为内存分配往往以页为单位, i386下一般一页是 4K。

Page 29: Linux 中的内存管理

29

mmap() 函数的定义原型:void *mmap (void *start , int length, int prot, int flags, int fd, int offset) start :映射到用户空间的起始地址,一般为NULL,由系统自动选择 length:长度(以字节为单位) prot:表示对所映射区间的访问模式,如可写、可读、可执行等,如

PORT_READ。 flags:用于其他控制目的

MAP_SHARED:与子进程共享虚存区MAP_PRIVATE: “ ”子进程对这个虚存区是 写时考贝MAP_LOCKED:锁定这个虚存区,不能交换。MAP_ANONYMOUS:匿名区,与文件无关

fd:代表一个已打开的文件 offset:文件的起点

Page 30: Linux 中的内存管理

30

mmap 的两种映射方式:( 1 ) mmap 使用特殊文件进行匿名内存映射:

适用于具有亲缘关系的进程之间;子进程继承父进程匿名映射后的地址空间,同样也继承

mmap() 返回的地址,由于这里不是一般的继承关系,父子进程都可以通过修改映射区域进行通信。( 2 ) mmap 使用普通文件提供内存映射:

适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用 mmap() ;典型调用代码如下: fd=open(name, flag, mode);

if(fd<0) ... ptr=mmap(NULL, len , PROT_READ|

PROT_WRITE, MAP_SHARED , fd , 0);通过 mmap() 实现共享内存的通信方式有许多特点和要注意的地方,不再详细说明。

Page 31: Linux 中的内存管理

31

将命名文件 test映射到进程用户空间int i,fd;char *buf;fd=open(“test”,O_RDONLY);buf=mmap(0,12,PORT_READ,MAP_PRI

VATE,fd,0);for (i=0;i<12;i++)

printf(“%c\n”,buf[i]);

Page 32: Linux 中的内存管理

32

内存分配和回收3

Page 33: Linux 中的内存管理

33

linux 的物理存储器划分静态存储器( static memory )虚拟地址空间的 3G-4G 实际上映射在物理内存的低端。 RAM 的 1M之前保留做 BIOS 或其它特定情况所用。另外再分一些永久地存放内核代码以及静态数据。动态存储器( dynamic memory )

RAM 中除去静态存储器的部分,动态分配给进程和内核的部分。属于稀缺资源。整个系统的性能取决于如何有效地管理动态存储器。对于动态存储器要尽可能做到:按需分配,不需要时释放。 保留 内核映象 动态内存

0 0x100000 start_mem end_mem

Page 34: Linux 中的内存管理

34

内存分配都是先分配虚拟内存,再分配物理内存的。虚存的分配:

用户空间上分配虚存:按页;内核空间上分配虚存:一种按页分,另一种

用 slab模式在虚拟地址按字节分配。物理内存的分配

分配和回收的基本单位是物理页( i386 体系结构下是 4K )采用伙伴算法,可快速分配连续的物理页面,也解决了外碎片问题。

Page 35: Linux 中的内存管理

内存分配是先分配虚拟空间,然后才分配页面从而虚拟空间映射到物理空间

保留 内核映象 动态内存

0 0x100000 start_mem end_mem

用户空间 0-3G 页方式获得虚拟地址空间 物理地址空间

页为单位分配slab

获 得页 面

linux规定 high_memory 不能超过0xc0000000+896M 。若物理地址 0<=x<=896M,内核映像地址最大不超过 0xc0000000+x 。

内核对象有两种分配虚拟内存方式,采用 slab分配模式以字节为单位,或页分配模式

Page 36: Linux 中的内存管理

36

伙伴算法举例

伙伴关系从最小号开始,成对找伙伴

Page 37: Linux 中的内存管理

37

1 )用户空间分配内存 valloc/malloc--free#include<unistd.h>#include<stdlib.h>#include<stdio.h>#define A_MEGABYTE 1024*1024int main(){

char *some_memery; int megabyte=A_MEGABYTE; int exit_code=EXIT_FAILURE;

some_memery=(char*)valloc(megabyte);if(some_memery!=NULL){

/* sprintf将格式化字符串输出到目的字符串,本例中目的字串是some_memery */

sprintf(some_memery,“Hello world!\n”);printf("%s",some_memery);free(some_memery);printf("memery is free!\n");exit_code=EXIT_SUCCESS;}

exit(exit_code);}

Page 38: Linux 中的内存管理

38

2 )内核空间分配内存内核空间上分配虚存:

一种按页分, vmalloc另一种用 slab模式在虚拟地址按字节分配, kmalloc 。

Page 39: Linux 中的内存管理

39

slab 可解决内碎片问题,因为很多数据结构请求分配的内存大小不足以用一个页面那么大。

缓冲区

slab

slab

对象

Struct kmem_cache_t

Struct kmem_slab_t

内核虚存中划出一片区域。把这片区域划分为多个块,每块就是一个 Slab

每个 Slab 由一个或多个页面组成,每个 Slab 中存放的是同类型的对象

频繁使用的数据结构可划分为专用缓冲区,如存放一组task_struct类型的对象

使用频度低,占空不足一个页面的数据结构可创建一组通用缓冲区来处理。

Page 40: Linux 中的内存管理

40

内存

Page 41: Linux 中的内存管理

41

内核的虚拟内存分配回收函数①slab专用缓存区:

kmem_cache_alloc ()、 mem_cache_free ()②slab 通用缓冲区:

kmalloc( ) 、 kfree( )void *kmalloc(size_t size,int flags);

这些函数是向 slab 分配器要虚拟空间,而 slab根据情况再通过伙伴算法从内存要物理页面。kamlloc 分配的内存块要由 kfree释放。分配和回收要配对使用,避免内存泄露和其他 bug 。③内核非连续内存区内核程序通过 slab 不能获得大块内存,所以专门在内核空间保留一些空( VMALLOC_START ~ 4GB

),用 vmalloc 进行分配:void *vmalloc(unsigned long size)

Page 42: Linux 中的内存管理

42

都是内核代码在内核空间分配内存(虚拟内存),但分配的内存在内核空间的不同位置。kmalloc() 分配的内存处于 3GB ~ high_memory之间,vmalloc() 分配的内存在 VMALLOC_START ~ 4GB之间,

地址连续性不同。kmalloc 虚拟地址连续,物理地址也连续。硬件设备使用的内存常需要此种。很多内核代码出于性能的考虑常用该方式获得内存;还比如内核中的驱动程序。vmalloc 虚拟地址连续,但通过分页映射的物理内存不保证物理地址连续。软件程序可使用此类内存。往往在需要大块内存不得已时才用此方式获得内存,如动态装载一个模块到内核中时。

vmalloc()与 kmalloc()之区别

Page 43: Linux 中的内存管理

43