
从0写操作系统
jiaruitao777
这个作者很懒,什么都没留下…
展开
-
1.自己写操作系统--开发环境的搭建与第1个操作系统(完全可复现)
本文为操作系统的实践的第一部分,包括环境的搭建,和制作第一个操作系统并运行起来。完全可复现。原创 2019-11-21 19:15:36 · 2833 阅读 · 1 评论 -
2.编写主引导记录MBR
这一篇继续编写MBR,但是开发环境有一点的小改动,是在Linux环境下的bochs中进行测试我们写好的MBR好了,我们马上开始。1. 首先先在Ubuntu中安装 bochs2.9.6。sudo apt-get update //先升级一下,以免后续的安...原创 2019-11-28 09:57:08 · 562 阅读 · 0 评论 -
3.计算机上电 到 BIOS 再到 MBR的过程详解
本文详细分析了计算机上电到BIOS 再到MBR的过程,包括寄存器的改变,物理地址的改变,各部分的作用等等。原创 2019-11-28 14:11:45 · 468 阅读 · 0 评论 -
4.实现MBR控制显示器,读硬盘,加载loader的功能
前面博文 编写主引导记录MBR 中的MBR程序没有实际意义,只是单纯的在屏幕显示几个字符,而且不是自己直接操作显卡,是调用了BIOS 的中断。1. MBR 直接控制屏幕输出本文先来实现 MBR 通过往显存中放入数据来控制屏幕的输出。少废话,先上代码。%include "boot.inc" ;包含头文件SECTION MBR vstart=0x7c00 ; 定义一个节,虚拟地址0x7c0...原创 2019-11-30 11:09:26 · 1842 阅读 · 0 评论 -
5.从实模式进入保护模式
本文接上一篇博文,在 loader.S中干点正事儿。进入保护模式。1.什么是保护模式?最经典的 8086CPU 是只有实模式的。实模式下有很多的不足,所以有了保护模式。实模式的缺点:操作系统和用户程序处于同一特权级。用户程序所引用的地址都是指向真实的物理地址。用户程序可以自行修改段基址,可以访问所有内存。访问超过 64KB 的内存区域要切换段基址。一次只能运行一个程序。共 20...原创 2019-12-02 20:18:43 · 872 阅读 · 0 评论 -
6.检测物理内存的容量
操作系统是管理硬件的大管家,所以它要知道有哪些硬件资源啊。比如要检测内存,知道内存的容量。1.学习 Linux 中的获取内存的方法学习 Linux 中的获取内存的方法,调用 BIOS 中断 0x15 来实现内存容量的检测。0x15 中断一共有 3 个子功能:EAX=0xe820 ;遍历主机上全部内存AX=0xe801 ;分别检测低 15MB 和 16MB~4GB 的内存,最大支持 ...原创 2019-12-04 15:14:38 · 998 阅读 · 1 评论 -
7.构建页表,启用分页机制
关于分页的原因,分页机制的原理就不赘述了。0.一些问题问:前面已经把内存分段了,难道直接舍弃,采用分页嘛?答:要兼容,不舍弃。分段用 [段基址:偏移地址]得到一个线性地址,得到的线性地址再经过分页机制的转换,才得到真正的物理地址。才把这个物理地址送到地址线去访问真实的物理内存。分页机制是建立在分段机制之上的。段部件的作用是根据段基址和偏移地址得到一个线性地址。在没有启用分页机制的情况下...原创 2019-12-05 11:08:39 · 461 阅读 · 0 评论 -
8.加载内核并初始化内核
1. 用 C 语言写内核int main(void){ while(1); return 0;}最最简单、没有实用的内核。咱们的目的是加载内核并初始化内核。所以内核不需要太复杂。用 gcc 编译器:gcc -c -o main.o main.c //编译汇编到目标代码,不进行链接。ld 进行链接程序,指定可执行程序的起始虚拟地址。ld main.o -Ttext 0x...原创 2019-12-06 11:12:23 · 380 阅读 · 0 评论 -
9.完善内核-实现自己的打印函数
经过前面 8 个博客,已经把执行内核之前的准备工作完成了,接下来跳转到内核执行。本次的打印函数就是控制显卡来完成的,就像之前 MBR 读硬盘,加载loader、loader.S 读硬盘加载内核到内存一样,控制显卡就是控制显卡中相关的寄存器。对于显卡中寄存器的控制,涉及到端口,还是直接使用汇编来实现。步骤:备份寄存器现场获取光标位置获取待打印字符判断字符是否为控制字符,回车、换行、退...原创 2019-12-08 19:46:38 · 327 阅读 · 0 评论 -
10.完善内核-实现打印字符串
上一节通过控制显存实现了打印单个字符,这一节调用上一节的函数来实现打印字符串。在print.S中添加:global put_str ;全局声明put_str: push ebx push ecx ;备份ebx,ecx xor ecx, ecx mov ebx, [esp + 12] ;从栈中取出字符串的地址.goon: mov cl, [ebx] ;挨个读出字符 c...原创 2019-12-08 20:18:39 · 293 阅读 · 0 评论 -
11.完善内核-打印整数
接前两篇博客.print.S中添加:global put_intput_int: pushad mov ebp, esp mov eax, [ebp+4*9] ; call的返回地址占4字节+pushad的8个4字节 mov edx, eax mov edi, 7 ; 指定在put_int_buf...原创 2019-12-08 20:49:33 · 219 阅读 · 0 评论 -
12.创建IDT、中断处理程序,初始化8259A,中断测试
0. 中断那些事儿中断分类外部中断1.1 可屏蔽中断(INTR)1.2 不可屏蔽中断(NMI)内部中断2.1 软中断2.2 异常一共0~255,256个中断。这个0~255就是中断向量号。处理器就是根据中断向量号来定位中断处理程序的。操作系统是中断驱动的,在实模式下有中断向量表(IVT),中断发生后找到中断处理程序的入口;在实模式下有中断描述符表(IDT),中断发生后根据...原创 2019-12-11 16:05:15 · 1352 阅读 · 0 评论 -
13.设置8253,提高时钟中断的频率
上一篇博文《12.创建IDT、中断处理程序,初始化8259A,中断测试》中,检测的是IRQ0的中断,就是时钟中断,这个中断时8253提供的,默认时18.206Hz,我们这次通过设置 8253 来把时钟中断的频率提高到 100 Hz。CLK是 8253 的时钟输入频率。8253 有3个独立的计数器。往控制字寄存器写入控制字来设置 8253 的工作方式。往计数端口写入计数初值。8253...原创 2019-12-11 21:30:05 · 1552 阅读 · 0 评论 -
14.ASSERT断言的实现 + Makefile
断言 ASSERT 是用宏来实现的。原理是判断传给 ASSERT 的表达式是否正确。参看博客《断言(assert)的用法》https://blog.youkuaiyun.com/jiaruitao777/article/details/103509909assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终...原创 2019-12-12 15:38:50 · 645 阅读 · 3 评论 -
15. C语言实现位图 bitmap
1. 位图是什么?位图是一种资源管理的方式,按位bit与资源一对一的对应关系。位图中的每一位将于内存中的每一页一一对应。一个位有 2 种状态,即 0 和 1 ,用位图中的每一个位来表示实际物理内存的 4KB(一页) 。如果某位为 0 ,表示该位对应的页未分配。反之,某位为 1 ,表示该位对应的页已分配。2. 实现位图实现位图,用到两个文件,bitmap.c 和 bitmap.h。b...原创 2019-12-13 10:01:27 · 2252 阅读 · 0 评论 -
16.内存管理系统-在虚拟、物理内存池中申请内存并修改页表使其映射
利用上一节的位图,来实现内存管理系统。内存管理本质上就是修改页表项。位图的作用是用来申请和释放内存时用的。1. 内存池的规划无论是内核还是用户进程,最终都要运行在物理内存上,所以物理内存分成两个内存池:用户物理内存池和内核物理内存池。在分页机制下程序的地址都是虚拟地址,虚拟地址的范围取决于地址总线的长度,咱们32位环境下,虚拟地址的空间是 4GB。每个任务都有自己的 4GB虚拟地址空间...原创 2019-12-16 12:29:17 · 489 阅读 · 0 评论 -
17.在内核空间实现线程
1. 实现线程有两种方法一种是在用户进程中实现线程,另一种是在内核空间实现线程。各自优缺点:(1)在用户进程实现线程线程的调度算法由用户进程自己实现,不同进程可以不同,更加灵活。不用陷入内核态即可完成线程的切换某个线程出现阻塞,操作系统就认为i是整个进程出现阻塞,会将整个进程挂起。操作系统调度的调度单元是整个进程。提速并没有很大的提高。(2)在内核空间实现线程一个线程出现...原创 2019-12-17 15:49:42 · 616 阅读 · 0 评论 -
18.实现双向链表
本文为实现双向链表,为以后的 就绪队列、全部任务队列准备数据结构。一共包含两个文件,放在 /lib/kernel 目录下。list.h#ifndef __LIB_KERNEL_LIST_H#define __LIB_KERNEL_LIST_H#define NULL ((void*)0)#define bool int#define true 1#define false 0...原创 2019-12-18 14:42:34 · 194 阅读 · 0 评论 -
19.实现线程调度
接前面几篇博文,在 17 在内核空间实现了线程, 18 实现了双向链表的数据结构。那么继续实现线程调度。操作系统是中断驱动的,线程的调度也是和中断息息相关的。首先是 PCB 的继续完善。thread.h 中添加/* 进程或线程的pcb,程序控制块 */struct task_struct { uint32_t* self_kstack; // 各内核线程都用自己的内核栈 e...原创 2019-12-18 16:17:56 · 283 阅读 · 0 评论 -
20.实现同步机制--锁
上一篇 博文中 运行了 3 个线程(主线程、k_thread_a、k_thread_b ),运行一段时间就会出现异常,这是由于产生了竞争条件。有 2 种竞争条件:(1)对显存未实现互斥访问。(2)对“光标寄存器”未实现互斥访问。1. 相关概念公共资源所有任务共享的一套资源。临界区各个任务访问公共资源的临界代码,临界区是指令,不是受访的静态公共资源。互斥某一时刻,公共资源...原创 2019-12-19 16:08:49 · 156 阅读 · 0 评论 -
21.用锁实现终端输出
本文就是利用上一篇博文的 锁 ,来实现终端输出,避免产生竞争条件。新建一个文件 console.c。console.c#include "console.h"#include "../lib/kernel/print.h"#include "../lib/std_int.h"#include "../thread/sync.h"#include "../thread/thread.h...原创 2019-12-19 16:19:07 · 200 阅读 · 0 评论 -
22.简单的键盘中断测试
1. 硬件连接先看一下硬件的连接:键盘里有个 8048 芯片,当按下某个按键时, 8048 就把这个键对应的数值发送给 8042 ,8042就知道是哪个键被按下了,就会给 8259A 发送中断。一个按键被按下,产生的编码叫通码(makecode),断开时叫断码(breakcode)。按键有 3 种状态,“按下”、“按下保持”、“弹起”。有好几种键盘扫描码,需要一个“中间件”来隐藏各套扫...原创 2019-12-19 19:08:30 · 811 阅读 · 0 评论 -
23.键盘驱动程序
在上一篇博文的基础上来修改键盘的驱动。只修改 keyboard.c(1)添加一些宏定义,(2)修改中断处理函数。其它不变。功能:功能有限,只能处理主键盘区的字符,只能和 Ctrl、Caps、Shift 组合。#include "keyboard.h"#include "../lib/kernel/print.h"#include "../kernel/my_interrupt.h"#...原创 2019-12-19 21:03:23 · 458 阅读 · 0 评论 -
24.键盘输入缓冲
上一节实现了键盘驱动程序,每按一个键,都打印出来。本节实现一个环形缓冲区,来作为键盘输入的缓冲。缓冲区要有的功能:同一时刻只能被一个生产者或消费者使用(互斥);–锁来实现缓冲区满时,生产者不能往缓冲区添加数据;缓冲区空时,消费者不能从缓冲区取数据。ioqueue.h#ifndef __DEVICE_IOQUEUE_H#define __DEVICE_IOQUEUE_H#inc...原创 2019-12-20 10:10:10 · 831 阅读 · 0 评论 -
25.实现系统调用syscall
原理:系统调用就是用户程序调用操作系统提供的函数,那么就需要从3特权级变化到0特权级。仿照Linux中的系统调用,用中断的方式来实现特权级的变化,进而实现系统调用。并不是一个系统调用就占用一个中断号,而是所有的系统调用都使用一个中断号,就是0x80 。通过eax的值来确定具体是哪个系统调用。实现的步骤:修改中断描述符表IDT,添加0x80号中断的描述符。建立一个系统调用的子功能表sy...原创 2020-02-12 15:48:22 · 834 阅读 · 0 评论 -
26.实现printf
1.printf是什么?printf是c语言的标准输出函数,原型是int printf(const char *format, …);其中format 是格式化字符串,其中就包含“%类型字符”.“…”是可变参数,参数不固定。printf 由两个函数封装而成,一个是 vsprintf ,另一个是 write。vsprintf 负责把 format 中的%d、%s、%c……给替换成真正...原创 2020-02-12 21:47:07 · 681 阅读 · 0 评论 -
27.实现sys_malloc
在之前内存管理中,只是实现了页的申请,也就是说要申请内存,最小的单位也是1页即4k。这节来实现不同规格的内存分配,当然还是在之前页的申请的基础上来完成。1. arenaarena是提供内存分配的数据结构,包含两个部分,一部分是元信息,另一部分是内存块。arena可以是一个页框,页可以是多个页框。对于小于1024字节的内存申请,arena就是一个页,在这一个页中除去元信息,剩下的空间就平均的...原创 2020-02-13 12:43:06 · 386 阅读 · 0 评论 -
28.实现sys_free
无论在之前的内存管理中(实现一页的分配),还是上次的sys_malloc 中,都没有实现内存的回收机制。本次就来实现内存的回收,包括页内存的回收和回收sys_malloc申请的小内存。1. 实现 mfree_page 回收多个页框回收页框包含三步:(1)回收物理内存池——物理内存池的位图相应位清零(2)回收虚拟内存池——虚拟内存池的位图相应位清零(3)去掉页表中虚拟地址和物理地址的映射...原创 2020-02-13 15:18:08 · 283 阅读 · 0 评论 -
29.实现malloc & free
结合前几节的内容,实现系统调用malloc 和 free。根据系统调用的步骤:(1)封装函数malloc & free(2)在函数中调用宏 _syscall1(),在宏中就执行int 0x80 中断进入内核态。(3)在syscall_table 数组中添加函数sys_malloc 和 函数 sys_free. 这样在中断处理子程序中就会跳转到 sys_malloc 和 sys_...原创 2020-02-13 16:31:36 · 255 阅读 · 0 评论 -
30. 硬盘驱动
一般的主板上有两个硬盘通道,叫做 ata 通道 或 ide 通道。第一个 ide 通道的中断信号挂在 8259A 的 IRQ14 上, 第二个挂在 IRQ15 上。一个通道可以挂两块硬盘,对,两块硬盘共用一个中断接口。每块硬盘又可以分区。分为主分区和逻辑分区。硬盘驱动程序包括,硬盘的初始化,硬盘的读操作、写操作。先定义数据结构/* 分区结构 */struct partition {...原创 2020-02-15 18:28:14 · 668 阅读 · 0 评论 -
31.创建文件系统 file system
在 MBR、EBR、OBR的区别和联系 博文中,介绍了硬盘是如何分区的。再次把硬盘的分区图贴到下面。每一个分区可以有一个文件系统,不同的分区可以有不同的文件系统。仿照Linux中的ext2文件系统来创建文件系统。在一个分区中,先是引导块,然后是超级块,超级块是操作系统元信息的元信息。操作系统的元信息包括:inode 位图,inode 数组,空闲块位图,根目录等。空闲块就是用来创建文...原创 2020-02-18 20:54:23 · 408 阅读 · 0 评论