说明: 修改记录采用/////////////////// 隔开的一个部分对应一个版本, 如果没有标明对应 那个版本, 就只有麻烦去找这个记录块是在那个版本出现的,然后寻找那个 版本的源代码来对照注释查阅. 修改记录: 146 2007.1.20 setup32 支持INT13 AH=42h 模式加载os。 键盘在real pc不工作的问题时复位键盘有问题。现在不复位就凑合能用了。 145 2007.1.19 随便修补了一下boot,现在可以支持在bochs下启动VESA 了,可是速度好满啊。 在真实的PC 上keyboard工作不正常. 估计需要scancode2 来支持了. 144 2007.1.14 bootsector.S ready. 1) 支持lba/chs 方式的hd启动 2) 以为着可以从usb/cdrom启动 3) 支持从flopy启动,采用bios探测软驱chs(这可能带来某种限制,新机器上 该没有问题) 4) bootsector.S 将boot driver 设置到寄存器%dl, %cs为SETUP_SEG 6) bootsector.S 还有超过128 个字节的空间,可以存储setup的位置 7) 如果128字节不够用,可加载一个tiny的setup32,通过tinySetup32 加载真正的 setup32. 8个扇区足够tinySetup32 用了, 8*512= 4K. 8个扇区需要bootsector.S最多 8*8=36 字节的空间. 够用了 143 2007.1.10-2007.1.11 重新写了fsector.asm ==> bootsector.S. 采用lba2chs的方式加载setup。采用bios调用 int13 ah=8 获取CHS (floppy 也这么办,如果不支持,是不是AT 前的机器阿?) todo: lba支持, load spicific sector(sector list in bootsector). 142 2007.1.6 晚上10:00 //release 0.0.9.3 这三天写了一堆的bios调用,加载os的代码是唯一个第一次就成功的 bios调用. vesa的调用调试了好几天. bios1348 也耽搁了两天,bios中断参数 传递错误,竟然是因为我找的中断资料有误.赫赫 os可以用setup32加载了. 传递参数确实方便的多了. back to real 的几处重要改动: 1) e820 需要传递eax给bios,所以调整堆栈相应值,传递32bit的寄存器给real mode的bios. 2) 为了方便return_real_and_back 做成一个函数,这样可以减小bin image大小. 增加参数给这个函数,每一个bios调用函数都需要在自己的堆栈 建立一个real_regs, 因为setup32的堆栈足足有7M(esp=0x800000),这样在bios 和printf之间就没有什么限制了. os back !!!!!!! (对os 最重要的改动是保留0-1M 所有内存,那里是setup32的地盘 ) 141 2007.1.3 晚上10:00 back to real mode and return again的总结: 遇到的主要问题: 1.无法返回实模式 必须在gdb中定义好real mode segment的描述符,然后load到cs,ds等。 2. 现象:setup32 while巡回无法停止,cpu状态全乱! back to PM agian 的时候没有恢复eip,而是代替作了leav,ret, 导致堆栈 调整失败 2.5现象,real stack 返回值错误 从real mode返回cpu寄存器给setup32的时候,堆栈指针没有调整 3. 现象:putc,getkeystate,putstr正常,但是ModeInfo不正确,但是如果不调用 vbeinfo则可以返回modeblock但是返回值还是不正确。 原因:real mode SS:SP == 0x9000:00x0 , too small, change to 9000:a00x0 140 2007.1.2-2007.1.3 晚 经过仔细追踪,主要是怀疑堆栈, 赫赫,终于发现, real mode的堆栈设置 是 ss:sp 0x9000:0x0024 ,只有30几个字节. vbeinfo比较复杂,导致堆栈溢出. 变成 ss:sp 0x9000:0xfffe 绕回高端了,这会破坏某些bios的工作区. cpu状态也 会乱掉. 思路有了,解决也就不难了. 调整到ss:sp 0x9000 0xa0024, 可以了. 这下子几乎搞定了. 140 2007.1.2-2007.1.3 返回值成功了,测试函数用的是bios_ctrlstate. 137) 提到了整个流程。 在调试中出现了几个非常令人费解的事情。 bios_vbe_Info 成功了,但是返回值总是0x4f01, 而不是期望的004f。颇经 周折没有效果。开始写bios_vbeModeInfo,并用Vbe_ModeList 进行遍历mode info block的操作。 问题大发了: 1. while 循环问题很大,cpu状态全乱了, 2.不能填充es:si 指示的modeinfo block,但是vbeinfo怎么就可以呢? 第一个问题肯定是堆栈除了问题, 察看setup32.c的汇编码,发现由 setup.s 代替gcc进行leav ret真是极大的错误,因为没有调整堆栈. 修改后循环终于可以工作了. 第二个问题就神秘了,后来发现如果不调用vbeinfo,则vbemode block可 以被填充,但是返回值是错的,不是0,就是1301. 问题也更加扑朔迷离. 看来得使用printf了. 移植了kenerl的kprint系统. 没有进展. ???? 还是得先解决返回值的问题啊. 也是堆栈,经过仔细bochs, 终于发现 back32的时候需要调整堆栈指针才可以的. 满以为返回值这下该正确了吧. 神秘的是,bochs可以了, vmware不行. 但是vmware下面bios_ctrl 状态的判断之返回值确实是正确的. 总在想,vbeinfo,怎么会影响vbemode呢? 有啥问题? 1.3号晚上后来又发现 先vbemode 后vbeinfo则都能获取block,但是返回值总不正确. 这连个函数 又和bios_ctrl有何不同? 139 2007.1.1 晚上 返回值成功。back setup32 的时候重新将寄存器压入堆栈。bochs调试 tips: lb 0x10180 return to setup32 的断点 138 2007.1.1 早上 新的一年了。 将代码整理了一下,制作宏来方便使用。尝试传递指针参数给 BIOS, 发现stack frame 少 了bp,增加了之后竟然不可以。 晚上的时候 突然想起来对指针进行了++操作没有恢复。第二天一试,竟然 可以了。赫赫。就是bios_str. setup 在1m一下工作,16bit 的segment:offset 足够用了。下一步是bios 服务 完成后返回所有寄存器的值,以便判断是否成功。 137 2006.12.31 ///////////release a dirty version for PM->real->bios->PM again well done 趁热打铁完成setup 的构思: setup.S 完成CPU 模式切换的任务,其他一概不管. 1) setup.S org 0 是setup的入口,开启A20, 切换到PM, 跳转到setup32.C 2) setup.S org 128 是返回real mode的入口,完成切换到real mode 调用bios的功能 3) setup.S org 384 是重返pmode 的入口, bios 完成之后 iret 到这个地址 重新建立32bit的运行环境, ret 到setup32, 完成setup32 对bios的完整调用 下面是完整流程: 1) setup32.C : bios_call , 在32bit 堆栈 stack(esp): 0x800000 上pushal, save all PMode CPU state 2) 为了方便采用固定的real mode stack: 0x90000(ss:0x9000, sp:0) (实际上栈顶是0x90000+sizeof(real_regs)) 3)在地址0x90000 (16 bit 栈第一个stack frame)处建立如下堆栈主要是 bios调用地址, 恢复32bit运行环境的入口地址(setup.S: org 384 ), 以及bios_call 的32 bit 栈顶地址(也是为了恢复32bit 运行环境) typedef struct _real_regs { /*stack bottom*/ unsigned short ax; unsigned short bx; unsigned short cx; unsigned short dx; unsigned short si; unsigned short di; unsigned short gs; unsigned short fs; unsigned short es; unsigned short ds; unsigned short ip; /*bios call entry, return real mode call this */ unsigned short cs; unsigned short iret_ip; /* iret 到return 32bit 的代码*/ unsigned short iret_cs; unsigned short iret_flags; /* unsigned short flags;*/ long eip; /*32bit return address*/ long esp; /*saved 32bit stack */ /*return 32bit 负责重新恢复32位环境,popad */ }real_regs; 4) jmp 到setup.s org 128 (返回real mode, 调用bios服务的入口地址) 5) setup.s org 128 从建立的16bit 堆栈中建立16bit环境并ret 到bios 调用(reg.ip, reg.cs) 6) bios服务完成之后,ire 到setup.s org 384, 重新切回32bit Pmode 7) 从16-bit 堆栈恢复32bit 堆栈, popal, leave, ret(setup32.c:bios_call 返回到caller ) 几个地方值得注意,采用了全局变量: 1) setup.S org 128, org 384 为两个入口函数 2) 16bit 堆栈固定为0x90000 + sizeof(real_regs) 136 2006.12.30 /////////////release a clean clear demo for to PM and back to real mode 还以为as 的.code 除了问题呢所以有一段代码这样写的 movw $0x20,%ax .word 00 /*PM 下, movew $0x20, %ax, 会被认为成mov $0x20, %eax*/ movw %ax, %ds 但是后来发现这样也是可以的: .code32 movw $0x20,%ax /* .word 00 */ /*PM 下, movew $0x20, %ax, 会被认为成mov $0x20, %eax*/ movw %ax, %ds 从这个 表现看, ljmp 也是可以工作地。这个版本和就是专门清理了一个 干净简单的从real mode -〉pmode --〉real mode 的DEMO版本。 其实无须操作8259,A20, 也可以演示所有的东西。 至此,所有的back_to_real mode 的问题都得到了相当不错的解释。enjoy ///////////////////////////////////////////////// realse a version for back to real mode really work 135 2006.12.30 似乎back to real mode 可以工作了,其实更大的问题还在后头阿。bochs 可以在setup 设置 的"real mode" 工作了, 但是vmware 并不买账,crash。 下面就是有问题的代码: movw $0x0001,%ax /* protected mode (PE) bit*/ lmsw %ax /* This is it!*/ /* ljmp $8,$setup32+0x10000) # will be set to 0x100000 call setup32 */ .byte 0x66, 0xea # prefix + jmpi-opcode .long back_to_real_mode+0x10000 # will be set to 0x100000 .word 8 back_to_real_mode: movw $0x10,%ax movw %ax,%es movw %ax,%fs movw %ax,%gs movl %cr0,%eax andw $0xffFe,%ax /*不应该强制清0,应该保留现有值*/ movl %eax,%cr0 /* lgdt gdt_zero */ /*如果从这里lgdt,导致jmp到未知区域*/ /* jmp FAR CleanUp */ .byte 0xEA /* ;jmp far CleanUp*/ .long x .word 0x1000 x: lgdt gdt_zero movw $0x1000,%ax movw %ax,%ds /* ;restore DS */ movw $0x9000,%ax /*bootsector*/ movw %ax,%ss movw $1024,%sp movb $0x41,%al movb $0xE, %ah movw $0x7,%bx int $0x10 die: jmp die; gdt_zero: .word 0 .word 0 1).vmware 不工作,真实的pc也不可以work。问题在那里啊? 尝试各种改动,总是没有什么突破. 尝试不开启A20, 不关闭82559 的, 未果.尝试不修改任何段寄存器,未果(cs 除外). ..... 还有什么 是没有试过的? 赫赫. 2).在印象中,存在一种dos4gb 模式....., 是通过切换到PM 设置加载特殊的段描述符来实现的. 所以一直认为real mode 也是需要GDT 的, 但是只是使用一个cpu内的高速cache. 3).bochs 为什么在上面的代码逻辑下也可以? 恐怕是bochs 有问题,和真实的cpu 还有差距. 只要加载gdtr 为0, bochs 就将cache刷新为某个默认值. 导致setup 上当受骗. 4). 试了一下,重新定义一个Segment Desc. , 模糊的人为将D/B 位置为0即可. 就像如下代码 movw $0x0001,%ax /* protected mode (PE) bit*/ lmsw %ax /* This is it!*/ /* ljmp $8,$setup32+0x10000) */ .byte 0x66, 0xea # prefix + jmpi-opcode .long back_to_real_mode+0x10000 # will be set to 0x100000 .word 0x8 /**Volume III : 9.9.2. Switching Back to Real-Address Mode */ back_to_real_mode: /* now in PM*/ lgdt gdt_real reload_x: movl %cr0,%eax andw $0xffFe,%ax /*不应该强制清0,应该保留现有值*/ movl %eax,%cr0 /* jmp FAR CleanUp */ .byte 0xEA /* ;jmp far CleanUp*/ .long x .word 0x1000 x: .align 4 gdt_real: .word 0,0,0,0 /* dummy*/ /* kernel code 8*/ .word 0xFFFF /* limit 0-15bit */ .word 0x0000 /* base address 0-15 */ .byte 0x00 /* base address 23:16 */ .byte 0x9A /* P|DPL|S|TYPE 1|00|1|1010 */ .byte 0xCF /* |G|DB|0|AVL|SegLim19:16 1|1|0|0|1111 */ .byte 0x00 /* base address 31-24*/ /*kernel data 10 */ .word 0xFFFF /* 4G - limit=2047 (2048*4096=8Mb)*/ .word 0x0000 /* base address=0*/ .byte 0x00 /* base address=0*/ .byte 0x92 /* data read/write || 1 00(dpl)/1(code/data) 1010 */ .byte 0xCF /* 1100,粒度4k,32bit默认操作数,1111(4G), */ .byte 0x00 /*base */ gdt_48: .word 0x800 /* gdt limit=2048, 256 GDT entries*/ .word gdt_real,0x1 /* 注意 gdt base = 0X1xxxx */ 可惜这里的问题就更多了。不断的crash, 无论流程多么简单,无论我将 lgdt gdt_real 放到哪个地方。 5). 不断试验和调试,发现一个奇怪的事情, bochs调试的时候本来是 mov $0, %ax 却变成了movl 0xffe8000, %eax. 抓了一个调试过程放到这里: 00010000: ( ): mov ax, cs ; 8cc8 00010002: ( ): mov ds, ax ; 8ed8 00010004: ( ): mov es, ax ; 8ec0 00010006: ( ): cli ; fa 00010007: ( ): lgdt [ds:0xa0] ; 0f0116a000 0001000c: ( ): call 0xb0 ; e8a100 0001000f: ( ): mov al, 0x41 ; b041 00010011: ( ): mov ah, 0xe ; b40e 00010013: ( ): mov bx, 0x7 ; bb0700 00010016: ( ): int 0x10 ; cd10 00010018: ( ): mov ax, 0x1 ; b80100 0001001b: ( ): lmsw ax ; 0f01f0 0001001e: ( ): opsize jmp 0008:00010026 ; 66ea260001000800 00010026: ( ): opsize mov eax, 0x20 ; 66b820000000 0001002c: ( ): mov ds, ax ; 8ed8 0001002e: ( ): mov ss, ax ; 8ed0 00010030: ( ): mov es, ax ; 8ec0 00010032: ( ): opsize jmp 0018:0001003a ; 66ea3a0001001800 0001003a: ( ): mov eax, cr0 ; 0f20c0 0001003d: ( ): and ax, 0xfffffffe ; 83e0fe 00010040: ( ): mov cr0, eax ; 0f22c0 00010043: ( ): opsize jmp 1000:0000004b ; 66ea4b0000000010 0001004b: ( ): mov ax, 0x1000 ; b80010 0001004e: ( ): mov ds, ax ; 8ed8 n Next at t=1350668 (0) [0x00010002] 1000:0002 (unk. ctxt): mov ds, ax ; 8ed8 Next at t=1350669 (0) [0x00010004] 1000:0004 (unk. ctxt): mov es, ax ; 8ec0 Next at t=1350670 (0) [0x00010006] 1000:0006 (unk. ctxt): cli ; fa Next at t=1350671 (0) [0x00010007] 1000:0007 (unk. ctxt): lgdt [ds:0xa0] ; 0f0116a000 Next at t=1350672 (0) [0x0001000c] 1000:000c (unk. ctxt): call 0xb0 ; e8a100 Next at t=1350679 (0) [0x0001000f] 1000:000f (unk. ctxt): mov al, 0x41 ; b041 Next at t=1350680 (0) [0x00010011] 1000:0011 (unk. ctxt): mov ah, 0xe ; b40e Next at t=1350681 (0) [0x00010013] 1000:0013 (unk. ctxt): mov bx, 0x7 ; bb0700 Next at t=1350682 (0) [0x00010016] 1000:0016 (unk. ctxt): int 0x10 ; cd10 Next at t=1351363 (0) [0x00010018] 1000:0018 (unk. ctxt): mov ax, 0x1 ; b80100 Next at t=1351364 (0) [0x0001001b] 1000:001b (unk. ctxt): lmsw ax ; 0f01f0 Next at t=1351365 (0) [0x0001001e] 1000:0000001e (unk. ctxt): opsize jmp 0008:00010026 ; 66ea2600 01000800 Next at t=1351366 (0) [0x00010026] 0008:00010026 (unk. ctxt): opsize mov ax, 0x20 ; 66b82000 Next at t=1351367 (0) [0x0001002a] 0008:0001002a (unk. ctxt): add byte ptr ds:[eax], al ; 0000 在 opsize jmp 0008:00010026 ; 66ea260001000800 之后明明是 00010026: ( ): opsize mov eax, 0x20 ; 66b820000000 调试的时候竟然变成了opsize mov ax, 0x20 ; 66b82000, 之后代码段全乱了. 看来as对16bit 32 比特的混合编程不如NASM 做的好(还是我不会用?), .code32 不起作用. 这个问题看起来像是32 bit PM 下强制使用byte 0x66 作为指令的opsize 会引起混乱,eax强制 成了ax.0x66 像是对指令长度作了一个取反操作.    6). 问题更大的是 /* jmp FAR CleanUp */ .byte 0xEA /* ;jmp far CleanUp*/ .long x .word 0x1000           这时候已经切换到了real mode, 这里必须使用0x66 作为opsize. 或者用 .word x 代替.long x 7) 实际上6)中提到的问题是后来才发现的.这段代码利用lgdt gdt_real的办法 来刷新段描述符缓冲是不正确的做法.终于在网络上找到一个例子.也 也曾经尝试过: 加载16 bit 的段描述符, 利用far jump 加载描述符缓冲.由于没有 意识到32bit,16bit 对程序和CPU 的影响,犯了严重的错误.导致不断的死机重 启找不到原因. 正确的做法是如下程序:(注意,所设置的描述符容seg limit > 0xffFF, 因为在PM 加载 了这个CS 后需要跳专到一个offset 是0x10000以上的 reload_x:) cli /* load idt gdt*/ lgdt gdt_48 /* load gdt with whatever appropriate*/ /* 禁止8259 响应任何中断*/ call dis_irq movw $0x0001,%ax /* protected mode (PE) bit*/ lmsw %ax /* This is it!*/ /* ljmp $8,$setup32+0x10000) */ .byte 0x66, 0xea # prefix + jmpi-opcode .long back_to_real_mode+0x10000 # will be set to 0x100000 .word 0x8 /**Volume III : 9.9.2. Switching Back to Real-Address Mode */ back_to_real_mode: /* now in PM, so don't use 0x66 for opsize */ /* load 16bit real mode gdt entry*/ .byte 0xea # prefix + jmpi-opcode .long reload_x+0x10000 # will be set to 0x100000 .word 0x18 reload_x: movl %cr0,%eax andw $0xffFe,%ax /*不应该强制清0,应该保留现有值*/ movl %eax,%cr0 /* jmp FAR CleanUp */ /*back to real mode, use 66 for prefix of opcode */ /* or use .byte 0xea, .word x .word 0x1000*/ .byte 0x66,0xEA /* ;jmp far CleanUp*/ .long x .word 0x1000 x: /* Segment descriptor Sys Sgg G x 0 x 0 Code Seg 1 1 C R A Data seg G B 1 0 E W A +-------------+-/-/-/-/--------+-/--/-/-------+------------+ | | |D| |A|Seg-Lim | |D | | | | | base 31:24 |G|/|0|V| |P| P|S| Type | Base 23:16 | | | |B| |L| 19:16 | |L | | | | +-------------+-\-\-\-\--------+-\--\-\-------+------------+ AVL -- Available for use by system software BASE -- Segment base address D/B -- Default operation size (0 = 16-bit segment; 1 = 32-bit segment) DPL -- Descriptor privilege level (0-3) G -- Granularity (clear for byte unit of seg. limit, set for 4k unit) LIMIT-- Segment Limit P -- Segment present S -- Descriptor type (0 = system; 1 = code or data) data: accessed (A), write-enable (W), and expansion-direction (E)(1 for down). code: accessed (A), read enable (R), and conforming (C). */ .align 4 gdt: .word 0,0,0,0 /* dummy*/ /* kernel code 8*/ .word 0xFFFF /* limit 0-15bit */ .word 0x0000 /* base address 0-15 */ .byte 0x00 /* base address 23:16 */ .byte 0x9A /* P|DPL|S|TYPE 1|00|1|1010 */ .byte 0xCF /* |G|DB|0|AVL|SegLim19:16 1|1|0|0|1111 */ .byte 0x00 /* base address 31-24*/ /*kernel data 10 */ .word 0xFFFF /* 4G - limit=2047 (2048*4096=8Mb)*/ .word 0x0000 /* base address=0*/ .byte 0x00 /* base address=0*/ .byte 0x92 /* data read/write || 1 00(dpl)/1(code/data) 1010 */ .byte 0xCF /* 1100,粒度4k,32bit默认操作数,1111(4G), */ .byte 0x00 /*base */ /*real mode segment descriptor*/ .word 0xFFFF /* 0x18 */ .word 0 /* (base gets set above)*/ .byte 0 .byte 0x9A /* P|DPL|S|TYPE 1|00|1|1010 */ .byte 0x80 /* |G|DB|0|AVL|SegLim19:16 1|0|0|0|1111 */ .byte 0 .word 0xFFFF /*0x20 */ .word 0 /* (base gets set above) */ .byte 0 .byte 0x92 /* present, ring 0, data, expand-up, writable*/ .byte 0x0 /* |G|DB|0|AVL|SegLim19:16 1|1|0|0|1111 */ .byte 0 gdt_48: .word 0x800 /* gdt limit=2048, 256 GDT entries*/ .word gdt,0x1 /* 注意 gdt base = 0X1xxxx */ ///////////////////////////////134--131 back up a version for back to real mode 134 2006.12.28 看看retun to real mode 如何工作,以及调试过程. 开始时代码是这个样子: swich to pm 然后立刻swich back to real mode,显示一个字符,检查是否成功. /* */ movw $0x0001,%ax /* protected mode (PE) bit*/ lmsw %ax /* This is it!*/ /* ljmp $8,$setup32+0x10000) # will be set to 0x100000 call setup32 */ .byte 0x66, 0xea # prefix + jmpi-opcode .long back_to_real_mode+0x10000 # will be set to 0x100000 .word 8 back_to_real_mode: movw $0x10,%ax movw %ax,%es movw %ax,%fs movw %ax,%gs movl %cr0,%eax movw $0, %ax movl %eax,%cr0 /* jmp FAR CleanUp */ .byte 0xEA /* ;jmp far CleanUp*/ .long x .word 0x1000 x: movw $0x1000,%ax movw %ax,%ds /* ;restore DS */ movw $0x9000,%ax /*bootsector*/ movw %ax,%ss movw $1024,%sp movb $0x41,%al movb $0xE, %ah movw $0x7,%bx int $0x10 die: jmp die; 可惜, 死机,重启屡屡发生. 通过bochs进行调试. lb 0x10000 // setup.bin 的加载位置 单步跟踪, 到x:处必死. bochs dump_cpu 如下: eax:0xd08e9000 ebx:0x7 ecx:0xf0025 edx:0x1000 ebp:0x137 esi:0x200 edi:0x200 esp:0x400 eflags:0x46 eip:0x1004e cs:s=0x8, dl=0xffff, dh=0xcf9a00, valid=1 ss:s=0x9000, dl=0xffff, dh=0x9309, valid=7 ds:s=0x1000, dl=0xffff, dh=0x9301, valid=3 es:s=0x1000, dl=0xffff, dh=0x9301, valid=1 fs:s=0x10, dl=0xffff, dh=0xcf9200, valid=1 gs:s=0x10, dl=0xffff, dh=0xcf9300, valid=1 ldtr:s=0x0, dl=0x0, dh=0x0, valid=0 tr:s=0x0, dl=0x0, dh=0x0, valid=0 gdtr:base=0x10068, limit=0x800 idtr:base=0x0, limit=0x3ff dr0:0x0 dr1:0x0 dr2:0x0 dr3:0x0 dr6:0xffff0ff0 dr7:0x400 tr3:0x0 tr4:0x0 tr5:0x0 tr6:0x0 tr7:0x0 cr0:0x60000010 cr1:0x0 cr2:0x0 cr3:0x0 cr4:0x0 inhibit_mask:0 done 系统刚刚启动的时候,cpu状态如下: dump_cpu eax:0x0 ebx:0x0 ecx:0x0 edx:0x300 ebp:0x0 esi:0x0 edi:0x0 esp:0x0 eflags:0x2 eip:0xfff0 cs:s=0xf000, dl=0xffff, dh=0x9b0f, valid=1 ss:s=0x0, dl=0xffff, dh=0x9300, valid=1 ds:s=0x0, dl=0xffff, dh=0x9300, valid=1 es:s=0x0, dl=0xffff, dh=0x9300, valid=1 fs:s=0x0, dl=0xffff, dh=0x9300, valid=1 gs:s=0x0, dl=0xffff, dh=0x9300, valid=1 ldtr:s=0x0, dl=0x0, dh=0x0, valid=0 tr:s=0x0, dl=0x0, dh=0x0, valid=0 gdtr:base=0x0, limit=0x0 idtr:base=0x0, limit=0x3ff dr0:0x0 dr1:0x0 dr2:0x0 dr3:0x0 dr6:0xffff0ff0 dr7:0x400 tr3:0x0 tr4:0x0 tr5:0x0 tr6:0x0 tr7:0x0 cr0:0x60000010 cr1:0x0 cr2:0x0 cr3:0x0 cr4:0x0 inhibit_mask:0 done 对比发现对cr0的设置是在世不妥,改为 andw $0xffFe,%ax /*开始直接强制cr0 为0, 后感觉不妥改成清除bit 0*/ 之后还是不行,在次对比ducmp_cpu 的结果和检查bochs的输出结果,发现 gdtr 也该是0, (实际上不应该阿). 所以企图使用 lgdt gdt_zero 解决这个问题. gdt_zero: .word 0 .word 0 开始时方法如下: movl %cr0,%eax movw $0, %ax movl %eax,%cr0 lgdt gdt_zero /* jmp FAR CleanUp */ .byte 0xEA /* ;jmp far CleanUp*/ .long x .word 0x1000 x: 结果是更加有问题了, 导致jmp不知道到了什么地方.意识到应该加到 jmp的后面: movl %cr0,%eax movw $0, %ax movl %eax,%cr0 /* jmp FAR CleanUp */ .byte 0xEA /* ;jmp far CleanUp*/ .long x .word 0x1000 x: lgdt gdt_zero 这次总算可以了. 可以怪异的现象出现了, 我把 lgdt gdt_zero 去掉..... 竟然还是可以地. .......无语了!! Intel 的手册上没有说必须lgdt zero .估计是bochs的问题.不管了, 留着这句 代码做个纪念吧. 完整的代码如下: movw $0x0001,%ax /* protected mode (PE) bit*/ lmsw %ax /* This is it!*/ /* ljmp $8,$setup32+0x10000) # will be set to 0x100000 call setup32 */ .byte 0x66, 0xea # prefix + jmpi-opcode .long back_to_real_mode+0x10000 # will be set to 0x100000 .word 8 back_to_real_mode: movw $0x10,%ax movw %ax,%es movw %ax,%fs movw %ax,%gs movl %cr0,%eax andw $0xffFe,%ax /*不应该强制清0,应该保留现有值*/ movl %eax,%cr0 /* lgdt gdt_zero */ /*如果从这里lgdt,导致jmp到未知区域*/ /* jmp FAR CleanUp */ .byte 0xEA /* ;jmp far CleanUp*/ .long x .word 0x1000 x: lgdt gdt_zero movw $0x1000,%ax movw %ax,%ds /* ;restore DS */ movw $0x9000,%ax /*bootsector*/ movw %ax,%ss movw $1024,%sp movb $0x41,%al movb $0xE, %ah movw $0x7,%bx int $0x10 die: jmp die; gdt_zero: .word 0 .word 0 133. 2006.12.28 这一周的时间,尝试将setup.asm 也改写成AT&T 汇编。同时希望setup通 过C 来写探测系统参数的程序. 先是汇编改写,问题不大. 而后尝试用gcc 生成16bit 的汇编码, 经过多方尝试 为果. 首先在setup32.c 中加入__asm__(".code16");, 结果是C 代码根本无法 正确运行. 而后,使用__asm__(".code16gcc"), 结果是传递参数可以了,但是所有的地址操作 都是32bit的,stack frame 倒是正常了. 连全局变量都无法访问. 不得不放弃这种尝试.改用32bit C, 这里的问题是as无法混合使用.code16, .code32, 只能手工编写jmp指令,所以将setup.bin拆分出来一个setup32.bin,用于容纳 32bit的C 代码. 现在的启动文件由以下几部分组成: | boot:fsector.bin | |setup: setup.bin setup32.bin | {os:os.bin} 设计意图是: 1. boot:fsector.bin 负责装载setup 2. boot 首先将控制权交给setup.bin, 启动保护模式,马上转交setup32.bin. 3. setup32.bin 利用setup.bin 提供的x86 PM/RM 切换,通过32bit C 的强大编程能力 提供全面的系统设备探测功能.并且实现非常的方便和优美,至少 不用asm 计算传递给内核参数的位置,格式,offset 等等. setup 和os只需要 共享同一个头文件即可解决所有问题. 4. 长远看来, setup 可以替代grub, 做成一个单独的可装载文件,至少os可以 采用任意格式放到任何文件系统内. 因为setup可以强大到支持文件系统 的读取操作. 5. 当前情况下, boot 将setup 和os 统统加载到内存,调试完成之后setup就可以 达到当初的设计要求了: 灵活,强大, 传递参数简单灵活可扩展. 132.2006.12.20 将head.asm 改写为AT&T 的gcc 格式。 _gdt must align 4, refer to kernel/head.s . 没有想到再做点事情的时候已经1年过去了。 131.2005.12.20 vm86虽然凑合着能用,问题依然很多. 比如不能调度,无法正常结束等等.另外有个问题,还不 知道是怎么回事bochs当设置内存为64m以下在执行v86的时候死机,并且reset后内存探测失败.提 示一下,先设置为大于64m用吧. //////////////////////////////////////////////////////////////release 0.0.9.2 130.2005.12.14 我大概解决了(129)提到的中断关闭的问题.希望系统越来越清晰,稳定.简单说一下调试过程 首先我已经确定和schedu有关,所以就想看看是不是schedu在local_irq_restore的时候eflags的 IF位是关闭的:我是这么写的 if(x&IF_MASK==0)/*不幸错误严重,==的优先级高于&........这个错误我犯了两次了*/ kprintf("##%s restore eflags with IF DISABLE\n",current->name); 你看到了,c写的有问题. 结果是这代码被gcc优化掉了.什么也没有看到.让我很郁闷.无奈. 只好make debug debugx将反汇编代码弄出来分析,我所怀疑的setup_trap_stack,ct_exit都没有 发现太大问题. .......... 终于被我发现 /* For spinlocks etc */ #define local_irq_save(x) __asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x): /* no input */ :"memory") 看到=g这个约束条件了吧.结果gcc生成的代码如下: c0104e7a: 9c pushf c0104e7b: 59 pop %ecx #######HERE flag保存在寄存器 c0104e7c: fa cli c0104e7d: 89 f3 mov %esi,%ebx c0104e7f: 89 f0 mov %esi,%eax c0104e81: 9c pushf c0104e82: 56 push %esi c0104e83: 57 push %edi c0104e84: 55 push %ebp c0104e85: 89 66 2c mov %esp,0x2c(%esi) c0104e88: 8b 62 2c mov 0x2c(%edx),%esp ###cpu切换到新的工作栈 c0104e8b: c7 46 28 9a 4e 10 c0 movl $0xc0104e9a,0x28(%esi)###Another task Active c0104e92: ff 72 28 pushl 0x28(%edx) c0104e95: e9 42 ff ff ff jmp c0104ddc <__switch_to> c0104e9a: 5d pop %ebp c0104e9b: 5f pop %edi c0104e9c: 5e pop %esi c0104e9d: 9d popf c0104e9e: 51 push %ecx ### local_irq_resotre ,ecx已经被破坏啦!!!!!!! c0104e9f: 9d popf c0104ea0: eb b8 jmp c0104e5a c0104ea2: 89 f6 mov %esi,%esi 看看ABI怎么说: a.out and ELF on i386 CPUs says that the registers %ebx, %edi, and %esi must not be changed by a function 很明显,调用一个子程序的时候gcc能保证这个几个寄存器不被破坏.ecx已经被破坏的一塌糊涂了. 原因是找到了,但是问题更多了: 1)切换到另一个任务就是切换到另一个堆栈. 既然如此我们为什么要保存这几个寄存器??不是 c程序不会破坏这几个寄存器吗? 一个理解方式是: 将switch_to看作一个函数, 现在, switch_to是callee func.对于调用方函数 schedu看来switch_to不过是一个函数,只要满足abi要求,管他执行了些什么流程呢. 2)还有疑问:gcc怎么会相信ecx没有被破坏呢? 这里的原因是,switch_to是一个宏,是一段内嵌汇编,而这段内嵌汇编中没有声明ecx会被破坏啊. 所以产生这么一段代码.看看switch_to的约束条件: :"=m" (prev->thread.esp),"=m" (prev->thread.eip), \ "=b" (last) \ :"m" (next->thread.esp),"m" (next->thread.eip), \ "a" (prev), "d" (next), \ "b" (prev)); \ 确实没有声明ecx被破坏. 我做了几个试验,一个是修改local_irq_save,另一个为switch_to是加上 "c"(dummy)这样的约束条件,都可以解决问题. 3)linux 2.6,2.4都使用这个函数,为什么没有问题??? 查看相关代码,首先linux没有在switch_to周围使用这种"长程"变量,然后经过反汇编linux的 schedule函数,发现static inline的函数(ver>2.4.20)并没真正inline,gcc生成的是call指令,这样 的话,在switch_to之后调用了其他函数,gcc自然会假定ecx之类寄存器已经被破坏.故而不会产生有 问题的代码. 我也测试了一下,在我们的switch_to之后调用一个kprintf或者空函数,都可以解决这 个问题. 4)从侧面反映了local_irq_save到local_irq_resotre之间的代码不能够失去cpu,人家本来的 注释也说是/* For spinlocks etc */. sipinlocks etc 这下理解其中深意了. 5)吓我一跳,vm86不行了老系统崩溃,后来看到v86关中断的代码被关闭了,打开就好了.不过也 用的是local_irq_save,以后再改吧. 129.2005.12.14 经过几天的调试,基本建成了一个trap调度的框架代码,也解决了许多问题,比如堆栈溢出啥的. 也考虑了同步/互斥问题,做了很多试验. 所有的注释都写在trap.c中.正当我以为差不多可以的时候 突然发现在bochs环境下系统很容易锁死(命令div0,pgfault).经过几天的定位知道锁死是因为中断 被关闭了,不幸,不知道谁关的. 检查发现线程kbd_main中只要有调用schedu就必然锁死,去掉就可以. ..........定位中. 注: 死锁在vmware中也发生,只不过在bochs中很容易出现. 128.2005.12.11 philosophy提到要实现一个trap和系统调用的"server",为之建立一个统一的运行环境让core 变得让"man"可以预测,这个机制强制"kernel"随时可以被剥夺cpu(preemption),强制设计kernel 的时候区分进程local和进程间互斥--做到更加的模块化. 127.2005.12.10 经zhangpeng(室友)推荐,升级到了vmware5.0,装了fc4,却发现无法编译通过了.修改了几个 地方, Ship to GCC4.0 FC4. 126.2005.12.9 最近一段时间在考虑这个系统应该如何进一步发展.不期忘它能多么的如何如何.但也不希望 单纯的就是 学习linux/other os--->移植到这里--->修来修去都不知道要做些什么. 所以起草了一个philosophy,总的有个总体的考虑.开始写bootloader的那个年代就是对系统 充满神秘的疑问,就是想搞搞清楚. 到如今也明白了些.反而不知道该干啥. 125. 2005.11.21 转眼间,房子装修完成了.有机会再作些修改.也不知道最近都修改了些啥,反正都无关紧要. 想要让slbmp显示大于2M的bmp文件,所以写了一个巨土鳖的vmalloc. 对文件 另,对文件各处都有调整. 最重大的事情是今天,11.21日,vm86调试通过了. 起码能够用使用bios的功能,切换vesa显 示模式准没有问题了. 原来的v86不能使用是由于输入输出不能成功执行.我们建立的tss中所包含的io bit map 只包含前3ff的io端口.这样,v86_enable_port对于3ff以上的端口无能为力. 导致在一个io上 死循环.我曾经尝试,将所有io端口包含进tss的iobitmap但是没有成功(加大bitmap定义,修改 tss selector的limit).后来就放弃了调试,转而为vm86模拟io操作.经过一定的调试后果然一 切都好起来了. 可以自由的使用bios啦.如果对这个os进行改造,做成一个类似grub的boot loader或者其 他种种系统,就有了bios作为后盾,有了丰富巨大的空间可以自由发挥了. 124. 2005.9.18 今天简略学习了一下python 感觉比较强大。用于管理make file应该比较好。。。。。。。。。。。。。 123. 2005.8.24 转眼就是一年。我都不知道该如何编译这个系统了。 。。。。惭愧啊 顺便修正了一个bug。上一个版本发布的时候少了一个宏__byte的定义。随手加到了8259.c 中。 ///////////////////////////////////////////////////////////////////////////////////////////////// 122. 2004.11.18 minix floppy driver 比较适合做一个demo. 121. 2004.11.17 floppy driver 确实需要一个结构保存状态, 因为几个寄存器只写, 如重要的 dor(digital out register). Seat down writing os, or go home? ----20:28. 120. 2004.11.17 DMA 研究了一下, 成果记录于dma.h 备忘. demo 了一下floppy reading. 发现fdc_out 总是报告有数据无法写入fdc. 经过 仔细查找, 是一个垃圾命令没有读完所有的result , 导致fdc 状态处于0xd0, 即有数据需要输出. 阅读linux的floppy, 同时剥离繁杂处理,实现一个pure hardware driver. 感想: 难道在制造 一个容易阅读和移植的仓库?当你若干岁月以后 回头来看, 仍然能够区分这里只是硬件要求的逻辑, 还能在代码中 找到相关寄存器简单的描述, 能够找到当初参考的文档和代码. 最后, 可以轻易的找到并复用实现功能的这几个文件, 还有一个 接口的简单指导. 物理驱动仅仅是一个最小实现, 在特定的os, 也许应该对应写一个 设备驱动, 处理和os的接口? 119. 2004.11.16 开始研究一下floppy driver. 先读一下data sheet, 虽然不是pc-at标准但是 确符合at标准. intel 的82077A, NEC 8272A. 阅读datasheet,将寄存器地址用宏记录下来,并给出寄存器的描述. 做完寄存器描述后准备阅读programming Considerations, 同时记录到floppy.c. 然后就可以编码了. 为了使用DMA, 顺便研究一下8273. 118. 2004.11.10 分离出一个e820.c , make it clear&clean. if (flags & ~(SLAB_DMA|SLAB_LEVEL_MASK|SLAB_NO_GROW)) BUG(); dead here. because origin SLAB_LEVEL_MASK == SLAB_KERNEL, but here ,not equal. 117. 2004.11.8 又过了一个星期天. 把driver/onsole 改名为console, 当初还不了解phone target, 现在好了. 116. 2004.11.5 休息了两天, 今天把代码整理了一通. 移动头文件, 重命名头文件等. include目录少了几个子目录. 记得一开始, 在virtural pc 下面可以使用vesa, 后来怎么都搞不定.现在居然 又可以了...? 115. 2004.11.3 准备搞定这个编译问题. 很久没有遇到过编译问题了. 没有思路, 错误处前后皆无问题. google , 有人怀疑这个是gcc 的一个bug. ??? 郁闷!, 所以整理头文件包含关系, 搞了很久, 还是有问题. 今天把slab.c 一点一点的去掉看看是那里的问题. 将代码去完了, 还是不行. 最后才怀疑是不是某个头文件? 排除了一下, 发现是sem.h. 进去一看, struct semaphore{ spinlock_t spin; int x; /* temp for irq dis haha!!!*/ } 少了一个分号. !!!!!!!!!!!!! 114. 2004.11.1 移植slab. add file percpu.h(only none smp). add /kernel/timer/jiffers.h 发生一件事情, 无论如何无法编译通过, 提示: two types specified in one empty declare (一个typdef 或者struct 定义) short , long , unsigned invalid for jiffers (或者其他变量) 113. 2004.10.29 整理代码, 内存分配的全局变量隐藏. 112. 2004.10.27 vm86 下执行 int 0x10中断, 不断emulate io 指令, 但是没有成功的切换显示模. 现在调用vm86模式中断的任务还不能返回32bits 模式. 需要搞一下. 为了使调用do_vm86_int 的任务可以返回32bit 模式, 先看了看linux的实现. linux 下返回用户模式的指定函数,不准备使用这种方式. 希望一种能够象任务切换 的方式. 于是加入如下代码: #define switch_to_vm86(regs,current) do {\ asm __volatile__( \ "pushfl \n\t" \ "pushl %%esi\n\t" \ "pushl %%edi\n\t" \ "pushl %%ebp\n\t" \ "pushal \n\t" \ /*当前任务从这里结束 */ \ "movl %%esp,%0\n\t" /* save ret ESP */ \ "movl $1f,%1\n\t" /* save ret EIP (ret from vm86)*/ \ "movl %2,%%esp\n\t" /*switch to vm86 regs frame */\ "jmp start_vm86_iret \n\t" /* "ret" to vm86 */\ /* vm_i32_reload return address*/\ "1:\n\t" \ "popal \n\t" \ "popl %%ebp\n\t" \ "popl %%edi\n\t" \ "popl %%esi\n\t" \ "popfl" \ :"=m" (current->thread.vmretesp),"=m" (current->thread.vmreteip) \ :"r" (regs)); \ }while(0); /*same as enty.c iret stack frame*/ __inline_asm__ ".global start_vm86_iret \n\t" ".align 4 \n\t" "start_vm86_iret: \n\t" RESTORE_ALL /* bypass err code */ " addl $4, %esp \n\t" " iret \n\t"); void FASTCALL(vm_i32_reload()); void vm_i32_reload() { } #define ret_to_32(current) do {\ asm __volatile__( \ "movl %0, %%esp\n\t" /* restore ret ESP */ \ "pushl %1\n\t" /* restore ret EIP (ret from vm86)*/ \ "jmp vm_i32_reload\n" /* return to 32bits */ \ /*NEVER run here*/\ ::"m" (current->thread.vmretesp),"m" (current->thread.vmreteip) \ ); \ }while(0); void return_to_32bit() { kprintf("Return to 32bit esp %08x, eip %08x \n", v_cr->thread.vmretesp,v_cr->thread.vmreteip); ret_to_32(current); BUG(); } 可惜总是在32bit 模式trap fault. 仔细分析了一下. 在切换到vm86模式的时候 堆栈如下: ____________________________ | vm86 iret stack frame | +------------------------------------+ | 32 bits frame when reach | | to macro switch_to_vm86 | +-------------------------------------+ <---- switch_to_vm86 | pushf | | pusha | | | +------------------------------------+ <---- ret_to_32 frame trap fault 原因之一应该是tss->esp0: +------------------------------------+ <------ tss->esp0 | vm86 iret stack frame | +------------------------------------+ | 32 bits frame when reach | | to macro switch_to_vm86 | +-------------------------------------+ <---- switch_to_vm86 frame | pushf | | pusha | | | +------------------------------------+ <---- ret_to_32 frame 这样的设置将破坏到达switch_to_vm86 macro 之前的32bits frame 以及ret_to_32 的stack frame . 当然就异常百出, 什么非法指令什么的. 后来参考了linux 将tss->esp0 修改成如下样子 +------------------------------------+ | vm86 iret stack frame | +------------------------------------+ <------ tss->esp0 | 32 bits frame when reach | | to macro switch_to_vm86 | +-------------------------------------+ <---- switch_to_vm86 frame | pushf | | pusha | | | +------------------------------------+ <---- ret_to_32 frame 当时没有想明白, 这样还是破坏了一些stack frame. 一着急干脆另起一个堆栈: char xst[1024];, 将tss-> esp0 指向这个数组顶部. 结果仍旧triple fault. 打印出来的信息显示在返回32bits的时候在task 结构中保存的 vmretesp , vmreteip全都是0. ................... 对了, current 宏依赖于tss->esp0!!!! 哼哼, 再搞一个变量,!!!! 在iret 到vm86的时候记录一下current tss->esp0 = xst+512; //&vstack->VM86_TSS_ESP0; // dump_ptreg(regs); v_cr = current; 返回32bit的时候使用v_cr. void return_to_32bit() { kprintf("Return to 32bit esp %08x, eip %08x \n", v_cr->thread.vmretesp,v_cr->thread.vmreteip); ret_to_32(v_cr); BUG(); } now, 可以进而vm86, 执行中断, 然后再返回到32bits 模式了. 不过,返回的时机没有 仔细安排,中断也没有起到任何作用, vm86模式下还有非法指令等错误. 这些以后 再说吧. 框架和机制已经可以了. 111. 2004.10.25 改为按住crtl 键启动vbe, 否则只启动vga, 这样调试方便. 110. 2004.10.24 vm86 先停一下. 想到一个注意. 所写的代码最好方便移植. 把一个可移植单位的头文件和c 文件放到一 起移植应该就方便多了. 但是如此以来头文件的包含极为不爽了, 所以: 1. 在include 下面设置一个叫h的子目录, 在编译的时候将所有这种模块的 头文件拷贝到这个目录下. 2. 改动了一下makefile 实现了这个功能. 即tagert cph 3. 将make debug 的输出放到了debug 目录下, bochsrc 也放到这个目录 4. 实现cph的时候为了让cp 总有一个文件可以拷贝, 在scripts下有个.h.h 的文件. 109. 2004.10.24 00033821545e[CPU ] seg->selector.value = 0000 00033821566e[CPU ] seg = DS 当然猜想ds没有设置对, 参考了linux, ia32, 发现应该在会到内核的时候重新设置ds, es 等选择子. 因为没有用户态的线程所以从来没有考虑这种事情. 在SAVE_ALL 中恢复ds, es. 看起来正常了, 可以执行do_vm86_int 并返回到内核了. 切换到demo 任务的时候可能还不正常, 并且demo 任务无法返回到32bit 模式. go on ................. 108 .2004.10.24 设置tss 的io bitmap, 不过还是调用vm86 int 10h 的时候无法正常运行, 直接triple fault. 查ia32的手册第三卷, 说当IOPL < 3 的时候vm86下的中断,如现在的int 6d, 被重定向到 #GP. 将iopl设置为0, 仍然是triple fault. 按说应该回到PMODE 然后出现一个#GP异常的. google, thinking , 啊...., 会到PMODE 应该设置好tss.esp0, tss.ss0. set it. 仍然triple fault! ........... 不过看boch调试信息, 已经回到PMODE , 并且已经到trap_service_routine. 然而有大量的如下信息: 00033821545e[CPU ] seg->selector.value = 0000 00033821566e[CPU ] seg = DS 00033821566e[CPU ] seg->selector.value = 0000 00033821587e[CPU ] seg = DS 00033821587e[CPU ] seg->selector.value = 0000 00033821608e[CPU ] seg = DS 00033821608e[CPU ] seg->selector.value = 0000 00033821629e[CPU ] seg = DS 00033821629e[CPU ] seg->selector.value = 0000 00033821650e[CPU ] seg = DS 00033821650e[CPU ] seg->selector.value = 0000 00033821671e[CPU ] seg = DS 00033821671e[CPU ] seg->selector.value = 0000 00033821692e[CPU ] seg = DS 00033821692e[CPU ] seg->selector.value = 0000 00033821713e[CPU ] seg = DS 00033821713e[CPU ] seg->selector.value = 0000 00033821734e[CPU ] seg = DS 00033821734e[CPU ] seg->selector.value = 0000 00033821734p[CPU ] >>PANIC<< exception(): 3rd (11) exception with no resolution 00033821734i[SYS ] Last time is 1098685740 00033821734i[CPU ] protected mode 00033821734i[CPU ] CS.d_b = 32 bit 00033821734i[CPU ] SS.d_b = 32 bit 00033821734i[CPU ] | EAX=00004f02 EBX=00000008 ECX=00000010 EDX=c0155f9c 00033821734i[CPU ] | ESP=c015f698 EBP=c015f6a0 ESI=c0103456 EDI=c1ffbfb4 00033821734i[CPU ] | IOPL=0 NV UP DI NG NZ NA PE CY 00033821734i[CPU ] | SEG selector base limit G D 00033821734i[CPU ] | SEG sltr(index|ti|rpl) base limit G D 00033821734i[CPU ] | DS:0000( 0002| 0| 3) 00000000 0000ffff 0 0 00033821734i[CPU ] | ES:0000( 0002| 0| 3) 00000000 0000ffff 0 0 00033821734i[CPU ] | FS:0000( 0002| 0| 3) 00000000 0000ffff 0 0 00033821734i[CPU ] | GS:0000( 0002| 0| 3) 00000000 0000ffff 0 0 00033821734i[CPU ] | SS:0010( 0002| 0| 0) 00000000 000fffff 1 1 00033821734i[CPU ] | CS:0008( 0001| 0| 0) 00000000 000fffff 1 1 00033821734i[CPU ] | EIP=c0101c91 (c0101c91) 00033821734i[CPU ] | CR0=0xe0000019 CR1=0x00000000 CR2=0x00000000 00033821734i[CPU ] | CR3=0x00150000 CR4=0x00000000 00033821734i[CPU ] >> 8b 00033821734i[CPU ] >> 14 00033821734i[CPU ] >> 9d 00033821734i[CPU ] >> a0 00033821734i[CPU ] >> f2 00033821734i[CPU ] >> 15 00033821734i[CPU ] >> c0 00033821734i[CPU ] >> : mov EDX, DS:[c015f2a0 + EBX<<2] 00033821734i[CTRL ] quit_sim called 看来esp0 设置是有问题的, 在任务切换或者异常过程中有没有考虑到的地方. 107. 2004.10.24 尝试切换到vm86模式(vm86.c ), 但是总是异常导致vmware复位. 找不到原因. 所以考虑使用bochdeg进行调试. 使用vga启动系统, 去掉文件系统(boch不知持vesa2.0, 文件系统有缺陷), 就可以使用boch进行调试了. make debug 可以获取系统的汇编码, 可以找到do_vm86_int 的地址. 使用 break 设上断点.(help "break" 可以获取帮助). 使用si 1 进行单步跟踪, 发现确实能够进入vm86 模式, 但是执行第一句 代码就不行了. boch 输出为: 00027036668p[CPU ] >>PANIC<< exception(): 3rd (14) exception with no resolution 00027036668i[SYS ] Last time is 1098519385 00027036668i[CPU ] v8086 mode 00027036668i[CPU ] CS.d_b = 16 bit 00027036668i[CPU ] SS.d_b = 16 bit 00027036668i[CPU ] | EAX=00004f02 EBX=0000c121 ECX=00000000 EDX=c0155f8c 00027036668i[CPU ] | ESP=00008000 EBP=00000000 ESI=c01031f2 EDI=c1ffbfb4 00027036668i[CPU ] | IOPL=3 NV UP DI PL NZ NA PO NC 00027036668i[CPU ] | SEG selector base limit G D 00027036668i[CPU ] | SEG sltr(index|ti|rpl) base limit G D 00027036668i[CPU ] | DS:0000( 0002| 0| 3) 00000000 0000ffff 0 0 00027036668i[CPU ] | ES:0000( 0002| 0| 3) 00000000 0000ffff 0 0 00027036668i[CPU ] | FS:0000( 0002| 0| 3) 00000000 0000ffff 0 0 00027036668i[CPU ] | GS:0000( 0002| 0| 3) 00000000 0000ffff 0 0 00027036668i[CPU ] | SS:9000( 0002| 0| 3) 00090000 0000ffff 0 0 00027036668i[CPU ] | CS:c000( 0001| 0| 3) 000c0000 0000ffff 0 0 00027036668i[CPU ] | EIP=000000e6 (000000e6) 00027036668i[CPU ] | CR0=0xe0000019 CR1=0x00000000 CR2=0xfffffffc 00027036668i[CPU ] | CR3=0x00150000 CR4=0x00000000 00027036668i[CPU ] >> cd 00027036668i[CPU ] >> 6d 00027036668i[CPU ] >> : int 6d 00027036668i[CTRL ] quit_sim called 信息显示在vm86 模式下执行int 6d 就完蛋 (int 0x10). 后来换成执行 int 0x13, 还是不行. 第一句指令是 jmp xxxx. 心理比较奇怪. 怎么这个都 不行? goole >>PANIC<< exception(): 3rd (14) exception with no resolution 发现是triple fault的意思 奥, page table 是kernel的, 终于找到原因所在了, 哈哈. 临时做了一个函数map_ram_user 临时映射0 - 1M 为user 模式, 结果变成: 00027166041i[CPU ] allow_io(): CPL > IOPL: no IO bitmap defined #GP(0) 00027166041p[CPU ] >>PANIC<< exception(): 3rd (14) exception with no resolution 00027166041i[SYS ] Last time is 1098670943 00027166041i[CPU ] v8086 mode 00027166041i[CPU ] CS.d_b = 16 bit 00027166041i[CPU ] SS.d_b = 16 bit 00027166041i[CPU ] | EAX=00000469 EBX=00007fbe ECX=00000010 EDX=c0150402 00027166041i[CPU ] | ESP=00007f92 EBP=00007f96 ESI=c010338e EDI=c1ffbfb4 00027166041i[CPU ] | IOPL=3 NV UP DI PL ZR NA PE NC 00027166041i[CPU ] | SEG selector base limit G D 00027166041i[CPU ] | SEG sltr(index|ti|rpl) base limit G D 00027166041i[CPU ] | DS:8000( 0002| 0| 3) 00080000 0000ffff 0 0 00027166041i[CPU ] | ES:0000( 0002| 0| 3) 00000000 0000ffff 0 0 00027166041i[CPU ] | FS:0000( 0002| 0| 3) 00000000 0000ffff 0 0 00027166041i[CPU ] | GS:0000( 0002| 0| 3) 00000000 0000ffff 0 0 00027166041i[CPU ] | SS:8000( 0002| 0| 3) 00080000 0000ffff 0 0 00027166041i[CPU ] | CS:f000( 0001| 0| 3) 000f0000 0000ffff 0 0 00027166041i[CPU ] | EIP=0000045d (0000045d) 00027166041i[CPU ] | CR0=0xe0000019 CR1=0x00000000 CR2=0xfffffffc 00027166041i[CPU ] | CR3=0x00150000 CR4=0x00000000 00027166041i[CPU ] >> ee 00027166041i[CPU ] >> : out DX, AL 00027166041i[CTRL ] quit_sim called 虽然还是triple fault , 但是已经是一个io 错误了, IOPL: no IO bitmap defined #GP(0). 再单步跟踪可以看到可以执行很多命令了. 106. 2004.10.23 看看vm86 模式. 先把ptrace.h 的寄存器pt_regs 加上vm86 特有的寄存器. /////////////////////////////////////////////////////////////////////////////////////////////////////////////// 105. 2004.10.22 发布kernel 0.0.9.0 104. 2004.10.22 读fat32 文件系统问题少多了. 也搞了个inode. 完全参考文档FAT: General Overview of On-Disk Format. inode对应文件或者目录, super 是本文件系统的基本信息. ref linux, 但是不是完全相同. 103. 2004.10.17 加上周六周日阅读FAT32 的文档, 搞定大硬盘读文件不正常的问题.是因为没有使用BPB.SectorPerCluster 计算cluster 的位置. 为了支持超过8.3GB 的硬盘, 采用了LBA 模式访问. 修正读文件错误, 以BPB 的cluster 大小为准. 102. 2004.10.14 决定将setup程序用as写, 一步步来,慢慢都换成as, 这样方便写, 可以和C 共享头文件. detectmem.asm -> ram.S ok! 101. 2004.10.11 简单看看fat32 文件系统和partition. 101 . 2004.10.10 将显示模式改为自动探测. 现在只支持32bpp 800x?. 如果启动的时候按住ctrl键,则强制使用vga 模式启动. 否则使用自动探测值. 100. 2004.9.29 把vga12模拟成fb, 只使用其中的一个pixel plane, 速度就快了不少. 并且可以和vesa的操作统一起来. 目前只支持vga12, vesa (800x600) 32 bpp. 可以作些其他的了. 99. 2004.9.27 vga lfb vga lfb 模拟驱动. mode13是fb的,而mode12只能模拟成lfb, 速度比较慢. fill_rect 的变量vram 不小心定成了unsigned * 结果工作一直不正常,费了不少力气. 98. 2004.9.19 vesa lfb 字符显示。准备做一个简单的接口界面将内核的显示输出同显示设备 分开。 要支持汉字,内核简单搞个驱动,本来可以以后作模块的。 97. 2004.9.18 VESA 调试中, vmware 就是8位色不能使用, 而我恰恰用了这个模式。 一个是没有仔细看模式属性,另一个是一开始vbemode 只探测了30 个模式,漏掉了32bits 的几个模式。 ////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 97. 2004.9.18 release kernel-0.0.8.5 . 96. VESA Linear Frame Buffer 调试过程--------2004.9.18 早就说要使用更高的分辨率,可惜一直没有调试出来VESA 模式。 最早的时候使用vgalib, 没有调试成功。后来才写了段程序获取VESA 模式 列表,可以使用vbeMode, vesa 命令察看显卡的vesa 模式。 搁浅了一段时间后不甘心, 又来试验。线性显存模式从道理上讲应该 很简单才对。 前两天写一个map_ram , 可以任意映射内存地址。经查,vesa 模式0x124 是800x600 的模式,在setup 中调用bios ,可以切换到这个模式。但是 按照vbemode 提供的信息,lfb addr 在 0xfd000000, 写全1 进去, 没有反应。 很是郁闷。 可能是map_ram 错了。 做实验验证了一下,对的. 到google 搜索, 有人提到vmWare 支持32bits 的模式较好,其他模式可能有问题。 于是想搜索一个32 bits 的模式, 发现vbemode 只能现实20 个模式,一并也改了。 找到了一个800x600 32bit 的0x140. 不幸的是还是黑屏。 后来看看代码,原来是找到的那个画线函数不支持斜线。ft. 改成画直线。。。。。。居然可以了。 要不要启动VESA , 到setupvga.asm 的最后一行去改。 这次就做成这样吧。 95. 调试中遇到的几个问题: 2003.9.15 综述: schedu 可以任意使用,但不能滥用。 1. 为何timer 中断中不可以调用schedu 而kbd 就可以呢? 可能是timer 每秒100 次而schdu 又较慢。实验了一下,HZ = 10 的时候在timer 中断中也可以调用schedu. 当HZ 达到50 的时候系统明显变慢. 另外在hz=100, 把schedu 放到8259A ack 之后就对系统无明显影响, 偶尔情况怎么放都没有问题。 2. 为何纪录下来的schedu 嵌套次数不低于3 ? 如果任务少的话, 深度就会降到1。原因是: 所有的任务都是调用schedu 切换出去的。当前只会有一个任务 离开schedu , 所以此数值必然稳定于任务总数减一。 3. 为什么switch_to 要锁中断, 而linux 则不用? switch_to 在内核中运行,能够打断switch_to 运行的时间有: a) 主动调用 ---- ? b) 异常 ----- 内核中是不容许有异常的 c) 所以只有中断可以打断switch_to, 如果switch 没有上锁,并且堆栈和eip 操作还没有完成,被中断打断,然后中断又切换到这个进程。 就会全乱调。 4. gcc 使用寄存器有约定的,所以switch_to 切换的时候不用保存所有的regs. 但是这个约定是什么? 到底必须保存那些寄存器? 94. 2004.9.13 92 )的问题查明了。vmware 编译的时候时间对不上。 ./up 没有起作用。 总之是编译的问题。在锁定switch_to 的时候, 可以随意使用schedu. 为何必须锁定,原因再查。 93. 2004.9.11 开始移植linux 的frame buffer . 所有移植的文件都在 include /fb 或者driver/fb 中。 编译已经可以了。 写了一个map_ram, 用于映射显存。(注意 alloc_page 分配的是page 结构。) 92. 2004.9.10 只要不在 timer 中断中,schedu 可以在中断处理中调用: 原以为保护switch_to 即可,但是后来发现也不可以。原因待查。 91。2004。9。9 支持 expshell 的历史功能 (very simply ) 90. 2004.9.8 试着将时钟频率提高到1000Hz, 然后expsh 停止 响应。 考虑是中断占用cpu 时间太多,然后将timer中断的log 作为一个任务, 系统响应速度明显比100Hz 要快。 89. 2004.9.7 添加一个统计schedu 嵌套深度的代码. 并验证是否在每一个任务中都可以 使用schedu. 结果是, 在_kernel 任务中无法使用此函数进行任务调度. 经测试, 对switch_to 的调用加上保护即可. 增加宏SPECIAL_KERNEL_TASK, 在其等于1 的情况下, 使用特殊任务(_kernel), 否则将 其作为普通任务进行调度。 88. 2204.5.14 在中断中加入几个logo 作为演示. 87. 2004.5.11 修正新创建的任务返回后关闭中断的问题. 86. 2004.5.10 修改代码让编译的时候少报警告. ////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 85. 2004.5.7 kernel-0.0.8.0 release. 84. 2004.5.6 增加内核线程支持。 1. 首先企图使用linux 的方法,搞出一个init任务: 首先定义初始化一个init 任务结构, tss, init thread (宏 INIT_TSS, INIT_TASK ,INIT_THREAD), 然后写swap, 最后利用 linux 的_switch_to 宏进行切换. 问题是这样走的弯路远, 只想迅速做出一个原型. 而linux的方法(必须) 通过系统调用完成又不直观-- 比如一个进程都没有的时候linux 的 fork如何进行的复制(init 的创建也是通过fork). 2. 所以采用另外的方法, 直接创建线程: 构造一个调用栈pt_regs (见函数 create_task create_thread switch_to schedu) 首先, 系统对中断和异常的调用栈进行了统一, 无论何时任务的内核 堆栈都是和pt_regs 一致的, 这为统一操作创造了有利条件. create_task create_thread 创建了这个系统堆栈结构使新的任务从中断返回时 执行线程指定的函数----entry. create_thread 还指定了从switch_to 宏返回地址为ret_from_shedu (entry.c), 所以新创建 的任务直接从entry.c " 恢复"执行环境执行任务入口函数----entry. 3. 有几个问题在写代码的时候要注意 1) create_thread 要初始化完整pt_regs, 包括xds,xss,xgs等, 否则 entry.c iret 的时候 就会异常. 2) create_thread 特别要注意pt_regs 的变量eflags 的设置, 现在置为2 , 这是cpu 复位以后的初始状态, 但是这个值是禁止中断的, 导致切换到此 任务后cpu中断关闭, 从而失去系统的动力. ========这个bug还没有 改正, 以留做纪念. init_b 调用sti(), 就是这个目的.========= 这个问题 没有被注意到之前, 现象很奇怪, init_b init_t 在任务入口调用一下 kprintf 系统才可以工作, 百思不解. 后来发现是因为其中有对sti 的调用. 4. init_b 值得分析一下 { static unsigned long jiold = 0; //jiold=jiffies; //kprintf(""); sti(); while(1) { //if((jiffies-jiold)>240) { // putchar('b'); char_color = char_color++%16; barrier(); ------------------------------------>如果没有这句话, 导致gcc 将修改以后的 变量char_color 存入一个寄存器没有真正写入内存, 后果是此任务没有达到目的: init_b 对文本颜色的 修改没有影响到任务init_t.(jiffies 工作正常,因为jiffies 是 一个volatile 类型的变量. // jiold = jiffies; } } } 5. schedu 中if(current!=_kernel) p = _kernel; /* 调度kernel 运行 */ 说明如下: 系统将main (main.c)也设计为一个隐含的任务, 称为_kernel 其堆栈 在编译期间定为8k对齐(见head.asm 的变量_k_stack, _k_top) 并且 无需初始化其struct task_struct 和 pt_regs. _kernel 任务只在 switch_to 宏中使用其thread 结构 , 而pt_regs 在中断发生时 已经自动构造完成. 83. 2004.4.26 添加tss dump 命令 82. 2004.4.24 补齐cr0-4 的简单说明(i386.h i386.c) 81. 2004.4.9 多任务 1. 初始化tss, tss 描述符 2. 加载tr 3. 初始化init 进程堆栈 80. 2004.4.9 task 结构,tss结构定义编译通过. ---> 8k 边界--> +----------+ | 堆栈 | | ... | | task | +----------+ 基本属于linux 的设计. ///////////////////////////////////////////////////////////////////////////////////////// release kernel 0.0.7.9 79. 2004.1.7 - 1.14 vesa 信息获取, vesa mode 信息获取. 使用vesa 命令获取vesa相关信息. 使用vbemode <0-29>获取vesa mode 相关信息. 78. 2004.1.7 改用新的boot 参数传递: 采用一个参数指针数组 vesa 信息获取. 77. 2004.1.4 添加新的shell 命令kmsg 76.2004.1.4 不是每一个显示输出都使用中文, 应该在应该使用的地方使用,做到不 滥用. 现在可以在shell 里使用中文.其他地方可以不用 75. 2004.1.3 page_alloc.c 初始化过程代码胶结完成,可以启动(是否正常待测).boot mem 梢做修改. 有了一个初步可用的物理页面分配器. 稍后需要精简代码, 调整结构. 有了页面管理器就可以进一步搞thread, swap, fs,net.... 总之内存管理是基础性的东西. 74. 2003.12.29 裁减page_alloc.c 并 编译通过. 删除phymem.c. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// release kernel 0.0.7.8 73. 2003.12.26 增加fixup 支持,trap.c, fault.c. 简单page falut , div0 处理. 72. 2003.12.23 增加tread.c(dump_regs,dump_stack) ptrace.h. 71. 2003.12.20 trap 试验 -- 修改context 中的eip, 见entry.c 代码. 就是0.0.7.8beta 70. 2003.12.20 把entry.asm 换成 entry.c ,减少.s 文件. 69. 2003.12.19 制作c 文件内的中断门处理例程. 见i8259.c 68. 2003.12.18 将irq_table 从 entry.asm 移入i386.c. 67. 2003.12.17 尽量将汇编编码移入c 文件, 最大程度上减少汇编代码 将idt, _lidt 从entry.asm 移入i386.c . 66. 2003.12.15 终于知道在中断处理函数中为什么不能保存恢复fs,gs 了: 这两个寄存器没有初始化(head.asm), pop 时候会引起异常. 65. 2003.12.15 调整代码结构(i386 门的初始化, 8259 和中断初始化), 剔除临时代码(原来的kbd中断函数). ///////////////////////////////////////////////////////////////////////////// 64. 2003.12.15 发布 0.0.7.7 版. 63. 2003.12.12-15 弄来linux的hd驱动, 作各种测试: 狂读测试, 读不存在的磁道测试, 等待时间测试. 中断测试: 注意得开启2号中断和14号中断(8259 级连口和硬盘中断), 应答也必须应答两片8259, i386 寄存器 0x20,0xa0. 提高显示速度 -- 去掉phy_copy ,换成memcpy, 效果很明显. 62. 2003.12.10 搞了一个IDE 驱动, 启动ekshell: 采用特殊的数据段 .ekscmd 注册调试命令. 建立kernel/ekshell 目录, 存放embed kernel shell 实现文件. 61.5 ?????? linux 开发版发布. ////////////////////////////////////////////////////////////////////////////// 61. 2003.12.6 停滞了半年了,不知道修改过什么了, 不过下一版会有一个比较强大的make, 并且转移 到linux下开发,因为djgpp 已经影响了进一步的开发,比方说djgpp我搞不定错误输出, 它总是将win 终端搞得缓冲区变小. 60. 2003.5.8 在kernel 和include 中增加mm 目录. 以及phymem.c . 59. 2003.5.5 新增panic.c . 2003.5.7 新增cache.h dma.h . 嗐, 几乎演变成一场c&p. i386 相关的头文件通过arch.h 进入内核. 59. 2003.5.5 增加段.init. 方便丢弃无用代码 58. 2003.5.5 把bootmem 搞过来研究一下. 好像bootmem 一开始就能管理所有普通ram (相对high mem 来讲). 但是实际上这时 页表映射还没有完全建立起来. 只有0--8M 已经映射. 并且从内核_end 开始 才属于bootmem 分配和管理. 如果加上bss 内核有最大2M (应不会吧?). 还有5M (内核在3G+1M , 所以是8-2-1). 4G mem, 最多用掉4k*1024+4k page table. 何况内核只映射 大概800M 内存. 所以这样做(bootmem 一开始就能管理所有普通ram)没有问题. 57. 2003.5.5 增加bitops.h . 整理page.h 中的操作. 位操作, vsprint, 以及各种原子操作, 应该不用coding. bitops.h 中一般没有下划线的函数是原子操作, 有下划线的相应函数不是 原子操作 . 56. 2003.5.4 增加page.c 和page.h . 重点是i386 的4k 大小的页表操作. 55. 2003.5.4 head.s 中的页表不再使用绝对定位. 而放到bss.pg 段,这样初始化页表的时候 就不用考虑这几个绝对定位的页表了. 54. 2003.5.4 把所有的.s 文件更名为.asm. 发布si 教本,支持at&t 的汇编. 53. 2003.4.27 在bss段增加段 .page . 这个段页对齐. 准备去掉head.s 中使用的绝对定位方式定义的内核页表,页目录. ////////////////////////////////////////////// 52. 2003.4.22 ld 在输出输入的文件名包含 console 时会等待键盘输入或者输出. 真是郁闷. 只好叫onsole 了. 51. 2003.4.21 终于决定使用lds. 因为迄今为止对用做初始化的页表仍然使用" 绝对 " 定位. 见head.s . 在coff中, lds 中的变量到c中访问需要注意下划线的问题. 另外 PROVIDE 宏是不是 export 一个变量就没有深究了. 以前试过在head.s 中使用 align 4096, 但是总是把_pgdir 定位于 0x200 这样的地址. 今天看了看默认的lds, 发现其中有align(0x200) 等定位语句. 以前做不成应该是这个问题. 51. 2003.4.20 修改目录结构. 1) 保持了include 目录的含义: 体系无关. 增加了arch.h 在include 目录. 用于隔离 体系特定的文件. 2003.4.21 2) driver/keyboard 在include 目录新增drv目录. driver/keyboard 向 include/drv 提供头文件 kbd.h. 50. 2003.4.19 修改setup.s . 我们在setup.s 中不对8259 重编程. 这个工作main 做了. 但是禁止8259 响应任何中断. 把8259 的代码分离到setup/8259.s . 49. 2003.4.17 给编译选项加上 -nostdlib . 48. 2003.4.17 把vga, kernel 都换成子make. 发现写到软盘的系统不太正常. 键盘驱动有问题. 按a, 不出a,出一堆乱码. 切换到图形模式后log信息不能正常输出. 怀疑是rawrite 没有把最后一个磁道或者扇区写到软驱,导致数据不正常. 把软盘影像扩大一点, 正常, 好像证实了以上猜测. 共享一个 img 吧,测试出一个512K+2K, 写软驱费点时间吧. 47.2003.4.16 e820 基本上可以正常工作了. 46.2003.4.16 昨天和今天做了一些工作. 1. 收集mem map 信息. boot/setup/detectmem.s e820 boot/setup/setupvga.s 提供字符串输出 boot/setup.s 和boot/setup 目录构成 setup 程序 boot/boot.h boot.inc 启动规范,分别给c 和 asm 使用的头文件 搞了一段 e820 的代码来跑. 但是printf 好像不支持打印 long long ,挺郁闷. 于是 2. 移植linux的vsprint 系统 include/ ctype.h isdigital 等支持字符解析的宏 linkage.h asmlinkage, extern "C" 的封装 stdarg.h 多参数函数支持 stddef.h NULL offsetof limits.h 空文件 /////////// posix_types.h 空文件, 包含stddef.h 和 i386目录下同名文件 string.h 空文件, 包含i386 目录下同名文件 types.h 各种数据类型的别名 include/i386 div64.h 64bit 除法运算 , vsprintf 有直接使用 //////////// posix_types.h __kernel_xxxx_t 定义, 间接包含 string.h string 库, 间接使用 types.h 类型定义, 间接使用 kernel/ ctype.c 对应于ctype.h 的数组 kprintf.c kprintf 实现 vsprintf.c vsprintf 实现 3.发现printf("%02x" 0x333333) 不会截断 0x333333. 记得有的系统下时只输出2个char的 45.2003.4.14 改进makefile. 使用子make的方式工作. 另外由于setup.s 要和 detectmem.s 连接到一起, 引起了不少问题. 1. setup.s 中原来使用nasm的times机制使setup.s 生成的bin的大小严格的为2k. 但是 把两个文件连接到一起时就不能使用这种机制了. 我们采用了partcopy. 见/boot/makefile 2. 因为要连接到一起,所以, 使用了a.out格式. 但是,nasm在生成a.out格式时生成32bits 代码. 所以在setup.s detectmem.s 中要使用 [bits 16] 强制nasm生成16 bit代码. 3. 连接到一起时起初没有注意到必须在ld 中指定生成 binary 格式的文件, 并且.text 从 0 地址开始. 见/boot/makefile 的rule : setup.bin ld $(setupo) -Ttext 0x0 -O binary -o setup.oo objcopy -R .comment -R .note -S -O binary setup.oo $@ 44.2003.4.14 fsector.s 软驱启动记录,自举到0x90000 setup.s 由fsector.s 加载到 0x10000,长2048 字节, 其后是以head.s 开始的内核. kernel/head.s 内核被setup.s 装载到 0x100000 (1M). boot.h 定义由setup.s 传递到内核的各种数据结构,由setup.s 和head.s 共同使用. 目前参数放在 0x90000, 即覆盖 fsector.s. boot.inc 和boot.h完全相同, 但由.s 文件引用. 注意保持一致. boot/setup 目录是 setup.s 需要的各个文件,如内存探测等. detectmem.s 内存检测 ////////////////////////////////////////////////////// 43. 2003.4.6 @@@@@@@@ 移植Lingix 的汉字系统. 发现不能输出汉字. steup.s 中吧内核搬移到物理地址1M 时,搬移的块大小不足, 把 line 116 行的0x30000, 改成0x60000 才解决问题. 否则, 虽然系统不死,但是任何文字都不输出, 漆黑一片. ??? 这是第二次发现boot 能力不足. 42. 2003.4.6 建立build 目录, 从此在这个目录下编译. 把软盘安装从partcopy 升级成rawrite. 把bootimg 改名为bootimg.flp 方便vmware使用. 把fbootimg 改名为fboot.img. 41. 2003.3.30 发现一个bug. 从0.0.6.3 开始, 写到软盘的ExpOS 开始不能正常启动. 而vmware 模拟就正常. 经查因为partcopy 有问题, 大于14k 的内核不能正确的写到软盘. 换成rawrite 以后问题解决. rawrite -f bootimg -d a: rawrite 再各种linux安装盘的dosutils目录中很容易找的到. 40. 2003.2.22 在一个c 文件中可以如下的写一段代码 /* * Test Gcc feature */ __asm__( "_interrupt:\n\t" "pushl -256\n\t" "jmp 1f \n\t 1:"); 这段代码可以不在任何函数中. linux 用gcc 的这个特性构造他的中断处理函数.见hw_irq.h 宏BUILD_IRQ. 39. 2003.1.5 函数命名规则: 1) 下划线 如果一个函数以下划线开始, 则函数应该是一下情况之一: a) 该函数是一个硬件特性的第一级封装, 如 _gate_fill b) 该函数是本模块提供给其他模块的接口如 _fill_int_gate _irq_reg _enirq_8259 _init_8259 这样好像不合linux 的做法噢, 去掉b) 吧.(2003.3.30) 38. 2002.11.11 ia32.c 更名为archlib.c , ia32.h 更名为arch.h 37. 2002.11.8 exception 处理. 写了一些代码处理error code , i386 中有些exception带有errcode, 一些没有. 36. 2002.11.3 0.0.6.4 begin. 删除临时代码, 如arch.s 中的中断处理函数kbd, timer. /////////////////////////////////////////// 35 2002.10.25 修改makefile 生成fimg, 方便安装软盘映象, 因为安装到软盘无须pad. 中断处理中无法保存fs gs 这两个寄存器,否则就死掉. 到底为什么啊? 34 2002.10.25 完成一些8259 的操作, 把barrier 弄了过来, 但是不会使. 33. 2002.10.24 archarch.s 改名为arch.s. 调整函数名. 完成简单的irq 管理. 命名规则: 硬件的第一层封装函数, 变量, 加下划线_foo_bar. 辅助宏加两个下划线如__byte. outb in 未遵守这个规则. 其他: foo(x,y) : 一般数据从y--->x. linux 下好像是x-->y , 比如out 32. 2002.10.22 写_irq_reg. 负责_irq_table (arch.s), 的设置. 用_gate_fill 设置_hw_gatexx (arch.s) 到idt, 注册irqact到_irq_table. 然后才能处理一个irq. 31. 2002.10.21 写nasm的宏, 产生中断门的处理代码. iaarch.s hw_gate, gate_gen. 30. 2002.10.20 0.0.6.2 封版(因为我要删改代码,不留个版本以后没法参考) /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 29. 2002.10.18 改成轻量级kprintf 后, 用c 写的handler 不行了. 改造成汇编的. 屏显较乱, 在timer 中表存 g_col 后正常了. 28 2002.10.18 找了一个轻量级的kprintf 支持. 以便调试. doprintf.c _printf.h stdarg.h string.h _null.h _size_t.h _va_list.h 27. 2002.10.17 0.0.6.1 结束了, 开始0.0.6.2. 删除ia32arch.s 中的Setup_Idt 和temp gate. 设置i8259 做如下映射IRQ 0-7 -> INT 20h-27h, IRQ 8-15 -> INT 28h-2Fh. 这样, timer 映射到了0x20, 我不用暴力了从事了. 给g_handler 换了个名字----- _timer. 炮制了一个_kbd 开始不能使用, 后来发现原来把timer安装到了 中断0x21 的处理函数. ok了. 键盘中断顺序打印ascii 码, 粉红色. timer 显示动画. 这两个活动在 屏幕 第一行. main 在idle时在当前光标位置显示绿色动画. 哈哈, 挺热闹了. 中断处理保存的寄存器少, 根本不足以支持系统运行, 由于live 的重入, 一会功夫就会多重故障而死^_^. (还别说, 系统函数简单 死掉的情况较少) ////////////////////////////////////////////////////////////////////////////////////////////////////// 26. 2002.10.16 看看i8259 的信息. 使用c 写了一个g_handler, regparm 传参方式. 但是没有使用. 编译使用-O2 优化, idle 太快了, 看不清楚. 优化果真好. g_handler 中使用g_row 控制timer 的动画出现的位置但是失败了, 不知为何. 尝试extern volatile int g_row. 不行. 看反汇编, 无果. 查证是中断号不对(gate num=8), 不知是那个了. 现在把中断处理 统统换成g_handler, 哈哈, 时钟可以驱动屏幕右上角的动画同时可以 处理轮询键盘. g_handler 中保存的寄存器不够, 不管了! (可能有时不正常) 不用声明 g_row 的volatile 属性也可以工作了. 另: 现在不是自动dependence, 改动makefile 要make clean. ^_^. ////////btw 一开始键盘驱动和timer 并存. 之后只留下idle动画, timer动画和idle动画 的位置和颜色不同(不知道那个是timer的中断号, 暴力切换,^_^, 见main.c). 演示的内容多了一点点. 25. 2002.10.14 把32 个保留的I/E(中断/异常), 的分类情况放到ia32arch.s 的表中. Minix 中只使用中断门描述符.(idt gate) 24. 2002.10.13 既然用c 来实现gate 设置, 就不用0.0.6 head.s(0.0.6.1 在ia32arch.s) 中的设置gate 的函数了. 23 . 2002.10.12 完成_gate_fill 函数. 换成_gate_fill 后忘了lidt. ^_^. 22 . 2002.10.11 kenel 文件说明: head.s ------- 控制转移到------> main.c | | ia32arch.s ia32.c ia32.h //////////////////////初始化 流程 head.s ------- 负责设置保护模式, 分页. main.c ------- head.s 设置好环境后就交给main.c 负责初始化. /////////////////////体系架构支持 ia32arch.s --------ia32 体系结构支持, 负责中断, 调用, 异常, 调度机制. ****** ia32.c --------IA32 体系 函数库. ****** ia32.h --------Ia32 体系函数库, 支持kenel gnu. ////////////////////头文件结构 kernel.h 宏函数库, /kernel 目录下的总控头文件. ////////////////////临时支持 kbd.c kbdriver.c 临时文件. vga.c 临时文件, 支持调试. 21. 修正了中断处理中一个显示错误. mov edx, 0x0a730a4f ;Os ;0x310a330a bug fixed 0x310a330a 不是一31. 因为x86 是little endian. ####################################begin 0.0.6.1 2002.10.10 23:02 20. 注释掉了main.c 中的两段代码. 把idle 动画中对键盘leds 的操作去掉了. 使能8259 的代码后移到idle 动画前. 把__asm__ ("int $3"); 注释掉, idle 动画消失. 这说明19. 的分析是正确的. 在head.s 中的中断处理中发送EOI , idle 动画出现. 说明8259 和timer 正常. 只使能keyboard. outb_p(0x21, ~0x02); outb_p(0xA1, ~0x00); 在main.c 中读0x60 端口, 这样使键盘可以重新产生中断. 现在的idle 状态下, 按动键盘将会驱动动画. 是8259 , keyboard 键盘以及中断 的良好演示程序. 封版. ^_^. (不读0x60 端口键盘果然不能产生中断, 我试过了.) /////////////////////////////////////////////////////beging 0.0.6 19. 通过下面的分析, 由于没有EOI, 确实只能收到一次键盘的中断. 0.0.5.5 封版吧, 留住这个经验. 另外我叫这个版本是kernel. 他有了中断! 18. 2002.10.8 23.40 . 第一次这么顺利, 中断处理竟然有所反应! a) 制作中断相关的注释, 主要是Gate. b) 在head.s 中添加nop_handler, Tmp_IntGate,Setup_Idt 三个函数和变量, 从而设置好idt 使中断可以被处理. c) main.c 中的idle 动画采用中断来实现 while(1){ kbdledon(); //live(); kbdledoff(); //__asm__ ("int $3"); //原来是强制产生中断, //在中断处理中调用 live //live(); } main.c 一开始就使能了键盘和timer的8259 中断. 调用main 之前开了中断, sti. /* Enable IRQ0 (timer) and IRQ1 (keyboard) at the 8259 PIC chips, disable others: */ outb_p(0x21, ~0x03); outb_p(0xA1, ~0x00); 在kbd操作中会产生中断. 读取键盘响应后使键盘再次具备产生中断 的能力. 但是我觉的在中断处理中还应该有 outportb(0x20, 0x20); 以向8259 发送EOI. (有这个动作反而有点问题) 但是现在没有,也能工作, 是不是timer啊? 后来发现kbdledon() 中调用了live. Bug! 注释调了这个调用idle动画消失. 打开//__asm__ ("int $3"); . 又出现了idle动画. 这才算成功了. (注释掉 __asm__("int $3") , vmWare 会没有响应. 太快了?) 17. 0.5.4 到此为止吧. 虽然改动不多, 费力却不少. 16. 为了调试3g处的内核, 使用die:jmp die 这种原始武器, 发现分页以后如果 重新加载内核gdt 就会死机, 并且是把选择子 装入ds 拿那一刻. 我用section .data 把idt, gdt 放到数据段, 发现字符串输出都不正常了! 是一堆乱码! 努力总会感动上帝! 2002.10.5 8:53. 突然意识到, 代码在物理内存1M的地方, 1:1 映射! 不是-T 0xc0000000 , 而是-T 0xc0100000. 这样, 什么问题都没有了. 顺便把flag 清除到了全零状态. 现在, 把Makefile 写成-T 0xc0000000 或-T 0x100000 都可以正确工作. section .data 也没有问题了. mov esp,0xc0800000; 堆栈建在3g+8M 处. vga.c : 驱动改成3g ok. 显存在0xc00b8000. 生成kerlsyb.txt kerllst.txt . 主意: 由于页表都使用了绝对定位, 所以引用时无须把地址减去3G. 15. 2002.10.4 试图仿照linux 把内核移到3g去运行, 但是, 始终不成功. 把setup_paging 函数去掉了省去了调用这一步.(因为-T 3g, 调用时必须用类似call setup_paging-3g 这样的语句. 我没有 试不知行不行! ) 注: 调试时为了看3g处的页表有没有设置好使用: mov edx, 0x310a330a mov eax, 0xc00b8000 mov [eax],edx 测试0xc00b8000 地址是不是显存,以此确信页表正常. 14. head.s 中开启分页的代码如下 (见0.0.5.3) @1 ;mov ax,cs ; right, forgot this at first. didn't work :-) @2 ;mov ds,ax ; 代码段描述符是不容许写的!!!!!! @3 call setup_paging ;尽快开启分页 lidt [idt_descr] ; reload idt with kernel idt lgdt [gdt_descr] ; reload gdt with kernel gdt jmp 8:_head_s_pmode_flush ; cs<--8,code segment _head_s_pmode_flush: mov ax,0x10 ; ds<--16, data segment mov ds,ax mov es,ax mov ss,ax mov esp,0x800000;_k_top ;8M stack mov eax,0 @@3 @3 原来在@@3 那个地方, 分页开启很好. 移到@3 那个地方就不工作了. 经查, 原来是@1,@2的问题. 着两句以后相当于用setup.s 的代码段来访问内核 页表, 但是代码段不容许写!!! 所以就死掉了. (见intel 32bit系统体系卷3 3.4.3.1.) 13. 把setup.s 中的gdt 修改成和head.s 一样. (见10.) 12. 考虑到保留开启分页的这段经验. 中止0.0.5.3 版. 保留堆栈在8M, 开启分页. 分页的平滑过渡基于0-8M的恒同映射. 11. 终于开启了分页.(2002.10.2 22:08) 一开始总是死机. 不知为何. 一开始在.text 中定义_kpg_dir, 这样以来增大了内核的尺寸. 又没有控制好 _kpg_dir 的起始地址, setup_paging 用_pgt0+7 来设置属性位当然不正确. 我看过了os.o 的符号表, _kpg_dir 起始地址最后三位不是000.(objdump -t os.o) setup_paging: mov eax,_pgt0+7 ;set present bit/user r/w mov [_kpg_dir],eax 然后查nasm的资料, 使用absolute 把页目录和页表定位于.bss 0x200000(2m). 还调整了fsector.s setup.s, 以避免bug3 的重现. 但是还是没有成功. 以但启动分页(cr0分页使能), 就重启. 据经验, 这可能是页表设置不正确. 果然在setup_paging 中查出几个错误. 但是还是不行, 直到查出最后一个bug. 下面的cmp eax,0x800007, 原来是 cmp eax,0x7ff007, 移植自linux. 但是据这段程序, 第二个页表的最后一项没有设置. 正好还用到了, 因为原来把系统堆栈设置在8M. (见head.s: mov esp,0x800000)(如果本版看不出, 见0.0.5.2) ;设置两个页表, 恒同映射 mov edi,_pgt0 ;+4092 mov eax,0x007 ;/* 8Mb - 4096 + 7 (r/w user,p) */ setup_paging1: mov [edi],eax add eax,0x1000 ;页面号加1(物理页面号) add edi,4 cmp eax,0x800007 jl setup_paging1 后来把内核栈该到.bss中, 证实了这个错误. 10. 像linux 一样绕过x86的分段机制. 见head.s. 9. 加入一个轮询模式的键盘驱动程序,虽然有些bug. 8. 加入vga的光标支持, kbd 的简单演示. 遇到了终生难忘的bug3. 7. 改进makefile , 建立两个目录 长文件名会引起no rule make ... 的错误. 6. 已经再win下编译了, 不再考虑用c++了. 顺便整理一下屏显. 还好,gcc下的c还可以使用// 来注释.^_^ 5.移植到djgpp 使用windows 下的gcc nasm 编译成功,并且使用vmWare调试. 至此,开发方式定型了: sourceinsight, vmvare, djgpp, nasm. ^_^ 下一步考虑使用tc 或者其他的编译器制作setup中的16位程序. (使用C 加强setup.s 的功能) 4.Boot 装载 setup+os 到 0X10000 3.在实模式下跳到 setup, setup 把 os 移动到 0x100000 (1M) 2.然后切换到保护模式,用一句长跳转 jmp dword 8:0x100000 (nasm can mix 16bit and 32bit code) 1.使用了 c++ 主意事项: 2).setup.s 预留了2048(0x800)个字节供以后使用 1).setup.s 预留空间, setup 搬移 os 的位置(紧邻setup之后),应该一致. 总结: 系统从 1M 的地方跑起来,总算像个操作系统的 Loader 了!!!!! Bug 表: Bug 4:(2002.10.2 ) 昨天用times 1024 dd 0 为kpg_dir 预留空间. nasm 中times 用于填充文件, 而不是"预留". 引以为戒. Bug 3:(2002.9.25 22:46) 前几天写完了vga的光标支持. 开始了对keyboared的探索. 写到键盘leds控制,......... 到了键盘reset( 键盘命令ff, 或者控制器命令aa). vmWare 开始报triple fault 错误! 开始漫长无助的探索! 考虑是不是我得键盘不支持复位? 但是为什么! 复位都不支持, 还两种都不支持. 考虑如果键盘的缓冲区没有数据我去读会不会产生这个毛病? 考虑我忽略键盘的应答去发命令会不会产生这个毛病? 考虑键盘正在处理上一个命令, 就发命令会不会死机? .............. 无功而返!!~!!!!!!!!! 今天换了一台计算机, 换了gcc(2.8.1 ---> 3.1), 换了vmWare. 使用以前的0.0.5.1...... 竟然可以了!!!!!!!!! 是谁的错? ...................... 回家一试.........果然成! .............. 想不通. 就反复试几个不同的方式, 发现只要main.c 中调用kb_reset 就玩完...... ............. 发现键盘的灯亮了一个就玩完.......... (忘了怎么该了). 反汇编........ 分析.............. 无功............ 后来主意到同是打开键盘灯的程序其实都试验成功过. 但是往里边加上几句话 就不行了. 换成试验通过的那个kbdledon 也不行. 为什么灯亮了一个就完蛋?因为他发给键盘命令后向下执行......?!.... 为什么加上几句话就不行? triple fault!?............ 我这么试了试: 保留失败的kbdledon , 注释掉kb_reset..........因为一调用kb_reset 就死. ............成功了!................ 想了想....... 是不是太大了?我得核心是不是太大? ............哈哈哈哈哈哈哈哈哈哈哈哈哈! 有可能. fsector 中有加载os的代码, 是有长度限制的. 一看才12k! setup.s 中更小. 0x1500 * 4... 6k. 继续像这个方面测试. 果然如此. 代码少的时候, kb_reset 也可以工作, 两种方式都可以! 改掉fsector 和setup.s 中的代码. 打开所有的代码, 增大os. .......... 一切都没有问题? ..................... triple fault ........有何感想??????? Bug 2 :( 2001.12.? 系统起动后跳到 0x10000 ,但是setup地址gdt_48 定义为 gdt,0x9 改为 gdt,0x1 问题解决. Bug 1 :( 2001.12.15 晚 11:51 bug fixed 先看看这个程序段 ;move os.bin to 0x100000 mov esi,0x010800 ;source offset mov edi,0x100000 ;dest offset mov ecx,0x1500 ;count move: mov ax, word [dword ds:esi] mov word [dword ds:edi],ax dec ecx cmp ecx,0 je endmove inc esi inc edi jmp move ;晕倒,没写这一句,怎么他妈的移动! endmove: 看到我写的注释了吗? 一开始竟然没写上 jmp move 后来把 ;dest offset 改为 0xb8000(直接写屏),发现屏幕上只有 一个红色的 f 在闪动,才查到这个问题 Bug 备忘录: 1) 在切换到保护模式后错误的使用 int 0x10 显示字符串 2) 使用不可写的段描述符 向显存写字符 3) 没有把选择子装入 ds 4)今天遇到几个问题: 2002.9.16 a) 去掉了函数c 中的类变量ab, 就不能出现绿色的Copyright信息. b) 去掉全局的类变量aaa, 就不能启动os. (head.s 不工作). 经过反复试验和观察, 结论如下: b) 不能复现, 认为是djgpp 环境运行不稳定,和着copy/b问题,总之, 没有确切原因. 应该不会再出现. a) 再函数重的类变量, (on stack )会调用初始化函数. 而全局的类 变量则要做未知的特殊处理. 所以导致g_row g_col 特别是g_color 没有处世化. 从而再不确定的位置打印出黑色字,被误认为 是os死掉了. 结论: 1) 使用cpp 做os 对全局类变量要有初始化动作,方法未知. 2) 一定要清bss 3) 全局变量一定要初始化. 定义时赋值好像不起作用,其实起 了作用, 因为位置不会变,只有颜色变黑,从而出现误会. 因为定义时赋值为0(黑). 5) show("Compile at:"__TIME__); 会死机. show("Compile at:"__TIME__".");则不会. 可能__TIME__不用0结尾,或者show有问题. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 早期的几个note: 4.写到软盘时应该去掉pad吧(?!), 否则可能出错. (partcopy 的错, 见修改记录41) 3.修改这个文档,修改 Boot.s和setup.s 的输出信息,使其更加美观. 2002.9.1 2.定制djgpp nasm 在win下编译 1. 开发工具升级到: vmWare 3.1, gcc 3.1. 编译不再中断.(djgpp's mini bug)