系统引导过程分析与编制

类别:编程语言 点击:0 评论:0 推荐:

   读了2004年《程序员》杂志中的《程序员田园》板块中相关的文章之后,对此颇有感触。因此也想对此作一番小小的研究。

其实说穿了,系统引导很简单:

1、  启动电源之后,硬件完成它所应完成的工作;

2、  BIOS按照CMOS中保存的设置,按照启动顺序读取启动盘中的0柱面0磁道1扇区,将512字节装载到内存的0:7C00处并运行,并把硬件的控制权交给软件;判断一个扇区是否是引导扇区的依据是它是否以055AA结束;

3、  这512字节引导记录的任务就是完成必需的初始化工作,然后装载、运行操作系统的内核。

 

分析完原理之后,让我们来动动手,作一番尝试。

该过程所需要的工具如下:

1、  NASM或MASM汇编语言编译器,我选择NASM;自从接触到这个Open Source的编译器以后,我就把MASM扔在一旁了。其主要原因,是因为它可以编写跨平台的程序。下载连接:http://sourceforge.net/projects/nasm

2、  一个虚拟机软件,VMWare或者VirtualPC均可。用虚拟机做实验,可免去一次又一次重启的麻烦,也可以保护自己的爱机;

3、  Ultra Edit + ASM语法高亮显示包。用汇编语言写程序,没有什么好的IDE,这个我认为是最完美的组合了。也有人推荐用Edit Plus的,但由于个人喜好的原因,我一直使用这个。http://www.ultraedit.com,词库文件可以免费下载。

 

先来做一个最简单的测试,它完成的任务仅仅是引导系统后在屏幕上显示红色的’A’:

打开Ultra Edit,输入如下代码:

      

ORG 7C00H          ;设置程序装载时的偏移量

 

       MOV AH,0            ;调用BIOS中断,设置显示模式

       MOV AL,1

       INT 10H

      

       MOV AH,09H        ;调用BIOS中断,显示字符

       MOV AL,'A'

       MOV BH,0

       MOV BL,4

       MOV CX,1

       INT 10H

             

       JMP $                   ;停止程序运行

       DW 0AA55H         ;扇区结束标志

 

然后编译这段程序,nasm boot.asm –f bin –o boot.bin。

-f开关符是指定输出方式为flat,-o是指定输出文件。Flat方式不会为源程序添加任何其它信息,只是照翻源程序,将其翻译成机器码。不过NASM默认的输出方式就是flat,所以这个开关符也可以省略。

因为其不足512字节,所以我用UltraEdit的Hex编辑模式将其打开,插入489个字节,将其凑满512字节。

接下来的任务就是准备一张软盘,用绝对扇区读写工具将其写入磁盘;幸好,手头的MINIX源码光盘中有这么一个工具,可以将其编译一下拿来用,源代码如下:

 

/* fdvol.c - A stripped down version of the MINIX vol program.  It takes

 * a file and breaks into into floppy-sized chunks, writing each one raw

 * to a floppy.

 *

 *      Usage:          fdvol file drive KB [slow]

 *

 *    Examples:       fdvol 1440 a: foo.taz             # 1.4 MB floppy a:

 *                  fdvol 1200 b: foo.taz             # 1.2 MB floppy b:

 *                      fdvol slow 360 a: foo.taz       # old machine

 *                      fdvol 1440 a: foo bar            # concatenate

 *

 * The optional 'slow' parameter forces the program to write in units of 3

 * sectors. Folk tradition has it that this works around some buggy BIOSes.

 *

 * This code borrows heavily from Mark Becker's RaWrite program.

 */

 

 

#include <sys/types.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <ctype.h>

 

#define TEST 0

 

#if !TEST

#include <alloc.h>

#include <bios.h>

#include <dir.h>

#include <dos.h>

#include <io.h>

#endif

 

#define FALSE       0

#define TRUE (!FALSE)

 

#define SECTORSIZE   512

 

#define    RESET    0

#define    LAST      1

#define    READ     2

#define    WRITE   3

#define    VERIFY  4

#define    FORMAT       5

 

int done;

char buffer[18*SECTORSIZE];    /* do I/O in units of up to 18 sectors */

char testbuf[SECTORSIZE]; /* to do a test read of the first sector */

 

 

int handler(void)

{

/* Catch CTRL-C and CTRL-Break. */

 

  done = 1;

  return(0);

}

 

void msg(char (*s))

{

/* Print an error message and quit. */

 

  fprintf(stderr, "%s\n", s);

  _exit(1);

}

 

void Error(int status, int cyl, int head, int sector)

{

/* Identify the error code with a real error message. */

 

 

  fprintf(stderr, "\nError occured while writing cyl %d, head=%d, sector=%d\n", cyl,head,sector+1);

  switch (status) {

    case 0x00:      msg("Operation Successful");                            break;

    case 0x01:      msg("Bad command");                               break;

    case 0x02:      msg("Address mark not found");                        break;

    case 0x03:      msg("Attempt to write on write-protected disk"); break;

    case 0x04:      msg("Sector not found");                           break;

    case 0x05:      msg("Reset failed (hard disk)");                  break;

    case 0x06:      msg("Disk changed since last operation");           break;

    case 0x07:      msg("Drive parameter activity failed");                break;

    case 0x08:      msg("DMA overrun");                                break;

    case 0x09:      msg("Attempt to DMA across 64K boundary");          break;

    case 0x0A:      msg("Bad sector detected");                       break;

    case 0x0B:      msg("Bad track detected");                         break;

    case 0x0C:      msg("Unsupported track");                         break;

    case 0x10:      msg("Bad CRC/ECC on disk read");                   break;

    case 0x11:      msg("CRC/ECC corrected data error");               break;

    case 0x20:      msg("Controller has failed");                       break;

    case 0x40:      msg("Seek operation failed");                      break;

    case 0x80:      msg("Attachment failed to respond");                 break;

    case 0xAA:     msg("Drive not ready (hard disk only");                     break;

    case 0xBB:      msg("Undefined error occurred (hard disk only)");      break;

    case 0xCC:     msg("Write fault occurred");                       break;

    case 0xE0:      msg("Status error");                                  break;

    case 0xFF:      msg("Sense operation failed");                           break;

  }

  exit(1);

}

 

 

void main(int argc, char *argv[])

{

  int disknr = 1, fdin, count, drive, head, cyl, status, sector;

  int max_cyl, chunk, nsectors, ct;

  long offset, drive_size, r, cyl_size;

  char *p, c;

  int slow;

  int kbsize;

  char **files;

  int nfiles, i;

 

#if !TEST

  /* Catch breaks. */

  ctrlbrk(handler);

#endif

 

#if 0 /* Do we have to clear the screen? */

  fprintf(stderr, "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");

#endif

 

  if (argc > 1 && strcmp(argv[1], "slow") == 0) {    /* Lousy BIOS? */

       slow = 1;

       argc--;

       argv++;

  } else {

       slow = 0;

  }

 

  /* Check the arguments for validity. */

  if (argc < 4)

        msg("Usage: fdvol [slow] #kilobytes drive-letter file1 [file2 ...]");

 

  kbsize = atoi(argv[1]);

 

  p = argv[2];

  c = *p;

  if (c == 'a' || c == 'A')

       drive = 0;

  else if (c == 'b' || c == 'B')

        drive = 1;

  else

       msg("fdvol: Second parameter must be drive, either   a:   or   b:");

 

  files = argv + 3;

  nfiles = argc - 3;

 

  switch(kbsize) {

      case 360:

        cyl_size = 9*2*SECTORSIZE;      /* bytes/cylinder */

        max_cyl = 39;                   /* zero-based counting */

       drive_size = cyl_size * (max_cyl+1);

        chunk = (!slow ? 9 * SECTORSIZE : 3 * SECTORSIZE);

        nsectors = chunk/SECTORSIZE;

        break;

 

      case 720:

        cyl_size = 9*2*SECTORSIZE;      /* bytes/cylinder */

        max_cyl = 79;                   /* zero-based counting */

       drive_size = cyl_size * (max_cyl+1);

        chunk = (!slow ? 9 * SECTORSIZE : 3 * SECTORSIZE);

        nsectors = chunk/SECTORSIZE;

        break;

 

      case 1200:

        cyl_size = 15*2*SECTORSIZE;          /* bytes/cylinder */

        max_cyl = 79;                   /* zero-based counting */

       drive_size = cyl_size * (max_cyl+1);

        chunk = (!slow ? 15 * SECTORSIZE : 3 * SECTORSIZE);

        nsectors = chunk/SECTORSIZE;

        break;

 

      case 1440:

        cyl_size = 18*2*SECTORSIZE;     /* bytes/cylinder */

        max_cyl = 79;                   /* zero-based counting */

       drive_size = cyl_size * (max_cyl+1);

        chunk = (!slow ? 18 * SECTORSIZE : 3 * SECTORSIZE);

        nsectors = chunk/SECTORSIZE;

        break;

 

      default:

       msg("fdvol: First parameter must be one of: 360, 720, 1200, or 1440");

  }

 

#if !TEST

  biosdisk(RESET, drive, 0, 0, 0, 0, testbuf);

#endif

 

/*

 * Start writing data to diskette until there is no more data to write.

 * Optionally read and write in units of 3 sectors.  Folk tradition says

 * that this makes fewer buggy BIOSes unhappy than doing a whole track at a

 * time.

 */

  offset = 0;

  i = 0;

  fdin = -1;

 

  while(1) {

       if (done > 0) {

              if (done == 1) msg("User abort");

#if !TEST

              biosdisk(READ, drive, 0, 0, 1, 1, testbuf); /* Retract head */

#endif

                fprintf(stderr, "Done.                                                      \n");

              exit(done == 1 ? 1 : 0);

       }

 

       /* Until a chunk is read. */

       count = 0;

       while (count < chunk) {

              if (fdin == -1) {                   /* open next file */

#if !TEST

                     _fmode = O_BINARY;

#endif

                     fdin = open(files[i], O_RDONLY);

                     if (fdin < 0) {

                            perror(files[i]);

                            exit(1);

                     }

              }

                                                 /* read from file */

              ct = read(fdin, buffer + count, chunk - count);

              if (ct < 0) {

                     perror(files[i]);

                     exit(1);

              }

              if (ct == 0) {                       /* end of file */

                     close(fdin);

                     fdin = -1;

 

                     /* choose next file */

                     if (++i >= nfiles) break;  /* no more files */

              }

              count += ct;

       }

 

       if (count == 0) {                         /* absolute EOF */

              done = 2;

              continue;

       }

 

       if (count < chunk) {                           /* pad last track */

              /* Pad out buffer with zeroes. */

              p = &buffer[count];

              while (p < &buffer[chunk]) *p++ = 0;

              done = 2;

       }

 

       r = offset % drive_size;

       if (r == 0) {

              /* An integral number of diskettes have been filled. Prompt. */

                fprintf(stderr, "Please insert formatted diskette #%d in drive %c, then hit Enter%c\n", disknr, c, 7);

              disknr++;

#if !TEST

              while(bioskey(1) == 0) ;       /* wait for input */

              if ((bioskey(0) & 0x7F) == 3) exit(1);  /* CTRL-C */

                biosdisk(READ,  drive, 0, 0, 1, 1, testbuf); /* get it going */

#endif

 

       }

       /* Compute cyl, head, sector. */

       cyl = r/cyl_size;

       r -= cyl * cyl_size;

       head = (r < cyl_size/2 ? 0 : 1);

       r -= head * cyl_size/2;

       sector = r/SECTORSIZE;

      

        fprintf(stderr, "Track: %2d  Head: %d  Sector: %2d    File offset: %ld\r",

                cyl, head, sector+1,offset);

#if !TEST

        status = biosdisk(WRITE, drive, head, cyl, sector+1, nsectors, buffer);

        if (status != 0) Error(status, cyl, head, sector);

#else

       write(1, buffer, chunk);

#endif

        offset += chunk;

   }

}

 

然后,用这个工具将刚才编译成功的bin文件写入软盘就成功了。J

如果要对其进行更深入的研究,可以参阅相关书籍。

在http://sourceforge.net/上面搜索NASM的时候,我偶然看到了一个FDOS的项目,可以免费获得。它是“在一张磁盘上的DOS系统”,支持常用DOS命令、FAT12、TSR等。它的文档中有很详细的引导记录说明,我将在下一篇中对其源代码作一些剖析。

另外,网上也能找到MSDOS系统的汇编源代码,有兴趣的同志也可以拿来研究一下。当然,拿Linux的引导代码来研究也未尝不可,但刚开始的时候,不应好高骛远,打击自己的信心,还是从简单的开始吧。J

本文地址:http://com.8s8s.com/it/it26409.htm