| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 4820 人关注过本帖, 2 人收藏
标题:一步步写操作系统之第一步:加载内核到内存中
取消只看楼主 加入收藏
miaowangjian
Rank: 2
等 级:论坛游民
帖 子:34
专家分:30
注 册:2010-1-29
结帖率:100%
收藏(2)
 问题点数:0 回复次数:1 
一步步写操作系统之第一步:加载内核到内存中
      在完成了《自己动手写操作系统》第三章的几个实验(除了分页机制实验外)后。下面就要迈开编写自己的操作系统这万里长征的第一步了。

      在迈出第一步前,有几件事要简要说明一下。
      首先,为将要实现的操作系统命名为:maios。请不要问我这个名称的含义是什么……
      其次,制作此操作系统的目的是什么,或者所此操作系统的用户是谁。简单的说,maios是为自己开发,给自己使用而设计的一个个人专用操作系统。至于要实现的具体功能什么的,在此就不进行详细说明了。
      最后,关于进度安排与完成期限,嗯,进度安排与完成期限是什么意思来着……

      虽说是编写自己的操作系统,但是开头这几步里,基本还是按照《自己动手写操作系统》后面几章的步调慢慢来。

      在这一步里的任务就是想办法将软盘(外存)里的操作系统内核加载到内存里面,并且能够将执行权交给内核。
      我们知道,操作系统的启动步骤一般为:处于引导扇区的boot(引导程序)将loader(装载程序)加载到内存里,由loader完成一些必要的初始准备工作,然后才将操作系统的内核加载到内存的适当位置,最后将执行权交给内核……
      但因为是给自己写的操作系统,一开始也没有什么需要初始化的,在此,我就偷懒一下,省略掉loader(装载程序)。直接在boot(引导程序)完成加载内核以及进入保护模式的工作,然后跳转到内核入口点。在以后如果boot无法满足需要后,再添加loader什么的;)

以下是相关代码:
code:run.c
程序代码:
//文件:run.c
//功能:运行此程序,会自动编译“boot.c”文件与"kernel.c"文件,生成"maios.img"镜像文件,
//      然后启动虚拟机运行镜像文件里的操作系统。在编写与修改代码期间,不必关闭此程序。
//      在改完代码并保存好后,在此程序(控制台)点击回车键后,会再次编译运行修改后的代码。
//      如此,可以方便快捷的在windows下修改与测试所写的操作系统 ;)
//运行:请使用yc09编译器编译此程序。
//作者:miao
//时间:2010-5-13

#define FDISK_SIZE 1474560              //镜像大小:1.4MB

//虚拟机设置
char *maiosSrc =
"megs: 32                                          \n"
"romimage: file=BIOS-bochs-latest, address=0xf0000 \n"
"vgaromimage: VGABIOS-elpin-2.40                   \n"
"floppya: 1_44=maios.img, status=inserted          \n"
"boot: a                                           \n"
"log: maios.out                                    \n"
"mouse: enabled=0                                  \n"
"keyboard_mapping: enabled=1, map=x11-pc-us.map    \n";

//编译指定代码文件并放入镜像指定位置
//filename:要编译的文件名 imgBuffer:保存到的镜像缓冲区
//startIndex:指定起始位置 limitSize:编译后程序限定大小
int CompileFile(char *fileName, byte *imgBuffer, int startIndex, int limitSize)
{
  char *tempBuffer;   //保存编译后程序的临时缓冲区

  //编译fileName里的代码,结果放到tempBuffer中
  int length = YC_CompileCpp(&tempBuffer, fileName, 0, 0);
  if(length <= 0 || length > limitSize)
  {
    printf("文件: %s  中存在一些错误或文件过大(超过%d字节):%d字节\n", fileName,limitSize,length);
    return 1;
  }
  printf("文件: %s  编译成功,大小为:%d字节。\n", fileName, length);

  //将编译好的程序放到镜像引导扇区缓冲区指定起始位置
  memcpy(imgBuffer + startIndex, tempBuffer, length);
  free(tempBuffer);
 
  return 0;
}

int main(int argc, char **argv)
{
  char * filePath = argv[0];             //当前文件夹路径
  char fileName[MAX_PATH];               //用于缓存各个文件名
  //将可执行文件的完整路径去掉文件名,保留文件夹路径
  for( int i = strlen(filePath);filePath[i] != '\\';i--)
    filePath[i] = '\0';
  byte *imgBuffer = new byte[FDISK_SIZE];//镜像缓冲区

_start:
  //编译引导程序并放在引导扇区
  if(CompileFile("boot.c", imgBuffer, 0, 509))
    goto _restart;

  //0000H-01FFH 为FAT引导扇区[第0扇区]  以55 AA标志结束 长度为200H(512)字节
  imgBuffer[510] = 0x55;
  imgBuffer[511] = 0xaa;//标记软盘引导结尾
 
  //编译内核
  if(CompileFile("kernel.c", imgBuffer, 512, 65536*8))
   goto _restart;

  //创建操作系统镜像maios.img
  if(YC_writefile("maios.img", imgBuffer, FDISK_SIZE) != FDISK_SIZE)
  {
    printf("写: %s 文件过程中出现错误。\r\n", fileName);
    goto _restart;
  }

  printf("\n%s 创建成功。\n",  fileName);

  //生成操作系统虚拟机配置文件maios.src
  YC_writefile("maios.src", maiosSrc, strlen(maiosSrc));
  //运行虚拟机
  YC_WinExec(strcat(strcpy(fileName, filePath), "bochs.exe"), "-q -f maios.src");

_restart:
  printf("\n点击回车重新编译运行!\n\n\n");
  while(getchar() != '\n');
  goto _start;
  return 0;
}

code:global.h
程序代码:
//文件:global.h
//功能:操作系统的公共头文件
//作者:miao
//时间:2010-5-13

//定义GDT(全局描述符)属性
#define  DA_32         0x4000 //32位段
#define  DA_DRW        0x92   //存在的可读写数据段属性值
#define  DA_DRWA       0x93   //存在的已访问可读写数据段类型值
#define  DA_CR         0x9A   //存在的可执行可读代码段属性值
#define  DA_C          0x98   //存在的可执行可读代码段属性值
//定义LDT(局部描述符)属性
#define  DA_LDT        0x82   //局部描述符表类型值
//定义门属性
#define  DA_386CGate   0x8c   //386调用门类型
//描述符特权等级(0~3:从高到低)
#define  DA_DPL0       0x00   //描述符特权等级为3
//选择子
#define  SA_TIL        0x4    //将TI位置1,表示是LDT选择子

typedef    unsigned int   t_32;  //4字节
typedef    unsigned short t_16;  //2字节
typedef    unsigned char  t_8;   //1字节
typedef    int            t_bool;//4字节
typedef unsigned int   t_port;//4字节

//存储段描述符/系统段描述符
struct DESCRIPTOR         //共 8 个字节
{
  t_16  limit_low;        //Limit 2字节
  t_16  base_low;         //Base  2字节
  t_8   base_mid;         //Base  1字节
  t_8   attr1;            //P(1) DPL(2) DT(1) TYPE(4)  1字节
  t_8   limit_high_attr2; //G(1) D(1) 0(1) AVL(1) LimitHigh(4) 1字节
  t_8   base_high;        //Base  1字节
};
#define Descriptor(bas,len,attr) { \
               (len) & 0xffff, \
               (bas) & 0xffff, \
               ((bas)>>16)&0xff, \
               (attr) & 0xff, \
               (((attr)>>8) &0xf0) + (((len)>>16) & 0x0f), \
               ((bas) >> 24) & 0xff } \

#define Gate(slector,offset,dCount,attr) { \
               (offset) & 0xffff, \
               slector, \
               (dCount)&0x1f , \
               attr, \
               ((offset)>>16) &0xff, \
               ((offset) >> 24) & 0xff } \


code:boot.c
程序代码:
//文件:boot.c
//功能:操作系统的引导程序,将内核程序(引导扇区后的几个扇区内容)加载到内存0x7f00处(省略loader程序)
//     加载完内核后,进入保护模式并跳转到内核程序的入口点(内存0x7f00处)
//运行:run.exe自动会编译boot.c与生成img并调用Bochs运行此程序。
//作者:miao
//时间:2010-5-13

#define YCBIT 16     //告诉编译器,以16位格式编译程序
#define YCORG 0x7c00 //告诉编译器,在7c00处加载程序
#include "global.h"

//GDT界限,只负责跳转到保护模式,到时会加载新的GDT
DESCRIPTOR label_gdt[] =
{
  //         段基址   段界限   属性
  Descriptor(0,       0,       0),
  Descriptor(0x7f00,  0xfffff, DA_CR | DA_32),//32位代码段,可执行可读
};
//GDT 选择子,根据GDT界限设置偏移量值
#define SelectorCode32 8*1 //指向32位段处

#pragma pack(1)
struct GDT_PTR
{
  unsigned short size;
  void *addr;
};
#pragma pack()
GDT_PTR  GdtPtr = {sizeof(label_gdt), (char*)label_gdt}; //段界限,基地址

asm void main()
{
  mov   ax, cs
  mov   ds, ax
  mov   ax, 0x0
  mov   es, ax
 
    //清屏
  mov   ah, 06h       //屏幕初始化或上卷
  mov   aL, 00h       //AH = 6,  AL = 0h
  mov   bx, 1110h    //蓝色底色
  mov   cx, 0         //左上角: (0, 0)
  mov   dl, 4fh       //第0列
  mov   dh, 1fh       //第0行
  int   10h           //显示中断
 
  //将软盘引导扇区后的几个扇区信息(内核程序)移动到内存中
  mov   al, 0x04    //移动4个扇区
  mov   ah, 0x02    //表示读软盘扇区到内存
  mov   bx, 0x7f00  //移动到的位置:es:bx
  mov   cl, 0x2     //开始扇区(位0-5),磁道(柱面)号的高2位(位6-7)
  mov   ch, 0x0     //磁道(柱面)号的低8位
  mov   dl, 0x0     //驱动号(若为硬盘,位7置1)
  mov   dh, 0x0     //磁头号
  int   0x13
 
  mov   ax, cs
  mov   es, ax
 
  lgdt  GdtPtr        //加载 GDTR

  cli                 //关中断

  //打开地址线A20
  in    al, 92h
  or    al, 00000010b
  out   92h, al

  //准备切换到保护模式,置cr0的PE位为1
  mov   eax, cr0
  or    eax, 1
  mov   cr0, eax

  //真正进入保护模式(内核入口点)
  jmp   dword SelectorCode32:0x0
}

code:kernel.c
程序代码:
//文件:kernel.c
//功能:内核程序,目前功能仅为显示一个字符串。用以证明成功加载内核到内存并运行内核代码。
//运行:run.exe自动会编译boot.c与生成img并调用Bochs运行此程序。
//作者:miao
//时间:2010-5-13

#define YCBIT 32  //告诉编译器,以32位格式编译程序
#define YCORG 0x0 //此值会对在编译时对变量函数等产生地址基址偏移量,简单起便,设置为0
#include "global.h"

#define ProtecAddr 0x7f00 //进入保护模式后的程序基址
#define retf db 0xcb      //因为yc09编译器不识别指令retf,使用宏直接定义指令retf

asm void DispStr();//显示一个字符串,需要先设置好esi指向字符串地址,edi指向字符串的起始位置

//GDT 选择子
#define SelectorCode32 8*1 //指向32位段处代码段,可执行可读
#define SelectorVideo  8*2 //指向显存首地址
#define SelectorData32 8*3 //指向32位段处,这样,在程序中的变量就可以读写了
#define SelectorLDT    8*4 //指向LDT,通过这个跳转到局部任务
//门选择子
#define SelectorCallGateTest 8*5 //指向门任务

//GDT界限,注意,这个与boot.c中的GDT不同,从boot.c跳转过来后会立即载入这个新的GDT
DESCRIPTOR label_gdt[] =
{
  //         段基址      段界限   属性
  Descriptor(0,                0, 0),
  Descriptor(ProtecAddr, 0xfffff, DA_CR | DA_32),  //32位代码段,可执行可读
  Descriptor(0xb8000,     0xffff, DA_DRW),         //显存地址段,可读可写
  Descriptor(ProtecAddr, 0xfffff, DA_DRW | DA_32), //令32位代码段的变量可以读写
};

#pragma pack(1)
struct GDT_PTR
{
  t_16 size;
  void *addr;
}GdtPtr = {sizeof(label_gdt), (char*)&label_gdt + ProtecAddr}; //段界限,基地址
#pragma pack()

char Msg1[] = "I am in kernel now!";

//内核入口点
asm void main()
{
  lgdt  cs:GdtPtr           //加载新的GDTR

  mov   eax, SelectorVideo
  mov   gs, ax              //视频段选择子(目的)
  mov   eax, SelectorData32 //令32位代码段的变量(printPlace)可以读写
  mov   ds, ax

  //下面显示一个字符串(显示已经到达保护模式信息)
  mov   esi, &Msg1  //源数据偏移
  mov   edi, ((80 * 0 + 0) * 2) //目的数据偏移。屏幕第0行, 第0列。
  call  DispStr

  died:
  jmp   died
}

//显示一个字符串,需要先设置好esi指向字符串地址,edi指向字符串的起始位置
asm void DispStr()
{
  mov   ah, 14h     //蓝底红字(ah = 14h)
//循环逐个将字符串输出
_DispStr:
  mov   al, ds:[esi]//因为可读,才能用cs指向当前段的Msg1字符串
  inc   esi
  cmp   al, '\0'    //判断是否字符串结束
  jz    _stop
  mov   gs:[edi], ax
  add   edi, 2
  jmp   _DispStr
_stop:             //显示完毕
  ret
}

搜索更多相关主题的帖子: 内存 操作系统 加载 内核 
2010-05-14 01:20
miaowangjian
Rank: 2
等 级:论坛游民
帖 子:34
专家分:30
注 册:2010-1-29
收藏
得分:0 
我看的是初版,后来发现于源已经出了第二版《Orange S:一个操作系统的实现》,很贵加上初版我还没看完,就没舍得马上再买了
我之前看了一下《LINUX内核完全注释》的前几章,但前阵子都花时间在找编译器和解释器相关的资料上了(主要是为了我的毕业设计)。

之所以目前内核加载结构使用两层,一方面是为了加快启动速度,好方便修改几句代码后马上能运行查看到修改效果。另一方面是,图形界面还没有纳入考虑范围,loader除了完成加载内核外,还没有其他事可干。添加一个loader的话,就要考虑使用fat12之类的文件系统,但关于文件系统,我还在考虑中。所以打算将loader推到后面再弄。

我打算先在字符界面下先弄个简单的tty ,接着再弄个简单的编辑器。之后试着为这个操作系统实现一个专用的编译器。
在这整个期间,要穿插的完成一个内存管理系统和一个文件系统
2010-05-14 16:52
快速回复:一步步写操作系统之第一步:加载内核到内存中
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.028827 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved