• 博客访问: 216992
  • 博文数量: 11
  • 博客积分: 286
  • 博客等级: 二等列兵
  • 技术积分: 445
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-01 21:39
文章分类
微信关注

IT168企业级官微



微信号:IT168qiye



系统架构师大会



微信号:SACC2013

订阅
热词专题
kernel笔记——内存管理 2012-07-20 23:28:12

分类: LINUX

理解linux内存管理首先得理解内存映射。

程序用的都是逻辑地址,以下为objdump反汇编程序的结果,左边一列都是逻辑地址:

  1. 000000000040053c <main>:
  2. 40053c:      55                      push %rbp
  3. 40053d:     48 89 e5             mov %rsp,%rbp
  4. 400540:     bf 4c 06 40 00    mov $0x40064c,%edi
  5. 400545:     e8 e6 fe ff ff        callq 400430 <puts@plt>
  6. 40054a:     b8 00 00 00 00   mov $0x0,%eax
  7. 40054f:      c9                       leaveq
  8. 400550:     c3                       retq

vmcore中,我们看到内存地址也是逻辑地址:

  1. crash> bt 1300
  2. PID: 1300 TASK: ffff81024fba1810 CPU: 0 COMMAND: "sshd"
  3. #0 [ffff81026a2e9cd8] schedule         at ffffffff802d9025
  4. #1 [ffff81026a2e9da0] schedule_timeout at ffffffff802d9a6b
  5. #2 [ffff81026a2e9df0] do_select        at ffffffff801935f2
  6. #3 [ffff81026a2e9ed0] sys_select       at ffffffff80193880
  7. #4 [ffff81026a2e9f80] system_call      at ffffffff8010ad3e

MMU固件完成逻辑地址到物理地址的转换:


从上图看到转换分成两部分,段式内存管理将逻辑地址转换成线性地址,线性地址再经过页式内存管理单元转换成物理地址。


对于intel x86架构的cpu,段的基址为0,段式内存管理并不真正起作用,我们看到的逻辑地址可以理解为就是线性地址。

 

线性地址到物理地址的转换关系,由内核中的页表(page table)维护,下图说明了线性地址转换成物理地址的过程:


寄存器cr3中保存了进程的页基地址,它是物理地址,每个进程的页基地址都不相同,通过这样实现进程间物理地址空间隔离。


64位操作系统中,线性地址各段长度在<asm/pgtable_64_types.h>中定义。

以上计算地址的操作由cpu完成,而页表由内核维护。

 

进程内存布局

对于32位操作系统,进程的内存布局如下:


我们使用c库的malloc申请内存时,malloc调用brk()mmap()实现堆(heap)或映射区域(memory mapping segment)的增长。

 

malloc申请内存,其实申请到的不是真正的内存,而是虚拟内存,当程序使用这些内存的时候,将出发缺页异常,内核这时才分配物理内存页面给用户。

 

内核中,由do_page_fault函数处理缺页异常,do_page_fault调用handle_mm_fault,该函数中计算线性地址中的pgdpudpmdpte值,然后调用handle_pte_fault,在handle_pte_fault函数中,调用do_anonymous_page等函数完成内存申请工作。

 

pmap命令读取/proc/PID/maps文件,使用该命令可以查看特定进程的内存映射情况,以下为当前bash进程的部分堆、栈、文件映射和匿名内存段信息:

  1. linux # pmap $$
  2. 7561: bash
  3. START                          SIZE      RSS     PSS   DIRTY   SWAP    PERM   MAPPING
  4. 0000000000400000   552K    480K   248K       0K        0K      r-xp    /bin/bash
  5. 000000000068f000  1180K  1104K 1104K 1104K        0K      rw-p   [heap]
  6. 00007fd7397b9000      20K       16K     16K     16K        0K      rw-p   [anon]
  7. 00007fd7399c0000        4K         4K       4K       4K        0K       r--p   /lib64/libdl-2.11.1.so
  8. 00007fff5ec37000         84K       24K    24K     24K        0K      rw-p   0000000000000000 00:00 [stack]

以上pmap结果显示的每个地址段,在内核中由vm_area_struct结构表示,vm_area_struct结构在<linux/mm_types.h>中定义,该结构包含地址段起始地址、结束地址、权限标志等信息。

 

vmcore中,通过以下方法,我们可以找到某个进程的vm_area_struct结构,查看其内存映射信息。

首先获取到进程task_struct结构的地址:

  1. crash> bt
  2. PID: 1300 TASK: ffff81024fba1810 CPU: 0 COMMAND: "sshd"
  3. ……

找到mmactive_mm字段的值:

  1. crash> struct task_struct ffff81024fba1810
  2. struct task_struct {
  3.   ……
  4.   mm = 0xffff810273d6ef80,
  5.   active_mm = 0xffff810273d6ef80,
  6.   ……
  7. }

解析mm地址对应的mm_struct结构,第1个字段就是mmap

  1. crash> struct mm_struct 0xffff810273d6ef80
  2. struct mm_struct {
  3.   mmap = 0xffff810272b20870,
  4.   ……
  5. }

解析mmap地址对应的vm_area_struct结构,就找到了该进程的一段内存映射,继续解析vm_next,就可以遍历该进程的所有内存映射:

  1. crash> struct vm_area_struct 0xffff810272b20870
  2. struct vm_area_struct {
  3.   vm_mm = 0xffff810273d6ef80,
  4.   vm_start = 47474870689792,
  5.   vm_end = 47474870800384,
  6.   vm_next = 0xffff810276839b50,
  7.   ……
  8. }


伙伴算法

对内核而言,内存最小的分配单位为4k,为解决外部碎片问题,内核提供了伙伴算法(buddy algorithm),伙伴算法用于管理最小单位为4k的连续的内存块。

 

使用以下命令,可以将系统中内存相关的信息显示到/var/log/messagesdmesg中:

  1. echo m > /proc/sysrq-trigger

由该命令打出的信息中,包含了伙伴算法管理的内存块信息:

  1. Jul 20 01:38:39 linux197 kernel: Node 0 Normal: 1362*4kB 1137*8kB 491*16kB 336*32kB 192*64kB 100*128kB 69*256kB 34*512kB 14*1024kB 3*2048kB 795*4096kB = 3370112kB-

以上输出说明4kB的内存有1362块,8kB的连续内存有1137块,以此类推。

 

伙伴算法分配与回收内存的策略如下:

  • 当所需的小块连续内存不足以分配时,将拆分较大块的连续内存块
  • 当内存返回给内核时,若相邻的内存块为空闲内存,则与相邻的内存合并

 

slab

slab解决内部碎片问题,申请一块保存task_struct结构的内存,该块内存小于4k,而分配出去的内存若还是4k,则会造成内存浪费。

 

slab依据内核中各种数据结构为单位,进行内存分配。/proc/slabinfo提供了查询slab信息的接口。

 

对于系统内存的整体使用情况,我们可以通过/proc/meminfo接口查到:

  1. cat /proc/meminfo
  2. MemTotal:    8110624 kB
  3. MemFree:     6336284 kB
  4. Buffers:        78020 kB
  5. Cached:        1514352 kB
  6. ……

free命令就是读取/proc/meminfo,进行内存信息显示的。

 

buffers用于块设备缓存,cached用于文件缓存。查看内核中的meminfo_proc_show函数,我们就能发现bufferscached的具体计算方法。

 

执行以下dd命令,我们可以看到free命令中buffers值的增长:

  1. dd if=/dev/sda2 of=/dev/null bs=1000 count=10000000

 

内存回收

对于linux server,以下两种情况会触发内核通过页帧回收算法(page frame reclaiming algorithm, PFRA)进行内存回收:

  • 内存使用紧张
  • 内核线程kswapd周期性回收 

回收内存的方式大体有两种:

  • 丢弃数据
  • 将数据交换出去 

下图说明了内核中回收内存相关的函数调用:


Reference: Chapter 12 - Memory Management, Linux kernel development.3rd.Edition


阅读(5364) | 评论(0) | 转发(9) |
给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册