MIT 6.828 学习笔记2 阅读main.c

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/scnu20142005027/article/details/51150601
#include <inc/x86.h>
#include <inc/elf.h>

/**********************************************************************
 * This a dirt simple boot loader, whose sole job is to boot		// 一个简单的 Bootloader,用于读取内核
 * an ELF kernel image from the first IDE hard disk.
 *
 * DISK LAYOUT
 *  * This program(boot.S and main.c) is the bootloader.  It should	// Bootloader 储存在第一个扇区
 *    be stored in the first sector of the disk.
 *
 *  * The 2nd sector onward holds the kernel image.		// 第二个扇区开始储存内核
 *
 *  * The kernel image must be in ELF format.		// 内核必须为 ELF 格式
 *
 * BOOT UP STEPS
 *  * when the CPU boots it loads the BIOS into memory and executes it
 *
 *  * the BIOS intializes devices, sets of the interrupt routines, and
 *    reads the first sector of the boot device(e.g., hard-drive)
 *    into memory and jumps to it.
 *
 *  * Assuming this boot loader is stored in the first sector of the
 *    hard-drive, this code takes over...
 *
 *  * control starts in boot.S -- which sets up protected mode,
 *    and a stack so C code then run, then calls bootmain()
 *
 *  * bootmain() in this file takes over, reads in the kernel and jumps to it.		// bootmain 函数读取并跳转到内核
 **********************************************************************/

#define SECTSIZE	512	// 扇区大小 512bytes
#define ELFHDR		((struct Elf *) 0x10000) // Makefrag 文件中设置内核起始地址为 0x10000


void bootmain(void)
{
	struct Proghdr *ph, *eph;

	// read 1st page off disk
	readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);	// 读取内核的前 2048bytes 到内存

	// is this a valid ELF?
	if (ELFHDR->e_magic != ELF_MAGIC)	// 判断 magic number
		goto bad;

	// load each program segment (ignores ph flags)
	ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);	// 指向 program header table 头部
	eph = ph + ELFHDR->e_phnum;	// 指向 program header table 尾部
	for (; ph < eph; ph++)
		// p_pa is the load address of this segment (as well
		// as the physical address)
		readseg(ph->p_pa, ph->p_memsz, ph->p_offset);	//逐段读入内存

	// call the entry point from the ELF header
	// note: does not return!
	((void (*)(void)) (ELFHDR->e_entry))();	// 调用内核程序

bad:
	outw(0x8A00, 0x8A00);
	outw(0x8A00, 0x8E00);
	while (1)
		/* do nothing */;
}

#define ELF_MAGIC 0x464C457FU	/* "\x7FELF" in little endian */	// elf.h 部分内容

struct Elf {	// ELF header
	uint32_t e_magic;	// must equal ELF_MAGIC
	uint8_t e_elf[12];	// magic number 的相关信息
	uint16_t e_type;	// 文件类型,1 = 可重定位的,2 = 可执行的,3 = 共享的,4 = 核心的
	uint16_t e_machine;	// 机器的指令集结构,例如 0x03 代表 x86,0x08 代表 MIPS 等
	uint32_t e_version;	// ELF 文件版本
	uint32_t e_entry;	// 程序入口的地址
	uint32_t e_phoff;	// program header table 的偏移地址
	uint32_t e_shoff;	// section header table 的偏移地址
	uint32_t e_flags;	// 与机器的架构相关的值
	uint16_t e_ehsize;	// ELF header 大小
	uint16_t e_phentsize;	// program header table 条目大小
	uint16_t e_phnum;<	// program header table 条目数量
	uint16_t e_shentsize;	// section header table 条目大小
	uint16_t e_shnum;<	// section header table 条目数量
	uint16_t e_shstrndx;	// 含有 section 名称的条目索引
};

struct Proghdr {	// program header table 程序头表
	uint32_t p_type;	// program header 类型
	uint32_t p_offset;	// 相对于文件的偏移地址
	uint32_t p_va;<	// 虚拟地址
	uint32_t p_pa;<	// 物理地址
	uint32_t p_filesz;	// 在文件中的大小
	uint32_t p_memsz;	// 在内存中的大小
	uint32_t p_flags;	// 相关的标志
	uint32_t p_align;	// 对齐方式
};

下图为 ELF 文件结构


第 9 行,判断 magic number,magic number 可以理解为识别文件类型的一段标识,在这里就是 0x7f454c46


// Read 'count' bytes at 'offset' from kernel into physical address 'pa'.
// Might copy more than asked
void readseg(uint32_t pa, uint32_t count, uint32_t offset)
{
	uint32_t end_pa;

	end_pa = pa + count;	//段尾部地址

	// round down to sector boundary
	pa &= ~(SECTSIZE - 1);		// 对齐扇区

	// translate from bytes to sectors, and kernel starts at sector 1
	offset = (offset / SECTSIZE) + 1;	// 可以理解为第几块扇区,这个变量即扇区号

	// If this is too slow, we could read lots of sectors at a time.
	// We'd write more to memory than asked, but it doesn't matter --
	// we load in increasing order.
	while (pa < end_pa) {	// 逐个扇区读入
		// Since we haven't enabled paging yet and we're using
		// an identity segment mapping (see boot.S), we can
		// use physical addresses directly.  This won't be the
		// case once JOS enables the MMU.
		readsect((uint8_t*) pa, offset);
		pa += SECTSIZE;
		offset++;
	}
}
可以利用 objdump 命令来查看内核中的 program header 条目,在我的系统中显示如下:

hiroshi@Hiroshi-PC:~/6.828/lab/obj/kern$ objdump -p kernel

kernel:     文件格式 elf32-i386

程序头:
    LOAD off    0x00001000 vaddr 0xf0100000 paddr 0x00100000 align 2**12
         filesz 0x0000712f memsz 0x0000712f flags r-x
    LOAD off    0x00009000 vaddr 0xf0108000 paddr 0x00108000 align 2**12
         filesz 0x0000a300 memsz 0x0000a944 flags rw-
   STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4
         filesz 0x00000000 memsz 0x00000000 flags rwx
可以看到,第一段与第二段都是 4KB 对齐的,也就是占了 8 个扇区,而这两段也就是上面结构图中两个大括号表示的那两段

还可以发现,第二段的 filesz 与 memsz 是不相同的,这是因为 .bss 节中的全局变量在文件中不占空间,在内存中才分配空间并初始化

如果不明白 readseg 的,可以用其中一个段模拟一遍这个函数


void waitdisk(void)
{
	// wait for disk reaady
	while ((inb(0x1F7) & 0xC0) != 0x40)
		/* do nothing */;
}
void readsect(void *dst, uint32_t offset)
{
	// wait for disk to be ready
	waitdisk();

	outb(0x1F2, 1);		// count = 1
	outb(0x1F3, offset);
	outb(0x1F4, offset >> 8);
	outb(0x1F5, offset >> 16);
	outb(0x1F6, (offset >> 24) | 0xE0);
	outb(0x1F7, 0x20);	// cmd 0x20 - read sectors

	// wait for disk to be ready
	waitdisk();

	// read a sector
	insl(0x1F0, dst, SECTSIZE/4);
}
这两个函数中,比较令人疑惑的是 0x1F2 到 0X1F7 代表什么,还有为什么要写入 offset

我们先来看看下面一些关于 ATA 和 LBA 的资料(表格只列出相关内容,这里的寻址方式应该是 28-bit LBA)

Command Block Register
CS1FX- CS3FX- DA2 DA1 DA0 Address READ (DIOR-)  WRITE (DIOW-)
1 0 0 0 0 0x1F0 Data Data
1 0 0 0 1 0x1F1 Error register Features
1 0 0 1 0 0x1F2 Sector count Sector count
1 0 0 1 1 0x1F3 Sector number Sector number
1 0 1 0 0 0x1F4 Cylinder low Cylinder low
1 0 1 0 1 0x1F5 Cylinder high Cylinder high
1 0 1 1 0 0x1F6 Drive/head Drive/head
1 0 1 1 1 0x1F7 Status Command

LBA
Register D7 D6 D5 D4 D3 D2 D1 D0
0x1F3 LBA7 LBA6 LBA5 LBA4 LBA3 LBA2 LBA1 LBA0
0x1F4 LBA15 LBA14 LBA13 LBA12 LBA11 LBA10 LBA9 LBA8
0x1F5 LBA23 LBA22 LBA21 LBA20 LBA19 LBA18 LBA17 LBA16
0x1F6 1 LBA 1 DRV LBA27 LBA26 LBA25 LBA24

第一个表中前面的五列为引脚信号,接着是对应的地址,各地址代表的寄存器意义如下:

0x1F3 R/W,数据寄存器

0x1F2 R/W,扇区数寄存器,记录操作的扇区数

0x1F3 R/W,扇区号寄存器,记录操作的起始扇区号

0x1F4 R/W,柱面号寄存器,记录柱面号的低 8 位

0x1F5 R/W,柱面号寄存器,记录柱面号的高 8 位

0x1F6 R/W,驱动器/磁头寄存器,记录操作的磁头号,驱动器号,和寻道方式,前 4 位代表逻辑扇区号的高 4 位,DRV = 0/1 代表主/从驱动器,LBA = 0/1 代表 CHS/LBA 方式

0x1F7 R,状态寄存器,第 6、7 位分别代表驱动器准备好,驱动器忙

0x1F8 W,命令寄存器,0x20 命令代表读取扇区

接下来看代码,在 waitdisk 函数中,while 条件所代表的意义为读取驱动器状态,利用逻辑 AND 取出第 6、7 位,并与 0x40(01000000) 比较,若相等则代表驱动器准备好

接下来看 readsect 函数

第 6 行,由于每次只读取 1 个扇区,所以向扇区数寄存器写入 1

第 7 、8、9 行,写入扇区号、柱面号信息,注意每个函数写入的大小为 1byte

第 10 行,注意 0xE0 = 11100000,代表主驱动器,LBA 寻址方式

第 11 行,向命令寄存器写入读取扇区的命令

第 17 行,读取扇区,具体代码在 x86.h,这段内嵌的汇编代码我看不懂……不过从注释上看就是读取扇区


参考资料:
http://www.mcufan.com/article/ata-interface.pdf

http://www.t13.org/documents/UploadedDocuments/project/d0791r4c-ATA-1.pdf

http://wiki.osdev.org/ATA_PIO_Mode#28_bit_PIO

https://en.wikipedia.org/wiki/Executable_and_Linkable_Format




猜你喜欢

转载自blog.csdn.net/scnu20142005027/article/details/51150601