自己写操作系统学习总结

怎样自己写一个简单的操作系统?

https://www.zhihu.com/question/20207347


我写的时候一些经历:
第一次写的时候3000行左右的就无法调试了,当时主要参考了linux 0.11(基础太潜)。
第二次写的时候,基本定下几个核心模块, 启动/内存管理/进程管理/用户环境/磁盘驱动/块缓冲/文件系统/中断,然后就开始发重新明轮子(不参考其他OS代码),常用算法参考wikipedia自己调试实现,驱动代码参考http://www.osdev.org,顺利写到用户态shell可以运行,然后就没兴趣写下去了。


如果以学习为目的,可以参考MIT OS开放课程里面几个LAB,分为6个LAB都有指导书,每个LAB都需要自己来填写核心代码,并且可以用内置脚本来评分,6个LAB最后合起来刚好是xv6 OS(/JOS)的代码。(http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-828-operating-system-engineering-fall-2006/labs/)。
(MIT这个课程又更新了: http://pdos.csail.mit.edu/6.828/2011/,这次用git管理代码,还添加了lwip移植的lab)


想写的OS,当然最好的学习模板就是FreeRTOS了。整个内核只有三个文件。其中一个还是链表的数据结构,操作系统核心就一个文件完成。但是已经移动很多单片机和ARM CPU上。当年我还想把它移植到s3c6410上,后来没时间只写一个分析文档就算了。


只要你学过汇编和C,只满足于写个“简单的操作系统",一两天就能搞定。


随便把你写过的小程序,用直接写硬盘软件(或者自己编一个,调用winapi的WriteFile函数就可以),写进硬盘/U盘引导区。记住,起始地址是7c00。


这就好了。注意,引导区只有440个字节给你用,程序不能太大,所以这部分基本要用汇编写。440字节后面是硬盘/U盘分区信息,不可以乱动。乱动以后bios可能没法识别硬盘/U盘。
引导区最后两个字节必须是55AA,不过一般你不用管,硬盘/U盘格式化的时候都已经给你写好了。


这样你的程序就在开机的时候直接运行了。


想调用大程序也没问题,你需要写个不超过440字节的程序,负责把第二个扇区的内容载入内存并执行,由第二个扇区的代码负责把所有代码载入内存。后面的部分用C写或者别的高级语言写都没什么问题了。


至于你想要具体作什么,就写什么,屏幕上画个画,管管硬盘上的文件,和用户互动一下,你随意。和你平常写程序没啥两样。总之一个能完成任务的简单操作系统就完事了。


最多几天时间你就能搞定。这差不多能搞个接近dos1.0的“简单操作系统”。


不过接下来才是你真正要学的。例如文件管理,内存管理,保护模式,进程管理啥的。麻烦归麻烦,但解放了自由了,终于可以编写内核态才准用的代码了,做的事情全部都是用户态下面不让做的事情。操作系统的学习,从这里才算真正开始。


说难不难,先从写bootloader开始吧。楼上有人提到FreeRTOS了,确实开源的bootloader很多,代码量也不大。
其实写个操作系统没有你想的那么高大上,无非就是把硬件点起来。


谢邀,谈到这个问题不得不推荐这本书
《Orange'S:一个操作系统的实现 (豆瓣)》
这本书是真的从零开始教你写出一个操作系统的雏形,还包括实现一个Shell,


另外,阅读Linux0.10的代码也是一个不错的入门方法,


可以参考CMU的Operating System Design and Implementation,4个月可以开发出一个x86上ramdisk的OS了
http://www.cs.cmu.edu/~410/


试试 Nachos,教学用的。
他有完整版的,可以参考学习。
也有框架版的,然后自己去开发相关模块(文件系统管理、进程管理、中断管理)。
这些都是用 C 语言实现的。
至于再底层的,是需要用汇编实现的。


看看uCOS/II的核心代码,几千行而已。


当初自己也有过想亲自动手写一个OS的冲动。推荐一本书吧——《30天自制操作系统》【日】川合秀实。啊喂,听起来很像“30天精通xxx”、“xxx从入门到精通”之流好吗!我是不看这种低端书籍的。这么想你就错了,川合秀实是一位以“轻量化”编程思想见长的“非主流”开发者,2000年因自行开发的OSASK项目而名声大噪。书中行文幽默诙谐,绝壁是入门好书啊。关键是通过自己动手写操作系统,可以顺便对CPU什么的有更深一步的了解呢~


可以看看Linux 0.11/0.12(2万行以内),或者参考《Linux内核完全剖析:基于0.12内核》: 
或者有本书《自己动手写操作系统》( http://book.douban.com/subject/1422377/ )。
前边两种都是基于x86的。


Uboot(主要是ARM平台)、UC/OS、busybox等也是可以参考的,基本都有跨平台版本的。


谈谈我的思路和写过的经验。


1、把握简单操作系统的最基础的两个组件:
1.1 心跳代码。
1.2 任务调度。


2、代码架构把握:
2.1 纵向上, 硬件相关层和硬件无关层分离。
2.2 横向上,根据 心跳、任务、中断、I/O、文件 不断在 1 的基础上扩展丰富OS。


3、构建环境:
3.1 在 x86+win 的PC机上,选择合适的 编译环境(如 VC )。
3.2 从基础组件开始,边学、边写、边调。主要掌握硬件无关部分。
3.3 牛逼后,找个开发板,学写 BIOS。开始掌握硬件相关部分。
3.4 将PC上写的OS代码移植到开发板上。


布朗大学的weenix练习  给了OS大体框架 自己一步步实现进程线程,文件系统,虚拟存储,多线程框架。 所有这些框架代码填完后就基本上完成一个可以执行程序的操作系统了 WeenixWiki


下面是一个师兄曾经写过的操作系统,纯搬运:
CosOS是一个的简易操作系统,它因兴趣而生,本身没有太多实用价值。开发CosOS目的在于学习研究各种计算机底层知识。
目前CosOS版本为0.01,采用微内核结构,实现文件系统、多任务、多控制台(TTY)、分页内存管理, 为应用程序提供库函数。
标准输入输出:printf、gets
文件操作:open、close、read、write、unlink、stat
目录操作:opendir、readdir 、closedir
内存分配:malloc、free  
执行程序:execv、execl
进程:fork、exit 等


由于CosOS开发时间不长,为了尽快实现一个雏形,很多地方使用了最简陋的实现。在CosOS的代码中可以看到很多 《JamesM's kernel development tutorials》和《Orange_'s.一个操作系统的实现》的影子。CosOS是从学习和模仿这两份资料开始的。这两份资料给了CosOS很大的帮助,从中借鉴了很多代码。CosOS综合了它们的优点,使CosOS离现代操作系统更进一步。目前CosOS的文件系统是从Orange's 文件系统移植而来, 并进行了优化。在以后CosOS的不断完善中,将使用更复杂、更先进的实现替换掉这些简陋、非原创代码。


SVN地址: http://svn.ecjtu.org/svn/cosos/
大家可以使用SVN客户端(如 TortoiseSVN)来下载源码和文档。 


附截图一张:


入门推荐mit 操作系统课,代码量不大,而且文档齐全。


如果你就是想体会一下,在windows或者linux上写一个用户态程序,实现一个调度器就好了,代码量一千行不到,几天就能搞定,之后你想干什么,你自己就会知道。


先gentoo定制吧


现在操作系统的工程开发已经很少重新开发内核了,一般都是在某个操作系统的内核基础上结合各种第三方开源库开发发行版,比如Linux的各种桌面和嵌入式发行版、Android、Firefox OS、Chrome OS、Tizen等等。
========

如何从零开始写一个简单的操作系统?

https://www.zhihu.com/question/25628124


大二的时候,老师对我们说:“如果有谁能自己写一个内核出来,那么,他平时可以不来听课,也不用做平时作业,做出来还能加分,怎么样,有没有人有兴趣?”


       和老师一番讨价还价之后,我成为全年级几百号人里唯一一个自己写内核/整个学期都不去教室听课/任何作业都不做的那个人(代表着我的身边将没有可以提供参考的人,任何资料都只能自己找)。


       一开始买了《30天自制操作系统》,上面写着需要软盘还有其它的模拟器,我的初衷是写一个可以烧在真机上一按开机键就能跑起来的那种,所以看了几页后就丢开了。后来又找了国人写的一本,也不是特别符合,也丢开了。


       这时我看到了那本教材(俗称绿宝书),约莫800页。之后的两个星期里,我每天泡图书馆,以每小时10页的速度读完了它,在上面乱涂乱画了许多标记。800页的英文书,我从中学到了大量的基本概念(线程进程,内存算法,寻址方式等等)。


      接着我寻思直接从网络上而不是从书上寻找资料,TA师兄给我提供了一个OS Development,我照着上边的例子,写了数以千记的汇编代码,习得了汇编技能。


      此时,我具备基本的概念知识,对程序的语言也已经理解,知道了虚拟机的调试方法,差的,就只有对内核整体是如何协作不太明白。于是我去找来老师用于教学的PintOS,找来MIT那个项目的代码,还有国内一个高校自制的OS(是几个研究生一起写的),仔细研究了一遍,最后开始写代码。


     在那个学期里,我放弃了LOL,一心看代码,写内核,写各种模块,将过程记录在博客上,花了三个月的时间,最终写出一个具备terminal的内核(文件系统没写好,时间不够),可以跑命令,运行函数,管理内存和进程,处理中断。


如果你想知道具体整个编写的过程是怎样的,可以看看我当时的记录,如下(很长):
原文:(http://www.ilovecl.com/2015/09/15/os_redleaf/ )


(一)OS说明
今后,我就要开始折腾操作系统,有了一点小小干劲。


我的计划是,先看过一份用于教育目的的系统源码,再去翻找相应的资料(我手头已有绿宝书),在翻资料的同时开始写代码,然后做好移植真机的工作,DONE!
 我也明白,理性很丰满,现实很骨感,这过程不会如同我计划中这般简单和轻松。但是,见难而退可不是我的风格(那样我会被红叶二小姐调戏的),不管如何,我都会,怎么说呢,尽力吧。


出于课程需求,斯坦福那些人亲自写了一个名为“pintos”的系统。pintos的结构比较简单,分为进程管理、文件系统、用户程序、虚拟内存等几个部分,也正是因为这个原因,我选择pintos作为我的参考蓝本,现在在读它的源码。


在接下来的几个月时间里,不出意外的话,我会不断的在博客上更新我的进度。


(三)交叉编译环境
倘若我们要在ubuntu上编译另外一个完整的OS,交叉编译环境是必不可少的玩意,维基百科有云:


交叉编译器(英语:Cross compiler)是指一个在某个系统平台下可以产生另一个系统平台的可执行文件的编译器。
 (想起以前,我为了给路由器编译OPENWRT,下载大量源码,愣是编译了几天几夜。那时候的我,真是“可爱”。)
 为了配置好交叉编译环境,我废了好大力气,最后勉强找到了组织。
 编译环境大致分为2部分,binutils和gcc。我先装好gcc-4.9.1,之后下载gcc-4.9.1和binutils-2.25的源代码,似乎gcc版本与binutils版本要对应来着…


开始编译之前,需要准备全局变量(在命令行中敲入以下命令):


export PREFIX=”$HOME/opt/cross”
 export TARGET=i686-elf
 export PATH=”$PREFIX/bin:$PATH”
 编译Binutils
 cd $HOME/binutils-2.25
 mkdir build-binutils
 cd build-binutils


 #注意是在源码目录下面新建一个文件夹,然后cd到该文件夹里,然后才配置configure,不这么做的话,嘿嘿..
 ../binutils-x.y.z/configure –target=$TARGET –prefix=”$PREFIX” –with-sysroot –disable-nls –disable-werror
 make
 make install
 –disable-nls 告诉binutils,不要添加本地语言支持


–with-sysroot 告诉binutils,在交叉编译器中允许sysroot


编译GCC
 cd $HOME/gcc-4.9.1
 mkdir build-gcc
 cd build-gcc


 #注意是在源码目录下面新建一个文件夹,然后cd到该文件夹里,然后才配置configure,不这么做的话,嘿嘿..
 ../gcc-x.y.z/configure –target=$TARGET –prefix=”$PREFIX” –disable-nls –enable-languages=c,c++ –without-headers
 make all-gcc
 make all-target-libgcc
 make install-gcc
 make install-target-libgcc
 –disable-nls 告诉GCC,不要添加本地语言支持。


–without-headers 告诉GCC,不要依赖任何本地库,我们必须在自己的OS中实现库。


–enable-languages 告诉GCC,不要支持除了C、C++之外的语言。


提醒
 不同机器配置不同,编译速度也不同。


编译这两个软件,我花了近3个钟,机器配置之低自不必说,说了都是泪。


如果任何人的任何编译过程出了任何问题,请仔细地、认真地、用心地再看看上面的命令,在你没有弄懂它的原理之前,请不要擅自做任何“改进”(血淋淋、赤裸裸的教训呀)。


(五)OS模糊框架
翻完了手头的绿宝书,我才晓得,人都是被逼出来的。


操作系统的概念都差不多已经知道,接下来,该由“理论态”切换到“实践态”了喔(书还是不能看太多,会中毒的–)。


对了,从别人推荐的地方弄来了一个框架(曾在android平台写了几万代码,我深深体会到框架的作用),轻松开工吧。


先说明一下这个框架:Meaty Skeleton,开源示例,内核和用户分离,方便扩展,嗯,没了。


最近烦杂事情很多,心情,不算愉快也不算低落吧,近来又梦见红叶,不知道又要发生什么,不管。


(六)内核第一步任务:GDT完成
天色已晚,又下着雨,我也忘记带伞了,嗯,等会儿再回去好了,这个商城的环境还是蛮好的。


今天实现了GDT。


(也不算是实现吧,因为我打算使用纯分页的流氓招数,放弃纯分段或分段分页混合,所以就不太用心于实现GDT,只是浏览INTEL的官网,借用了几个FLAG定义之类的东西,匆匆就写完了GDT)


下面是记忆:


使用内嵌式汇编
 分4个段,两个高级的内核分段,两个低级id用户分段
 预留了一个TSS,虽然也不打算用硬件实现任务切换(听前辈们说,硬件实现非常的麻烦)
 把 设置GDT表的函数(init_gdt)放在kernel/arch/i386/global_descriptor_table.c中,而段 segment_descriptor的定义(seg_desc)则放在kernel/include/kernel /global_descriptor_table.h
 引用了英特尔的一份公开资料
 一些全局或者说全世界通用的参数放在kernel/include/kernel/global_parameter.h,有些人更绝,把所有函数的原型放在一个地方,哪怕内核级函数和用户级函数混在一起
 翻了太多资料,头都晕了
 按进度来看,有点紧,也无妨。


(七)内核第二步任务:IDT完成
佛说人者,非人者,名人者。
 已经写好IDT的载入,加上之前的GDT载入,就已经完成两个与机器硬件相关的模块(准确的说,应该是给CPU的特定单元载入内容)。不过我并没传说高手那么厉害,高手们一天一个模块,可我近几天连IDT对应的IRC和HANDLE都还没弄。


在bochs上调试时,分别 键入info gdt 和info idt 1,能看到GDT和IDT的内容。


今日要点:


AT&T汇编和寻常的INTEL有些许区别,不过区别不是很大
 GDT和IDT都是固定的表,必须实现,实现方法各异
 之前留下的TSS并非用于切换任务,而是用于保存从“用户态”回到“内核态”时必须使用的跳转地址
 未完待续
 后记,IDT里面的OFFSET并没有得到正确的值,因为IRQ还没设置好,相应的HANDLE还没有弄好
 2015年4月16日01:14:25补充:


设 置了IDT表中的头32个项,也就是ISR(interrupt service routines),它专门处理诸如“除以0”/“Page Fault”/“Double Fault”等exception,它对exception的处理方式也很简单,或者说根本没有处理,仅仅是打印exception的类型而已。


我 随便写了一句int a = 1/0,调试的时候,bochs提示”write_virtual_checks(): no write access to seg”。可能是内核还没具有从用户态跳转到内核态的能力吧,毕竟IDT的头32个项都拥有ring0的级别,明天再看看。


补上3种中断类型:


Exception: These are generated internally by the CPU and used to alert the running kernel of an event or situation which requires its attention. On x86 CPUs, these include exception conditions such as Double Fault, Page Fault, General Protection Fault, etc.
Interrupt Request (IRQ) or Hardware Interrupt: This type of interrupt is generated externally by the chipset, and it is signalled by latching onto the #INTR pin or equivalent signal of the CPU in question. There are two types of IRQs in common use today.IRQ Lines, or Pin-based IRQs: These are typically statically routed on the chipset. Wires or lines run from the devices on the chipset to an IRQ controller which serializes the interrupt requests sent by devices, sending them to the CPU one by one to prevent races. In many cases, an IRQ Controller will send multiple IRQs to the CPU at once, based on the priority of the device. An example of a very well known IRQ Controller is the Intel 8259 controller chain, which is present on all IBM-PC compatible chipsets, chaining two controllers together, each providing 8 input pins for a total of 16 usable IRQ signalling pins on the legacy IBM-PC.
 Message Based Interrupts: These are signalled by writing a value to a memory location reserved for information about the interrupting device, the interrupt itself, and the vectoring information. The device is assigned a location to which it wites either by firmware or by the kernel software. Then, an IRQ is generated by the device using an arbitration protocol specific to the device’s bus. An example of a bus which provides message based interrupt functionality is the PCI Bus.
Software Interrupt: This is an interrupt signalled by software running on a CPU to indicate that it needs the kernel’s attention. These types of interrupts are generally used forSystem Calls. On x86 CPUs, the instruction which is used to initiate a software interrupt is the “INT” instruction. Since the x86 CPU can use any of the 256 available interrupt vectors for software interrupts, kernels generally choose one. For example, many contemporary unixes use vector 0x80 on the x86 based platforms.
 今天载入到IDT中的,正是第一种类型(Exception),只不过换了个名字叫ISR而已。


未完待续。


2015年4月18日12:06:45补充:


之前的”write_virtual_checks(): no write access to seg”错误并不是权限的问题,而是段寄存器DS的值错误,它的值应该是0x10,可我给它赋值0x08。0x08是段寄存器CS的值,0x10才是段寄存器DS的值。


另外,这at&t汇编里面,把C语言函数的地址赋给寄存器,必须在函数名前面加上$。


至此,ISR彻底完成,只是,似乎IRQ又出了点问题….


未完待续。


(十)内核第三步任务:分页完成
稍微做下记录…


得到内存大小
 首先,利用grab得到物理内存的实际大小。


物理内存管理
 然后,用一个数组map来监督物理内存,数组的每一项都对应着一个4K的物理内存。在这里我遇到了一个问题:数组的大小如何设置?因为还没有内存分配功能,所以不可能allocate一块或new一块内存来存放数组。找来找去也没找到合适的方案,就自己弄一个粗鲁一点儿的:设置数组大小为1024  1024。这样一来,数组的每一项对应4K,有1024  1024项,恰好可以对应4G大小的物理内存。但这样又有一个缺陷,倘若物理内存没有4G而是128M,那么该数组就有大部分元素被废弃了。现在先,额,不管这个,之后再解决。


至于这物理内存它的实际分配,我是这么觉得的:把前64M的物理内存当作内核专属(把内核的所有内容全都加载到此处),剩余的物理内存才是空闲内存,用于allocate。


为了方便分配物理内存,我采取最最最简单的方法:把所有空闲的物理页放到一条链里,需要的时候直接拿出来就可以了。


虚拟内存管理
 之后,就是把page_directory地址放入CR3并开启硬件分页功能了。


page_directory,page_table等作用于虚拟地址。对于这4G的虚拟地址空间,排在前面大小为MEM_UPPER的一大块虚拟内存都是内核空间,剩下的排在后面的都是用户空间。也就是说,在有512M的物理的情况下,虚拟内存的前512M是内核态,后面的3584M是用户态。


分页错误
 内存分配的过程中,可能出现“页面不存在”、“页面只读”及“权限不足”3种错误。处理分页错误,CPU会自动调用14号ISRS,我们要做的,是把我们写的处理函数地址放到14号ISRS的函数栏即可。


每次分页错误,CUP调用14号ISRS,继而跳入我们设计好的处理函数(-_-陷阱?)。


不过我现在也是暂时先不写分页错误的处理函数,如果内存真的任性真的出错了,我也不会管它的,傲娇就傲娇吧。


到这里,分页就算是初步完成了。


致命的伤痛
 很遗憾,物理内存设置好了,虚拟内存设置好了,也正常工作了,但是我一旦开启硬件的分页功能,就有”physical address not available”的错误,直接重启了,到底是怎么回事…再看看吧…


未完待续。


2015年5月1日12:54:14补充:


bochs的”physical address not available”提示是这么个回事,把一个内容不对的分页目录加载进硬件(也就是把分页目录地址置入CR3)。在初始化分页目录时,我直接用了4M大页的方式初始化,但弄错了byte和KB的数量级,所以就出了一点小小的问题。


遗留:page fault函数,待日后再写。


写内存分配去吧!


未完待续。


(十一)内核第四步任务:内存分配完成
内存分配?这可是个麻烦的活,不过,如果你足够聪明的话,就没什么问题了。 ——前人
 上 一次,我准备好了分页的相关内容,比如说,载入分页目录/开启硬件支持/划分物理内存/划分虚拟内存等等。这一次,不会怂,就是干(为写内存分配模块而奋 斗,高扛自由的鲜红旗帜,勇敢地向前冲….)。分页准备好之后,下一步是如何地分配内存,比如,如何分配一页空白的可用的物理内存?如何分配一块空白 的虚拟内存?如何连续地分配等等等等。
 第一节:申请和释放空白物理内存
 申请物理内存,在分页的机制下,就是申请一页或连续几页空白的物理内存,释放则反过来。


在 分页的时候,我已经将所有的空白物理页都放进了一个链表之中,现在要申请一个空白物理页,从链表中拿出来即可,太简单了。释放空白物理页,将物理页重新放 进链表里即可,也是非常的简单,有点简单过头了。当然啦,简单有省时省力的优点,同时,也有“无法同时分配许多页/分配大内存时(比如数十M)很吃力”的 缺点。这,按我的习惯,先留着,以后再说,现在能简单就简单。


写好allocate_page和free_page两个函数之后,分配空白页倒是正常,但是内核出现”double fault”的错误,也就是8号ISR被CPU调用了,具体为甚,现在还不清楚,待我瞧瞧再说。


未完待续。


查资料如下:


Normally, when the processor detects an exception while trying to invoke the handler for a prior exception, the two exceptions can be handled serially. If, however, the processor cannot handle them serially, it signals the double-fault exception instead. To determine when two faults are to be signalled as a double fault, the 80386 divides the exceptions into three classes: benign exceptions, contributory exceptions, and page faults. Table 9-3 shows this classification.


Table 9-4 shows which combinations of exceptions cause a double fault and which do not.


The processor always pushes an error code onto the stack of the double-fault handler; however, the error code is always zero. The faulting instruction may not be restarted. If any other exception occurs while attempting to invoke the double-fault handler, the processor shuts down.


————————————————————————–


Table 9-3. Double-Fault Detection Classes
 Class ID Description


1 Debug exceptions
 2 NMI
 3 Breakpoint
 Benign 4 Overflow
 Exceptions 5 Bounds check
 6 Invalid opcode
 7 Coprocessor not available
 16 Coprocessor error


0 Divide error
 9 Coprocessor Segment Overrun
 Contributory 10 Invalid TSS
 Exceptions 11 Segment not present
 12 Stack exception
 13 General protection


Page Faults 14 Page fault
 ————————————————————————–


Table 9-4. Double-Fault Definition
 SECOND EXCEPTION


Benign Contributory Page
 Exception Exception Fault
 Benign OK OK OK
 Exception


FIRST Contributory OK DOUBLE OK
 EXCEPTION Exception


Page
 Fault OK DOUBLE DOUBLE
 ————————————————————————–


大概意思是:同时出现了2个中断,CPU不知道该处理哪个先,就是这样,就是如此的简单。之前没有这个错误,但分配和释放几个物理页之后就有这个问题,我估摸着两个都是Page fault,再看看吧。
 刚刚调试了一下,我发现不是分配和释放几个物理页的问题,而是cli()和sti()的成对出现,去掉它们就没这个问题;更奇怪的是,就算只有sti() 允许中断出现,也会double fault,莫非我这前面关了中断或者是前面遇到了不可解决的中断遗留到现在?难道,是irq的重定位有问题?到底是为什么呢?先算入历史遗留问题吧,还 有重要的模块要完成。
 (事情有点麻烦了呢?并不是内存分配这里出了问题,而是sti()惹的祸,不管这哪个位置,只要调用sti()开启中断,就会double fault,看来必须解决这个问题才行,我不可能一直不开中断吧…-_-)
 睡了一觉,起来查资料,看到了关键的一句:make sure you didn’t forget the CPU-pushed error code (for exceptions 8,10 and 14 at least)到了,我翻出代码一看,哎呀嘛,我只注意到了8号软中断,没注意到10号和14号软中断(14号处理page fault),删去两行代码后,顺利开启中断!
 未完待续。
 第二节:分配内存(malloc/free)
 既然已经可以正常地分配和释放物理内存页,那么在这一小节之中,很自然地,我的任务就是分配内存了。


所谓“天将降大任于斯人也,必先让他实现一个内存分配的算法”,不外乎就是说,要实现void malloc(int size)和int free(void p, int num_page)两个大众情人函数。


它 的大概思路就是这样的:先初始化一个桶,把可用的内存块都塞进去,要分配内存时,直接从桶里面找,找到了当然万事大吉大家都开心,如果找不到,就调用上面 那个申请空白的物理内存页的函数,弄一个4K物理内存页过来,将这个内存页分割成小块,丢到桶里面,然后继续找,就是这样….
 2015年5月5日23:19:08补充:


遇到一个bug:每次申请的时候,可以正常申请,但是一旦使用了申请的内存,内核就报”page fault”的错误。想来想去,看来看去,最终发现,我在初始化分页机制的时候出了点小小的问题。


秘技解决:


初 始化虚拟内存时,我将大小和物理内存一样大(比如129920K)的虚拟内存设为内核级别并可用,剩下3个多G的虚拟内存是用户级别但不可用,我使用4M 大页载入分页表,所以我实际上载入了129920/4096 = 31个大小为4M可用的内核级别虚拟内存页,也就是说,在虚拟内存这个空间里,仅仅有31  4096 = 126976K的可用空间,其它的虚拟内存均是不可用的非法的;而在初始化物理内存时,我将前64M留给内核,后面的物理内存用于malloc和 free,比如有129920K,我把它划分为129920 / 4 = 32480个4K大小的物理内存页,也就是说,在物理内存这个空间里,仅仅有32480  4 = 129920K的可用空间,其它的物理内存均不在管理范围之内;这样一来,就出大问题了。


假设我们要申请一个物理页,由于使用链的方式管理物理页,申请到的就是排在后面的物理内存,比如申请到了129916K到129920K这一个物理内存页,现在,我们要使用它,会发生什么呢?page fault!!!!!!!


为 什么?很明显,在虚拟内存的空间里,最大的有效内存是126976K,CPU的分页表里只能找到前126976K,现在让CPU去找129916K,它根 本就找不到!它以为这个虚拟地址并没有对应这物理地址,是个错误!(附上page fault的引发条件:A page fault exception is caused when a process is seeking to access an area of virtual memory that is not mapped to any physical memory, when a write is attempted on a read-only page, when accessing a PTE or PDE with the reserved bit or when permissions are inadequate.)
 于是我稍作改正,就正常了,可以正常使用申请到的内存-_-。


未完待续。


(十二)内核第五步任务:系统时钟中断、键盘中断
我现在的状态不是很好,刚弄好系统时钟中断,每10ms发出一个中断请求;但键盘中断并没有弄好,没有识别键盘的按键SCANCODE,所以暂时只能识别第一次按键,系统收不到第二次按键中断,明个儿我再来看看,已经很晚了--!!
 未完待续。


2015年5月9日 15:51:00补充:


查了一番资料,调试了一番,现在,键盘中断正常工作了,键盘可以正常工作,每输入一个字符,就在屏幕上显示出来。


嗯哼,可以进入到进程模块了。


(十三)内核第六步任务:进程创建
在自习室里,我突然想到一个问题:一个进程,如何去创建它?(虽然之前翻完了大宝书,但毕竟一个多月都过去了,忘了具体的实现-_-)


翻 翻书,找到一个和我的设想相差不多的方案:用一个特定的结构体代表一个进程,结构体中包含进程的相关信息,比如说进程的pid、上下文、已打开的文件、优 先级、已经占用的CPU时间、已经等待的时间、虚拟内存空间、错误码等等,创建进程的时候,只需要跳转到进程的虚拟内存空间即可。至于如何跳转,那就是内 核态的事情了,一般的进程都处在用户态,也就不必关心太多。


如此,我们便是可以创建并运行一个进程了(不考虑文件系统),既然可以创建进程,可以切换进程,那么进程调度就很容易了,不过就是个复杂的进程切换过程而已,下一节便是写进程的调度罢。


(十四)内核第七步任务:进程切换与进程调度
黄粱一梦。
 看到这句古语,顿时感慨万千,没想到仅仅数周时间,我的人生竟发生了这么大的转折(不是一夜暴富),仿佛一夜醒来,到另外一个平行世界里去。甚至,在睡梦中我都会惊醒。


逝 者已逝,再多的话语也没用。只是,我不甘愿就这么结束而已。她也曾经说过:“此身不得自由,又何谈放纵”,现在我竟是极度赞同了。曾经想过在割腕的那一瞬 间,她的脑海里究竟有什么,有没有浮光掠影,有没有回放这一生的片段?如此年轻的生命,选择自我了断,需要多少黑暗沉淀,多少的落寞与失望…似乎一下 子也看开了。


(以上只是个人情感的流露,忍不住必须得写些什么,请忽略)


简单记录一下吧,没什么心情。


进程切换时,只需要切换进程上下文,把context刷新一遍即可。


至于进程调度,这个就简单许多了(其实也挺复杂),在时钟中断到来的时候,调整各个进程的优先级,并切换到相应的进程,就是这么简单。


  叔的答案,然后看了 F 叔给的这个链接 基于 Bochs 的操作系统内核实现  ,当然是什么都看不懂,除了惊诧之外也了解了一件事情:一个人写一个简单的操作系统内核是一件非常帅气并且可行的事情。


于是我开始写了,那时候我的水平大概是:只会做 C 语言的习题,编译的话只知道按 F9,汇编知道常见的指令,另外会一点点的 Win 32 编程,能流畅使用 Windows。


一开始我找了《30 天自制操作系统》来看,每天看书,然后把从书里把代码打出来,一次一次地编译运行。因为要同时写汇编和 C,所以从那时候起就开始用 vim。


在啃完了差不多半本书后,开始觉得没意思了……因为觉得作者为了让内容更简单而省略了太多细节。也看了于渊的《Orange‘s 一个操作系统的诞生》,依然没看下去:汇编用得太多了。期间也曾斗胆发邮件给 F叔,然后他推荐了 Bran's Kernel Development Tutorial 这个教程,于是我就从这教程重新开始了: 「30天自制操作系统」 Stop & 「OS67 」 Start


那时候大概是大二上学期,于是在 github 上又开了一个 repo,一开始在 Windows 下开发,后来又切换到了 Linux 下,因为 Bran's 用的 bootloader 是 Grub,不符合我的初衷,所以就自己写了一个,之后便跟一路教程写,跨过了保护模式这道坎,完成了基本的设备驱动。 


在完成 Bran's 后,我又部分参考了 写一个操作系统内核有多难?大概的内容、步骤是什么? - To浅墨的回答 中推荐的:hurley25/hurlex-doc · GitHub  文档,完成了一些简单的调试函数和库函数,printk 和内存分配。
事实证明,尽早写好调试函数诸如 panic, assert 和 printk 是非常重要的。 大量地使用有助于你尽快地发现 bug (当然前提是这些函数本身不能有 bug)。


看完了 hurlex-doc 该看的部分后,很长一段时间了都不知道该干嘛好,模仿 hurlex-doc 里的内核线程切换也一直出错。这一情况一直持续到我开始读 Xv6, a simple Unix-like teaching operating system 。


如果你去看知乎关于「自制内核」的问题,你会发现 xv6 被反复地提及并推荐,事实上它非常值得被推荐:这是我读完大部分代码之后真切体会到的。


之前的 Bran‘s 和 hurlex-doc 的篇幅都比较小,我是在电脑和 kindle 上看完的,xv6 相对来说代码量比较大,有 9000+ 行和一份文档,之后我又找到了这个:ranxian/xv6-chinese · GitHub xv6 文档的中文译版,所以我就去花了十二块钱学校打印店打印了一份中文文档和一份代码。这又是一个正确的决定,让我不必对着电脑就能看代码。


在之后的时间里,我先读了 xv6 中文件系统相关的部分,然后改写它的代码为我的内核添加了一个 类似 Minix 的文件系统。 然后几乎又照抄了其中了进程调度的部分(做了部分简化),又在原来的代码基础上为添加操作系统的接口,接着写用户程序,过程几乎是「一路顺风」。看 xv6 的那段时间也经常是处于醍醐灌顶的状态。


最后我终于在差不多一个月前完成了这个简陋的操作系统内核:
LastAvenger/OS67 · GitHub  (没错其实我是来骗 star 的)


历时一年,一路点亮了不少技能树(虽然都点得不好),这样算是「从零开始写一个简单的操作系统」么? 跟进一步说,有谁不是从零开始的呢? 所以想做的话,现在就开始做好了。
这是被「翻烂」了的 xv6 源代码和中文文档(其实是放书包里被磨烂了)


「故事」讲完了,接下来说一点经验之谈吧……


* 知乎上总是有人在讨论「做一个玩具编译器和做一个玩具内核何者更有趣」之类的问题,然后总有各种大V 跳出来说内核有多 dirty 而编译器多 clean,事实上除了 CPU 上的几个表因为历史原因长得恶心一点,内核并没有什么特别 dirty 的地方,另外,想做点什么打发时间,不过是两个代码量稍多的入门项目,有什么好纠结的?
* 写内核的过程中,你会接触到一些这辈子大概只会用到一次的知识,A20 线已经成为历史,日常的编程里面也不需要你懂得 GDT IDT 的结构。但是单凭内核主要部分部分(文件系统,进程,内存)给你带来的知识而言,这点冗余是值得的。 
* 尽早实现调试函数并大量使用,善于利用 bochs 的内置调试器,能省下你不少时间。
* 有时候觉得书里的做法非常奇怪,你觉得你有更好的做法,一般是你想错了。(当然只是一般)
* 上面说看 xv6 一路顺风是假的,20% 时间在抄代码,80% 的时间用来调试。
* 对我这种能力一般的人来说,「写内核」只是好听的说法,正确的说法是「抄内核」。当然,就算是抄一个,也算是受益匪浅了。
* 抄 xv6 的好处在于,即使你的代码出错了,你可以坚信,正确的答案肯定在 xv6 的代码里,或许只是你还没理解透而已,只要不断地看和理解,你就离正确的道路越来越近。


汇编不重要,但是要有一定计算机组成的基础,并对一个现代 kernel 的结构有大体的认识,至少大致上理解虚拟内存和文件系统有哪些东西。不要看 《the orange's》和《三十天编操作系统》,面太小,代码质量不高,就别拿 DOS 当操作系统了。个人比较推荐《莱昂氏 UNIX 源码分析》(已绝版,可淘宝打印)、《linux 0.11 内核详解/剖析》 ,写代码之前至少先都啃一遍。教程的话推荐 《bran's kernel development tutorial》和 osdev 上的一些资料。顺着它们搭开发环境,出一个简单的 bootlaoder,可以编译 C 代码即可。然后拿大把大把的时间慢慢给 bootloader 加东西就好了,能用 C 就不要用汇编。开发中的很多细节要到开发时才留意的到,这时可以自己思考,也可以去抄 linux 0.11, xv6, unixv6 这几套优秀的源码。


现在想来,开发一个 kernel 的主要内容在于“实现”而不是“设计”,更重要的是用时间去理解这些优秀的设计为什么合理,自己别出心裁的想法一般不用多想,一定是错的。


不要在 x86 的一些历史遗留问题上花太多时间,比如 bootloader 的保护模式会在开头挡住一大批人,可是这并不重要,只要知道有这个接口可以引导你的二进制代码即可。知道 GDT 可以区分用户态/内核态,IDT 可以给中断绑回调就行了。在调试上会花费大量时间,可以慢慢琢磨怎样提高调试的效率。然后需要的就只是耐性了,说实话也挺无聊。


我来写一个如何在15天内完成一个嵌入式实时操作系统,并移植到stm32单片机的攻略吧。第一次看到这个问题是在大概两个月之前,从那时候开始决定自己也写一个操作系统,于是开始看os的基本概念,进程切换,任务调度,内存管理,任务通信,文件系统等等。


前言:
大约在两个周不到前动手,平均每天7个小时,从完全不知道怎么下手(真的快哭了),到现在完成了一个----基于优先级时间片调度,具有信号量,消息队列,内存管理的嵌入式系统并命名为--LarryOS,并且移植到stm32(Cortex-M3架构)上,还在stm32上实现了一个malloc和free函数,受益匪浅。现在还正在完善。我是用自己命名的,当然,大家写好了也可以用自己命名,图个开心么 ,想想是不是很激动啊。 


对于这个问题下的回答的看法:
大神真的好多,几乎都是x86型的,难度真的要比我的大,不得不承认。不过,我觉得对于新手,写那样一个系统真的是太艰辛了,比如我这种入ee坑的在校大三学生,课真的太多了,周内最少三节课,而且和cs没有一毛钱关系,课余时间太少,如果花费一个学期或者三个月的时间来写,可能有些得不偿失。因为许多想写os的朋友,和我一样,一是觉得写os很酷,二是想通过写来理解os,毕竟看书看了就忘,也没有衡量自己学的好坏的标准。因此,选择写嵌入式系统,是一个非常好的选择 。


知识储备:
这个问题想必是很多同学顾虑的,到底写一个嵌入式系统需要什么知识储备?我从自己的经历出发,并加以简化,大家可以参考一下。


自身版:
1c语言要求:我自己在大学里几乎没学过c语言,上机几乎10道题会2道,期末全靠背。后来开始自学了c,看了c语言程序设计现代方法,c和指针 c专家编程 第一本书的课后题,做了3分之2吧,就这样,没了
2汇编要求:会基本的x86指令,仅仅是基本,看了王爽的汇编语言,程序写的很少,仅仅上机课写过,不过能很快写出来。
3微机原理或者计算机组成原理:老师上课太坑了实在,我自己看了csapp的前四章大概,豁然开朗,推荐这个。
4操作系统:看了三个礼拜os的基本概念,仅仅是了解概念,没办法深入,也没地方。。。。
5数据结构:看过浙大的数据结构公开课的3分之2,会写基本的链表,队列,最基本的树和树的基本的遍历办法,图完全不会 
6单片机基础:大约两年的单片机基础
7新手看到这里,可能已经慌乱了。。。。不要怕,我说的很多东西,写嵌入式系统用不到啊 ,继续,发一个精简版


精简版
1:c语言 能把我推荐的c书的第一本或者c primer之类的书看个一半,或者自己本身知道指针的概念,知道结构体指针,函数指针,二级指针的用法,仅仅是概念和写法就行了,不用深入,用不了多久。
2汇编:知道有几条常用指令,用法和功能是什么就可以了
3组成原理:知道中断是什么,入栈和出,寄存器,知道汇编对应的计算机大概动作就可以了,用不了一个周
4操作系统:找个公开课或者书,看看大概的os概念,一个周就够了,我现在很多概念还是不知道,后面可以慢慢学
5数据结构:会写链表,队列就行了,我们不写文件系统,不用树
6单片机基础:用过单片机,不用自己写驱动代码,仅仅可以在别人的驱动下,编写一些简单的逻辑代码就行,完全没学过的人,两个礼拜就可以用stm32来编程了,如果之前学过51,一个礼拜不到即可,因为我们只是借助一下单片机,避免使用虚拟机,方便操作系统的调试。因为我们可以用单片机,输出某些信息在串口或者液晶屏,让我们直接看到代码的错误点,方便我们调试。


因为很多大一的新生想写,推出一个极限版~~
极限版
1.学过C,知道有指针这个东西,其他的边做边学。
2.不懂汇编,边做边查,边查边写。
3.知道什么是寄存器,知道中断的概念
4.知道OS是由什么组成的
5.数据结构完全不会,遇到链表,队列时再查,或者直接抄也行
6.不学如何使用单片机,遇到再查


正文:


一、开发环境
对我来言,这倒是很重要的一点。第一次萌生想写OS的想法时,在网上搜索了不少资源,大多数都是在叙述框架,如何构建一个操作系统。然而对于当时的我来说,根本不知道用什么平台来写,如何调试自己的程序,看了一些朋友的发帖,推荐用XX模拟器,在XX平台开发,完全看不懂,界面好像也很老旧,而且大多是英文,当时有点敬而远之。自己手上只有个codeblocks软件,想来想去也不知道怎么用这个开发OS。直到有一天,突然顿悟,OS不就是一堆程序,我还打算让他运行在单片机上,那么用单片机常用的开发工具不就行了!!!---------Keil uVision5 


学过单片机的朋友,相信非常熟悉这个软件,使用起来也非常简单,网上随便一查,就可以下载和安装了,这里就不详细展开说了。如果不知道如何使用的,先熟悉一下这个环境,再开始写,相信半个小时即可上手。
二、参考资料


既然是运行在真机上,必然要对它有所了解,我们这里采用的是STM32(Cortex-M3架构),市面上非常火热的一款,资料丰富,大家有什么问题谷歌一下,有很多前人的经验让你借鉴。如果不知道如何谷歌的朋友,点开这个链接去操作,免费,大约15分钟就能翻墙了如何优雅的访问谷歌、谷歌学术等网站 | 欧拉的博客。


1.Cortex-M3权威指南(中文版),这本书会详细的讲解,中断处理,异常,ARM汇编等知识,我们会在任务切换的时候用到。(PDF即可)




2.嵌入式实时操作系统ucos(邵贝贝审校),ucos中有很多我们可以借鉴的地方。


3.谷歌,有一些基础知识遗忘的时候,谷歌可以让你很快补充上来


三、从写一个最简单的os做起


我们这里假设我们写一个支持32个任务并发执行的简易OS


1.任务就绪表


   我们假设这里任务有两种状态,就绪态和非就绪态。


我们定义一个32位的变量OSRdyTbl(就绪表),它的最高位(第31位)为最低优先级,最低位(第0位)为最高优先级。OSSetPrioRdy()函数的功能是,你传递一个数(优先级),把这个优先级对应的任务设置为就绪态。同理,见图:


OSDelPrioRdy()函数将某任务从就绪表移除


OSGetHighRdy()函数选出最高优先级的任务


这里我们就完成了,设置某任务为就绪态,从就绪态删除某任务,获得最高优先级任务的任务。


2.任务控制块


想必这个概念大家很清楚了,每个任务都对应一个任务控制块,典型的用处是,任务切换时,通过控制块来获知每个任务的堆栈(因为控制块有指针指向该任务的堆栈)


此外,再定义几个变量。


注释写的很清楚,不解释了!


3.主堆栈


在此STM32中,提供两个堆栈指针,一个是主堆栈(MSP),一个是任务堆栈(PSP),可以通过查Cortex-M3权威指南(中文版)得到。所以我们既要建立一个主堆栈,又要为每个任务建立自己的堆栈(一个任务一个),这里我们先不管任务堆栈,只看主堆栈。


OS_EXCEPT_STK_SIZE是个宏,大家可以自己设定,我这里设的是1024,一定要尽量大一些。为什么?因为裸机下,进入中断服务程序时,系统会把许多寄存器入栈,而且支持中断嵌套,也就是刚入栈完又入栈,所以有可能会堆栈溢出,非常危险。


CPU_ExceptStkBase大家先别管,它指向的是数组最后一个元素。


4.建立一个任务


我们通过Task_Create()函数来建立一个任务,第一个参数用来传递该任务的任务控制块,第二个参数用来传递函数指针,第三个传递该任务的堆栈。tcb->StkPtr=p_stk这句将该任务控制块中应当指向栈顶的指针,指向了该任务的新栈顶(前面定义了TCB,自己可以翻一翻),在写该函数时,一定要看Cortex-M3权威指南,不然你怎么知道有这么多寄存器,而不是仅仅从R1到R7?看到这里,还想看懂的,应该都是真的想写OS的朋友,这里有不懂的去看Cortex-M3权威指南,你会豁然开朗的。这里,Task_End是一个几乎永远不会执行的函数,何时会执行,先不管了,看它的内容。


5.欢迎来到主函数


①第一行:在该主函数中,第一行我们让主堆栈指针指向了主堆栈,那么,这个主堆栈是哪里来的呢?很简单,我们自己定义的,哈哈。


很熟悉吧,前面发过这个图,大小你来指定,注意要大!!!!!


②第二、三行:建立一个任务,第一个参数传递了该任务的控制块,第二个参数是该任务的任务函数,第三个是堆栈(数组最后一个)


是不是很好奇,任务1和任务2是什么?一起了来看


大家自己随意定义,开心就好。


Task_Switch()函数又是什么呀?


这里Task_Switch()是我们用来测试的程序,当任务1运行时,完成i++后,将最高优先级设置为任务2,并用OSCtxSw()切换到任务2,OSCtxSw是用汇编写的,是一个隐藏BOOS,我们先不管。


③第四行:程序刚运行时,是没有最高优先级的,所以我们用p_TCBHighRdy=&TCB_Task1;来随意指定一个任务为最高优先级


④:最后一行:OSStartHighRdy()该函数也是汇编,和OSCtxSw()并称为2大BOSS,我们会在后面解密。OSStartHighRdy和OSCtxSw很相似,不过OSStartHighRdy()用于当所有任务都没有运行过时,用于初始化(当然具有任务切换的作用)并成功运行第一个任务,而OSCtxSw()是在OSStartHighRdy()之后,使用OSCtxSw()时,最起码有一个任务已经运行了(或正在运行)。


6.完工?


到这里,从宏观说已经基本完工了,这就是一个简易的OS的基本状况。看到这里,大家可以休息一下了,消化一下,马上有BOSS要出现了,解决那两个BOSS后,就真正做成了一个简易的OS。




7.两大boss之OSStartHighRdy()函数


7.1任务视图


终于开始了任务切换环节,这是我画的一副任务切换图,自我感觉非常好,不过大家应该看起来很困难,字太丑了~~~~


通过分析上面这张图,来确定如何写OSStartHighRdy()函数:


①中:此时有一个任务1,但是任务1我们仅仅是建立了,并没有让它运行。这里我们认为任务1是由三部分组成:任务代码,任务堆栈,该任务的任务控制块。


②中:我们想让任务1运行起来,任务1是什么?就是一堆代码,如何运行起来?-----让PC(程序计数器)指向任务代码即可(依靠出栈POP)。同时,我们还要让SP指向任务的堆栈,这里的SP当然是PSP(任务堆栈)


7.2写OSStartHighRdy()函数


下面开始写OSStartHighRdy(),它的功能就是上图的①和②




2,3,4,5行中的那些数字,是外设对应的地址,我们往相应的地址写值,即可完成某些目的。12,14,15行是我们之前定义过的几个变量,忘了的回头翻一翻。17行是将OSStartHighRdy()函数extern了一下,因为我们在主函数要用。




上图就是OSStartHighRdy的内容,我们一起来看。28,29,30行设置了PendSv异常的优先级,问题来了,什么是PendSv???


Cortex-M3权威指南中其实讲了,很详细,这里为了缩减篇幅,不详细说,大家只用知道PendSV 异常会自动延迟任务切换的请求,直到其它的中断处理程序都完成了处理后才放行。而我们只用触发PendSV异常,并把任务切换的那些步骤,写在PendSv中断处理任务中。


7.2.1PendSv处理程序


经过39和40行,我们往控制器里写值,触发了PendSv异常,现在程序会进入异常处理程序,就是下图:


在此函数中,如果PSP为0,则进入OS_CPU_PendSVHandler_nosave()函数,其实在OSStartHighRdy我们将PSP设置为了0,所以必然会进入OS_CPU_PendSVHandler_nosave()函数。


7.2.2OS_CPU_PendSVHandler_nosave()函数


在该函数中,我们让p_TCB_Cur指向了最高优先级的任务,因为有出栈,所以重新更新了SP。


7.2.3恭喜


到了此处,一个任务已经可以成功运行起来了!!!!!!


BUT only one task!我们需要让它多任务切换


7.3写任务切换OSCtxSw()函数


与OSStartHighRdy非常相似,也是往中断控制器里写值,进入PendSv异常。


7.3.1任务视图


依然是这张喜闻乐见的图!!!!!!!


③:任务1要切换到任务2,因为待会要进入任务2,会破坏任务1,所以我们要保存任务1的现场,把寄存器入栈


④:因为任务1有入栈动作,栈顶肯定变了,我们修改任务1控制块的值,让它指向新的栈顶


⑤:什么都没有,只是想说明。我们建立了一个任务2,仅仅是建立了。


⑥:很简单,要想让任务2运行,则让PC和SP分别指向任务2的任务代码和任务堆栈即可。


⑦:什么都没有,只是想说明,任务2活的很开心,可以运行了


7.3.2由OSCtxSw()进入PendSv异常


与OSStartHighRdy不同的是,这时的PSP肯定不为0了,所以不会跳转,会顺序执行55行以后的程序,也就是任务视图里的③,继续执行④。执行完60行的程序后,继续顺序执行,执行下面程序:


和OSStartHighRdy函数的后续步骤几乎一样,找到优先级最高的任务,让指针指向它即可!!!!


1.大功告成!!!!!!!!


到这里,我们已经彻底完成了一个简易OS的设计!!!!


8.1如何查看成果?


8.1.1增添程序


我们在主函数中加入一个函数


大家可以把它理解为一个库,调用之后,我们就可以在串口(屏幕)显示某些字符了。


同理,在任务1和任务2中加入printf函数


8.1.2编译并下载程序


8.1.3利用串口调试助手观察


网上搜串口调试助手,会有很多工具,随意下一个就OK!


可以看到,按照我们的预期,任务1和任务2轮流输出字符在屏幕上!


8.2如何调试程序?


8.2.1调试器


必然是神器--J-link或者ST-link,一个大概50,嵌入式开发神器,记得以前刚开始玩单片机的时候,调试全靠打印消息在屏幕,觉得好用的不得了,经常有人给我说,你用调试器调试啊,我都鄙夷的回一句,需要么?(哈哈,那时确实不需要) 等开始写OS时才知道,这东西真是救命稻草,没有它,怎么看寄存器的值和异常返回的值呢?


8.2.2界面


点击DEBUG后,你可以看到寄存器的值。想想我们之前要入栈,出栈,如果哪一步错了,自己估计把串口吃了,也看不出来吧,哈哈!!!!!!


单步调试什么的,不说了,用的很多。


9.写到此处的感想


答主因为写这个OS,在凳子上久坐了两周,这两天腰疼,只好躺着写这个回答了!哈哈!


也正好因为腰疼,感觉时间比较多。不过和想象的真的不一样,本来觉得可以一气呵成,结果短短的篇幅,就写的自己的思维都大乱了,而且也挺费时间的,前后用了有6个小时了。想写OS的朋友,参考上面的步骤,加上自己琢磨,应该也能写一个出来。如果哪里有不清楚的,不要心急,如果真的是一两天就写成了,还有什么锻炼的意义,有点失去初衷了。不懂的就去查权威指南和OS的书籍,相信你会收获的非常多!


为什么要写这个回答?
这是我写了6个小时多以后来补充的问题,因为我自己也纳闷了,放着自己的事不做,跑来写这么一堆干什么........刚才路上走着想到答案了----------想留个纪念。还有不到两个月就要寒假了,我打算考研,也就是说这是大学里做的最后一个项目了(毕设除外),真的挺伤感。从大一一路折腾过来,现在要突然一年不能折腾,简直泪流满面!!!!!!!!!!!!!!!!!!希望我这个简单的开头,能让想写OS的新人得到一丝启发。


写操作系统能学到什么?
这也是我想写这个回答的原因,对我的改变挺大
前几天有个好朋友(大一开始和我一起学单片机的朋友,后来他一直在做单片机项目,却没有补过任何基础知识),打电话问我XX模块怎么用,其实是很简单的一个问题,直接百度都可以,但是他还是想要来问下我,我说很简单,就XX做就可以,有问题你再百度,但是他好像不想听到这种回答,想让我说到他一听就知道怎么做了,才敢开始做,不然就不敢启动项目。那时我才突然意识到,以前的我就是这样啊,做什么项目做什么东西,老是想要集成的模块,资料丰富的模块,如果没有什么源驱动代码,我就不敢做了,生怕买回来,用不了之类的,甚至有源码也不敢买,因为源码和我的机型不一样,连修改源码都怕。开始写OS,我还蛮恐惧的,因为不是科班,没学过,不懂。涉及的东西也很广,从编程语言到数据结构,组成原理,操作系统,怕拿不下来,也找不到好的资源,不知道怎么写起。通过这次项目,我想应该学到了一下东西:


1.遇到不懂的没有那种很强的抵触感了,开始学会查芯片手册,查原理书,开始配合谷歌查BUG,现在即使是拿来我没有接触过的模块,无论最后做的出来与否,我肯定先去了解它是什么,概念是什么,再去找厂家提供的资料,提供的源码,去一行一行的看,自己修改或者重写,这个比下面说到的什么具体的知识,我想都重要的多。我现在还记得第一次和第二次,第三次打开Cortex-M3权威指南(中文版)时的情景,看了几页就吓得我关了,简直是天书啊,其实耐心看,真的能看懂。
2.把自己数据结构里学的东西,真正带到了项目,虽然也写过队列等这些数据结构的题,但是在以前的嵌入式开发里,几乎用不到,有时候觉得好没用啊这些。这次通过实现OS的消息队列,看linux的文件系统,知道了这些在实际的巨大用处。
3.对OS的基本概念和运转有了认识
4.对C语言的理解深刻了许多
5.每个人的体验都不一样,没什么补充的了O(∩_∩)O哈哈~


四、简单几步将简易OS改造为--优雅的简易OS


1.为什么不优雅?


在该函数中,任务1执行完,立马就会切换到任务2,然而在实际中,我们希望是这样的:


任务1每100毫秒执行一次


2.系统节拍


几乎每个实时操作系统都需要一个周期性的时钟源,称为时钟节拍或者系统节拍,用来跟踪任务延时和任务等待延时


我们在main函数中输入这样一句


这里我们配置了定时器中断,每5毫秒一次中断。中断后会进入中断处理函数,下面来写中断处理函数:


大家只用管if里面的函数即可:我们在某个函数中将TCB_Task[i].Dly置为x,中断处理函数会每5毫秒,将非0的TCB_Task[i].Dly减一。如果TCB_Task[i].Dly非0,对应的任务是不会运行的(因为被我们删除就绪态了,这里看不到),当TCB_Task[i].Dly减为0,我们才将该任务置为就绪态


3.编写OSTimeDly()函数


也就是1中图片所示的函数,它可以让某任务指定每X毫秒运行一次。


第65行,可以关闭中断,同理,第68行,开启中断。为什么要关闭中断?因为中断会影响我们执行下面的代码,先关闭中断,执行完后再打开。66行将该任务从就绪态变为非就绪态,将要延迟的时间赋值为TCB_Task[OSPrioCur].Dly,然后调度(也就是切换任务)


4.调度函数OSSched()


非常简单,刚才我们将某个任务变为了非就绪态,紧接着就找就绪态任务中优先级最高的任务,然后切换


5.是否完成了呢?----空闲任务


乍一看,好像完成了,实则不然,虽然我们任务1每X毫秒运行一次,任务2每Y毫秒运行一次,但终归里面有空闲时间:任务1和任务2都不在运行,所以我们需要创建一个空闲任务,当CPU没有东西可以运行时,运行空闲任务。以下就是空闲任务:


6.来到主函数:


其他的倒是没问题,72行有个陌生的函数:OS_TASK_Init();


其实就是之前的OSStartHighRdy()函数的升级版,非常简单


先创建一个空闲函数,再获取优先级最高的任务,然后执行最高优先级的任务。


7.一个优雅的简易OS诞生了


不好看?想加个界面?没问题-----其实已经有了,大家观察58行,就是让液晶屏显示一句话,我们把背景修改为红色,醒目一点:


很开心能有这么多朋友喜欢,非常感谢 。我开了一个简单的头,相信真的喜欢os的朋友,只要认真去做,一定也能实现一个更好的作品。后面的暂时不打算写,如果有了新的思路,一定会再写出来。授人以鱼不如授人以渔,看到这里已经有动手的能力了,想写的朋友不要害怕,尽管去做!!!加油


五、加入信号量
六、加入消息队列
七、内存管理
八、实现一个free和malloc玩玩?


这个事情我干过啊。


我来介绍一下清华计算机系的 OS 课程吧 -- ucore
2007 年以前,清华 OS 课程用的是 MIT 6.828 的 JOS,完全照搬实验内容。
2008 年的时候,陈渝老师开始从 xv6 出发,设计清华自己的课程实验。(xv6 是 JOS 的课程阅读材料,JOS 的前餐,宏内核架构。好处是简单,缺点是太简单以至于什么都没有。。)
2010 年的时候,经过陈渝 & 向勇老师以及几代助教不停的努力,xv6 被改造的越来越丰(hun)满(luan)了。那会儿我在陈老师实验室读硕士,正好不喜欢分配的项目,就和老板商量了一下,重新设计和实现了一个新的玩具系统给学生做实验,也就是后来的 ucore。


印象最深刻的是,当我提出重新搞一个小 OS 做玩具的时候,陈老师表示绝对的支持,连夜给我列了一个差不多两页纸的提纲。。。于是我增加了 6-7 个月的工作量,差不多 2w 行代码。。。。当然,这是吐槽。。。。


== 下面说点 ucore 有关的正题 ==


GitHub - spinlock/ucore 这个是我 2011 年最初的 ucore 代码,后面会介绍一下里面都实现了哪些东西。当时完成项目之后就赶紧忙毕业去了,所以 lwip 和 smp 部分并没有实现。
chyyuu (chyyuu) · GitHub 这个应该是陈渝老师的账号,可以关注一下~~(所搜 ucore 的话,比如 Search · user:chyyuu ucore · GitHub 能找到一大堆的东西)。
GitHub - chyyuu/ucore_os_lab
GitHub - chyyuu/ucore_os_docs
GitHub - chyyuu/ucore_os_book
GitHub - chyyuu/ucore_os_plus 这个应该是我毕业以后很多学弟的修改版,增加了很多东西,也删掉了很多东西,我都快认不出来了。。


总体来说了,这个项目还是很不错的项目,除了代码屎一些。


完全是按照操作系统从启动,到内存管理,到进程调度,再到文件系统,最后到用户态程序和简单的 shell,这么一路写下来的。考虑到再玩儿下去我就快没法毕业了,所以 lwip 和 smp 最后并没有支持。


至少在 spinlock/ucore 下面,一共实现了 6个 lab,lab 下面又有 19个 project,每个 project 又可能有多个不同的实现方式。总之,从 ucore 的目录结构上能看出来,每一个 project 都是在前一个 project 的基础上进行迭代,修改而来的,你能很清晰的看出来,一个玩具 OS 是如何从几百行开始逐渐完善出来的。(这也是我最后没有把 smp 写完的原因,因为 smp 需要从内存管理开始支持,虽然 smp 本身很简单,典型的如 spinlock、rwlock 的实现也很简单,但是要从最开始改起实在是太烦了)


当然,最终也还是一个玩具。


== 回到如何从 0 写一个完整的 OS ? ==


看 ucore 项目下的 README,那是最好的提纲
然后开始实践的时候,要多读代码
启动过程代码我参考了 JOS 和 xv6 的代码,涉及到硬件,你还需要一本 intel 手册,第二卷介绍各种指令集,第三卷介绍 x86 架构
内存管理开始,我推荐 《Linux 内核源代码情景分析》这本书,虽然 2.4 内核比较老,但是书写的很好,另外 《深入 Linux 内核架构》写的也不错,我看的书不多,当时大部分代码和设计参考了前者
文件系统部分我参考的 os161 的代码和 ext2 有关的设计
凑个数


嗯,我能介绍的就这些。这只是一个玩具 OS,据说能在真机上跑起来,我没试过。


== 一点讨论 == 


有没有 GUI?没有,因为我不觉得那个和操作系统原理有什么很大的关系。
为什么 x86?作为计算机课程教学,我觉得 x86 普遍一些,后来做了 x64 的支持,主要是启动和内存管理上的修改(为了支持 x64,我用了一周时间修改代码,当然结果是很糙的了,用了大量的 long jump 使得编译出来的二进制文件体积很大,实际上有更巧妙地方法,但是那会儿我真的快毕业不能了,所以就暂时先那样了,不然还要预计花费1周时间)。现在看可能选 ARM 更简单一些,不过作为教学,特别是 2012年,我觉得可能 x86 更接地气一些 ~~


我操作系统原理懂得不是很多,学的也不是很好,后来写 ucore 的出发点其实是给自己补补课~


== README.md ==


下面就是 spinlock/ucore 下面 i386 部分的 README.md,你能看到面都有些什么(仅列出实现的部分,每个项目后面的括号表示该项目是从哪个项目的,话说 README 不知道是谁写的,虽然烂的看不下去,但都是宝贵的历史遗产,给翻译一下):


--------------------------------------------------------------
lab1: boot/protect mode/interrupt


boot/protect mode
---------------------
proj1       (--)        : bootloader 保护模式,串口输出字符串
proj2       (proj1)     : bootloader 读写磁盘,读取 elf 文件
proj3       (proj2)     : bootloader 加载 OS 并输出字符串


stack
--------------------
proj3.1     (proj3)     : 打印函数栈信息


interrupt
--------------------
proj4       (proj3.1)   : 响应串口、键盘以及时钟中断
proj4.1     (proj4)     : 支持 LGDT LTR 进行段操作,为 proj4.1.1/4.1.2 做准备


user mode v.s. kernel mode
--------------------
proj4.1.1   (proj4.1)   : 进行 kernel/user mode 切换,通过中断
proj4.1.2   (proj4.1)   : 进行 kernel/user mode 切换,通过门调用
--------------------
proj4.2     (proj4.1)   : 支持简单 remote gdb 调试
proj4.3     (proj4.1)   : 支持硬件 breakpoint & watchpoint 调试


lab2: memory management (physic/virtual)
-------------------------
proj5       (proj4.3)   : 物理内存检测和分配框架、支持分页
proj5.1     (proj5)     : 物理内存分配策略:best fit
proj5.1.1   (proj5.1)   : 物理内存分配策略:first fit
proj5.1.2   (proj5.1)   : 物理内存分配策略:worst fit
proj5.2     (proj5.1)   : 物理内存分配策略:buddy 4K ~ 4M


slab
--------------------
proj6       (proj5.2)   : 物理内存分配策略:slab 32B ~ 128K


virtual memory management structure
--------------------
proj7       (proj6)     : 支持缺页中断 & 虚拟内存管理


swap
--------------------
proj8       (proj7)     : 页替换 swap in/out


memory map
--------------------
proj9       (proj8)     : 内存映射 map, unmap, dup, exit 等
proj9.1     (proj9)     : 共享内存 (share memory)
proj9.2     (proj9.1)   : 支持 COW (copy on write)


lab3: process
-------------------------
proj10      (proj9.2)   : 支持 PCB, 内核线程 (kernel thread)
proj10.1    (proj10)    : 用户态 elf 程序加载以及 fork, execve, load_icode, yield 和 round-robin 调度策略
proj10.2    (proj10.1)  : 支持系统调用 wait, kill, exit
proj10.3    (proj10.2)  : 支持堆管理 sys_brk
proj10.4    (proj10.3)  : 支持 sleep, timer 有关的操作和系统调用
proj11      (proj10.4)  : 创建 kswapd 管理 swap 分区
proj12      (proj11)    : 系统调用 map, unmap, 以及父子进程间的共享内存、线程组、基于共享内存的进程间通信


lab4 scheduling
-------------------------
proj13      (proj12)    : 调度策略 First Come First Serve (FCFS)
proj13.1    (proj13)    : 调度策略 Round-Robin (RR)
proj13.2    (proj13.1)  : 调度策略 MultiLevel Feedback Queue (MLFQ)


lab5 mutex/sync
-------------------------
proj14      (proj13.2)  : 内核支持 semaphore
proj14.1    (proj14)    : 用户程序 semaphore 以及基于 semaphore 的进程间通信
proj14.2    (proj14.1)  : semaphore 支持 timeout
proj15      (proj14.2)  : event (with timeout)
proj16      (proj15)    : mailbox


lab6 filesystem
-------------------------
proj17      (proj16)    : vfs 框架, file, devices (stdin, stdout, null)
proj17.1    (proj17)    : pipe (anonymous pipe and fifo)
proj18      (proj17.1)  : sfs 'simple filesystem' 仅文件操作
proj18.1    (proj18)    : mkdir/link/rename/unlink (hard link)
proj18.2    (proj18)    : 支持 exec
proj18.3    (proj18.2)  : 支持 exec 参数 (最多 32)
proj19      (proj18.3)  : 简单 shell


最后提一句。spinlock/ucore 下最开始没有任何注释,里面的注释都是后来别人加的,很多地方都不太对。chyyuu/ucore 下的代码注释可能是学弟完善的,也可能不是。所以参考的时候小心一下~


操作系统这玩意…并不是都像windows那样图形界面一堆工具,甚至不像linux发行版那样带一堆命令行工具。
以linux为例,图形界面就不说了命令行?那是bash,是个独立软件包,人家在bsd在unix在darwin上都跑得妥妥的。
一个纯粹的操作系统,其实只是定义了驱动接口(用别人的驱动),定义了最简单的进程调度管理,定义了内存分配。这就已经是操作系统了。
所以写一个新的操作系统真的真的不是特别困难。困难的是你的os出来之后除了你自己大概是不会有人给他写驱动写程序的,除非用户多;啥都没有的os不会有人用。于是恶性循环…


MINIX完整实现一段一段给你讲,包括代码都给了。是的,就是那个MINIX。


我大约是在08-09年的时候写过一个迷你的操作系统。大家可以在这里看到我的源代码 https://code.google.com/p/minios2/。当时这个项目完全是自己的兴趣爱好。后来代码较多了觉得需要花费过多的精力不合适就放弃了。


整个项目是从0开始的。因为主要在windows上开发,所以主力编译器是msvc6.0。虽然说很不可思议。但是当你明白了编译链接的原理以及PE文件的格式之后,这其实并不难。当然现在如果用高版本的msvc写的话会更容易。


另一个难点是需要寻找以及阅读大量的资料。包括比如386保护模式,bios调用,8259中断控制器,pci总线控制器,8253时钟控制器,ATA硬盘控制器,各种以太网卡控制器等。当时这些资料在网上非常分散,收集很不容易。现在貌似好找多了。


另外你需要对数据结构,计算机体系结构以及操作系统原理有一定的了解。这个基本上本科和研究生课程里的知识就足够了。当然你也要有足够的编程经验,因为有些错误可能会很难调试。


以下是我当时写的一个简单的文档。


minios目前已经完成的功能:


bootsector


进入保护模式


内存分配模块


简单的线程调度模块


信号量


时钟


延时调用DPC和定时调用TPC


统一的设备驱动模型


标准输入输出设备驱动


内存及字符串相关的标准C库函数


附件codes.zip的目录结构如下:


codes


|-relocate        连接程序的源代码,将bootsector和minios连接成一个可启动的磁盘镜像


|-bootsector      bootsector的源代码


|-minios          minios的源代码


|-bin             所有的目标都在此目录中。其中minios.vhd就是可启动的磁盘镜像


如何启动minios:


你必须安装Microsoft的Virtual PC 2007


你可以在微软的官方网站下载他的安装程序,程序大小约30M


http://download.microsoft.com/download/8/5/6/856bfc39-fa48-4315-a2b3-e6697a54ca88/32%20BIT/setup.exe


安装完成后就可以双击codes/bin/vm.vmc运行minios了


如何编译minios:


编译minios共需要三种编译器。


codes/bootsector/bootsector.asm必须用nasm进行编译,将编译的结果命名为bootsector并且拷贝到codes/bin


codes/minios/platform/platform.asm必须用masm32进行编译,编译的结果在codes/minios/platform/platform.obj


其余的代码都用vc6编译即可,vc6的工程在codes/minios/minios.dsw


如果你手边没有nasm和masm32,不要紧,因为这两个文件一般不需要改动,直接用我编译好的目标文件就可以了


双击minios.dsw打开vc6,点击菜单Project->Project Setting->Debug,修改Executable for
debug session一栏


将Virtual PC.exe的完整路径填入。如果你安装在默认的路径下,就不需要修改它。


然后直接Ctrl-F5运行就可以编译并且运行了。


vc工程是在dll的工程的基础上配置的


1、将所有相关的文件加到工程中来。


2、由于对于debug版本的代码生成,vc会加入不少调试代码,不好控制,所以删除Win32 Debug的配置


3、由于默认的Release配置中,会加入Intrinsic
Functions的优化,他会用vc libc中的函数代替你写的标准C语言库函数。因此必须自定义优化方案。project setting->C++->Optimizations选customize并且勾上除了Assume
No Aliasing, Generate Intrinsic Functions, Favor Small Code, Full Optimization外的优化选项。


4、在project
setting->C++->Preprocessor->Additional include directories中加入include这个目录,并且勾上Ignore
standard include paths


5、project
setting->Link中,output file name改成../bin/minios.dll。勾上Ignore all default libraries和Generate mapfile,
object/libraty modules中的内容清空


6、project
setting->Link->Debug中mapfile name改成../bin/minios.map,project setting->Link->Output中Entry-point
symbol改成main


7、project
setting->post-build step中添加一行"../bin/relocate.exe" ../bin/minios.dll ../bin/bootsector
../bin/minios.vhd


8、project
setting->Debug中Executable for debug session改成C:\Program
Files\Microsoft Virtual PC\Virtual PC.exe,working directory改成../bin,Program
arguments改成-singlepc -pc vm -launch


如果我没有忘记什么的话,应该就这些了。这样你的vc就可以编译minios的原代码了。编译的结果在../bin/minios.dll


为什么使用dll的工程呢?


因为windows的dll中有一个relocation的段,他列出了该dll文件如果要重定位的话所有需要修改的地址偏移。假设dll默认的加载位置是0x10000000,而在minios中我希望把它定位在0x400000则只需要把重定位表的每一项所指向的地址减去(0x10000000- 0x400000)就可以了。这也是relocate.exe这个程序的主要工作。


至于具体pe文件的结构以及重定向表的结构,网上有很多,我手边暂时没有资料,可以参看relocate.exe的原代码


minios的引导过程和内存布局


首先,pc机的bios程序会将bootsector加载到0x7C00, 此时段寄存器的值我也不大清楚,但是不要紧,自己重新设一遍吧。把ds, es, ss都设成cs一样的值,把sp放在0x8000的位置上,这样我们就有了512字节的堆栈了。


然后,bootsector将minios读出放在从0x400000开始的内存空间上。随后bootsector简单的设置了GDT后直接进入保护模式并且将控制权交给minios的entrypoint。从0x100000到0x3fffff是内核堆,内核所需要的动态内存都可以从此堆上使用keMalloc和keFree分配。内存最低的4K字节被用来存放中断向量指针,就像纯DOS那样。从0x4000开始到0x8000存放了PCI总线配置数据块。从0x10000到0x1ffff的64K字节用来作为IDE的DMA内存块,其他的低端内存暂时还没有分配,可能会作为文件系统的缓存。


main函数先重新配置了8259中断控制器,8253时钟控制器,设置了IDT
GDT,初始化时钟,内核堆和任务子系统后,建立了第一个任务main,入口是keEntryMain


keEntryMain首先打开中断并且加载console和keboard的驱动,然后建立DPC任务,kdebug任务以及一个测试任务


我来骗一下star哈哈
szhou42/osdev
虚拟内存✔
硬盘驱动,EXT2文件系统,虚拟文件系统✔
简陋的GUI界面✔
多进程(烂尾楼 :) )✔
网络数据收发(只是简单收发raw packets,各种网络协议还在写)✔


这是我学习过程中用到的主要网站和资料
BrokenThorn Entertainment


Global Descriptor Table


Unofficial Mirror for JamesM's Kernel Development Tutorial Series


Expanded Main Page


Build software better, together :)


楼上大神全是几千字长文看起来有点怕,我简明扼要地说一些写OS的重要心得吧


1 教程上没看懂的代码千万不要照抄,最好在自己理解的基础上重新写一遍。我在看JamesM's Kernel教程的paging部分时就吃了这个亏,全盘抄了他的代码,结果发现他的代码总有神奇的bugs让你的os崩溃。结果我把写了一个半月的代码全部推翻,在完全理解后自己重新写出来的paging代码,不能说完全没bugs吧,至少现在已经四五个月了都没有再因为paging部分的代码而使os崩溃。


2 搭建方便使用的调试器,我个人用的是qemu+gdb配合,源码级调试


3 在os最基础的设施(中断,异常,VGA driver)都实现后,马上写个printf和hexdump函数,因为有一些极端情况,gdb下断点+单步跟踪+观察变量 这种办法会失效 :(。


4 快点写个malloc函数!越快越好!!文件系统,进程管理,GUI这些都需要用到大量的数据结构,而最方便的方法就是用malloc来申请和释放这些结构。


最后,说一下os开发的流程,这只是我个人的路线。
第一步,很多人会想写bootloader,但是我建议先跳过这一步,直接用grub或者qemu的自带bootloader,先跳过这些繁琐的细节,专注于OS内核的开发。


第二步,建立好各种gdt,idt,中断,异常等机制,这样系统出什么错的时候马上就能发现。


第三步,printf函数,这意味着你得先写VGA driver,但两者都不难


第四步,实现虚拟内存和分页机制,在此基础上实现kmalloc函数。


第五步,实现多进程/线程


第六步,写个PCI驱动!PCI是用来访问各种硬件的,例如硬盘,网卡,都得通过PCI来控制,实际上我就是因为想写硬盘驱动,才写的PCI驱动。


第七步,写硬盘驱动,实现EXT2文件系统,实现VFS文件系统。


第八步,GUI,设计一种数据结构存储和显示各种窗口。初步可以用VGA试验一下编写图形操作系统的乐趣,但是要想有高分辨率 真彩色还是得写VESA驱动才能得到。 很多人觉得图形操作系统很酷炫,但这反而是写OS里面最简单,最容易调试的一步(当然了要做VESA驱动还是很麻烦,因为在保护模式下没法用中断调用)。


第九步,实现网卡驱动,实现TCP/IP协议栈!


第十步,发挥你的想象!Network File System maybe?


强答一个给自己的os做个广告。。
https://github.com/zhengruohuang/toddler


这个os的设计目标是。。highly portable, highly flexible, highly usable
(high performance,real-time神码的暂时不在考虑范围之内,下面你会看到,很多设计并不high performance)
另外,虽然说是portable,但是除了ia32以外,只把一小部分移植到了raspberry pi 2上。。
(如果有人想donate各种奇特arch的machine的话欢迎联系我。。尤其需要mips和sparc)


大概说一下各种feature
首先这个os支持smp,嗯,貌似比不少人的都厉害了
然后,这个是microkernel。。但是并没有做成那种夸张的many-server的(尽管如此但是其实也有不少server)
然后当然,kernel本身也被做成了一个process,而kernel本身并没有像其他os一样被map到了所有process的1g或2g的位置。每个process的最高4mb的位置,被map到了被称为hal的地方。。对,就是hardward abstraction layer那个意思的hal。这样每个process几乎可以利用全部的address space,以及,尤其对于kernel,便不再存在所谓high memory的概念了,对kernel而言,便是简单的1 to 1 map,这样便极大的简化了physical mem mgmt的复杂程度,以及,整个设计看起来非常的干净
另外对于hal,便是实现highly portable的核心。所有arch相关的代码全部在hal中,而hal提供了一组函数以及定义供kernel使用,当然代价是牺牲了很大的性能以及相当多extra的context switch。hal所导出的函数都是经过精心设计的,尽量避免了over abstraction。例如说,kernel并不假设某个arch支持mmu或某种特定的mmu设计,因此hal所导出的与mem map相关的函数,也非像linux一样假设了抽象的多级页表结构


然后,是计划之中的。。environmental systems。对于os本身而言,kernel和其他essential 的各种server并不应当是unix-like,或者刻意去支持、模仿某一种os或标准,而那些工作,应当交由env systems来实现。例如说,虽然某一个server中,实现了一种树形结构用于管理多种资源,但并不应是完全像vfs一样,而完全兼容linux的vfs则应当在linux env system中实现,也就是将vfs的结构map到底层os的结构中。当然这样做的目的是,一个修改过的linux kernel作为一个env system,从而运行多种linux程序,而实现highly usable。当然,env system还可以油很多,比如freebsd env system,reactos env system等等
当然,这么做虽然蛮有趣,但并不一定是最好的办法。但是作为一个人在业余无聊时间开发的os,为了让它能运行很多很多程序,从而可能有人来用它,最终变为highly usable,也是不得以而为之的了


最后还有件事。。这个os还有自己的build system


ps 看到有的答案说“有时候觉得书里的做法非常奇怪,你觉得你有更好的做法,一般是你想错了。(当然只是一般)” 
真的蛮不喜欢这种说法的
虽然我很推崇祖先的智慧很伟大。。而且“当然只是一般你想错了”
但是,os这个东东,完全取决于你的设计,你觉得有更好的做法,是在正常不过的事情了,祖先的做法,很多是有历史局限性的(例如fork和exec,如果你不知道的话,这么做最开始完全是为了照顾一种奇葩的机器,但后人把这种奇怪的机制吹了天花乱坠也是莫名其妙),但假如你无法跳出那种历史局限的话,一味的否定自己更好地想法也是正常不过了


有本书不错,叫做操作系统设计:Xinu方法 (豆瓣)
手把手写操作系统。作者是普度大学大名鼎鼎的Comer。
麻雀虽小,五脏齐全:进程调度,信号量,消息传递,内存池,io中断,时钟,文件系统,shell(叫做tty),还有驱动(虽然只是路由器),甚至还写了个小型的UDP协议栈。


不过有一点,作者用的是mips,虽然有改写成x86版本(xinu在vbox上,使用debian8,并在上面进行xinu编程 - u011274209的专栏 - 博客频道 - CSDN.NET)。而且里面用的是内核级线程,但作者称为进程。


https://github.com/wuxx/8086sos
x86上实现的一个极简的“操作系统”,两个任务互相切换,300行8086汇编,代码仅占用在一个MBR里。


https://github.com/wuxx/sos
最近在写的一个嵌入式操作系统,基于树莓派,目前实现了多任务,信号量,邮箱,自写了一个简单的libc。


试着做过。这是一个极有挑战的事情。它需要你有非常广的知识:
最基本数字电路知识,这样你才能知道如何控制芯片。
你需要熟悉一款CPU,这很难。新出的CPU如ARM资料少。传统的CPU如x86设置起来又极为复杂。
你需要非常熟悉C语言、比较熟悉汇编语言,还要熟悉汇编inline,需要比较熟悉编译器,熟悉freestanding的环境。
你需对操作系统是如何工作的非常清楚,因为你几乎不可能设计出一个标新立异的操作系统来。
你需要有极强的诊断调试能力,你通常无法知道你的程序为什么不能正常运行,这几乎会逼疯你。


如果是x86建议按以下步骤来走:
1、写一个bootloader能输出HelloWorld.
2、你的Bootload能加载另一段程序主程序,主程序能显示HelloWorld。
3、主程序能进入保护模式,能接收定时中断、并在屏幕上显示时间。
能做到第3点你应该就知道下面应该干什么了。但我觉得工作5年,能做到第3点的人少于1%。


大二下学期的时候,迷上了操作系统,于是去图书馆借了一本于渊的《一个操作系统的实现》,然后就开始一步一步跟着写,这个系统底层是基于x86的。前后大概写了有三四次吧。前两次都是跟着写,然而经常会被各种考试之类的打断,很难再接着原来的思路进展。后来第三次第四次的时候感觉已经驾轻就熟了,就索性脱开书去做,当然还是照着作者的思路,只不按自己的理解重新去实现一遍,期间也参考了其他的一些相关资料。基本上把内存保护映射,异常处理,进程切换,外设驱动,字符终端,图像显示之类的都简单实现了一遍。后来不满足于虚拟机里玩,又把系统写进了我的U盘,有时候无聊了,就把U盘插电脑上跑一跑,自我满足一下。
后来接触到51、ARM等系列的芯片,也在上面简单的实现过。不过可用资源大都很少,简单的实现一下进程切换而已了。
再后来入坑linux,看了一些内核代码,驱动代码,感觉自己原来的代码很low,就没在继续了 
========

自己动手写操作系统-工作环境搭建

 
最近在看《自己动手写操作系统》 于渊著,看第一页头就蒙了,书上说要准备一张空白软盘,都2010年了,上哪去找软盘啊,自从接触计算机以来我还没见过软盘呢,估计要到科技博物馆才找得到吧,呵呵。其实在第二章-搭建你的工作环境 中作者提到其实并不需要用真正的软盘,用虚拟软盘映像就可以了,也就是*.IMG文件,反过来想想,连计算机都可以虚拟,软盘当然也可以虚拟了。软盘映像文件并不需要自己制作,随书附赠的光盘中的源代码里面就有(TINIX.IMG),将自己写的汇编源代码编译成*.bin文件后使用作者提供的软盘绝对扇区读写工具FloppyWriter将*.bin写入到TINIX.IMG,这样一张虚拟的引导盘就完成了。FloppyWriter工具在光盘的Tools文件夹中。如果没有光盘也不要紧,可以在博文视点网站下载,网址是:http://www.broadview.com.cn/01577 (需要先登录),也可以在http://osfromscratch.org/downloadcn 下载源代码。
   
一、 开始搭建工作环境:
    1. 一台计算机(Windows操作系统)
    2. 虚拟机 VirtualBox
       可以在官网下载,下载地址:http://www.virtualbox.org/wiki/Downloads
       之所以选择VirtualBox是因为它是开源免费的,速度也可以,作者使用的虚拟机是Virtual PC 5.0,该虚拟机是商业软件,5.0版本的比较难找,网上可以找到5.2版本的,但我下载安装后不能正常运行,提示不能在PAE模式下运行,虽然网上可以找到解决方案,需要对电脑做些配置,但觉得麻烦,个人还是喜欢用VirtualBox,下载安装后没有任何问题。
    3. NASM 汇编编译程序
      下载地址:http://sourceforge.net/projects/nasm
      或者官网下载:http://www.nasm.us/pub/nasm/releasebuilds/
      这个是用来编译我们写的汇编程序的,以前常用MASM,但作者推荐这个。
    4. 软盘绝对扇区读写工具 FloppyWriter.exe
       在随书附赠的光盘中提供,在OS/Tools/FloppyWriter/Release文件夹下,运行 该程序有两个按钮可以选择,使用软盘映像的话使用下面那个Write File To Image。前面提到,如果没有光盘,可以去网上去下载光盘中的内容。
    5. 源代码编辑器
       可以用记事本,也可以用其他编辑工具。


二、写一个小的“操作系统”
     1.编写源代码:(书上有)
      ;boot.asm
      org 07c00h      ;告诉编译器程序加载到7c00处
       mov ax, cs
       mov ds, ax
       mov es, ax
       call DispStr    ;调用显示字符串例程
       jmp $           ;无限循环
DispStr:
       mov ax, BootMessage
       mov bp, ax       ;es:bp=串地址
       mov cx, 33
       mov ax, 01301h   ;ah=13, al=01h
       mov bx, 000ch    ;页号为0(bh=0) 黑底红字(bl=0ch,高亮)
       mov dl, 0
       int 10h          ;10h 号中断
       ret
BootMessage:       db  "Hello lijie, Welcome to OS World!"
times  510-($-$$)  db 0 ;填充剩下的空间,使生成的二进制代码恰好为512字节
dw     0xaa55           ;结束标志
   
     2. 编译boot.asm
      安装或解压缩NASM后,将NASM的安装目录加到PATH环境变量中后就可以在命令行的任何目录下使用NASM命令。进入到源代码所在目录,输入命令 nasm boot.asm -o boot.bin 后回车便完成了源代码的编译。
     3.将boot.bin写到TINIX.IMG
       从光盘源代码中复制TINIX.IMG文件到你的源代码目录下,运行FloppyWriter.exe,点击Write File To Image,分别在弹出窗口中选择boot.bin和TINIX.IMG,提示成功 OK。
     4. 在虚拟机中运行
       a.在虚拟机中新建一台PC
        启动VirtualBox->新建->名称任意取,操作系统选Other,Version选Other/Unknown->内存默认(可改变)->创建新的虚拟硬盘->动态扩展->选择虚拟硬盘的保存地址或默认,大小默认(可改变)->完成。
       b.将TINIX.IMG注册到VirtualBox
        管理(F)->虚拟介质管理(V)->虚拟软盘(F)->注册(A)->在弹出窗口中找到刚刚制作完成的TINIX.IMG打开->确定。
       c.运行刚刚创建的PC
        选择刚刚创建的PC->开始->首次运行向导->下一步->介质类型选软驱,介质位置选刚刚注册的TINIX.IMG->下一步->完成。OK,应该可以看到哪一行黑底红字了吧,呵呵。


第二种方法:
最近在业余阅读时找到一本《自己动手编写操作系统》觉得不错,视角很新颖,是有关操作系统书籍中为数不多的看了不想睡觉的读本。
 
由于这本书的出版时间较早,书中引导盘还使用的是软盘,所以在跟随做的第一个实验代码时就遇到些小困难。最后实验成果后在此做个记录。
 
书中第一个实验就是编写一段汇编代码作为引导程序,编译后将二进制文件写入到引导盘(书中采用的是软盘),然后让电脑从软盘启动进而执行我们的引导程序。
 
首先给出书中的引导程序——boot.asm
    org 07c00h
    mov    ax,    cs
    mov    ds,    ax
    mov    es,    ax
    call DispStr
    jmp $
DispStr:
    mov    ax,    BootMessage
    mov    bp,    ax
    mov    cx,    16
    mov    ax,    01301h
    mov    bx,    000ch
    mov    dl,    0
    int    10h
    ret
BootMessage:    db    "Hello, OS world!"
    times    510-($-$$)    db    0
    dw 0xaa55
下面则需要对这段汇编代码进行编译,于是我们就遇到了第一个问题:选择什么样的编译器?如何编译代码?
书中推荐了使用NASM,至于为什么要选择NASM,大家可以百度一下NASM诞生的故事,它的诞生就是因为诸多汇编编译器的种种劣势,最后催生出NASM这个比较好用的编译器。
安装NASM。NASM没有图形化界面,要么装在DOS下,要么装在Linux下。本人的电脑是WIN8+Ubuntu的虚拟机,所以果断选择在Ubuntu中安装了NASM,只需要在终端敲上apt-get install nasm,然后等着安装完成即可。安装后测试下:
 
01.jpg
安装完成编译器后将windows下编写好的代码拷贝到Ubuntu中用编译器编译,发现出错。编译生成的二进制文件boot.bin大小为0,并不是我们期望的512字节。
于是便在Ubuntu下用vi打开我们的汇编代码查看一下,发现:
02.jpg
在代码中出现了很多的“^M”字符。
哦,原来我用了DOS格式的文件放在了UNIX下面进行了编译,难怪编译器不认识。
注意:在DOS格式下面,回车换行是0x0D0A,而UNIX下是0x0A,所以当DOS格式文件到了UNIX下面后0x0D则会变成“^M”这个控制字符。
03.jpg
这个问题的解决也有很多种方法,而本人是直接在UE中把文件都改成用UNIX格式编辑了:
04.jpg
 
修改了格式以后,在Ubuntu下可以正确编译了!生成了一个512字节大小的二进制文件boot.bin。
 
下面就该是制作启动盘的问题了。要知道,现在的电脑想要用软盘启动已经是不可能了。但是我们有个东西叫虚拟机——它可以给我们提供一个“软盘”接口,不过同样也是个虚拟接口。
 
虚拟机我用的是VM Player,并没有去用workstation,因为新版本的player已经功能够强大了。如下图一步步建立自己的虚拟机,但是在驱动选择时,选从软盘启动。
05.jpg
06.jpg
07.jpg
08.jpg
09.jpg
我们得为软盘提供一个image文件,但之前对boot.asm文件编译后得到的是二进制文件boot.bin。网上也有很多提出的方法如何将bin文件打包成img文件,其实我们简单分析一下会发现,两者的转换其实只需要在windows下改一下后缀名!!!原因是:img文件也就是镜像文件,原本就是对软盘中的数据进行二进制级别的拷贝备份,而我们的bin文件本身就是二进制文件,所以对于内存或者存储设备来说,在这个地方img和bin文件是完全一样的。
 
将boot.bin改后缀变成boot.img后就可以让虚拟机去加载它了。
然后去启动你的“操作系统”吧!

========

跟我一起写操作系统(一)——10分钟写个操作系统

http://www.cnblogs.com/lucasysfeng/p/4846119.html


想动手,但不知从何入手,是学习一门新知识普遍会遇到的尴尬点。笔者喜欢边实践边学习理论,笔者的写作思路是: 入门的文章要避免讲一些高深的理论,而应该先抛出demo,从研究demo入手,逐步加深demo的难度,从而学习这个过程中涉及到的理论知识 。下面就让我们花10分钟写个“操作系统”。


第一节 开发环境


我们在linux下制作软盘、编译内核等,因此需要linux开发环境。如果你用windows, 那么在windows下安装VMware, 在VMware中安装ubuntu虚拟机,此ubuntu作为开发环境。


注:笔者的开发环境是windows--VMware--ubuntu14.04.


第二节 计算机启动过程


写操作系统看似是一个复杂的过程,但 只要我们将过程分解,完成每一步,那么完成一个操作系统就是水到渠成的事 了。好了,我们就看一下计算机的启动过程,看操作系统何时被启动的。


第一步:读取BIOS


按下电源按钮后,计算机首先读取一块ROM芯片,这块芯片里的程序是"基本输入输出系統"(Basic Input/Output System),即BIOS.


第二步:硬件自检 


BIOS会检查计算机硬件是否满足运行条件,如果硬件出现问题,主板会发出不同含义的蜂鸣,启动中止。


第三步:启动顺序


硬件检查完成后,BIOS会将控制权交给下一阶段的启动程序,注意,“下一阶段的启动程序”可能存放在硬盘中,也可能存放在CD/DVD中,或者软盘中等等,可以设置BIOS选择从哪个设备启动。


第四步:主引导记录


BIOS找到了“下一阶段的启动程序”所在设备, 会读取该设备的第一个扇区,即读取最前面的512字节,称为主引导记录 。主引导记录会告诉计算机下一步到哪里去找操作系统。


第五步:bootloader


计算机读取"主引导记录"前面446字节的机器码之后,运行事先安装的“启动管理器”bootloader,由用户选择启动哪个操作系统。如果你安装了多个操作系统,那么就要从这步做出选择了。


第六步:加载内核


好了,选择操作系统(内核)后,会加载内核,下面就交给内核去处理了。


第三节 主引导记录


我们使用虚拟机来启动操作系统,上面的第一步和第二步我们不做,由虚拟机去完成;第三步“启动顺序”我们选择从软盘启动(我们用镜像代替,并不是真的软盘),需要对虚拟机做下设置,选择从软盘启动。下面重点来看第四步,我们写一下“主引导记录”,让BIOS读取我们写的主引导记录。


1. 引导扇区代码(不必深究,简单了解)


文件名:boot.asm


org 07c00h ;告诉编译器加载到07c00h处
mov ax, cs 
mov ds, ax
mov es, ax
call DispStr  ;调用显示字符串函数
jmp $  ; 无限循环
DispStr:
mov ax, BootMessge
mov bp, ax
mov cx, 16
mov ax, 01301h
mov bx, 000ch
mov dl, 0
int 10h
ret
BootMessge: db "hello, OS world!"
times 510 - ($ - $$) db 0
dw 0aa55h  
2. 编译


注:如果没有NASM,安装它 sudo apt-get install nasm


$ nasm boot.asm -o boot.bin


     执行完上述命令,会生成boot.bin文件,上面的步骤可以用下图表示:  
第四节 制作软盘引导映像


1. 先制作一个空的软盘映像文件diska.img:


$ dd if=/dev/zero of=diska.img bs=512 count=2880


2. 制作一个包含boot.bin的映像文件boot.img:


$ dd if=boot.bin of=boot.img bs=512 count=1


3. 将diska.img中1个扇区后面的数据拷贝到boot.img的后面:


$ dd if=diska.img of=boot.img skip=1 seek=1 bs=512 count=2879


这样就做成了一个大小为1.44Mb的包含引导代码的映像文件boot.img。


4. 将虚拟机ubuntu中的文件boot.img拷贝到windows下 (鼠标直接拖拽,如果不行google之)。


第五节 用软盘镜像boot.img启动一个空的虚拟机


1. VMware创建空的虚拟机,去掉开机从CD/DVD启动选项。 


2. 网络选择host-only模式。


3. 选择从软盘驱动,路径选择上一节已经拷贝到windows下的镜像boot.img. 


4. 开启虚拟机电源,看到如下画面,恭喜你,成功了。


好了,至此,我们完成了引导镜像,后续会介绍grub,即选择操作系统并载入内核,并进一步讨论进程管理、内存管理、文件系统和中断等等。

  • 23
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值