本系列文章为MIT6.S081的学习笔记,包含了参考手册、课程、实验三部分的内容,前面的系列文章链接如下
操作系统MIT6.S081:[xv6参考手册第1章]->操作系统接口
操作系统MIT6.S081:[xv6参考手册第2章]->操作系统组织结构
操作系统MIT6.S081:[xv6参考手册第3章]->页表
操作系统MIT6.S081:[xv6参考手册第4章]->Trap与系统调用
操作系统MIT6.S081:P1->Introduction and examples
操作系统MIT6.S081:P2->OS organization and system calls
操作系统MIT6.S081:P3->Page tables
操作系统MIT6.S081:P4->RISC-V calling conventions and stack frames
操作系统MIT6.S081:P5->Isolation & system call entry/exit
操作系统MIT6.S081:P6->Page faults
操作系统MIT6.S081:Lab1->Unix utilities
操作系统MIT6.S081:Lab2->System calls
操作系统MIT6.S081:Lab3->Page tables
操作系统MIT6.S081:Lab4->Trap
操作系统MIT6.S081:Lab5->Lazy allocation
文章目录
一、操作系统内存使用情况
在真正的操作系统中,内存是如何使用的
登录到Athena计算机(MIT内部共享使用的计算机),执行
top指令输出内存的使用情况。
查看Mem这一行,可以看到:
①计算机中总共有多少内存(33048332)
②大部分内存都被使用了(4214604+26988148),应用程序用得较少,buff/cache用得较多。这在操作系统中很常见,因为我们不想让物理内存就在那闲置着,所以这里大块的内存被用作buff/cache。
③还有一小块内存是空闲的(1845580),但是并不多。
注:
①以上是一个非常常见的场景,大部分操作系统运行时几乎没有任何空闲的内存。这意味着如果应用程序或者内核需要使用新的内存,那么我们需要丢弃一些已有的内容。现在的空闲内存(free)或许足够几个page用,但是在某个时间点如果需要大量内存的话,要么是从应用程序,要么是从buffer/cache中,需要撤回已经使用的一部分内存。所以,当内核在分配内存的时候,通常都不是一个低成本的操作,因为并不总是有足够的可用内存,为了分配内存需要先撤回一些内存。
②如果你查看输出的每一行,VIRT表示的是虚拟内存地址空间的大小,RES是实际使用的内存数量。从这里可以看出,实际使用的内存数量远小于地址空间的大小。所以,我们上节课讨论的基于虚拟内存和page fault提供的非常酷的功能在这都有使用,比如说demand paging。
二、中断的处理流程与硬件设备
中断的使用场景
中断对应的场景很简单,就是硬件想要得到操作系统的关注。例如:
----网卡收到了一个packet,网卡会生成一个中断。
----用户通过键盘按下了一个按键,键盘会产生一个中断。
操作系统需要做的是:保存当前的工作---->处理中断---->恢复之前的工作。这里的保存和恢复工作与之前系统调用过程非常相似。所以系统调用、page fault、中断都使用相同的机制。
中断与系统调用的区别:
①asynchronous(异步)。当硬件生成中断时,中断处理程序与CPU上当前运行的进程没有任何关联。但如果是系统调用的话,需要进入内核,然后在调用进程的上下文中运行。
②concurrency(并发)。对于中断来说,CPU和生成中断的设备是并行运行。网卡自己独立处理来自网络的网络包,然后在某个时间点产生中断。同时,CPU也在正常运行。所以CPU和设备之间是并行运行的,我们必须管理这里的并行。
③program devices(对硬件编程)。我们这节课主要关注外部设备,例如网卡、UART。这些设备需要被编程,每个设备都有一个编程手册,就像RISC-V有一个包含了指令和寄存器的手册一样。设备的编程手册包含了它有什么样的寄存器、它能执行什么样的操作、在读写控制寄存器时设备会如何响应。不幸的是,设备的手册不如RISC-V的手册清晰,这会使得对设备的编程更加复杂。
中断的产生
我们这节课会讨论的内容:
----控制台中的提示符$是如何显示出来的。
----如果你在键盘输入ls,这个ls字符是怎么读入并显示在控制台。
----实现上述目标所需要的所有机制。
我们首先要关心的是:中断是从哪里产生的?
因为我们主要关心的是外部设备的中断,而不是定时器中断或软件中断。外设中断来自于主板上的设备,下图是一个SiFive主板(QEMU模拟的那个),可以发现有大量的设备连接在或者可以连接到这个主板上。
这里连接了许多设备,如以太网卡、MicroUSB、MicroSD、重置按钮等,主板上的各种线路将外设和CPU连接在一起。下面两个引脚一个是用来传输的UART0 Tx,另外一个是用来接收的UART0 Re。
SiFive文档
下图是SiFive有关处理器的文档,图中的右侧是各种各样的设备,如UART0。我们在之前的课程已经知道UART0这些硬件设备会映射到内核内存地址中低于0x80000000的地方,而左边所有的DRAM都映射在地址空间的0x80000000之上。通过向相应的设备地址执行load/store指令,我们就可以对设备进行编程。
所有的设备都连接到处理器上,处理器通过Platform Level Interrupt Control(PLIC)来处理设备中断。我们进一步查看PLIC的结构图:
从左上角可以看出,我们有53个不同的来自于设备的中断。这些中断到达PLIC之后,PLIC会路由这些中断。图的右下角是CPU的核,PLIC会将中断路由到某一个CPU核。如果所有的CPU核都正在处理中断,PLIC会保留中断直到有一个CPU核可以用来处理中断。所以PLIC需要保存一些内部数据来跟踪中断的状态。
这里的流程总结如下:
①PLIC通知当前有一个待处理的中断
②其中一个CPU核会声称接收中断,这样PLIC就不会把中断发给其他的CPU处理
③CPU核处理完中断之后,CPU会通知PLIC处理完毕
④PLIC将不再保存中断的信息
问答
学生提问:PLIC有没有什么执行机制来确保公平?
Frans教授:这里取决于内核以什么样的方式来对PLIC进行编程。PLIC只是分发中断,而内核需要对PLIC进行编程来告诉它中断应该分发到哪。实际上,内核可以对中断优先级进行编程,这里非常的灵活。
三、驱动
驱动的概念
通常来说,管理设备的代码称为驱动,xv6中所有的驱动都在内核中。我们今天要看的是UART设备的驱动,代码在uart.c文件中。
驱动的结构
如果我们查看驱动代码的结构,我们可以发现大部分驱动都分为两个部分:
bottom和top。
----bottom部分通常是中断处理程序(Interrupt handler)。当一个中断送到了CPU并且CPU接收这个中断,CPU会调用相应的中断处理程序。中断处理程序并不运行在任何特定进程的上下文中,它只是处理中断。
----top部分是用户进程或者内核的其他部分调用的接口。对于UART来说,这里有read/write接口,这些接口可以被更高层级的代码调用。
通常情况下,驱动中会有一些队列(或者说buffer),top部分的代码会从队列中读写数据,而中断处理程序(bottom部分)同时也会向队列中读写数据。这里的队列可以将顶部和底部解耦,并允许设备和CPU上的其他代码并行运行。
----通常对于中断处理程序来说存在一些限制,因为它并没有运行在任何进程的上下文中,所以进程的页表并不知道该从哪个地址读写数据,也就无法直接从中断处理程序读写数据。所以驱动的top部分通常与用户的进程交互,进行数据的读写。
----在很多操作系统中,驱动代码加起来可能会比内核还要大,主要是因为对于每个设备都需要一个驱动,而设备又很多。
设备编程
通常来说,对设备编程是通过内存映射I/O(memory mapped I/O)完成的。
----在SiFive的手册中,设备地址出现在物理地址的特定区间内,这个区间由主板制造商决定。
----操作系统需要知道这些设备位于物理地址空间的具体位置,然后再通过普通的load/store指令对这些地址进行编程。
----load/store指令实际上的工作就是读写设备的控制寄存器。例如,对网卡执行store指令时,CPU会修改网卡的某个控制寄存器,进而导致网卡发送一个网络包。所以这里的load/store指令不会读写内存,而是会操作设备。
----你还需要阅读设备的文档来弄清楚设备的寄存器和相应的行为,有的时候文档很清晰,有的时候文档不是那么清晰。
下图中是SiFive主板中的对应设备的物理地址。

本文深入探讨了操作系统内存的使用情况,强调了内存管理的重要性,特别是在资源紧张时如何回收和分配内存。接着,详细阐述了中断处理流程,区分了中断与系统调用的异步和同步特性,并介绍了如何在xv6中设置中断。此外,还解析了UART驱动的结构,包括其top和bottom部分,以及如何处理并发问题。最后,讨论了中断在现代操作系统中的演进,包括轮询和中断之间的权衡。







最低0.47元/天 解锁文章
2621

被折叠的 条评论
为什么被折叠?



