概述
一个基于X86的小型中文操作系统的设计、编码与调试
简介:本文通过一个基于X86的小型中文操作系统的建立过程,说明了一个小型操作系统的设计原理,编码过程和实现方法。作为一个开放源代码的系统,本文通过对源代码的剖析,较为详细地说明了内存管理,进程结构等的具体实现,并最后介绍了在开发过程中对产生的系统内核的调试,对操作系统的实践具有一定的指导意义。
一 引言
操作系统是计算机的软件基础。在进行一些系统编程实验的时候,我们需要对一些系统程序进行试验,并对这些程序进行量化评测。现有的大型操作系统,如Windows和Linux过于复杂,不适合进行试验。并且由于系统本身的开销比较大,对程序的量化评测要扣除系统(如进程调度)的开销,不易把握。因此我们自行设计开发了一个基于X86的小型中文操作系统。本文介绍了在开发中采用的一些方法和技巧,探讨小型操作系统的实作过程。
二 设计
1. 微内核
微内核是内核的一种形式。在微内核中,各个独立的模块被分离出来,作为独立的实体存在(进程)。在系统运行过程中,各个模块的进程独立运行,各进程通过消息通讯机制进行通讯。由于微内核系统具有良好的结构和可移植性,且易于调试,所以在设计中我们采用了微内核作为我们的内核结构。
2. 内存布局/管理
在X86提供的保护模式(Protected Mode)中,可以采用分段和分页两种方法来对内存进行管理[1]。我们可以通过对GDT或是LDT的相应的描述符表项来对内存的相应段进行设置,如段边界、段大小、特权级等属性[2]。在一个小型的系统中,可以通过将系统的整个内存空间全部设为一个段,即一个平的内存段,来达到简化系统的目的。这样做的缺点是不能有效利用到保护模式的一些优点,如段数据的保护。在NASM中,我们通过以下的代码对GDT进行段的设置:
_gdt:
dw 0, 0, 0, 0 ; (0)
; kernel cs 0x08 (1)
dw 0x3FFF ; base: 0, limit: 64M
dw 0x0000
dw 0x9A00
dw 0x00C0
; kernel ds 0x10 (2)
dw 0x3FFF ; base: 0, limit: 64M
dw 0x0000
dw 0x9200
dw 0x00C0
; user cs 0x1b (3)
dw 0x3FFF ; base: 0, limit: 64M
dw 0x0000
dw 0xFA00
dw 0x00C0
; user ds 0x23 (4)
dw 0x3FFF ; base: 0, limit: 64M
dw 0x0000
dw 0xF200
dw 0x00C0
3. 进程结构
多任务是一个现代操作系统所必须的一部分。在一个任务切换到另一个任务的过程中,我们必须保存现在正在运行的这个进程的上下文,以便在下次调入运行时能够在现在断下的地方继续运行,然后再调入下一个应该运行的进程的上下文,开始下一个进程的运行。在我们的系统中,简单地保存了下面的这些信息:
struct proc_struct
{
int pid; // 进程id
int parent;
struct proc_struct* next_proc; // 下一个进程
struct proc_struct* prev_proc; // 上一个进程
// 进程状态
long eip;
long eax;
long ebx;
long ecx;
long edx;
long esp;
long ebp;
long edi;
long esi;
long eflags;
// 段寄存器
short cs;
short ds;
short es;
short ss;
short fs;
short gs;
// 进程已经运行时间
int total_tick;
unsigned char stack[STACK_NUM]; // 堆栈段
};
其中pid字段记录了这个进程的ID,parent记录了父进程的ID,next_proc是指向下一个要运行的进程的上下文的结构指针,以便系统调入下一个进程运行时进行快速定位,prev_proc记录了在这个进程之前运行的进程结构地址。由此可见,在我们的实现在,我们采用了双向链表对进程进行管理,既简单,又对进程的增加、删除、调序带来了方便。对于当前正在运行的进程,系统用struct proc_struct* p_proc这个指针来指向其进程结构。
在进行了以上的设置以后,我们就可以实现简单的进程管理系统了。process_schedule函数在进程双向链表中循环切换,在一个进程运行完一定的时间后,直接载入下一个进程的上下文,然后跳到下一个进程的执行点,进行执行。
int process_schedule(void)
{
// 进行一些进进程管理
if(p_proc->next_proc == NULL)
{
// 只有这一个进程运行,跳回去继续运行
jump_to_proc(p_proc);
}
p_proc = p_proc->next_proc; // 转到下一个进程
jump_to_proc(p_proc); // 切换过去
return 1;
}
其中,jump_to_proc这个函数在取得下一个进程的上下文后,将上下文装入CPU各寄存器中,然后运行jmp指令跳到进程的执行点中进行执行。
void jump_to_proc(struct proc_struct* p)
{
// 跳到指定的进程中去运行
temp_ebp = p->ebp;
temp_eip = p->eip;
temp_eax = p->eax;
temp_ebx = p->ebx;
temp_ecx = p->ecx;
temp_edx = p->edx;
temp_esp = p->esp;
temp_edi = p->edi;
temp_esi = p->esi;
temp_eflags = p->eflags;
__asm__("movl %0,%%ebp/n/t"
"movl %2,%%ebx/n/t"
"movl %3,%%ecx/n/t"
"movl %4,%%edx/n/t"
"movl %5,%%esp/n/t"
"movl %6,%%edi/n/t"
"movl %7,%%esi/n/t"
"pushl %8/n/t"
"popfl/n/t"
"movl %1,%%eax/n/t"
"sti/n/t"
::"m"(temp_ebp),
"m"(temp_eax),
"m"(temp_ebx),
"m"(temp_ecx),
"m"(temp_edx),
"m"(temp_esp),
"m"(temp_edi),
"m"(temp_esi),
"m"(temp_eflags));
switching = 0;
__asm__("jmp *%0/n/t"
::"m"(temp_eip));
// 此函数不应被返回
}
4. 文件系统
在FAT16,FAT32,EXT2等文件系统中,FAT32由于其实用性和简单性,我们选用它作为实现在文件系统。在硬盘驱动的基础上,我们简单地实现了FAT32文件系统的读功能,这样在后续的开发中,我们可以将各个功能模块作成单独的文件调入执行,增大系统的灵活性。
5. 启动设计
X86系统加电后BIOS执行完自检,将启动设备的第一个扇区读入到物理地址为0x7C00的内存单元中,然后跳到0x7C00去运行。由此可见,操作系统与BIOS的接口就在于启动设备(软驱,硬盘驱动器或是光盘驱动器)的第一个扇区。一般在这第一个扇区完成操作系统的初步载入。但我们在实践中发现,在一个扇区这么小的空间内(512个字节),将整个系统载入比较困难,且如果安装在PC上会与已经存在的操作系统发生冲突。
在这种情况下,我们想到GNU的GRUB启动装载器。GRUB能够支持多个操作系统的共存,通常我们将系统内核编译成GRUB启动装载器能够识别的文件格式,GRUB便能将系统加载到指定的内存地址中去,然后跳到我们的系统中去运行。
三 编码过程
1. 开发环境
我们尝试了两种开发环境,Linux和Windows。在Linux环境下,程序开发工具丰富好用,但是在对中文的支持上不够。在Windows下,适合操作系统开发的程序开发工具并不是很多,但对中文支持很好,且具有更大的通用性。所以,在进行了一段时间的Linux开发后,我们选用Windows做为开发平台。
2. C编译器
我们采用GNU的gcc编译器进行C程序的开发。Gcc是Linux下著名的C程序编译器,支持内联的汇编语句,比较适合系统程序的开发。且支持多种目标文件的格式,如coff,elf等,可以将C源程序方便地编译成多种目标文件。在Windows环境下,默认情况下产生coff文件格式,而在Linux环境下,默认产生elf格式的文件。
3. 汇编器
在众多的汇编器中,我们选用nasm来进行汇编部分代码的编译。这是因为nasm非常适合操作系统的开发。如用其它的汇编器,在从实模式到保护模式的过渡过程中,因为涉及到16位与32位指令的过渡,一般要直接写二进制代码来进行jmp的步骤。而在nasm中,直接用jmp dword就可以轻松做到。可以使用nasm –f coff a.asm来产生coff格式的目标文件,-f开关用于选择要产生的目标文件类型。Nasm也支持elf、coff等多种目标文件格式。
4. 连接器
在用编译器产生C和汇编目标文件后,下一步是用连接器将它们连接成一个内核文件。我们选用GNU的ld连接器来进行目标文件的连接工作。
5. 二进制文件格式
在Windows环境下,coff文件格式是得到编译器和连接器较为广泛支持的一种目标文件格式。在coff目标文件中,C与汇编产生的标号(Symbol)略有不同,在开发中应予以重视。即gcc在对C源程序中的标号进行处理时,在前面添加了一个下划线。如果我们在汇编程序中要引用这个标号,就要注意增加一个下划线。如有一个C程序里面有一个void maind(void)函数,如在汇编中引用,则要这样定义:
extern _maind
如果编译成elf文件格式,则没有此问题。
四 调试过程
1. 使用X86模拟器
作为计算机的基础软件,在编译后的操作系统一定要在真实的机器上运行才能知道程序是否正确。这样,如果要进行操作系统的调试,要么把本机重启,要么找一台专门的机器用于调试。这样做都不是很方便。
我们还有一个选择就是使用X86系统的模拟器。模拟器运行于现有的操作系统之上,其将操作系统内核读入,在其内部摸拟X86系统。这样,我们运用摸拟器就可以直接在开发机上进行操作系统的运行,非常方便快速。
常见的X86模拟器有VMWare、Bochs等。VMWare是商业软件,有Windows和Linux两种版本,易于使用,但其是为一般用户开发的,功能较少。Bochs是开源的优秀模拟器,免费使用,而且它体积小巧,功能强大。在我们的开发中,使用Bochs进行系统的模拟环境,并借用Bochs进行二进制级别的调试。
2. 使用Bochs进行二进制级别的调试
在操作系统的开发中,调试是一件比较困难的事情。因为在开发的初期无法移植一些知名的调试软件之前,调试一般只能依靠二进制级的工具来进行。前面提到的模拟器Bochs,在除了模拟功能以外,还具有一定的调试功能。在Bochs目录下,BOCHSDBG.EXE即为集成了调试功能的Bochs可执行文件。在Bochs运行后,出现了如下的提示后,就可以输入一些进行诸如断点设置,汇编/反汇编,查看CPU状态等调试命令。
Next at t=0
(0) context not implemented because BX_HAVE_HASH_MAP=0
[0x000ffff0] f000:fff0 (unk. ctxt): jmp f000:e05b
<bochs:1>
下面是用Bochs进行调试的一些常用命令,熟练掌握这些命令,对调试系统是非常有帮助的。
命令 | 说明 |
c | 继续执行(continue) |
step [count] | 单步执行,count为步数,默认为1(execute count instructions) |
Ctrl-C | 停止运行 |
quit | 退出bochs |
vbreak seg:off | 虚拟地址断点,seg为段地址,off为偏移,用于在指定的虚拟地址处设置断点(virtual address break) |
pbreak addr | 物理地址断点,addr为物理地址。用于在指定的物理地址处设置断点(physical address break) |
info break | 显示已经设置的断点情况 |
delete n | 删除断点(Delete a breakpoint) |
x /nuf addr | 查看在线性地址处的内存单元内容(Examine memory at linear address addr) |
xp /nuf addr | 查看在物理地址处的内存单元内容(Examine memory at physical address addr)其中nuf的意义同上 |
setpmem addr datasize val | 设置在物理内存地址addr上的内存单元内容。Datasize为要设置的单元大小,val为要设置的值。 |
info registers | 打印出CPU寄存器的当前值。 |
set $reg = val | 将一个寄存器赋值,其中reg可以换为eax等寄存,如set $eax = 0xA |
dump_cpu | 将CPU的整个状态全部打印出来 |
disassemble start end | 反汇编指定地址的程序。Start为开始的线性地址,end为结束的线性地址。 |
参考文献:
1. Intel,IA-32 Intel® Architecture Software Developer’s Manual Volume 3: System Programming Guide,Intel Corporation,2002
2. 杨季文,80x86汇编语言程序设计教程,清华大学出版社,1998
3. Peter Abel,IBM PC Assembly Language And Programming(Fourth Edition),Prentice Hall,1998
4. W.Richard Stevens,Advanced Programming in the UNIX Environment,Addison-Wesley,1993
5. M.Morris Mano,Computer System Architecture(Third Edition),Prentice Hall,1993
6. Barry B.Brey,The Intel Microprocessors(Fifth Edition),Pearson Education,2000
7. Maurice.Bach,The Design of The UNIX Operating System,Prentice Hall,1986
8. Andrew S.Tanenbaum Albert S.Wooddhull,Operating System:Design and Implementation(Second Edition), Prentice Hall,1997
本文来源:互联网 作者:华中师范大学城市与环境科学学院 陈斌
关于操作系统 微内核 内存管理 进程管理 模拟器 的文章
- 一个基于X86的小型中文操作系统的设计、…
- 一个基于X86的小型中文操作系统的设计、…
- 对嵌入系统应用中x86CPU的重新评价
- 利用BIOS定制实现嵌入式产品的差异化
- 基于X86的信息家电SoC解决方案
资讯今日推荐
- IDG:开源渐成主流2011收入可达58亿美元
- WiMAX冲击波:Wi-Fi被指结束黄金时代
- TD终端招标在即 40亿盛宴中的本土机遇
- Vativ科技公司近日发布新一代HDMI接收器…
- AMI推新型无传感器单芯片步进电机驱动器
![解决方案](http://www.mcuol.com/adphoto/ad218_solution.gif)
博文推荐
- MEDC文档:USB启动试验教程
- 8086FPGA核跑吃豆子游戏开发手记(1)
- 32位嵌入式系统——IT从业人员的机遇与挑战
- 白领:如何与咆哮老板面对面
- 多核、多线程代表着嵌入式系统的未来
社区推荐
- 学WINCE开发和LINUX开发,哪个容易些?
- 长篇连载:精彩armlinux演义(转)
- 超级残忍:暴力解剖iPhone全过程!
- 硬件设计中一些术语的简称
- 哈佛结构和冯·诺伊曼结构的区别
![telit](http://www.mcuol.com/adphoto/ad218_telit.gif)
下载推荐
- Linux百科书籍,不下亏了
- linux基本命令.exe
- ARM开发详解全集
- 嵌入式 uClinux及其应用开发
- 深入uclinux嵌入式操作系统
![威盛](http://www.mcuol.com/adphoto/ad218_via.gif)
推荐方案
- 移动电视系统设计挑战及解决方案大扫描
- 数码相框方案技术属性
- 基于IPP的嵌入式音频解码器设计与优化
- 基于OS20的机顶盒软件体系及其应用设计
- 基于nRF905的无线数据传输设备设计
最后
以上就是难过发箍为你收集整理的一个基于X86的小型中文操作系统的设计、编码与调试http://www.mcuol.com/Tech/207/1289.htm一个基于X86的小型中文操作系统的设计、编码与调试的全部内容,希望文章能够帮你解决一个基于X86的小型中文操作系统的设计、编码与调试http://www.mcuol.com/Tech/207/1289.htm一个基于X86的小型中文操作系统的设计、编码与调试所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复