4.4 源代码编译安装必知必会

4.4.1 源代码文件

大家经常说开源,到底什么是开源呢?所谓开源,就是开放源代码的简称,对于应用软件而言,其关键就是源代码文件。有了源代码文件就可以修改及定制,这里以Linux环境最常用也最基本的C源代码文件为例来介绍源代码文件的优势。

基于C语言开发的源代码文件就是一个最简单的ASCII码文本文件,当然扩展名通常为*.c,以和文本文件的*.txt相区别,其实二者没有本质的差异,下面就来看一个C源代码文件。

这就是一个最为简单的C程序的源代码文件,一定要牢记C程序总是从main函数开始执行的。多数Linux程序的源代码都比较复杂,一个开源项目可能由成百上千个C源代码文件组成。

4.4.2 开源编译器GCC

要将C源代码变成二进制可执行文件,离不开编译器,最早的开源编译器是GCC。GCC是自由软件基金会刚成立不久时推出的一款开源编译器,开放源代码和开源编译器缺一不可,最新版本是GCC 7.3。使用GCC不一定非要学过编译原理,因为编译原理比较复杂,而GCC则比较简单,虽然有200多个参数,但常用的也不过那么几个,许多参数即使是C程序员也很难遇到,更不用说系统使用者了。GCC项目的Logo如图4-5所示。

图4-5 GCC项目的Logo

GCC就是将ASCII码的C源代码文件从高级语言C编译为低级语言(如汇编语言和机器语言)的一个过程。C语言之所以是高级语言,主要是因为其与硬件平台无关且人类对其更容易理解,而低级语言,如汇编和机器语言,都与特定的硬件相关(X86的汇编语言和ARM或PowerPC的汇编是不同的),且对于人类而言都非常难懂。于是就有了编译器,它可以帮助人类将易懂且与平台无关的C源代码编译为相应的汇编语言和机器语言。能在一个硬件平台编译其他硬件平台的代码的编译器被称为交叉编译器,如在X86硬件体系中编译ARM的可执行代码。此外,开源世界中的C编译器也不止GCC一种,还有后起之秀LLVM(Low Level Virtual Machine)和Clang。Clang是LLVM的一个编译器前端,支持C、C++、Objective-C以及Objective-C++。LLVM项目的Logo如图4-6所示。

图4-6 LLVM项目的Logo

LLVM是继GCC之后, 在学术界和产业界都有很大影响力的开源编译器,需要声明的是,这里对Clang仅做了解,本章重点讨论GCC,更多关于LLVM的信息请访问其官方主页:https://llvm.org/。

Clang官方主页:https://clang.llvm.org/。

官方文档地址:https://llvm.org/docs/。

4.4.3 四步从源代码到可执行文件

这里假设C的源代码文件没有错误(通常是不可能的),可以直接编译通过,但即便如此,C的源代码文件还是一大堆ASCII文本文件,根本无法运行,这就需要编译器和相关的C头文件(header file)来编译,通过编译将文本文件的源代码构建成一个可执行文件。需要强调的是,此处用到的编译器是指GNU的GCC(GNU C Compiler)编译器,而C的头文件则是指Linux环境的头文件。之所以要强调这些,是因为Linux环境的C编译器有很多,头文件也有很多,并且可执行文件格式为ELF/ELF64(64位平台),ELF是执行和链接格式(Elecutable and Linking Format)的缩略词,计算机世界存在很多可执行文件格式,但Linux环境可执行文件格式大多是ELF/ELF64,一定要和Windows环境的EXE可执行文件格式进行区别和切割。

由于C语言属于高级计算机语言(人类可读懂),故其生成可执行文件的大致思路是通过编译器转化为一系列低级机器语言指令。这里的低级语言是指X86的汇编语言,然后将汇编语言指令按照可执行目标程序的格式打包并以二进制磁盘文件形式保存到Linux的文件系统中,这时的可执行程序其实处于一种休眠状态,等待处理器将其调入内存并执行,一旦被处理器调用,程序就变得生龙活虎了。

具体过程只有四个步骤:编译预处理、编译过程(防止混淆,区别于大家常说的编译)、汇编和链接。首先是编译预处理,主要是指预处理器(CPP)根据源程序中以字符“#”开头的命令,修改源程序,将所需要的头文件加入源代码文件,经过预处理的源代码文件通常以“*.i”作为扩展名。预处理器主要关注C源代码文件中的#define(宏定义指令)、#include(头文件包含指令)、#if、#elif(条件编译指令)等关键字。

源代码编译安装环境的搭建,运行如下命令即可搭建一个最为基本的编译环境:

build-essential是编译软件工具及相关头文件的集合,实际应用中仅有build-essential是不够的,还需要很多库才可以顺利完成编译安装,由于这里还没有用到,所以先不考虑这些。

有了编译环境就可以走的更远、研究的更深,可执行如下命令查看源代码文件hello.c在预处理(CPP)后的变化:

运行如下命令查看hello.i文件:

从上述结果可以看出,编译预处理只是对源文件进行了扩展,如添加相应的头文件、宏替换等,编译预处理后仍然是C源码文件,只是文件变长了许多。

编译预处理后就进入到编译过程,即编译器(CCL)将经过预处理器处理得到的C源代码文件hello.i“翻译”为相应的汇编语言,并将所有汇编语句保存到hello.s文件中,其中包含汇编语言程序。汇编语言是一种与硬件相关的低级程序语言,对于人类而言具有最基本的可读性,比二进制代码易读,可以运行以下命令进行编译:

再次运行cat命令,查看hello.s文件:

这次所看到的就是汇编代码了,看起来像天书一样。

编译过程成功后,汇编器(AS)将hello.s再次“翻译”为只有计算机可以读懂的目标机器指令文件,目标文件中至少有代码段(rx)和数据段(rwx),并以*.o为文件扩展名,实现命令如下:

汇编之后的文件已经不能使用cat命令打开了,因为此时已经是二进制文件了,不过这还没完,还需要链接才能成为真正的可执行文件。

链接主要是链接程序(LD)将有关的目标文件链接起来,如多个C源码文件编译后生成多个目标文件,一个文件中引用的符号同该符号在另一个文件中的定义连接起来,使得所有的目标文件成为一个能够被操作系统执行的统一整体。至此,C的源代码就变身为ELF格式的可执行文件了,链接的关键操作如下:

最终得到了可执行程序hello,该程序能够在终端中运行并打印Hello world!字符串,上述编译其实可以通过如下命令完成:

4.4.4 Linux中的编译安装

前面做了那么多铺垫,主要是为了在Linux中通过源代码安装程序,安装过程也不复杂,包括经典的三大步骤,即配置、编译和安装。需要说明的是,hello.c是最简单的C程序,只有一个源文件,而Linux中可用的开源软件如MySQL或Apache等,由于实现的功能复杂,大多都是由成千上万个C源码文件构成的,而且每个源码文件不是三五行,而是百行千行甚至是万行,所以编译起来也不容易,由于实在是复杂,所以,就由Makefile文件来规划整个编译流程,不但要正确编译所有源码文件,还要保持高效、开源。项目越来越复杂,手工编写Makefile也变得越来越复杂,最后只能借助脚本来生成相应的Makefile文件,然后在所生成Makefile的指引下开始编译应用程序,完成编译后再将编译好的程序存储在Linux文件系统的相应目录中。下面以Wireshark为例来加深对编译安装的理解。

需要强调的是,编译过程中可能需要大量的第三方函数库,无论是静态库还是动态库,都需要提前安装好,且版本匹配。否则,configure配置过程将会报错,因为不满足编译所需要的环境,故无法生成项目的Makefile文件,make也就无从着手进行编译了。

Tips:动态库和静态库。

动态库又称为动态链接库,类似于Windows环境的DLL文件。动态库代码在可执行程序运行时才载入内存,在编译时仅做简单引用,将动态库与程序代码独立就可以提高代码的可复用度,并且可以降低程序的耦合度,因此,所生成代码的体积比较小。而静态库则在编译过程中为库代码载入可执行程序,因此体积比较大。动态库和静态库的关键区别在于二进制代码被载入的时机不同。需要知道的是,源代码编译安装时,使用动态库的概率要远远高于静态库。

前面已经提到的文件系统层次结构标准FHS,规定了大部分动态链接库文件应该存储到/usr/lib目录下,唯一例外的情况是,某些库是在系统启动时加载的,则保存到/lib目录下,而那些不是系统本身的部分库,则放置在/usr/local/lib目录下,所以,/lib、/usr/lib和/usr/local/lib目录就是静态链接库的大本营,需要牢记,且多数动态链接库以*.so为扩展名,遇到此类文件时要特别小心,不要轻易修改或删除。

当Linux程序运行时,由于静态库在程序编译时会被链接到目标代码中,程序运行时直接调用该静态库,而动态库在程序编译时并不会被链接到目标代码中,而是在程序运行时才被载入,因此,在程序运行时还需要动态库的存在,在没有找到所需要的相应动态库或动态库版本不对时,程序都会报错。

1. 准备工作

安装Wireshark所需要的库,使编译过程更加顺利,关键操作如下:

2. 编译过程

接下来就开始编译Wireshark了,具体操作如下。

运行如下命令下载Wireshark源代码:

成功下载后执行下列命令解压解包:

3. 配置Wireshark源码

通过./configure可执行脚本来创建Makefile:

成功创建Makefile之后,就可以在此文件的指引下将源码编译为可执行文件,关键操作如下:

4. 权限配置

安装后还要配置权限,让普通用户也可以使用Wireshark抓包功能,具体操作如下:

成功安装后,还需要在~/.bashrc中更新系统的环境变量,具体操作如下:

为了方便使用,加入启动图标:

成功创建Wireshark图标之后,从文件管理器打开/usr/share/applications目录,将鲨鱼鳍图标复制到桌面,复制后原来的图标会变样,但只需要双击运行,在随后弹出的对话框中选择信任此应用,图标就会恢复原样。不仅是Wireshark,几乎所有的图形化应用都可以如法炮制,创建桌面快捷方式。

上述就是一个从源代码编译安装的完整实例,希望大家能熟练掌握,在后面的服务器章节中还会有更为复杂的编译内核,以及大型软件的安装实例等着大家去实践。