当人按下笔记本开机键时.cpu的cs寄存器(基址)跟ip(偏移量)寄存器加电.被强制初始化为(jmp xxx:xxx) 跳转到bios所在的地址.

接着bios开机自检(这个不需要了解,只需了解最后跳转到0x7c00处即可.对于写kernel的人来说也是透明的.除非你是写bios的).它将自动从0盘1扇区加载mbr(主引导程序,512字节必须是以0x55,0xaa结尾..如果不够,可以用times 510-($-$$) db 0.很多都是这样定义的.) 加载到0x7c00处.因为bios执行的最后一条指令时jmp 0x7c00处.

紧接着用汇编编写mbr.. Mbr的功能是也是从硬盘读取loader(加载内核的程序). Loader存放的位置跟加载到的内存地址由自己决定...mbr就是根据硬盘提供的接口(,0x1f2~0x1f7)读取内容.movsb之类的指令复制到内存.最后一个jmp指令跳转到loader加载的地址

(注:以上的地址都是物理地址)

紧接着编写loader程序(加载内核程序).在loader程序中.我打算通过bios中断获取整个计算机的物理内存.并且进入保护模式.因此在loader.S中..我要定义代码段,跟数据段.显示段(显示字符.一般是0xb8000位置.)还是定义页表(采用二级页表

--------------------------------------------------------------------

数据段的内容是定义

全局描述符表跟代码段数据段显示段描述符..还有定义各种段选择子.位置自己定义.(这里我深深体会到了intel为了跟前代保持兼容多么拼了..定义的描述符真的好恶心.....的说

代码段:

通过int 15h中断获取物理内存.保存在某一固定物理位置.紧接着打开A20 Gate.加载gdt(全局描述符表).将cpu的cr0的pe位置置为1(由此进入保护模式,cs,ds的值不再是段基址.而是段选择子)

紧接着将各种段选择子复制到段寄存器中(保护模式)................紧接着..就开始创建二级页表(位置.自己固定.不过我看知乎上..一般是在1m物理内存以下)..   .创建好以后.用sgdt将描述符表地址(现在是物理地址)加载到寄存器中(注意在创建页目录表的时候...一般我看知乎是将地址映射到0xc000000处.(windows是采用这地址)........开启映射前后....内核的虚拟地址(一个原本的物理地址变成了线性地址),还有0xc000000..这2个线性地址都要映射到1m以下物理内存.等于768页目录跟0页目录(寻址方式.一个虚拟地址的前10位为页目录表索引.中间10位为页表索引.最后10位为页框偏移量)都指向一个页表.

紧接着栈指针也加上0xc000000处...为了映射).紧接着将cr0寄存器的31位置为1,表示开启分页机制..注意之前的加载的gdt是物理地址.现在改为线性地址.需要重新加载(其实就是原来的地址+0xc000000处..一个lgdt[gdt_ptr].ok...最后.......跳转到内核所在的地址(自己定义内核所在的地址).......内核编写需要了解elf(格式.推荐<<程序员的自我修养>>..我没看..只是网友推荐罢了).定义elf头.程序头表.节头表.内核大小偏移量.等这些定义好了后.就可以加载内核了.一jmp指令就搞定了.....其实加载的内核一开始就是int main(void){while(1); return 0}....没办法.啥系统调用都没实现.啥都要自己实现....没办法...轮子是一步一步造出来的嘛.

保护模式的话.主要体现在cpu的特权级...有了操作系统后.其实一个程序分为用户部分跟内核部分(权限不同..一般用户部分是3特权级,0是最高特权级.还有IO特权级这个就懒得解释了都一样.哪些端口可以访问权限).........主要从低特权级转移到高特权级(用户态到内核态转换)只能通过各种门(任务们,调用门.陷阱门.中断门等) 条件.访问门时 cpl<=dpl[门] &cpl>=dpl[段] 这个表示r3可以使用内核服务访问段max(cpl,rpl)<=dpl[段] 这样就可以访问段. 从高特权级向低特权级返回的话.用ret弹出ip.或者retf cs:ip.......

PS:门描述符也是定义在GDT中.Call 调用门选择子.参数就是选择子.选择子找到门描述符.结果门描述符定义了代码段的基址(段选择子)跟偏移量..接着..找到内核例程.............坑爹.....对了..从用户态到内核态.所用的代码段跟数据段权限是不同的.so.所对应的栈也不同.有个TSS(任务状态段就是用来保存上下文的..其实一般只保存ss:sp... TSS效率比较低.linux只用了一个TSS并不切换).

紧接着要了解函数调用约定.查了wiki.c语言用的是cdecl约定(eax,edx,ecx由调用者保存.其他是被调用者保存.返回值在eax中.由调用者清理栈空间(就是esp+xx.实际不能说清理),参数压栈从右到左.例子自己google查吧

紧接着...卧槽..总该用c语言了吧.....之前一直用汇编..开始实现打印hello os.然后就....想顺便学学内联汇编..结果我了个大去...内联汇编是.AT&T汇编语法跟x86汇编有很大不同.没办法.....google对比下.一个一个指令弄呗......接着..想实现一个printf函数..除了 bios中断....算了..自己实现..一查显卡端口控制..我勒个去..吓得半死..VGA寄存器..一大堆..从0懒得实现了......从github抄了别人实现可以光标跟踪的输出函数....不过也懂得其原理.一个读入写出寄存器一个控制寄存器...忘了....不过不重要.略过

紧接着实现中断......哥现在理解的所谓操作系统就是由中断驱动的.所谓的时间片不过是(时间中断产生的间隔时间罢了),等于躺着被调用的代码....中断根据网上资料cpu提供统一的接口为中断信号的公共路线..毕竟没则么多引脚.分为INTR跟NMI(灾难性错误).只管INTR就可以了.编写中断向量表(IDT).并且要保存在IDTR寄存器中.一般发出的信号就是中断向量号.这个号就是中断描述符表的索引,加上IDTR得到某中断描述符具体位置.接着.中断描述符保存中断处理程序的选择子跟偏移量..接着道GDT中找.到代码段的起始地址再加上偏移量(保存在IDT描述符中).最后还要了解8259A(外设的可屏蔽中断的代理.包括键盘.时间中断).编程设置它初始化.设置主片葱片级联方式........最后编写中断处理程序(参考 unix v6实现)..最后时间中断....网上一般采用计数器8253设置时间中断发生的频率.提速降速.随你便.2333333                                接下来就是哥认为最兴奋的一笔.内存管理.通过位图映射来管理内存.位图的每一位映射一个4k大小的页.0表示该页未被使用.1表示已经被使用..位图相关处理函数写好后.正式开始规划内存了.. 
          内存池的划分参考知乎某大牛说的答案.内存池分为虚拟内存池跟物理内存池.虚拟内存池管理虚拟地址.物理内存管理物理地址.......................我的话把物理内存一半给用户,一半给内核........虚拟内存池(每个用户进程都有一个).但内核虚拟地址只有一个(因为内核只有一个.对不对).有关位图的位置就是自己定义了.......虚拟地址映射物理地址.完成此操作就OK了(不过这里还没实现用户进程.so.只完成了内核相关虚拟地址映射.).其实malloc的..就是用链表管理更小的内存块.我这里最小的内存块...是4k...太大了..要很小(16字节.32字节.64字节.等).用内存块描述符管理更小相同规格的内存块.用块描述符链表将整个链接起来.
      接下来实现线程..线程哎..说实话..也没啥.就一个执行流(有自己的栈.寄存器映像)..不过只能使用进程的虚拟地址(没资源).没啥很高深的..总之进程跟线程就是人为搞的代码块.各种状态也是人自己划分的.自圆其说.2333333333333.(注:我实现的是内核线程.不然..还要自己实现.用户线程调度..太麻烦了.不然这些都交给内核调度岂不美哉.
   一开始呢.要实现PCB(不然..你的上下文保存在哪?.TSS,cpu只自动获取ss:sp,需要自己手动压入当前各种寄存器状态.因为如果用TSS(在内存)切换效率相当低.so.基本linux也不用TSS).TCB分为2部分(一个是被中断打断的栈空间.一个是线程自己空间,说明一点.一般发现线程基本都是个函数.函数的地址....嘿嘿没错就是当运行这个线程时cs:ip指向函数地址.就通过赋值语句.)忘说了.pcb需要占用一页大小内存.so.从内核内存池中申请一页大小.)...有关内核调度.只不过时间中断处理程序调用了scedule函数(开始要判断是否该线程时间片用完了没).........用链表管理就绪队列.跟总队列..其实我搞的这简单内核最复杂不过是用来链表罢了.所以写个简单内核..初中生差不多都可以完成.当然linus那种大神....不是一般人可以达到的.

最后说说锁...保证原子性(不能被打断).其实最基本的就是一条关中断cli汇编指令.实现的.只不过封装了一层..哎.....没想到这么简单吧

最后说说系统调用(参考linux最初版本,实现的这功能其实现在已经被弃用了)...mov eax,子功能号. Int 号码.调用syscall_table[eax].(其中system_table保存的是子功能的函数名(地址)........)...最后就是无聊的抄...系统调用实现的代码了.............

最后要实现用户进程.用户进程最主要区别就是特权级是最低特权级3.还有有自己的虚拟地址.流程(向内核申请一页大小.创建pcb.初始化pcb.创建管理虚拟地址用户位图...紧接着创建线程...线程中创建页目录表.添加到就绪队列中...等待调度......这里最关键是要让用户进程在特权级3下运行..so.其用户进程所使用的代码段跟数据段都必须是3特权级......完结.

参考<<一个操作系统的实现>>,<<x86从实模式到保护模式>>.<<现代操作系统>>

(上面其实已经写的省略了很多.这是我一口气写完的..可能忘了很多....如果有啥不对.请求指正)

文件系统跟shell部分.(...文件系统打算照搬unix v6的..我读都没读完..shell参考学院的教材(unix/linux编程实践)......

好了.很惭愧.只做了一点微小的工作.谢谢大家.

写一简单kernel心得的更多相关文章

  1. linux设备驱动归纳总结(五):4.写个简单的LED驱动【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-84693.html linux设备驱动归纳总结(五):4.写个简单的LED驱动 xxxxxxxxxxx ...

  2. 一步一步写一个简单通用的makefile(三)

    上一篇一步一步写一个简单通用的makefile(二) 里面的makefile 实现对通用的代码进行编译,这一章我将会对上一次的makefile 进行进一步的优化. 优化后的makefile: #Hel ...

  3. 【Linux开发】linux设备驱动归纳总结(五):4.写个简单的LED驱动

    linux设备驱动归纳总结(五):4.写个简单的LED驱动 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  4. 用Python写一个简单的Web框架

    一.概述 二.从demo_app开始 三.WSGI中的application 四.区分URL 五.重构 1.正则匹配URL 2.DRY 3.抽象出框架 六.参考 一.概述 在Python中,WSGI( ...

  5. 如何写一个简单的http服务器

    最近几天用C++写了一个简单的HTTP服务器,作为学习网络编程和Linux环境编程的练手项目,这篇文章记录我在写一个HTTP服务器过程中遇到的问题和学习到的知识. 服务器的源代码放在Github. H ...

  6. 如何写一个简单的shell

    如何写一个简单的shell 看完<UNIX环境高级编程>后我就一直想写一个简单的shell来作为练习,因为有事断断续续的写了好几个月,如今写了差不多来总结一下. 源代码放在了Github: ...

  7. 用C#Winform写个简单的批量清空文件内容和删除文件的小工具

    用C#Winform写个简单的批量清空文件内容和删除文件的小工具 本文介绍这个简单得不能再简单的小项目.做这个项目,有以下目的. 1 当然是做个能用的工具 2 学习使用Github 关于用VS2013 ...

  8. 分享:计算机图形学期末作业!!利用WebGL的第三方库three.js写一个简单的网页版“我的世界小游戏”

    这几天一直在忙着期末考试,所以一直没有更新我的博客,今天刚把我的期末作业完成了,心情澎湃,所以晚上不管怎么样,我也要写一篇博客纪念一下我上课都没有听,还是通过强大的度娘完成了我的作业的经历.(当然作业 ...

  9. linux设备驱动归纳总结(十一):写个简单的看门狗驱动【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-112879.html linux设备驱动归纳总结(十一):写个简单的看门狗驱动 xxxxxxxxxxx ...

随机推荐

  1. 【单调队列】P1886 滑动窗口

    GET 单调队列 题目描述 现在有一堆数字共N个数字(N<=10^6),以及一个大小为k的窗口.现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值. 例如: Th ...

  2. python入门:输出1-10的所有数(自写)

    #!/usr/bin/env python # -*- coding:utf-8 -*- #输出1-10的所有数(自写) """ 导入time库,给kaishi赋值为数字 ...

  3. mybatis枚举类型处理器

    1. 定义枚举值的接口 public abstract interface ValuedEnum { int getValue(); } 所有要被mybatis处理的枚举类继承该接口 2. 定义枚举类 ...

  4. 【linux】 服务器文件说明

    文件名 说明 /etc/resolv.conf  域名解析服务器地址文件 /etc/services 服务程序对应端口号文件 /etc/passwd 登录账号文件 /etc/hosts 本地IP域名解 ...

  5. Django小总结

    初始Git git init 初始化本地仓库,会在根目录下创建一个.git文件夹 git log 查看提交日志 git status 查看日志 git add 文件名 添加到缓存区 git commi ...

  6. Can Japan stand up to US request to contain China?

    From Global Times Two days before US President Donald Trump's visit to Japan, A fake news story caug ...

  7. managed unmanaged

    Enable function-level control for compiling functions as managed or unmanaged.     #pragma managed # ...

  8. JAVA-基础(六) Java.serialization 序列化

    序 列 化 序列化(serialization)是把一个对象的状态写入一个字节流的过程. Serializable接口 只有一个实现Serializable接口的对象可以被序列化工具存储和恢复.Ser ...

  9. luogu1501 [国家集训队]Tree II

    lct裸题 #include <iostream> #include <cstdio> using namespace std; typedef long long ll; i ...

  10. 了解JavaScript核心精髓(一)

    ES5 1.声明脚本 <script type="text/javascript"></script> 2.DOM与BOM DOM(Document Obj ...