和婉吧 关注:4贴子:303
  • 3回复贴,共1
*^_^*


1楼2006-10-08 16:05回复

    此处显存的概念说得不是很清楚,有必要进一步解释一下。假设屏幕由80列×25行组成,那么第一行就需要160字节,其中一个字节用于表示字符,另外一个字节用于表示字符的属性。如果要在第三行显示某一字符的话,就要跳过显存的第0和1字节(它们是用于显示第1列的),第2和3字节(它们是用于显示第2列的),然后把需要显示字符的ASCⅡ码值入第4字节,把字符的属性写入第5字节。 


    把程序写至启动扇区 


    下面写一个C程序,把我的操作系统写入软盘第一扇区。程序内容如下: 

    #include /* unistd.h 需要这个文件 */ 
    #include /* 包含有read和write函数 */ 
    #include 
    int main() 

    char boot_buf[512]; 
    int floppy_desc, file_desc; 
    file_desc = open("./boot", O_RDONLY); 
    read(file_desc, boot_buf, 510); 
    close(file_desc); 
    boot_buf[510] = 0x55; 
    boot_buf[511] = 0xaa; 
    floppy_desc = open("/dev/fd0", O_RDWR); 
    lseek(floppy_desc, 0, SEEK_CUR); 
    write(floppy_desc, boot_buf, 512); 
    close(floppy_desc); 



    首先,以只读模式打开boot文件,然后在打开文件时把文件描述符复制到file_desc变量中。从文件中读取510个字符,或者读取直到文件结束。在本例中由于文件很小,所以是读取至文件结束。然后关闭文件。 

    最后4行代码打开软盘驱动设备(一般来说是/dev/fd0)。使用lseek找到文件开始处,然后从缓冲中向软盘写512个字节。 

    在read、write、open和lseek的帮助页中,可以看到与函数所有有关的参数及其使用方法。程序中有两行比较难懂: 

    boot_buf[510] = 0x55; 
    boot_buf[511] = 0xaa; 


    该信息是用于BIOS的,如果它识别出该设备是一个可启动的设备,那么在第510和511的位置,该值就应该是0x55和0xaa。程序会把文件boot读至名为boot_buf的缓冲中。它要求改变第510和第511字节,然后把boot_buf写至软盘之上。如果执行代码,软盘上的前512字节就包含了启动代码。最后,把文件存为write.c。 


    编译运行 


    使用下面的命令把文件变为可执行文件: 

    as86 boot.s -o boot.o 
    ld86 -d boot.o -o boot 
    cc write.c -o write 


    首先将boot.s文件编译成目标文件boot.o,然后将该文件连接成最终的boot文件。最后C程序编译成可执行的write文件。 

    插入一个空白软盘,运行以下程序: 

    ./write 


    重新启动电脑,进行BIOS的界面设置,并且把软盘设为第一个启动的设备。然后插入软盘,电脑从软盘上启动。 

    启动完成后,在屏幕上可以看到一个字母A(蓝底白字),启动速度很快,几乎是在瞬间完成。这就意味着系统已经从我们制作的软盘上启动了,并且执行了刚才写入启动扇区的程序。现在,它正处在一个无限循环的状态。所以,如果想进入Linux,必需拿掉软盘,并且重启机器。 

    至此,这个操作系统就算完成了,虽然它没有实现什么功能,但是它已经可以启动机器了。 

    下一期我将在这个启动扇区程序里加入一些代码,使它可以做一些比较复杂的事情(比如使用BIOS中断、保护模式切换等等)。


    3楼2007-03-02 04:12
    回复

      第二个扇区中的代码如下所示(把这些代码保存至文件sbect2.s之中): 

      entry start 
      start: 
      mov ah,#0x03 
      xor bh,bh 
      int 0x10 

      mov cx,#26 
      mov bx,#0x0007 
      mov bp,#mymsg 
      mov ax,#0x1301 
      int 0x10 

      loop1: jmp loop1 
      mymsg: 
      .byte 13,10 
      .ascii "Operating System is Loading......" 


      上面代码将被加载至段地址为0x500处,并且被执行。在这段代码中,使用了中断10h来获取目前的光标位置,然后显示信息。 

      从第3行到第5行用于得到目前光标的位置,在此中断10h选用的是功能3。然后,清除了bh寄存器的内容,并把字符串送至ch寄存器中。在bx中,我们送入了页码及显示的属性。此处,我们想要在黑背景上显示白色的字符。然后,把要显示字符的地址送到bp之中,信息由两个字节组成,其值分别为13的10,它们分别对应回车和LF(换行)的ASCⅡ值。接下来是一个由29个字符组成的串;在下面实现的功能是输出字符串然后移动光标;最后是调用中断,然后进入循环。 

      C程序代码 
      C程序的源代码如下所示,将其存储为write.c文件。 

      #include /* unistd.h needs this */ 
      #include /* contains read/write */ 
      #include 
      int main() 

      char boot_buf[512]; 
      int floppy_desc, file_desc; 
      file_desc = open("./bsect", O_RDONLY); 
      read(file_desc, boot_buf, 510); 
      close(file_desc); 
      boot_buf[510] = 0x55; 
      boot_buf[511] = 0xaa; 
      floppy_desc = open("/dev/fd0", O_RDWR); 
      lseek(floppy_desc, 0, SEEK_SET); 
      write(floppy_desc, boot_buf, 512); 
      file_desc = open("./sect2", O_RDONLY); 
      read(file_desc, boot_buf, 512); 
      close(file_desc); 
      lseek(floppy_desc, 512, SEEK_SET); 
      write(floppy_desc, boot_buf, 512); 
      close(floppy_desc); 



      在上一期中,我曾经介绍过如何操作能启动的软盘。现在这一个过程稍微有点不同,首先把由bsect.s编译出来的可执行文件bsect拷贝至软盘的启动扇区。然后再把由sect2.s产生的可执行文件sect2拷贝至软盘的第二个扇区。 

      把上述文件置于同一目录之下,然后分别对其进行编译,方法如下所示: 

      as86 bsect.s -o bsect.o 
      ld86 -d bsect.o -o bsect 

      对sect2.s文件重复以上的操作,得出可执行文件sect2。编译write.c,插入软盘后执行write文件,命令如下所示: 

      cc write.c -o write 
      ./write 

      下一步我们要做的事情 

      从软盘启动以后,可以看到显示出来的字符串。这是使用了BIOS中断来完成的。下一期要做的事情是在这个操作系统中实现实模式向保护模式的转换。


      5楼2007-03-02 04:13
      回复
        自己动手写操作系统(三) 
        作者:伊梅 


        在上两期中(自己动手写操作系统1,2),我向大家讲述了如何使用Linux 提供的开发工具在软盘的启动扇区写一些代码,以及如何调用BIOS的问题。现在,这个操作系统已经越来越接近当年Linus Torvalds的那个具有"历史意义"的Linux内核了。因此,要马上把这个系统切换到保护模式之下。 

        什么是保护模式 

        自从1969年推出第一个微处理器以来,Intel处理器就在不断地更新换代,从8086、8088、80286,到80386、80486、奔腾、奔腾 Ⅱ、奔腾4等,其体系结构也在不断变化。80386以后,提供了一些新的功能,弥补了8086的一些缺陷。这其中包括内存保护、多任务及使用640KB以上的内存等,并仍然保持和8086家族的兼容性。也就是说80386仍然具备了8086和80286的所有功能,但是在功能上有了很大的增强。早期的处理器是工作在实模式之下的,80286以后引入了保护模式,而在80386以后保护模式又进行了很大的改进。在80386中,保护模式为程序员提供了更好的保护,提供了更多的内存。事实上,保护模式的目的不是为了保护程序,而是要保护程序以外的所有程序(包括操作系统)。 

        简言之,保护模式是处理器的一种最自然的模式。在这种模式下,处理器的所有指令及体系结构的所有特色都是可用的,并且能够达到最高的性能。 

        保护模式和实模式 

        从表面上看,保护模式和实模式并没有太大的区别,二者都使用了内存段、中断和设备驱动来处理硬件,但二者有很多不同之处。我们知道,在实模式中内存被划分成段,每个段的大小为64KB,而这样的段地址可以用16位来表示。内存段的处理是通过和段寄存器相关联的内部机制来处理的,这些段寄存器(CS、DS、 SS和ES)的内容形成了物理地址的一部分。具体来说,最终的物理地址是由16位的段地址和16位的段内偏移地址组成的。用公式表示为: 

        物理地址=左移4位的段地址+偏移地址。 

        在保护模式下,段是通过一系列被称之为"描述符表"的表所定义的。段寄存器存储的是指向这些表的指针。用于定义内存段的表有两种:全局描述符表(GDT)和局部描述符表(LDT)。GDT是一个段描述符数组,其中包含所有应用程序都可以使用的基本描述符。在实模式中,段长是固定的(为64KB),而在保护模式中,段长是可变的,其最大可达4GB。LDT也是段描述符的一个数组。与GDT不同,LDT是一个段,其中存放的是局部的、不需要全局共享的段描述符。每一个操作系统都必须定义一个GDT,而每一个正在运行的任务都会有一个相应的LDT。每一个描述符的长度是8个字节,格式如图3所示。当段寄存器被加载的时候,段基地址就会从相应的表入口获得。描述符的内容会被存储在一个程序员不可见的影像寄存器(shadow register)之中,以便下一次同一个段可以使用该信息而不用每次都到表中提取。物理地址由16位或者32位的偏移加上影像寄存器中的基址组成。实模式和保护模式的不同可以从图1和图2中很清楚地看出来。 


        图1 实模式的寻址 


        图2 保护模式下的寻址 


        图3 段描述俯的格式 

        此外,还有一个中断描述符表(IDT)。这些中断描述符会告诉处理器到那里可以找到中断处理程序。和实模式一样,每一个中断都有一个入口,但是这些入口的格式却完全不同。因为在切换到保护模式的过程中没有使用到IDT,所以在此就不多做介绍了。 

        进入保护模式 

        80386 有4个32位控制寄存器,名字分别为CR0、CR1、CR2和CR3。CR1是保留在未来处理器中使用的,在80386中没有定义。CR0包含系统的控制标志,用于控制处理器的操作模式和状态。CR2和CR3是用于控制分页机制的。在此,我们关注的是CR0寄存器的PE位控制,它负责实模式和保护模式之间的切换。当PE=1时,说明处理器运行于保护模式之下,其采用的段机制和前面所述的相应内容对应。如果PE=0,那么处理器就工作在实模式之下。 

        切换到保护模式,实际就是把PE位置为1。为了把系统切换到保护模式,还要做一些其它的事情。程序必须要对系统的段寄存器和控制寄存器进行初始化。把PE位置1后,还要执行跳转指令。过程简述如下: 
        


        6楼2007-03-02 04:14
        回复