用网络卡从并口上启动Linux

发表于:2007-07-04来源:作者:点击数: 标签:
作者:raoxianhong 代码: 1、到底想干什么 了解 Linux 的启动过程,制作一个自己的Linux启动程序,可以增加对Linux的了解,还能学习PC机的启动机制,增进对计算机结构的了解,增强对Linux内核学习的信心。 也可以在某些专用产品中使用(比如专用的 服务器 )

  作者:raoxianhong
  
  代码:
  
  1、到底想干什么
   了解Linux的启动过程,制作一个自己的Linux启动程序,可以增加对Linux的了解,还能学习PC机的启动机制,增进对计算机结构的了解,增强对Linux内核学习的信心。
  也可以在某些专用产品中使用(比如专用的服务器)。为此,我尝试在原来代码的基础上修改制作了一个用网络卡从并口上启动Linux的程序,以博一笑,其中有许多问题值得研究。
  2、Linux对启动程序的要求
   Linux(bzImage Kernel)对启动程序的要求比较简单,你只要能够建立一个启动头(setup.S),
  给出一些信息,然后将kernel(/usr/src/linux/arch/i386/boot/compressed/bvmlinux.out)调到
  绝对地址0x100000(1M地址处),如果有initrd,则将它调到内存高端(离0x100000越远越好,比如如果
  initrd小于4M,就可以将它调到地址0xB00000,即12M处,相信现在已经很少有少于16M内存的机器了),
  然后执行一些初始化操作,跳到内核处就行了。
   当然,说起来容易做起来还有点麻烦,以下分几个问题解释。
  3、PC机开机流程--启动程序放在何处
   PC机加电后,进入实模式,先进行自检,然后初始化各个总线扩展设备(ISA, EISA,PCI,AGP),
  全部初始化做完后,从当前启动设备中读一个块(512字节)到07C0:0000处,将控制转到该处。
   了解这个过程,我们可以决定将启动程序放在何处:
   1)放在启动设备的MBR(主启动记录中),比如磁盘的启动扇区。这是一般的启动方式。
   2)放在总线扩展设备的扩展rom中,比如网卡的boot rom就行,这里制作的启动程序就是放在网卡中,可以支持 16K字节。
   3)哪位高手能够修改ROMBIOS,让BIOS在做完初始化后不要马上从启动设备读数据,而是调用一段外面 加入的程序(2K字节就够了,当然也必须与修改后的BIOS一起烧在BIOS ROM中),就可以从BIOS启动!
   4)先启动一个操作系统,再在此操作系统中写启动程序(比如lodlin16就是从DOS中启动Linux,好象中软
   提供了一个从Windows下启动Linux的启动程序)。
  4、操作系统放在何处
   操作系统(一般内核在500K-1M之间,加上应用程序可以控制在2M以内,当然都经过压缩了)的数据选择余地就大了,
  可以从软盘、硬盘、CDROM、网络、磁带机、并口(软件狗上烧个内核和应用程序?)、串口(外接你的设备)、 USB设备(?)、PCI扩展卡、IC卡等等上面来读;各位还有什么意见,提醒提醒。有位老兄说实在不行可以用键盘启动,每次启动时把内核敲进去,还有int 16h支持呢,做起来也不难,应该是最节省的方案了。
   反正一个原则是,在启动程序中能够从该设备上读就行了,这里最简单的就是并口了,简单的端口操作,不需 要任何驱动程序支持,不需要BIOS支持,比磁盘还简单(磁盘一般使用int 13h,主要是计算柱面啊、磁头啊、磁道啊、扇区啊好麻烦,幸好有现成的源代码,可以学习学习)。
   好了,我们挑个简单的方案,将启动代码(bootsect.S+setup.S)放到网络卡的boot rom中,内核数据和应用数据放到另外一台计算机上,用并口提供。下面谈谈几个相关的问题。
  5、将数据移动到绝对地址处
   第一个问题,我们得到数据,因为是在实模式下,所以一般是放在1M地址空间内,怎样将它移动到指定的地方去,
  在setup.S 的源代码中,使用了int 15h(87h号功能)。这里将该段代码稍加改动,做了些假设,列到下面:
  流程是:
   if (%cs:move_es==0)/*由于使用前move_es初始化为0,因此这是第一次调用,此时es:bx是要移动的数据
   存放处bx=0,es低四为位为零表示es:bx在64K边界上,fs的低8位指定目的地地址,
   也以64K字节为单位,用不着那么精确,以简化操作*/
   {
   将es右移四位,得到64K单位的8位地址(这样一来,最多只能将数据移动到16M以下了),作为源数据
   描述符中24位地址的高8位,低16位为零。
   将fs的低8位作为目的地的描述符中24位地址的高8位,同样,它的低16位为零。
   将es存放在move_es中,es自然不会是零,因此以后再调用该例程时就进行正常的移动操作了。
   ax清零返回。
   }
   else
   {
   if (bx==0)/*bx为零,表示数据已经满64K了,应该进行实际的移动*/
   {
   调用int15h 87h号功能,进行实际的数据移动(64K, 0x8000个16字节块)。
   目的地址(24位)高8位增一,往后走64K
   ax = 1
   return;
   }
   else
   {
   ax = 0;
   return;
   }
   }
  # we will move %cx bytes from es:bx to %fs(64Kbytes per unit)
  # when we first call movetohigh(%cs:move_es is zero),
  # the es:bx and %edx is valid
  # we configure the param first
  # follow calls will move data actually
  # %ax return 0 if no data really moved, and return 1 if there is data
  # really to be moved
  #
  movetohigh:
   cmpw $0, %cs:move_es
   jnz move_second
   # at this point , es:bx(bx = 0) is the source address
   # %edx is the destination address
   movb $0x20, %cs:type_of_loader
   movw %es, %ax
   shrw $4, %ax
   movb %ah, %cs:move_src_base+2
   movw %fs, %ax
   movb %al, %cs:move_dst_base+2
   movw %es, %ax
   movw %ax, %cs:move_es
   xorw %ax, %ax
   ret # nothing else to do for now
  move_second:
   xorw %ax, %ax
   testw %bx, %bx
   jne move_ex
   pushw %ds
   pushw %cx
   pushw %si
   pushw %bx
   movw $0x8000, %cx # full 64K, INT15 moves words
   pushw %cs
   popw %es
   leaw %cs:move_gdt, %si
   movw $0x8700, %ax
   int $0x15
   jc move_panic # this, if INT15 fails
   movw %cs:move_es, %es # we reset %es to always point
   incb %cs:move_dst_base+2 # to 0x10000
   popw %bx
   popw %si
   popw %cx
   popw %ds
   movw $1, %ax
  move_ex:
   ret
  move_gdt:
   .word 0, 0, 0, 0
   .word 0, 0, 0, 0
  move_src:
   .word 0xffff
  move_src_base:
   .byte 0x00, 0x00, 0x01 # base = 0x010000
   .byte 0x93 # typbyte
   .word 0 # limit16,base24 =0
  move_dst:
   .word 0xffff
  move_dst_base:
   .byte 0x00, 0x00, 0x10 # base = 0x100000
   .byte 0x93 # typbyte
   .word 0 # limit16,base24 =0
   .word 0, 0, 0, 0 # BIOS CS
   .word 0, 0, 0, 0 # BIOS DS
  move_es:
   .word 0
  move_panic:
   pushw %cs
   popw %ds
   cld
   leaw move_panic_mess, %si
   call prtstr
  move_panic_loop:
   jmp move_panic_loop
  move_panic_mess:
   .string "INT15 refuses to aclearcase/" target="_blank" >ccess high mem, giving up."
  6、用并口传输数据
   用并口传输数据,可以从/usr/src/linux/driver.net/plip.c中抄一段,我们采用半字节协议,
  并口线连接参考该文件。字节收发过程如下:
  #define PORT_BASE 0x378
  #define data_write(b) outportb(PORT_BASE, b)
  #define data_read() inportb(PORT_BASE+1)
  #define OK 0
  #define TIMEOUT 1
  #define FAIL 2
  
  int sendbyte(unsigned char data)
  {
   unsigned char c0;
   unsigned long cx;
   data_write((data & 0x0f));
   data_write((0x10 | (data & 0x0f)));
   cx = 32767l * 1024l;
   while (1) {
   c0 = data_read();
   if ((c0 & 0x80) == 0)
   break;
   if (--cx == 0)
   return TIMEOUT;
   }
   data_write(0x10 | (data >> 4));
   data_write((data >> 4));
   cx = 32767l * 1024l;
   while (1) {
   c0 = data_read();
   if (c0 & 0x80)
   break;
   if (--cx == 0)
   return TIMEOUT;
   }
   return OK;
  }
  int rcvbyte(unsigned char * pByte)
  {
   unsigned char c0, c1;
   unsigned long cx;
   cx = 32767l * 1024l;
   while (1) {
   c0 = data_read();
   if ((c0 & 0x80) == 0) {
   c1 = data_read();
   if (c0 == c1)
   break;
   }
   if (--cx == 0)
   return TIMEOUT;
   }
   *pByte = (c0 >> 3) & 0x0f;
   data_write(0x10); /* send ACK */
   cx = 32767l * 1024l;
   while (1) {
   c0 = data_read();
   if (c0 & 0x80) {
   c1 = data_read();
   if (c0 == c1)
   break;
   }
   if (--cx == 0)
   return TIMEOUT;
   }
   *pByte |= (c0 << 1) & 0xf0;
   data_write(0x00); /* send ACK */
   return OK;
  }
  为了能够在setup.S下收字符,特将字符接收子程序该为AT&T汇编语法
  (也没有什么好办法,在DOS下用TURBO C 2.0将上述代码编译成汇编代码,然后手工转换成AT&T格式,据说有程序可以自动进行这样的转换,
  有谁用过请指教):
  rcvbyte:
   pushw %bp
   movw %sp, %bp
   subw $6, %sp
   movw $511, -2(%bp)
   movw $-1024, -4(%bp)
   jmp .L13
  .L15:
   movw $889, %dx
   inb %dx, %al
   movb %al, -6(%bp)
   testb $128, -6(%bp)
   jne .L16
   inb %dx, %al
   movb %al, -5(%bp)
   movb -6(%bp), %al
   cmpb -5(%bp), %al
   jne .L17
   jmp .L14
  .L17:
  .L16:
   subw $1, -4(%bp)
   sbbw $0, -2(%bp)
   movw -2(%bp), %dx
   movw -4(%bp), %ax
   orw %ax, %dx
   jne .L18
   movw $1, %ax
   jmp .L12
  .L18:
  .L13:
   jmp .L15
  .L14:
   movb -6(%bp), %al
   shrb $1, %al
   shrb $1, %al
   shrb $1, %al
   andb $15, %al
   movw 4(%bp), %bx
   movb %al, (%bx)
   movb $16, %al
   movw $888, %dx
   outb %al, %dx
   movw $511, -2(%bp)
   movw $-1024, -4(%bp)
   jmp .L19
  .L21:
   movw $889, %dx
   inb %dx, %al
   movb %al, -6(%bp)
   testb $128, %al
   je .L22
   inb %dx, %al
   movb %al, -5(%bp)
   movb -6(%bp), %al
   cmpb -5(%bp), %al
   jne .L23
   jmp .L20
  .L23:
  .L22:
   subw $1, -4(%bp)
   sbbw $0, -2(%bp)
   movw -2(%bp), %dx
   movw -4(%bp), %ax
   orw %ax, %dx
   jne .L24
   movw $1, %ax
   jmp .L12
  .L24:
  .L19:
   jmp .L21
  .L20:
   movb -6(%bp), %al
   shlb $1, %al
   andb $240, %al
   movw 4(%bp), %bx
   orb %al, (%bx)
   xorw %ax, %ax
   movw $888, %dx
   outb %al, %dx
   jmp .L12
  .L12:
   movw %bp, %sp
   popw %bp
   ret
  能够收发字符还不行,作为协议,总得知道数据的起始和结束,也应该进行简单的检错。这里采用
  字符填充方式进行数据包编码,用‘\’表示转义字符,数据包头用\H表示,数据包结束用\T表示
  如果数据中有'\',则用\\表示(从printf的格式串中学来的),数据包后面跟一个字节的校验和,
  这样就可以收发数据包了,具体程序如下:
  int rcvpack(unsigned char * pData, int * pLength)
  {
   int ret;
   int length;
   unsigned char checksum;
   int maxlength;
   int status;
   maxlength = *pLength + 1;
   if (maxlength<=0)
   return FAIL;
   if (pData == NULL)
   return FAIL;
   checksum = 0;
   length = 0;
   status = 0;
   while (1)
   {
   unsigned char ch;
   int count;
   count = 10;
   while (1)
   {
   if ((ret = rcvbyte(&ch)) != OK)
   {
   count--;
   if (count==0)
   {
   printf("\nReceive byte timeout\n");
   return ret;
   }
   }
   else
   break;
   }
   switch (status)
   {
   case 0:
   {
   if (ch == '\\')
   {
   status = 1;
   }
   }
   break;
   case 1:
   {
   if (ch == 'H')
   status = 2;
   else
   status = 0;
   }
   break;
   case 2:
   {
   if (ch == '\\')
   {
   status = 3;
   }
   else
   {
   length ++;
   if (length>maxlength)
   {
   printf("Buffer overflow(%d>%d)\n", length, maxlength);
   return FAIL;
   }
   *pData++ = ch;
   checksum += ch;
   }
   }
   break;
   case 3:
   {
   if (ch == '\\')
   {
   length++;
   if (length>maxlength)
   {
   printf("Buffer overflow (%d>%d)\n", length, maxlength);
   return FAIL;
   }
   checksum += ch;
   *pData++ = ch;
   status = 2;
   }
   else
   if (ch =='T')
   {
   unsigned char chk;
   *pLength = length;
   if (rcvbyte(&chk)!=OK)
   return FAIL;
   if (checksum==chk)
   {
   return OK;
   }
   else
   {
   printf("ERROR: Checksum is nozero(%d-%d)\n", checksum,chk);
   return FAIL;
   }
   }
   else
   {
   printf("ERROR: a '\\' or 'T' expected('%c')!\n ", ch);
   return FAIL;
   }
   }
   }
   }
  }
  int sendpack(unsigned char * pData, int length)
  {
   int ret;
   unsigned char checksum;
   checksum = 0;
   if (length<=0)
   return OK;
   if ((ret = sendbyte('\\')) != OK)
   return 1;
   if ((ret = sendbyte('H')) != OK)
   return 2;
   while (length>0)
   {
   unsigned char ch;
   ch = *pData++;
   checksum += ch;
   if ((ret = sendbyte(ch)) != OK)
   return 3;
   if (ch == '\\')
   {
   if ((ret = sendbyte(ch)) != OK)
   return 4;
   }
   length--;
   }
   if ((ret = sendbyte('\\')) != OK)
   return 5;
   if ((ret = sendbyte('T')) != OK)
   return 6;
   if ((ret = sendbyte(checksum)) != OK)
   return 7;
   return OK;
  }
  同样,也将rcvpack改成AT&T汇编(减少了几个printf语句):
  
  chbuffer:
   .byte 0
  overflow:
   .string "Buffer overflow..."
  rcvpack:
   pushw %bp
   movw %sp, %bp
   subw $12, %sp
   pushw %si
   movw 4(%bp), %si
   movw 6(%bp), %bx
   movw (%bx), %ax
   incw %ax
   movw %ax, -6(%bp)
   cmpw $0, -6(%bp)
   jg .L26
   leaw overflow, %si
   call prtstr
   movw $2, %ax
   jmp .L25
  .L26:
   orw %si, %si
   jne .L27
   movw $2, %ax
   jmp .L25
  .L27:
   movb $0,-8(%bp)
   movw $0, -10(%bp)
   movw $0, -4(%bp)
   jmp .L28
  .L30:
   movw $10, -2(%bp)
   jmp .L31
  .L33:
  # movw -4(%bp), %ax
  # addb $'0', %al
  # call prtchr
   leaw chbuffer, %ax
   pushw %ax
   call rcvbyte
   popw %cx
   movw %ax, -12(%bp)
   orw %ax, %ax
   je .L34
   decw -2(%bp)
   cmpw $0, -2(%bp)
   jne .L35
   movw -12(%bp), %ax
   jmp .L25
  .L35:
   jmp .L36
  .L34:
   jmp .L32
  .L36:
  .L31:
   jmp .L33
  .L32:
   pushw %si
   leaw chbuffer, %si
   movb (%si), %al
   movb %al, -7(%bp)
   popw %si
  # call prtchr
   movw -4(%bp), %ax
   cmpw $3, %ax
   jbe .L58
   jmp .L56
  .L58:
   cmpw $0, %ax
   je .L38
   cmpw $1, %ax
   je .L40
   cmpw $2, %ax
   je .L43
   cmpw $3, %ax
   je .L47
   jmp .L56
  .L38:
   cmpb $92, -7(%bp)
   jne .L39
   movw $1, -4(%bp)
  .L39:
   jmp .L37
  .L40:
   cmpb $72, -7(%bp)
   jne .L41
   movw $2, -4(%bp)
   jmp .L42
  .L41:
   movw $0, -4(%bp)
  .L42:
   jmp .L37
  .L43:
   cmpb $92, -7(%bp)
   jne .L44
   movw $3, -4(%bp)
   jmp .L45
  .L44:
   incw -10(%bp)
   movw -10(%bp), %ax
   cmpw -6(%bp), %ax
   jle .L46
   movw $2, %ax
   jmp .L25
  .L46:
   movb -7(%bp), %al
   movb %al, (%si)
   incw %si
   movb -7(%bp), %al
   addb %al, -8(%bp)
  .L45:
   jmp .L37
  .L47:
   cmpb $92, -7(%bp)
   jne .L48
   incw -10(%bp)
   movw -10(%bp), %ax
   cmpw -6(%bp), %ax
   jle .L49
   movw $2, %ax
   jmp .L25
  .L49:
   movb -7(%bp), %al
   addb %al, -8(%bp)
   movb -7(%bp), %al
   movb %al, (%si)
   incw %si
   movw $2, -4(%bp)
   jmp .L50
  .L48:
   cmpb $84, -7(%bp)
   jne .L51
   movw -10(%bp), %ax
   movw 6(%bp), %bx
   movw %ax, (%bx)
   leaw chbuffer, %ax
   pushw %ax
   call rcvbyte
   popw %cx
   orw %ax, %ax
   je .L52
   movw $2, %ax
   jmp .L25
  .L52:
   movb -8(%bp), %al
   cmpb chbuffer, %al
   jne .L53
   xorw %ax, %ax
   jmp .L25
   jmp .L54
  sChecksumFailed:
   .string "Checksum error!"
  .L53:
   leaw sChecksumFailed, %si
   call prtstr
   movw $2, %ax
   jmp .L25
  .L54:
   jmp .L55
  .L51:
   movw $2, %ax
   jmp .L25
  .L55:
  .L50:
  .L56:
  .L37:
  .L28:
   jmp .L30
  .L29:
  .L25:
   popw %si
   movw %bp, %sp
   popw %bp
   ret
  好了,万事具备了,先用上面的c代码写另外一台计算机上的“服务”程序(也用来测试),这台
  计算机运行DOS,用TURBO C 2.0编译运行:
  运行时将initrd.img和内核编译后的/usr/src/linux/arch/i386/boot/compressed/bvmlinux.out
  拷贝到该计算机的c:\下,然后带参数 s c:\bvmlinux.out c:\initrd.img运行即可。
  至于启动程序,还得进行少许修改,才能烧到boot rom 中,见后面的说明。
  int main(int argc, char* argv[])
  {
   FILE* pFile;
   int count = 2;
   if (argc<3)
   {
   printf("Usage testspp [s | r] \n");
   return 1;
   }
   while(count {
   if (argv[1][0] == 's')
   pFile = fopen(argv[count], "rb");
   else
   pFile = fopen(argv[count], "wb");
   if (pFile==NULL)
   {
   printf("Can't open/create file %s\n", argv[2]);
   return 2;
   }
   if (argv[1][0]=='r')/*receive*/
   {
   unsigned long filesize;
   char buffer[10244];
   int length;
   /*get filelength */
   length = 10244;
   printf("Receiving filesize package\n");
   while( (rcvpack(buffer, &length)!=OK) && (length!=4))
   length = 10244;
   filesize = *(long*)buffer;
   printf("file size is:%ld\n", filesize);
  
   while (filesize>0)
   {
   length = 10244;
   if (rcvpack(buffer, &length) != OK)
   {
   printf("Receive data package failed\n");
   return 0;
   }
   if (length>0)
   fwrite(buffer, 1, length, pFile);
   filesize-=length;
   printf("\r%ld Bytes Left ", filesize);
   }
   }
   else/*send*/
   {
   unsigned long filesize;
   /*send file length*/
   unsigned long stemp;
   int ret;
   fseek(pFile, 0, 2);
   filesize = ftell(pFile);
   fseek(pFile, 0, 0);
   printf("\nfile size is:%ld\n", filesize);
   /*
   while ((ret = sendpack((char *)&filesize, 4)) != OK)
   {
   printf("send file size failed(%d)\n", ret);
   }
   */
   while (filesize>0)
   {
   char buffer[10240];
   long size;
   int ret;
   size = fread(buffer, 1, 10240, pFile);
   if ((ret = sendpack(buffer, size)) != OK)
   {
   printf("Send data package failed(%d)\n", ret);
   return 0;
   }
   filesize -= size;
   printf("\r\t%ld Bytes Left", filesize);
   }
   }
   fclose(pFile);
   count++;
   }/*while*/
   return 0;
  }
  5、对bootsect.S的修改
   目前的bootsect.S ,主要的问题是,它是从软盘上读数据,将这些代码换成对rcvpack的调用即可,
  另外,它不支持调入initrd,应该增加相应的代码。问题在于,bootsect.S中没有那么多空间来放rcvpack
  相关的代码(毕竟只有512字节,当然,如果烧在boot rom中,就不存在这个问题了,但是用软盘调试时
  就不行了,因此干脆编制load_kernel和load_initrd放在setup.S中,然后在bootsect.S中进行回调即可。
  bootsect.S 修改如下(只给出修改部分):
  .....
  .....
  ok_load_setup:
   call kill_motor
   call print_nl
  # Now we will load kernel and initrd
  loading:
  # 先打印Loading字符
   movw $INITSEG, %ax
   movw %ax, %es # set up es
   movb $0x03, %ah # read cursor pos
   xorb %bh, %bh
   int $0x10
   movw $22, %cx
   movw $0x0007, %bx # page 0, attribute 7 (normal)
   movw $msg1, %bp
   movw $0x1301, %ax # write string, move cursor
   int $0x10 # tell the user we're loading..
  load_kernel_img:
  # 将load_kernel函数的指针放到0x22C处这里进行调用就行了(软盘启动过程中,此前已经将setup.S
  # 从磁盘上调到bootsect.S,即0x0200之后,注意setup.S的头部是一张表,这里“提前”消费了)
  # 0x22C is the load kernel routine
   bootsect_readimage = 0x22C
   lcall bootsect_readimage
  load_initrd_img:
  # 将load_initrd函数的指针放到0x220处
  # 0x220 if the load initrd routine
   bootsect_readinitrd = 0x220
   lcall bootsect_readinitrd
  # After that (everything loaded), we jump to the setup-routine
  # loaded directly after the bootblock:
   ljmp $SETUPSEG, $0
  ......
  ......
  6、对setup.S的修改
   对setup.S进行修改,主要是:修改setup.S头部,增加load_kernel和load_initrd函数等,具体如下。
   修改setup.S头部如下(为好看,这里删除了原来的部分注释):
  start:
   jmp trampoline
   .ascii "HdrS" # header signature
   .word 0x0202 # header version number (>= 0x0105)
  realmode_swtch: .word 0, 0 # default_switch, SETUPSEG
  start_sys_seg: .word SYSSEG
   .word kernel_version # pointing to kernel version string
  type_of_loader: .byte 0
  loadflags:
  LOADED_HIGH = 1
   .byte LOADED_HIGH # 只支持bzImage
  setup_move_size: .word 0x8000
  code32_start: # here loaders can put a different
   .long 0x100000 # 0x100000 = default for big kernel
  ramdisk_image: .long 0xB00000 # ramdisk 调到12M处
  ramdisk_size: .long 0 # 由load_initrd来设置长度
  bootsect_kludge:
   .word load_initrd, SETUPSEG #0x220, 放置load_initrd函数的指针
  heap_end_ptr: .word modelist+1024 pad1: .word 0
  cmd_line_ptr: .long 0
  load_kernel_call:
   .word load_kernel, SETUPSEG
  trampoline: call start_of_setup
   .space 1024
  load_kernel和load_initrd:
  load_imsg:
   .byte 13, 10
   .string "Load INITRD from PARPort(378)"
  load_kmsg:
   .byte 13, 10
   .string "Load Kernel From PARPort(378)"
  reading_suc:
   .string "."
  reading_failed:
   .string " failed"
  read_len:
   .word 0, 0
  read_total:
   .word 0, 0
  read_buffer:
   # 如何在AT&T语法中完成intel语法中的 db 1280 dup(0),那位请指教
   # AT&T汇编的语法何处寻?
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
   .string "012345678901234567890123456789012345678901234567890123456789"
  load_initrd:
   pushw %ds
   pushw %es
   pushw %cs
   popw %ds
   pushw %cs
   popw %es
   cld
   leaw load_imsg, %si
   call prtstr # 打印提示
   movw $0x1000, %ax
   movw %ax, %es
   xorw %bx, %bx
   movw $0x00B0, %ax # initrd数据先调到0x1000:0000处,
   # 满64K即移动到12M(0xB00000)处
   movw %ax, %fs
   movw $0, %cs:move_es
   movl $0, %cs:read_total
   call movetohigh # 初始化数据移动部分
   call .ld_img # 从并口上读入一个文件并移动到指定位置
   movl %cs:read_total, %eax
   movl %eax, %cs:ramdisk_size # 设置ramdisk_size和ramdisk_image
   movl $0x00B00000, %eax
   movl %eax, %cs:ramdisk_image
   popw %es
   popw %ds
   lret
  load_kernel:
   pushw %ds
   pushw %es
   pushw %cs
   popw %ds
   pushw %cs
   popw %es
   cld
   leaw load_kmsg, %si
   call prtstr
   movw $0x1000, %ax
   movw %ax, %es
   xorw %bx, %bx
   movw $0x0010, %ax
   movw %ax, %fs
   movw $0, %cs:move_es
   movl $0, %cs:read_total
   call movetohigh
   call .ld_img
   popw %es
   popw %ds
   lret
  .ld_img:
  .ld_nextpack:
   pushw %bx
   pushw %es
   leaw read_len, %si
   movw $1124, %ax
   movw %ax, (%si)
   pushw %si
   leaw read_buffer, %ax
   pushw %ax
   movw %bx, %ax
   call rcvpack # 调用rcpack接收一个数据包read_buffer中
   popw %cx
   popw %cx
   popw %es
   popw %bx
   cmpw $0, %ax # 成功?
   je .ld_suc
   leaw reading_failed, %si
   call prtstr
  .ld_panic:
   jmp .ld_panic # 失败则死循环
  .ld_suc:
   leaw read_buffer, %si
   movw %bx, %di
   movw $256, %cx # move 1024 bytes
   rep
   movsl # 从read_buffer移动到es:bx处,强制假定一个数据包长度
   # 就是1024字节,最后一个数据包除外。
   addw $1024, %bx # 更新bx, 如果bx加到零,则表示已经满64K,后面的调用中
   call movetohigh # 进行实际的数据移动
   movw %ax, %dx #
   cmpw $0, %ax # 如果进行了64K数据移动,就打印一个'.'
   je .ld_1
   leaw reading_suc, %si
   call prtstr
  .ld_1:
   leaw read_len, %si
   xorl %eax, %eax
   movw (%si), %ax
   addl %eax, %cs:read_total
   cmpw $1024, %ax # 更新收到数据总字节数,如果收到的字节数少于1024,则表示
   # 收到最后一个数据包,这得冒点风险,万一最后一个数据包刚好
   # 是1024字节,怎么办好呢?赌一把吧!
   jb .ld_lastestpack
   jmp .ld_nextpack # 接着接收下一个数据包
  .ld_lastestpack:
   # 最后一个数据包收到后,不见得满64K,此时应该强制数据移动
   cmpw $0, %dx
   jne .ld_exit
   xorw %bx, %bx
   call movetohigh
  .ld_exit:
   ret
  7、用软盘进行调试,将启动程序烧到bootrom中
   好了,大功告成,对内核进行配置,然后make bzImage,将bvmlinux.out拷贝到“服务器”上,建立
  initrd也放在“服务器”上,然后放张软盘在软驱中,dd if=/usr/src/linux/arch/i386/boot/bzImage of=
  /dev/fd0 count=32将bootsect.S+setup.S部分拷贝到软盘上,重新启动(先连接好并口线)。启动后再在
  “服务器”上启动文件“服务”程序,终于可以将Linux从并口上启动了!
   做少量调整(主要是去掉读setup.S部分的代码),即可以将此bzImage的前8(16?)K写在一个文件中,
  处理成boot rom映象,烧到boot rom中,插到网络卡上,启动机器即可。这就是用网络卡从并口上启动Linux。
  
  

原文转自:http://www.ltesting.net