操作系统是特殊的大型基础软件,它向下管理计算机系统中的全部硬件资源,向上为应用程序和用户提供友好、一致的抽象视图,内部构成非常复杂。本节以UNIX操作系统为例,介绍操作系统的基本组成。UNIX操作系统是绝大多数操作系统架构设计的灵感源泉,因此被视作现代操作系统的“鼻祖”。UNIX操作系统的设计出于一批天才黑客的构想,其“大道至简”的设计哲学富有启发性,值得每一位软件爱好者学习。
UNIX操作系统为现代操作系统设计了经典的体系结构,一般可分为3个主要软件层,分别是系统内核、系统调用接口和系统应用程序(即操作系统提供的管理工具等)。
如图2.1所示,系统内核直接管理计算机系统的全部硬件资源,通过系统调用接口向应用程序提供系统服务,并通过用户界面为用户提供抽象、一致的计算服务。UNIX操作系统的体系结构将计算机系统中的硬件资源与系统应用程序分离,为软件开发者提供一致、可移植的系统开发接口,为终端用户提供简洁、一致的用户交互界面,因此广泛应用于大多数操作系统。
为了保证计算机系统的运行安全,UNIX操作系统启用了系统级的安全策略,将软件运行于不同的保护层级,如图2.2所示。系统内核运行于内核空间,可直接访问计算机系统的内存、硬盘及各种外设等全部硬件资源,并通过系统调用接口提供系统服务;其他软件一般都运行于用户空间,必须通过访问系统服务来访问物理资源,且软件各自逻辑独立、享有虚拟的全部物理资源,确保系统资源的有效分配、应用程序的独立运行。
图2.1 UNIX操作系统的典型体系结构
图2.2 UNIX操作系统的层级架构
系统内核(简称为内核),是操作系统的核心组成部分,负责进程管理、内存管理、文件管理和设备管理等,并通过系统调用接口向应用程序提供计算机系统的基本功能。
内核的架构主要有宏内核和微内核两种。宏内核将全部的内核模块作为一个单一的内核态任务运行于同一个地址空间,内核模块之间通过函数实现调用,这种方式实现简单、运行效率高;微内核则将内核拆分为多个独立的模块分别运行于独立的地址空间,模块之间通过消息传递机制来实现服务调用,内核的扩展性好、可靠性高。微内核被视作内核的发展方向,但事实上宏内核仍被广泛采用 。
内核为应用程序提供系统服务的机制是系统调用。系统调用(System Call)是把应用程序的参数传给内核,请求相应的内核函数在内核里完成相应的处理,并将处理结果返回给应用程序的过程。系统调用本质上是一种过程调用,但它涉及不同权限的地址空间切换和物理设备资源使用等问题,一般通过中断机制来实现,过程非常复杂。系统调用接口明确了内核所提供的系统调用的名称、输入参数和返回值等详细信息。Windows操作系统中的CreateProcess、ReadFile等,UNIX类操作系统中的fork、read等,都是经常使用的系统调用接口。
Linux采用宏内核架构,由于其开放源码、系统调用接口兼容POSIX规范 ,发布以来即得以快速传播,与GNU项目相结合催生了优秀的GNU/Linux操作系统。
有关操作系统的基本原理,在第4章会详细讨论。有关Linux的源码结构、裁剪定制及编译和构建等问题,在第6章会详细讨论。
操作系统一般还提供必要的应用程序——系统应用,如编译器、公用函数库、编辑器、Shell等,以支持计算机系统的管理和维护,以及办公软件、娱乐软件、科学计算软件、工业软件和中间件等用户应用程序的开发和运行。
GNU项目基于POSIX开发了大量的应用程序,包括以上提及的必要的系统应用,正好可与莱纳斯·托瓦尔兹开发的Linux内核组成完整的操作系统,即GNU/Linux。这些软件非常优秀,如同一个个业务小能手,聚合在一起组成一个强大的软件团队,深受程序员的喜爱,也因此蓬勃发展。
编译器是开发新应用程序的必备工具。它通过非常复杂的编译过程,将抽象度较高的编程语言代码(源程序)转化成抽象度较低的机器语言代码(目标程序),为操作系统提供功能扩展能力。C语言因改写UNIX操作系统而发明,因此C语言编译器最初是为UNIX而生的,至今已广泛应用于各种操作系统。常见的C/C++编译器主要有GCC(GNU Compiler Collection,GNU编译器套件)、Microsoft Visual C++等。
GCC中的C/C++编译器一般是UNIX类操作系统默认安装的C/C++编译器。此外,GCC还包括Objective-C、Java、Ada和Go语言的编译器前端及这些语言的运行库。GCC最初是为GNU操作系统专门编写的一款编译器,是彻底的自由软件,现在被大多数UNIX类操作系统(如Linux、BSD、macOS等)采纳为标准的编译器,同样适用于Windows操作系统(借助MinGW)。
GCC是最受欢迎的GNU自由软件之一,它基于CLI使用,提供了大量的参数与选项,功能强大,支持性能优化。GCC通常与Makefile或CMake等工具相配合,可构建强大、灵活、可移植的软件构建方案,是开放源码领域使用非常广泛的编译器。
有关如何用GCC在Linux开发环境下进行C/C++开发,在第5章会详细讨论。
Microsoft Visual C++是微软公司发布的C/C++编译器,提供Windows API(Application Program Interface,应用程序接口)、3D图形DirectX API、.NET Framework等丰富的库函数支持。它以拥有语法高亮、IntelliSense(自动完成功能)及高级除错功能而著称,曾广泛应用于Windows系统各种系统服务和应用软件开发。
此外,新兴编译器也在不断涌现。
2007年,苹果公司开发了Clang编译器,在BSD开源协议下发布。Clang是Clang LLVM的一个编译器前端,它目前支持C/C++、Objective-C及Objective-C++等编程语言。Clang具有编译快速、占用内存少、诊断信息可读性强、易扩展、易于集成开发环境集成等优点。此外,还具有ARC(Automatic Reference Counting,自动引用计数)静态分析功能,能够自动分析程序的逻辑,在编译时就找出程序可能存在的bug。
2019年8月底,华为方舟编译器正式开源(OpenArkCompiler),其目标是构建一个基于MapleIR的跨语言编程环境,实现跨语言的全局分析及优化。目前,方舟编译器加入了对C语言程序编译的支持,当然继续开源也是实现Java和C混合编译的基础。未来,方舟编译器不仅会支持对来自Java语言的IR(Intermediate Representation,中间表示)代码进行JIT(Just-In-Time,即时)编译,也会支持对来自C、C++语言的IR代码进行JIT编译。
2021年4月,华为发布了GCC for openEuler。该编译器基于开源的GCC-10.3开发并进行了优化和改进,实现软硬件深度协同优化,在OpenMP、SVE(Scalable Vector Extension,可伸缩向量扩展)向量化、数学库等领域挖掘极致性能,是一种在Linux下适配鲲鹏920处理器的高性能编译器。GCC for openEuler默认使用场景为TaiShan服务器、鲲鹏920处理器、ARM架构,支持的操作系统为CentOS 7.6、openEuler 20.03或openEuler 22.03等。
公用函数库通常指的是C/C++运行库,由编译器将它与用户开发的应用程序链接在一起,实现功能封装和代码复用,为操作系统的功能扩展提供统一机制,极大降低软件开发和移植的复杂性。这些标准的运行库的实现方式主要是基于操作系统提供的系统调用接口、API(如POSIX),并将相关基础功能封装为简洁、一致的跨平台标准API,提高应用程序的开发效率和可移植性,这种实现方式是整个计算机软件体系的基础。常见的C/C++运行库有ANSI(American National Standards Institute,美国国家标准学会)的libc/libc++、GNU的glibc/libstdc++和微软公司的msvcrt等。
libc是符合ANSI C标准的一个公用函数库,包含C语言基本函数的运行库。这个库可根据头文件划分为15个部分,包括类型、错误码、浮点常数、数学常数、标准定义、标准I/O(Input/Output,输入输出)、工具函数、字符串操作、时间和日期、可变参数表、信号、非局部跳转、本地信息、程序断言等。
glibc是GNU发布的C运行库,是GNU/Linux操作系统中非常基础的API,服务于几乎其他所有应用程序,glibc架构如图2.3所示。glibc除了提供libc的全部功能外,还封装了信号量、进程间通信等基于POSIX的系统调用,几乎囊括所有的UNIX中通行的标准,可见其包罗万象。
glibc是GNU/Linux演进的一个重要里程碑,更是C语言编程爱好者的学习宝库。glibc按照GNU LGPL(GNU Lesser General Public License,GNU宽通用公共许可证)发布,源码全部开源,可免费下载和学习。例如,C语言库函数strcpy函数的实现是软件开发相关岗位的一道常见面试题,然而glibc给出了迥然不同的代码逻辑,其核心代码大致如下。
图2.3 glibc架构
char *strcpy(char *dest, const char *src) { char *s=src; const ptrdiff_t off=dest-s-1; do { s[off]=*s; } while (*s++!='\0'); return dest; }
思考:上述代码的完整实现要稍微复杂一些,另外针对不同CPU平台的实现可能有所不同。就上面这段核心代码来看,glibc实现的主要思路是什么?glibc中还有strlen等很多其他函数的实现,读者可自行阅读glibc相关源码。
编辑器是用于修改文本文件的工具,在不同的操作系统中的地位可能截然不同。在Windows操作系统中,编辑器似乎无足轻重,但在UNIX类操作系统中,编辑器却是使用极为频繁的重要工具。
在Windows等GUI操作系统的日常使用中,很少用到编辑器,特别是在当今以触摸屏作为主要输入接口、以娱乐为主的智能手机和智能平板操作系统中,已不再需要编辑器。相反,系统维护者通过使用不同的二进制工具修改系统配置,软件开发者也必须使用不同的编辑器,它们分布在各种不同的集成开发环境中,如MATLAB、Visual Studio等。这些集成开发环境大都使用不同的编辑器,开发者不得不适应不同的编辑习惯。
UNIX类操作系统迥然不同,编辑器是配置维护和软件开发的基本工具,由黑客程序员出于自己的需要而开发。它们设计优雅,或效率高、或功能强,从发布以来,不断扩展并沿用至今,已成为UNIX优秀文化的重要代表之一。它们富有创意的设计将文本编辑这一任务做到极致。用户只需要掌握极少数编辑器的使用方法,并选择按自己喜爱的方式操作,即可非常高效地编辑所有文本文件。这一切得益于UNIX操作系统广泛采用文本的优秀设计。
UNIX操作系统的设计哲学之一就是尽可能使用文本文件,而不是二进制文件。文本文件解析简单,任何时候都可用任何文本编辑器打开,而不会出现由于文件损坏或版本过低等兼容性问题无法读取的情况。在UNIX类操作系统中,所有的系统配置都使用文本文件,甚至HTTP(Hypertext Transfer Protocol,超文本传送协议)、SMTP(Simple Mail Transfer Protocol,简单邮件传送协议)、POPv3(Post Office Protocol Version 3,邮局协议第3版)、FTP(File Transfer Protocol,文件传送协议)等最初的许多互联网应用协议也都采用文本格式。在UNIX类操作系统中,使用文本文件,还可高效地完成制表绘图、编写技术报告/学术论文等许多图文结合的编辑任务。例如,编辑如下文本,即可用LaTeX工具生成包含公式甚至电路图的PDF文档,如图2.4所示。
\documentclass{article} \begin{document} \[ x(t)=\sum^{+\infty}_{k=-\infty}\alpha_k\cdot e^{jk(\frac{2\pi}{T})t}\] \newpage \begin{figure}[h!] \begin{circuitikz} \draw (0,0) to[V,v=$U_q$] (0,2) to[short] (2,2) to[R=$R_1$] (2,0) to[short] (0,0); \end{circuitikz} \end{figure} \end{document}
图2.4 LaTex用文本文件生成的公式和电路图
在UNIX文化的激励下,涌现出了一批优秀的编辑器软件,如ed、ex、sed、vi、vim、nano、Emacs等。它们提供了许多强大、灵活的功能,为现代编辑器提供设计参考。事实上,诸多现代编辑器,如Notepad++、UltraEdit、Sublime Text、Atom、jEdit,以及Visual Studio Code等,都在不断地从“前辈”身上借鉴思路并进行创新。相比之下,这些几十年前的优秀编辑器,依然是经典,其中最为流行的是Vi和Emacs,分别是两种不同编辑流派的杰出代表。
Vi是由加利福尼亚大学伯克利分校的研究生比尔·乔伊 于1976年发布的第一款全屏幕控制台,也是一款通用的文本编辑器,被誉为“编辑器之神”。在此之前,人们只能使用行编辑器,如肯·汤普森开发的ed及乔治·库鲁里斯(George Coulouris)开发的em 等。Vi的核心设计思想是让程序员的手指始终保持在键盘的核心区域,就能完成所有的编辑动作。它功能强大、效率极高,可编辑系统配置文件、各种编程语言文件,几乎可在任何UNIX类操作系统上运行。这个编辑器因其独特的使用方式,让无数程序员爱不释手,也让无数程序员从入门到放弃。
Vim(Vi IMproved)是基于Vi编辑器的改进版,是由布拉姆·穆莱纳尔(Bram Moolenaar) 开发的一款增强版的文本编辑器,具有更丰富的内置高级功能。它支持语法高亮、自动缩进、自动补全、模式匹配与替换、区块操作和无限次撤销等。Vim也支持用户自定义脚本和插件,可进行个性化定制和扩展。Vi系列编辑器最大的优点就是,在一个新的系统上无须配置即可立即使用,无须担心使用习惯不一致。Vim在管理员和程序员中广受欢迎,与Emacs一起成为深受UNIX类操作系统用户喜爱的文本编辑器。Vim多buffer编辑界面如图2.5所示。
图2.5 Vim多buffer编辑界面
Emacs在20世纪70年代诞生于麻省理工学院人工智能实验室(MIT AI Lab),是著名的文本编辑器和集成开发环境,被誉为“神的编辑器”。Emacs针对多种文档定义了不同的“主模式”(major mode),包括普通文本文件、所有编程语言的源文件、HTML(Hypertext Markup Language,超文本标记语言)文档、LaTeX文档,以及其他类型的文本文件。Emacs是一个易扩展、高度可定制的开源编辑器,具有许多优秀特性,如自定义模式、自定义快捷键、自定义宏操作、多种选择模式、多窗口、多书签、多剪贴板、选中区域起始点切换、矩形区块操作,以及支持显示图片、PDF文档等。
Emacs是具备高可移植性的重要软件之一,能够在当前大多数操作系统上运行,包括GNU/Linux、Windows、Android,以及iOS等,并且具有极其丰富的功能扩展,几乎可用于处理所有的文本编辑任务。然而,Emacs编辑器复杂的按键操作、极为陡峭的学习曲线,令新手望而生畏,但一旦熟练掌握使用方法,即可用它高效处理多种文本编辑任务,再也无法离开。Emacs多buffer编辑界面如图2.6所示。
注:左上为C++代码,左下为Shell界面,右上为Markdown文档,右下为心理医生程序和小游戏。
图2.6 Emacs多buffer编辑界面
此外,还有nano、JOE、Pico等众多优秀的开源编辑器,都属于非常简单的编辑器,常用于系统安装过程或嵌入式系统等资源受限场景,感兴趣的读者不妨试试看。
用户界面是指操作系统为用户提供的使用接口,便于用户使用操作系统提供的服务。在UNIX类操作系统中,用户界面一般是指Shell(外壳),即操作系统(内核)与用户之间的外部界面层。用户界面的任务是接收用户的键盘或鼠标输入,调用或“启动”另一个程序;此外,用户界面通常具有额外的功能,例如改变当前目录、查看目录内容、控制作业任务等。简单地说,用户界面是指启动其他程序的程序。
用户界面通常分为两类:CLI(Command Line Interface,命令行界面)和GUI(Graphical User Interface,图形用户界面)。CLI指文本模式下的用户界面,主要依据用户的命令行文本来进行交互,一般只使用键盘进行输入、使用文本信息作为输出,如UNIX类操作系统的各种Shell程序。GUI指图形模式下的用户界面,主要依据用户的图形指令进行交互,多使用鼠标等指针设备进行输入、使用图形内容作为输出,如Windows操作系统的Explorer程序,macOS中的Finder应用,UNIX类操作系统中的GNOME、KDE、Xfce等。
Windows系统的用户界面通常以GUI为主,用户用鼠标和键盘在被称为“文件资源管理器”的Explorer程序中与系统进行交互,操作简便,对使用者非常友好。对开发者来说,GUI的交互效率太低,且难以实现自动化。虽然Windows的命令解释程序cmd.exe提供了简单的CLI,但CLI功能非常有限。2006年微软公司发布的PowerShell可视为cmd.exe的升级版,基于.NET Framework开发,同时提供了命令解释和脚本编程的交互环境,包含大量的内部命令,可通过模块扩展功能,还可自动执行任务,例如用户管理、CI/CD(Continuous Integration/Continuous Deployment,持续集成/持续部署)、云资源管理等,功能较为强大,发布后迅速成为Windows系统的首选CLI。
UNIX类操作系统为开发者提供了非常友好的交互界面,其突出特点是学习曲线陡峭,熟练后使用极为灵活高效。UNIX类操作系统的用户界面以CLI为主,用户主要使用键盘在命令行终端中与系统交互,也可选择运行GUI。CLI具有非常友好的命令解释功能,以及脚本编程功能,可高效地进行软件开发工作,还支持批量任务的自动化。另外,虽然CLI是基于纯文本的,但人们也开发了许多高效、有趣的应用,例如基于文本的菜单系统、用字符绘图的工具软件(见图2.7)等,可在CLI下展示丰富多彩的内容。
图2.7 CLI中的ASCIIArt
CLI实际上是由一种称为“Shell”的应用程序在操作系统内核的基础上实现的。1977年,斯蒂芬·伯恩(Stephen Bourne)在贝尔实验室为V7 UNIX开发了Bourne Shell,其凭借简洁、快速的特点,一直被沿用至今,并成为UNIX类操作系统的默认Shell,简称为sh。sh在脚本语言引入了控制流、循环和变量,提供了更强大的语言与操作系统交互,还引入了一系列今天仍在使用的功能,包括管道、重定向与Here文档 等。sh在Shell脚本自动化的前进道路上迈出了至关重要的一步,成了其他派生Shell的基石。
今天的Bash(Bourne Again Shell)等流行的Shell更是具备了命令行编辑特性,极大地提升了操作系统的使用效率和交互体验。openEuler等GNU/Linux系统中默认的Shell是Bash,它与sh完全向后兼容。Bash有许多特色,可提供命令补全、命令编辑和命令历史表等功能,还包含很多csh和ksh具备的优点,有灵活和强大的编程接口,极大地提高了操作系统命令交互和脚本开发的效率。
在UNIX类操作系统中,无论是CLI还是GUI,都只是一种普通的应用程序,只不过执行着与系统交互的特殊任务。这种灵活的设计,一方面使系统更简单,另一方面给予了用户更多自由。用户可选择喜爱的用户界面,还可通过窗口管理器来非常灵活地定制个性化的用户界面,并且是否运行GUI也可由用户自己根据需要来决定。例如,FVWM是一个优秀的窗口管理器,几乎可将GUI定制成任何样式,FVWM自定义桌面如图2.8所示。
图2.8 FVWM自定义桌面
实际上,UNIX类操作系统中的GUI是基于X Window的桌面应用程序。X Window在1984年由麻省理工学院研发,它的设计哲学的原则之一是提供机制,而非策略。X Window系统将GUI的内容和图形界面的显示分离开,分别在被称为X Client和X Server的两种不同的应用程序中实现。有趣的是,X Window系统中客户端、服务器概念与Windows远程桌面的客户端、服务器刚好相反。在X Window中,产生图形输出内容的设备称为X Client,而具有显示器、键盘、鼠标等I/O设备称为X Server。X Client通过X协议与X Server进行通信,它们可分布于不同的主机。这种GUI体系极具灵活性,即使远程的UNIX主机没有连接显示器和键盘的服务器,依然可在其他计算机的X Server中与它进行GUI交互。
X11R6是曾被广泛采用的经典X协议版本,X Server在Linux上的主要实现是XFree86和Xorg。X Server也可运行在Windows系统上,因此Linux中的X Client图形界面也可显示在Windows桌面中。上述连接的设置也非常简单,通过SSH(Secure Shell,安全外壳)工具中的转发机制即可实现,在第7章中介绍了相关内容。
正因为以上特点,很多UNIX用户认为UNIX具有非常友好的交互界面,可定制程度高、使用效率高,可极好地满足个性化需要。