1.0: 操作系统接口(OS interface)
操作系统功能及接口介绍
操作系统的任务是为多个程序提供电脑硬件资源的共享使用,提供比硬件单独本身更加有用的一套服务集合
操作系统管理和抽象底层的硬件
文字处理软件不需要关心它使用的是何种类型的磁盘硬件
操作系统在多个程序间共享硬件资源使得(或直观看上去是,并行和并发)在同时运行
操作系统可以控制程序间的交互,使得他们可以共享数据或者共同工作(一个系统由多个进程协同工作,如智能驾驶系统)
操作系统通过接口向用户程序提供服务,设计好的接口是一个难度挺大的工作
一方面想让接口简单和窄小(simple and narrow)容易实现
另一方面我们也倾向于向应用程序提供很多复杂的特性(sophisticated features)
在设计接口时依赖一些少数的机制可以将接口进行组合从而实现更加的通用性
本课程采用的操作系统
xv6,提供了在Unix操作系统中的一些基础的接口,同时也模仿了unix的内部设计
Unix提供的是窄接口(narrow interface)但提供了较好的将它们进行组合的机制,从而实现了较好的通用性
这个接口非常成功,现代的操作系统(BSD,Linux,macOS,Solaris,甚至微软windows)都拥有Unix-like interfaces
内核空间和用户空间
如下图所示,内核(kernel)是一个特殊的程序,为运行程序提供服务
运行的程序称为进程,拥有内存,包含了指令,数据和堆栈
指令实现了程序的计算
数据是在计算执行过程中的变量
堆栈组织程序的过程调用
一个电脑通常只有一个内核,但同时运行多个进程
系统调用
当一个进程需要触发一个内核服务时,会触发一个系统调用(system call)
一个系统调用即为操作系统的一个接口
系统调用会进入内核态
内核执行服务功能并返回
因此从整个过程来看,进程会在用户空间和内核空间来回切换执行,内核采用了CPU提供的硬件保护机制,使得每一个进程在用户态执行时只能访问自己的内存空间。
内核使用硬件权限(supervisor previledge)执行这些保护,而在用户态下程序在执行时不具有这些权限,
当用户程序调用一个system call时,硬件提升权限等级并开始执行在内核中预先安排好的函数
内核提供的系统调用的集合就是用户程序看得见的接口
xv6的内核提供了传统Unix的内核提供的服务子集
shell程序是一个普通的用户程序,从用户读取命令并执行它们
补充知识
1、cpu的abi定义了函数调用时参数的传递,cpu不同寄存器的状态保存机制对吗?
对的。**CPU的ABI(Application Binary Interface)**定义了以下内容,其中包括函数调用时参数的传递方式以及寄存器的状态保存机制:
参数传递机制 ABI规定了在函数调用过程中,函数的参数如何传递。例如:
参数是通过寄存器传递,还是通过栈传递? 如果通过寄存器传递,哪些寄存器被用来存储函数参数(如 x86-64 使用 RDI、RSI、RDX 等寄存器)? 多余的参数(超出寄存器容量)是否会通过栈传递? 返回值的传递(例如大多数体系结构中返回值使用特定的寄存器,如 x86-64 的 RAX 寄存器)。
寄存器的状态保存机制 ABI还定义了调用者保存(caller-saved)和被调用者保存(callee-saved)寄存器:
调用者保存寄存器:调用者在调用函数之前,需要保存这些寄存器的值(如果调用者需要在返回后继续使用这些值)。 被调用者保存寄存器:被调用的函数需要负责保存这些寄存器的值,并在函数执行完毕后恢复它们。
不同的CPU架构规定了不同的寄存器保存规则。例如: 在 x86-64 ABI 中,RBX、RSP、RBP、R12-R15 是被调用者保存的寄存器,而 RAX、RCX、RDX 等是调用者保存的寄存器。 在 ARM64 ABI 中,X0-X7 用于参数传递和返回值,X19-X28 是被调用者保存的寄存器。
栈帧布局 ABI还定义了栈帧的布局和调用约定,例如:
栈的增长方向(向下增长或向上增长)。 栈对齐要求(例如 16 字节对齐)。 函数调用时栈上的局部变量和返回地址的存放位置。
其他相关内容 异常处理、动态链接、内存对齐等也可能在ABI中有定义。 例如,某些CPU架构对浮点数寄存器的使用有特殊规定。 综上,ABI是为了确保不同编译器生成的程序能够在同一架构的系统上正确交互运行,其中函数调用的参数传递方式和寄存器的状态保存机制是重要的组成部分。
2、调用者和被调用者寄存器的特性
调用者保存寄存器(Caller-Saved Registers) 定义:
在函数调用前,调用者需要自行保存这些寄存器的内容(如果需要在调用后继续使用)。 被调用函数可以自由使用这些寄存器,而无需负责保存原值。 特点:
在 x86-64 中的常见例子:RAX, RCX, RDX, RSI, RDI, R8-R11。 在 ARM64 中的常见例子:X0-X7(参数寄存器)。 优点:
提高效率:如果调用者确定寄存器的内容不再需要,可以不保存,避免了多余的开销。 灵活性高:被调用者无需关注寄存器的状态,直接使用。 缺点:
调用者负担增加:如果调用者需要保存的寄存器较多,保存/恢复的开销可能较高。 复杂性:编译器在生成代码时需要仔细分析哪些寄存器需要保存。 2. 被调用者保存寄存器(Callee-Saved Registers) 定义:
在函数调用过程中,被调用者需要保存和恢复这些寄存器的原始值。 调用者可以假定这些寄存器的值在函数调用前后保持不变。 特点:
在 x86-64 中的常见例子:RBX, RBP, R12-R15。 在 ARM64 中的常见例子:X19-X28。 优点:
调用者简单:调用者无需为这些寄存器负责,可以直接假定调用前后的值不变。 程序稳定性好:多个函数嵌套调用时,寄存器的内容能被妥善维护。 缺点:
被调用者开销增加:被调用者必须保存所有使用到的寄存器,即使部分寄存器不需要恢复。 函数嵌套层次深时,被调用者保存/恢复寄存器的开销可能较大。
编译器使用寄存器的一些规则策略: 实际工作机制 编译器在生成代码时,会根据 ABI 规则对寄存器进行分类和分配。具体机制包括:
寄存器分配: 某些寄存器会被固定分配为参数传递寄存器、返回值寄存器、临时寄存器等(例如 x86 中的 eax, ARM 中的 r0 等)。 嵌套调用分析: 如果函数内嵌套调用多,调用者寄存器的保存频率较高,编译器可能倾向于选择被调用者寄存器。 寄存器溢出到栈: 如果寄存器不够用,编译器会将多余的数据溢出到栈中。