毕业到现在感觉自己一直在走弯路,浪费了太多时间,学习东西也总坚持不下去,这次计划从汇编开始一步步学习计算机知识,以求还能找回自己的初心。
第8章 硬盘和显卡的访问与控制 本章代码分为用户程序与加载程序,大致流程计算机上电ROM-BIOS将读取硬盘主引导区内容(硬盘主引导区放的是加载程序),将它加载到内存地址0x0000:0x7c00,然后跳转到这里执行,然后加载程序将用户程序加载到特定地址,更新用户程序的段地址并跳转到用户程序执行。
SECTION mbr align=16 vstart=0x7c00 /*vstart=0x7c00是由于程序被加载到0x0000:0x7c00这样设置,这个段的所有标号的偏移地址 从0x7c00开始,这样引用时就不用加上0x7c00。*/ 设置加载程序的栈空间,也在0x0000这个段中,从0xFFFF往下增长,还是留了很多余地。 mov ax,[cs:phy_base] //cs这里应该是0x0000,phy_base是从0x7c00开始计算的段偏移地址,里面存的是0x10000 mov dx,[cs:phy_base+0x02] //ax中是0x0000,dx中存的是0x0001 mov bx,16 //用这个32位数除以16得到段地址传递给ds数据段寄存器与附加段寄存器,为0x1000,这里的cs还是很有必要的, div bx //phy_base是标号所以是偏移地址,需要添加程序段地址,只是这里恰好是0x0000。 mov ds,ax mov es,ax read_hard_disk_0: //从硬盘读取一个扇区,端口寄存器是8位的,每次接收一个字节。硬盘接口的数据端口是16位端口,所以要读取一个 mov [bx],ax //扇区的数据设置cx为256,循环读取数据放在ds:bx位置,读内存中的数通过地址引用时会自动添加ds数据段寄存 //器,这里为bx,为偏移地址,段地址位ds,为0x1000。 //要加载的程序头部放着与加载程序的协议信息,其中包括用户程序总长度,入口点偏移地址,入口点所在代码段的汇编地址,段重定位表信息。 //首先通过读前四个字节的数据除以512获得用户程序所占的扇区数目。然后通过调用read_hard_disk_0并改变ds的值将用户程序的剩余部分加载进内存。 mov dx,[bx+0x02] mov ax,[bx] //首先获取入口程序所在代码段的汇编地址 call calc_segment_base //调用calc_segment_base得到入口程序目前所在的段地址,能这样计算的原因是段都是16位对齐 calc_segment_base: push dx add ax,[cs:phy_base] //adc数值相加并加上进位这样可以计算32位加法,计算完成后低16位右移4位,并将高16位循环右移4位即低4位移 adc dx,[cs:phy_base+0x02] //动到高4位,然后与0xf000与只保留高四位,用这个结果与ax与,这样ax中则为入口程序目前的段地址。 shr ax,4 ror dx,4 and dx,0xf000 or ax,dx pop dx ret //然后将此段地址替换回去,接下来取出段重定位表的项数,循环执行上述动作,分别将每个段的目前的段地址计算出来并替换回去以使用 //户程序执行时能够在段中跳转与引用段中数据。 jmp far [0x04] //然后此位置(实际为ds:0x0004)为入口点的偏移地址,使用far即为远跳转指的是跳转的地方不在同一个 //段,则会从10004取出2个字分别放入CS与IP寄存器中,第一个字为刚才我们替换的入口程序的段地 //址,第二个字为偏移地址(偏移地址不用替换,不论段被加载到哪里偏移地址是不会变的),这样就将 //控制权转交给用户程序。 用户程序主要是 用户程序从start开始。 mov ax,[stack_segment] //程序跳转过来栈寄存器还指向原来的栈,这里设置到用户程序自己的栈() mov ss,ax mov sp,stack_end mov ax,[data_1_segment] //设置到用户程序自己的数据段 mov ds,ax mov bx,msg0 call put_string //显示第一段信息 push word [es:code_2_segment] mov ax,begin //由于retf只是从栈中取出两个字分别初始化CS和IP,所以我们可以自己主动压栈来使程序跳转到我们想要执行的任何地方, push ax //这里因为我们已经跟新了各个段的段地址,所以也可以直接将我们更新位置的数据压进去。 retf //这里转移到代码段2执行 SECTION code_2 align=16 vstart=0 //定义代码段2(16字节对齐)[stack_segment] begin: push word [es:code_1_segment] mov ax,continue push ax //同样的方法又跳转到代码段1,偏移地址为continue标号相对于程序段1的偏移地址 retf //转移到代码段1continue接着执行 continue: mov ax,[es:data_2_segment] //将段寄存器DS切换到数据段2 mov ds,ax mov bx,msg1 call put_string //显示第二段信息 jmp $ //结束堵塞在这大致流程就是这样,自己走一遍加深下印象,看到哪里就写到哪里就有点乱,O(∩_∩)O。