最近开始阅读Structure of Interpretation of Computer Science即计算机程序的构造与解释这本书。该书中使用的是Scheme作为Lisp的方言。 在此之前华盛顿大学的《程序设计语言》这门课的PartB中,我使用的Lisp方言是Racket,配合DrRacket作为IDE使用。因为最近一直在配置vim,这一次打算使用vim作为主力开发环境。
Unescaped left brace in regex is illegal here in regex; marked by <-- HERE in m/^\@strong{ <-- HERE (.*)}$/ at /home/guest/qemu/scripts/texi2pod.pl line 320.
make: *** [Makefile:474:qemu.1] 错误 255。
该错误是由于perl版本更新后正则表达式语法的变动造成的,直接修改安装脚本的/home/chenzhihao/qemu/scripts/texi2pod.pl line 320,将{改成 \{即可;
# Start the CPU: switch to 32-bit protected mode, jump into C. # The BIOS loads this code from the first sector of the hard disk into # memory at physical address 0x7c00and starts executing in real mode # with %cs=0 %ip=7c00. # boot.S 主要将CPU切换至32位保护模式,并且跳转进入C代码
# Set up the important data segment registers (DS, ES, SS). 设置重要的段寄存器为0 xorw %ax,%ax # Segment number zero movw %ax,%ds # -> Data Segment movw %ax,%es # -> Extra Segment movw %ax,%ss # -> Stack Segment
# Enable A20: # For backwards compatibility with the earliest PCs, physical # address line 20 is tied low, so that addresses higher than # 1MB wrap around to zero by default. This code undoes this. # 开启A20: # A20的介绍已经给出,不再赘述。 seta20.1: inb $0x64,%al # Wait for not busy 等待缓冲区可用 testb $0x2,%al # Test for bit1 # if bit1 = 1 then buffer is full jnz seta20.1
movb $0xd1,%al # 0xd1 -> port 0x64 outb %al,$0x64 # Prepare to write output port 准备写入输出端口
seta20.2: inb $0x64,%al # Wait for not busy 等待缓冲区可用 testb $0x2,%al jnz seta20.2 # The same as above 同上
# Switch from real to protected mode, using a bootstrap GDT # andsegment translation that makes virtual addresses # identical to their physical addresses, so that the # effective memory map does not change during the switch. lgdt gdtdesc # Load gdt size/base to gdtr 设置全局描述符表 movl %cr0, %eax # Control register 0 # bit0 is protected enable bit # 读取控制寄存器0的值,其Bit0为允许保护模式位 orl $CR0_PE_ON, %eax # Set PE 将允许保护模式位置1 movl %eax, %cr0 # Update Control register 0 设置控制寄存器0
# Jump to next instruction, but in32-bit code segment. # Switches processor into32-bit mode. ljmp $PROT_MODE_CSEG, $protcseg # 通过ljmp指令(跳转至下一条指令)进入保护模式
.code32 # Assemble for 32-bit mode 指导生成32位汇编代码 protcseg: # Set up the protected-mode data segment registers 设置保护模式的数据段寄存器 movw $PROT_MODE_DSEG, %ax # Our data segment selector movw %ax, %ds # -> DS: Data Segment movw %ax, %es # -> ES: Extra Segment movw %ax, %fs # -> FS movw %ax, %gs # -> GS movw %ax, %ss # -> SS: Stack Segment
# Set up the stack pointer andcallinto C. 设置栈指针并且调用C movl $start, %esp # Stack has the opposite extension direction than Code # 注意栈的延伸方向和代码段相反 call bootmain #调用main.c中的bootmain函数
# If bootmain returns (it shouldn't), loop. spin: jmp spin # Bootstrap GDT 引导GDT .p2align 2 # force 4 byte alignment gdt: SEG_NULL # null seg 默认第一个段描述符为空 SEG(STA_X|STA_R, 0x0, 0xffffffff) # code seg 设置代码段描述符 SEG(STA_W, 0x0, 0xffffffff) # data seg 设置数据段描述符 # 关于SEG宏可以参考mmu.h gdtdesc: # 用于设置全局段描述符寄存器 .word 0x17 # sizeof(gdt) - 1 # Size of gdt .long gdt # address gdt # Base address of gdt
为了理解main.c,需要如下的知识储备。
ELF文件格式
可执行和可链接格式(Executable and Linkable Format)是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。
磁盘是电脑主要的存储媒介。磁盘是由盘片构成的。每个盘片有两面或者称为表面,表面覆盖着磁性记录材料。盘片中央有一个可以旋转的主轴,它使得盘片以固定的旋转速率旋转,通常是5400-15000转每分钟(Revolution Per Minute)。磁盘通常包含一个或多个这样的盘片,并封装在一个密封的容器内。
/********************************************************************** * This a dirt simple boot loader, whose sole job is to boot * an ELF kernel image from the first IDE hard disk. * * DISK LAYOUT * * This program(boot.S and main.c) is the bootloader. It should * 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. * * 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. **********************************************************************/
// read 1st page off disk 从磁盘上读取第一页 readseg((uint32_t)ELFHDR, SECTSIZE * 8, 0);
// is this a valid ELF? 通过ELF魔数确认ELF有效 if (ELFHDR->e_magic != ELF_MAGIC) goto bad;
// load each program segment (ignores ph flags) 读取各个段 ph = (struct Proghdr *)((uint8_t *)ELFHDR + ELFHDR->e_phoff); // 程序头部表的起始地址 eph = ph + ELFHDR->e_phnum; // 程序头部表的结束地址 for (; ph < eph; ph++) // p_pa is the load address of this segment (as well // as the physical address) // p_pa是加载地址也是物理地址 readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
// call the entry point from the ELF header 从ELF头调用程序入口 // note: does not return! ((void (*)(void))(ELFHDR->e_entry))();
bad: // stops simulation and breaks into the debug console outw(0x8A00, 0x8A00); outw(0x8A00, 0x8E00); while (1) /* do nothing */; }
// Read 'count' bytes at 'offset' from kernel into physical address 'pa'. // 从内核的offset处读取count个字节到物理地址pa处 // Might copy more than asked 可能会读取超过count个(扇区对齐) voidreadseg(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; // 算出扇区数 注意扇区从1开始(0为引导扇区)
// 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. // 考虑到没有开启分页以及boot.S中使用了一一对应的映射规则, // 加载地址和物理地址是一致的。 readsect((uint8_t *)pa, offset); pa += SECTSIZE; offset++; } }
voidwaitdisk(void){ // wait for disk reaady 等待磁盘准备完毕。 while ((inb(0x1F7) & 0xC0) != 0x40) /* do nothing */; }
voidreadsect(void *dst, uint32_t offset){ // wait for disk to be ready waitdisk();
outb(0x1F2, 1); // count = 1 0x1F2 Disk 0 sector count // Read one sector each time outb(0x1F3, offset); // Disk 0 sector number (CHS Mode) // First sector's number outb(0x1F4, offset >> 8); // Cylinder low (CHS Mode) outb(0x1F5, offset >> 16); // Cylinder high (CHS Mode) // Cylinder number outb(0x1F6, (offset >> 24) | 0xE0); // Disk 0 drive/head // MASK 11100000 // Drive/Head Register: bit 7 and bit 5 should be set to 1 // Bit6: 1 LBA mode, 0 CHS mode outb(0x1F7, 0x20); // cmd 0x20 - read sectors /*20H Read sector with retry. NB: 21H = read sector without retry. For this command you have to load the complete circus of cylinder/head/sector first. When the command completes (DRQ goes active) you can read 256 words (16-bits) from the disk's data register. */
// wait for disk to be ready waitdisk();
// read a sector insl(0x1F0, dst, SECTSIZE / 4); // Data register: data exchange with 8/16 bits // insl port addr cnt: read cnt dwords from the input port // specified by port into the supplied output array addr. // dword: 4 bytes = 16 bits }
staticstructCommandcommands[] = { { "help", "Display this list of commands", mon_help }, { "kerninfo", "Display information about the kernel", mon_kerninfo }, { "backtrace", "Display information about the stack frames", mon_backtrace }, };
staticvoidattribute_punch(int ch, int *cnt){ staticint value = 0; // value staticint state = A_NORM; // current state staticint temp = 0x0000, attribute = 0x0000; // temp attribute, current attribute
switch (state) { // state machine case A_NORM: if (ch == 0x1B) { // [ESC] state = A_TRANS; // transfer from A_NORM to A_TRANS } else { cputchar((attribute & 0xFF00) | ch); // put character with attribute *cnt++; } break; case A_TRANS: if (ch == '[') { // [ state = A_ESCAPE; // transfer from A_TRANS to A_ESCAPE } else { state = A_NORM; // transfer from A_TRANS to A_NORM } break; case A_ESCAPE: if (ch >= '0' && ch <= '9') { // digit - update value value = value * 10 + ch - '0'; } elseif (ch == ';' || ch == 'm') { // ; or m set temp and clear value if (value == 0) { temp = colormap[0]; } elseif (value == 5) { temp |= 0x8000; } elseif (value >= 30 && value <= 38) { temp |= colormap[value - 30] & 0x0700; // look up in color map } elseif (value >= 40 && value <= 48) { temp |= colormap[value - 40] & 0x7000; // avoid complex cases } value = 0; if (ch == 'm') { // m needed extra work - update attribute attribute = temp; temp = 0x0000; state = A_NORM; // transfer from A_ESCAPE to A_NORM } } else { // non_digit nor m state = A_NORM; // transfer from A_ESCAPE to A_NORM } break; } }
intvcprintf(constchar *fmt, va_list ap){ int cnt = 0;
// vprintfmt((void*)putch, &cnt, fmt, ap); // use attribute_punch rather than punch vprintfmt((void *)attribute_punch, &cnt, fmt, ap); return cnt; }