庖丁解牛Linux操作系统分析

978-7-115-61973-0
作者: 孟宁娄嘉鹏
译者:
编辑: 李瑾

图书目录:

详情

本书将可移植操作系统 POSIX 标准和 CPU 指令集架构 ISA 两层接口通过 Linux 操作系统贯通起来,涵盖了 Linux 操作系统的各个主要方面,主要有以 openEuler 操作系统为例的 POSIX 工具集、计算机系统的工作原理、x86 和 ARM64 汇编语言、系统调用的工作机制、进程描述和内存管理、可执行程序工作原理、内核线程和 I/O 驱动框架、进程调度和进程切换、KVM 和容器技术、Linux 安全相关技术等 Linux 系 统运作的各个关键机制。 本书首先以 Linux 社区规则、Linux 发展的渊源、Linux 基本使用和命令工具作为导引;然后以存储程序计算机相关的工作原理、x86 和 ARM64 汇编语言、指令乱序问题、mykernel 精简内核实验以及 Linux内核源代码编译和系统构建作为 Linux 内核的入门基础;接着焦深入理解系统调用,并在 x86 和 ARM64系统调用实现的基础上延伸到进程的创建、可执行程序的加载和进程的切换,其中涉及了进程描述符、进程地址空间和程序编译构建等相关的内容;最后总结了 Linux 系统的一般执行过程和系统架构,并拓展到KVM 和容器技术,以及 Linux 系统安全相关技术。

图书摘要

版权信息

书名:庖丁解牛Linux操作系统分析

ISBN:978-7-115-61973-0

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。


版  权

编  著 孟 宁 娄嘉鹏

责任编辑 李 瑾

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315

内 容 提 要

本书将可移植操作系统POSIX标准和CPU指令集架构ISA两层接口通过Linux操作系统贯通起来,涵盖了Linux操作系统的各个主要方面,主要有以openEuler操作系统为例的POSIX工具集、计算机系统的工作原理、x86和ARM64汇编语言、系统调用的工作机制、进程描述和内存管理、可执行程序工作原理、内核线程和I/O驱动框架、进程调度和进程切换、KVM和容器技术、Linux安全相关技术等Linux系统运作的各个关键机制。

本书首先以Linux社区规则、Linux发展的渊源、Linux基本使用和命令工具作为导引;然后以存储程序计算机相关的工作原理、x86和ARM64汇编语言、指令乱序问题、mykernel精简内核实验以及Linux内核源代码编译和系统构建作为Linux内核的入门基础;接着聚焦深入理解系统调用,并在x86和ARM64系统调用实现的基础上延伸到进程的创建、可执行程序的加载和进程的切换,其中涉及了进程描述符、进程地址空间和程序编译构建等相关的内容;最后总结了Linux系统的一般执行过程和系统架构,并拓展到KVM和容器技术,以及Linux系统安全相关技术。

本书适合作为高等院校计算机、软件工程专业高年级本科生和研究生的教材,同时可供计算机软件相关从业人员学习参考。

不小心在Linux的世界畅游了多年,依稀记得刚进入这个世界时,迷失在代码的海洋里的那份恐慌和无知,更庆幸那时的无知无畏。如今Linux社区特别繁荣,很多中国人加入Linux社区贡献者的行列,国内也出现了不少像openEuler一样出色的Linux发行版。

我们分析研究Linux源代码的目的是什么,相信每一位读者都有自己的答案。如今Linux被广泛应用在数十亿台设备和各种各样的场景中,我相信很多读者不仅享受畅游在Linux世界里的乐趣,还有学习和工作中各自不同的、或直接或间接的学习动机。

本书为Linux的学习者提供了理论和实践相结合的学习方式,从自由软件和操作系统的发展、Linux基本使用方法等概览性的内容切入,带领读者开启模型驱动、代码梳理和调试验证三位一体的学习旅程。

所谓模型驱动是从第一性原理出发,从理解计算机系统基本工作原理——存储程序计算机开始,深入关键环节和特殊机制,逐渐丰富读者心里的那个Linux操作系统运作的模型,使之越来越精确,越来越具体。

在模型驱动的过程中,本书始终坚持代码梳理和调试验证相结合的实践出真知的原则,比如将函数调用堆栈[1]与C语言代码的x86和ARM64汇编语言代码的执行过程分析结合起来;比如系统调用机制分别用系统库函数调用和汇编语言代码触发系统调用,并与系统调用内核代码分析结合起来;比如对fork和execve系统调用中的特殊代码进行剖析时,对Linux操作系统运作模型中的关键点做了精准的刻画。对于进程切换的关键汇编语言代码分析等这些循序渐进、独具匠心的编排设计就不在此“剧透”了。

[1] 一般来说,堆和栈是两个概念,我们这里的“函数调用堆栈”是指堆叠起来的函数调用栈,除非分开使用堆和栈,本书用到的“堆栈”一词可以理解为栈,或者堆叠起来的栈空间。

本书不仅对操作系统核心工作机制的Linux实现的代码进行了验证,还进一步将这些纳入KVM技术、Linux容器技术和Linux安全技术的理解和应用之中。我们知道虚拟化技术是云计算技术的基础,如今更是被应用到了边缘计算和终端设备中,未来的操作系统设计者需要深刻理解虚拟化技术,使得操作系统能够更好地适应云、管(边)、端等不同场景下的需求,而不管是在哪种场景下安全问题都是操作系统设计中不可忽视的关键问题。这些也都是实现未来新一代操作系统技术创新所必备的知识。

相信本书能让你收获颇丰,简述以上,是以为序。

陈莉君 教授

中国开源软件推进联盟“开源杰出贡献人物奖”获得者

前  言

当您拿起这本书的时候,相信您和我一样对探究Linux操作系统内部的运作机制抱有浓厚的兴趣。正是因为这浓厚的兴趣,我在2009年主动申请担任陈香兰老师主讲的Linux操作系统分析课程的助教,2012年开始和李春杰老师合作主讲Linux操作系统分析课程。随着对Linux内核理解的深入,2013年,我在Linux内核代码的基础上开发了mykernel实验平台,相对于从硬件平台或虚拟机入手编写操作系统内核,mykernel提供了与CPU指令集架构基本无关的极简虚拟机和短小精悍的模拟内核,巧妙地规避了CPU初始化过程中晦涩、繁杂的汇编语言代码。因此mykernel得到了诸多内核学习者的良好反馈,也产生了诸如kernel-in-kernel一类的衍生项目。

正是由于mykernel使用者和中国科学技术大学软件学院同学们的支持和鼓励,在孙志岗老师的指导下,“Linux内核分析”慕课课程有幸在2015年入选网易云课堂“顶尖中文大学计算机专业课程体系”;随后我和娄嘉鹏、刘宇栋两位老师一起合作出版了《庖丁解牛Linux内核分析》一书;线上课程“Linux操作系统分析”获得了教育部“国家精品在线开放课程”和“国家级一流本科课程”认定,还被中国高校计算机教育MOOC联盟评为“优秀课程”。其实我自己心里清楚这一连串荣誉名不副实,我并没有真正揭开Linux内核运作的机制,或者说对很多地方知其然不知其所以然。几年来我一直不断扩展教学内容,比如从32位x86扩展到64位x86,还扩展到了I/O和网络协议栈等,但是对Linux内核的运行机制和源代码的设计结构背后隐藏的奥秘还不够清晰,不够融会贯通。

2021年暑假,在华为公司张相锋、伍伯东两位工程师协助下,我将课程内容扩展到了ARM64指令集架构,通过与x86架构的对比,我开始理解Linux内核源代码兼容不同CPU指令集架构的软件设计,走出了x86架构的思维惯性。打通CPU指令集架构这一底层接口之后,再结合POSIX标准中的工具集、标准库及系统调用这些上层接口,我深入理解了Linux操作系统的进程地址空间和进程切换等关键技术。从这里我体会到了老子《道德经》中“万物并作,吾以观复”的玄妙。为了分享这种洞悉玄妙的愉悦,暑假结束前我就以“ARM64 Linux核心精讲”为题在网易云课堂上发布了视频,本书的撰写也是出于同样的目的。

本书是中国科学技术大学软件学院多年来Linux操作系统分析课程的教学总结,践行以代码为中心,理论和实践相结合,以系统化的视角深入分析Linux系统运转的结构特征和关键环节,辅助系统软件开发者将理论和实际在头脑中进行融合。“Linux内核分析”慕课课程从2015年在网易云课堂“顶尖中文大学计算机专业课程体系”中上线以来,据不完全统计,在多个平台累计选课不少于10万人次,课程还得到不少兄弟高校和软件企业的采纳。从学员反馈看,在mykernel实验平台及Linux操作系统构建和调试中学习,代码执行的一般模型和特殊环节都能逐一得到深入理解,有效提高了学习者对计算机系统的理解水平。

在华为智能基座项目的支持下,进一步结合工业界系统软件人才能力的需求,本书和相关课程得以升级和拓展。如果能在此基础上形成与Linux操作系统相关的课程群,加强全国兄弟高校的教研合作和师资培训,并有组织地在全国推广我们总结出的有益做法和课程资源,就一定能为提高系统软件人才培养质量和提供国家科技战略需求人才贡献力量。

我们在Linux操作系统教学中一直秉持开放共享的开源理念,从慕课课程建立之初就通过网易云课堂和学堂在线等在线教育平台免费向社会开放教学视频,通过GitHub/Gitee免费向社会共享源代码和实验环境,而且很多教学资源都是教研室师生独创的,也因此得到兄弟高校任课教师和学员的广泛好评。相信在同行和学员的支持和帮助下,我们能够进一步推出全网独一无二的教学资源,并在此基础上创新教研形态,加强教学研究和师资培训,凝练出系统软件人才培养的新方法、新思路和新范式。

致谢

在本书编写过程中得到众多单位和个人的支持与帮助。

首先感谢我的妻子,她独自承担了家中一切事务,才使得我有精力完成本书的写作,某种意义上她是本书得以出版的第一关键。

感谢华为公司的孙海峰、罗静和严伟等业界专家对于本书的编写给予的大力支持和帮助。

感谢伍伯东、张相锋、刘昊、卢景晓等华为技术专家在本书编写过程中给予的具体指导。

本书受到2021年度中国科学技术大学研究生教育创新计划项目优秀教材出版项目资助,感谢学校对教材出版工作的支持。

感谢人民邮电出版社陈冀康、李瑾和出版编辑团队的辛苦努力,使得本书得以出版。

感谢我的同事陈香兰老师,我是从做她的助教开始学习Linux内核的;感谢西安邮电大学陈莉君老师和国防科技大学罗宇老师等前辈,我是从他们的著作中不断学习进步的;感谢中科院计算所张福新老师审阅了书稿并给出了具体修改意见;感谢李春杰、娄嘉鹏和刘宇栋老师,围绕Linux操作系统我们曾共同学习、一起工作。

在本书编写过程中做出贡献的有:娄嘉鹏老师负责了第12章的编写,其他章节的早期版本娄老师亦有所贡献;李春杰老师对全书内容的编写给予了指导并完整审阅了书稿;艾平、严炼、刘志涛、刘子洋、刘润晗、卢宇、马一飞等同学辅助了本书部分内容的编写和个别实验的验证。在此对以上老师和同学,以及所有直接或间接为本书做出贡献的朋友致以诚挚的谢意。

由于编者水平有限,书中不当之处在所难免,欢迎广大同行和读者批评指正。

孟 宁

2023年5月

资源与支持

资源获取

本书提供如下资源:

本书源代码及教学PPT;

本书思维导图

异步社区7天VIP会员

要获得以上资源,您可以扫描下方二维码,根据指引领取。

提交勘误信息

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区(https://www.epubit.com),按书名搜索,进入本书页面,点击“发表勘误”,输入错误信息,点击“提交勘误”按钮即可(见下图)。本书的作者和编辑会对您提交的错误信息进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

与我们联系

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们。

如果您所在的学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

关于异步社区和异步图书

“异步社区”(www.epubit.com)是由人民邮电出版社创办的IT专业图书社区,于2015年8月上线运营,致力于优质内容的出版和分享,为读者提供高品质的学习内容,为作译者提供专业的出版服务,实现作者与读者在线交流互动,以及传统出版与数字出版的融合发展。

“异步图书”是异步社区策划出版的精品IT图书的品牌,依托于人民邮电出版社在计算机图书领域30余年的发展与积淀。异步图书面向IT行业以及各行业使用IT的用户。

第1章 Linux操作系统概览

本章以Linux操作系统为中心,讨论了开源软件社区和操作系统的发展历史等相关背景,同时介绍了Linux操作系统的安装和使用的基本方法,最后以openEuler操作系统为例分类整理了POSIX标准中的相关命令工具。

1.1 自由软件江湖里的“码头”和规矩

1.1.1 自由软件世界的“擎天大柱”Linux

Linux这个名字来源于它的最初作者Linus Torvalds的名字Linus,Linux最后一个字母x来源于UNIX,Linux是Linus和UNIX的一个合成词。

Linus Torvalds在1991年就读于赫尔辛基大学期间发布了第一个版本的Linux。如今Linux已经成为众多领域的支柱软件,人们已经越来越离不开它。在Linux操作系统的支撑下,技术的变革速度超出了人们的想象,与Linux操作系统有关联的应用程序的增长速度也以指数规模增长。因此越来越多的开发者不断地加入开源社区和学习Linux开发的潮流当中。

大约在1970年,Dennis Ritchie和Ken Thompson在贝尔实验室发明了UNIX操作系统。如今的Windows、Linux、macOS,以及Android和iOS等主流操作系统,也深受UNIX的影响,或直接继承或克隆了UNIX。Linux是UNIX操作系统的一个克隆版本,所以Linux是一个类UNIX(UNIX-like)的操作系统,即Linux几乎完全复制了UNIX操作系统的思想和做法,但是所有的源代码都是重新编写的。

1.1.2 江湖的由来——自由软件运动

为什么需要重新克隆一个类UNIX的操作系统Linux呢?因为UNIX涉及商业版权纠纷,这限制了它的发展,而Linux从一开始就是开源且免费的,使用的是GPLv2的许可证。

自由软件运动的带头人Richard M. Stallman和他创始的自由软件基金会对UNIX的商业版权深恶痛绝,以至于自由软件基金会资助的GNU操作系统的名字GNU就是“GNU’s Not UNIX!”的缩写,有趣的是这还是一个递归缩写。

GNU操作系统创建于1984年,比Linux还要早7年,它有自己的内核,称为Hurd,但是并不像Linux内核那么流行。目前使用的大多数Linux发行版主要是基于Linux内核+GNU的基础库及应用软件打造的,严格来说Linux仅仅指Linux内核;而Linux系统中内核之外的基础库及应用软件大多数是自由软件基金会支持的项目。

在自由软件的世界里,如果说老大是Richard M. Stallman,老二就是Linus Torvalds了。

世界充满了斗争,尤其是老大和老二的斗争更是激烈。自由软件世界里的老大和老二也有争执,集中体现在对软件许可证GPLv3的不同意见上。

1.1.3 江湖的规矩——开源软件许可证

开源软件许可证有很多,比如Linux内核采用的GPLv2许可证、Android采用的Apache 2.0许可证等,它们之间的区别如图1-1所示。

图1-1 开源许可证的区别

常见的许可证有LGPL许可证、Mazilla许可证、GPLv2许可证、GPLv3许可证、BSD许可证、MIT许可证、Apache 2.0许可证和木兰宽松许可证第2版(MulanPSL-2.0)。

修改后是否可以闭源?如果可以闭源,说明这个许可证对于商业使用比较友好,比如Android App使用了Android的框架和API,但是由于Android采用的是Apache 2.0许可证,所以如果你开发一个Android App是可以闭源作为商业秘密的。

修改后是否采用相同的许可证?如果回答“是”,则必须采用相同的许可证,意味着基于GPL许可证的源代码新增的源代码也必须使用GPL许可证来发布,这有点像病毒感染,只要接触了GPL的代码,所有代码必须遵守GPL许可证进行开源,显然GPL许可证对商业不友好。但是从另一个角度来看,只要自由软件遵守GPL许可证,每个人就都有自由获取、修改、使用和发布它的权力。

商业软件的编写是带有营利性质的,但还是存在一些理想主义者。

很多开源软件是自由软件和商业软件的折中,借助商业世界里的资源来推动软件源代码的共享,其中以Apache 2.0许可证和木兰宽松许可证第2版(MulanPSL-2.0)最具代表性,它们都包含从GPLv3开始引入的专利许可,尤其值得一提的是,MulanPSL-2.0是以中文为主的中英双语许可证。这类商业友好的开源软件取得了巨大成功。Linus Torvalds及Linux基金会拒绝将Linux内核源代码的许可证升级到GPLv3,大概就因为持有这种商业友好的折中观点。

1.1.4 江湖的危局——GPLv2和GPLv3

GPLv2许可证只能解决版权问题,不能解决专利问题。即对于软件源代码来讲,它只包含版权,这是Linux克隆UNIX而不侵权的原因。

源代码背后的技术方法可能被申请了发明专利,而发明专利保护的是技术方法本身,不管具体用什么样的代码实现。这就会给自由软件带来拥有版权而专利侵权的问题。微软就曾借助这一点宣称要起诉Linux系统的用户侵犯了Windows的专利,造成自由软件世界的重大危机。怎么应对这一重大危机呢?Richard M.Stallman通过升级GPLv2到GPLv3成功化解了它。

GPLv3解决专利问题的重要思路是沉淀在互联网上的绝大多数知识产权属于自由软件,如果持有隐性专利的组织或个人要状告自由软件使用者专利侵权,那么后者也有可能反告前者在互联网上对自由软件的侵权,从而达到权利公平、法律平衡的制约效果。

尽管GPLv3成功化解了自由软件世界的危局,但并没有得到广泛的采纳,因为GPLv3不如GPLv2商业友好,杜绝了商业上使用的法律空间。而Linus Torvalds认为商业版权并不坏,还会有助于改进软件的安全性。

这里举个例子,Android是基于Linux内核开发的手机操作系统,Linux内核使用的是GPLv2许可证,且Linus Torvalds在Linux内核源代码的版权文件中特别说明,版权不包含通过正常系统调用来使用Linux内核服务的用户程序。

The Linux Kernel is provided under:
 
    SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
 
Being under the terms of the GNU General Public License version 2 only,
according with:
 
    LICENSES/preferred/GPL-2.0
 
With an explicit syscall exception, as stated at:
 
    LICENSES/exceptions/Linux-syscall-note

Android框架却是使用Apache 2.0许可证发布的,因为在Linux内核和Android框架之间做了隔离,并没有使用GNU的自由软件,比如glibc等,使得GNU/Linux自由软件并不能感染上层代码,如果Linux内核升级到GPLv3,GPLv3不允许修改许可证,那么这种隔离措施将是无效的。

简要总结一下,LGPL允许以动态链接的方式使用而不感染代码,GPLv2可以通过进程间通信的方式使用而不感染代码,GPLv3在法律上杜绝了商业软件合法调用自由软件代码的模糊空间。

需要特别说明的是,这部分涉及法律文本的理解,其中的很多问题连知识产权律师和法官都无法清晰界定,作为法律的外行,我提供的只是个人的理解,仅供参考,如有理解有误的地方欢迎批评指正。

1.2 操作系统成长记

1.2.1 操作系统诞生的背景

为了深入理解操作系统,不仅需要学习操作系统原理,还需要从操作系统成长的过程说起。图1-2简要梳理了操作系统的成长过程。

图1-2 操作系统的成长过程

最初并没有操作系统。IBM 701开放式计算站(open shop)是由使用者手动操作的,工作效率很低。为了有效利用IBM 701,每个用户被分配了最低15分钟的计算时间。在这15分钟里,使用者通常要为配置设备花掉10分钟,等准备工作完成后,经常最多只有5分钟来做实际的计算工作,大约浪费了2/3的时间。每个月因浪费计算时间造成的损失高达14.6万美元。

为了减少计算时间的浪费并使开发人员从计算机房中解放出来,开发人员被要求在穿孔卡上准备程序和数据,然后提交到计算中心去执行。开放式计算站由此变成了封闭式计算站(closed shop)。最初的操作系统可以认为是封闭式计算站里的操作员。

1.2.2 早期的软件操作系统

早期的软件操作系统只是一些API函数库,并且大多重要的工作还是由操作员来完成。开发人员在编写每个程序时都需要处理一些共同的事情,比如系统初始化和I/O相关的底层代码等,自然就总结出一些常用的库函数以便直接调用,这些API函数就是软件操作系统的源头。如果早期操作系统只是一些API函数,那么当时操作系统最核心的功能(即任务管理)是怎么做的呢?

这些大型机通常由操作员来管理控制,一次运行一个程序。以IBM 7094的工作流程(如图1-3所示)为例,首先操作员从开发人员那里收集一组组穿孔卡片,使用卫星计算机1401把卡片上的作业输入磁带;然后操作员将这个磁带安装到一个与主计算机7094相连接的磁带机上,这便是输入(Input);其后主计算机按照程序在磁带中出现的顺序,每次运行一个,并将数据输出到另一个磁带上;最后输出数据的磁带被操作员移动到卫星计算机1401中,并通过行式打印机打印出来,这便是输出(Output)。

图1-3 IBM 7094工作流程示意图

当主计算机7094执行程序的时候,两台卫星计算机同时做着输入和输出的工作。操作员完成了现代操作系统需要做的主要工作,比如决定程序运行的顺序,也就是任务调度。如果想让你的程序得到优先执行,就得通过操作员想办法插队。

这种计算模式就是批处理(batch processing)系统,IBM 7094所使用的共享操作系统(shared operating system)是早期的批处理系统。一般批处理系统先做好准备工作,然后由操作员以分批的方式来执行程序。这时的计算机还没有以交互方式使用,因为早期的计算机非常昂贵,要有效利用计算机机时,而批处理方式可以最大限度地充分利用计算机机时。

1.2.3 系统调用的概念

系统调用(system call)概念的诞生是操作系统发展的一个重要里程碑。Atlas Supervisor 是第一个提出现代操作系统诸多概念的系统,它率先采用了系统调用的工作机制。

系统调用和API函数的过程调用(procedure call)有何不同呢?系统调用的工作机制将程序的执行权限进行了划分,就像Linux系统分为用户态和内核态,系统调用将用户程序和操作系统内核进行了隔离,通过添加一些特殊的硬件指令和硬件状态让用户程序进入操作系统的过程更加正式、可控,这样有效保护了操作系统提供的底层代码,使得整个系统更加稳定,不会让用户程序的错误造成整个系统的崩溃,而普通的API函数过程调用没有任何保护机制。

系统调用和函数过程调用之间的关键区别:系统调用将执行操作跳转到操作系统内核中,同时提高硬件执行指令的特权级别(进入内核态),用户程序在用户态运行,这意味着硬件限制了用户程序的功能。在用户态运行的用户程序通常不能直接发起I/O请求,不能直接访问任何物理内存或向网络上发送数据包。用户程序只能通过系统调用来访问底层硬件提供的功能,通常通过一个称为陷阱(trap)的特殊指令,比如32位x86指令集中的int $0x80,硬件将控制权跳转到预先指定的陷阱处理程序,即系统调用处理入口,同时将特权级别提升到内核态,在内核态,操作系统具有访问系统硬件的权限。当操作系统完成系统调用请求的服务时,会通过特殊的陷阱返回指令(比如x86指令集中的iret指令)将控制权交还给用户程序,返回到用户态,回到用户程序离开的地方继续执行。

1.2.4 多道程序操作系统

多道程序(multiprogramming)从根本上改变了操作系统。在大型机时代之后,进入小型机(minicomputer)时代,像数字设备公司(DEC)的PDP系列就是小型机的经典,这时计算机的成本更低,同时硬件功能也日趋强大。这些变化同时也使处理器开始能够支持程序的并发执行和控制。中断技术能够使一个处理器控制多道程序的并发执行,这时多道程序操作系统开始出现。

多道程序操作系统不是一次只运行一个程序,而是将大量程序加载到内存中并在它们之间快速切换,从而提高CPU的利用率。这种切换是非常重要的,因为I/O访问速度很慢,在I/O访问过程中CPU空闲。CPU空闲的时候为什么不切换执行其他用户程序呢?通过快速切换不同的用户程序,CPU的利用率可以得到极大提高。

1.2.5 笼罩在UNIX上的阴影

小型机时代最成功的操作系统当属UNIX操作系统,它汇集了众多操作系统的思想,尤其是继承了Multics系统的诸多思想。

1969年,Dennis Ritchie和Ken Thompson在贝尔实验室开始尝试寻找Multics的替代品。1971年年底,他们开发的UNIX系统能够在PDP-11小型机上支持3位用户。然而,直到1973年UNIX内核用C语言重写之后人们才开始知道它的存在,1974年之前根本没有出版过任何关于UNIX的文献。随着计算机的小型化,小型机(尤其是PDP-11)催生了一批新用户,他们对现存的操作系统软件感到失望,这群人更倾向于UNIX系统。UNIX操作系统提供的功能都不是全新的,而是使已有的操作系统的功能使用起来更简便。UNIX系统提供了C语言编译器,开发人员很容易编写自己的程序并分享源代码,这使得UNIX非常受欢迎。UNIX成为开放源代码软件的早期形式,很重要的原因是作者向所有请求的人免费提供源代码。源代码的可读性也非常重要,UNIX是用C语言编写的内核,比较容易理解,因此比较容易吸引其他人为UNIX添加新的、很酷的功能。到了20世纪80年代中期,UNIX已经成为操作系统中领先的、事实上的行业标准,并诞生了诸多UNIX版本,比如由加利福尼亚大学伯克利分校团队发布的BSD版本,还有Sun公司的Solaris、IBM公司的AIX、HP公司的HP-UX等。

至此操作系统的内在成长告一段落,它从无到有,从弱到强,直到UNIX操作系统诞生并统治小型机时代。操作系统从幼年到成年的过程不仅经历了硬件环境的不断升级,还经历了诸多新思想、新方法的洗礼,日趋成熟、稳定和强大。

随着UNIX带来的市场蛋糕越来越大,AT&T及贝尔实验室与这些不同的UNIX厂商产生了有关UNIX版权的法律纠纷,导致UNIX的传播速度有所放缓,从而使很多人怀疑UNIX能否存活下去,这成为笼罩在UNIX上的阴影。

另外,操作系统的发展在个人计算机兴起的初期出现了一次大的倒退。

1.2.6 早期个人计算机操作系统的大倒退

在UNIX笼罩阴影的同时,出现了一种称为个人计算机(personal computer,PC)的机器,相对于小型机来说,这种机器价格便宜且性能不错,非常适合大众。在苹果公司的Apple Ⅱ和IBM PC的引领下,这种可以放到桌面上的个人计算机很快占领了主流市场。由于早期的PC操作系统开发人员忘记了或者从未知道小型机时代的经验教训,导致PC操作系统在发展过程中出现了诸多痛苦的经历。早期的PC操作系统,比如微软的DOS,由于没有重视内存保护,造成恶意或非恶意的用户程序能够完全访问整个内存;再比如第一代macOS的进程调度策略,常常意外陷入无限循环占用整个系统导致重新启动。

PC操作系统经历过一段时间的痛苦历程后很快就重新站在了前辈操作系统打下的地基上。微软的Windows操作系统采用了操作系统历史上的伟大思想,特别是从Windows NT开始,微软在操作系统技术上有了巨大的飞跃。乔布斯重新回到苹果公司时带着基于UNIX的NEXTSTEP操作系统,并以此为基础打造了macOS X,使得UNIX在台式计算机和笔记本计算机上非常流行。

1.2.7 移动互联网和AIoT时代的操作系统

移动互联网时代,手机操作系统没有走弯路,而是直接站在了UNIX之上。手机操作系统的市场被谷歌公司的Android操作系统和苹果公司的iOS操作系统垄断了。Android是基于Linux内核的手机操作系统,而Linux是UNIX的克隆版本;iOS操作系统的源头更是可以直接追溯到UNIX系统。至此操作系统发展到了巅峰。

然而伴随着万物互联、车联网、5G、AI等新应用和新技术的出现,操作系统似乎开始进入“中年危机”,在应对更加复杂的分布式计算环境时显得力不从心。因此在单机操作系统的基础上,逐渐发展出了云端分布式操作系统和终端分布式操作系统,操作系统注定将重新开启新的生命历程。

云端分布式操作系统是以虚拟化和容器技术为基础的,比较有代表性的就是openEuler操作系统引入的StratoVirt技术和iSula通用容器引擎。StratoVirt是计算产业中面向云数据中心的企业级虚拟化平台,实现了一套架构支持虚拟机、容器、Serverless三种场景。相比Docker,iSula 通用容器引擎是一种新的容器解决方案,提供统一的架构设计来满足CT和IT领域的不同需求。相比Golang编写的Docker,轻量级容器使用C/C++实现,具有轻、灵、巧、快的特点,不受硬件规格和架构的限制,底噪开销更小,可应用领域更为广泛。此外,还出现了Hadoop、Spark、OpenStack和Kubernetes等不同用途的云端分布式操作系统。

终端分布式操作系统以华为鸿蒙系统最具代表性。鸿蒙系统引入了分布式软总线技术。此技术旨在提供通信相关的能力,包括WLAN服务能力、蓝牙服务能力等网络服务能力,以及组网、传输和进程间通信等能力,为多终端的统一管理和调度建立基础通信环境。

在终端和云端之间还有边缘计算,未来的操作系统将面临多终端、边缘计算和云计算等多种复杂环境的挑战。

1.3 国产操作系统概述

1.3.1 国产操作系统的发展历程

操作系统在软硬件生态中扮演着承上启下的作用,具有衔接下层物理设备及资源和上层软件应用及服务的作用。因此操作系统国产化是自主可控软硬件生态的根本保障。

国产操作系统的研发可以追溯到20世纪70年代,杨芙清院士于1973年率领软件课题组设计出了150机整套操作系统软件,运行在我国第一台百万次集成电路计算机上,打破了西方的封锁。1981 年,她又主持完成了我国第一个用高级语言编写的大型机操作系统——240机操作系统。杨芙清院士是中国在软件开发领域做出显著贡献的第一个程序员。

20世纪80年代末,我国探讨并提出打造自主通用操作系统,后经过专家研究论证,制定了以跟踪Unix操作系统为技术路线的计划,并在“八五”攻关计划中正式立项。20世纪90年代初,我国正式推出COSIX 1.0操作系统,随后正式提出包括中文、微内核和系统安全等特色功能的COSIX 2.0操作系统。当时国产操作系统以学习UNIX等国外先进操作系统的相关概念和进行实验性的设计为主。

COSIX操作系统项目出色地完成了国产操作系统实验性的设计工作及人才队伍建设,而UNIX操作系统因为版权问题陷入商业纠纷,为其发展前景蒙上了阴影。20世纪90年代,随着自由软件运动一起发展的Linux操作系统发展势头迅猛,它通过克隆UNIX操作系统解决了版权问题,并迅速占领了操作系统的技术高地。2000年前后,基于Linux的国产操作系统迅猛发展,诞生了一批国产Linux操作系统产品,比如中软Linux(COSIX Linux)、红旗Linux和蓝点Linux(BluePoint Linux)等。这一阶段,国产操作系统确立了以Linux内核为基础的技术路线,产品脱离实验阶段,步入实用化阶段。

经历了Linux系统发展热潮之后,受到微软Windows操作系统的冲击,也受国内外经济和政策环境的影响,国产操作系统发展势头趋缓,一些缺乏商业化运营支撑的国产操作系统产品逐步被市场淘汰。然而经过行业洗牌,个别技术和运营都较为扎实的国产操作系统还是得到了一些市场机会,比如麒麟软件旗下的麒麟操作系统、深度科技旗下的Deepin操作系统、统信软件旗下的统信UOS等都发展势头良好,这表明国产操作系统正走向成熟,逐步成为真正可用的产品。

2018年以来发生的美国制裁中兴事件、芯片断供和对华为等中国高科技公司的各种打压,再次揭示了国产化软硬件生态的重要性,掀起了国产操作系统发展的新一轮热潮,其中最具代表性的就是华为鸿蒙系统(HarmonyOS)和欧拉系统(openEuler)。经过多年的发展,国产操作系统已取得长足进步,从“能用”走向“好用”,也从追赶走向有了一点引领操作系统发展方向的苗头。

国产操作系统积极因应外部国际环境和借助国家国产化的政策导向,向市场化发起了新一轮的冲击,相信不久的将来国产操作系统及其承上启下的软硬件生态将在以国内大循环为主体、国内国际双循环相互促进的新发展格局中大放异彩。

国产Linux操作系统发展历程如图1-4所示。国产Linux操作系统始于20世纪90年代,大多数基于Fedora、CentOS、Debian/Ubuntu等国外Linux发行版进行二次开发,直到2019年,华为公司开源了openEuler操作系统,它是自主演进的根操作系统,不是基于其他任何Linux发行版的二次开发,这是它与其他国产Linux操作系统的主要差异。

图1-4 国产Linux操作系统发展历程

1.3.2 openEuler操作系统

openEuler是一款开源操作系统,它的前身是华为公司发展近10年的服务器操作系统EulerOS,2019年开源,更名为openEuler。

当前openEuler内核源于Linux,支持鲲鹏及其他多种处理器,能够充分释放计算芯片的潜能,是由全球开源贡献者构建的高效、稳定、安全的开源操作系统,适用于数据库、大数据、云计算、人工智能等应用场景。同时,openEuler是一个面向全球的操作系统开源社区,通过社区合作,打造创新平台,构建支持多处理器架构、统一和开放的操作系统,推动软硬件应用生态繁荣发展。

openEuler以年月为版本号,以便用户了解版本发布时间,例如openEuler 22.03表示发布时间为2022年3月。

openEuler 22.03基于Linux Kernel 5.10内核构建,在进程调度、内存管理等方面有10余处创新,包括深度优化调度、I/O、内存管理,提供ARM64、x86、RISC-V等更多算力支持;新增启动选项preempt=none/voluntary/full,允许内核动态切换抢占模式;通过移动PMD/PUD级别的表项,加速映射大块内存的速度;采用per memcg lru_lock,减少云原生容器实例锁竞争,提升系统性能;通过共享映射方式将HugeTLB管理页中无实际作用的tail页释放掉,从而降低管理结构的开销,降低大页管理自身内存占用;本地TLB和远端TLB刷新并行,优化TLB shootdown流程,加速TLB刷新,提升业务性能;对于超过基本内存页面大小的空间进行vmalloc()分配时,会尝试使用大页而不是基本页来映射内存,可以极大地改善TLB的利用,降低TLB miss;使用配置CONFIG_UCE_KERNEL_RECOVERY打开,在copy_from_user场景下消费UCE时,使用kill关联用户态进程取代内核panic,该特性默认是关闭的,可通过内核启动参数cmdline接口(uce_kernel_recovery=[0,4])和proc接口动态开关(/proc/sys/kernel/uce_kernel_recovery)进行配置。

openEuler 22.03增加了新介质文件系统Eulerfs,该文件系统创新元数据软更新技术,基于指针的目录双视图计数机制,减少元数据同步开销,有效提升文件系统create、unlink、mkdir、rmdir系统调用性能。

openEuler 22.03做了内存分级扩展,支持多种内存、存储介质扩展系统内存容量,降低内存使用成本。

openEuler 22.03新增用户态交换支持,通过etMem的策略配置,对于淘汰的冷内存,通过用户态swap功能交换到用户态存储中,达到用户无感知,性能优于内核态swap。

openEuler 22.03新增gazelle用户态协议栈,无须应用程序修改和重新编译即可使用,支撑上层业务获得高性能、低时延的网络传输。gazelle用户态协议栈基于dpdk和lwip,实现支持无锁、多线程的高性能用户态协议栈,加速应用程序的网络性能,无须修改适配和重新编译即可使用。

openEuler 22.03做了云原生调度增强,在云业务场景中,交互类应用对延时敏感,在线业务存在潮汐现象,CPU资源利用率普遍较低(不足15%),在线和离线业务混合部署是提升资源利用率的有效方式。在现有的内核资源分配和管理机制上,通过QAS可以确保在线任务对CPU的快速抢占、确定性的调度运行,同时压制离线任务干扰;通过OOM回收支持优先级,优化OOM时内存回收调度算法,在发生OOM时,优先对低优先级的进程组进行内存回收,保障在线业务的正常运行;通过容器混合部署框架,对K8s集群下的混合部署,openEuler用户仅需给业务打上在线或离线的标签,系统即能自动感知业务的创建,并根据业务优先级进行配置,实现资源的隔离和抢占。

openEuler 22.03支持QEMU热补丁机制,即支持libcareplus热补丁机制,提供一种在线修复进程bug的技术,使得QEMU进程能够在不影响虚拟机业务的情况下,在线解决QEMU进程bug。

openEuler 22.03支撑了容器化操作系统KubeOS,实现云原生集群OS的统一容器化管理。包括OS容器化管理、对接K8s容器和OS统一管理、原子化的生命周期管理,以及OS轻量化裁剪,减少不必要的冗余包,可实现快速升级、替换等。

openEuler 22.03支持轻量安全容器增强,基于StratoVirt轻量虚拟化技术,实现容器级别的低负载和虚拟机的高安全。包括支持UEFI启动、ACPI表的构建以及为虚拟机添加包括virtio-pci在内的PCIe/PCI设备;支持VFIO,提供将host上物理设备直通给虚拟机的能力,使虚拟机获得接近裸设备的高性能;支持直通设备热插拔,即支持virtio-blk-pci、virtio-net-pci和VFIO等设备的热插拔,有效避免更换外设引起的系统停机和业务中断。

openEuler 22.03具有iSulad增强,shimv2收编了kata-runtime、kata-shim和kata-proxy进程,通过加载一次运行时并通过RPC调用来处理各种容器生命周期管理命令来简化架构,不必为每个容器一直运行一个容器运行时。

openEuler 22.03具有eggo支持容器管理双平面部署,eggo是openEuler云原生Sig组K8s集群部署管理项目,提供高效、稳定的集群部署能力。包括集群配置版本化管理,配置统一Git repo版本化管理,使用仓库汇总和跟踪集群的配置信息,以及x86/ARM双平面,实现OS双平面集群化部署、监控、审计等场景。

openEuler 22.03支持边缘计算,提供跨边云的协同框架(KubeEdge+),实现边云之间的应用管理与部署、跨边云通信等基础能力。包括管理协同—— 实现单集群设备统一管理,应用秒级发放;网络协同—— 支持跨边云双向通信,私有子网中的边缘节点通信;边缘自治—— 实支持边缘自治,确保网络不稳定状态下边缘节点正常工作,支持边缘节点元数据持久化和快速恢复;边缘轻量化—— 内存占用少,可在资源受限情况下工作。

openEuler 22.03支持嵌入式镜像,具有轻量化能力,开放yocto小型化构建裁剪框架,支撑OS镜像轻量化定制,提供OS镜像小于5 M和小于5 s快速启动等能力;具有多硬件支持,新增支持树莓派4B作为嵌入式场景通用硬件;具有软实时内核,基于Linux-5.10内核提供软实时能力,软实时中断响应时延为微秒级;可以混合关键性部署,实现SoC内实时和非实时多平面混合部署,并支持zephyr实时内核;具有分布式软总线基础能力,集成鸿蒙的分布式软总线,实现欧拉嵌入式设备之间的互联互通;具有嵌入式软件包支持,新增80+嵌入式领域常用软件包的构建。

openEuler 22.03支持secPaver,它是一款SELinux安全策略开发工具,用于辅助开发人员为应用程序开发安全策略,提供高阶配置语言,根据策略配置文件内容生成SELinux策略文件,降低SELinux使用门槛。

openEuler 22.03支持NestOS,它是一款在openEuler社区CloudNative sig组孵化的云底座操作系统,专注于提供最佳的容器主机,大规模下安全地运行容器化工作负载。NestOS是开箱即用的容器平台,搭载了iSulad、docker、podman、cri-o等主流容器基础平台;NestOS具有简单易用的安装配置过程,采用了Ignition技术,提供个性化配置;NestOS具有安全可靠的包管理方式,使用rpm-ostree进行包管理;NestOS具有友好可控的自动更新代理,采用zincati实现无感升级;NestOS有紧密配合的双系统分区,此分区设计确保系统安全。

openEuler 22.03具有很多第三方应用支持,比如KubeSphere,它是在Kubernetes之上构建的以应用为中心的容器平台,完全开源,由青云科技发起,并由openEuler社区SIG-KubeSphere提供支持和维护;比如OpenStack Wallaby,Wallaby是2021年4月份发布的OpenStack的最新稳定版本,包含nova、kolla、cyborg、tacker等核心项目的重要更新;比如OpenResty,它是基于Nginx与Lua的高性能Web平台。

openEuler 22.03具有对多种桌面环境的支持,提供更多的开发桌面选择和更好的开发体验。包括DDE新增支持画板、音乐和影院应用;UKUI新增支持中文输入法和多媒体;kiran-desktop支持麒麟信安桌面系统;支持GNOME桌面系统。

openEuler操作系统的发布件包括ISO发布包、虚拟机镜像、容器镜像、嵌入式镜像和repo源等,方便不同用户的安装和使用。

1.4 与Linux的第一次亲密接触

1.4.1 Linux内核发展简史

下面详细介绍Linux这个自由软件的发展历程。

1991年11月,芬兰赫尔辛基大学的学生Linus Torvalds写了一个小程序,后来取名为 Linux,放在互联网上。他表达了一个愿望,希望借此搞出一个操作系统的“内核”来,这完全是一个偶然事件。

1993年,在一批高水平黑客的参与下,Linux-1.0版诞生了。

1994年,Linux的第一个商业发行版Slackware问世。

1996年,美国国家标准技术局的计算机系统实验室确认Linux-1.2.13(由Open Linux公司打包)符合POSIX标准。POSIX标准让软件跨操作系统平台运行成为可能,因为它定义了操作系统提供的功能标准,使得软件代码只要进行适当适配就可以在另一个完全不同的操作系统上运行。比如Linux下的应用程序可以移植到Windows上运行。

2001年,Linux-2.4版内核发布,这是一个重要的版本,意味着Linux内核日趋成熟。

2003年,Linux-2.6版内核发布,此版内核开疆扩土,占领了很大的操作系统市场份额。Linux内核版本号由3组数字组成(x.y.z),其中x是内核主版本;y的偶数,表示稳定版本,y的奇数表示开发中版本;z是错误修补的次数。

2011年,Linux-3.0版内核发布,为什么从2.6升级到3.x?Linus说Linux进入第三个10年,从3.0开始内核不再用奇偶数表示稳定或开发版本,3.x的版本号为3.0~3.18。

2015年,Linux-4.0版内核发布,3.x到4.x进行了重大的架构升级,最重要的架构升级就是支持实时内核补丁,4.x内核的版本号为4.0~4.20。

2019年,Linux-5.0版内核发布,为什么从4.x升级到5.x呢?Linus说4.x的版本号到了4.20,再升级到4.21就不便于计数了。

截至2022年6月,在Linux内核官网上可以看到的Linux内核是Linux-5.18.1。

1.4.2 安装一个Linux系统

以使用VirtualBox创建openEuler虚拟机为例,大致步骤如下。

(1)下载安装虚拟机软件VirtualBox。

(2)下载openEuler。

(3)在虚拟机软件VirtualBox中安装openEuler操作系统。

(4)openEuler操作系统的网络配置如图1-5所示。

① 使用nmcli connection show查看所有连接。

② 使用nmcli device connect enp0s3尝试连接。

图1-5 openEuler操作系统的网络配置

(5)在openEuler操作系统中下载安装常用工具,举例如下。

① git安装:dnf install git。

② 安装VIM:dnf install vim。

③ 安装tar:dnf install tar。

以使用VirtualBox安装配置一个Ubuntu Linux虚拟机为例,大致步骤如下。

(1)下载安装虚拟机软件VirtualBox。

(2)下载Linux发行版Ubuntu Desktop。

(3)在虚拟机软件中安装Ubuntu Desktop操作系统。

(4)配置共享文件夹、共享粘贴板以方便虚拟机与宿主机之间进行数据传输。

(5)在默认情况下,Ubuntu Desktop没有预装C/C++开发环境,但Ubuntu提供了build-essential包可以把相关C/C++开发环境安装好,具体安装方法如下。

$ sudo apt-get install build-essential
$ apt depends build-essential # 用命令查看哪些包被 build-essential 依赖

以使用QEMU创建openEuler ARM64虚拟机为例,大致步骤如下。

(1)下载安装虚拟机软件QEMU。

(2)下载openEuler ARM64虚拟机镜像文件。

(3)运行QEMU,安装openEuler ARM64虚拟机,过程如下。

新建一个文件夹,并将解压好的openEuler ARM64虚拟机镜像文件放到该目录,进入QEMU的安装路径,将edk2-aarch64-code.fd(系统引导文件)文件复制并放入上面镜像的同级目录下。在Shell终端进入该目录并执行如下命令。

$ qemu-system-aarch64 -m 4096 -cpu cortex-a57 -smp 4 -M virt -bios edk2-aarch64-code.fd
 -hda openEuler-20.03-LTS.aarch64.qcow2 -serial vc:800x600

后面参数是一些虚拟机的具体配置,如内存、CPU等。在打开的QEMU虚拟机窗口中按Ctrl+Alt+2组合键切换到串口控制台,如图1-6所示。

图1-6 串口控制台

输入用户名和密码,官方虚拟机镜像已经生成用户名root和密码openEuler12#$。登录成功后如图1-7所示。

图1-7 openEuler ARM64虚拟机登录成功

到此,openEuler ARM64虚拟机安装完成。上述镜像也可以换成官方的ISO镜像自行安装。

1.4.3 Linux命令行简明指南

使用Linux系统的典型场景是作为开发者编写代码。在开始编写代码之前,需要在GitHub或者Gitee等代码托管平台上创建一个版本库,比如menu。然后使用如下命令完成从代码托管平台复制一个git版本库到本地Code目录下,之后进入项目目录并创建文件夹,比如lab1。

~/$ ls
~/$ cd Code
Code/ $ git clone https://github.com/mengning/menu.git
Code/ $ cd menu
menu/ (master) $ mkdir lab1
menu/ (master) $ cd lab1

以上命令步骤用到了几个目录操作的命令。Linux系统的文件目录结构类似一棵树,顶层是根目录/,往下默认目录基本都会有其特定作用,如/etc 为配置文件保存目录,可以在/home目录下对应的个人账户目录下存放自己的文件,上面代码中“~/”表示/home目录下对应的当前登录的个人账户目录。

这里可能用到的命令简要介绍如下。

ls:显示指定工作目录下的文件和文件夹列表。常用选项-a显示所有文件及目录;-l列出详细信息。

cd:切换当前工作目录到指定目录,如“cd Code”即进入Code目录。

mkdir:用于创建指定名称目录,如“mkdir lab1”在当前目录下创建了一个目录lab1。常用选项-p表示若上层目录尚未建立,则一并建立上层目录;-m表示建立目录的同时设置目录的权限。

有关目录还有一些特殊的地方,比如前面提到的“~/”表示/home目录下对应的个人账户目录。还有相对路径的用法,“.”表示当前所在的目录;“..”表示当前目录的上层目录。

使用vi编辑器编辑hello.c文件。

lab1/ (master*) $ vi hello.c

Vim是Linux上一款功能十分强大的编辑器,从vi发展而来,是vi的增强版,其代码补全、编译及错误跳转等方便编程的功能特别丰富,被广泛使用。Vim/vi有3种模式:命令模式(Normal模式)、编辑模式和底线命令模式。

刚启动的Vim/vi处于命令模式(Normal模式),在这个模式下可以通过h、j、k、l键作为上、下、左、右键移动光标。从命令模式切换到编辑模式和底线命令模式的方法如下。

i键:即insert的首字符,用于切换到编辑模式以插入的方式输入字符到文档。

:键:即“Shift+;”,用于切换到底线命令模式。

ESC键:用于返回命令模式(Normal模式)。

在命令模式(Normal模式)下输入“i”(即insert之意)切换到编辑模式。在这种模式下,如果权限允许就可以用插入的方式输入字符到文档,对文件内容进行修改。按ESC键回到命令模式(Normal模式)。

在命令模式(Normal模式)下输入“:”切换到底线命令模式。在这个模式下可以输入的命令主要有如下3个。

q //退出程序
w //保存文件
wq//保存且退出

当然,如果对Linux命令行下的Vim编辑器不习惯,也可以用Visual Studio Code编辑器编辑代码,有关Visual Studio Code和Git的详细使用方法可以参考作者的另一本书——《代码中的软件工程》。

在编辑模式下输入hello.c的内容如下。

#include <stdio.h>
int main()
{
    printf("hello world!\n");
}

编译执行hello程序可以使用gcc编译器编译一个c文件,将此文件作为gcc的参数,用-o加上可执行文件名称,比如gcc -o hello hello.c,最后生成的可执行文件为hello。编译完成后,使用./hello命令即可执行hello程序。

lab1/ (master*) $ gcc -o hello hello.c
lab1/ (master*) $ ./hello
hello world!

有时需要将工程文件打包成压缩包作为成果以附件上传(一般情况下不会把.o中间文件、可执行文件等打包进去),而且网上很多资源都是以压缩包的形式共享。因此,压缩、解压命令尤为重要。Linux下常用的打包或解包命令为tar命令,常用选项为-zcvf和-zxvf。

lab1/ (master*) $ cd ../..
Code/ $ tar -zcvf menu.tar.gz ./menu # 打包
Code/ $ tar -zxvf menu.tar.gz # 解包

常用选项-zcvf和-zxvf分别解释如下。

//打包
'-z' //打包同时压缩
'-c' //创建新的 tar 文件
'-v' //列出每一步处理涉及的文件的信息
'-f' //指定要处理的文件名
//解包
'-z' //支持 gzip 解压文件
'-x' //解包提取文件
'-v' //列出每一步处理涉及的文件的信息
'-f' //指定要处理的文件名

除了打包的方式,现代程序员最常用的工具就是Git版本控制,这里提供一个简要的实验操作过程供参考练习。

~/ $ mkdir Code
~/ $ cd Code
# 需要到 GitHub或者Gitee等代码托管平台上创建自己的 git 版本库
Code/ $ git clone https://github.com/[your_name]/[your_git_repo].git
Code/ $ cd [your_git_repo]
Code/ $ mkdir lab1
lab1/ (master*) $ vi hello.c # 输入 hello world 程序
lab1/ (master) $ gcc -o hello hello.c # 编译 hello.c
lab1/ (master*) $ ./hello # 运行 hello 程序
hello world!
# 将源代码通过 git 进行版本控制
lab1/ (master*) $ git add hello.c
lab1/ (master*) $ git commit -m "hello world"
[master 40425fe] hello world
1 file changed, 7 insertions(+)
create mode 100644 lab1/hello.c
lab1/ (master*) $ git push
......
 5f24b93..40425fe  master -> master

1.5 openEuler操作系统中的常用Linux命令参考

由于不同的Linux发行版自带的命令集有所不同,这里根据POSIX标准中给出的命令集作为参照,对openEuler操作系统中常用的命令进行大致分类和介绍。

1.5.1 查看系统相关信息的命令

uname命令:用于打印当前系统相关信息(内核版本号、硬件架构、主机名称和操作系统类型等)。

date命令:显示或设置系统时间与日期。

locale命令:将有关当前语言环境或全部公共语言环境的信息写到标准输出上。

logname命令:可以显示自己初次登录到系统中的用户名,主要识别sudo前后情形,与whoami相反。

who命令:显示目前登录系统的用户信息。

whoami命令:显示当前的用户是谁,也就是显示自己的用户名。

df命令:列出文件系统的整体磁盘空间的使用情况。可以用来查看磁盘已被使用多少空间。

du命令:也是查看使用空间的,但是与df命令不同的是,du命令会列出当前目录下所有文件的磁盘占用情况。

env命令:用于显示系统中已存在的环境变量。

getconf命令:用于获取系统信息,比如getconf PAGE_SIZE查看系统内存页面大小。

logger命令:是一个Shell命令接口,可以通过该接口使用Syslog的系统日志模块,还可以从命令行直接向系统日志文件写入一行信息。

man命令:是Linux下的帮助命令,通过它可以查看Linux中的命令帮助、配置文件帮助和编程帮助等信息。

1.5.2 用户管理和权限管理相关的命令

useradd命令用来新建一个用户,groupadd命令用来新建用户组,usermod命令修改用户信息,passwd命令用来更改用户密码。

chmod命令用来变更文件或目录的权限。chown命令用于改变某个文件或目录的所有者和所属的组,可以向某个用户授权,使该用户变成指定文件的所有者或者改变文件所属的组。chgrp命令用于修改文件(或目录)的所属组。为了方便初学者记忆,可以将chgrp理解为“change group”的缩写。

newgrp命令可以切换用户的有效组。

id命令可以显示真实有效的用户ID UID和组ID GID。UID是一个用户的唯一身份标识。组ID(GID)则对应多个UID。id命令已经默认预装在大多数Linux系统中。要使用它,只需要在控制台输入id。

umask命令设置用户创建文件的默认权限。

1.5.3 文件和目录相关的命令

cd命令:是Change Directory的缩写,用来切换工作目录。

ls命令:用来显示目标列表,在Linux中是使用率较高的命令。ls命令的输出信息可以进行彩色加亮显示,以区分不同类型的文件。

mkdir命令:用来创建目录。

mv命令:用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中。

cp命令:用来将一个或多个源文件或者目录复制到指定的目的文件或目录。它可以将单个源文件复制到一个指定文件名的具体的文件或一个已经存在的目录下。此命令还支持同时复制多个文件。

rm命令:可以删除一个目录中的一个或多个文件或目录,也可以将某个目录及其下属的所有文件及其子目录均删除。对于链接文件,只是删除整个链接文件,而原有文件保持不变。注意,使用rm命令要格外小心。因为一旦删除了一个文件,就无法再恢复。unlink命令通过系统调用函数unlink删除指定的文件,和rm命令作用一样,都是删除文件。在openEuler中,删除操作需要输入Yes才能生效。

pwd命令:是Print Working Directory的缩写,其功能是显示当前所在工作目录的全路径。主要用在当不确定当前所在位置时,查看当前目录的绝对路径。

ar命令:是一个备份压缩命令,用于创建、修改备存文件(archive),或从备存文件中提取成员文件。此命令最常见的用法是将目标文件打包为静态链接库。

unzip命令:用于解压缩由zip命令压缩的“.zip”压缩包。

zcat命令:用于不真正解压缩文件,就能显示压缩包中文件内容的场合。

cksum命令:是检查文件的CRC是否正确,确保文件从一个系统传输到另一个系统的过程中不被损坏。这种方法要求校验和在源系统中被计算出来,在目的系统中又被计算一次,两个数字进行比较,如果校验和相等,则该文件被认为是正确传输的。

touch命令:用于创建文件或修改文件/目录的时间戳。

vi命令:是UNIX操作系统和类UNIX操作系统中最通用的全屏幕纯文本编辑器。Linux中的vi编辑器叫Vim,它是vi的增强版,与vi编辑器完全兼容,而且实现了很多增强功能。

ed命令:是Linux中功能最简单的文本编辑程序,一次仅能编辑一行而非全屏幕方式的操作。ed命令并不是一个常用的命令,一般使用比较多的是vi命令。

sed命令:是一个面向字符流的非交互式编辑器,也就是说,sed不允许用户与它进行交互操作。

awk命令:是一个强大的文本分析工具,相对于grep的查找、sed的编辑,awk在对数据分析并产生报告时,显得尤为强大,简单来说,awk 将数据逐行读入,以空格作为默认分隔符,对每行进行切片,并对切开的部分进行各种分析处理。printf是awk的重要格式化输出命令。

iconv命令:用来转换文件的编码方式,比如它可以将UTF8编码转换成GB18030编码,反过来也行。

cat命令:将文件或标准输入组合输出到标准输出。这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用。cat命令的名称来源于concatenate一词。

more命令:是一个基于vi编辑器的文本过滤器,它以全屏幕的方式按页显示文本文件的内容,支持vi中的关键字定位操作。

head命令:用来显示档案的开头,默认打印其相应文件的开头10行。

tail命令:用于输入文件中的尾部内容,默认在屏幕上显示指定文件的末尾10行。

file命令:用来探测给定文件的类型。

find命令:用来在指定目录下查找文件。任何位于参数之前的字符串都将被视为欲查找的目录名。如果使用该命令时不设置任何参数,则此命令将在当前目录下查找子目录与文件,并且将查找到的子目录和文件全部进行显示。

grep命令:是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式打印,它的使用权限是所有用户。

sort命令:在Linux中非常有用,它将文件进行排序,并将排序结果标准输出。

tsort命令:对文件执行拓扑排序。

wc命令:为统计指定文件中的字节数、字数、行数,并将统计结果显示输出。

join命令:用于将两个文件中指定栏位内容相同的行连接起来。找出两个文件中指定栏位内容相同的行,并加以合并,再输出到标准输出设备。

split命令:可以将一个大文件分割成很多个小文件,有时需要将文件分割成更小的片段,以提高可读性、生成日志等。

csplit命令:用于将一个大文件分割成小的碎片,并且将分割后的每个碎片保存成一个文件。碎片文件的命名类似“xx00”“xx01”。csplit是split的一个变体,split只能够根据文件大小或行数来分割,但csplit能够根据文件本身特点来分割。

ln命令:是Linux中又一个非常重要的命令,它的功能是为某一个文件在另外一个位置建立一个同步的链接,最常用的参数是-s,具体用法是:ln –s 源文件 目标文件。

cmp命令:用来比较两个文件是否有差异。当相互比较的两个文件完全相同时,则该指令不会显示任何信息。若发现有差异,预设会标识出第一个不同之处的字符和列数编号。

diff命令:用来比较两个文件或目录的不同,并且是以行为单位来比对的。一般是用在ASCII纯文本文件的比对上。因为是以行为比对的单位,所以diff通常用在同一文件(或软件)的新旧版本差异对比上,常常用于生成源代码的补丁包文件(patch)。

patch命令:用于修补文件,就不得不提到diff命令,也就是制作patch的必要工具,此命令用来检查文件中不可移植的部分。

dd命令:将指定大小的数据块复制为一个文件,并在复制的同时进行指定的转换。

1.5.4 进程相关的命令

bg命令:将进程搬到后台运行,使前台可以执行其他任务。该命令的运行效果与在命令后面添加符号&的效果是相同的,都是将其放到系统后台执行。

fg命令:将进程搬到前台运行。

nohup命令:可以在退出账户之后继续运行相应的进程,nohup是no hang up的缩写,即不挂起。

jobs命令:查看当前有多少在后台运行的命令。

fuser命令:是用来显示一个进程所有正在使用的file、filesystem或者sockets。

ps命令:用于报告当前系统的进程状态。可以搭配kill命令随时中断、删除不必要的程序。此命令是基本同时也是非常强大的进程查看命令,使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等。

kill命令:通过向进程发送指定的信号来结束相应进程。在默认情况下,采用编号为15的TERM信号。此信号将终止所有不能捕获该信号的进程。对于那些可以捕获该信号的进程就要用编号为9的kill信号,强行“杀掉”该进程。

nice命令:用于以指定的进程调度优先级启动其他程序。

renice命令:可以修改正在运行的进程的调度优先级。

time命令:可以获取一个程序的执行时间,包括程序的实际运行时间以及程序运行在用户态和内核态的时间。

1.5.5 进程间通信(IPC)相关的命令

ipcs命令:输出当前系统下各种方式的IPC状态信息(共享内存、消息队列、信号)。

ipcrm命令:移除一个消息对象,或者共享内存段,或者一个信号集,同时会将相关数据也一起移除。当然,只有超级管理员或者IPC对象的创建者才有这项权利。

mkfifo命令:创建一个FIFO特殊文件,是一个命名管道,可以用于进行进程间通信。

1.5.6 基本的开发者工具

c99—— compile standard C programs。

fort77—— FORTRAN compiler。

yacc代表Yet Another Compiler Compiler。

m4命令:是一个宏处理器,将输入复制到输出,同时将宏展开。宏可以是内嵌的,也可以是用户定义的。

make命令:是GNU的工程化编译工具,用于执行Makefile工程文件编译众多相互关联的源代码文件,以实现工程化的管理,提高开发效率。

nm命令:用于显示二进制目标文件的符号表。

od命令:用于将指定文件内容以八进制、十六进制或其他格式显示,通常用于显示或查看文件中不能直接显示在终端的字符。

strip命令:从字面上可以把它理解成脱衣服的意思,简单地说,就是给文件脱掉外衣,具体就是从特定文件中剥掉一些符号信息和调试信息,使文件变小。

expand命令:用于将文件的制表符(TAB)转换为空白字符(space),将结果显示到标准输出设备。

1.5.7 I/O相关的命令

mesg命令:用于设置当前终端的写权限,即是否让其他用户向本终端发信息。将mesg设置为y时,其他用户可利用write命令将信息直接显示在屏幕上。

read命令:从键盘读取变量的值,通常用在Shell脚本中与用户进行交互的场合。

tty命令:可以查看现在使用的终端标识。

stty命令:对当前为标准输入的设备设置某些I/O选项,该命令将输出写到当前为标准输出的设备中。

tee命令:读取标准输入,把这些内容同时输出到标准输出和(多个)文件中,此命令可以重定向标准输出到多个文件。

1.5.8 Shell脚本中的常用命令

crontab命令:可以在固定的间隔时间执行指定的系统指令或Shell脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常适合周期性的日志分析或数据备份等工作。

sh命令:Shell命令语言解释器,执行命令从标准输入读取或从一个文件中读取。

test命令:Shell环境中测试条件表达式的实用工具。

expr命令:一款表达式计算工具,用来完成表达式的求值操作。

command命令:调用指定的命令并执行,命令执行时不查询Shell函数。此命令只能执行Shell内部的命令。

echo命令:用于在Shell中打印Shell变量的值,或者直接输出指定的字符串。

at命令:用于在指定时间执行命令。允许使用一套相当复杂的指定时间的方法。

batch命令:用于在指定时间,当系统不繁忙时执行任务,用法与at命令相似。

basename命令:用于显示去除路径和文件后缀部分的文件名或者目录名。

bc命令:英文全拼为“Binary Calculator”,是一种支持任意精度的交互执行的计算机语言。bash内置了对整数四则运算的支持,但是并不支持浮点运算,而bc命令可以很方便地进行浮点运算。

cal命令:名字来自英语单词“Calendar”,用于显示当前日历或者指定日期的日历,如果没有指定参数,则显示当前月份。

getopt命令:与getopts都是Bash中用来获取与分析命令行参数的工具,常用在Shell脚本中分析脚本参数。

dirname命令:去除文件名中的非目录部分,仅显示与目录有关的内容。

hash命令:负责显示与清除命令运行时系统优先查询的哈希表(hash table)。Linux系统下会有一个哈希表,每个Shell独立,第一次使用该命令时,Shell解释器默认会从PATH路径下寻找该命令的路径。第二次使用该命令时,Shell解释器首先会查看哈希表,表中没有该命令时才会去PATH路径下寻找。

sleep命令:常用于在Shell脚本中延迟时间。

wait命令:用来等待命令的命令,直到其执行完毕后返回终端。该命令常用于Shell脚本编程中,待指定的命令执行完成后,才会继续。

xargs命令:xargs是给命令传递参数的过滤器,也是组合多个命令的工具。其可以将管道或标准输入(stdin)数据转换成命令行参数,也能够从文件的输出中读取数据,还可以将单行或多行文本输入转换为其他格式,例如多行变单行、单行变多行。

true和false命令:始终返回设定的退出状态。程序员和脚本通常使用退出状态评估命令执行得成功与否(0为成功,非0为不成功)。

本章实验

安装openEuler操作系统,熟练使用Linux常用命令。

相关图书

Linux常用命令自学手册
Linux常用命令自学手册
Linux后端开发工程实践
Linux后端开发工程实践
轻松学Linux:从Manjaro到Arch Linux
轻松学Linux:从Manjaro到Arch Linux
Linux高性能网络详解:从DPDK、RDMA到XDP
Linux高性能网络详解:从DPDK、RDMA到XDP
跟老韩学Linux架构(基础篇)
跟老韩学Linux架构(基础篇)
统信UOS应用开发实战教程
统信UOS应用开发实战教程

相关文章

相关课程