Cosmos里程碑1--.net/C#开源操作系统学习系列四
2011-03-28 12:06 Hundre 阅读(4969) 评论(10) 编辑 收藏 举报使用的代码包为cosmos14395.zip,以下把COSMOS里程碑1简称为COSMOS MS1或 MS1 COSMOS
申请:由于本篇内容涉及的技术的东西太多,小弟水平有限,如有思想认识上的错误,欢迎大家指正。
对于IBM-PC兼容机的硬件启动流程我目前理解是这样的:通电->BIOS初始化与自检->CPU初始化->加载硬盘的0磁道0柱面1扇区的内容进内存并开始执行这一部分的内容。到此,硬件的过程—注意是硬件--就走完了,剩下的就都交给0磁道0柱面1扇区的程序来执行,这里面的东西就是我们程序员要做的东西了。
这里如果没有操作系统的话,那么从0磁道0柱面1扇区开始执行的程序就是我们自己写的程序,但这样一次只能执行一个,很浪费硬件资源,如果要换别的程序的话还得重新启动一次硬件,所以我们希望这个首先执行的程序可以一直运行,然后再向这个程序发出命令,让这个程序来执行我们想要执行的程序,这样就可以不用重启硬件就能运行多个程序了,慢慢地这个我们对其发出命令让其运行别的程序的程序就变成了我们所谓的操作系统了。
随着操作系统的发展,现代操作系统的一般都需要做以下工作:
1. 进程管理
2. 内存管理
3. IO管理
4. 文件系统管理
(以上为参考《操作系统的设计与实现》一书)
对于我们这次要分析的COSMOS MS1,那么他实现了以上哪些功能呢,严格来说都没实现(第一个里程碑版本嘛,能运行就可以了),要说的话可以说实现了一些IO管理上的功能,如向显示器上输出字符和相应键盘输入。下面通过一副图来说一下COSMOS
MS1操作系统(即PC机软件部分)的启动过程:
由图中可以看到,0磁道0柱面1扇区是一个叫做BOOT(也有叫做BOOTLOADER的)的程序,由它把操作系统的内核加载到内存中,这里再注意是操作系统内核,不是整个操作系统。那么为什么不一开始就加载操作系统而是要通过BOOT来完成加载呢?
因为对于硬盘来说,1个扇区只有512字节,早期操作系统的活比较少的时候可以这么加载,可现代的操作系统已经远远大于512字节,所以只能先运行一个加载程序,然后由加载程序来完成操作系统的加载。
那么操作系统加载进来之后干什么呢,这个就得了解下CPU了。对于x86系列的CPU,操作系统会要开启CPU的段式寻址、分页存储、对CPU进行设置然后进入保护模式等(x86的CPU默认情况是只在保护模式下运行的,并且不使用段式寻址和分页的功能),对于别的系列的CPU,如ARM等,也需要根据CPU产品的说明文档对CPU进行相应的设置来开启CPU所具有的特定功能。在这之后,会在内存上开辟出操作系统自己的栈空间和堆空间,初始化机器的IO。这些做完了之后,对于图形界面的操作系统,就会运行图形界面程序,对于命令行界面的操作系统就会去运行命令行程序(就是常听到的Shell)。然后就是我们平时看到的操作系统的界面了,这就算是启动完成了。
理论到这里就结束了,开始我们的实践吧。开始之前先把启动项目换成Cosmos.Shell.Guess
再说明一下,本版本使用VS2010编译之后似乎无法正常运行,这里是我个人的理解:里程碑1的程序还是VS2008的项目,如果使用VS2010的朋友在打开项目之后会提示升级,升级完后编译会出现部分无法编译通过的情况,是因为升级之后部分项目升级成使用.net 4.0的框架,而部分项目还是用的.net 2.0或3.5的framework,当使用.net 2.0或.net 3.5的项目引用了.net 4.0项目的程序集时,是无法通过编译的,这时尽管把这些项目该为使用.net 4.0的framework后可以正常启动进到编译界面,但是编译操作系统的过程中会出现“未将引用设置到对象”错误,原因不明………..
好了,了解了流程就直接上代码,跟着代码逐行进行说明,找到Program文件,定位到以下代码
/// 为什么这个是程序的开始函数,请看小弟的上一篇拙文 :)
/// </summary>
publicstaticvoidInit()
{
//初始化硬件
Kernel.Boot.Default();
//加载一些在硬件设置完毕之后,程序启动之前需要运行的一些东西,COSMOS中每一个需要在这里运行的东西叫做一个Stage(不知道操作系统理论中是不是有这么一个概念?),比如说一些欢迎词和版权声明之类的。这加载进来的是一个欢迎语的Stage
Kernel.Staging.DefaultStageQueue stages = newCosmos.Kernel.Staging.DefaultStageQueue();
//运行上一步加载的Stage
stages.Run();
//剩下的大家应该都能看得懂了
Randommt = newRandom();
intnum = mt.Next();
System.Console.WriteLine("I am thinking of a number between 0 and 100. What is it?");
System.Console.ForegroundColor = ConsoleColor.Blue;
System.Console.Write("Take a guess: ");
System.Console.ForegroundColor = ConsoleColor.White;
short guess = short.Parse(System.Console.ReadLine());
while (guess != num)
{
System.Console.ForegroundColor = ConsoleColor.Red;
if (guess >num)
System.Console.WriteLine("Too high.");
else
System.Console.WriteLine("Too low.");
System.Console.ForegroundColor = ConsoleColor.Blue;
System.Console.Write("Take another guess: ");
System.Console.ForegroundColor = ConsoleColor.White;
guess = short.Parse(System.Console.ReadLine());
}
System.Console.WriteLine("You got it!!!!");
stages.Teardown();
}
主要分析一下Kernel.Boot.Default()这个函数干了什么事,前面一大堆理论中关于CPU,内存,IO初始化这些的操作都是在这里面完成的
//Init Heap first - Hardware loads devices and they need heap
//居然是先在内存中初始化堆(就是开辟堆空间) 还以为是先对CPU进行设置呢……
//初始化堆的过程其实就是一个在系统中建立各种不同的对象的过程,用这些对象来对内存进行管理
Heap.CheckInit();
//现在该是设置CPU和初始化IO了,稍后对该函数进行分析
Cosmos.Hardware.PC.Global.Init();
// Now init kernel devices and rest of kernel
//对设备进行初始化,看名字这应该是个键盘
Keyboard.Initialize();
}
再看一下Cosmos.Hardware.PC.Global.Init()里面做了什么
//终于到CPU了
//这里先是new 了一个处理器,但是从函数的带来看这个处理器什么都没做,那玄机就是在Processor这个类的构造函数里面了。跟进去可以看到,可以看到构造函数里面创建了GDT(简单的说就是对于x86的CPU,如果要使用保护模式的话就需要创建一个GDT,并对它进行设置来开启CPU的保护模式功能),但是,由于是MS1(里程碑1),实现程度还很小,创建GDT只是一个空操作,实际什么也没做,估计会在后续版本中添加进来。
mProcessor = new Processor();
//对中断控制器
Bus.CPU.PIC.Init();
//All old.. need to port ----------------
//对串口进行初始化
HW.Serial.InitSerial(0);
//对调试工具进行初始化(这个应该是在初期才使用的吧,商业操作系统上在这里应该没有调试相关的代码)
HW.DebugUtil.Initialize();
HW.DebugUtil.SendMessage("Logging", "Initialized!");
//初始化时钟?不知道有啥用
HW.PIT.Initialize(Tick);
// Partially new
//初始化中断处理程序,此处是挂在不同的处理程序到对应的中断向量号上
Interrupts.Init();
// end partially new
//初始化ATA存储设备,通常指的是硬盘,此处传入的参数是一个委托,委托内容为一个死循环,也就是没有做任何实现,估计以后会补上
HW.Storage.ATA.Initialize(Sleep);
//创建IDT,如果要使用CPU的分页功能就必须创建一个IDT,并对其进行设置。这里这个函数也是没有做任何实现,因为还是MS1,COSMOS能做的事情还很少,没必要使用到CPU的分页功能。
HW.CPU.CreateIDT();
// end old -----------------
//把键盘添加到系统设备中
HW.Device.Add(new Bus.CPU.Keyboard());
}
这样就算是基本完成了,当然,对于不同的操作系统,初始化的流程可能会不一样,这得根据实际的硬件情况来决定。
以上的初始化函数中大量调用了这个操作:void Write8(UInt16 aPort, byte aData)
这个是直接往端口中写入数据,根据IBM-PC兼容机的端口说明,对不同的端口进行初始化其实就是往相应的端口中写入不同的数据(但通常都是写入数据0)
这里完了之后我们再跳回到上一层函数,看看Keyboard.Initialize()里面做了什么
public static void Initialize() {
mBuffer = new Queue<uint>(BufferSize);
// Old
Hardware.Keyboard.Initialize(HandleScancode);
// New
mKeys = new List<KeyMapping>(128);
//以下都是添加字符的映射关系进映射关系表
#region Letters
AddKey(0x10, 'q');
AddKey(0x100000, 'Q');
AddKey(0x11, 'w');
AddKey(0x110000, 'W');
AddKey(0x12, 'e');
AddKey(0x120000, 'E');
AddKey(0x13, 'r');
AddKey(0x130000, 'r');
AddKey(0x14, 't');
AddKey(0x140000, 'T');
AddKey(0x15, 'y');
AddKey(0x150000, 'Y');
AddKey(0x16, 'u');
AddKey(0x160000, 'U');
AddKey(0x17, 'i');
AddKey(0x170000, 'I');
AddKey(0x18, 'o');
AddKey(0x180000, 'O');
AddKey(0x19, 'p');
AddKey(0x190000, 'P');
AddKey(0x1E, 'a');
AddKey(0x1E0000, 'A');
AddKey(0x1F, 's');
AddKey(0x1F0000, 'S');
AddKey(0x20, 'd');
AddKey(0x200000, 'D');
AddKey(0x21, 'f');
AddKey(0x210000, 'F');
AddKey(0x22, 'g');
AddKey(0x220000, 'G');
AddKey(0x23, 'h');
AddKey(0x230000, 'H');
AddKey(0x24, 'j');
AddKey(0x240000, 'J');
AddKey(0x25, 'k');
AddKey(0x250000, 'K');
AddKey(0x26, 'l');
AddKey(0x260000, 'L');
AddKey(0x2C, 'z');
AddKey(0x2C0000, 'Z');
AddKey(0x2D, 'x');
AddKey(0x2D0000, 'X');
AddKey(0x2E, 'c');
AddKey(0x2E0000, 'C');
AddKey(0x2F, 'v');
AddKey(0x2F0000, 'V');
AddKey(0x30, 'b');
AddKey(0x300000, 'B');
AddKey(0x31, 'n');
AddKey(0x310000, 'N');
AddKey(0x32, 'm');
AddKey(0x320000, 'M');
#endregion
#region digits
AddKey(0x1, '`');
AddKey(0x10000, '~');
AddKey(0x2, '1');
AddKey(0x20000, '!');
AddKey(0x3, '2');
AddKey(0x30000, '@');
AddKey(0x4, '3');
AddKey(0x40000, '#');
AddKey(0x5, '4');
AddKey(0x50000, '$');
AddKey(0x6, '5');
AddKey(0x60000, '%');
AddKey(0x7, '6');
AddKey(0x70000, '^');
AddKey(0x8, '7');
AddKey(0x80000, '&');
AddKey(0x9, '8');
AddKey(0x90000, '*');
AddKey(0xA, '9');
AddKey(0xA0000, '(');
AddKey(0xB, '0');
AddKey(0xB0000, ')');
#endregion
#region Special
AddKey(0x1C, '\n');
AddKey(0x1C0000, '\n');
AddKey(0x39, ' ');
AddKey(0x390000, ' ');
AddKey(0x0E, '\u0968');
AddKey(0x0E0000, '\u0968');
#endregion
#region Punctuation
AddKey(0x34, '.');
AddKey(0x340000, '>');
#endregion
}
好了,硬件的设置(初始化)总算完了,让我们回到最上层的函数看看接下来的是什么。
在这之后操作系统的任务就算是基本完成了,剩下的就应该是程序的调度运行的工作了,从源代码中可以看到COSMOS是直接运行了一个猜数的程序,我们看一下运行截图:
好了,COSMOS MS1的启动流程和运行到这里就算是结束了。但是还有几个问题没有解开,比如:System.Console.WriteLine(string)这个函数。对于在.net framework环境下编程的朋友可能还没有意识到,问题在什么地方。
首先System.Console.WriteLine是向显示器上显示字符,但是在我们刚才的代码中没有看到与显示器设备初始化相关的代码,在.net 环境下之所以能够向显示器上输出字符是因为.net framework在程序启动之前已经进行了这部份相关代码的加载,当程序中遇到System.Console.WriteLine类似的函数调用时,其实是调用的.net framework中的代码来实现的,这就是为什么我们在编写.net 程序的时候需要引入相应的dll,这些加载代码就在这些dll里面。
但是COSMOS是独立于.net framework来运行的,也就是说COSMOS在运行时是没加载过任何.net framework中的dll,这部分功能的实现COSMOS是通过编写自己的程序库(Plugs)来完成的,这个部分上一篇文章介绍IL2CPU编译器时小小地涉及了一下,更多的内容将在下一遍文章中说明,呵呵(诶呀!谁扔过来的鸡蛋!)
欢迎大家实践学习交流,任何意见和错误都欢迎指出,一起学习,共同进步 J
广告时间:欢迎光临小弟的淘宝话费充值小店捧场,呵呵
参考资料:
Intel Vol 3A System Programming Guide Part 1