
一步步编写操作系统
郑大刚子的《一步步编写操作系统》,内容相当不错,想起天天爬图书馆,写汇编的日子,算是重温了上学时的梦想。首发应该是这里转载,如有侵权,请联系删除。再次感谢郑大刚子
sinolover
完成项目,就要软硬兼施
展开
-
【转】PE文件结构详解--(完整版)
(一)基本概念PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任何扩展名。那Windows是怎么区分可执行文件和非可执行文件的呢?我们调用LoadLibrary传递了一个文件名,系统是如何判断这个文件是一个合法的动态库呢?这就涉及到PE文件结构了。PE文件的结构一般来说如下图所示:从起始位置开始依次是DOS头,NT头,节表以及具体的节。DOS头是用来兼容MS-原创 2020-12-19 14:21:35 · 1661 阅读 · 1 评论 -
一步步编写操作系统81 at&t内嵌汇编语法
内联汇编的格式也变得让人生畏了,感觉既不像C语言,也不像汇编语言,似乎是一种中间产物,不信您看:asm [volatile] (“assembly code” : output : input : clobber/modify)和前面的基本内联汇编相比,扩展内联汇编在圆括号中变成了4部分,多了output,input,和clobber/modify三项。其中的每一部分都可以省略,甚至包括a...原创 2019-10-29 18:37:58 · 249 阅读 · 0 评论 -
一步步编写操作系统80 扩展内联汇编1
由于基本内联汇编功能太薄弱了,所以才对它进行了扩展以使其功能强大。不过,易用性往往与功能强弱是成正比的,如您所料,扩展内联汇编确实有点难,但在求知欲的驱使下,就让咱们痛并快乐着吧。gcc本身是个c编译器,要让其支持汇编语言,必然牵扯到以下问题:在内联汇编代码插入点之前的c代码,其编译后也要被分配寄存器等资源,插入的汇编代码也要使用寄存器,这是否会造成资源冲突? 汇编语言如何访问c代码中的...原创 2019-10-29 18:37:10 · 208 阅读 · 0 评论 -
一步步编写操作系统 79 在c代码中内联汇编
基本内联汇编是最简单的内联形式,其格式为:asm [volatile] (“assembly code”)各关键字之间可以用空格或制表符分隔也可以紧凑挨在一起不分隔,各部分意义如下:关键字asm用于声明内联汇编表达式,这是内联汇编固定的部分,不可少。asm和__asm__是一样的,是由gcc定义的宏:#define __asm__ asm。因为gcc有个优化选项-O,可以指定优...原创 2019-10-29 18:36:26 · 152 阅读 · 0 评论 -
一步步编写操作系统 78 intel汇编与AT&T汇编语法区别
本节咱们介绍下intel汇编语法和at&t汇编语法的区别。以上表中未列出这两种语法在内存寻址方面的差异,个人觉得区别还是很大的,下面单独说说。在Intel语法中,立即数就是普通的数字,如果让立即数成为内存地址,需要将它用中括号括起来,“[立即数]”这样才表示以“立即数”为地址的内存。而AT&T认为,内存地址既然是数字,那数字也应该被当作内存地址,所以...转载 2019-07-18 18:33:34 · 369 阅读 · 0 评论 -
一步步编写操作系统 77 内联汇编与AT&T语法简介
内联汇编之前和大家介绍过了一种汇编方法,就是C代码和汇编代码分别编译,最后通过链接的方式结合在一起形成可执行文件。另一种方式就是在C代码中直接嵌入汇编语言,强大的GCC无所不能,咱们本节要学习的就是这一种,它称为内联汇编。其实还有另外一种,就是那些技术大牛才能玩得转的方式,将c代码编译为汇编代码后,再修改汇编代码。什么是内联汇编内联汇编称为inline assembly,GCC...转载 2019-07-18 18:31:46 · 337 阅读 · 0 评论 -
一步步编写操作系统 76 用汇编语言编写字符打印函数
之前咱们介绍显卡上那么多的寄存器终于发挥用处了,我们看看前文中介绍的表CRT Controller Data Registers中索引为0Eh的 Cursor Location High Register寄存器和索引为0Fh的Cursor Location Low Register寄存器,这两个寄存器都是8位长度。分别用来存储光标坐标的低8位和高8位地址。访问CRT controller寄存器...翻译 2019-07-18 18:29:33 · 481 阅读 · 0 评论 -
一步步编写操作系统 75 从显卡读取光标位置1
我们在打印字符时,通常都不用指定字符显示的坐标位置,大家也没觉得有什么奇怪,原因是字符是在当前光标的位置处显示的,而且光标的位置会一直更新顺延,我们的字符一直跟着光标走,似乎光标就是字符的导航一样,而我们已经习惯了跟随光标。我想大伙儿已经清楚了光标和字符的关系了,对,它们的关系就是没有任何关系^_^。“光标在哪字符就在哪”,这是我们人为有意设置的,我们是在光标处打印字符。也就是说,我们也可以不在光...转载 2019-07-18 18:29:02 · 360 阅读 · 0 评论 -
一步步编写操作系统 71 直接操作显卡,编写自己的打印函数71-74
一直以来,我们在往屏幕上输出文本时,要么利用bios中断,要么利用系统调用,这些都是依赖别人的方法。咱们还用过一个稍微有点独立的方法,就是直接写显存,但这貌似又没什么含量。如今我们要写一个打印函数了,似乎,我们马上就要站起来了之前我们讲述了有关显卡的知识,但当时怕影响兄弟们的学习积极性,我们并没有说把有关显卡的寄存器罗列出来。话说,出来混早晚要还的,躲得过初一躲不过十五。如今我们需要通过端口来...转载 2019-07-18 18:25:14 · 526 阅读 · 0 评论 -
一步步编写操作系统 69 汇编语言和c语言共同协作 70
由于有了上一节的铺垫,本节的内容相对较少,这里给大家准备了两个小文件来实例演示汇编语言和c语言相互调用。会两种不同语言的人,只是掌握了同一件事物的两种表达方式。人在学习一种新语言时,潜意识里是建立了语言符号与事物形象的映射关系,比如我们在学习grape这个单词时,我们之所以认为它就是我们所认知的葡萄,是因为我们知道这两个名词都是在描述同一种圆圆的、黑紫色、甜酸的这一水果的形象,如果脑子中不存在...原创 2019-07-18 18:19:44 · 1419 阅读 · 0 评论 -
一步步编写操作系统 67 系统调用的实现1-2 68
接上文:系统调用的子功能要用eax寄存器来指定,所以咱们要看看有哪些系统调用啦,在linux系统中,系统调用是定义在/usr/include/asm/unistd.h文件中,该文件只是个统一的入口,指向了32位和64位两种版本。在asm目录下提供了这两个版本,文件名分别是unistd_32.h 和unistd_64.h,这里给大家摘录了部分32位x86平台下的unistd_32.h文件,见图...转载 2019-07-18 18:16:47 · 592 阅读 · 0 评论 -
一步步编写操作系统 66 浅析c库函数与系统调用1
本来说好的接下来的工作是要去“丰满”我们的内核,可咱们这种一步一回头的学习方式还得继续啊。其实我了解大家急切写内核的心情,但本书《操作系统真象还原》(请大家支持正版)的目的不是写一个操作系统就完事了,而是让大家明白一个至少能运行的操作系统为什么要这样写,所以咱们的学习方式必然是边学习理论知识边实践。如果不给大家交待清楚必要的理论知识,我也对不起自己的良心,我不能为了自己的懒惰而假装大家都明白了。另...转载 2019-07-18 18:14:34 · 228 阅读 · 0 评论 -
一步步编写操作系统 65 标准调用约定stdcall 汇编实战
因为c语言遵循的调用约定是cdecl,咱们也自然要遵守cdecl约定了。不过为了起到对比的作用,除了介绍cdecl外,也会介绍下stdcall。既然咱们用的是调用约定是cdecl,那对它的介绍最好让它离下一节的内容近一些,所以先说一下咱们不用的stdcall吧^_^,其实这两个差别就在于由谁来回收栈空间。stdcall的调用约定意味着:调用者将所有参数从右向左入栈; 被调用者清理参数...原创 2019-07-23 15:52:21 · 340 阅读 · 0 评论 -
一步步编写操作系统 62 函数调用约定
由于我们要将c语言和汇编语言结合编程啦,所以一定会存在汇编代码和c代码相互调用的问题,有些事情还是要提前交待给大家的,本节就是要给大家说下函数调用规约中的那些事儿。函数调用约定是什么?调用约定,calling conventions,从字面上理解,它是调用函数时的一套约定,是被调用代码的接口,它体现在:参数的传递方式,是放在寄存器中?栈中?还是两者混合; 参数的传递顺序,是从左到右传...原创 2019-07-11 11:12:54 · 247 阅读 · 0 评论 -
一步步编写操作系统 61 任务状态段 TSS
I/O位图是位于TSS中的,它可以存在也可以不存在,它只是用来设置对某些特定端口的访问,没有它的话便默认为禁止访问所有端口。正是由于它可有可用,所以TSS的段界限TSS limit(即实际大小-1)并不固定。当TSS中不包括I/O位图时,TSS只有104字节大小。话说回来了,当处理器执行某些IO指令时,若当前特权级比IOPL低,处理器就会认为也许只是给当前任务单独放行了某些端口,于是它就到TSS中...转载 2019-07-10 09:17:52 · 671 阅读 · 0 评论 -
一步步编写操作系统 60 cpu的IO特权级2 什么是驱动程序
用户程序可以在由操作系统加载时通过指定整个eflags设置,操作系统如何设置自己的IOPL呢,即使内核IOPL为0也得写进去eflags寄存器中才生效。可惜的是,没有直接读写eflags寄存器的指令,不过可以通过将栈中数据弹出到eflags寄存器中来实现修改。可以先用pushf指令将eflags整体压入栈,然后在栈中修改相应位,再用popf指令弹出到eflags寄存器中。另外一个可利用栈的指令是i...原创 2019-07-10 09:17:41 · 526 阅读 · 0 评论 -
一步步编写操作系统 59 cpu的IO特权级1
在保护模式下,处理器中的“阶级”不仅体现在数据和代码的访问,还体现在指令中。一方面将指令分级的原因是,有些指令的执行对计算机有着严重的影响,它们只有在0特权级下被执行,因此被称为特权指令(Privilege Instruction)。比如hlt指令,它可以让计算机停机,处理器只信任操作系统,所以它不得不放在0特权级下。同类的指令还有lgdt,lidt,ltr,popf等,这些对计算机的正常运行...转载 2019-07-09 15:37:16 · 533 阅读 · 0 评论 -
一步步编写操作系统 58 门、调用门与RPL序 3
接前文:并不是任何当前特权级都可以使用门结构, 在使用门结构之前,处理器要例行公事做特权级检查,参与检查的不只是CPL和DPL,还有RPL,为了说清楚这个检查过程,咱们得先介绍下RPL。RPL,即请求特权级,为了解释清楚,咱们得多花点工夫好好说道说道。我们本节始终在说特权级转移,处理器从一个特权级转移到另一个特权级,任意时刻处理器所处的特权级称为当前特权级。重复叙述的目的是强调当前特权...转载 2019-07-09 15:36:25 · 331 阅读 · 0 评论 -
一步步编写操作系统 57 门、调用门与RPL序 2
接上文:提供了4种门的原因是,它们都有各自的应用环境,但它们都是用来实现从低特权级的代码段转向高特权级的代码段,咱们这里也只讨论有关特权级的功用:1.调用门call和jmp指令后接调用门选择子为参数,以调用函数例程的形式实现从低特权向高特权转移,可用来实现系统调用。call指令使用调用门可以实现向高特权代码转移,jmp指令使用调用门只能实现向平级代码转移。2.中断门以int指令主动...转载 2019-07-09 15:35:47 · 428 阅读 · 0 评论 -
一步步编写操作系统 56 门、调用门与RPL序 1
小弟多次想把调用门和RPL分开单独说,但几次尝试都没有成功,我发现它们之间是紧偶合、密不可分,RPL的产生主要是为解决系统调用时的“越权”问题,系统调用的实现方式中,以调用门和中断门最为适合。由于以后我们将用中断门实现自己的系统调用,故在此本着扩充知识面的目的给大伙儿介绍调用门,通过调用门的实例,让大伙儿理解特权级那点事儿。处理器只有通过“门结构”才能由低特权级转移到高特权级,处理器就是这样设...原创 2019-07-09 15:32:37 · 391 阅读 · 0 评论 -
一步步编写操作系统 55 CPL和DPL入门2
接上节。图中第132行的jmp指令,段选择子为SELECTOR_CODE,其RPL的值为RPL0,RPL0定义在include/boot.inc中,其值为0。选择子的索引部分值为1,表示对应GDT中第1个段描述符,该描述符的DPL为0,(它是用include/boot.inc中的DESC_DPL_0定义的,图中未展示)。在跳转之前,CS为0,其低2位RPL部分为0,也就是CPL为0,当...原创 2019-07-09 09:21:53 · 795 阅读 · 0 评论 -
一步步编写操作系统 54 CPL和DPL入门1
我们在工作中,公司都给员工配有员工卡,此员工卡就是员工身份的“标签”,用它来出入公司、食堂就餐等。给公司创造价值的是员工的生产力,不是员工卡,员工卡只是公司人事部门管理员工的一种手段而已。现在说计算机,既然是用特权级来维护计算机世界的和平,那总该给每个被管理的对象加个特权“标签”,也就是说cpu得知道谁的特权高谁的特权低,这样才能辨识出是否有低特权级的程序越级访问高特权级资源的违法行为。员...原创 2019-07-05 09:29:20 · 771 阅读 · 0 评论 -
一步步编写操作系统 53 任务状态段TSS介绍
操作系统是利用PCB来维护所有任务的,包括进程和线程,但cpu提供的是TSS,linux系统可没用它,因为效率太低。但是还是要了解下TSS才清楚操作系统中某些操作的原因。本节中所讲的特权级与它有着密不可分的联系,TSS作用不止涉及特权级,还包括任务寄存器环境,任务管理相关的内容,为了不干扰大家,这里只介绍和特权级相关的内容,待将来咱们用到更多内容时再和大伙儿细说。TSS,即Task Sta...转载 2019-07-04 18:43:38 · 2254 阅读 · 4 评论 -
一步步编写操作系统 52 深入浅出cpu的特权级
所谓保护模式下的“保护”,主要体现在特权级上,以后随着后面工作的展开,会越来越多的和它们打交道,现在是时候说道说道了。在人类社会中出现恶势力时,人们总是希望出现一位具有神力的英雄来拯救世人、主持公道。阶级是骨子里的东西,它一直存在,然而阶级和平等并不矛盾,阶级是为了平等而生,这是自然界为维护秩序自然产生的事物,如果打破了这个平衡,平等也将不复存在。正因为阶级和平等之间的相互制约,才有了今天的民...转载 2019-07-04 18:42:49 · 350 阅读 · 0 评论 -
一步步编写操作系统 51 加载内核4
咱们的内容都是连栽的,如果您没看过我之前的文章,本节您是看不懂的。接上节。介绍完内核初始化的函数kernel_init后,本节代码部分还差一点点没说啦,下面看代码:…略179 ;在开启分页后,用gdt新的地址重新加载180 lgdt [gdt_ptr] ; 重新加载181182 ;;;;;;;;;;;;;;;;;;;;;;;;;;;; 此时不刷新流水线也没问题 ;;;;;;;...转载 2019-07-05 09:29:14 · 406 阅读 · 0 评论 -
一步步编写操作系统 50 加载内核3
接上节,在这里,我们把参数放到了栈中保存,大家注意到了,参数入栈的顺序是先从最右边的开始,最后压入的参数最左边的,其实这是某种约定,要不,为什么不先把中间的参数src入栈呢。既然主调函数按照从右到左的顺序在栈中压入参数,被调函数中必须分清楚这三个参数分别在栈中哪个位置。栈是向下扩展的,这一点通过push指令压栈时,栈指针esp的值越来越小能体现出来,所以最后压入的第1个参数是离栈顶(esp指向的地...转载 2019-07-04 18:40:39 · 396 阅读 · 0 评论 -
一步步编写操作系统 49 加载内核2
内核文件kernel.bin是elf格式的二进制可执行文件,初始化内核就是根据elf规范将内核文件中的段(segment)展开到(复制到)内存中的相应位置。在分页模式下,程序是靠虚拟地址来运行的,无论是内核还是用户程序,它们对cpu来说都是指令或数据、没什么区别,交给cpu的指令或数据的地址一律被认为是虚拟地址。坦白说,内核文件中的地址是在编译阶段确定的,里面都是虚拟地址,程序也是靠这些虚拟地址来...原创 2019-11-04 18:04:29 · 452 阅读 · 0 评论 -
一步步编写操作系统 48 加载内核1
其实,我们等了这一刻好久好久,即使我不说,大家也有这样的认识,linux内核是用c 语言写的,咱们肯定也要用c语言。其实...说点伤感情的话,今后的工作只是大部分(99%)都要用c语言来写,还有一些要用到汇编的地方。大家也不要因此气馁心灰(其实突然不用汇编还会想它呢,这不是玩笑),我在此过程中一定会尽我所能让内容简单易接受。我们的内核文件是kernel.bin,这个文件是由loader将其从硬...原创 2019-07-05 09:29:07 · 545 阅读 · 0 评论 -
一步步编写操作系统 47 elf格式文件分析实验
在上一节中,我们讲述了elf格式的部分理论知识,为什么是部分呢?因为我们本着“够用”的原则,只把我们需要了解的部分说完啦。不过,我相信大部分同学仅仅凭上一节中的理论知识还是领悟不到elf本质,咱们在本节开始分析前面咱们写过的“内核”,让大家看清elf文件的每一个字节。为了让大家看清楚elf文件内部,咱们要用之前的xxd命令,为了方便使用,如很久很久以前所述,已经将其封装成了xxd.sh脚本,参...转载 2019-07-04 18:38:14 · 426 阅读 · 0 评论 -
一步步编写操作系统 46 linux的elf可执行文件格式1
ELF文件格式依然是分为文件头和文件体两部分,只是该文件头相对稍显复杂,类似层次化结构,先用个ELF header从“全局上”给出程序文件的组织结构,概要出程序中其它头表的位置大小等信息,如程序头表的大小及位置、节头表的大小及位置。然后,各个段和节的位置、大小等信息再分别从“具体的”程序头表和节头表中予以说明。ELF格式的作用体现在两方面,一是链接阶段,另一方面是运行阶段,故它们在文件中组织布...转载 2019-07-04 18:36:20 · 416 阅读 · 0 评论 -
一步步编写操作系统 45 linux的elf可执行文件中的段和节
接上文,为了描述清楚文件格式的本质,咱们先从最基本的“段”说起。程序中最重要的部分就是段(segment)和节(section),它们是真正的程序体,是真真切切的程序资源,所以下面的说明咱们以它们为例。程序中有很多段,如代码段和数据段等,同样也有很多节,段是由节来组成的,多个节经过链接之后就被合并成一个段了,之前咱们有通过实例解释过segment和section之间的关系。段和节的信息也是...转载 2019-07-04 18:37:07 · 1129 阅读 · 0 评论 -
一步步编写操作系统 48 二进制程序的加载方式
接上节,程序头可以自定义,只要我们按照自己定义的格式去解析就行。也许我光这么一说,很多同学还是不能彻底明白如何自定义文件头,因为大多数同学都是用高级语言来写程序,即使用了偏底层的c语言,不同平台的c编译器也会根据系统平台自动添加文件头,不给咱们亲手体验自定义程序头的机会。汇编语言非常 灵活,所以用它来构建任意文件格式则是非常方便的。书看到这里,我估计您已经发现我是个非常体贴的人,哈哈,所以给大家呈...转载 2019-07-04 18:34:58 · 524 阅读 · 0 评论 -
一步步编写操作系统 47 48 二进制程序运行方式
操作系统并不是在功能上给予用户的支持,这种支持是体现在机制上。也就是说,单纯的操作系统,用户拿它什么都做不了,用户需要的是某种功能。而操作系统仅仅是个提供支持的平台。虽然我们是模仿linux来写一个黑屏白字的系统,但如果没有windows的话,估计当今这个世界将会失去70%以上的光芒。由于有了操作系统的支持,我们可以安装一些软件,也就是应用程序,比如安装了QQ或一些其它的即时通讯工具,这样我们...转载 2019-07-04 18:34:52 · 788 阅读 · 0 评论 -
一步步编写操作系统 46 用c语言编写内核3
再把上节代码贴出来,1 //int main(void) {2 int _start(void) {3 while(1);4 return 0;5 }有没有同学想过,这里写一个_start函数,让其调用main函数如何?其实这是可以的,main函数并不是第一个函数,它实际上也是被别人调用的,不过这是编译器背后的策略啦,好奇心大的同学自己尝试下吧。虽然把函数名改成_start...转载 2019-07-04 18:31:14 · 571 阅读 · 0 评论 -
一步步编写操作系统 45 用c语言编写内核2
在linux下用于链接的程序是ld,链接有一个好处,可以指定最终生成的可执行文件的起始虚拟地址。它是用-Ttext参数来指定的,所以咱们可以执行以下命令完成链接:ld kernel/main.o -Ttext 0xc0001500 -e main -o kernel/kernel.bin从左到右说一下参数,-Ttext 指定起始虚拟地址为0xc0001500,这个地址是设计好的,为什么用这...转载 2019-07-03 10:05:03 · 663 阅读 · 0 评论 -
一步步编写操作系统 44 用c语言编写内核1
先来个简单的,欢迎我们神秘嘉宾——main.c。这是我们第一个c语言代码。1 int main(void) {2 while(1);3 return 0;4 }它没法再简单啦,简单的程序似乎能帮助咱们更容易的理解所学的知识,哈哈,我说的是似乎,其实,再长的代码,编译后生成的文件结构也是由那几个部分组成,万变不离其宗。这里所说的文件结构是指将来要说的elf文件格式,在此不多说,...转载 2019-07-03 10:04:47 · 2191 阅读 · 0 评论 -
一步步编写操作系统 43 汇编语言和c语言的理解
也许有的同学喜欢用汇编语言来实现操作系统,觉得用汇编来写程序似乎更简单直接,可控性比较强,有种“一切尽在掌握”的赶脚。而用c语言实现操作系统这件事,虽然轻松很多,但似乎隐约感觉到有些慌张。因为虽然c语言相对来说更接近于人的逻辑思维,但恰恰是这种优越性,给一些好学的同学带来了困扰,毕竟咱们是在写底层的软件,必须要随心所欲地控制cpu,要时时刻刻知道cpu在干什么。而感觉上,c语言不能直接控制cpu,...转载 2019-07-03 10:04:38 · 1135 阅读 · 0 评论 -
一步步编写操作系统 42 用c语言编写内核
在这之前,我们一直用汇编语言直接与机器对话,如果大家不知道这个世界上有高级语言的话,我想大家也不会觉得写汇编代码的过程很辛苦,哈哈,幸福确实是比较出来的。相对于汇编语言,用c 语言写内核是非常爽的事,马上我们就要步入内核实践中啦,所以现在和大伙儿聊聊c语言写内核的体会。通常,我们写的代码都是直接编译成可执行文件,那是因为我们是在写用户程序,操作系统为咱们提供了很多便利,所以编译和链接一气呵成,...转载 2019-07-03 10:04:30 · 3835 阅读 · 0 评论 -
一步步编写操作系统 41 快表tlb 简介
分页机制虽然很灵活,但您也看到了,为了实现虚拟地址到物理地址的映射,过程还是有些麻烦的。先要从CR3寄存器中获取页目录表物理地址,然后用虚拟地址的高10位乘以4的积做为在页目录表中的偏移量去寻址目录项pde,从pde中读出页表物理地址,然后再用虚拟地址的中间10位乘以4的积做为在该页表中的偏移量去寻址页表项pte,从该pte中读出页框物理地址,用虚拟地址的低12位做为该物理页框的偏移量,呼…终于完...转载 2019-07-03 10:04:18 · 1923 阅读 · 0 评论 -
一步步编写操作系统 40 内存分页下用户程序与操作系统的关系
分页的第一步要准备好一个页表,我们的页表是什么样子呢?现在我们要设计一个页表啦。设计页表其实就是设计内存布局,不过在规划内存布局之前,我们需要了解用户进程与操作系统之间的关系。前面讲保护模式时,我们知道,为了计算机安全,用户进程必须运行在低特权级,当用户进程需要访问硬件相关的资源时,需要向操作系统申请,由操作系统去做,之后将结果返回给用户进程。进程可以有无限多个,而操作系统只有一个,所以,...转载 2019-07-03 10:04:13 · 432 阅读 · 0 评论