写一简单kernel心得
接着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心得的更多相关文章
- linux设备驱动归纳总结(五):4.写个简单的LED驱动【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-84693.html linux设备驱动归纳总结(五):4.写个简单的LED驱动 xxxxxxxxxxx ...
- 一步一步写一个简单通用的makefile(三)
上一篇一步一步写一个简单通用的makefile(二) 里面的makefile 实现对通用的代码进行编译,这一章我将会对上一次的makefile 进行进一步的优化. 优化后的makefile: #Hel ...
- 【Linux开发】linux设备驱动归纳总结(五):4.写个简单的LED驱动
linux设备驱动归纳总结(五):4.写个简单的LED驱动 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 用Python写一个简单的Web框架
一.概述 二.从demo_app开始 三.WSGI中的application 四.区分URL 五.重构 1.正则匹配URL 2.DRY 3.抽象出框架 六.参考 一.概述 在Python中,WSGI( ...
- 如何写一个简单的http服务器
最近几天用C++写了一个简单的HTTP服务器,作为学习网络编程和Linux环境编程的练手项目,这篇文章记录我在写一个HTTP服务器过程中遇到的问题和学习到的知识. 服务器的源代码放在Github. H ...
- 如何写一个简单的shell
如何写一个简单的shell 看完<UNIX环境高级编程>后我就一直想写一个简单的shell来作为练习,因为有事断断续续的写了好几个月,如今写了差不多来总结一下. 源代码放在了Github: ...
- 用C#Winform写个简单的批量清空文件内容和删除文件的小工具
用C#Winform写个简单的批量清空文件内容和删除文件的小工具 本文介绍这个简单得不能再简单的小项目.做这个项目,有以下目的. 1 当然是做个能用的工具 2 学习使用Github 关于用VS2013 ...
- 分享:计算机图形学期末作业!!利用WebGL的第三方库three.js写一个简单的网页版“我的世界小游戏”
这几天一直在忙着期末考试,所以一直没有更新我的博客,今天刚把我的期末作业完成了,心情澎湃,所以晚上不管怎么样,我也要写一篇博客纪念一下我上课都没有听,还是通过强大的度娘完成了我的作业的经历.(当然作业 ...
- linux设备驱动归纳总结(十一):写个简单的看门狗驱动【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-112879.html linux设备驱动归纳总结(十一):写个简单的看门狗驱动 xxxxxxxxxxx ...
随机推荐
- 【单调队列】P1886 滑动窗口
GET 单调队列 题目描述 现在有一堆数字共N个数字(N<=10^6),以及一个大小为k的窗口.现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值. 例如: Th ...
- python入门:输出1-10的所有数(自写)
#!/usr/bin/env python # -*- coding:utf-8 -*- #输出1-10的所有数(自写) """ 导入time库,给kaishi赋值为数字 ...
- mybatis枚举类型处理器
1. 定义枚举值的接口 public abstract interface ValuedEnum { int getValue(); } 所有要被mybatis处理的枚举类继承该接口 2. 定义枚举类 ...
- 【linux】 服务器文件说明
文件名 说明 /etc/resolv.conf 域名解析服务器地址文件 /etc/services 服务程序对应端口号文件 /etc/passwd 登录账号文件 /etc/hosts 本地IP域名解 ...
- Django小总结
初始Git git init 初始化本地仓库,会在根目录下创建一个.git文件夹 git log 查看提交日志 git status 查看日志 git add 文件名 添加到缓存区 git commi ...
- 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 ...
- managed unmanaged
Enable function-level control for compiling functions as managed or unmanaged. #pragma managed # ...
- JAVA-基础(六) Java.serialization 序列化
序 列 化 序列化(serialization)是把一个对象的状态写入一个字节流的过程. Serializable接口 只有一个实现Serializable接口的对象可以被序列化工具存储和恢复.Ser ...
- luogu1501 [国家集训队]Tree II
lct裸题 #include <iostream> #include <cstdio> using namespace std; typedef long long ll; i ...
- 了解JavaScript核心精髓(一)
ES5 1.声明脚本 <script type="text/javascript"></script> 2.DOM与BOM DOM(Document Obj ...