Chinaunix首页 | 论坛 | 博客
  • 博客访问: 53546
  • 博文数量: 9
  • 博客积分: 350
  • 博客等级: 二等列兵
  • 技术积分: 100
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-03 13:03
文章分类

全部博文(9)

文章存档

2011年(1)

2010年(8)

分类:

2010-07-28 20:58:09

操作系统开发 - 前言
by Mike, 2009, Updated 2010
本系列教程旨在从基础教授和演示操作系统的开发。
介绍

欢迎!

本系列文章和教程是关于计算机和操作系统的。主要作为开发操作系统过程中的一个指导,描述了系统级编程的体系结构和概念。

本系列的目标是提供最全面的操作系统和计算机系统的向导,并尝图涵盖它的每一个方面。

在开始本系列教程前,我感觉应该介绍一下我们选择的语言,以及哪些是读者需要知道的,语言中的一些重要概念和如何去使用它。我们也会涵盖只在嵌入式平台和系统级软件中使用的概念。

本系列使用C和X86汇编语言。在开始教程前应当对这些语言有一个比较好的了解。本章包含对这些语言的回顾。

C语言概述

首先假设您已经知道如何用C语言编程。这是一个对C语言重要部分的快速的概述,以及它能为我们做什么。

如何在内核中使用C语言16位和32位C

在开始编写系统前你会发现没有什么东西能够帮助到我们。在开机时,系统是运行在32位编译器不支持的16位实模式下,这是第一个比较重要的事情:如果你想要创建一个16位实模式操作系统,你必须使用16位的C编译器。然而,如果你决定创建一个32位的操作系统,你就必须使用一个32位的C编译器。16位C与32位C代码并不兼容。

在本系列教程中,我们会创建一个32位的操作系统,因此,我们将使用32位的C编译器

C和可执行文件格式

C语言的一个问题是它不支持输出扁平二进制程序。一个扁平二进制程序基本可以定义为入口点函数(比如main())始终在程序文件的第一个字节的程序。等等,什么?为什么要这样?

这应当追溯到DOS COM编程的美好日子。DOS COM程序是扁平二进制-这们既没有定义入口点与没有符号名称。要执行这些程序,所需要做的是“跳”到程序的第一个字节。扁平二进制程序没有特殊的内部格式,所以也就没有什么标准。它只是一堆1和0。当PC开机后,系统BIOS ROM获得控制权,它不知道该如何去启动一个操作系统。正因为如此,它运行另一个程序-引导程序去加载 加载操作系统。BIOS并不需要知道这个程序的文件内部格式和它做什么。因此,它将所有的引导程序看作是扁平二进制程序。不论引导磁盘引导扇区是什么内容,它都会加载,并跳到那个程序的第一个字节。

因此,引导扇区的第一部分,也被称为引导代码或都第一阶段(Stage 1),它不能用C编写。这是因为所有的C编译器输出的程序文件有特殊的内部格式-它们可以是库文件、对象文件、或者是可执行程序。只有一种语言本身支持这点(可以编写汇引导代码)-汇编语言。

如何在引导程序中使用

尽管引导程序第一部分必须用汇编是事实,但也可以用C语言。有许多不同的方法可以做到这样(用C语言)。一种是Windows和我们自己的操作Neptune使用的。我们把汇编代码与C语言代码结合到一个文件里。汇编代码负责启动系统和调用我们的C程序。因为这些程序结合到一个文件中,所以第一阶段只需要加载我们的包含汇编和C的程序文件。

这是一种方法 - 还有其它的。大部分引导程序是使用C的,包括GRUB、Neptune引导程序,微软的NTLDR和Boot Manager。因为我们使用32位C语言,也有方法把混合16位和32位C代码。

实现这个很复杂并且需要很多技巧。因此,我们坚持在引导程序系统中使用汇编。如果读者需要,我们可能会在后续高级教程部分描述如何使用C。

调用C内核

当引导程序准备好后,它通过调用我们的入口函数来加载我们的可执行的C内核。因为C程序有特殊的内部格式,引导程序必须知道如何解析内核文件和定位到入口点函数并调用它。本系统,稍微晚一些我们会涵盖如何做到这些。这就允许我们在内核中使用C和其它我们建立的库文件。

指针介绍

因为你以读到这里,我假设你已经善于使用指针。在系统软件中,指针到处都在使用。因此,掌握好指针是非常重要的。

指针只是保存地址的简单的变量。定义指针,我们使用*操作符:

char* pointer;

请记住,指针保存的是一个“地址”?我们给上述指针设置任何东西,这个地址指向哪里?上述代码演示的是一个野指针。野指针是一个可以指向任何地址的指针。请记住,C并不会为你做任何的初始化。因此,上述指针可以指向任何地址,其它变量、地址0、某些数据的一部分、你自己的代码、一个硬件地址。

物理地址空间(PAS)物理地址空间(PAS)定义了所有可以使用的地址。这些地址可以引用PAS内部的任何地址。这包含物理内存(RAM),硬件设备,甚至是无。这与保护模式操作(比如Windows)下编写应用程序有非常大的不同,保护模式下所有的地址都是内存。

这里有一个例子,在编写应用程序时,如下例子会导致一个段故障错误,导致程序崩溃:

char* pointer = 0; *pointer = 0;
这里创建一个指向内存地址为0的指针(并不归你所有),因此,系统不允许你向其写入数据。

现在,在我们未来的C内核中尝试一下...没有崩溃!而是覆盖了中断向量表(IVT)的第一个字节。

由此,我们可以总结一些重要的区别:
  • 使用空指针系统不会崩溃
  • 指针可以指向PAS中的任何地址,既可能是内存,也可能不是

如果试图从一个不存在的内存地址读取数据,会得到垃圾数据(当时系统数据总线上的数据)。尝试往一个不存在的地址写入数据什么也不会做。向一个不存在的内存地址写,并立即往回读,可以会得到与写入相同的结果,也可能得不到,这取决于所写的数据是否仍然在数据总线上。

事情变得越来越有趣了。ROM设备被映射到相同的PAS。这意味着使用指针可能会写/读ROM设备的部分。系统BIOS是一个比较好的ROM设备的例子。因为ROM设备是只读的,写一个ROM设备与写一个不存在的内存位置效果是相同的。然而,却可以从ROM设备读

其它设备可能也会被映射到PAS,取决于系统配置。这意味着读/写PAS不同部分可能产生不同类型的结果。

正如您所知道的,与编写应用程序相比,指针在系统编程中起到了更大的作用。可能更容易的想到指针不是作为“指向内存地址的变量”,而是“指向PAS地址的变量”,因为它可能是内存,也可能不是。

动态内存分配

在编写应用程序时中,正常会调用malloc()free()或者newdelete申请堆内的一块内存。这与系统编程不同,申请内存,我们这些做:

char* pointer = (char*)0x5000;
这真是太酷了,Huh?因为我们能够控制任何东西,我们指定指针指向PAS的某一个地址(必须是RAM),并说“这是我们新的1024字节缓存”等。

在这里重要的事情是没有动态内存分配。C/C++中的动态内存分配是需要操作系统提供的系统服务。但是,等等!我们不是正在开发我们自己的操作系统?这就是问题所在:)为了提供malloc()和free()或new和delete我们需要自己写内存管理服务和函数。

在那之前,唯一的方式是“分配”一个地址空间上没有使用的地址作为缓冲区。

内联汇编

有一些事情C本身不能做。系统服务和与硬件会话我们需要使用汇编语言。

大部分编译器提供了内联汇编的关键字。比如,微软VC++使用_asm:

_asm cli ; disable interrupts
我们也可以使用汇编代码块:
_asm { cli hlt }
标准库和运行时库(RTL)

可以使用外部库 - 这些库函数没有使用系统服务,任何像printf(), scanf(), 内存函数, 或几乎一切,只有一小部分可以使,大约有90%函数需要重写,所以最好是写自己写。

RTL是应用程序在运行时使用的服务和函数的集合。从本质上讲,它们需要操作系统动行,并提供了这些服务。因此,需要开发自己的RTL

启动RTL负责调用C++构造和析构函数。如果想使用C++,必须开发支持这些的RTL代码,这使用到了编译器的扩展。

本系统中,我们了支持C/C++特性的RTL和基本的标准库。

修正错误调试

因为没有printf和某种方式可以使用的调试器,当当工作时该如何办?本系统使用(解释)Bochs调试器 - Bochs模拟器带的调试器。这可以用于运行自己的操作系统,并可以用于协助修复遇到的大部分更常见的错误。

另外唯一的方法是开发自己的允许输出信息的函数,这是最有可能告诉你程序崩溃前走了多远。

下回分解

本章到此为止:)下一章,我们开始到操作系统世界去冒险,看一看它们是什么,以及我们在整个系列中都会使用到的工具。

原文地址: 

阅读(2613) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~