• Linux 之 start_kernel() 下的 setup_arch()


    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    运行结果:

    在这里插入图片描述

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    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];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    此函数:

    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];
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    显示结果:和 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];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    所以 chgidx值 是 段数量 old_nr 的 2 倍。

    2. 接下来,将按内存地址排序 change-point list(低 -> 高)排序
    排序的规则:
    /* if > , swap /
    /
    如果 当前地址小于上一个地址 /
    /
    or, if current= & last=, swap /
    /
    或是 当前的地址(起始地址) == 上一个地址(结束地址) */

    	/* if  > , swap */
    	/* 如果 当前地址小于上一个地址 */
    	/* or, if current= & last=, 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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    第一个还能理解。
    第二个 为什么( 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
    
    • 1
    • 2
    • 3
    • 4

    很明显,这里的第二行是和第四行同属于一对,第一行和第三行是一对。由于之前的排队 第二行和第三行进行了交换。
    这样一看,第一行就和第四行变成一对了。
    第一个地址和第四个地址把他俩其中的地址变成了一块 (括号栈平衡) 。

    这样划分得到了 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);
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    在这里插入图片描述
    结合代码:

    	  //查找范围: 目前加入 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--;
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    接下来:
    整个 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;
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    而 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;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    最后的排序结果:

    在这里插入图片描述

    这里我明天会用一个视频作为动态讲解,把链接发在这里。

    【Linux2.4 初始化模块2 之 sanitize_e820_map 函数】
    后面待续。。。。。。

  • 相关阅读:
    MySQL 事务的简单理解
    05 | 基础篇:某个应用的CPU使用率居然达到100%,我该怎么办?笔记
    linux内核发包工具pktgen
    OpenCV Series : TI - DSP - CCS
    基于SSM框架的安全教育平台
    "万字" Java I/O 详解
    常见位运算公式使用场景
    解决因国内各大镜像站关停导致无法下载镜像的问题
    【调研】在线考试系统调研
    转载-Troubleshooting .NET Blazor WASM Debugging
  • 原文地址:https://blog.csdn.net/orange1710/article/details/133747708