orange‘s学习日志

2012-10-10

  *第5章学完了,真像作者说的,“喘口气了”。想到学习第三章时的痛苦,心里开阔了一些:第三章确实是个坎儿,我之前只学了王爽的80X86汇编,没接触过保护模式和80386工作机制,所以第三章劈头盖脸的就是选择子,段描述符,门描述符,gdt,idt,ldt,tss,特权级,堆栈切换等等,彻头彻尾的新知识,而我当时还没意识到(现在想起来这一点才是可怕的),没有心理准备,每天学的都不满意,不开心...不过,虽然方法不对头,但终究一点儿点儿摸过来了,这时才猛发现杨季文那本黑皮书,在这段日子里被翻老了半截。很感谢在网上认识的ganboing,他对我讲学orange's,光有一点8086基础不够,还说到了杨季文的书。对一个自学者而言,书籍有多重要只有他自己知道,这也是在浪费了很多时间与精力后才明白的道理。


2012-10-11

  *第五章末尾,orange已经具备了对0~16号异常和基本中断的响应能力,作者说加上bochs自身的调试功能就有了“双保险”。其实不见得,一旦orange自身的idt生效,bochs就不能在异常发生时捕获到异常信息:实际上bochs对异常的处理能力比我们简陋的idt要强的多,他在系统崩溃时候打印出所有寄存器(包括投影寄存器)信息,而且能找到引发程序崩溃的指令并反汇编出来,对于后一点orange的idt就很难做到。所以确切来说并非”双保险“,而是”弱保险“代替了”强保险“:但这没办法,orange总要有自己的idt,我们能做的,就是把idt做的和bochs一样强甚至更强。


  *5.54小节,lidt之后,idt立即给了我见面礼:

  检查源码,发现是我往8259A送去的ocw是11111110B,时钟中断忘了屏蔽,于是cpu每秒18.2次的在idt里寻找20h中断对应的gate,而我只定义0~16号的gate,cpu寻址偏移显然超出了idt的界限,那就保护异常了。而且是每秒18.2次的保护异常(我在异常处理程序里发送了EOI)。


  *一直想用nasm实现这样的一个宏:%macro dbR 1,实现如下效果

  dbR ax ;宏替代之后实际是db 'ax'

  dbR bx;宏替代之后实际是 db 'bx'

  ...

  我反复尝试了很多次,没做出来。

  PS:2012-10-17 解决

  原来nasm还有%defstr这个功能,这样用:

--------------------------

  %macro dbR 1

  %defstr register %1

  db register

  %endmacro

-------------------------

感谢sholber

2012-10-13 

  *所谓的“平坦模式”,仍属于保护模式下selector:offset寻址方式的范畴,只不过此时段寄存器的对应描述符的段基址都是0。20位的指针加上4kb的粒度,光靠偏移就达到4G的寻址范围。

  c语言用的就是平坦模式(只是不清楚每个c可执行文件是在哪里对ds等段寄存器做的初始化),所以指针的本质是“数据段的偏移值”,因为平坦模式下,由ds指示的数据段基址为0,所以指针的值就对应了线性地址。在汇编里调用c函数时,ds指示的数据段段基址就不一定为0,这时c函数里的c指针很容易寻址失败。看下面一段代码:

test.c

--------------------------------------------

extern void dispInt(int);           //dispInt函数用汇编实现,功能是将接受一个int类型参数,并以16进制打印到控制台。

void print_b8000(){

      dispInt(*(int *)(0xb8000));   //显然,这里是想把屏幕左上角前两个字符对应的“属性值+ascii码”打印出来

      return;

}

-------------------------------------------

接下来在汇编里调用print_b8000函数
---------------------------------------------------------------------------

mdispStrn 'end',ahMod_green       ;这里调用我自己实现的一个宏,功能是在控制台打印一个绿色的字符串“end"

call print_b8000

---------------------------------------------------------------------------

编译,链接并在bochs裸机上运行,效果如图

屏幕左上角前两个字符是绿色的“e”,“n”,预期第二行应输出:0x026E 0265,但实际却是“0x2E00 0007”(这两个数完全不着调),显然c指针寻址失败了。因为汇编环境下调用c函数,按照baseOfDS+valueOfPointer计算指针指示的线性地址,这里valueOfPointer是0xb8000没错,但baseOfDS就不一定是平坦模式下的0了。我们试着在调用c函数之前把让ds指向一个段基址为0的数据段,这样指针应该能正常工作了:

---------------------------------------------------------------------------

mov ax,selector_room_plain     ;从选择子名字可以猜到,它指示的存储段是平坦模式下的4G内存空间,段基址为0

mov ds,ax

mdispStrn 'end',ahMod_green    ;这里调用我自己实现的一个宏,功能是在控制台打印一个绿色的字符串“end"

call print_b8000
---------------------------------------------------------------------------

编译,链接,bochs裸机环境测试:


看第二行,果然是预期的0x026E 0265

做到这一步还是很感慨的:汇编环境下,华丽的指针也走下神坛,露出本来面目。



*c语言里想声明某个函数是在外部用汇编实现的,这样写(以上面的代码为例)

extern void  dispInt(int);

别写成extern dispInt,这样写还不如不写,因为不写的话,链接器还能找到dispInt函数(反正实现该函数的汇编文件里声明过“global dispInt”,有这这一句就够了),可一旦写成“extern dispInt”,链接器就会把dispInt当成变量,报错:called object is not a function


*2012-10-30

今晚近距离的体会到:c语言中的变量即是汇编语言中的标签。我在kernel.c中写:

----------------------------------

extern sec_data;//sec_data是我在汇编文件里定义的标签,源码如下:

                              //sec_data:db 'hello os->world'

                              //我已经知道sec_data对应的地址是0x31D50

dispInt(sec_data);这是我自己实现的一个函数,原型是dispInt(int);

------------------------------------

  结果竟然(现在看来是当然的)输出奇怪的0X6C6C6568,并不是预料中0X31D50.

  这就揭露了c语言变量的本质,程序员眼中的a,b,c变量在编译器看来都只是一个个地址数值(例如上面的sec_data对应地址0X31D50),c语言通常不关心一个变量所标识的地址,只关心它所标识地址处的数值——例如上面的0X6C6C6568正是‘hell'的ascii码。

  所以上面dispInt(sec_data), c编译器会这样传递参数:push [sec_data],而非push sec_data。这就是c语言对待变量的方式。

  我把代码修改了一下:

--------------------------------------

extern sec_data;

dspInt(sec_data);

dispInt(&sec_data);//这里取变量本身的地址

------------------------------------

输出如下图(开头儿的“end”不用管),因为还有其它代码


*2012-11-9

  发现用ld链接的话,目标文件的书写次序似乎有讲究,例如我的makefile里:

kernelRelyO=  ../lib/kernel.o ../lib/kernel_c.o ../lib/dispStr.o ../lib/proc_asm.o

ld -s -Ttext 0x30400 -o ../bin/kernel.elf $(kernelRelyO)

假如把../lib/kernel.o换到其他位置(只要不是第一个),例如写成:

kernelRelyO= ../lib/kernel_c.o ../lib/dispStr.o ../lib/proc_asm.o   ../lib/kernel.o

再make,就发现链接出来的kernel.elf不能执行了(你会发现0X30400地址处的指令根本不是_start标签处的指令)。

我细细想了想,kernel.asm里面存放着入口标签_start,即kernel.o是整个elf文件的入口,是不是因此得把它放在第一位?这似乎牵扯到ld的原理,暂不深究。


*c语言里的NULL常量是定义在stdio.h里面的,gcc不认识null(小写),NULL的字面值其实就是0.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值