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

#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-DA2DA1DA0AddressREAD (DIOR-) WRITE (DIOW-)
100000x1F0DataData
100010x1F1Error registerFeatures
100100x1F2Sector countSector count
100110x1F3Sector numberSector number
101000x1F4Cylinder lowCylinder low
101010x1F5Cylinder highCylinder high
101100x1F6Drive/headDrive/head
101110x1F7StatusCommand

LBA
RegisterD7D6D5D4D3D2D1D0
0x1F3LBA7LBA6LBA5LBA4LBA3LBA2LBA1LBA0
0x1F4LBA15LBA14LBA13LBA12LBA11LBA10LBA9LBA8
0x1F5LBA23LBA22LBA21LBA20LBA19LBA18LBA17LBA16
0x1F61LBA1DRVLBA27LBA26LBA25LBA24

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

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




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值