用“芯”探索:教你构建龙芯平台的Linux系统

978-7-115-55849-7
作者: 孙海勇
译者:
编辑: 俞彬

图书目录:

详情

本书通过讲解如何在龙芯CPU下制作Linux系统及其发行版来介绍Linux操作系统的组成,同时为读者提供了一种为非x86架构CPU制作和移植发行版的思路。本书将制作Linux系统的过程分为准备、制作临时系统、制作目标系统、制作发行版4个阶段。准备阶段可让读者对实际制作过程中用到的技术细节有所了解;制作临时系统阶段介绍如何为没有可用系统的机器制作一个可用的系统;制作目标系统阶段介绍如何在一个临时系统的基础上将Fedora系统移植到目标机器上;制作发行版阶段配合软件仓库、安装系统、包构建管理制作等对目标系统进行扩展,完成一个相对完整的发行版的制作。

图书摘要

版权信息

书名:书名书名

ISBN:978-7-115-书号-7

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

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

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

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


版  权

著    人名人名

责任编辑 人名人名

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

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

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

读者服务热线:(010)81055410

反盗版热线:(010)81055315

内 容 提 要

本书通过讲解如何在龙芯CPU下制作Linux系统及其发行版来介绍Linux操作系统的组成,同时为读者提供了一种为非x86架构CPU制作和移植发行版的思路。本书将制作Linux系统的过程分为准备、制作临时系统、制作目标系统、制作发行版4个阶段。准备阶段可让读者对实际制作过程中用到的技术细节有所了解;制作临时系统阶段介绍如何为没有可用系统的机器制作一个可用的系统;制作目标系统阶段介绍如何在一个临时系统的基础上将Fedora系统移植到目标机器上;制作发行版阶段配合软件仓库、安装系统、包构建管理制作等对目标系统进行扩展,完成一个相对完整的发行版的制作。

本书适合Linux系统制作爱好者学习和阅读,也可作为大中专院校相关专业师生的参考书。

中国自主产权芯片技术与应用丛书


编委会

总主编

胡伟武  龙芯中科技术股份有限公司董事长

张 戈  龙芯中科技术股份有限公司副总裁

靳国杰  龙芯中科技术股份有限公司总裁助理

编 委

杜安利 郭同彬 叶骐宁 陈华才

编辑工作委员会

主 任

张立科

副主任

  刘 琦

委 员

宋吉文     赵祥妮   张天怡

  李天骄   陈万寿 杨海玲 陈冀康

前  言


从最早接触龙芯到现在已经有10多年了,这10多年间我给龙芯移植的Linux系统也有好几个版本了,其中有基于LFS(Linux From Scratch)编译的简单系统,也有通过Debian这样现成的系统裁剪定制而成的系统,但是最让我觉得有挑战性的还是移植整个Fedora发行版。

这些年我也遇到不少对制作Linux系统感兴趣的人,其中有经验丰富的高手,也有初步了解的新手,在相互交流中我总能收获新的知识,渐渐地对于制作一个系统我也积累了越来越多的心得。

在写作本书之前,我所想的就是把最近几年自己在发行版移植工作中的收获和心得好好地总结一下,把我所掌握的相关技术和技巧展示给大家。这样既可以让我在总结知识的过程中有机会深入思考,也可以和更多对制作系统感兴趣的读者交流我所了解的知识。

移植一个未支持目标平台的发行版是有一定挑战性的,我认为最有挑战性的是移植方法和思路。移植方法和思路不是唯一的,而是有多种选择的,同时随着开源技术的改进,以及具体技术实现的变化,移植方法和思路也会得到扩展和改进。尽管本书只提供一种当下技术环境所适合的方法和思路,但请读者在阅读本书的时候一定不要有固化步骤的思想,而应通过这些步骤理解移植思路,不断开拓自己的思路,找出更适合自己的方法和步骤。

本书分为4个阶段。

第一阶段是准备。设置这个阶段主要是为了让读者对后面实际制作过程中用到的技术细节有所了解。

第二阶段是制作临时系统。这个阶段主要介绍如何为一个没有可用系统的机器制作一个可用的系统。

第三阶段是制作目标系统。这个阶段介绍的是如何在一个临时系统的基础上将Fedora系统移植到目标机器上。

第四阶段是制作发行版。这个阶段的工作是完善目标系统,以及配合软件仓库、软件包编译及管理工具、安装系统制作等对目标系统进行扩展,完成一个相对完整的发行版的制作。

读者可以根据自己的实际情况来确定阅读内容和方式,例如,如果你对某个阶段的内容非常熟悉可以跳过相应的部分,有选择性地进行阅读。

本书并没有改变发行版的名称,只展示了将Fedora系统移植到龙芯平台上的过程和方法。在制作一个拥有独立名称的发行版系统时,可以修改某些与发行版名称相关的地方,因该操作并不影响制作和移植系统的方法,本书未对这方面进行过多的讲解。

若本书介绍的移植方法和思路能为读者制作和移植一个发行版提供帮助,本书的目的就达到了。至于发行版如何更好地运营和扩展自己的特色,是另一个需要考虑的问题。

读者可前往本书的配套资源页面:https://github.com/sunhaiyong1978/Book-PortingDistro,查看和下载本书所涉及的配置文件、软件补丁等文件,以及后续内容更新和勘误。如需探讨技术性问题,可发送邮件至youbest@sina.com,笔者会在收到邮件后尽快回复。

孙海勇

2020年12月

第一阶段 准备

第01章 龙芯CPU和Linux发行版

我们首先来了解一下本书的两个重要角色:龙芯CPU和Linux发行版。

1.1 龙芯CPU

龙芯CPU是中国科学院计算技术研究所(现已成立龙芯中科技术股份有限公司,简称龙芯中科)自主研发的通用处理器。从2001年成立项目到2019年12月发布龙芯3A4000处理器,在这近20年的时间中,龙芯中科发布了多款不同型号的处理器。

龙芯CPU主要分3个系列,产品以 32 位和 64 位的单核及多核 CPU 为主。

1.龙芯1号

龙芯1号系列处理器是32位的低功耗、低成本处理器,实现了带有静态分支预测和阻塞Cache(高速缓存)的乱序执行流水线,且集成各种外围接口,形成面向特定应用的单片解决方案。龙芯1号主要应用在云终端、工业控制、数据采集、手持终端、网络安全、消费电子等领域,如智能门锁、水表监测等,如图1.1所示。

图1.1 龙芯1C处理器

2.龙芯2号

龙芯2号系列处理器是64位低功耗系列处理器,实现了带有动态分支预测和非阻塞Cache的超标量乱序执行流水线;同时还使用浮点数据通路复用技术实现了定点的SIMD(Single Instruction Multiple Data,单指令流多数据流)指令,并集成各种外围接口。龙芯2号主要应用在嵌入式计算机、工业控制、移动信息终端、汽车电子等领域,如设备控制终端、一体机、NAS(Network Attached Storage,网络附属存储)等,如图1.2所示。

图1.2 龙芯2F处理器

3.龙芯3号

龙芯3号系列处理器是64位多核系列处理器, 基于可伸缩的多核互联架构设计。龙芯3号主要应用在桌面和服务器领域,如桌面计算机和各类服务器等,如图1.3所示,其中龙芯3A系列CPU针对桌面计算机,龙芯3B系列CPU针对服务器。

图1.3 龙芯3A4000处理器

1.2 GNU/Linux操作系统和发行版

1.2.1 GNU简介

GNU是由理查德·斯托曼(Richard Stallman)在1983年9月27日公开发起的一项计划,它的目标是创建一套完全自由的操作系统。

GNU是GNU’s Not UNIX的缩写,UNIX是一种广泛使用的商业操作系统。GNU要实现UNIX系统的接口标准并开发操作系统中的各个部件,这里不乏重量级的核心组件,如GCC、Glibc等。GNU计划采用了当时已经可以自由使用的部分软件,例如TeX排版系统和X Window视窗系统等。GNU计划也开发了许多其他的自由软件,这些软件被移植到其他操作系统平台上,例如Microsoft Windows、BSD、Solaris及Mac OS。

GNU计划的另一个重要组成部分就是许可证,主要包括GPL、LGPL和GFDL等。

GPL(GNU General Public License,通用公共许可证)是一个被广泛使用的自由软件许可证,该许可证分别在1989年1月、1991年6月和2007年6月发布了1.0、2.0和3.0版本,其中GPL V2.0是使用最广泛的版本。

LGPL(GNU Lesser General Public License,GNU宽通用公共许可证)是GPL的另一个版本,是为了应用于一些软件函数库而设计的,该许可证分为2.0、2.1和3.0版本。

GFDL(GNU Free Documentation License,自由文档许可证)是一个内容开放的著作权版权许可证。为了GNU计划的顺利实施,自由软件基金会于2000年发布了GFDL,并在2000年3月、2002年12月和2008年11月3日发布了1.1、1.2和1.3版本。

注意:

在很多软件的名称中,开头的G或者GNU就代表了GNU计划,该软件一般也是GNU计划的一部分。

GNU计划的图形标志是牛羚头像,如图1.4所示。

图1.4 GNU计划的标志

虽然GNU计划的发展促进了开源软件的发展,但真正让大多数人开始认识开源软件的却是Linux。这个以黄嘴企鹅为标志的开源软件让开源精神迅速地蔓延到全世界,现在Linux几乎成了开源软件的代名词。

1.2.2 Linux简介

严格来讲,Linux只是一个操作系统内核的名称,但很多人习惯将使用Linux内核的操作系统叫作Linux。

Linux内核最初只是林纳斯·托瓦兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的。Linux内核的第一个版本在1991年9月发布,在之后的几年中Linux内核被越来越多的人所认可,许多程序员参与其开发,Linux内核很快成为全世界范围内的开源软件。

Linux内核的迅速发展得益于开发者开放的态度,而使用GPL协议则成为Linux迅速发展的最重要保障。

Linux内核并不是GNU计划的组成部分,但它同GNU计划中建立的各种操作系统工具组合成了一个可用的操作系统,一般我们把这样的系统称为GNU/Linux。

Linux的标志是一只黄嘴企鹅,如图1.5所示。有许多GNU/ Linux下的游戏名称中会出现Tux(黄嘴企鹅的名字),一般游戏的主角就是这只可爱的企鹅。

当Linux遇到GNU后,可以说互相成就了对方,Linux内核获得了大量系统软件的支持,而GNU也获得了让这些系统软件发扬光大的内核。Linux内核几乎是跟GNU的软件一起发展壮大的,我们用的Linux操作系统通常都离不开GNU的软件,因此通常把使用GNU软件和Linux内核的操作系统称为GNU/Linux操作系统。

图1.5 Linux的标志

1.2.3 GNU/Linux的发行版

在GNU/Linux操作系统十几年的发展中,越来越多的管理模式、开发模式、开发组织等融合进了GNU/Linux操作系统,从而形成了各种GNU/Linux发行版(本书简称Linux发行版或发行版),且不断发展壮大,由最初屈指可数的几个发行版到现在的几百个发行版,在数量和质量上都在不断发展。

下面介绍几个代表性的发行版,这些是目前比较有实力和用户群较大的Linux发行版。

1.Debian GNU/Linux——发行版中的常青树

Debian是1993年由伊恩·默多克(Ian Murdock)发起的,默多克受到当时Linux与GNU的鼓舞,他的目标是使Debian成为一个公开的发行版。Debian的标志如图1.6所示。

图1.6 Debian的标志

几乎所有的专业Linux用户都知道Debian,Debian及其衍生版本的用户群是最大的,其中包括后起之秀Ubuntu。

Debian中最具有特色的部分是包管理工具。包管理工具中包含了一组诸如以apt开头的命令,可以自动进行软件包的关系分析,自动安装所需要的软件包。该软件包管理模式后来也被RPM的包管理工具所学习,Debian也成了deb包管理系的首创系统。

衍生版本:Ubuntu、Knoppix。

2.Fedora Linux——Linux系统的代名词

Fedora系统由RedHat公司发起和维护。RedHat公司于1994年发布了第一个RedHat Linux系统,当版本到RedHat Linux9.0之后则更名为Fedora,到本书完成时Fedora已发布到第32个版本。Fedora的标志如图1.7所示。

图1.7 Fedora的标志

曾几何时,在中国提到Linux没有不知道RedHat的,RedHat一度在中国成为Linux的代名词,RedHat也是最早出现的Linux发行版中的重要一员。

RedHat Linux开发了RPM这样的软件包管理工具,这也成了RPM包管理系的鼻祖。

当RedHat成为RedHat公司的商业产品标识后,Fedora占据了其在开源社区的位置,并沿用了RPM包管理工具。

衍生版本:Centos、Fusion。

3.Slackware Linux——古老而简洁的发行版

Slackware可以算是最古老的Linux发行版了,它的历史超过了RedHat Linux,是由帕特里克·沃尔克丁(Patrick Volkerding)开发的GNU/Linux发行版,其标志如图1.8所示。

图1.8 Slackware的标志

KISS(Keep It Simple,Stupid——保持简单)是Slackware一贯的原则,即尽量保持系统的简洁,从而实现稳定、高效和安全的运行。

Slackware没有包管理工具,在包管理工具盛行的今天,这样的设计似乎跟不上潮流,不过这却体现出了Slackware的KISS风格。对于熟悉Linux的用户,这样使用会更加方便。

衍生版本:Slax。

4.Arch Linux——快速、轻量的发行版

Arch Linux是一个针对i686/x86-64的优化的Linux发行版,其标志如图1.9所示。

图1.9 Arch Linux的标志

Arch Linux的基本理念是快速、轻巧、弹性与简单。它只安装最小化的基本系统,用户可以根据自己的特定需求选择配置和安装相应的软件。

Arch Linux使用Pacman包管理工具。虽然Pacman是一个比较轻量级的包管理工具,但完全可以胜任软件包的基本管理工作。

衍生版本:Manjaro。

5.Gentoo——可高度定制的发行版

Gentoo最初由丹尼尔·罗宾斯(Daniel Robbins,FreeBSD的开发者之一)创建。开发者熟悉FreeBSD致使Gentoo借鉴并引入了类似BSD中的ports系统——portage,将其作为包管理工具。

Gentoo的标志如图1.10所示。

图1.10 Gentoo的标志

与其他绝大多数知名的发行版相比,Gentoo是一个比较有特点的Linux发行版。它采用源代码的安装方式,主要通过发布方提供一个软件包的编译文件,通过portage对其进行解析来确认依赖关系及编译条件,并对软件包进行编译、安装和管理。Gentoo的另外一个特色是其文档的丰富性,非常适合开发者使用。

衍生版本:Sabayon。

目前已经出现了几百个基于Linux内核的发行版,而且每隔几个月就会有新的Linux发行版或者已有发行版新版本的发布。

从大多数的Linux发行版来看,比较容易区分的是各自的包管理工具以及各自的启动方式;成熟的包管理工具不算太多,因此现在几百个发行版大多使用的是类似的包管理方式。

1.3 基础发行版和衍生发行版

在全世界有几百个Linux发行版,国产的Linux发行版也有十几个,有些已经消亡,有些则发展势头很盛。我把这些Linux发行版分成两类:基础发行版和衍生发行版。

为了让本书后续部分的介绍不那么啰唆,如无特别说明,发行版一词表示以Linux为内核的发行版。

1.3.1 基础发行版

什么样的发行版可以称为基础发行版呢?

基础发行版数量不多,通常都有自己独特的且最早实际使用的软件包格式以及包管理工具,另外这些基础发行版大多十分活跃,软件的更新比较及时。它们还有一个重要的特点是,其源代码包通常是其衍生发行版的来源。这里列几个有代表性的基础发行版。

①以Debian为基础的发行版,其特征是主要采用DEB包格式以及APT包管理工具。

②以Fedora为基础的发行版,其特征是主要采用RPM包格式以及YUM/DNF包管理工具。

③以Arch为基础的发行版,其特征是主要采用PKG包格式以及Pacman包管理工具。

基础发行版更加注重软件的功能性、正确性、可用性、安全性以及可管理性等基础方面,另外也更加注重这些基础方面所使用的新技术的完善,这些对于整个操作系统都具有非常重要的意义。

基础发行版通常拥有大量的用户,开发和更新活动非常活跃,这导致用户使用的系统需要频繁更新。但这不是所有用户都喜欢的模式,因此有些基础发行版会划分为稳定、测试、不稳定版本,或是正式版本、测试版本等来满足不同用户的需求。

但即使划分了不同版本也不能满足大量用户的不同需求,例如对教育的需求、对安全的需求。虽然这些基础发行版可以通过定制安装来实现各种合理的需求,但是这些定制工作需要用户具备一定的技术能力才能实现。基础发行版通常带有良好的开发环境,因此开发人员更加愿意使用基础发行版,但对于普通用户而言则可能无法很好地满足需求,这就给衍生发行版提供了生长的土壤。

1.3.2 衍生发行版

衍生发行版数量众多,大多是基于基础发行版制作的,采用与其使用的基础发行版相同的包管理工具和软件包格式。衍生发行版有的采用二进制级别的衍生,即直接使用基础发行版以及制作好的安装包进行制作和发布;有的采用源代码级别的衍生,即采用基础发行版制作好的源代码包进行重新编译和打包二进制包,然后再重新制作系统和发布;有的也可能混合二进制级别和源代码级别的衍生来进行衍生发行版的制作和发布。

基础发行版和衍生发行版并没有孰优孰劣之分,虽然衍生发行版是基于某个基础发行版的,但有可能衍生发行版比基础发行版更加受欢迎。例如著名的Ubuntu就是基于Debian的衍生发行版,但是Ubuntu在易用性、界面方面做了很多的改善,它不但基于源代码进行软件包的重新打包,而且会改进源代码,使其在功能易用性上更加符合用户的需求,甚至可以使自己的改进影响到基础发行版。所以衍生发行版比基础发行版更加知名也是常见的事情。

另外衍生发行版通常都有一定的目标群体,也就是一款衍生发行版可以比基础发行版更加适合某个特定领域的用户,例如有专门针对教育的发行版,也有专门针对系统安全的发行版,还有针对游戏的发行版,等等。

补充一点:并不是只有以Linux内核为基础的操作系统才有基础发行版和衍生发行版的说法,其他的系统也会有基础和衍生发行版,如BSD系统。但基础发行版系统很重要的特征就是开源,例如Debian Linux所提供的各种软件包几乎都是开源的。如果软件不是开源的,几乎是不会被收录的。开源导致这些基础发行版系统特别容易被修改,这就为产生各种衍生发行版提供了条件。而类似Windows、iOS这样的系统由于版权及闭源,基本不会出现衍生的版本。

1.3.3 如何选择发行版

那么针对几种基础发行版和数百种衍生发行版,用户该如何选择呢?

基础发行版具有良好的发展环境以及相对完善的开发环境,这对于开发人员有非常大的吸引力,对一些喜欢追求新生事物以及同时需要大量参考资料的人也非常合适。

如果你想选一种发行版来进行教学、科学研究、搭建复杂的网络系统等,那么找一个针对这些方面的衍生发行版会更加高效和适用。

衍生发行版通常还会针对桌面、交互界面等用户直观感受的部分进行定制、开发,以吸引更多的用户,因此有些衍生发行版模仿其他非开源操作系统界面也就可以理解了。

很多衍生发行版是基础发行版一部分软件包的集合和定制,也会有针对性地增加基础发行版中没有提供的软件,包括一些闭源软件和商业软件。因此用户可能需要具备一些额外的条件才能使用,这也是选择衍生发行版还是基础发行版时需要考虑的。

有人认为某个衍生发行版比基础发行版的界面更好看,用起来更顺手;同样有人认为基础发行版更好用更完善。所以选择衍生发行版还是基础发行版完全取决于个人的需求,而不是哪种发行版更优秀。而作为本书移植的目标,选择一个相对完善的基础发行版是比较合适的。

1.4 Linux相关标准

Linux系统具有开放性,任何个人和组织都可以在其代码的任何层面上进行修改,这非常容易导致系统的分化,相互之间的不兼容。为了避免这类情况的发生,Linux系统遵循一些标准,从而保持不同的Linux发行版可以相互兼容。

本节主要介绍Linux中最常见的几种标准,后续在Linux制作中会根据这些标准增加一些步骤,步骤中会有相关的说明。

此处不对这些标准的内容细节进行说明,只对标准进行简单的介绍。

1.4.1 POSIX标准

POSIX是Linux中最早涉及的标准,POSIX的制定要早于Linux的出现,它是为UNIX系统制定的标准。Linux是类UNIX系统,所以也加入了POSIX标准的兼容。

POSIX是IEEE(Institute of Electrical and Electronics Engineers,电气和电子工程师协会)为了在各种UNIX系统上运行软件而定义API(Application Programming Interface,应用程序接口)的一系列互相关联的标准的总称。

POSIX是理查德·斯托曼应IEEE的要求而提议的一个易于记忆的名称。它是Portable Operating System Interface(可移植操作系统接口)的缩写,字母X表明其对UNIX API的传承。

POSIX在15份不同的文档中对操作系统与用户软件的接口进行了规范,主要内容包括3个部分:POSIX系统调用、POSIX命令和工具、POSIX兼容测试。同时POSIX还提供了一套POSIX兼容性测试工具,称为PCTS(POSIX Conformance Test Suite)。

1.4.2 LSB——Linux系统兼容的新起点

在20世纪90年代中期,开发人员也开始了实现Linux的标准化方面的工作。实际上,他们一直都在尝试使Linux遵守POSIX标准,因此Linux在源代码级上具有很好的兼容性,然而对于Linux来说,仅仅保证源代码级的兼容性还不能完全满足要求。

在UNIX时代,大部分系统使用的是专有的硬件,软件开发商必须负责将自己的应用程序从一个平台移植到其他平台上;每个系统的生命周期也很长,软件开发商可以投入足够的资源为各个平台发布二进制文件。然而对GNU/Linux系统来说情况则完全不一样,GNU/Linux发行版众多,各自的发展速度也很快,软件开发商不可能为每个发行版都发布一个二进制文件,这对Linux的标准化提出了一个新的要求:二进制兼容性,即二进制程序不需要重新编译,就可以在其他发行版上运行。LSB(Linux Standards Base,Linux核心标准)对各个库提供的接口以及与每个接口相关的数据结构和常量进行了定义。

1.4.3 FHS——文件存放标准

FHS(Filesystem Hierarchy Standard,文件系统层次化标准)是LSB中的一个组件,之所以将其单独提取出来介绍,是因为Linux社区中的第一个标准化努力就是针对文件系统层次的标准。FHS用于规范系统文件、工具和程序的存放位置及命名,还有系统中的目录层次结构。例如,ifconfig命令应该放在/usr/bin目录还是/usr/sbin目录中,光驱应该挂载到/mnt/cdrom还是/media/cdrom中。这些需求最终共同促进了FHS的诞生,也为LSB的发展奠定了基础。

POSIX是Linux一直遵循的标准,目的是实现代码的可移植性,很多符合POSIX的程序在不同的UNIX平台上重新编译后就可以正常运行,这正是得益于这些系统符合POSIX标准。而LSB的出现是为了控制数目庞大的Linux发行版出现分化的趋势,符合LSB的程序不需要重新编译就可以在任何符合LSB的Linux系统上运行起来。

POSIX、LSB和FHS等标准的出现,无论是对开源软件还是私有软件在Linux上的发展都起到了促进作用;有了这些标准,Linux上的软件在其他平台或Linux各个发行版之间的移植都变得更加容易。

1.5 本书的目标

自从龙芯2E发布后,龙芯平台上的Linux发行版就不断出现,从最初的华镭Linux,到共创、红旗Linux等国内大量基于Linux操作系统品牌为龙芯定制的衍生发行版,以及Loongnix等专门针对龙芯移植优化的衍生发行版,再到Debian的MIPS架构版本和Fedora的龙芯移植版本等软件包及其丰富的基础发行版,龙芯发行版的生态在不断的完善和优化中成长起来了。

虽然能在龙芯平台上使用的发行版越来越多,但仍然有一些需要注意的问题。一方面,因为各种原因,大部分在龙芯平台上使用的衍生发行版包含的软件版本、引入的开源技术或多或少存在滞后的现象。为了能让一些用户实现在龙芯平台上尝试新软件和新技术的想法,针对为数不多的基础发行版的移植就显得尤为重要。另一方面,基础发行版通常集合的都是开源软件,非常适合进行移植;而不少衍生发行版要么有一定的专用性,要么存在一些闭源软件,导致难以获得其修改部分的代码,并不适合拿来做示范。

本书将要介绍的就是把基础发行版之一的Fedora发行版移植到龙芯CPU上的方法和过程。选择Fedora发行版主要是因为其提供的软件都是开源的,且Fedora发行版官方没有提供基于龙芯CPU的版本。这样既适合用于介绍移植方法,也有一定的实际意义。还有一个原因是我自己成功移植过Fedora 21/28/32这3个版本的发行版,因此更熟悉方法。

我们通常说移植某个发行版到某个平台上,其实就是把该发行版的软件源代码包针对该平台重新进行编译和打包。这和制作一个新的衍生发行版很类似,如果要说不同的地方,大概有以下两点。

①衍生发行版大多跟基础发行版使用相同的架构平台,如基础发行版是x86_64架构的版本,衍生发行版大多也是x86_64架构的版本。而移植发行版必然和基础发行版使用的架构平台不同,如基础发行版没有龙芯CPU的版本,那么移植基础发行版就是要制作一个可以在龙芯CPU上运行的基础发行版。

②衍生发行版需要更多关注目标用户的需求实现,以及用户界面的修改等功能方面的问题。而移植发行版则需要考虑如何让发行版能在目标架构平台上正常运行起来。

当然移植发行版并不限于基础发行版,能合法获得并使用源代码的衍生发行版同样也适合拿来移植。

你也可以认为本书是在做一个适合用于讲解如何通过移植发行版来制作的衍生发行版。这句话也许有点绕口,至于是移植发行版还是做衍生发行版都不那么重要,重要的是理解和掌握方法。通过本书让我们来揭开移植以及制作Linux发行版的秘密,或许掌握了本书的内容后,你会发现无论是移植发行版还是制作衍生发行版,其实都不难。降低移植和制作一个发行版的门槛才是本书的真正目标。

1.6 版权,关于开源协议

说到GNU和Linux,大家就会想到开源,但是开源不代表可以没有约束的使用。为了践行“人人为我,我为人人”的合作共赢的思想,开源软件往往都会使用至少一种许可协议来保护软件本身,例如GPL协议。

大多数开源软件,特别是GNU项目的开源软件,都会在各自的软件源代码中加入开源协议文本,通常是以COPYING开头的文件形式存放在源代码目录中,我们使用某个开源软件的源代码时可以先了解一下该软件的开源协议。

GPL协议大致可以理解为使用了该软件包的源代码,那么修改后的源代码也必须是开源的,且继续保持GPL协议。从发行版的角度来说,这意味着使用了带有GPL协议的软件所制作的发行版,无论有没有对软件本身的代码进行修改,都必须以GPL协议把最终软件对应的源代码发布出来,允许任何人进行下载和再使用。

除了GPL协议之外还有其他许多开源协议,下列几种是常见的开源协议。

当然,协议本身是严肃严谨的内容,以上只是对协议的大致介绍,并不能代表协议本身的全部要求。使用者应当以协议本身所描述的条款为依据来使用开源的源代码,另外不同软件使用的协议不同,所以要以软件发布时所使用的协议为准,而不是自己想到哪个协议就用哪个协议,也不能自己想当然地变更软件的协议。

如果Linux发行版使用了大量GPL协议的软件,则应当遵守各个软件所使用的协议,将自己修改的内容也开源出来。这既是协议本身的要求,也是使用这种开源协议的开源软件应当遵守的行为准则。若可以将源代码提供给软件上游(软件所属的组织或个人),还可以更好地促进Linux发行版的进步与统一,避免出现兼容性的问题。

第02章 基础知识

在移植发行版之前,我们还是先要了解一些基础知识,这样会比较容易理解接下来的移植过程中的步骤和方法。

2.1 方案选择

无论是移植还是制作一个发行版,制作过程的方案并不是唯一的,本节将带你了解制作发行版的不同方案。

2.1.1 初始系统与目标系统

在即将开始移植一套发行版到龙芯机器上的时候,我不得不承认没有办法在一台什么系统都没有的龙芯机器上移植发行版。我需要一个满足要求的Linux操作系统,这个系统存放在什么介质上以及运行在什么机器上不重要,但必须有这个Linux系统。如果想凭空制作出一个操作系统是不现实的,这是不得不接受的现实。

事实上,目前已经很难有办法在一台裸PC(个人计算机)上直接制作一个操作系统了,当然也没有这个必要。Linux操作系统非常容易获取,所以我们也不再考虑用什么方法凭空制造一个操作系统。

我们要做的事情实际上是在一个现成的操作系统(称为初始系统)上逐步搭建出一个操作系统(称为目标系统)。

先来说一下目标系统,我们的目的很明确,就是在龙芯机器上运行一个Fedora Linux发行版,所以目标系统必须基于龙芯CPU的指令集进行编译,且最终运行在使用龙芯机器上。

再来说说初始系统,前面说了我们不得不接受的现实是必须要有一个初始系统才能够制作目标系统,但并没有明确这个初始系统运行在什么平台上。如果龙芯机器上有一个满足要求的Linux系统,那么我们可以将其作为初始系统。但如果龙芯机器上没有满足要求的Linux系统,又或者龙芯机器上的系统不具备制作新系统的某些条件呢?这时我们可以考虑用一个不在龙芯机器上运行的初始系统,这个初始系统可以是一个运行在采用x86或者Power处理器的机器上的Linux系统。

2.1.2 初始系统的基本要求

我们提到要移植一个目标系统需要一个满足要求的Linux系统,那么这个Linux系统需要具备什么条件呢?

我们需要一个通用的Linux系统,而不是一个为某个设备或者某个应用需求而专门定制的Linux系统。通用的Linux系统通常在PC或者服务器上使用,为了获得使用上的普适性,所集成的软件大多是较常用和常见的,这一要求可与我们制作过程中使用的命令更加符合。

这个Linux系统应当具备C/C++这些编程语言的编译器,我们需要使用GCCGNU Compiler CollectionGNU编译器套件,GNU项目中著名的开源编译器,包含了支持C/C++等多种语言的编译工具)来完成制作过程中的编译。虽然现在还有其他C/C++的编译器(如CLANG,另一个开源编译器),但是GCC的适用性更广,所以我们所需要的Linux系统必须具备GCC编译环境。

能处理源代码包的命令工具也是这个Linux系统所必须具备的。处理源代码包的具体命令与我们要移植的发行版有关,如果是基于RPM包格式的发行版,那么rpm命令是必需的。其他包格式也有类似的命令。如果没有专门的包格式,那么至少也需要能处理TAR包格式的命令。

基于上面对基本要求的描述,一个使用某个包格式的基础发行版会更适用,因为上述要求对基础发行版来说是最基本的。

2.1.3 目标系统的制作方法

在确定了初始系统的基本要求后,接下来就要考虑目标系统的制作方法了,第一个问题就是初始系统运行在什么硬件平台上。

这个问题显而易见有两种方向的考虑:在龙芯架构平台上运行,在其他架构平台上运行。这两种方向的选择带来了下一个问题:制作目标系统的方式。

1.初始系统运行在龙芯架构平台上

有一个满足要求并能在龙芯架构平台上运行的Linux系统,可以让我们方便地开始编译目标系统。这种直接在相同架构平台上制作目标系统的方式称为本地编译,这意味着只要有一台龙芯机器就可以开始制作了。

2.初始系统运行在其他架构平台上

如果在龙芯架构平台上没有满足要求的Linux系统,我们也不需要灰心。因为其他架构平台上满足要求的Linux系统非常容易获取,我们可以利用其他架构平台上的Linux系统来移植龙芯架构平台上的目标系统。这种在一种架构平台上制作另一种架构平台系统的方式称为交叉编译。

2.1.4 本地编译和交叉编译

无论是本地编译还是交叉编译,只要使用正确的方法都可以完成目标系统的制作。我们可以分析一下两者的优劣来决定如何选择。

1.优劣势分析

(1)本地编译的优劣势

优势
劣势

2)交叉编译的优劣势

优势
劣势
2.编译方案选择

从本地编译和交叉编译的优劣势来看,它们之间是相反的,也就是说当我们采用它们中的一种方案时必然会丢失另一种方案的优势,这多少有点可惜。但如果换一个角度来看,也可以说它们之间是互补的,也就是说我们需要想办法结合它们的优势来共同完成我们的目标系统。

从我们要制作的目标系统是一个Fedora Linux发行版来看,本地编译是必需的,因为交叉编译难以完成Fedora这种大型发行版提供的所有软件包,并且Fedora自身的包编译系统也无法很好地支持交叉编译。但如果从目标平台根本没有一个满足要求的Linux系统的角度来考虑,又不得不使用交叉编译。

所以我们采用一种按阶段划分的制作方案:使用交叉编译完成一个最基本的临时系统,利用这个临时系统使用本地编译来完成目标系统的制作。

编译方案考虑好了,还需要把要制作的目标系统更具体化一些。目前已经确定采用Fedora Linux发行版作为移植的目标系统,但其版本众多,不同版本所集成的软件包也不一样,我们采用比较新的Fedora 32版本。

选择Fedora 32版本没有什么特别的原因,只因为写作本书的时候它是最新的版本,但在未来的Fedora Linux系统以及各主要开源软件没有发生本质变化的情况下,移植的方法和原理也是通用的,也就是说读者移植Fedora 32之前或之后的版本在方法上没有什么大的不同,区别在一些具体的细节上,如软件版本、使用的补丁等,这些需要读者根据实际情况进行改动。

笔者在本书中尽力帮助读者一步步实现目标系统的制作,读者也应理解和掌握制作目标系统的思路和方法,这样才能真正掌握移植发行版的方法,不会被某个具体版本所束缚。

2.2 交叉编译的原理

既然已经确定采用交叉编译的方式制作临时系统,那么下面就让我们先来了解一下交叉编译吧。

2.2.1 了解CPU

在开始介绍交叉编译之前,先简单了解一下制作临时系统过程中涉及的与CPU相关的知识。

1CPU架构

(1)x86 CPU

x86(或80x86)是Intel公司首先开发制作的一种微处理器体系结构的泛称。

该系列CPU在较早期以数字来命名,结尾都是86,包括Intel 8086801868028680386以及80486,因此其架构被称为x86。因为数字不能作为商标注册,Intel公司及其竞争者生产的新一代CPU均使用了可注册的名称,如Pentium等。

不同型号的x86CPU是向下兼容的,为了区别它们之间的差别而使用不同的标识,如兼容80386表示为i386,之后依次是i486i586i686等。

80386开始,x86CPU进入了32位时代,一般把这个时期的CPU称为IA-32Intel公司之后又推出了IA-64CPU,但这种CPU不兼容IA-32,导致大量的32位软件不能运行。几乎同时,AMD公司推出了完全兼容32位指令集的64CPU,这类兼容CPU被称为x86_64Intel公司随后也推出了x86_64CPU。随着技术的发展,如今在x86架构中x86_64(即64位)的CPU已经成为主流。

(2)MIPS

MIPS(Microprocessor without Interlocked Piped Stages,无内部互锁流水级的微处理器)是世界上最早出现的一种RISC处理器,其机制是尽量利用软件办法避免流水线中的数据相关问题。它是在20世纪80年代初期由斯坦福大学的亨尼西(Hennessy)教授领导的研究小组研制出来的。

MIPS不仅在嵌入式领域被广泛应用,同时也应用于一些早期的工作站和计算机系统。

MIPS的系统结构及设计理念比较先进,其设计理念强调软硬件协同提高性能,简化硬件设计。

设计MIPS的厂家有许多,MIPS的指令实现也有所不同,可能会简化一些指令集,也可能会增加一些特有的指令集。龙芯采用了MIPS指令集并增加了许多独有的指令。

2.大端和小端的字节序

字节(Byte)序又称端序、尾序,英文为Endianness。在计算机科学领域,字节序是指存储多字节数据时各字节的存储顺序,典型的情况是整数在内存中的存储方式和在网络中的传输顺序。Endianness有时候也指位(bit)序。

字节序根据多个字节组成的数据在内存的地址低位的存储方式又分为两种类型:如果数据开始的字节(最高有效位)存储在内存的地址低位,则称为大端序(big-endian)或大尾序;相反,如果是最低有效位存储在内存的地址低位,则称为小端序(little-endian)或小尾序。

例如,单个字节是8位的数据0x0A0B0C0D,其存储情况如表2.1所示;单个字节是16位的数据0x0A0B0C0D,其存储情况如表2.2所示。

表2.1 8位存储方式

类型

地址增长方向→

大端序

……

0A

0B

0C

0D

……

小端序

……

0D

0C

0B

0A

……

表2.2 16位存储方式

类型

地址增长方向→

大端序

……

0A0B

0C0D

……

小端序

……

0C0D

0A0B

……

字节序只是一种数据存储方式,使用大端还是小端并无好坏之分。x86一般是小端设计,大多数的SPARCScalable Processor Architecture,可扩充处理器架构)处理器则是大端设计。ARMAdvanced RISC Machines)和MIPS是可配置的,但一般来说一款CPU设计出来后其大小端设计是固定的。龙芯采用的也是小端设计。

2.2.2 交叉编译的定义

所谓交叉编译,就是在一个架构平台系统上编译生成另一个架构平台系统上的程序文件(通常是CPU架构不同)。进行编译的平台系统被称为主平台系统,使用程序文件的平台系统被称为目标平台系统。

这里的平台系统指的是硬件体系结构和操作系统软件两个部分的合称。

体系结构也可称为架构,主要是通过CPU的指令集来进行区分;操作系统则通常以内核来进行区分。举例来说,体系结构常见的有x86MIPSARM等,操作系统有WindowsLinuxBSD等。

一种体系结构可以运行多种操作系统,一种操作系统也可能运行在不同的体系结构上,所以将者结合起来称呼一种平台系统,如x86 Linux表示x86上运行Linux系统,MIPS Linux表示MIPS上运行Linux系统。

交叉编译用于主平台系统和目标平台系统不兼容或不完全兼容的情况,与交叉编译对应的是本地编译。本地编译可以被认为是交叉编译的一个特例,在主平台系统和目标平台系统一致或者完全兼容的情况下,交叉编译即成为本地编译。

需要说明的是,兼容还是不兼容主要是以指令集和操作系统的内核来区分的,但不限于此。

2.2.3 交叉编译的适用范围

由本地编译和交叉编译的区别来看,交叉编译涉及的范围非常广,平台系统中的体系结构和操作系统有任何不兼容时就需要使用交叉编译。例如x86 Linux编译x86 BSDx86 Linux编译MIPS Linux或者x86 BSD编译MIPS Linux都应使用交叉编译。

交叉编译一般在难以在目标平台系统进行本地编译,或目标平台没有可以使用的系统等情况下使用。例如,本地编译会使用大的内存及存储空间,对CPU的性能要求也较高,但是一般早期ARM的计算机所配的内存和存储空间都比较小,有些只有几十兆字节,其CPU本身性能也不太高,嵌入式设备通常属于这种情况。这种计算机是无法或难以完成本地编译的,通常会采用x86下性能较好、内存和硬盘较大的计算机来进行交叉编译。

有些计算机虽然也是x86的,但性能较低,所配的内存、硬盘比较小,或有专门的用途,这时也会使用在另外一台性能强劲的计算机上为其交叉编译一个合适的系统的方式。

除了针对嵌入式设备常采用交叉编译外,移植系统这种使目标平台上的系统从无到有的制作过程也会用到交叉编译。

需要用到交叉编译的情况这里就不一一列举了,基本上不适合在目标平台编译的情况都适合使用交叉编译。

2.2.4 常用术语解释

为了帮助读者理解后续的制作过程,下面对一些常术语进行一些通俗的解释。

①主系统:制作Linux系统并不是在一无所有的裸机上完成的,需要一个可以帮助制作Linux系统的操作系统,这个系统就称为主系统。制作Linux系统是依靠这个系统来逐步完成的,主系统的选择非常重要。

本地系统(本地平台系统):正在使用的硬件平台(主要是CPU指令集)与其上运行系统的组合。

当前系统(当前平台系统):含义同本地系统(本地平台系统

④目标系统:要完成的系统。

目标平台系统:指所运行目标系统的硬件特性与目标系统本身的组合。

编译工具:本书GNU Binutils(一组二进制工具集)、GCC(GNU编译器套件合称为编译工具。

⑦工具链:本书Binutils、GCC和Glibc(标准C函数库)的组合称为工具链,有时候也会将一些需要用到的函数库作为工具链的一部分;使用工具链生成的可执行文件总是使用该工具链中的函数库。

交叉编译工具/交叉工具链:用于生成目标平台系统二进制程序文件的编译工具/工具链。

辅助命令:在编译软件包的过程中,除了工具链以外还需要一些命令的参与,如make,这些命令被合称为辅助命令。

运行环境:在一个运行的系统中可以存在多个不同的环境,这些环境有各自的根目录及环境设置,这样的环境被称为运行环境。主系统就是一个运行环境,在某个目录中存在一个系统,用chroot命令切换到该目录将会建立一个新的运行环境,其中运行的是以该目录作为根目录的系统。

编译环境:工具链连同辅助命令的合称。

交叉编译环境:交叉工具链和辅助命令的合称。

头文件:用于编译的一类文件,一般以.h作为文件的扩展名,存储了许多函数的接口描述、结构体信息等与程序设计相关的内容。

2.2.5 交叉编译目标系统

我们先来了解如何交叉编译目标平台计算机编码的二进制程序文件(简称程序文件)。

一个程序文件的生成需要该程序的源代码和一个编译环境,编译环境中的编译工具完成对源代码的编译并生成程序文件。交叉编译则使用交叉编译工具将源代码编译成目标平台的程序文件,过程如图2.1所示。

2.1 交叉编译的示意图

在简单说明了如何交叉编译程序文件后,下面将对交叉编译过程中的关键技术点进行讲解。

1buildhosttarget

交叉编译中经常会见到3个参数:buildhosttarget,它们对于创建交叉编译环境至关重要,想要正确搭建交叉工具链就必须对它们有所了解。

3个参数通常在编译软件包的过程中指定,下面分别对它们进行解释。

下面以编译GCC为例,介绍如何使用这3个参数。

./configure --build=编译平台 --host=运行平台 --target=目标平台 [各种编译参数]

(1)build参数

该参数表示当前运行的平台的名称,如果是采用Intel/AMD CPU的计算机,则build可能是x86_64-pc-linux-gnu;如果是龙芯3A计算机的Linux系统,则可能是mips64el- unknown-linux-gnu

build在不指定的情况下将自动尝试猜测当前平台的名称。

(2)host参数

该参数表示GCC在何种平台下运行。交叉编译时必须指定该参数;当前平台系统无法知道目标平台系统是什么,必须明确告诉它。如果程序需要运行在龙芯CPU上,可以指定hostmips64el-unknown-linux-gnu

host可以不指定,它将自动使用build探测出来的平台名称来定义,不过这就不是交叉编译了。

只有buildhost的设置不同时才被配置文件认定为交叉编译,这可以通过配置代码分析出来,下面是一段从GCCconfigure文件中截取的代码。

if test "x$host_alias" != x; then
  if test "x$build_alias" = x; then
    cross_compiling=maybe
  elif test "x$build_alias" != "x$host_alias"; then
    cross_compiling=yes
  fi
fi

其中,"x$build_alias" != "x$host_alias"用于判断buildhost是否相同,如果不相同则认为是交叉编译。

(3)target参数

该参数是让配置程序知道该软件用于处理何种平台系统上的文件。

./configure --help中经常能看到该参数的说明,然而target只在为数不多的几个软件包中有用,绝大多数软件包都不需要该参数。

如果不指定target,则会使用host的平台设置来定义要处理的平台。

GCCBinutilstarget不同于host时,则称GCCBinutils为交叉编译工具,也就是说targethost如果相同,则GCCBinutils是本地编译工具。

(4)交叉编译方式和交叉工具链

从上文对buildhosttarget的简单描述中我们可以看到,host不同于build是交叉编译,而target不同于host是交叉编译工具。这两者的差别可以理解为动词和名词,交叉编译是一个动作,即进行交叉编译的动作;而交叉编译工具是一个名词,用于说明这是一个进行交叉编译的编译工具。

(5)平台系统参数的指定

buildhosttarget指定平台系统参数的方式都一样,以多个连字符(-)表示分隔的字符串,常见的为x86_64-pc-linux-gnu,将其分解开为x86_64pclinux和gnu。

上述内容说明,x86_64-pc-linux-gnu实际上由3个部分组成。

如果在配置过程中不指定平台系统参数则会进行自动探测,一般由软件包中的config.guess和config.sub脚本进行。脚本会根据当前系统的uname等命令的返回值来进行判断,并最终返回猜测结果。猜测结果通常是准确的。如果计算机或系统十分特别,则可能判断错误,这时就必须使用手工指定的方式。

还需要说明的是,上面列举的hostbuildtarget以及configureconfig.guess和config.sub等均是GNU的标准处理方式,软件包的配置脚本通过autoconfautomakGNU自动化代码配置工具制作而成。其他不使用GNU代码配置工具的软件包设置这3个参数的方式会有所不同,但原理基本是一致的。读者需要根据具体的软件包来设置,一般设置这3个参数的方式中都会带有hostbuild和target的大写或小写字样。

(6)小结

2.Binutils和GCC

交叉编译程序文件过程中必须要有一套交叉编译环境,这套交叉编译环境由主系统参与完成。主系统应具备正常的编译工具链,用于搭建编译目标系统的环境,即主系统能够创建交叉编译环境。

创建交叉编译环境最为重要的步骤就是建立交叉工具链,而工具链中最重要的两个元素为编译器和汇编器,其对应两个软件包GCCBinutils。下面简单了解一下如何编译这两个软件包。

1)前提

建立交叉工具链的前提是主系统中必须存在正常的编译环境,主系统编译环境和即将制作的交叉工具链中的GCCBinutils的版本差距不能太大,建议大版本号相同,否则可能无法正确地完成交叉工具链中GCCBinutils的编译过程。

2)作用

BinutilsGCC都是为了生成目标平台上的代码。主系统通常没有针对目标平台的交叉编译工具,必须将它们建立起来,否则交叉编译无从谈起。

3)使用方式

交叉编译生成目标平台的系统。一般当前平台无法运行目标平台代码程序,因此交叉编译工具一定是运行在当前平台上(否则也就不叫交叉编译)。在当前平台上进行代码的编译、汇编及链接,生成的二进制文件在目标平台系统上运行,这些二进制文件在当前平台系统上可能无法运行。

4)参数的指定

交叉编译工具需要确定3个平台参数,target已经非常明确,指定为目标平台系统,buildhost根据之前的介绍可知都是当前平台系统。

5)编译

交叉编译工具BinutilsGCC都由主系统的工具链来生成。完整的GCC会依赖目标系统的C库,无法在目标系统的C库完成前就生成完整的GCC,但目标系统的C库又需要交叉编译工具来生成。要解决这个相互依赖的问题,需要使用一些交叉编译的技巧。

先产生一个功能相对简单的GCC编译器,它不需要目标系统C库即可完成,该编译器不能编译常规的程序,但可用于完成C库的编译;完成C库的编译后再次编译GCC,生成一个功能相对全面的GCC编译器,这个过程我们会在实际的制作过程中具体了解。

不关注具体过程的情况下,主系统编译生成交叉编译工具BinutilsGCC的过程如图2.2所示。

2.2 生成交叉编译工具BinutilsGCC

从图2.2可以看出,交叉编译工具BinutilsGCC本身不是交叉编译,而是本地编译,由BinutilsGCC的交叉版本编译生成程序的过程才是交叉编译。

3.交叉工具链的Glibc

下面介绍工具链中的另一重要部分——C函数库,通常采用Glibc。使用BinutilsGCCGlibc的组合是十分常见的,制作交叉工具链也依然使用该组合。

对于交叉工具链而言,BinutilsGCC必须运行在本地平台系统上,编译链接的对象是目标平台系统,因此链接的C函数库必然是目标平台系统才能正常链接,C函数库必须采用交叉工具链进行编译。

4.其他函数库文件

Glibc一般称为基础库,一个工具链有了基础库就可以完成一部分程序的编译,但这往往是不够的。有一些程序还需要链接Glibc之外的其他函数库,例如ZlibNcurses等,所以我们可以通过带有基础库的工具链编译这些库文件来不断完善工具链,这样工具链可以用于编译更多更复杂的软件。

交叉工具链也是一样,编译好了相对完整的GCC后,就可以编译一些常用的库文件,以完善我们制作目标平台系统的需求。

5.交叉编译环境

交叉编译工具BinutilsGCC制作出来后就可用于生成目标系统。编译一个软件包并不是只靠汇编和编译器就可完成,这个过程还需要大量的辅助命令参与。这些辅助命令大多是帮助完成编译过程中的某些步骤的,不会产生目标平台相关的代码文件,因此可以直接使用主系统中的辅助命令,这样就构成一个完整的交叉编译环境。

如果要构建的目标系统使用比较新的软件版本,而主系统中的一些命令不能正常处理某些步骤,此时就要根据实际情况在当前系统中安装一些较新的软件包。

2.2.6 Sysroot与DESTDIR

在交叉编译目标系统的过程中,我们会用到两个非常有用的参数:Sysroot(命令中使用sysroot)和DESTDIR。这两个参数都是为了目标系统的根目录与主系统的根目录和谐共存而设定的,都是设置路径,只是使用的地方不同。下面对这两个参数分别进行介绍。

1.Sysroot

Sysroot通常用在编译程序和处理文件时,该参数可以理解为程序运行时的行为参数。在编译程序和处理文件的时候,这个参数会在原路径前加上该参数设置的路径;而被编译的程序或被处理的文件并不会知道这个参数的存在,对于它们来说路径依旧是原来的路径。

简单理解Sysroot:例如编译及链接时默认路径是${sysroot}/usr${sysroot}/usr/lib${sysroot}/usr/include等形式,默认情况下${sysroot}是空字符串,就成了/usr/usr/lib/usr/include等目录。如果指定sysroot变量,例如sysroot=/cross,则路径就变为/cross /usr/cross/usr/lib/cross/usr/include等目录,编译链接时,头文件以及函数库文件将以Sysroot指定的目录为基础进行查找。

Sysroot所表示的目录不会用于目标系统实际的运行过程,仅在编译和链接的时候或者主系统中的某个程序处理目标系统中的文件时使用。因此只要Sysroot设置为目标系统的存放目录即可,这样就很好地解决了制作的目标系统根目录与当前系统所使用的根目录相同的矛盾,这个就是Sysroot的主要作用。

如果将Sysroot理解为申请文件和读取文件之间的分隔线,那么Sysroot未设置或设置为空时默认以根目录为基础目录来读取文件,如图2.3所示。

图2.3 Sysroot未设置或为空时文件读取示意图

当把Sysroot设置为目标系统存放目录时,将会以目标系统存放目录为基础目录来读取需要的文件,如图2.4所示。

Sysroot概念只存在于制作目标系统的相关软件包中,如GCCBinutils。另外还有一些用来进行配置默认路径下的文件等操作的软件包,可能会用到与Sysroot类似概念的参数。但这些软件包运行在主系统中,完成目标系统的制作后不会参与目标系统的运行。

读者还需要充分理解的是,若为目标平台系统编译了BinutilsGCC,而它们是目标平台系统上运行的软件,需要在目标平台上才能运行,在当前系统中它们并不主动参与整个交叉编译目标平台系统的过程,因此它们不需要Sysroot参数,它们是被当前系统的交叉工具链通过Sysroot处理的对象。

图2.4 Sysroot设置指定目录时文件读取示意图

2.DESTDIR

DESTDIR是一个比Sysroot更为常用的参数,该参数在将软件包安装到系统中的时候使用,软件包会安装到以该参数设置的路径为基础的目录中。

事实上无论有没有设置DESTDIR,软件安装的时候都会用到该参数。没有设置DESTDIR时相当于使用空字符串,这样就会按照prefix设置的目录来进行安装;当设置了DESTDIR时,则会在prefix设置的目录基础上再加入该参数设置的路径来进行安装。

通常软件包的安装命令如下:

make install

使用DESTDIR来指定安装目录,安装命令如下:

mmake DESTDIR=/cross install

prefix指定为/usr时,若不使用DESTDIR,则软件包将安装在/usr目录下;当指定DESTDIR/cross后,软件包将安装在/cross/usr目录下。

可将make install理解为make DESTDIR= install。

Sysroot一样,可将DESTDIR理解为一个申请安装和实际安装之间的分隔线。在DESTDIR未设置或设置为空时,将会以当前根目录为基础目录来将文件安装到相应的目录中,如图2.5所示。

图2.5 DESTDIR未设置或设置为空时操作示意图

DESTDIR设置为某个目录时,如目标系统存放目录,安装的过程将在DESTDIR所指定的目录中进行,就如同将DESTDIR所指定的目录作为根目录,如图2.6所示。

2.6 DESTDIR设置为指定目录时操作示意图

DESTDIR还有一个特别的用处,在我们把某个软件编译完成后,如果直接安装在系统里,那么安装了什么文件,安装在什么地方,我们很难了解完整。但是巧妙地使用DESTDIR参数就可以使我们了解清楚这些情况,例如使用如下命令:

make DESTDIR=${PWD}/dest install

其中,${PWD}变量是当前目录,DESTDIR=${PWD}/dest的意思就是把当前软件包中将安装到系统中的所有文件都安装到当前目录下的dest目录中,并且在dest目录中完整地复制了安装到系统中的目录结构。这个时候我们就可以很好地掌握这个软件的安装情况。

制作系统的时候好好掌握和利用这个参数可以让我们更清晰地了解系统的整体结构,并且DESTDIR具有指定安装路径的特性,使其成为包管理工具用来制作包文件的重要手段。

另外,某些软件的安装脚本会通过DESTDIR的设置与否来判断是交叉编译还是本地编译,若是本地编译可能会执行某些操作。此时我们就可以通过该参数来改变软件包安装的行为,使其符合我们的要求。

DESTDIR配合Sysroot可以很好地解决目标系统的配置、编译和安装过程中与主系统在目录问题上的矛盾。

并不是所有的软件包都使用DESTDIR指定基础目录,大多数GNU软件都会使用DESTDIR,通常使用AutoconfAutomake工具集生成的配置文件也会使用DESTDIR作为基础目录设置。也有一些软件包使用ROOT这样的参数作为指定基础目录的参数。读者要根据软件的实际情况使用相关参数,如果无法了解参数,可以参考软件包提供的说明文件以及检查Makefile文件中对于安装部分的设置,从中获得信息。

某些软件包的Makefile中不带基础目录设置,这时可以手工安装文件,也可以修改Makefile文件中与安装相关的部分,加入基础目录的设置再进行安装。

2.3 Fedora Linux发行版

本小节中将介绍本书的主角——Fedora Linux发行版。

2.3.1 Fedora Linux简介

Fedora Linux是一款由全球社区爱好者构建的面向日常应用的快速、稳定、强大的操作系统。

Fedora Linux最早由RedHat公司发起,是一款用来验证新技术的发行版。Fedora Linux最早基于RedHat Linux,在RedHat Linux终止发行后,RedHat公司便以Fedora Linux来取代RedHat Linux,应用于个人领域,另外发行RedHat Enterprise Linux(RHELRedHat 企业版Linux),应用于商业领域。

Fedora 社区十分活跃,使Fedora Linux也紧跟技术的进步。因其强大的定制性,有许多Linux系统厂家会基于Fedora Linux开发自己的Linux系统,目前已经出现大量基于Fedora Linux的衍生发行版,用于各种不同的领域。

2003年发布第一版到现在,Fedora Linux目前已经发布了32个版本,大约每6个月发布一个新版本Fedora Linux6版的名字为Fedora Core,所以Fedora Linux的简称会写成FC1FC2;从第7版开始名字改为Fedora。前20版每个版本都有一个代号,从第21版开始不再使用代号,直接使用数字作为标识,如Fedora 21Fedora 28 ,分别简称为F21F28,有些场合也可继续称为FC21,FC28

2.3.2 Fedora Linux的包管理工具

Fedora Linux最重要的特征就在包管理工具上,其实不光是Fedora,基础发行版的一个很重要的特征就在包管理工具上。Fedora Linux使用的包管理工具是DNF/YUM,包格式是RPM

RPMRPM Package Manager,最开始是RedHat Package Manager)是RedHat公司为其发行版设计的包格式,Fedora Linux并不是最早使用RPM格式的发行版,Fedora Linux的前身(Red Hat Linux)已经开始使用RPM作为其发行版的包格式,并且使用YUM来管理软件仓库,用于安装、更新及删除软件包。

YUM(Yellow dog Updater, Modified)包管理工具可以方便地通过系统设置的软件仓库地址和本地存放的rpm数据库来管理当前系统的软件包,可以安装、升级和删除系统的各个软件包。YUM包管理工具作为RedHat/Fedora Linux的主流包管理工具使用了10多年,但YUM的效率随着软件仓库中软件包数量的不断增多而越来越低下,因此出现了DNF

DNFDandified yum)包管理工具最早出现在Fedora 18这个版本中,之后不断地改进和成熟,到Fedora 22版本中则作为可以取代YUM的工具成为默认的包管理工具。相对于YUMDNF包依赖关系的分析性能更加高效,支持插件对功能的扩展,还提供了对YUM的高度兼容,即可以将yum的命令参数用于dnf命令中。

2.3.3 DNF软件仓库工具

DNF工具主要用来从软件仓库中搜索、下载和安装RPM文件,该工具功能非常多,下面介绍一下该工具在制作移植系统的过程中用到的主要功能。

1.安装软件包

安装软件包应当是包管理工具最基本的工作了,dnf命令可以通过不同的访问协议方式从软件仓库中下载软件包并进行安装。协议可以通过配置文件(/etc/yum.repos.d/目录中的多个文件)或命令行指定参数的方式设置,常见的有HTTPHTTPS协议(网络下载)与本地目录文件的协议。

举个例子,我们要安装GCC这个软件包,可以使用如下命令:

dnf install gcc

该命令会从配置文件中获取软件仓库的地址,并通过地址中的协议来下载指定的软件包,同时dnf命令会分析该软件包所依赖的软件包。例如,安装GCC的时候会依赖Binutilsdnf命令分析出这个依赖需求后会将Binutils软件包也作为需要安装的一部分进行安装;当用户确认安装行为后,dnf命令会下载GCCBinutils以及可能的其他依赖包,然后对下载的文件进行校验,若没问题则执行安装到系统的操作。

在安装的命令中,如果只是想下载指定的软件包以及该软件包所依赖的软件,而不是安装到系统里的话,可以使用如下命令:

dnf install --downloadonly gcc

使用--downloadonly参数不会将安装包安装到系统中,而只是将RPM文件下载到本地系统RPM文件的缓存目录中,我们可以到对应的目录中查找下载的文件。这种仅下载的模式有时非常有用,例如可以把某个软件包及其需要的依赖文件一起下载下来,然后复制到另外一台系统相同但可能不能联网下载的机器上进行安装。

使用install参数进行软件包安装的前提是指定的软件包并没有安装到系统中,若已经安装过则不会再进行安装。但若当前系统的某个软件包安装的文件遭到破坏需要重新安装时,就需要使用reinstall参数了,其用法如下:

dnf reinstall gcc

使用reinstall参数进行安装则无论指定的软件包是否被安装过都将重新进行安装,这种方式非常适合用来修复损坏的软件包。

当然,除了安装软件包,dnf命令还可以用来删除已安装的软件包,例如我们想删除Wget这个软件包,使用如下命令:

dnf remove wget

只要当前系统安装了Wget软件包,那么这条命令将提示用户删除该软件包,若用户同意则会进行删除,该软件包所包含的文件会从当前的系统中被清除。

2.安装系统

dnf命令可以同时指定安装多个软件包,例如要同时安装TarVimZip软件包,可以使用如下命令:

dnf install tar vim gzip

多个软件包之间使用空格进行分隔,该命令会同时分析指定的安装包所依赖的软件包并将其一起安装到系统中。

如果打算安装一组相关的软件包,我们可以使用组标记来描述软件包,例如安装基础开发环境的命令:

dnf install @c-development

@标记的含义是软件包组,即该标记直接跟着的字符串代表了组的名字,该组必须是软件仓库中存在的组,若组存在则dnf命令将把该组中包含的所有软件包当成指定安装的软件包并一起进行安装。

还可以指定多个组一起安装,每个组都用@符号进行标记即可,个组之间使用空格进行分隔。

既然dnf命令可以一次安装多个软件包和多个组的软件包,那么也就是可以通过几组软件包来安装一个系统,例如使用如下命令:

dnf install @core @c-development --installroot /opt/distro

其中,@core所代表的软件组包含了系统基本的程序命令和库文件,可以作为一个基本的系统进行使用,同时还安装了一组开发用的软件包,那么这个系统就具备了基本的编译开发能力--installroot参数则用来指定安装的软件包以哪个目录为基础,如果没有指定就是当前系统的根目录,这里指定了/opt/distro目录,则相当于在该目录中安装了一个基本系统。

3.软件包及源代码包的下载

除了安装软件,dnf命令还可以作为RPM文件的下载工具,例如想下载Wget软件包的RPM文件,但是又不想安装到系统中,可以使用如下命令:

dnf download wget

如果软件仓库中有WgetRPM文件,那么无论当前系统中是否安装了Wget,这条命令都会将WgetRPM文件下载到当前的目录中,但我们也要注意,该命令仅下载指定的RPM文件,该RPM文件所依赖的软件包并不会一同下载。

Fedora Linux发行版是开源的,dnf命令除了可以下载安装包,也可以下载指定软件包对应的源代码包。

使用dnf命令来下载源代码包非常简单,Fedora系统的软件仓库设置中就包含了源代码部分的内容,只要设置的源中包含了源代码就可以方便地进行下载。

例如我们要下载GCC的源代码包,使用如下命令:

dnf download --source gcc

如果网络和源速度够快,很快就会把一个GCC软件包的源代码包下载下来,Fedora Linux的源代码包都是以.src.rpm为扩展名的文件。

这里我们还需要了解一点,由于一个源代码包编译后会生成多个RPM文件,因此指定下载源代码包时指定这个源代码包生成的任意RPM文件的名字都可以,例如我们使用下面的命令下载源代码包。

dnf download --source readline-devel

其下载的是Readline软件包的源代码包,因为readline-devel这个RPM文件是编译Readline软件包时生成的。

4.更新软件包和系统

系统使用一段时间后一般都会有软件需要更新,更新的方式也是通过dnf命令来进行的。例如更新Wget软件包,可以使用如下命令:

dnf upgrade wget

更新的来源还是软件仓库,若软件仓库中的Wget软件包有新版本,那么该命令将提示用户安装新版本的软件包,若用户同意则会更新到最新版本。

更新不但可以针对指定的软件包,也可以针对整个系统。若想把整个系统都进行更新,可以使用不指定具体软件包的命令。

dnf upgrade

该命令将针对当前系统已安装的所有软件包进行检查,若发现有新版本则提示用户进行更新。对于当前系统未安装的软件包,即使软件仓库中的软件更新了,该命令也不会提示用户。

若更新的软件包出现了问题,那么还可以对指定软件包进行降级,例如同样是Wget软件包,我们对其进行降级安装。

dnf downgrade wget

这个时候若软件源中有比当前系统安装的版本低的安装包,则会提示用户是否降级安装Wget软件包;若软件仓库中没有更低版本的安装包,则不会进行任何变动。

需要注意的是升级可以针对整个系统,但是降级只能针对具体指定的软件包。

5.查询功能

软件仓库中包含了大量的软件,经常会发生用户想做某件事情却不知道应安装什么软件的情况。肯定无法一个一个查看软件仓库中的软件,因此自然会使用搜索查询的方式来获取需要的信息。

可以在dnf命令中使用search参数进行查询,例如要查询一个与PDF相关的软件,可以使用如下命令:

dnf search PDF

使用search参数时并不需要后面跟具体的软件包名称,该命令会将软件仓库中在名称、简介中出现PDF(忽略大小写)字样的软件包列出来供用户查看。用户可以从这些列出来的软件包中再筛选自己需要的软件包,然后安装到系统中。

除了通过关键字查询软件包之外,dnf命令还支持一种对开发人员非常有用的查询方式,即通过具体路径的文件名查找软件包。

我们知道某个程序的执行命令,但是不知道包含该命令的软件包的名称,于是就可以通过dnf命令进行查询。例如查找/usr/bin/rx这个命令所在的软件包,使用如下命令:

dnf provides /usr/bin/rx

此时会反馈出软件仓库中包含了该程序文件的软件包名称,根据反馈的信息安装软件包即可。

这个方法不仅适合可执行的程序,任何通过安装进入系统的文件都可以采用这个方式进行查询。例如在开发过程中出现某个头文件找不到的情况时,就可以通过该方式查找包含该头文件的软件包名并进行安装,这样就可以解决这个找不到文件的问题。

6.安装源代码包的编译依赖

在获取了软件包的源代码包后,若要进行编译,那么也要让当前系统满足编译指定软件包的条件,主要是当前系统应该安装了指定源代码包所需要的各种软件包。

源代码包的编译命令会根据当前系统中的软件包安装信息进行分析,并告诉用户缺少哪些软件包,用户可以通过dnf命令的安装功能一个一个地进行安装,但这比较麻烦。dnf命令支持对RPM格式的源代码包文件进行依赖分析功能,例如有一个foo-1.1-1.src.rpm源代码包,可以使用如下命令:

dnf builddep foo-1.1-1.src.rpm

该命令中的builddep参数告诉dnf命令要分析指定的源代码包所需要的依赖环境,这个时候dnf命令就会通过软件仓库查找当前系统缺少的依赖包,以及这些依赖包所依赖的软件包。若都能找到,则提示用户进行安装;若缺少某个依赖包,则会提示缺少的内容,以便用户进一步进行处理。

builddep参数不仅支持具体的RPM源代码包文件,还支持指定一个软件包的名字或者是源代码包中的spec配置文件。

7.其他功能

DNF工具提供的功能还有很多,这里不一一介绍了,感兴趣的读者可以使用命令的帮助参数dnf --help来获取各种参数的说明,也可以使用man dnf或者info dnf命令查看该命令的说明文档。

本书在介绍移植Fedora发行版到龙芯机器的过程中需要用到的所有软件包都是从软件仓库里下载下来进行编译的。

2.3.4 RPM文件简介

Fedora Linux的软件包,包括源代码包都是以RPM格式打包的,以.rpm为扩展名,称为RPM文件或者RPM包文件。我们需要了解一下这个软件包格式,实际上RPM格式的软件包是由一组信息和一组文件共同组成的:信息是必须存在的,这些信息称为元数据;而文件却是可有可无的,如果一个RPM包文件中不存在任何文件,通常称为虚包。

Fedora Linux的一个RPM包的文件名通常包括5个部分。

[软件名]-[版本号]-[修订次数].[发行版名称].[文件类型].rpm

例如软件包gcc-10.0.1-0.11.fc32.src.rpm,对应的各个部分如下:

软件名:gcc。该名称通常遵从该软件官方的名字,如gcc;但也有一些软件会在Fedora中修改名称,例如Linux内核官方的名字是linux,而在Fedora中则改名为kernel。

版本号:10.0.1。这通常是软件本身的版本号,对该软件本身来说具有唯一性,如果官方没有发布软件版本号,则可以使用类似git中的commit号等唯一编号作为版本号。

修订次数:0.11。这个数字通常是发行版自行定义和修改的,通常是随着该RPM包进行修改时增加的数字。

发行版名称:fc32。它是Fedora 32的缩写。当然我们可以根据需要修改发行版名称,那么重新打包的时候将会变更该部分的内容。

文件类型:若类型是src,说明该文件是一个源代码包,通过源代码包的编译可以生成x86_64mips64el这样的二进制架构类型的包,这些架构类型的包只能用在对应架构的机器上。还有一种noarch类型的包,也是经源代码包编译才会生成的,但是noarch类型的包没有与架构相关的内容,可以用在任何架构上。

RPM文件是一种包含了格式信息的文件,这里简单地探寻一下RPM包文件。假设存在一个foo-1.1-1.src.rpm文件,在Linux系统中,我们使用file命令查看一下该文件:

file foo-1.1-1.src.rpm

会返回类似下面的信息:

foo-1.1-1.src.rpm: RPM v3.0 src

我们可以从返回内容中获得的信息是,该文件是一个RPM格式的文件,且RPM的格式是3.0。所以说RPM包文件是有版本的,从第一代的RPM格式文件到现在的RPM格式文件是发生过变化的,目前基本都是使用3.0版本了。

RPM包文件版本的后面有一个src的标记,代表这是一个源代码包。如果我们查看的是一个二进制的包,例如wget-1.19.5-5.fc28.lemote.mips64el.rpm,则后面可能的信息是bin MIPSel,其可解读为二进制包,MIPS小端架构。

再进一步,我们可以通过rpm命令来查看RPM包文件的元数据,例如使用如下命令:

rpm -qp --info wget-1.19.5-5.fc28.lemote.mips64el.rpm

描述信息通常会返回如下内容(内容仅供参考):

Name        : wget
Version     : 1.19.5
Release     : 5.fc28.lemote
Architecture: mips64el
Install Date: (not installed)
Group       : Applications/Internet
Size        : 2976068
License     : GPLv3+
Signature   : RSA/SHA1, 2018年11月15日 星期四 16时52分03秒, Key ID 058b1a9ec70507b2
Source RPM  : wget-1.19.5-5.fc28.lemote.src.rpm
Build Date  : 2018年10月27日 星期六 01时01分27秒
Build Host  : sunhaiyong
Relocations : (not relocatable)
URL         : http://www.gnu.org/software/wget/
Summary     : A utility for retrieving files using the HTTP or FTP protocols
Description :
GNU Wget is a file retrieval utility which can use either the HTTP or
FTP protocols. Wget features include the ability to work in the
background while you are logged out, recursive retrieval of
directories, file name wildcard matching, remote file timestamp
storage and comparison, use of Rest with FTP servers and Range with
HTTP servers to retrieve files over slow or unstable connections,
support for Proxy servers, and configurability.

描述信息包含了软件名称(Name)、版本(Version)、创建时间(Build Date)、创建该文件所使用的源代码包(Source RPM,如果查询的是一个源代码包则此内容为none)以及软件包说明(Description,对软件包功能的简单描述)等基本内容。

如果查询参数中没有使用p参数,那么rpm命令会读取当前系统已安装的软件包信息,如果有符合指定名字的软件包安装在系统中,则显示该软件包的描述信息,当然部分内容会跟指定文件不同。例如对于已安装的软件包,在安装时间(Install Date)中会显示该软件包是什么时候安装到系统中的;如果是查询文件则会显示“(not installed)”,表示没有安装。

一个RPM包所具有的元数据远不止“描述信息”中的这些内容,还有大量的元数据用来描述软件包。如--requires参数会显示该软件包所依赖的运行环境,那么在使用dnf命令安装该软件包时会利用该依赖信息来安装依赖的软件包,对应的--provides则显示该软件包被依赖的信息。dnf命令安装其他软件时如果依赖该软件包,则会把该软件包一起安装到系统里。

RPM包文件还有大量的元数据,包管理工具就是通过软件包的元数据来进行整个操作系统的软件包管理的。

2.3.5 操作包文件的命令

了解了RPM格式文件的基本信息后,下面介绍与RPM格式文件最为密切的命令:rpm,对RPM格式文件的操作大多是直接或间接使用该命令完成的。

rpm命令就是针对RPM包文件进行操作的命令工具,是一个针对本地系统的RPM包文件管理工具,主要用于对存放在本地目录中的RPM包文件进行安装、卸载以及查看RPM包文件信息等相关操作。

1.安装RPM包文件

前面我们介绍了如何使用dnf命令安装软件包,这些软件包都是RPM格式的文件,因此也可以通过rpm命令进行安装。但是rpm命令只能用来安装本地目录中的文件,不能直接像dnf命令那样从网络下载RPM文件进行安装。且采用rpm命令进行安装时,虽然也会判断依赖是否满足条件,但是并不会尝试解决依赖问题,也就是说安装某个RPM包文件时若当前系统缺少所需要的依赖会提示用户,但不会像dnf命令那样去尝试找到缺少的依赖包并一起安装。

使用rpm命令安装RPM文件时必须写明文件所在路径以及完整的文件名,可以使用通配符,也可以一次安装多个RPM文件。以下是使用rpm命令安装的例子。

rpm -i foo-1.1-1.mips64el.rpm

若当前目录中存在foo-1.1-1.mips64el.rpm文件,且当前系统满足安装该文件的依赖包的条件,那么该文件会被安装到系统中,若正常安装不会有任何输出提示。

如果在该命令的基础上加上-v参数,会输出安装RPM文件的名字;如果想加入安装进度条,可以再加上-h参数,这样安装过程会比较清晰。例如命令:

rpm -ivh foo-1.1-1.mips64el.rpm

会产生类似如下的输出:

准备中...                            ################################# [100%]
正在升级/安装... 1: foo-1.1-1.mips64el.rpm ################################# [ 50%]

如果当前系统中已经安装了该软件包,要安装的版本高于系统已经安装的版本,可以使用-U参数代替-i参数,这样安装新软件包文件的同时会删除已安装软件包中所安装的文件。

dnf命令可以重新安装软件包来修复损坏的文件,rpm命令也同样可以做到。安装时使用--reinstall参数将会重新安装指定的RPM文件,若当前没有安装该软件包则等同于新安装。

使用通配符*和?可以匹配多个RPM文件,如gcc-*或者*-libs*等表达方式,例如:

rpm -ivh *libs*.mips64el.rpm

可能会匹配到多个文件名包含libs且扩展名是mips64el.rpm的文件,使用-v参数就可以在随后的输出中看到哪些文件被安装到系统中。

可以在一条rpm命令中指定多个RPM文件,每个文件使用空格分隔,而每个指定的RPM文件也可以使用通配符描述多个文件名有共同特征的文件。

rpm命令有一个特殊的功能,即可以安装软件源代码包,而dnf命令可以下载软件源代码包但不安装,安装源代码包使用rpm命令。rpm命令处理RPM格式源代码包文件的过程比较特殊,源代码包文件并不会像其他RPM包文件一样进行安装,而是会解压后存放到当前用户的用户目录中,安装的目录为~/rpmbuild,会在其中创建SPECSSOURCES两个目录,并将源代码包中扩展名是.spec的描述文件存放在SPECS目录中,其他文件都放在SOURCES目录中。

2.卸载已安装的RPM格式软件包

除了安装软件包,rpm命令也用来卸载RPM格式软件包。该命令使用-e参数卸载软件包,该参数与安装参数类似,可以配合-v-h参数来显示卸载的软件包和名称,例如使用如下命令:

rpm -evh wget

这条命令将检查当前系统是否有依赖Wget的软件包,如果有则会提示用户但不删除,若没有软件依赖Wget软件包,则立即卸载Wget软件包。因使用了-v-h参数,输出信息将给出卸载进度和提示信息。

我们看到卸载不需要指定完整的文件名,只需要指定软件包的名称即可,因为此时rpm命令和dnf命令类似,是从系统的RPM数据库中查找指定的软件包是否安装了。如果没有安装,则提示没有该软件;如果安装了,则再进一步判断是否可以卸载。

卸载软件包不使用通配符,但可以一次指定多个软件包的名称,适合一次卸载多个相互间有依赖关联的软件包。

3.强制类参数

无论是安装RPM文件还是卸载已安装的软件包,可能都会碰到依赖无法解决的情况。如果确定可以进行安装或卸载,那么就需要让rpm命令知道我们对此操作的确认,这时候就需要用到强制类参数。

1)依赖性强制参数

解决依赖问题的强制类参数是--nodeps,该参数将忽略所有提示的依赖错误而继续进行安装或者卸载,这在制作移植系统的初期会经常用到,例如安装时的命令:

rpm -ivh gcc-*.rpm --nodeps

该命令可以安装所有gcc开头的RPM文件,忽略任何依赖问题。若所有文件的依赖都满足时,--nodeps参数没有任何作用。但在有文件存在依赖错误的情况下,若不加该参数则安装过程会取消,所有指定的文件都不会被安装;若使用该参数,则所有文件都将进行安装。

在卸载软件包的时候也是一样,卸载的软件包可能被其他软件包所使用,那么卸载操作会导致出现依赖错误提示而取消卸载,但可以使用该参数来强制卸载,例如命令:

rpm -e wget --nodeps

在系统中有其他软件包依赖了Wget软件包提供的功能的情况下,若没有设置--nodeps参数,该卸载操作将被终止;若使用了该参数,则会直接卸载Wget软件包而不理会其他程序是否依赖于它。

从上面的介绍我们可以看到,使用--nodeps参数具有一定的风险,无论是安装还是卸载都会导致系统依赖的缺失。安装时被安装的软件存在缺少依赖的情况而可能无法使用,而强制卸载某个软件包则可能导致依赖它的软件包无法正常工作,因此如不是特殊制作阶段或者特殊情况请尽量避免该参数的使用。

2)冲突性强制参数

除了依赖问题,在安装RPM文件的过程中可能还会出现冲突,冲突一般有两种情况,一种是软件包级别的冲突,另一种是文件级别的冲突。

软件包级别的冲突一般属于依赖性问题,但即使使用--nodeps参数来解决依赖问题,多数情况下也会出现另一种冲突,即文件级别的冲突。接下来我们就来关注一下文件级别的冲突。

当有两个RPM包文件安装的一个或多个目录及文件名都相同但内容不同的文件的时候,无论这两个文件是一个已经安装在系统中另一个正在安装,还是正在同时安装这两个文件,rpm命令都会给出文件冲突的报告并取消安装,因为该问题不属于依赖问题,所以使用--nodeps参数并不会忽略该问题。

此时需要另一个强制参数:--force,该参数会忽略冲突而继续进行安装。若是新安装的软件包和已安装的软件包发生冲突,则该参数会强制将新的软件包所安装的文件覆盖已安装的软件包的原有文件;但如果冲突发生在指定安装的两个RPM包文件之间时,就是先指定安装的软件包中的文件覆盖后指定安装的软件包中的相同路径和名字的文件。

还有一种出现在同一个软件包上的冲突,就是当某个已经安装的软件包在其源代码包中进行了修改并重新编译了新版本后,这个新版本在安装的时候会提示与原软件包中的文件存在差异而冲突。这种情况有几种解决方式:

虽然有上面几种强制解决冲突的方式,但是强制解决冲突所使用的都是覆盖安装,这通常具有风险。特别是针对不同软件包之间冲突的覆盖,很可能导致被覆盖的软件包工作异常,因此要谨慎使用强制参数。通常来讲,针对软件包不同版本之间的冲突可以使用--force参数,但不同软件包之间应该避免使用,并找出冲突的原因然后避免和解决冲突。

4.查询指定文件所属的软件包

一个系统包含了大量的文件,这些文件大多是由各个软件包安装到系统中的。如果想了解某个文件是哪个软件包安装的要如何做呢?

使用rpm命令可以轻松地获取任何一个以安装方式进入系统的文件的原来归属软件包名称,例如要查询/bin/ping这个程序文件来自哪个软件包,可以使用如下命令:

rpm -qf /bin/ping

可能的返回是

iputils-20190515-5.fc32.mips64el

我们主要看软件包的名称即可,可以获得的信息就是该命令来自于iputils软件包,了解这些信息后就可以有的放矢地做一些改动了。

5.查看RPM文件的信息

我们知道RPM文件是带有数据格式的文件,在其所包含的数据信息中有很多有用的内容。通过rpm命令可以获取大多数的重要信息,这对我们了解RPM文件非常有帮助。

使用rpm命令既可以查看已经安装的软件包,也可以查看未安装的RPM包文件。例如查看已安装到系统中的iputils软件包的信息,可以使用如下命令:

rpm -q --info iputils

其中-q参数代表查询的意思。若查询一个RPM包文件,则增加-p参数,并且要指定完整的路径文件名,例如:

rpm -qp --info foo-1.1-1.fc32.mips64el.rpm

之前我们使用--info参数看过RPM文件的基本信息,那么还可以获取什么信息呢?

我们可以通过rpm命令的帮助来查看。

rpm --help

该命令会输出RPM的各种参数,这些参数在前文也有过介绍,读者若有兴趣可以逐一阅读说明,这里介绍跟RPM文件信息相关的一些主要参数。

更多的参数这里就不一一介绍了,详细内容可以参考man rpm和info rpm的说明。

2.3.6 RPM包文件构建工具

当了解了Fedora Linux是由RPM包构成的之后,我们不禁要问这种RPM格式的包是如何生成的呢?它使用rpmbuild命令来生成。

rpmbuild命令是专门用来制作RPM格式包的工具,它提供了最基础的制作功能,即使一些高级的制作工具也会调用该命令来完成RPM包文件的制作。

rpmbuild命令通过指定RPM源代码包文件的方式进行RPM包的制作,但RPM中必须包含一个以.spec为扩展名的文件。该文件描述了该源代码包的构建过程,rpmbuild命令通过该文件来生成RPM包文件,这个.spec文件我们通常称为SPEC描述文件。

除了RPM格式的源代码包,rpmbuild命令也支持从普通的TAR格式的打包文件或压缩打包文件中读取SPEC描述文件进行编译,前提条件是打包文件中包含SPEC描述文件。

rpmbuild命令还可以通过直接指定一个SPEC描述文件的方式进行RPM包的制作,事实上指定RPM源代码包文件或TAR格式打包文件的方式也就是解压缩文件,然后再指定编译其中的SPEC描述文件,直接指定SPEC描述文件少了解压缩文件的过程,但后续的制作过程是一致的。

虽然指定源代码包和SPEC描述文件的制作过程本质上是一样的,但是使用的参数会有所不同。举个例子,要编译Tar软件包的源代码包,可以使用如下命令:

rpmbuild --rebuild tar-1.32-4.fc32.src.rpm

前面介绍过,可以通过rpm命令安装RPM源代码包,例如安装Bash软件包的源代码包后会在当前用户的用户目录中产生SOURCESSPECS两个目录,其中SPECS目录用来存放Bash源代码包所包含的SPEC描述文件。rpmbuild命令指定SPEC描述文件进行制作的命令如下:

rpmbuild -bb ~/rpmbuild/SPECS/bash.spec

我们可以看到,SPEC描述文件通常都采用软件包名称再加上以.spec作为扩展名的文件名,如bash.spec。

rpmbuild命令编译一个软件包时无论是使用源代码包本身还是使用SPEC描述文件,都存在如下几个阶段。

以上阶段在rpmbuild命令中可以分别用一个字母进行描述:检查依赖阶段为r、预处理阶段为p、编译阶段为c、安装阶段为i、文件校验阶段为l、打包阶段为b、重建RPM源代码包阶段为s,以上全部阶段可以用a来表示。

rpmbuild命令对指定文件的不同类型使用不同的参数来对应,指定SPEC描述文件时使用-b参数,指定RPM格式源代码包时使用-r参数,指定包含SPEC描述文件的TAR格式源代码包时使用-t参数。

TAR格式的源代码包本书基本不会涉及,我们不进行详解,读者只要知道rpmbuild命令操作使用的参数和RPM文件中必须包含SPEC描述文件即可。

通过操作指定文件类型参数和阶段参数,我们就可以采用rpmbuild命令按照我们的要求处理各个阶段,这对于调试和开发都非常有帮助。

例如我们只想完成编译,那么可以使用如下命令:

rpmbuild -bc foo.spec

当编译完成后即结束命令,并不会进行安装或者打包RPM文件的过程,此时我们可以进入编译目录中进行调试等操作。

各个阶段有的独立也有的相互关联。独立阶段的含义是当命令指定该阶段时将仅执行该阶段要做的工作,例如s阶段;而关联阶段则存在一定的顺序,即指定后续的阶段将默认把前面关联的阶段一并执行,例如b阶段,会从r阶段开始依次执行pcil阶段,然后再执行b阶段。

对于关联阶段,若想只执行其中的某个阶段,可以使用参数--short-circuit,该参数可以跳过pci3个阶段。

直接指定RPM格式源代码包的参数-r对各个阶段的操作与指定SPEC描述文件的参数-b的含义和指定方式完全一致。

指定RPM格式源代码包还可以使用一些特有的参数,例如--rebuild参数仅针对RPM格式源代码包,其作用相当于-rb参数或指定SPEC描述文件时的-bb参数。

下面介绍一下rpmbuild命令常用的参数。

想了解更多有关rpmbuild命令的内容,可以使用man rpmbuild或者info rpmbuild命令进行查询。

2.3.7 SPEC描述文件介绍

通过对rpmbuild命令的了解,我们知道RPM文件包的核心文件就是SPEC描述文件中的内容。下面就对SPEC描述文件中的主要内容进行介绍,看看它是如何控制制作RPM文件的过程的。

SPEC描述文件简单来说分为多个部分,每个部分使用一个既定的名称进行定义,每个名称开头以%符号作为标记,标记通常分为3类:默认标记、控制类标记和可选标记。

默认标记不用专门描述出来,相当于在文件开始处设置了一个不带任何名字的package标记。默认标记用来定义该源代码包的基本属性,如软件包名称、版本号和主页地址等,还包括了RPM源代码包的所有文件的列表,这些文件是重新打包RPM源代码包时打包哪些文件的依据。

SPEC描述文件通常把包含的文件分为两类:源代码类文件和补丁文件,分别使用SourcePatch加上数字编号来定义。如有需要,可以增加相应的源代码文件和补丁文件,只要编号不重复即可。

默认标记中还包括了制作依赖条件,使用BuildRequires来定义,可以多次定义依赖条件。rpmbuild命令会通过定义的依赖条件来判断制作条件是否满足,如果不满足会提示用户,用户可以使用--nodeps忽略不满足的条件。默认标记中也可以定义制作出来的RPM文件的安装依赖条件,这些条件用Requires来定义。与BuildRequires不同,Requires定义的依赖不在rpmbuild命令中检查,而是会写入RPM文件中,在使用rpm命令安装RPM文件的时候进行检查。

除了依赖条件的定义外,还可以定义RPM的附加导出关键字,实际上依赖关系的查询就是通过各个RPM文件包导出的关键字来进行的。在打包阶段,RPM文件中会写入一些默认生成的关键字,如果觉得不够可以自行使用Provides定义附加的关键字,这些关键字会一并写入生成的RPM文件中。

除了默认标记外,rpmbuild命令通过SPEC描述文件制作RPM文件包需要控制类标记的参与。这些控制类标记主要有如下几个。

一个RPM源代码包可能会生成多个RPM文件就是因为在SPEC描述文件中定义了多个files标记。

除控制类标记外,还有一些可选标记来帮助我们完善制作的RPM文件,这些标记主要有如下几个。

带名字的package标记中通常不定义版本号、软件包主页,这些信息与默认标记中的定义共用;一般只定义该标记所对应生成的RPM文件的依赖关系,同样用BuildRequires和Requires来定义,还可以加入导出附加关键字的定义Provides。

packagedescriptionfiles3个标记所带的名字是相互对应的,不带名字的情况下就代表了与软件包名相同的RPM文件包所定义的依赖关系、简介和打包文件列表等信息,而如果加上名字,则3个标记都使用相同名字的作为一组,定义了对应名字的RPM文件所包含的依赖、简介和文件列表。

还有几个标记也同样使用名字来对不同的RPM文件包进行定义,我们简单地了解一下它们的用处。

对于SPEC描述文件,我们应当了解它的组成结构与rpmbuild命令的各个阶段的对应关系以及其中的各种标记的含义。制作移植系统的过程中难免会需要修改某些软件包的SPEC描述文件,或者制作原来没有的软件包时也可能需要编写SPEC描述文件,总之熟悉SPEC描述文件对我们制作和移植以RPM包管理为主的系统非常有用。

2.3.8 Fedora Linux实用网站

本书介绍的内容有限,如果读者希望多了解一些内容,还需要借助网络资源,下面介绍一些与Fedora Linux相关的网站,希望能够给读者的制作和移植操作提供一定的帮助。

1Fedora官方网站

这个网站中提供了包括Fedora系统的介绍、各类文档的下载等大量的资源。如果想研究Fedora系统,那么官方网站必然是不可缺少的信息来源。

2RPM包文件搜索和查询

网站地址:http://rpmfind.net/

该网站提供了对RPM包中的文件名、依赖关系、关键字等的检索信息。在不知道某个文件或者某个RPM文件包是谁提供的时,不妨在这个网站搜索一下试试看。

2.4 软件包配置常见参数

我们在编译软件包的时候经常要对软件包进行配置,通常使用configure配置脚本。该配置脚本一般都有大量的配置参数,下面就对大型编译工程中的各个软件包经常使用的参数进行讲解。

configure使用的参数通常都以--来表示。为了简化说明文字,下面的正文介绍中部分参数没有加--符号,读者要知道在实际使用中程序中需要增加。

configure的参数非常多,下面只介绍一些常见的参数。若想了解更多的参数及其用处,可以在软件包目录中使用下面的命令来获取:

./configure --help

读者可以根据输出的信息了解该软件包所支持的参数及参数的用处。

configure中,参数通常被分为6种类型:安装路径设置、程序名称定义、平台系统定义、功能开关选项、软件设置选项和环境变量,下面对这6种类型的参数进行介绍。

2.4.1 安装路径设置

通常配置一个软件包会涉及各种安装目录,下面这些参数都与安装目录的设置有关。

综上所述,该参数通常在将--prefix设置为/usr时会显式地设置为/etc,这是为了满足LSB的要求。但若是制作临时系统,除非一些特殊的软件包,通常不用设置该参数,使用默认的路径即可。

与路径有关的参数还是比较多的,例如与头文件相关的--includedir,与数据文件相关的--datadir,还有与手册文件相关的--docdir--mandir--htmldir等。这些参数大部分都使用默认的路径设置,不用专门指定,这里就不再一一介绍了,读者若有兴趣可以查询相关的说明。

2.4.2 程序名称定义

程序名称定义的参数用来对软件包编译生成的命令程序进行名称修改,通常是设置名称的前后缀以标明程序的特点或处理目标特征。相关参数一般有如下3个。

例如s,grub,grub2,的参数设置是将命令程序文件名中的grub改成了grub2。这个修改无法通过设置前后缀的方式解决,因为要改的内容在文件名的中间,此时通过表达式来修改就非常容易了。

2.4.3 平台系统定义

平台系统的定义通常只有两三个,实际上我们已经非常熟悉这些参数了。

3个参数在讲解交叉编译的时候反复出现,所以这里不再赘述。读者除了要熟悉这3个参数的含义外,还要知道任何一个编译过程都一定包含--build--host两个参数,而--target仅在部分软件包中具有意义,通常这类软件包是用来处理平台系统的相关文件的。

2.4.4 功能开关选项和软件设置选项

对于各个软件包来说,功能开关选项和软件设置选项部分的参数差异是非常大的,但是它们有一个共同的特征,就是功能开关选项使用--enable和--disable来启用和关闭选项,而软件设置选项则使用--with和--without来启用或关闭选项。

看着configure里数量众多的选项参数,且参数有的用--enable/--disable,有的用--with/--without,会让人觉得很混乱。

其实大多数比较规范的软件在选项以何种方式进行设置上是有一定规律的,我们下面就简单地归纳一下参数设置的规律。

根据名称我们可以进行大致的分类。

软件设置选项的参数也可以理解成一种行为的选择,即选择使用某个行为或者不使用某个行为,抑或是多种行为方式选择其中一种,但不管如何选择都不影响功能要达成的目的。这就好比我们要制作一个球体,可以使用木头材质,也可以使用金属,可以是空心的,也可以是实心的,但要达成的目的就是制作一个球体。

不同的选择可能导致处理性能的优化路径或其他一些动态的内容需要按照实际情况进行指定,也可能当前系统只能使用某种方法来处理。

举例说明如下。

例如在GCC软件包的参数中--enable-languages就是一个功能选项,设置的参数是支持语言的列表;在列表中的语言被启用,没有列出的则不启用。例如FORTRAN为支持语言,在列表中加上它才可以编译FORTRAN语言写的程序,若不加上则无法编译。

在制作临时系统时会使用--disable-nls设置一些软件,该参数根据语言环境的设置,输出对应的自然语言提示信息。这个参数如果关闭,则只能使用软件包的默认语言进行输出。

再例如GCC参数中的--with-nan=2008就是一个设置选项,该选项为程序代码中对浮点数的处理标准,可以选择2008标准,也可以选择Legacy标准,总之处理浮点数的功能是能达成的。

做个通俗但可能不太恰当的比喻:功能开关选项是某个事情你可以选择做还是不做;而软件设置选项是这个事情是要做的,但是你可以选择怎么做或者跳过某些步骤来做。

当然上述参数的规律不一定适合所有软件,也不是绝对的,读者可以在今后的制作过程中体会这些参数的含义。

2.4.5 环境变量

在配置过程中,环境变量也起到了至关重要的作用。通常情况下我们应该注意一些默认的环境变量对配置过程的影响,避免意外错误的发生。

例如CFLAGS这个环境变量,它在大多数软件包的配置和编译过程中都用来设置编译器使用的编译参数,所以如果设置的内容不合适可能导致编译错误。例如CFLAGS设置了-mabi=32,会导致强制使用32ABI进行编译,但如果我们没注意到这个环境变量设置的内容,则想编译64位程序的想法就可能出现变化。

那么有哪些环境变量会对配置过程有影响呢?

环境变量和配置参数类似,不同的软件包可使用的环境变量是不完全相同的,我们可以通过软件包提供的./configure --help查询。虽然各个软件包使用的环境变量不完全相同,但还是有一些通用的环境变量,下面我们就简单地罗列一些通用的环境变量。

还有很多衍生形式的环境变量,例如GCC软件包会使用BUILD_CCHOST_CC这类为不同目的使用的编译器设置的环境变量,也有CFLAGS_FOR_TARGET这种特殊阶段使用的环境变量。

对环境变量不要随意设置,应当在清楚环境变量会带来的影响后有明确目的地使用,以免造成不必要的问题。

相关图书

Linux常用命令自学手册
Linux常用命令自学手册
庖丁解牛Linux操作系统分析
庖丁解牛Linux操作系统分析
Linux后端开发工程实践
Linux后端开发工程实践
轻松学Linux:从Manjaro到Arch Linux
轻松学Linux:从Manjaro到Arch Linux
Linux高性能网络详解:从DPDK、RDMA到XDP
Linux高性能网络详解:从DPDK、RDMA到XDP
跟老韩学Linux架构(基础篇)
跟老韩学Linux架构(基础篇)

相关文章

相关课程