linux kernelのsetupの部分を読んでいて、E820
という謎の数字?が気になったのと、ここで得られたデータをboot時に後々利用しているので、少し重点的に調べてみる。
reference
http://www.uruk.org/orig-grub/mem64mb.html あるいは、BIOSのdocument: https://www.zfmicro.com/library/manuals/PhoenixBIOS4_rev6UserMan.pdf
linux versionは前回の記事と同様にlinux2.6.11で。
http://www.acpi.info/ ACPI 5.0のch.15 System Address Map Interfaces
Understanding the Linux Kernel : Appendix A System Startup
E820 とは?
E820とは、BIOSの命令から来ていて、RAMのmemory mapの状態を返す。
real modeの際にBIOSに INT 15h, AX=E820h
を投げると、physical addressのsizeやら状態(reserveかそうでないか?)やらが結果として返ってくる。
arch/i386/boot/setup.S
実際に最初から関連のコードを追っていく。
# https://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux-stable/+/refs/heads/linux-2.6.11.y/arch/i386/boot/setup.S#311 meme820: xorl %ebx, %ebx # continuation counter movw $E820MAP, %di # point into the whitelist(#define E820MAP 0x2d0) jmpe820: movl $0x0000e820, %eax # e820, upper word zeroed movl $SMAP, %edx # ascii 'SMAP' movl $20, %ecx # size of the e820rec pushw %ds # data record. popw %es int $0x15 # make the call
http://www.uruk.org/orig-grub/mem64mb.html の通りにレジスタに値を設定して、BIOSにinterruptを投げると、ES:DI Buffer Pointer
で指定したaddress(E820MAP
: 0x2d0)に20byte分(%ecx)以下のデータが入る:
Offset in Bytes Name Description 0 BaseAddrLow Low 32 Bits of Base Address 4 BaseAddrHigh High 32 Bits of Base Address 8 LengthLow Low 32 Bits of Length in Bytes 12 LengthHigh High 32 Bits of Length in Bytes 16 Type Address type of this range.
このデータはおおよそ、以下のような雰囲気のデータ1である:
BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved BIOS-e820: [mem 0x0000000000100000-0x00000000dffeffff] usable BIOS-e820: [mem 0x00000000dfff0000-0x00000000dfffffff] ACPI data BIOS-e820: [mem 0x00000000fec00000-0x00000000fec00fff] reserved BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved
typeの取りうる値は、以下の通り:
// include/asm-i386/e820.h #define E820_RAM 1 // usable #define E820_RESERVED 2 // reserved #define E820_ACPI 3 /* usable as RAM once ACPI tables have been read */ #define E820_NVS 4
mapが複数得られているが、setup.Sの方では、以下のようにループを回している:
# add %di register to loop meme820 movw %di, %ax addw $20, %ax movw %ax, %di again820: cmpl $0, %ebx # check to see if (A return value of zero means that this is the last descriptor.) jne jmpe820 # %ebx is set to EOF, goto next
ES:DI Buffer Pointer
で今、20byte分のデータが入ったから、di+=20とincrementしているのが上記の3行。下2行はEBX Continuation
とあるので、走査が終了しているかそうでないかの判別値のようだ。0であれば、終了するので、次に進む。0でなければ、jmpe820
に戻り再度int $0x15
を実行する。
linuxでは、legacy な機種にも対応するために、INT 15h, AX=E820h
に引き続いて、INT 15h, AX=E801h
とINT 15h, AH=88h
も実行する。
2つめの命令は、0x1e0
2に、3つめは、2
3に格納される(つまり、e820で格納したaddressとは違う場所)。この値は、include/asm-i386/setup.hにて、
// #define PARAM (boot_params) // unsigned char __initdata boot_params[PARAM_SIZE]; // #define PARAM_SIZE 2048 #define EXT_MEM_K (*(unsigned short *) (PARAM+2)) #define ALT_MEM_K (*(unsigned long *) (PARAM+0x1e0))
のようにaliasが設定されている4。
その後、setup.Sは最終的に、
# almost same as `jmpi 0x100000, __BOOT_CS` code32: .long 0x1000 # will be set to 0x100000 # for big kernels .word __BOOT_CS
となり、startup_32
関数にjmpする。setup.S自体の全体のフローはUnderstanding the linux kernel Appendix AのMiddle Ages: the setup( ) Function
にある。その後、head.S -> init/main.cのstart_kernel()を実行していく。
init/main.c
machine_specific_memory_setup
E820が関わっている場所は、start_kernel -> setup_arch -> machine_specific_memory_setup の部分:
static char * __init machine_specific_memory_setup(void) { char *who; who = "BIOS-e820"; // E820_MAP .. the address of the data result from `INT 15h, AX=E820h` // E820_MAP_NR .. the number of entries sanitize_e820_map(E820_MAP, &E820_MAP_NR); if (copy_e820_map(E820_MAP, E820_MAP_NR) < 0) { unsigned long mem_size; if (ALT_MEM_K < EXT_MEM_K) { mem_size = EXT_MEM_K; who = "BIOS-88"; } else { mem_size = ALT_MEM_K; who = "BIOS-e801"; } e820.nr_map = 0; // conservative default setup add_memory_region(0, LOWMEMSIZE(), E820_RAM); add_memory_region(HIGH_MEMORY, mem_size << 10, E820_RAM); } return who; }
前章のsetup.Sを踏まえた上でざっくり流れを追うと、
- BIOSから得られたmemory mappingが重複している場合があるので、もれなくダブりなく再構成する。[sanitize_e820_map]
struct e820map e820;
に1で再構成したデータを移動させる。low memoryに1のデータが有るため、高位のaddressに移動させたいという理由から。[copy_e820_map]- 2でうまく行かなかった場合は、legacyな
INT 15h, AX=E801h
かINT 15h, AH=88h
で得られた結果を利用する。(if文の中身) 2が問題なければ文字列"BIOS-e820"を返す。
という感じ。3番は背景だけ知っておけばそれでよいのでは?と思う。
copy_e820_mapは、その中のadd_memory_region
にて、
// static void __init add_memory_region(unsigned long long start, unsigned long long size, int type) // struct e820map e820; e820.map[x].addr = start; e820.map[x].size = size; e820.map[x].type = type; e820.nr_map++;
のように指定されている。
このstruct e820map e820
はこの後のsetup_memory
の内部でpage frameの終端(max_pfn
)を決める際に必要で、この変数は色んな場所に登場していくことになる。
-
これは、start_kernel -> setup_arch -> print_memory_mapでlogに出力されるものをとってきた。実際には、
machine_specific_memory_setup
にて、得られたデータを整形しているが、後述する。↩ -
movl %edx, (0x1e0)
,addl %ecx, (0x1e0)
の部分。↩ -
movw %ax, (2) // INT 15h, AH=88h - Get Extended Memory Size
の部分↩ -
__initdata
は#define __initdata __attribute__ ((__section__ (".init.data")))
と設定されており、これは、リンカスクリプト(arch/i386/kernel/vmlinux.lds.S)を覗いてみると、.init.data
のラベルが存在するはずだ!↩