首页 > 其他分享 >操作系统内核漫游

操作系统内核漫游

时间:2023-10-02 14:33:22浏览次数:38  
标签:保护模式 操作系统 中断 程序 描述符 内核 寄存器 漫游 OS

操作系统内核漫游(前传)

此文记录操作系统自向上如何运行。因为学校的教学中大多数直接开始教操作系统中的很多算法(至少我是如此),导致我学完之后依然不知道他在整个计算机中的身份地位,也并不清楚他如何与计组以及汇编、C语言之间的关联。此笔记算是学习学校所谓的操作系统的感性的前置(前传)知识吧。感性是因为有很多硬件细节只是从上层抽象的角度来说明他有这样一个东西,他的作用。我希望站在我当初啥也不懂的角度来逐步解开我的上述疑惑。当然,人在学习的过程中总是或多或少会忘记当初的心境和疑惑,以至于有的东西自己现在看来理所应当,但是讲给小白的时候却并不是那么简单,于是会再次深入思考他的机理。这大概就是费曼学习法的思想之一吧。

前置知识:CPU结构,例如寄存器地址线,硬盘结构,一点汇编知识。

参考书 : 《x86汇编语言 从实模式到保护模式》(主要)、《程序员的自我修养----链接、装载与库》、《自己动手写操作系统》

没有操作系统的时候如何coding

cpu根据寄存器中的值取指令然后不断执行。所以最简单的想要计算机执行程序,只需要把代码放到内存中一个合适的位置,然后合理设置好cpu指令地址即可。

那么第一个问题在于,谁去把最初的代码放到合适位置,谁来设置最初的指令寄存器呢?答案是BIOS

第二个问题,写汇编程序的时候,指令跳转之类的很多操作都要指明地址,但是我写的时候并不知道我最后程序真正运行的位置该怎么办,答案是重定位。

问题三,要怎么打出HelloWorld?,换句话说,怎么显示东西?,答案:把显示数据给显卡。

问题四,**如何调用自己写的函数? **,这个问题更偏向汇编知识,在汇编,被称为过程调用。核心是保存当前执行函数的信息,然后修改寄存器为目标函数的相关信息。

问题五,如何在程序执行的同时还能接受键盘(外设)的输入?,硬件机制----中断。

BIOS

Basic Input Output System. 基本输入输出系统。他是一段代码,被直接写死在内存ROM的一个固定区域。这段代码的作用之一就是加载硬盘的主引导扇区(0头0道1扇区)存储的512字节到内存的0x7c00处。并且修改指令寄存器到该位置。

主引导扇区末尾两个字节0x55AA用于标记该扇区是有效的。

为什么是0x55AA?很多地方说只是个标记作用,没什么特殊,但 这篇文章似乎可以从硬件角度说明原因。关于0xAA和0x55

为什么是0x7c00?最初使用BIOS的系统内存只有32KB,地址范围(0x0000-0x7FFF),为了给主引导扇区的代码(512)和数据(512)加载进来留足够的位置,那么开头就是0x7FFF - 512 - 512 + 1 = 0x7c00

cpu加电或者复位时,指令寄存器在硬件层面上被设置到BIOS的地址。

我感觉有很多东西追溯到源头,软件做不到的,就是软硬件做好约定。比如这里最初开始的时候没有程序运行,的指令寄存器谁来设置,就是硬件设置。

重定位

上面说一开始的程序会被加载到0x7c00,我们知道了这个地址,那么写程序的时候就可以用这地址来计算最后地址,即0x7c00加上一些程序中的相对地址即可。但是这样太麻烦。而且到后续的程序加载在哪个位置是我们不能预先知道,就更不能这样写了。

于是硬件又升级了,8086cpu访问内存地址使用段寄存器值 << 4 + IP寄存器值。这就是重定位。我们程序中的地址一般都是IP寄存器的值。偏移工作由硬件完成了。

为什么要左移4位?因为16位只能表示最大64KB内存,那个时候内存可以达到1MB,所以左移能表示更大范围。

这样的话,写程序的时候,只需要关注地址相对于当前段的偏移即可。段寄存器有代码段、数据段、栈段,在程序加载的时候会设置好,如果需要在段之间进行跳转,则需要修改段寄存器,如果只是段内跳转,就只需要修改IP的值。

那如果是段之间呢?如果有多个段,这个段的地址不在寄存器里面我怎么知道跳到哪里

答案是:由用户程序头部提供段的位置信息。

用户程序头部

我们一开始的程序是由BIOS加载的,后面的程序得靠我们自己加从硬盘读取,读取多少字节?有哪些段?这些信息存在用户程序头部。

加载用户程序的程序先读取头部,确定大小后,再接着读取后面的,并且根据不同段实际存放在内存中的位置修改头部中段的地址。

我们如何读取硬盘?使用in out等指令向IO接口的寄存器,也称之为“端口”,发送信息,读取或者写入数据。不同系统的端口的实现可能不同,有两种:1.映射到内存地址,访问这部分内存就是访问端口。2.cpu有个M/IO#引脚,用于控制cpu的指令用于访问地址还是内存。

如何显示--->显卡

显卡控制显示器上的像素显示什么颜色。显卡有自己的显存,要显示的内容(即每个像素点什么颜色)会先写入显存,然后交给显示器。那么我们只要能往显存写入数据即可。8086中显存会映射到一部分内存中,我们只需要往这部分内存写入显示数据即可。比如每个字节写入什么字符,什么颜色。

mov xxx 'L' ;字符数据,也可以直接写ASCII码,xxx是内存地址
mov xxx 0x00;颜色数据

如何过程(函数)调用

cpu的执行相当于状态机,状态则是指各个寄存器的值。只要我们保存了某个时刻所有寄存器的值,就能完美还原这个时刻的执行状态。函数执行也是,调用函数的时候把当前的寄存器值压栈,那个函数返回的时候再出栈,把寄存器值复原即可。

栈,其实也只是内存中的我们目前可以随便调用一块连续的区域。只不过给他取了个名字。

中断

中断是个很重要很常用的机制,后面的任务切换也会用到它,中断也有很多中,此处只是感性简单介绍

中断是硬件机制。简单说就是:外部硬件(硬件中断)或者汇编代码中(软件中断)发送信号给cpu,然后cpu就自动保存当前执行的现场,根据信号,执行对应的已经写好的中断处理程序,结束之后又返回。这个过程看起来和函数调用差不多,只不过保存现场(压栈)之类的动作是硬件自动完成(当然还有别的一些操作)。(毕竟我们程序自己执行的时候是没法检测到外部中断什么时候来的(可以轮询但效率低),也就没法进行这些保存现场的操作)

硬件中断外设发送中断信号,分为可屏蔽和不可屏蔽。简单说就是有的中断cpu可以忽略,不进行中断处理。

如何忽略可屏蔽中断?8086汇编中cli指令可以将中断标志位置0,就能忽略了。sti则反之。这个操作不可屏蔽中断无效。

如何找到对应的中断处理程序在哪?系统会维护一个中断向量表,里面存放了中断号对应的处理程序的位置。

软件中断就是汇编中可以手动调用的中断。这就很类似函数调用了。但仍有一点区别。

区别:软中断调用时将返回地址和CPU状态寄存器内容压栈,修改特权级,根据中断号查找中断向量表,找到ISR中断服务例程地址,跳转执行。

综上,函数调用和软中断调用的区别是,软中断多了修改特权级(后面才讲)查找中断向量表的功能,其他部分完全一样。

中断处理程序我们可以自定义。也有一些中断处理程序是BIOS提供或者硬件自带的,BIOS负责建立好这个中断向量表(有了操作系统之后可能就是操作系统建立了)。例如int 10h 调用10号中断,在屏幕显示数据,当然,我们也可以自己动手往显卡中写数据达到相同的效果。

那么软件中断和函数调用有什么区别呢?

软件中断存在的意义:大概就是有了操作系统之后,应用软件想要调用系统的功能,由于特权级不够,所以不能用函数调用的方式实现,需要用软中断的方式调用。参考文章

没有操作系统的coding有啥问题

到此为止,无OS的coding要用到的知识基本结束。那他存在什么问题呢?可以和我们现在编程体验做对比

  • 程序对内存有完全的读写权力,这样程序写的稍微不对,可能修改到别的程序的内存数据等等,总之就是想干啥干啥。不安全
  • 程序只能同步执行,一个执行完了轮得到下一个
  • 访问外存储器很麻烦,程序员需要清楚知道数据在磁盘上存储的位置
  • 程序大小不能超过内存
  • ......

这些不方便的地方就是操作系统要干的事情。比如进程管理,文件系统,内存管理,虚拟内存,软硬件资源管理和分配,IO接口,用户权限管理等。

操作系统仍然是一个普通的程序,他的加载和上面提到的普通程序没有太多区别,但是他实现了上述的功能。OS的代码在主引导扇区放不下,所以通常需要一个引导程序(就是存在那512字节中的)将其加载到内存中。一般称为BootLoader。

我们可以在原本的16位硬件实现操作系统的相关功能,比如微软的DOS(Disk Operation System)。但更多讨论操作系统如何实现是在32位处理器中,因为他内存更大,而且对于一些操作系统要实现的功能有着16位没有的硬件支持,比如段寄存器中的段选择子,段描述符缓存等。

在讨论OS之前,先看看32位处理器有什么新特性。

硬件机制----保护模式

有很长一段时间我对此理解不到位,以为操作系统就是保护模式。但是有些书上写的从实模式到保护模式的跳跃只需要设置好一些东西之后,开启xxx开关就行了,搞得我很是迷惑。

历史上最早提出保护模式的处理器是80286(16位寄存器,24位地址线),保护模式提供的一些硬件支持:

  • 特权级,不同的程序有不同的特权级,不符合条件特权级规则的访问会导致处理器发生中断处理
  • 新增很多寄存器,用于存放段选择子等等
  • 分页支持,比如处理器可以将地址拆分成页目录号和页内偏移(这里算是计组知识)
  • 内存访问方式有所不同,不再是自己直接告诉寄存器地址,而是告诉他段选择子等等
  • 不能
  • ......

与之相对的,之前介绍的没有这些机制的就叫实模式(注意,16位不等于实模式)。

在保护模式下coding要遵循这些新的机制。先介绍一下上面的这些新概念。

什么是段描述符

常见的描述符是段描述符,存储如下内容。由此可见,上面提到的很多机制其实都和描述符有关。

  • 基地址:指定了段的起始物理地址。
  • 界限:指定了段的大小,以字节或页面为单位。
  • 访问权限:包括对该段的读、写、执行权限以及特权级等。
  • 控制标志:包含一些额外的控制信息,如段的类型、是否可用于系统、是否可写保护等。

那段描述符谁来创建呢,放在哪呢?

首先,不是用户自己。哪就只能是OS了,OS从磁盘读取程序,然后根据描述符的规则写入相关信息。那么问题来了,操作系统程序的描述符谁来创建?其实在进入保护模式之前,硬件默认在实模式中执行,只需要在这个时候,有程序将操作系统的相关描述符写好即可。

当然,此处直接快进到OS可能有点突然,其实抛去OS不想,我们依然是可以向之前那样编程,只不过我们要手动设置自己的描述符,然后开始执行程序,从这个角度来讲,我们也并不需要操作系统就能运行程序。但是此时如果还想执行别的程序,就得把那个程序加载进来,然后创建描述符,谁来创建?只有我们自己目前已经在保护模式运行的这个程序来干了,从某种角度上来讲,我们这个程序也起到了OS的一小部分作用。

具体存放位置,有两种位置,一种是全局描述符表(GDT),一种是局部描述符表(LDT)。前者只有一个,一般属于操作系统;后者有很多个,属于程序自己。而且还有两个寄存器记录他们的存放位置。GDTR和LDTR。后者记录当前执行的程序的表地址。cpu建议每个用户程序拥有自己的LDT。

保护模式下的内存访问方式就和段描述符的关系

保护模式下,(以32位cpu举例),段寄存器分为两部分,低16位是是段选择子,高16位是描述符缓冲.

段选择子存储的是段描述符在描述符表中的索引号(不是字节偏移量)。

缓冲是通过选择子在表中查到信息后存在里面让cpu用的,程序用不了。信息是段的线性基地址,界限和属性。

所以,保护模式下访问内存,需要先将索引赋值给段选择器(代码段除外,保护模式下不可读写),cpu去找到信息,缓存之后,就可以使用和实模式差不多的方式了,也就是段的基地址加偏移。

并且,cpu会计算访问的地址有没有超过当前段边界,超了则会中断

特权级的硬件支持

段描述符中2位DPL位是特权级字段,0最高,3最低。由操作系统决定。程序调用其他段必须遵循相关规则,比如低级不能访问高级的相关程序,当然实际规则要更复杂一些。比如用户程序不能访问操作系统的代码。

另外,IO权限存在eflags寄存器中,用户程序想要IO只能通过调用OS的功能。而OS提供给用户程序的功能就是所谓的系统调用。其实就相当于OS封装的一些函数,比如访问系统资源的,分配线程的。

**那怎么才能让用户用到我OS提供的功能呢? **

两种方式

  1. 将相关的OS代码设置为依从代码,允许特权级更低的代码调用。“依从”的名字是因为,调用该代码的时候,段寄存器的特权级字段不会发生改变,依然是用户代码的特权级。之所以这么说,是因为下一个方法调用过程中,特权级会变

  2. 此处引入“门”的概念。

    先来感性认识:就像真门,通过它可以到另一个地方。OS中也是这样,门包括调用门、任务门、中断门、陷阱门。

    门其实还是一种描述符(描述符内有字段可以标注这是什么描述符),段描述符存的是内存信息,门描述符描述的是一个程序、过程(函数)、任务。举例:此处调用OS的功能就用的调用门,该描述符中存的是要调用的代码段的选择子,以及偏移。

    也就只是个概念而已。就把他当作描述符理解更好。

    通过使用call加上门描述符在GDT中的索引,就能跳到对应的OS代码中执行,并且代码段的特权级也会被修改为OS代码的特权级。这大抵就是学习讲的所谓的用户态陷入内核态吧。

任务隔离和切换的硬件支持

保护模式另一个安全指出就是防止不同程序随意读写其他程序的内容。正常情况下,一个程序能访问的部分只有自己段描述符中提到的部分,访问别处cpu会中断。

每个程序除了有LDTR外,还有一个寄存器TR,指向当前任务的TSS(任务状态段),里面存放了当前任务的所有寄存器信息。这个段的意义是任务切换后,下次再执行这个程序能很好恢复现场。TSS中有个部分存储了上一个任务的TSS位置,用于当前任务完成后切换回去。

一般来说,抢占式任务切换可以用时钟中断来进行,每当时钟中断来的时候就调用OS自定义的任务切换代码。

分页、虚存的硬件支持

其实这属于计组的部分了,并且在OS概念课上也会讲。此处补充一点,硬件上会有页表寄存器支持,那么我们就可以随便(理论上讲,实际上还是要按规则)将页表定义在某个位置。就像前文提到的GDT表,思想类似的。

符号表是什么?

用户想要调用内核的例程(函数/系统调用),但是写的时候并不知道地址,所以可以维护一张表,里面写上所有的内核例程符号名(函数名),之后由内核加载的时候将其替换为具体的地址。

其他类型的描述符

上面也提到了,门描述符中的调用门、任务门、中断门、陷阱门描述符。这种描述符大多是用来进行程序执行流跳转的。有被动跳转,比如有的中断门,也有主动跳转,比如调用门。

中断门也是替代了实模式的中断向量表。但作用差不多,都包含中断处理程序的位置等信息。

如何进入保护模式

有了上面的介绍,其实大概知道保护模式只是一些硬件机制。开启也很简单,关键是从实模式过渡过去有一些东西需要处理干净,比较两种模式有很多东西并不兼容,比如内存访问之类的。

先说如何:只要把控制寄存器的CR0位置1,然后跳转到32位代码段即可(跳转指令可以清空流水线并串行化cpu(计组知识))。

但在此之前,需要做一点前置准备。

  • 描述符和GDT还有设置GDTR

  • 比如打开A20地址线,这是第21根地址线,默认关闭,因为一开始的16位寄存器只有20根,但是16位的内存访问方式能访问21根地址线的大小,事实上并没有21,所以高位是被丢弃的。后来地址线变多了,在实模式下不关闭第21根地址线,那可能会进位到21位,从而使原本那些16位程序出现错误。因为原来21位不存在,可以看成恒为0.

  • 比如关闭中断

来自GPT:why

在实模式下,中断服务程序的执行是简单而直接的,当发生中断时,处理器立即跳转到中断向量表指定的入口地址执行中断服务程序。在这种情况下,中断可能会在任何时刻发生,甚至在进入保护模式的过程中。如果在进入保护模式之前不暂时关闭中断,可能会导致以下问题:

  1. 中断处理程序的地址不正确:由于进入保护模式后中断服务程序的入口地址由中断描述符表指定,所以在进入保护模式之前,中断向量表中的地址已经无效。如果发生中断时仍然允许中断响应,处理器会按照实模式下的方式执行中断服务程序,导致出现不可预料的错误。
  2. 保存和恢复中断现场的问题:在保护模式下,中断服务程序需要手动保存和恢复中断现场,包括通用寄存器、段寄存器和堆栈指针等。如果在进入保护模式之前不关闭中断,进入保护模式后仍然允许中断响应,可能会导致中断现场被破坏或混乱。

因此,在进入保护模式之前,一般会先暂时关闭中断(通过清零中断屏蔽位)以确保中断服务程序的正确性和可靠性。这样可以避免中断发生时进入保护模式之前的问题,并为进入保护模式后正确配置中断描述符表和相关的中断处理机制提供一个稳定的环境。一旦保护模式成功设置并初始化完毕,就可以打开中断,继续正常的中断响应和处理过程。

assembly和C的边界在哪?

我之前一直不知道写到什么程度才能用C代替汇编写OS

其实可以思考一下,为什么不用C,其实以我刚开始学的水平,根本不知道,因为我从来没用过C写过系统级程序,也没接触过内联汇编。

其实把内核加载程序写完就可以用C写了,但C和汇编并不是从此非此即彼,会有配合,只是很多操作用C写会简单很多。举例,这个网址中,这个人加载程序使用的是linux的GRUB,剩下的大部分代码就直接用C写的。

原理是,链接和重定位。举例来讲,c要调用汇编的某个函数func,只需要标记extern void func(),最后和汇编链接的时候,链接器会把地址填进去。汇编调用C代码也是如此。另外,C也可以内联汇编直接使用一些访问磁盘之类的功能。

既然可以内联汇编,那为什么不能用c写内核加载程序呢

内核加载程序是在16位实模式下运行的,C代码编译后的机器码无法运行在该模式下。

有OS和无OS的边界在哪?

其实到这里基本上对OS有个基本的感受了,OS并不是一种虚无缥缈高深莫测的东西,他也只是个程序。

加载程序加载的就一定是OS内核吗,也不见得,也可以是我们自己写的普通程序。

当某个程序拥有最高权限,并且提供给其他程序一些内存分配,资源访问,任务切换之类的功能的时候,OS,也就出现了。

如何和学校讲的的OS概念衔接(如何编写)

讲到这里只是讲了些许预备知识,真正如何实现操作系统在软件层面要干的事情还没怎么开始。比如进程调度、进程同步、内存管理、外存管理、文件系统。接下来参考这两个x86汇编语言操作系统实现,是同一个人的视频,前者讲了实现操作系统的一些前置知识,后者则是实现部分,除了我上面说的几个主要部分,还实现了套接字这种网络功能。

标签:保护模式,操作系统,中断,程序,描述符,内核,寄存器,漫游,OS
From: https://www.cnblogs.com/BayMax0-0/p/17739917.html

相关文章

  • 如何获取 C#程序 内核态线程栈
    一:背景1.讲故事在这么多的案例分析中,往往会发现一些案例是卡死在线程的内核态栈上,但拿过来的dump都是用户态模式下,所以无法看到内核态栈,这就比较麻烦,需要让朋友通过其他方式生成一个蓝屏的dump,这里我们简单汇总下。二:如何生成内核态dump1.案例代码为了方便演示,来一段简单的......
  • 操作系统(2)---操作系统的发展历史、启动
    一、操作系统的发展历史 真空晶体管计算机(由操作员负责)缺点:耗时太长,效率太低。每一个新的程序,必须等待前面的程序完成输入、计算、输出整个过程。单道批处理系统所以作业统一输入到一个磁带中,一个接一个运行,将所有输出到另一个磁带中。整个过程不中断,......
  • 操作系统(3)---操作系统的接口、内核
    一、操作系统的接口  用户使用操作系统的方式:命令行 shell是提供用户使用界面的程序(命令解释器),包括图形界面shell和命令行shell。其作用有二:传递请求指令,让操作系统执行命令保护内核图形按钮图形界面是一个包括画图的c程序。每一个硬件动作相当于一......
  • Linux操作系统学习2
    本周学习了Linux操作系统centos的用户管理,了解了用户和组的概念,同时也掌握了创建,修改与删除用户和组的方法。首先就是用户标识,也就是uid与gid,uid就是用户的id,gid就是用户组所属的id。学习了如下命令:查看当前用户登录信息的命令,id。查看文件所有者的命令:ll。查看进程的命令:psau......
  • mousocoreworker.exe 是 Windows 操作系统中的一个可执行文件。它是 Microsoft OneDri
    mousocoreworker.exe是Windows操作系统中的一个可执行文件。它是MicrosoftOneDrive的一部分,用于处理OneDrive文件同步和管理操作。Mousocoreworker.exe的主要功能如下:文件同步:它负责将本地计算机上的文件与OneDrive云存储进行同步,确保文件的实时更新和备份。文件管......
  • compattelrunner.exe 是 Windows 操作系统中的一个可执行文件。它是 Microsoft 官方提
    compattelrunner.exe是Windows操作系统中的一个可执行文件。它是Microsoft官方提供的用于收集计算机性能数据和故障排除的工具,这些数据旨在帮助Microsoft监测和改进Windows操作系统。Compattelrunner.exe的主要功能如下:收集数据:它定期扫描计算机上的所有文件和程序,并......
  • shiclient.exe是Microsoft Windows操作系统中的一个可执行文件,它是Shell Infrastructu
    shiclient.exe是MicrosoftWindows操作系统中的一个可执行文件,它是ShellInfrastructureClient的缩写。该程序负责处理WindowsShell(资源管理器)以及一些相关的操作。ShellInfrastructureClient(SIC)是Windows系统的一部分,它提供了Shell相关的功能和服务,包括任务栏、开始菜单、桌......
  • dwm.exe是Windows操作系统中的一个进程,它是Desktop Window Manager的缩写,负责管理和渲
    dwm.exe是Windows操作系统中的一个进程,它是DesktopWindowManager的缩写,负责管理和渲染桌面以及窗口的显示效果。DesktopWindowManager(DWM)是WindowsVista及其之后版本引入的一个特性,它通过使用硬件加速来实现窗口的合成和渲染,提供了透明、窗口阴影、动画效果等视觉特效。它还......
  • driverindexer 是一个用于管理Windows系统驱动程序的工具。它被用于创建、修改和维护W
    driverindexer是一个用于管理Windows系统驱动程序的工具。它被用于创建、修改和维护Windows操作系统的驱动程序索引。驱动程序索引是一个记录系统中所有已安装驱动程序信息的数据库。它包含了驱动程序的名称、文件路径、版本号等详细信息。Windows系统通过查询驱动程序索引来查......
  • drvinst.exe是Windows操作系统中的一个可执行文件,它被用于安装第三方驱动程序。驱动程
    drvinst.exe是Windows操作系统中的一个可执行文件,它被用于安装第三方驱动程序。驱动程序是指计算机硬件或外设的控制程序,负责控制硬件设备的操作。在Windows操作系统中,drvinst.exe会自动运行并安装已下载的第三方驱动程序。在某些情况下,您需要手动运行drvinst.exe来安装驱动程序......