2. Linux 之 start_kernel()
2.1 setup_arch()
2.1.1 show_memory_map()
void show_memory_map()
{
uint32_t mmap_addr = ((multiboot_t*)glb_mboot_ptr)->mmap_addr;
uint32_t mmap_length = ((multiboot_t*)glb_mboot_ptr)->mmap_length;
printk("Memory map:\n");
mmap_entry_t *mmap = (mmap_entry_t *)mmap_addr;
for (mmap = (mmap_entry_t *)mmap_addr; (uint32_t)mmap < mmap_addr + mmap_length; mmap++) {
printk("base_addr = 0x%X%08X, length = 0x%X%08X, type = 0x%X\n",
(uint32_t)mmap->base_addr_high, (uint32_t)mmap->base_addr_low,
(uint32_t)mmap->length_high, (uint32_t)mmap->length_low,
(uint32_t)mmap->type);
}
// setup_memory_region();
}
运行结果:
2.1.2 setup_memory_region()
static void setup_memory_region(void)
{
char *who = "BIOS-e820";
init_biosmap();
printk("biosmap.map = %p, biosmap.nr_map = %d\n", biosmap.map, biosmap.nr_map);
sanitize_e820_map(biosmap.map, &biosmap.nr_map);
}
2.1.2.1 init_biosmap();
从 multiboot_t 中获取 addr 、size、type。放到 e820map 结构体中。
//multiboot.h
struct multiboot_t {
uint32_t flags; // Multiboot 的版本信息
/**
* 从 BIOS 获知的可用内存
*
* mem_lower和mem_upper分别指出了低端和高端内存的大小,单位是KB。
* 低端内存的首地址是0,高端内存的首地址是1M。
* 低端内存的最大可能值是640K。
* 高端内存的最大可能值是最大值减去1M。但并不保证是这个值。
*/
uint32_t mem_lower;
uint32_t mem_upper;
uint32_t boot_device; // 指出引导程序从哪个 BIOS 磁盘设备载入的OS映像
uint32_t cmdline; // 内核命令行
uint32_t mods_count; // boot 模块列表
uint32_t mods_addr;
/**
* "flags"字段的 bit-5 设置了
* ELF 格式内核映像的section头表。
* 包括ELF 内核的 section header table 在哪里、每项的大小、一共有几项以及作为名字索引的字符串表。
*/
uint32_t num;
uint32_t size;
uint32_t addr;
uint32_t shndx;
/**
* 以下两项指出保存由BIOS提供的内存分布的缓冲区的地址和长度
* mmap_addr是缓冲区的地址,mmap_length是缓冲区的总大小
* 缓冲区由一个或者多个下面的大小/结构对 mmap_entry_t 组成
*/
uint32_t mmap_length;
uint32_t mmap_addr;
uint32_t drives_length; // 指出第一个驱动器结构的物理地址
uint32_t drives_addr; // 指出第一个驱动器这个结构的大小
uint32_t config_table; // ROM 配置表
uint32_t boot_loader_name; // boot loader 的名字
uint32_t apm_table; // APM 表
uint32_t vbe_control_info;
uint32_t vbe_mode_info;
uint32_t vbe_mode;
uint32_t vbe_interface_seg;
uint32_t vbe_interface_off;
uint32_t vbe_interface_len;
} __attribute__((packed)) multiboot_t;
struct e820map {
int nr_map; //段数量
struct e820entry {
unsigned long long addr; // start of memory segment
unsigned long long size; // size of memory segment
unsigned long type; // type of memory segment
} map[E820MAX];
};
2.1.2.2 sanitize_e820_map(biosmap.map, &biosmap.nr_map);
其他几个结构体:
//e820.h
struct e820entry {
__u64 addr; // start of memory segment
__u64 size; // size of memory segment
__u32 type; // type of memory segment
} __attribute__((packed));
//sanitize_e820_map 函数内结构体
struct change_member
{
struct e820entry *pbios;
unsigned long long addr;
};
此函数:
1. 首先:把创建的 change_point_list[i] 地址 交给 change_point[i]。
struct change_member change_point_list[2 * E820MAX];
struct change_member *change_point[2 * E820MAX];
for(i = 0; i < 2 * old_nr; i++) {
change_point[i] = &change_point_list[i];
}
change_point [ 0 ]->add 放第一个 段的起始地址
change_point [ 1 ]->add 放第一个 段的末尾地址-
change_point [ 0 ]->pbios / change_point [ 1 ]->pbios 存放的是同一个biosmap [ i ] 地址
chgidx = 0;
for(i = 0; i < old_nr; i++){
if(biosmap[i].size != 0){
// 记录起始地址
change_point[chgidx]->addr = biosmap[i].addr;
change_point[chgidx++]->pbios = &biosmap[i];
printk("%d # 0x%08X ",i,biosmap[i].addr);
printk("+ 0x%08X ",biosmap[i].size);
// 记录结束地址
change_point[chgidx]->addr = biosmap[i].addr + biosmap[i].size;
change_point[chgidx++]->pbios = &biosmap[i];
printk("= 0x%08X ",biosmap[i].addr + biosmap[i].size);
printk("address: 0x%08X\n",&biosmap[i]);
}
}
chg_nr = chgidx;
显示结果:和 memory_map 中的一致。
我这里是6个段,因为分别将起点 add 和终点 end 放到两个 change_point 中:
所以代码也是这样的:
// 记录起始地址
change_point[chgidx]->addr = biosmap[i].addr;
change_point[chgidx++]->pbios = &biosmap[i];
// 记录结束地址
change_point[chgidx]->addr = biosmap[i].addr + biosmap[i].size;
change_point[chgidx++]->pbios = &biosmap[i];
所以 chgidx值 是 段数量 old_nr 的 2 倍。
2. 接下来,将按内存地址排序 change-point list(低 -> 高)排序
排序的规则:
/* if <current_addr> > <last_addr>, swap /
/ 如果 当前地址小于上一个地址 /
/ or, if current=<start_addr> & last=<end_addr>, swap /
/ 或是 当前的地址(起始地址) == 上一个地址(结束地址) */
/* if <current_addr> > <last_addr>, swap */
/* 如果 当前地址小于上一个地址 */
/* or, if current=<start_addr> & last=<end_addr>, swap */
/* 或是 当前的地址(起始地址) == 上一个地址(结束地址) */
if( (change_point[i]->addr < change_point[i-1]->addr) || \
((change_point[i]->addr == change_point[i-1]->addr) &&
(change_point[i]->addr == change_point[i]->pbios->addr) \
&& (change_point[i-1]->addr != change_point[i-1]->pbios->addr)))
{
change_tmp = change_point[i];
change_point[i] = change_point[i-1];
change_point[i-1] = change_tmp;
still_changing=1;
}
第一个还能理解。
第二个 为什么( i -1 的 end ) == ( i 的 addr ) 时候要交换?
这里的 change_point[i-1]->addr != change_point[i-1]->pbios->addr 实际就是 end。end (->addr) 因为是 add+size,所以自然和 自己的起始地址 addr (->pbios->addr) 不相等。
我们看一下排序后的结果。
图中用 ^ 表示 addr v 表示 end。
这里可以明显看到
0x00000000 ^
0x0008FC00 ^
0x0008FC00 v
0x000A0000 v
很明显,这里的第二行是和第四行同属于一对,第一行和第三行是一对。由于之前的排队 第二行和第三行进行了交换。
这样一看,第一行就和第四行变成一对了。
第一个地址和第四个地址把他俩其中的地址变成了一块 (括号栈平衡) 。
这样划分得到了 3 块范围。
3. 排好序后,就是去重。
最主要就是这个 for 结构,以及打头的 if — is addr — else — is end — 操作。
is addr 就加入:
is end 的操作是删除,我们看看 OS 是如何操作的。
/* create a new bios memory map, removing overlaps */
/* 创建一个新的 bios 内存映射,去除重叠 */
overlap_entries = 0; // 重叠表中的条目数(number of entries in the overlap table)
new_bios_entry = 0; // 创建新的 BIOS 映射条目的索引(index for creating new bios map entries)
last_type = 0; // start with undefined memory type = 从未定义的内存类型起始处
last_addr = 0; // start with 0 as last starting address = 以 0 开头作为最后一个起始地址
/* loop through change-points, determining affect on the new bios map */
/* 遍历更改点,确定对新的 bios map 的影响 */
for(chgidx = 0; chgidx < chg_nr; chgidx++){
/* keep track of all overlapping bios entries */
/* 跟踪所有重叠的 bios 条目 */
if(change_point[chgidx]->addr == change_point[chgidx]->pbios->addr){
// 是一个 addr
overlap_list[overlap_entries++] = change_point[chgidx]->pbios;
}else{ //是一个 end
/* remove entry from list (order independent, so swap with last) */
/* 从列表中删除条目(顺序无关,因此与最后一个交换)*/
for(i = 0; i < overlap_entries; i++){
if(overlap_list[i] == change_point[chgidx]->pbios){
overlap_list[i] = overlap_list[overlap_entries - 1];
}
overlap_entries--;
}
}
//overlap_entries of number
printk("\n=== overlap_entries: 0x%08X ",overlap_entries);
for(i = 0; i < overlap_entries; i++){
printk("[%02d] : 0x%08X ",i,overlap_list[i]->addr);
}
}
结合代码:
//查找范围: 目前加入 overlap_list 中的
//注意:加入的都是 addr,只有 end 的时候才进行查找。
for(i = 0; i < overlap_entries; i++){
//这比较的是什么:比较 change_point[chgidx]->pbios (当前)的 end 是不是 和 overlap_list[i](addr) 是原一对。
if(overlap_list[i] == change_point[chgidx]->pbios){
//满足条件后,把当前最后一个(overlap_entries -1 )的 addr 替换代替给当前(i)的 addr
overlap_list[i] = overlap_list[overlap_entries - 1];
}
overlap_entries--;
}
接下来:
整个 for 就是一块, 【overlap_entries == 0】 的时候就是 【current_type == 0】 的 时候。不进入 for 循环,current_type 就会是默认的 0 。
current_type = 0;
//整个 for 中是一块, overlap_entries == 0 的时候就是 current_type == 0 的 时候。
for(i = 0; i < overlap_entries; i++){
if(overlap_list[i]->type > current_type){
// 取大的
current_type = overlap_list[i]->type;
}
}
而 current_type == 0 的时候,就是一个新的整块的开始。这里的关系比较动态,我明天会用视频来讲解,并附上链接。
【Linux2.4 初始化模块2 之 sanitize_e820_map 函数】
if(current_type != last_type){
if(last_type != 0){
new_bios[new_bios_entry].size = change_point[chgidx]->addr - last_addr;
/* move forward only if the new size was non-zero */
/* 仅当新大小不为零时才向前移动 */
if(new_bios[new_bios_entry].size != 0){
/* 没有更多空间用于新的 bios 条目, no more space left for new bios entries */
if(++new_bios_entry >= E820MAX){break;}
}
}
if(current_type != 0){
//chgidx 是当前匹配到的 坐标
printk("chgidx = %02d ",chgidx);
printk("change_point[]->addr = 0x%08X\n",change_point[chgidx]->addr);
printk("current_type = %02d\n",current_type);
new_bios[new_bios_entry].addr = change_point[chgidx]->addr;
new_bios[new_bios_entry].type = current_type;
last_addr = change_point[chgidx]->addr;
}
last_type = current_type;
}
最后的排序结果:
这里我明天会用一个视频作为动态讲解,把链接发在这里。
【Linux2.4 初始化模块2 之 sanitize_e820_map 函数】
后面待续。。。。。。