目录
2.1 操作系统服务(Operating System Services)
一组操作系统服务提供了对用户有帮助的功能:
另一组操作系统功能是为了通过资源共享来确保系统本身的有效运行
操作系统服务的一个视图
2.2 用户操作系统接口
CLI(命令行用户接口)
Bourne Shell 命令解释器
图形用户接口(GUI)
Mac OS X GUI
触摸屏接口(Touchscreen Interfaces)
2.3 系统调用(程序接口)(System Calls)
系统调用、API和C库
标准API示例
系统调用
系统调用参数传递(三种传参方法:寄存器,内存,栈)
通过表传递的参数
2.4 系统调用的类型
Windows和Unix/Linux系统调用的示例
Solaris 10 dtrace 跟踪系统调用
2.5 系统程序(System Services)
2.6 Linkers and Loaders
链接器和加载器的角色
2.7 为什么应用程序是特定于操作系统
API与ABI有何不同?
2.8 操作系统的设计和实现
操作系统的设计问题
操作系统的设计考虑
实现
2.9 操作系统结构
简单结构(Simple Structure)
MS-DOS结构
UNIX
层次结构(Layered Approach)
微内核机构(Microkernel System Structure)
单、宏内核结构(Monolithic Kernels Structure)
宏内核
模块(Modules)
混合系统(Hybrid Systems)
macOS和iOS结构
iOS
Android
2.10 构建和启动一个操作系统
构建和引导Linux
系统启动
2.11 操作系统调试
性能调整
追踪
BCC
Linux bcc/BPF跟踪工具
2.1 操作系统服务(Operating System Services)
一组操作系统服务提供了对用户有帮助的功能:
用户接口-几乎所有的操作系统都有一个用户接口(UI,User Interface)
命令行接口(CLI,Command-Line Interface)、图形用户接口(GUI,Graphics User Interface)、批处理(Batch)之间的不同
程序执行-系统必须能够将一个程序加载到内存中并运行该程序,结束执行,正常或异常(指示错误)
I/O操作-一个正在运行的程序可能需要I/O,这可能涉及到一个文件或一个I/O设备。
文件系统操作-文件系统特别有趣。显然,程序需要读取和写文件和目录,创建和删除它们,搜索它们,列出文件信息,权限管理。
通信——进程可以在同一台计算机上或通过网络在计算机之间交换信息
通信可以通过共享内存或通过消息传递(由操作系统移动的数据包)
错误检测-操作系统需要不断地意识到可能出现的错误
可能发生在CPU和内存硬件中,在I/O设备中,在用户程序中
对于每种类型的错误,操作系统应该采取适当的行动,以确保正确和一致的计算
调试设备可以极大地提高用户和程序员有效使用系统的能力
另一组操作系统功能是为了通过资源共享来确保系统本身的有效运行
资源分配-当多个用户或多个任务同时运行时,必须将资源分配给它们中的每一个。
许多类型的资源——一些(如CPU周期、主存和文件存储)可能有特殊的分配代码,其他的(如I/O设备)可能有一般的请求和发布代码。
记账-跟踪哪些用户使用了多少和什么类型的计算机资源
保护和安全——存储在多用户或网络计算机系统中的信息的所有者可能希望控制该信息的使用,并发进程不应相互干扰
保护包括确保对系统资源的所有访问都得到控制
来自外部的系统安全需要用户身份验证,扩展到保护外部I/O设备不受无效的访问尝试
如果一个系统要得到保护和安全,就必须在整个系统中采取预防措施。一个链条的强度只有它最薄弱环节的强度
操作系统服务的一个视图
2.2 用户操作系统接口
Operating System Interface
Command Interface (命令接口)
Program Interface (程序接口,system call)
User interface,命令接口- Almost all operating systems have a user interface (UI)
Command-Line Interface (CLI) ,命令行用户接口,文本界面
Graphics User Interface (GUI), 图形用户接口
Touch-Screen Interface 触摸屏接口
Voice Interface 语音接口
CLI(命令行用户接口)
CLI允许直接输入命令
有时用内核实现,有时用系统程序实现
有时会实现多种风格-shell
主要是从用户那里获取一个命令并执行它
主要是从用户那里获取一个命令并执行它
Linux、UNIX、Mac OS: bash、sh、tcsh
Windows:PowerShell、cdm(dos)
Bourne Shell 命令解释器
图形用户接口(GUI)
用户友好的桌面隐喻接口
通常是鼠标、键盘和显示器
图标表示文件、程序、操作等
界面中对象上的各种鼠标按钮会导致各种操作——提供信息、选择、执行函数、打开目录(称为文件夹)
由Xerox PARC发明
许多系统现在同时包括CLI和GUI接口
微软Winsows是使用CLI“命令”shell的GUI
苹果Mac OS X作为“Aqua”GUI接口,下面有UNIX内核和可用的shells
Solaris是带有可选GUI接口的CLI(Java桌面、KDE)
Linux、Windows、Mac OS
Mac OS X GUI
触摸屏接口(Touchscreen Interfaces)
触摸屏设备需要新的接口
鼠标不可能或不需要
基于手势的动作和选择
用于文本输入的虚拟键盘
2.3 系统调用(程序接口)(System Calls)
系统调用:对操作系统提供的服务的编程接口
系统调用是进程和操作系统内核之间的编程接口。
通常是用高级语言编写的(C或C++)
大多数是由程序通过高级应用程序接口(API,Application Program Interface)访问的,而不是直接的系统调用使用
三个最常见的API分别是用于Windows的Win32 API、用于基于POSIX的系统的POSIXAPI(包括几乎所有版本的UNIX、Linux和Mac OS X),以及用于Java虚拟机(JVM)的Java API
为什么要使用API而不是系统调用呢?
(请注意,本文中使用的系统调用名称是通用的)
系统调用、API和C库
应用编程接口(API)其实是一组函数定义,这些函数说明了如何获得一个给定的服务;而系统调用是通过软中断向内核发出一个明确的请求,每个系统调用对应一个封装例程(wrapper routine,唯一目的就是发布系统调用)。
一些API应用了封装例程。
API还包含各种编程接口,如:C库函数、OpenGL编程接口等
系统调用的实现是在内核完成的,而用户态的函数是在函数库中实现
的
标准API示例
考虑Win32API中的ReadFile()函数——一个用于从文件中读取的函数
传递给ReadFile()的参数的描述
HANDLE file—要读取的文件
LPVOID buffer—一个用来读写数据的缓冲区
DWORD bytesToRead—要读入缓冲区的字节数
LPDWORD bytesRead—在上次读取期间读取的字节数
LPOVERLAPPED ovl—表示是否正在使用重叠的I/O
系统调用
什么是系统调用
非正式地说,系统调用类似于函数调用,但是。。
函数实现在操作系统内部,或者我们将它命名为操作系统内核。
我们什么时候知道一个“函数”是否是一个系统调用?
阅读Linux下的手册页“syscalls”
让我们猜猜:谁是系统调用?
系统调用VS库函数调用
如果它们不是系统调用,那么它们就是函数调用!
以fopen()为例。
以fopen()为例。
那么,为什么人们发明了fopen()呢?
因为打开()太原始,不是程序友好的!
这些函数通常被打包在一个名为库文件的对象中。
系统调用参数传递(三种传参方法:寄存器,内存,栈)
通常,需要更多的信息,而不是所需的系统调用的标识
信息的确切类型和数量根据操作系统和调用而有所不同
用来将参数传递给操作系统的三种通用方法
最简单的方法:在寄存器中传递参数(寄存器传递参数)
有的情况下,可能有比寄存器更多的参数
参数存储在内存的块或表中,在寄存器中块的地址作为参数传递(通过地址传递参数)
Linux和Solaris都采用了这种方法
参数由程序放置或推到堆栈上,并由操作系统从堆栈中弹出(通过堆栈传递参数)
块方法和堆栈方法不限制正在传递的参数的数量或长度
通过表传递的参数
2.4 系统调用的类型
进程控制
文件管理
设备管理
信息维护
通信
Windows和Unix/Linux系统调用的示例
Win32 CreateProcess :example\newproc-win32.c
Solaris 10 dtrace 跟踪系统调用
Linux的ptrace动态跟踪工具;
Linux的strace命令显示系统调用
2.5 系统程序(System Services)
系统程序为程序的开发和执行提供了一个方便的环境。其可分为:
文件处理
状态信息
文件修改
编程语言支持
程序加载和运行
通信
应用程序
大多数用户对操作系统的看法是由系统程序定义的,而不是实际的系统调用
为程序的开发和执行提供了一个方便的环境
其中一些只是系统调用的用户接口;其他的则要复杂得多
文件管理-创建、删除、复制、重命名、打印、转储、列表,以及通常操作文件和目录
状态信息
一些(系统程序)询问系统的信息-日期、时间、可用内存量、磁盘空间、用户数量
其他的则提供详细的性能、日志记录和调试信息
通常,这些程序格式化并打印输出到终端或其他输出设备
有些系统实现了一个注册表,用于存储和检索配置信息
文件修改
用于创建和修改文件的文本编辑器
用于搜索文件内容或执行文本转换的特殊命令
编程语言支持-有时会提供编译器、汇编器、调试器和解释器
程序加载和执行-绝对加载器、可重新定位的加载器、链接编辑器和覆盖加载器、为高级语言和机器语言调试系统
通信-提供了在进程、用户和计算机系统之间创建虚拟连接的机制
允许用户将消息向彼此的屏幕发送消息、浏览网页、发送电子邮件消息、远程登录、将文件从一台机器传输到另一台机器
2.6 Linkers and Loaders
源代码编译成目标文件,设计为加载到任何物理内存位置-可重新定位的目标文件
链接器将这些组合成单个二进制可执行文件
还带来了库
程序作为二进制可执行文件驻留在辅助存储器上
必须由加载器带入内存中才能执行
重新定位为程序部分分配最终地址,并调整程序中的代码和数据,以匹配这些地址
现代通用系统不会将库链接到可执行文件中
相反,动态链接的库(在Windows中,DLLs)是根据需要加载的,由所有使用相同版本的同一库(加载一次)的人共享
目标,可执行文件具有标准格式,所以操作系统知道如何加载和启动它们
链接器和加载器的角色
2.7 为什么应用程序是特定于操作系统
在一个系统上编译的应用程序通常不能在其他操作系统上进行可执行操作
每个操作系统都提供了自己独特的系统调用
自己的文件格式等。
应用程序可以是多操作系统的
用Python、Ruby和多个操作系统上可使用的解释器等解释语言编写的
用语言编写的应用程序,其中包含一个包含正在运行的应用程序的VM(如Java)
使用标准语言(如C),在每个操作系统上分别编译,以便在每个操作系统上运行
应用程序二进制接口(ABI,Application Binary Interface)是等同于API的体系结构,它定义了二进制代码的不同组件如何在给定的体系结构、CPU等上为给定的操作系统进行接口。
API与ABI有何不同?
API定义了源代码和库之间的接口,同样的代码可以在支持这个API的任何
系统中编译;
ABI允许编译好的二进制代码在使用兼容ABI的系统中无需改动就能运行
2.8 操作系统的设计和实现
操作系统的设计和实现不是“可解决的”,但一些方法已经被证明是成功的
不同操作系统的内部结构可能有很大差异
首先要定义目标和规范
用户目标和系统目标
用户目标——操作系统应使用方便、易于学习、可靠、安全、快速
系统目标——操作系统应易于设计、实现和维护,同时也应灵活、可靠、无错误、高效
操作系统的设计问题
操作系统设计有着不同于一般应用系统设计的特征:
复杂程度高
研制周期长
正确性难以保证
Windows 2000开发的艰辛与规模
最早Uinx是1400行代码;
Windows xp有4000万行代码;
fedroa core有2亿多行代码,Linux kernel 4.10有2千多万
行代码。
解决途径:
良好的操作系统结构
先进的开发方法和工程化的管理方法(软件工程)
高效的开发工具
分离的重要原则
策略(Policy):要做什么?
机制(Mechanism):怎么做?
机制决定如何做某件事,策略决定将做什么
政策与机制的分离是一个非常重要的原则,如果以后要改变政策决策,它允许最大的灵活性
操作系统的设计考虑
功能设计:操作系统应具备哪些功能
算法设计:选择和设计满足系统功能的算法和策略,并分析和估算其效能
结构设计:选择合适的操作系统结构
按照系统的功能和特性要求,选择合适的结构,使用相应的结构设计方法将系统逐步地分解、抽象和综合,使操作系统结构清晰、简单、可靠、易读、易修改,而且使用方便,适应性强
实现
变化很大
早期操作系统以汇编语言
然后是系统编程语言,如Algol,PL/1
现在C,C++
实际上通常是多种语言的混合体
组件中的最低级别
主体用C表示
系统程序在C,C++,脚本语言,如PERL,Python,shell脚本
更高级的语言更容易移植到其他硬件上
但速度较慢
仿真可以允许操作系统在非本地硬件上运行
2.9 操作系统结构
Simple Structure简单结构
Layered Approach层次化结构
Microkernel 微内核
Monolithic Kernels Structure单/宏内核结构
Modules 模块
Hybrid 混合结构
简单结构(Simple Structure)
MS-DOS-被编写,以在最小的空间内提供最多的功能
不分为模块
虽然MS-DOS有一些结构,但它的接口和功能级别并没有很好地分离
MS-DOS结构
UNIX
UNIX-受硬件功能的限制,原来的UNIX操作系统的结构有限。UNIX操作系统由两个可分离的部分组成
系统程序
内核
由系统调用接口以下和物理硬件以上的所有内容组成
提供文件系统、CPU调度、内存管理和其他操作系统功能;一个级别的大量功能
UNIX、Linux单内核结构
传统UNIX系统结构
层次结构(Layered Approach)
操作系统被划分为若干层(层),每个层构建在较低的层之上。底层(第0层)是硬件;最高的(第N层)是用户界面。
使用模块化,选择层使每个层只使用低级层的函数(操作)和服务
微内核机构(Microkernel System Structure)
微内核——只有最基本的函数才能直接在一个核心内核中实现——微内核。所有其他功能都被委托给通过明确定义的通信接口与中央内核进行通信的自主进程
由下面两大部分组成 :
“微”内核
若干服务
从内核移动到“用户”空间
使用消息传递在用户模块之间进行通信
好处:
更容易扩展一个微内核
更容易将操作系统移植到新的架构中
更可靠(在内核模式下运行的代码更少)
更安全
危害:
用户空间到内核空间通信的性能开销
Windows NT ... Windows 8、Windows 10、Mac OS
L4 : http://www.l4hq.org/
华为:鸿蒙
Winsows内核
Windows NT 4.0 起,采用 microkernel 的架构
单、宏内核结构(Monolithic Kernels Structure)
宏内核:内核的整个代码——包括它的所有子系统,如内存管理、文件系统或设备驱动程序——都被打包到一个文件中。每个函数都可以访问内核的所有其他部分;
最早和最常见的操作系统架构(UNIX、MSDOS)
操作系统的每个组件都包含在内核中
示例: OS/360、VMS和Linux
宏内核
优点:高效的,因为组件之间的直接通信
缺点:
很难分离出错误和其他错误的来源
难以修改和维护
随着操作系统的发展,内核会变得更大。
模块(Modules)
大多数现代操作系统都实现了内核模块(kernel modules)
使用面向对象的方法
每个内核组件都是独立的
每个组件都通过已知的接口与其它内核组件交流
每个组件都可以根据需要在内核中加载
总的来说,类似于图层,但更灵活
Linux、Solaris等
混合系统(Hybrid Systems)
大多数现代操作系统实际上并不是一个纯粹的模型
混合技术结合了多种方法来解决性能、安全性和可用性需求
Linux和Solaris内核在内核地址空间,如此单片的,加上模块化的动态加载功能
Windows大多是单片的,再加上针对不同子系统个性的微内核
苹果Mac OS X混合,分层,AquaUI+Cocoa编程环境
下面是由Mach微内核和BSD Unix部分组成的内核,加上I/O工具包和动态可动态加载的模块(称为内核扩展kernel extensions)
macOS和iOS结构
iOS
苹果为iPhone,iPad提供的移动操作系统
基于Mac OS X的结构化,增加了一些功能
不以本机方式运行OS X应用程序
也可以运行在不同的CPU架构上(ARM vs. Intel)
可触摸Objective-C API来开发应用程序
给图形,音频,视频的媒体服务层
内核服务提供云计算、数据库
内核操作系统,基于Mac OS X内核
Android
由开放手机联盟(主要是谷歌)开发
开源
与IOS类似的堆栈
基于Linux内核,但已进行了修改
提供进程、内存、设备驱动程序管理
增加电源管理
运行时环境包括核心库集和Dalvik虚拟机
用Java和Android API开发的应用程序
编译为Java字节码的Java类文件,然后转换为可执行文件,而不是在Dalvik VM中运行
库包括网络浏览器(webkit)、数据库(SQLite)、多媒体、更小的libc的框架
Android 架构
2.10 构建和启动一个操作系统
操作系统通常被设计为运行在具有各种外设的一类系统上
通常,操作系统已安装在已购买的计算机上
但是可以构建和安装其他一些操作系统
如果从头开始生成一个操作系统
编写操作系统的源代码
为将要运行它的系统配置操作系统
编译操作系统
安装操作系统
启动计算机及其新的操作系统
构建和引导Linux
下载Linux源代码(The Linux Kernel Archives)
通过“make manuconfig”来配置内核
通过“make”来编译内核模块
生成vmlinuz,内核图像
通过“make modules”来编译内核模块
通过“make modules_install”将内核模块安装到vmlinuz中
通过“make install”在系统上安装新的内核
系统启动
当在系统上初始化电源时,将在一个固定的内存位置开始执行
操作系统必须向硬件提供,以便硬件可以启动它
小块代码-存储在ROM或EEPROM中的引导加载程序BIOS可以定位内核,将其加载到内存中,并启动它
引导块在固定的位置由ROM代码加载,ROM代码从磁盘加载引导加载器
现代系统用统一的可扩展固件接口(UEFI,Unified Extensible Firmware Interface)取代了BIOS
通用的引导加载程序,GRUB,允许从多个磁盘,版本,内核选项中选择内核
内核负载和系统随后正在运行
引导加载程序经常允许不同的引导状态,例如单用户模式
2.11 操作系统调试
调试是找到并修复错误
还有性能调整
操作系统生成包含错误信息的日志文件
应用程序的失败可以生成捕获进程内存的核心转储文件
操作系统故障可能会生成包含内核内存的崩溃转储文件
除了崩溃之外,性能调整还可以优化系统性能
有时使用活动的跟踪列表,记录下来以供分析
轮廓分析(Profiling)是对指令指针的定期抽样,以寻找统计趋势
克尼根定律:“调试的难度是最初编写代码的两倍。因此,如果您尽可能聪明地编写代码,根据定义,您就不够聪明来调试它。”
性能调整
通过消除瓶颈来提高性能
操作系统必须提供计算和显示系统行为度量的方法
例如,“top”程序或Windows任务管理器
追踪
收集特定事件的数据,例如系统调用调用中涉及的步骤
工具包括
strace-跟踪由进程调用的系统调用
gdb-源级调试器
perf-Linux性能工具的集合
tcpdump-收集网络数据包
BCC
如果没有理解它们操作的工具集,调试用户级和内核代码之间的交互几乎是不可能的
BCC(BPF Compiler Collection)是一个丰富的工具包,为Linux提供跟踪特性
另请参见原始的DTrace
例如,disksnoop.py跟踪磁盘I/O活动