Linux内存管理--虚拟地址(逻辑地址)物理地址&slab分配器&伙伴系统

内核版本:2.4.18. 针对i386
内容大纲

  1. x86地址映射与地址转换
    1. 实模式
    2. 虚实地址转换
  2. 物理页面的分配回收
    1. 数据结构
    2. slab
    3. buddy系统

1.x86地址转换

Linux内存管理--虚拟地址(逻辑地址)物理地址&slab分配器&伙伴系统_第1张图片

1.实模式

  • 指令系统的体系结构决定虚拟地址空间
  • 芯片的地址引脚数来决定物理地址空间

i386微处理器芯片有32位地址引脚, 但在实模式下, 只有 A 0   A 19 A_0~A_{19} A0 A19共20跟地址线有效. 因此20位二进制数可以编码 2 20 = 1 M 2^{20}=1M 220=1M个, 地址编码从00000H到FFFFFH. 所以i386在实模式下, 支持1M字节的储存器.

在实模式下, 虚拟地址转换为物理地址是由CPU中的MMU自动完成的.在指令中的逻辑地址是由段地址和相对偏移量组成. 存放段地址和偏移量的专用寄存器都是16位的, 而访问主存使用的物理地址是20位. 因此, 20位物理地址是由16位的偏移量和左移4位的段基址相加而获得的.

2.虚拟地址 → \rightarrow 物理地址

虚拟地址 = = == ==逻辑地址

  1. 虚拟地址的表示

    • 在保护模式下, i386指令系统使用一个48位的存储器指针来指示虚拟地址空间. 由两部分组成: 选择符和偏移量.
    • 选择符16位长,放在段寄存器中, 其作用是在全局或局部描述表中选择一个描述符, 以便确定一个虚拟地址空间中的一个特定的存储器段.
    • 偏移量32位, 放在指针寄存器或用户可寻址的32位数据寄存器中, 它的作用是定位由选择符选定的存储器段中的一个具体位置.
  2. 48位虚拟地址 → \rightarrow 32位线性地址

    在选择符被装入段寄存器时, 该选择符所对应的描述符由硬件自动从LDT或GDT表中装入相应的段描述符高速缓存寄存器中(对程序员透明). 把段描述符高速缓存寄存器装入值, 就实现了16位选择符到对应的32位段基址的转换, 然后把描述符中的32位段基址和32位的偏移量相加就形成了所需的线性地址.

  3. 32位线性地址 → \rightarrow 32位物理地址

    1. C R 0 CR_0 CR0的PE位为0时, 说明存储器禁止分页, 那么线性地址就是物理地址. 故在禁止分页时, i386存储器是一个纯分段结构
    2. C R 0 CR_0 CR0的PE位为1时, 存储器允许分页, 此时线性地址还不是物理地址. 进行页转换时
      1. 首先由控制寄存器 C R 3 CR_3 CR3中的页目录基址寄存器(PDBR)指明页目录在物理储存器的首地址
      2. 然后用目录域中的内容作为索引在页目录表中找到指向页表的起始地址
      3. 再用页域的内容作为索引在页表中找到页框的起始地址
      4. 最后, 把偏移量加到页框的首地址上, 得到所访问单元的物理地址.

2. 物理页面的分配回收

1.数据结构

Linux内存管理--虚拟地址(逻辑地址)物理地址&slab分配器&伙伴系统_第2张图片

  1. page
typedef struct page{ // 物理页帧描述符 位于mm.h
    struct list_head list;
    /*
    --指向映射的inode 
    --双向链表指针, 当该页映射inode时, 指向mapping中的三个链表之一.
    --这三个链表是 : clean_pages, dirty_pages, locked_pages
     */
    struct address_space *mapping; 
                                   
    unsigned long index;     /* 指向映射inode的偏移量 */ 
    /* 页高速缓存散列表的下一项, 指向page_hash_table.*/
    struct page *next_hash;        
   
    struct page **pprev_hash; /* 页高速缓存散列表的前一项 */
    atomic_t count;                /*引用数*/
    unsigned long flags;           /*页标志 */ 
    
    /*lru双向链表指针, 指向inactive_list或者active_list */
    struct list_head lru;          
   
                        
    wait_queue_head_t wait;        /*等待队列 */
    
    /*该页被用作磁盘块缓存时,指向缓存头部 */                            
    struct buffer_head *buffers;   
    
    /*该页的虚拟地址, 如果没有映射就为NULL */                
    void *virtual;   
    struct zone_struct *zone;     /*该页所属的zone  */ 

} mem_map_t;

  1. zone
/*位于mmzone.h
 * On machines where it is needed (eg PCs) we divide physical memory
 * into multiple physical zones. On a PC we have 3 zones:
 *依据物理地址划分...
 * ZONE_DMA   0 - 16 MB ISA DMA capable memory
 * ZONE_NORMAL  16-896 MB   direct mapped by the kernel //存放内核数据和用户数据
 * ZONE_HIGHMEM  > 896 MB   only page cache and user processes // 用户数据 ,页缓存
 */
typedef struct zone_struct {
    //常用字段
spinlock_t      lock; // zone自旋锁
    unsigned long       free_pages; // 当前 zone中的空闲页数
    /*
    pages_min : zone中的空闲页数小于这个值时, 只有内核才能申请到zone中的空闲页
    pages_low : zone中的空闲页数小于这个值时, 内核将立即启动swaping过程
    pages_high : zone中的空闲页数小于这个值时, 内核将启动swaping过程
    */
    unsigned long       pages_min, pages_low, pages_high;
    // 启动zone平衡过程标志
    int         need_balance;
    /*
       --不同大小的空闲页面
		--buddy使用的页链表数组 
     */
    free_area_t     free_area[MAX_ORDER]; 
    /*
     * Discontig memory support fields.
     */
    struct pglist_data  *zone_pgdat; // 所属的node
    struct page     *zone_mem_map; //指向zone的页描述符
    unsigned long       zone_start_paddr; //起始物理地址
    unsigned long       zone_start_mapnr; // 起始物理页号
//以下是不常用的字段
    char            *name;
    unsigned long       size;
} zone_t;

  1. pglist_data
//位于mmzone.h
typedef struct pglist_data {
//zone数组
zone_t node_zones[MAX_NR_ZONES]; 
//zone指针数组, 隐含申请分配内存zone的优先级
    zonelist_t node_zonelists[GFP_ZONEMASK+1]; 
    int nr_zones; // zone数目
    struct page *node_mem_map; // node中page数组
    unsigned long *valid_addr_bitmap; // page是否可用的位数组
    struct bootmem_data *bdata; // bootmem结构, bootmem管理信息
    unsigned long node_start_paddr; // node的起始物理地址
    unsigned long node_start_mapnr; // node的起始物理页号
    unsigned long node_size; // node页总数
    int node_id; // 当前node的编号
    struct pglist_data *node_next; //下一个node
} pg_data_t;

2.slab

slab中有啥结构体:页框, task_struct, mm_struct.

  1. 为什么引入slab分配器

    1. 基于页面的分配器不能满足多种要求
      内核数据结构大小不等都取整到页面不现实
    2. 内核的函数常常重复地使用同一类型的内存区,缓存最近释放的对象可以加速分配和释放
    3. 对内存的请求可以按照请求频率来分类,频繁使用的类型使用专门的缓存,很少使用的可以使用通用缓存
    4. 使用2的幂次大小的内存区域时硬件高速缓存冲突的概率较大,有可能通过仔细安排内存区域的起始地址来减少硬件高速缓存冲突
    5. 缓存一定数量的对象可以减少对buddy系统的调用,从而节省时间并减少由此引起的硬件高速缓存污染
  2. slab思想

    1. 将内存区看作对象
    2. 将对象按照类型分组成不同的高速缓存, 每个高速缓存都是同种类型内存对象的一种储备, 即每种对象类型对应一个高速缓存
  3. 分配策略
    slab分配器通过伙伴系统分配页框

    • 优先从半满的slab满足这个请求
    • 否则从全空的slab中取一个对象满足请求
    • 如果没有空的slab则向buddy系统申请页面生成一个新的slab

3.buddy(伙伴)系统

Linux内存管理--虚拟地址(逻辑地址)物理地址&slab分配器&伙伴系统_第3张图片

位图含义: 位图用来描述伙伴的状态
一对伙伴只使用一位表示.

  • 分配时把大页面掰碎成小页面
  • 回收时要跟原来的兄弟合并,才能被回收

具体的,分配回收代码过多,另开一篇.

你可能感兴趣的:(linux,内存管理,内核)