lab1
从BIOS到启动
When the BIOS runs, it sets up an interrupt descriptor table and initializes various devices such as the VGA display. This is where the “Starting SeaBIOS” message you see in the QEMU window comes from.
当 BIOS 运行时,它建立一个中断描述符表并初始化各种设备,如 VGA 显示器。这就是 QEMU 窗口中看到的“ Starting SeaBIOS”消息的来源。
After initializing the PCI bus and all the important devices the BIOS knows about, it searches for a bootable device such as a floppy, hard drive, or CD-ROM. Eventually, when it finds a bootable disk, the BIOS reads the boot loader from the disk and transfers control to it.
初始化 PCI 总线和 BIOS 所知道的所有重要设备之后,它将搜索可引导设备,如软盘、硬盘驱动器或 CD-ROM。最终,当它找到一个可引导磁盘时,BIOS 从磁盘读取引导加载程序并将控制权转移给它。
Floppy and hard disks for PCs are divided into 512 byte regions called sectors. A sector is the disk’s minimum transfer granularity: each read or write operation must be one or more sectors in size and aligned on a sector boundary. If the disk is bootable, the first sector is called the boot sector, since this is where the boot loader code resides. When the BIOS finds a bootable floppy or hard disk, it loads the 512-byte boot sector into memory at physical addresses 0x7c00 through 0x7dff, and then uses a jmp instruction to set the CS:IP to 0000:7c00, passing control to the boot loader. Like the BIOS load address, these addresses are fairly arbitrary - but they are fixed and standardized for PCs.
个人电脑的软盘和硬盘被分成512字节的区域,称为扇区。扇区是磁盘的最小传输粒度: 每个读或写操作的大小必须是一个或多个扇区,并与扇区边界对齐。如果磁盘是可引导的,则第一个扇区称为引导扇区,因为这是引导加载程序代码所在的位置。当 BIOS 找到可引导的软盘或硬盘时,它将512字节的引导扇区加载到物理地址0x7c00到0x7dff 的内存中,然后使用 jmp 指令将 CS: IP 设置为0000:7 c00,将控制权传递给引导加载程序。就像 BIOS 加载地址一样,这些地址是相当随意的,但是它们是固定的,并且是为 pc 标准化的。
简单来说就是BIOS检索可引导的设备, 然后将磁盘中的加载程序读取到内存中并将控制权交给控制程序。
Be able to answer the following questions:
- At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?
- What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?
- Where is the first instruction of the kernel?
- How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk? Where does it find this information?
1 | [f000:d190] 0xfd190: ljmpl $0x8,$0xfd198 这条指令跳转导致16位转到32位模式 |
Boot Loader
启动时候从扇区读取512字节,然后加载到内存的0x7c00地址, 先运行了boot.S中的内容,在boot.S中调用了boot_main1
2
3
4gdb中symbols-file obj/boot/main.o 来加载符号表
在boot.asm中查看对应的地址然后下断点
加载符号表之后可以查看ELFHDR
p *((struct Elf *) 0x10000)
1 | objdump -x 查看所有headers 包括符号 |
backtrace
有ebp直接每次ebp = *ebp回溯直到ebp为01
2
3
4
5
6
7
8
9
10
11
12
13
14int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
cprintf("Stack backtrace:\n");
uint32_t *ebp = (uint32_t *)read_ebp();
while (ebp) {
cprintf(" ebp %08x eip %08x args %08x %08x %08x %08x %08x\n",
ebp, *(ebp + 1), *(ebp + 2), *(ebp + 3), *(ebp + 4), *(ebp + 5), *(ebp + 6));
ebp = (uint32_t*)*ebp;
}
return 0;
}
增加函数名、行号等, 提示中显示用stab和stabstr节来实现.
.stab 节:符号表部分,这一部分的功能是程序报错时可以提供错误信息。
.stabstr 节:符号表字符串部分。
这两个节在kernel.ld中链接进去,并且为符号提供了地址1
2
3
4extern const struct Stab __STAB_BEGIN__[]; // Beginning of stabs table
extern const struct Stab __STAB_END__[]; // End of stabs table
extern const char __STABSTR_BEGIN__[]; // Beginning of string table
extern const char __STABSTR_END__[]; // End of string table
.stab节信息的读取1
2
3
4
5
6
7
8
9objdump -G obj/kern/kernel
Symnum n_type n_othr n_desc n_value n_strx String
118 FUN 0 0 f01000a6 2987 i386_init:F(0,25)
119 SLINE 0 24 00000000 0
120 SLINE 0 34 00000012 0
121 SLINE 0 36 00000017 0
122 SLINE 0 39 0000002b 0
123 SLINE 0 43 0000003a 0
- Symnum是符号索引,换句话说,整个符号表看作一个数组,Symnum是当前符号在数组中的下标
- n_type是符号类型,FUN指函数名,SLINE指在text段中的行号
- n_othr目前没被使用,其值固定为0
- n_desc表示在文件中的行号
- n_value表示地址。特别要注意的是,这里只有FUN类型的符号的地址是绝对地址,SLINE符号的地址是偏移量,其实际地址为函数入口地址加上偏移量。比如第3行的含义是地址f01000b8(=0xf01000a6+0x00000012)对应文件第34行。
在kdebug.c中增加如下代码1
2
3stab_binsearch(stabs, &lline, &rline, N_SLINE, addr);
if (lline <= rline)
info -> eip_line = stabs[lline].n_desc;
monitor.c1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
cprintf("Stack backtrace:\n");
uint32_t *ebp = (uint32_t *)read_ebp();
struct Eipdebuginfo info;
int result;
while (ebp) {
cprintf(" ebp %08x eip %08x args %08x %08x %08x %08x %08x\n",
ebp, *(ebp + 1), *(ebp + 2), *(ebp + 3), *(ebp + 4), *(ebp + 5), *(ebp + 6));
memset(&info, 0, sizeof(struct Eipdebuginfo));
result = debuginfo_eip(*(ebp + 1), &info);
if (result != 0) {
cprintf("failed to get debuginfo for eip %x.\r\n", ebp[1]);
}
else {
cprintf("\t%s:%d: %.*s+%u\r\n",
info.eip_file, info.eip_line, info.eip_fn_namelen, info.eip_fn_name, *(ebp + 1) - info.eip_fn_addr);
}
ebp = (uint32_t*)*ebp;
}
return 0;
}
lab2
1 | +------------------+ <- 0xFFFFFFFF (4GB) |
在JOS中, 0x00000000 -> 0x000a0000被称作basemem
0x000a00000 -> 0x00100000是IO hole
从0x00100000开始是extended mem
Exercise 1
boot_alloc
1 | // This simple physical memory allocator is used only while JOS is setting |
在boot_alloc中用到了end, end的定义在kernel.ld中1
2
3
4
5
6.bss : {
PROVIDE(edata = .);
*(.bss)
PROVIDE(end = .);
BYTE(0)
}
end为bss段最后一个地址
根据JOS的内存结构,从此处开始的内存为extended memory
boot_alloc实现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
29static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result = NULL;
extern char end[];
uint32_t size;
// Initialize nextfree if this is the first time.
// 'end' is a magic symbol automatically generated by the linker,
// which points to the end of the kernel's bss segment:
// the first virtual address that the linker did *not* assign
// to any kernel code or global variables.
if (!nextfree) {
nextfree = ROUNDUP((char *) end, PGSIZE);
}
if (n == 0)
return nextfree;
// Allocate a chunk large enough to hold 'n' bytes, then update
// nextfree. Make sure nextfree is kept aligned
// to a multiple of PGSIZE.
//
// LAB 2: Your code here.
size = ROUNDUP(n, PGSIZE);
if (PGNUM(nextfree - end + size) >= npages)
panic("boot_alloc: No enough mem!\n");
result = nextfree;
nextfree += size;
return result;
}
mem_init
1 | // Set up a two-level page table: |
1 | // Permissions: kernel R, user R |
page_init
第一个页面是始终被占用的, IO hole是被占用的, 我们刚才申请的页面也不应该被放入到page_free_list中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
37void
page_init(void)
{
// The example code here marks all physical pages as free.
// However this is not truly the case. What memory is free?
// 1) Mark physical page 0 as in use.
// This way we preserve the real-mode IDT and BIOS structures
// in case we ever need them. (Currently we don't, but...)
// 2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
// is free.
// 3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must
// never be allocated.
// 4) Then extended memory [EXTPHYSMEM, ...).
// Some of it is in use, some is free. Where is the kernel
// in physical memory? Which pages are already in use for
// page tables and other data structures?
//
// Change the code to reflect this.
// NB: DO NOT actually touch the physical memory corresponding to
// free pages!
size_t i;
pages[0].pp_ref = 1;
pages[0].pp_link = NULL;
page_free_list = NULL;
void* va = boot_alloc(0);
for (i = 1; i < npages; i++) {
if (i >= npages_basemem && page2kva(&pages[i]) < va) {
pages[i].pp_ref = 1;
pages[i].pp_link = NULL;
}
else {
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
}
}
page_alloc
1 | // |
需要注意memset的是对应的物理内存页.
page_free
1 | // |
Exercise 2
pgdir_walk
1 | pte_t * |
boot_map_region
1 | static void |
page_insert
pp -> pp_ref需要先加然后再page_remove, 这样就不会因为pp_ref被减为0然后原来va处的page被删除1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19int
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
{
// Fill this function in
pte_t *pte = pgdir_walk(pgdir, va, 1);
if (pte == NULL)
return -E_NO_MEM;
pp -> pp_ref ++;
if (*pte & PTE_P) {
tlb_invalidate(pgdir, va);
page_remove(pgdir, va);
}
*pte = page2pa(pp) | perm | PTE_P;
// pgdir[PDX(va)] = (pde_t)pte;
return 0;
}
page_lookup
1 | struct PageInfo * |
page_remove
1 | void |
Exercise 5
1 | ////////////////////////////////////////////////////////////////////// |
TODO
lab2 的buddy系统没有加
lab3
对gcc高版本的ld会出现问题
需要更改kernel.ld1
2
3
4
5
6
7.bss : {
PROVIDE(edata = .);
*(.bss)
*(COMMON)
PROVIDE(end = .);
BYTE(0)
}
出现kva:00000000的原因是因为ld提供的end只是bss段的, 而pmap.o中的kern_pgdir并没有被算作bss段, 而且kern_pgdir的地址是在end之后的, 在memset的时候被清空了, 所以才会出现kva:00000000的问题.
修改后就可正常运行了.
Exercise 1
mem_init
1 | // Make 'envs' point to an array of size 'NENV' of 'struct Env'. |
Exercise 2
env_init
1 | void |
env_setup_vm
1 | static int |
此处将kern_pgdir里UTOP之上的所有页都映射到了user的地址空间中,在这里如果将NPEDENTRIES则在下面lcr3(PADDR(e -> env_pgdir))之后会发生错误, 但是感觉现在内核中不是这样映射的
region_alloc
1 | static void |
load_icode
1 | static void |
这里ph -> p_va已经对应到用户进程里的页表了, lcr3()使用user的页表.
env_create
1 | void |
env_run
1 | void |
TODO
现代内核中 内核页表和用户空间页表有哪些项是相同的.
env_run中 curenv -> env_status 还有可能是什么值?