free_area 空闲区域

2.2.26 内核在启动初期,会创建空闲区域来为内存分配提供地址空间。通过 free_area_init 接口来初始空闲区域。

// 初始空闲区域
unsigned long __init free_area_init(unsigned long start_mem, unsigned long end_mem)
{
    mem_map_t *p;
    unsigned long i, j;
    
    i = (end_mem - PAGE_OFFSET) >> (PAGE_SHIFT + 7);    // 空闲区域的大小在 10 ~ 256 页之内,避免内存浪费
    													// 加 7 的操作是为了处理存在余数的情况下增加 1 页
    if (i < 10)
        i = 10;
    if (i > 256)
        i = 256;
    freepages.min = i;
    freepages.low = i * 2;
    freepages.high = i * 3;
    mem_map = (mem_map_t *)LONG_ALIGN(start_mem);   // 确定 mem_map 数组起始位置
    p = mem_map + MAP_NR(end_mem);
    start_mem = LONG_ALIGN((unsigned long) p);
    memset(mem_map, 0, satrt_mem - (unsigned long)mem_map);    // 将 mem_map 数组全部清空
    do {
        --p;
        atomic_set(&p->count, 0);       // mem_map 数组元素计数清 0,设置为 DMA 和预留
        p->flags = (1 << PG_DMA) | (1 << PG_reserved);
    } while (p > mem_map)
    
    for (j = 0; j < NR_MEM_TYPES; j++) {    // 空闲区域划分为多个类型,每个类型中包含一系列页
        unsigned long mask = PAGE_MASK;
        for (i = 0; i < NR_MEM_LISTS; i++) {
            unsigned long bitmap_size;
            init_mem_queue(free_area[j] + i);	// 初始链表
            mask += mask;
            end_mem = (end_mem + ~mask) & mask;
            bitmap_size = (end_mem - PAGE_OFFSET) >> (PAGE_SHIFT + i);      // 计算位图所需大小
            bitmap_size = (bitmap_size + 7) >> 3;
            bitmap_size = LONG_ALIGN(bitmap_size);
            free_area[j][i].map = (unsigned int *)start_mem;        // 位图地址
            memset((void *)start_mem, 0, bitmap_size);		// 位图清 0
            start_mem += bitmap_size;
        }
    }
    return start_mem;
}

分析上述位图创建过程,可知结构如下:

bit_map |--0--|...|--n--|
		   |
		   |
		   |--0--|...|--n--|
			  |
			  |

上述接口涉及的结构体和变量如下:

// 页结构体
typedef struct page {
	struct page *next;
	struct page *prev;
	struct inode *inode;
	unsigned long offset;
	struct page *next_hash;
	atomic_t count;
	unsigned long flags;
	struct wait_queue *wait;
	struct page **pprev_hash;
	struct buffer_head * buffers;
} mem_map_t;

// 全局定义页
mem_map_t * mem_map = NULL;

// 空闲区域结构体
struct free_area_struct {
    struct page *next;      // 空闲区域中的页链表
    struct page *prev;
    unsigned int *map;
    unsigned long count;
};

// 全局定义的空闲区域
static struct free_area_struct free_area[NR_MEM_TYPES][NR_MEM_LIST];

申请地址空间时,内核将根据 free_area 和 mem_map 数组来决定,内核提供了宏 RMQUEUE_TYPE 作为内存分配的最终执行者。

#define RMQUEUE_TYPE(order, type) \

// 通过下行代码可知,内核将空闲区域划分后,区域内的每个页又会按照 2^n 次方划分,并将指数级一致的页归类到一个链表中
do { struct free_area_struct * area = free_area[type] + order; \	
     unsigned long new_order = order; \
	do { struct page *prev = memory_head(area), *ret = prev->next; \
		if (memory_head(area) != ret) { \		// 判断当前指数级中是否存在空闲页
			unsigned long map_nr; \
			(prev->next = ret->next)->prev = prev; \		// 取走满足的空闲页,并在空闲链表中删除
			map_nr = ret - mem_map; \		// 计算页号
			MARK_USED(map_nr, new_order, area); \
			nr_free_pages -= 1 << order; \
			area->count--; \
			EXPAND(ret, map_nr, order, new_order, area); \
			spin_unlock_irqrestore(&page_alloc_lock, flags); \
			return ADDRESS(map_nr); \		// 返回页所指向的地址
		} \
		new_order++; area++; \
	} while (new_order < NR_MEM_LISTS); \
} while (0)

上述宏定义在 kmem_cache 结构被应用于内存分配接口中。

关于上述中的 free_are_init 接口则是在 paging_init 接口中被调用,不同的架构实现的页初始化不同,以 i386 为例。

__initfunc(unsigned long paging_init(unsigned long start_mem, unsigned long end_mem))
{
    pgd_t *pg_dir;      // 页目录
    pte_t *pg_table;    // 页表
    unsigned long tmp;
    unsigned long address;
    
    start_mem = PAGE_ALIGN(start_mem);  // 页对齐: return start_mem & ~(PAGE_SIZE - 1);
    address = PAGE_OFFSET;		// PAGE_OFFSET 代表页管理的起始位置
    pg_dir = swapper_pg_dir;    // 页目录入口地址
    
    pgd_val(pg_dir[0]) = 0;
    
    // #define USER_PGD_PTRS (PAGE_OFFSET >> PGDIR_SHIFT)
    // 页表的应用从 start_mem 位置开始,因此页目录的起始填充位置需要改变为 start_mem 对应的
    pgd_dir += USER_PGD_PTRS;   
    while (address < end_mem) {		// 页起始位置小于页结束位置
        
        ...
        
        pg_table = (pte_t *) (PAGE_MASK & pgd_val(*pgd_dir));   // 查看页目录中是否已存在页表地址
        if (!pg_table) {    // 当页目录中不存在页表信息时,将占用 start_mem 起始的部分空间来作为 pte 信息的存储空间
            pg_table = (pte_t *) __pa(start_mem);
            start_mem += PAGE_SIZE;
        }
        
        pgd_val(*pg_dir) = _PAGE_TABLE | (unsigned long) pg_table;  // 页目录中填充页表地址
        pg_dir++;
        
        pg_table = (pte_t *) __va(pg_table);
        for (tmp = 0; tmp < PTRS_PER_PTE; tmp++, pg_table++) {  // 循环填满一张页表
            pte_t pte = mk_pte(address, PAGE_KERNEL);   // 创建 pte,主要记录页地址和页属性
            if (address >= end_mem)
                pte_val(pte) = 0;
            set_pte(pg_table, pte);     // pte 填充 pg_table 
            address += PAGE_SIZE;
        }
    }
    start_mem = fixmap_init(start_mem);
#ifdef __SMP__
    start_mem = init_smp_mapping(start_mem);
#endif
    local_flush_tlb();      // 刷新页表(i386 主要修改 cr3 来改变页目录地址),其他架构可能存在其他方式
    
    return free_area_init(start_mem, end_mem);      // 开始构建空闲区域结构
}

根据上述分析可知 i386 内核页管理模式所占用的空间分布如下:

swapper_pg_dir
|------------|     start_memroy
| pgd        |---->|----------|
|------------|     | pte      |
				   |----------|

此外,关于代码中涉及的物理地址和虚拟地址的转换,分析接口 __pa / __va 可知 i386 的映射关系如下:

			虚拟地址空间            物理地址空间
            |------------|
kernel_base | 0xc0000000 | ---|
            |------------|    |     |          |
            |            |    |     |----------|
                              |---> | 0x0      |
                                    |----------|

综上,便是 free_area 的入口调用分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值