• 2005-11-21

    修复磁盘0磁道损坏的基本代码及其详解

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://datasos.blogbus.com/logs/1618031.html

    code segment 'code'
      assume cs:code,ds:code
      org 100h
    start:
      jmp begin
    msg1 db 'Please Insert a DISK in drive A:',0ah,0dh
      db 'Please any key to continue...',0ah,0dh,'$'
    msg2 db 'Format Success!',0ah,0dh,'$'
    msg3 db 'Format Failed!',0ah,0dh,'$'
    id db 0,0,2,2
      db 0,0,4,2
      db 0,0,6,2
      db 0,0,8,2
      db 0,0,10,2
      db 0,0,12,2
      db 0,0,14,2
      db 0,0,16,2
      db 0,0,18,2
      db 0,0,1,2
      db 0,0,3,2
      db 0,0,5,2
      db 0,0,7,2
      db 0,0,9,2
      db 0,0,11,2
      db 0,0,13,2
      db 0,0,15,2
      db 0,0,17,2;格式化地址控制标识
    date db 0eh dup (0);从此处开始的512个字节用于存放引导程序及BPB
    reserved dw 0
        db 200h-0eh-2h dup (0)
    fat db 0f0h,0ffh,0ffh;从此处开始的512个字节用于存放FAT域
      db 200h-3h dup (0)
    bedsector db 18 dup (0);用于记录当前扇区是否是坏扇区
    bedsectorsize db 0;用于记录坏扇区的大小
    bedsectorhead db 0;用于记录坏扇区头的扇区号
    bedsectortail db 0;用于记录坏扇区尾的扇区号
    goodsectorsize db 0;用于记录好扇区的大小
    sumsectorsize db 0;用于记录总扇区的大小
    formattimes db 0;用于记录格式化的次数
    changeid db 1;用于记录需更改的是磁道号还是磁头号
    inccl db 0;用于记录是否出现计算磁头、磁道、扇区号的特殊情况
    reformattimes db 0;用于记录重试次数
    push_a_b_c_dx proc near
      push bx
      mov bx,sp
      inc bx
      inc bx
      xchg ax,ss:[bx];交换ax寄存器和返回地址的值
      push cx
      push dx;到此以完成将ax,bx,cx,dx寄存器的内容压入堆栈
      push ax;将返回地址压入堆栈
      ret;返回调用程序
    push_a_b_c_dx endp;此子程序用于保存4个通用寄存器
    pop_d_c_b_ax proc near
      pop ax;ax的值为返回地址
      pop dx
      pop cx
      mov bx,sp
      inc bx
      inc bx
      xchg ax,ss:[bx];交换原ax寄存器和返回地址的值
      pop bx
      ret
    pop_d_c_b_ax endp;此子程序用于还原4个通用寄存器
    re_disk proc near
      call push_a_b_c_dx
      xor ax,ax
      xor dx,dx
      int 13h
      call pop_d_c_b_ax
      ret
    re_disk endp;此子程序用于复位软盘驱动器
    newint13 proc near
      mov [reformattimes],2;将重复次数写入内存
      re_newint13:
      push ax
      int 13h
      jc newint13_error;功能调用出错则跳转到出错例程
      pop ax
      ret;成功则返回
      newint13_error:
      pop ax
      call re_disk
      dec [reformattimes]
      jnz re_newint13;复位软盘驱动器后,检测重复次数是否为0,不是则跳转
      stc;是则置进位标志,以示功能调用不成功,再返回
      ret
    newint13 endp
    incsector proc near
      call push_a_b_c_dx
      lea bx,id;得到扇区控制地址标识所在的地址
      inc bx
      inc bx;使bx指向扇区控制地址标识中的扇区号部分
      mov cx,18;置循环次数
    loop_inc_id_0_0:
    inc byte ptr [bx];使扇区号加1
      cmp byte ptr [bx],18
      jbe no_above_18;比较扇区号是否小于等于18,是则跳转
      mov byte ptr [bx],1;不是,则将扇区号置1
      no_above_18:
      add bx,4;得到下一个要修改的扇区号的地址
      loop loop_inc_id_0_0;循环修改扇区号
      call pop_d_c_b_ax
      ret
    incsector endp
    format_succ proc near
      lea dx,msg2
      mov ah,9
      int 21h
      ret
    format_succ endp;显示格式化成功信息
    format_fail proc near
      lea dx,msg3
      mov ah,9
      int 21h
      ret
    format_fail endp;显示格式化失败信息
    writeboot proc near
      mov ax,301h
      lea bx,date
      mov cx,1
      xor dx,dx
      call newint13
      ret;将磁盘引导信息及磁盘参数块信息写入逻辑0扇区即0磁头1磁道1扇区
    writeboot endp
    writefat_no_ax proc near
      lea bx,fat;根据公式:磁道号=(保留扇区数+1)/36取商
      re_calculation:;磁头号=((保留扇区数+1)/36的余数)/18取商
      push ax;扇区号=((保留扇区数+1)/36的余数)/18取余数
      mov cl,36;保留扇区数由调用程序存放在ax寄存器中
      div cl;当ax中的值为18的倍数时为特例,需单独处理
      mov ch,al;考虑ax=18的情况,磁道号=0   磁头号=1     扇区号=0
      xor al,al;这将导致软盘驱动器找不到相应的扇区而失败
      xchg al,ah;正确值应为,磁道号=0 磁头号=0 扇区号=18
      mov cl,18;要算出正确值,则要处理这种特殊情况
      div cl;可以将ax的值减1,再参加运算,这时可以算出
      mov dh,al;磁道号=0 磁头号=0 扇区号=17,这显然也是错的
      mov cl,ah;为此可以设立一个标志,当扇区号为0时,将标志位置1
      pop ax;仅当标志位为1时将扇区号加1,并将标志位置0
      cmp cl,0
      jne cl_no_equ_0
      dec ax
      mov byte ptr [inccl],1
      jmp re_calculation
      cl_no_equ_0:
      cmp byte ptr [inccl],1
      jne no_inccl_1
      inc cl
      mov byte ptr [inccl],0
      no_inccl_1:
      mov ax,301h
      xor dl,dl
      call newint13
      ret
    writefat_no_ax endp;此子程序将写FAT表,但差磁道、磁头、扇区号
    writefat proc near
      mov ax,word ptr cs:[reserved]
      inc ax;将保留扇区数加1,得到第一个FAT表的位置
      call writefat_no_ax;调用写FAT表子程序
      jc write_fat_error;出错则退出
      mov ax,word ptr cs:[reserved]
      add ax,10; 将保留扇区数加10,得到第二个FAT表的位置
      call writefat_no_ax;调用写FAT表子程序
      write_fat_error:
      ret
    writefat endp  
    setreserved proc near
      call push_a_b_c_dx
      lea bx,sumsectorsize
      mov al,byte ptr [bx]
      dec bx;此时bx指向goodsectorsize
      mov ah,byte ptr [bx]
      sub al,ah;根据公式:保留扇区数=格式化扇区总数-好扇区数
      xor ah,ah;计算保留扇区数
      lea bx,reserved
      mov word ptr [bx],ax
      call pop_d_c_b_ax
      ret
    setreserved endp
    setbedsectorinformation proc near
      call push_a_b_c_dx
      lea bx,bedsector
      xor ax,ax;ax的值用于判断是要修改坏扇区头(0),还是坏扇区尾(1)
      mov cl,1;cl的值用于循环计数及指示当前扇区号
      findbedsector_0_0:
      cmp byte ptr [bx],1;比较坏扇区标识,1表示是坏扇区,0表示不是坏扇区
      jne no_bedsector_0_0
      push bx;保存坏扇区标识地址
      lea bx,bedsectorsize;得到坏扇区大小地址
      inc byte ptr [bx];将坏扇区大小加1
      inc bx;得到坏扇区头地址
      cmp ax,1
      je no_set_bedsectorhead_0_0;ax=0表示设置坏扇区头
      mov ax,1; ax=1表示设置坏扇区尾
      mov byte ptr [bx],cl;设置坏扇区头
      no_set_bedsectorhead_0_0:
      inc bx;得到坏扇区尾地址
      mov byte ptr [bx],cl;设置坏扇区尾
      pop bx
      no_bedsector_0_0:
      inc cl;扇区号及循环标识加1
      inc bx;得到下一个坏扇区地址
      cmp cl,19
      jne findbedsector_0_0;循环设置坏扇区大小及头尾标识
      call pop_d_c_b_ax
      ret
    setbedsectorinformation endp
    format proc near
      mov ax,501h;格式化软盘,磁头号及磁道号由调用者提供
      lea bx,id
      xor dl,dl
      call newint13
      ret
    format endp
    testsector proc near
      mov ax,401h;测试指定扇区是否完好,磁道号、磁头号及扇区号由调用者提供
      xor dl,dl
      call newint13
      ret
    testsector endp
    changeheadortrack proc near
      call push_a_b_c_dx
      lea bx,changeid;得到更改磁道号或磁头号标识,1表示更改磁头号
      mov al,byte ptr [bx];0表示更改磁道号及磁头号
      xor ah,ah
      xor byte ptr [bx],1;标识取反
      mov cx,18;循环修改磁道或磁头号18次
      lea bx,id;得到扇区控制地址标识起始地址
      add bx,ax;ax的值不是0,就是1,加上起始地址后得到要修改的磁道(AX+0)或磁头地址(AX+1)
      loop_change_0_0:
      cmp al,1
      je changehead_0_0;AL为1则修改磁头号
      inc byte ptr [bx]
      xor byte ptr [bx+1],1;修改磁头及磁道号
      jmp loop_start_0_0
      changehead_0_0:
      xor byte ptr [bx],1;修改磁头号
      loop_start_0_0:
      add bx,4;计算得到下一个要修改的地址
      loop loop_change_0_0
      call pop_d_c_b_ax
      ret
    changeheadortrack endp
    formatandtest proc near
      call format;调用格式化磁道例程
      pushf;保存标志寄存器
      jc format_error_0_0 ;错误则退出
      push cx
      push bx
      mov cx,21;设置初始化坏扇区次数
      lea bx,bedsector
      init_bedsector_0_0:
      mov byte ptr [bx],0;初始化为0
      inc bx;指向下一个要修改的地址
      loop init_bedsector_0_0
      pop bx
      pop cx
      test_next_sector_0_0:
      call testsector;测试扇区
      jnc test_succ_0_0;成功则转移,失败则设置坏扇区信息
      push bx
      lea bx,bedsector
      push cx
      and cx,0ffh;得到测试扇区号
      add bx,cx;得到上次要修改的地址加扇区号
      dec bx;得到本次要修改的地址,减1是因为扇区号从1开始偏移地址由0开始
      mov byte ptr [bx],1;设置此存储单元代表的扇区为坏扇区
      pop cx
      pop bx
      test_succ_0_0:
      inc cl;循环及扇区标识加1
      cmp cl,19
      jne test_next_sector_0_0;循环设置坏扇区
      call setbedsectorinformation;设置坏扇区信息
      call sum_goodsectorsize;计算好扇区的大小
      cmp byte ptr [goodsectorsize],32;比较好扇区数是否大于等于32
      jae goodsecotrsize_jae_32;是则跳转
      add_sumsectorsize_18:
      add byte ptr [sumsectorsize],18;不是则总扇区数加18
      jmp format_error_0_0;完成设置总扇区数
      goodsectorsize_jae_32:
      mov bl,byte ptr [bedsectorhead];是则得到坏扇区头的扇区号
      cmp bl,0;比较是否没有坏扇区
      je add_sumsectorsize_18;是则将总扇区数加18
      dec bl;不是则将坏扇区头的扇区号减1,得到此次格式化所得的好扇区数
      add byte ptr [sumsectorsize],bl;总扇区数加此次所得的好扇区数
      format_error_0_0:
      popf;还原标志寄存器
      ret
    formatandtest endp
    sum_goodsectorsize proc near
      call push_a_b_c_dx
      mov ah,18
      lea bx,bedsectortail
      mov al,byte ptr [bx];得到坏扇区尾,无坏扇区时为0,有则为扇区号
      mov dl,al;dl为坏扇区号
      sub ah,al;计算得到好扇区数
      lea bx,formattimes
      mov al,byte ptr [bx];得到格式化次数
      lea bx,goodsectorsize
      cmp al,1
      jne formattimes_no_1;格式化次数不为1时跳转,此时不要修改好扇区数
      dec ah;格式化次数为1则好扇区数减1,因为有一个保留扇区
      formattimes_no_1:
      cmp dl,0
      jne have_bedsector_0;坏扇区数不为0则跳转,表示用坏扇区
      add byte ptr [bx],ah;无坏扇区则好扇区数加ah,即加18
      jmp sum_goodsectorsize_end
      have_bedsector_0:
      mov dl,byte ptr [bedsectorhead];有坏扇区则的到坏扇区头的扇区号
      dec dl;坏扇区头减1表示在坏扇区头前拥有的好扇区数
      add byte ptr [bx],dl;原来的好扇区数加上这次得到的好扇区数
      cmp byte ptr [bx],32;比较是否有了32个连续的好扇区
      jae sum_goodsectorsize_end;有则计算结束
      cmp ah,0ffh;没有则比较ah的值是否为0FFh,因为格式化次数为1时好扇区数减了1,倘若没有好扇区,则有可能使ah的值为0FFh
      jne ah_no_equ_0ffh
      inc ah;ah为0ffh时加1
      ah_no_equ_0ffh:
      mov byte ptr [bx],ah;将新的连续的好扇区数存放在BX指示的存储单元
      sum_goodsectorsize_end:
      call pop_d_c_b_ax
      ret
    sum_goodsectorsize endp
    begin:
      push cs
      push cs
      pop ds
      pop es;使代码段数据段附加段指向同一段
      mov ah,9
      lea dx,msg1
      int 21h;显示提示信息
      mov ah,7
      int 21h;等待用户击键
      push ds
      xor ax,ax
      mov ds,ax
      mov bx,526h
      mov byte ptr [bx],18;设置软盘参数表的每道扇区数为18
      mov byte ptr [bx+4],0;设置格式化填充字节为0
      pop ds
      call re_disk;软驱复位
      mov cx,14;因总扇区数占用1个字节,而每次格式化将产生18个扇区,将256/18取商则可得到循环测试不能超过14
      reformatandtest:
      lea bx,goodsectorsize
      mov byte ptr [bx],0
      inc bx;bx指向sumsectorsize
      mov byte ptr [bx],0
      inc bx;bx指向formattimes
      mov byte ptr [bx],0
      loop_formatandtest:
      push cx
      lea bx,formattimes
      inc byte ptr [bx];格式化次数加1
      lea bx,id;得到格式化控制地址标识的起始地址(磁道号地址)
      mov ch,byte ptr [bx];设置格式化时用到的磁道号
      inc bx;得到格式化控制地址标识中的磁头号地址
      mov dh,byte ptr [bx];设置格式化时用到的磁头号
      mov cl,1;设置测试磁道时用到的磁道号
      call formatandtest;调用格式化及测试磁道子程序
      jc error;格式化出错则跳转
      lea bx,formattimes
      mov ah,byte ptr [bx];得到格式化次数
      lea bx,goodsectorsize
      mov al,byte ptr [bx];得到好扇区的大小
      cmp al,32;比较好扇区大小是否大于等于32(2*FAT+FCT=32)
      jae loop_formatandtest_end;大于等于则循环格式化及测试完成
      lea bx,bedsectorhead
      mov al,byte ptr [bx];得到坏扇区头的扇区号
      push si
      cmp al,1
      pushf;比较扇区号是否为1,并保存标志寄存器
      cmp ah,1
      pushf;比较格式化次数是否为1,并保存标志寄存器
      pop ax;将标志寄存器的值存入ax
      pop si;将标志寄存器的值存入si
      and ax,si;将ax,si进行与操作,主要用于确定标志位Z是否为0
      push ax;保存标志
      popf;还原标志寄存器
      pop si;还原si寄存器
      jne loop_formatandtest1;标志位Z不为0,则继续格式化并测试磁道
      call incsector;为0表示0道1扇区损坏,则扇区号加1
      pop cx;还原格式化计数器
      loop reformatandtest;循环重新格式化0磁头0磁道;请不要使用jmp指令,因为可能会出现18个扇区全部损坏的情况,那样将会陷入死循环
      loop_formatandtest1:
      call changeheadortrack;更改磁头或磁头、磁道号
      pop cx
      loop loop_formatandtest;循环格式化并测试磁道
      jmp error
      loop_formatandtest_end:
      pop cx;平衡堆栈
      call setreserved;计算保留扇区的大小
      call writeboot;写引导扇区
      jc error
      call writefat;写2个FAT表区
      jc error
      call format_succ;显示格式化成功信息
      jmp program_end
      error:
      call format_fail;显示格式化失败信息
      program_end:
      mov ax,4c00h
      int 21h;程序正常退出
    code ends
      end start

    编辑: 数据恢复


    历史上的今天:


    收藏到:Del.icio.us