现在由于发行的系统拥有预编译的二进制文件仓库,可以下载使用。通过编译源码生成软件主要有两个原因:
-
可用性(Availability)。尽管在发行版本仓库中有许多预编译程序,一些发行版本未必会包含全部的期望应用。在这种情况下,只有通过编译源码的方式来得到期望的程序。
-
及时(Timeliness)。虽然某些发行版包括了程序的最新版本,但是许多并不是这样的。这意味着想要拥有最新版本就需要编译程序。
有时候从源码编译软件非常复杂与专业,远远超出了许多用户的能力范围。但是,大多数编译任务只需要简单的几个调用步骤就可以完成。这却决于包。首先看一个简单的例子作为深入学习的开始。其中,要引入一个命令:
- make —Utility to maintain programs.
What Is Compiling?
简单来讲,编译是一个将源码翻译成机器语言的过程。
计算级处理器(CPU)在底层通过机器语言(machine language)执行程序。有一些数码描述一些非常细节的操作,例如,“拷贝这个字节”,“加上这个字节” 或 “指向此内存地址”。每个指令都由 二进制 来表示。早期程序都是由这些二进制码组成的。
随着 汇编语言(assembly language)出现克服了这个问题。通过简单的替换数码为容易的字符记忆法(mnemonics)例如 CPY(copy),MOV(move)。通过使用名为 assembler 程序 来处理 使用汇编写出的程序 为 机器语言。汇编语言至今为止还是使用在一些特殊的编程任务上,例如 设备驱动以及嵌入式中。
接下来就是 高级语言(high-level programming languages)的出现。它可以使得程序员屏蔽掉诸如处理器正在做什么的执行细节并解决手边的问题。最早的一批是(1950年)包括 FORTRAN (设计用于科学和技术任务),COBOL(商业应用),两者今天仍在有限使用中。
尽管现在有很多受欢迎的语言,有两种是占有优势。现代操作系统中大多数程序是由 C语言 或者 c++ 语言书写的。
高级语言书写的程序经由编译器(compiler)进行处理转化为机器语言。一些编译器将高级语言翻译成汇编语言,接下来使用 assembler 来执行转化成机器语言这最后的步骤。
通常与编译结合使用的一个过程称为链接。程序中要执行许多常规任务。例如,打开文件。许多程序都需要执行者一个操作,但是如果每个程序都要将其实现是非常浪费的。因此 库(libraries)提供了常规操作供多个程序来共享。称为链接(linker)的程序用于在编译器的输出与已编译程序所需的库之间形成连接。此过程的最终结果是可使用的可执行程序文件(executable program file)。
Are All Programs Compiled?
一些程序(例如 shell 脚本)不需要编译可以直接执行。他们都由 脚本语言(或者解释语言)来完成的。这些语言近年来越来越流行,包括 Perl,Python,PHP,Ruby 一起其他的语言。
脚本语言是由一个名为 解释器(interpreter)的特殊的程序来执行的。解释器输入一个程序文件并读取然后执行其中的每条指令。相比于编译程序,解释程序执行速度比较慢。这是因为解释程序中的每个源代码指令每次执行时都会被翻译,而对于已编译程序,源代码指令仅被翻译一次,并且该翻译永久记录在最终的可执行文件中。
为什么解释语言如此受欢迎?对于大多数编程作业来讲,能够 “足够快” 的得到结果,但是真实原因是相比于编译程序能够更快更简单的开发解释程序。开发程序经常以 编码,编译,测试不断地循环来完成。随着程序大小不断增大,编译将会耗费很长时间。解释语言将编译步骤去掉从而加速开发速度。
Compiling a C Program
为了完成编译,需要一些工具软件,例如 编译器,链接器以及make。在Linux中C语言编译器通常使用 gcc (GNU C Compiler),使用 Richard Stallman 编写。大多数发行版本系统默认没有安装 gcc。可以使用下面命令来查看是否存在:
[me@linuxbox ~]$ which gcc
/usr/bin/gcc
Obtaining the Source Code
我们准备编一个名为 diction 的 GUN 项目。此工具用来检查文本文件写作质量和风格。非常小巧以及易于构建。
首先下载源码:
[root@66 src]# ftp ftp.gnu.org
Trying 209.51.188.20...
Connected to ftp.gnu.org (209.51.188.20).
220 GNU FTP server ready.
Name (ftp.gnu.org:root): anonymous
230-NOTICE (Updated October 13 2017):
230-
......
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
227 Entering Passive Mode (209,51,188,20,111,134).
150 Here comes the directory listing.
lrwxrwxrwx 1 0 0 8 Aug 20 2004 CRYPTO.README -> .message
......
drwxrwxr-x 322 0 3003 12288 Aug 10 15:45 gnu
......
-rw-r--r-- 1 0 0 2830 Dec 18 2018 welcome.msg
226 Directory send OK.
ftp> cd gnu/diction/
250 Directory successfully changed.
ftp> ls
227 Entering Passive Mode (209,51,188,20,105,167).
150 Here comes the directory listing.
-rw-r--r-- 1 3003 65534 68940 Aug 28 1998 diction-0.7.tar.gz
-rw-r--r-- 1 3003 65534 90957 Mar 04 2002 diction-1.02.tar.gz
-rw-r--r-- 1 3003 65534 141062 Sep 17 2007 diction-1.11.tar.gz
-rw-r--r-- 1 3003 65534 189 Sep 17 2007 diction-1.11.tar.gz.sig
226 Directory send OK.
ftp> get diction-1.11.tar.gz
local: diction-1.11.tar.gz remote: diction-1.11.tar.gz
227 Entering Passive Mode (209,51,188,20,113,44).
150 Opening BINARY mode data connection for diction-1.11.tar.gz (141062 bytes).
226 Transfer complete.
141062 bytes received in 0.199 secs (709.07 Kbytes/sec)
ftp> bye
221 Goodbye.
[root@66 src]# ls
diction-1.11.tar.gz
【注】我们将源码保存在 ~/src。系统将会在 /usr/src 安装源码编译的程序,而供多个用户使用的源代码通常安装在/usr/local/src中。
可以看到提供的源码通常以 压缩 tar 文件形式存在。通常称为 tarball,此文件包含 source tree 或者 组成源代码的目录和文件的层次结构。
一旦tar文件下载完成,必须先拆包:
[me@linuxbox src]$ tar xzf diction-1.11.tar.gz
[me@linuxbox src]$ ls
diction-1.11 diction-1.11.tar.gz
【注】可以使用下面命令来查看包内文件结构:
tar tzvf tarfile | head
Examining the Source Tree
解压后文件夹中包含 Source Tree :
[me@linuxbox src]$ cd diction-1.11
[me@linuxbox diction-1.11]$ ls
config.guess config.sub configure.in de diction.1.in diction.pot diction.spec.in en en_GB.po getopt.c getopt_int.h install-sh misc.c NEWS nl.po sentence.c style.1.in test
config.h.in configure COPYING de.po diction.c diction.spec diction.texi.in en_GB getopt1.c getopt.h INSTALL Makefile.in misc.h nl README sentence.h style.c
属于 GNU 项目的程序会提供文档 README,INSTALL,NEWS 和 COPYING。这些文档描述了如何构建,安装,许可等信息。因此在尝试构建程序之前应该阅读这些文件。
另外一些以 .c 和 .h 扩展名的文件:
[me@linuxbox diction-1.11]$ ls *.c
diction.c getopt1.c getopt.c misc.c sentence.c style.c
[me@linuxbox diction-1.11]$ ls *.h
getopt.h getopt_int.h misc.h sentence.h
.c 文件包含软件包提供的两个C程序(style 和 diction),分为多个模块。对一个大型应用来说经常将程序分为更好管理的多个模块。可以查看源码:
[me@linuxbox diction-1.11]$ less diction.c
.h 文件为 头文件(header files)。头文件包含了源码或者库中的事务的描述。为了使编译器连接模块,它必须收到完成整个程序所需的所有模块的描述。diction.c 文件开头可以看到:
#include "getopt.h"
在读取 diction.c 源代码文件时候,此命令告诉编译器读取文件 getopt.h 以便“知道” getopt.c中的内容。 getopt.c 文件提供了style 和 diction 程序所共享的事务。
在 getopt.h 的 include 声明上面,也可以看到另一种形式的声明:
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
这些命令也是引用头文件,但是引用的头文件存在于此源树之外。他们由系统提供用来支持每个程序的编译。如果进入 /usr/include目录,就可以看到他们:
[me@linuxbox diction-1.11]$ ls /usr/include
在此文件夹中的这些头文件在我们安装编译器的时候就安装了。
Building the Program
大多数程序构建只有简单的两步:
./configure
make
configure 程序为 源树(source tree)提供的shell 脚本。职责为分析构建环境。大多数源码被设计成 轻量的(portable)。也就是说,它被设计可以在一种以上的类Unix系统上构建。为了达到这个目的,源码需要在构建过程中进行一些微调,以适应系统之间的差异。configure 还可以检查是否已安装必要的外部工具和组件。
运行 configure 。 由于configure不在Shell通常希望程序位于的位置,因此必须显式在命令前指定前缀 ./ 。这意味着 程序位于当前工作目录:
[me@linuxbox diction-1.11]$ ./configure
在测试和配置构建时,configure将输出很多信息。完成时输出类似于下面:
checking libintl.h presence... yes
checking for libintl.h... yes
checking for library containing gettext... none required
configure: creating ./config.status
config.status: creating Makefile
config.status: creating diction.1
config.status: creating diction.texi
config.status: creating diction.spec
config.status: creating style.1
config.status: creating test/rundiction
config.status: creating config.h
[me@linuxbox diction-1.11]$
输出中不包含错误信息是重点。如果存在错误信息,说明 配置已经失败了 ,并且程序在错误得到修复之前不会被构建。
configure 创建了在当前文件夹中若干文件。其中最重要的是 Makefile。Makefile 是 指导 make 程序如何正确的构建程序的配置文件。没有这个文件,make 将会拒绝运行。Makefile 同样的是一个文本文件,可以进行查看:
[me@linuxbox diction-1.11]$ less Makefile
make 程序将 makefile(通常名为 Makefile) 作为输入,makefile 描述了组成完整程序的组件间的关系与依赖。
makefile 的第一个部分定义了取代后面部分的变量。例如,可以看到下面的行:
CC = gcc
此行定义了C 编译器为 gcc。在makefile后面,可以看到使用的一个实例:
diction: diction.o sentence.o misc.o getopt.o
getopt1.o $(CC) -o $@ $(LDFLAGS) diction.o sentence.o misc.o \
getopt.o getopt1.o $(LIBS)
替换就在这里执行, $(CC) 的值在运行时将会被替换成 gcc。
大多数makefile由行组成,这些行定义了 目标(target)—— 此例中为可执行文件 diction以及它依赖的文件。其余行描述了从组件中创建目标需要的命令。此例中可执行文件diction 依赖于 diction.o, sentence.o,misc.o, getopt.o, and getopt1.o的存在。在 makefile,可以看到每个目标的定义。
diction.o: diction.c config.h getopt.h misc.h sentence.h
getopt.o: getopt.c getopt.h getopt_int.h
getopt1.o: getopt1.c getopt.h getopt_int.h
misc.o: misc.c config.h misc.h
sentence.o: sentence.c config.h misc.h sentence.h
style.o: style.c config.h getopt.h misc.h sentence.h
然而,上面命令中我们没有看到它们的任何命令。实际上这由文件中较早的常规目标处理,该目标描述了用于将任何.c文件编译为.o文件的命令:
.c.o:
$(CC) -c $(CPPFLAGS) $(CFLAGS) $<
运行 make 构建程序:
[me@linuxbox diction-1.11]$ make
make 按照 Makefile 的指导来运行,会产生很多输出。当构建完成后,可以看到 所有的 target 出现在文件夹中:
[me@linuxbox diction-1.11]$ ls
config.guess config.log configure de diction diction.c diction.spec diction.texi.in en_GB.mo getopt1.o getopt_int.h install-sh misc.c NEWS nl.po sentence.h style.1 style.o
config.h config.status configure.in de.mo diction.1 diction.o diction.spec.in en en_GB.po getopt.c getopt.o Makefile misc.h nl README sentence.o style.1.in test
config.h.in config.sub COPYING de.po diction.1.in diction.pot diction.texi en_GB getopt1.c getopt.h INSTALL Makefile.in misc.o nl.mo sentence.c style style.c
在文件中,可以看到 diction 与 style,此时我们已经从源代码编译了我们的第一个程序。
此时,如果再次运行make:
[me@linuxbox diction-1.11]$ make
make: Nothing to be done for `all'
相较于每次运行都进行构建,make只构建需要构建的部分。如果删除 getopt.o 文件再次运行make:
[me@linuxbox diction-1.11]$ rm getopt.o
[me@linuxbox diction-1.11]$ make
因为程序依赖于不存在的模块,因此make重新构建 getopt.o 并重新链接 diction 与 style 。这揭示了make 的另一个重要特性:它使得 target 实时更新。使用touch来检查:
[me@linuxbox diction-1.11]$ ls -l diction getopt.c
-rwxr-xr-x 1 me me 37164 2009-03-05 06:14 diction
-rw-r--r-- 1 me me 33125 2007-03-30 17:45 getopt.c
[me@linuxbox diction-1.11]$ touch getopt.c
[me@linuxbox diction-1.11]$ ls -l diction getopt.c
-rwxr-xr-x 1 me me 37164 2009-03-05 06:14 diction
-rw-r--r-- 1 me me 33125 2009-03-05 06:23 getopt.c
[me@linuxbox diction-1.11]$ make
运行make之后,它已将目标恢复为比依赖项新的target:
[me@linuxbox diction-1.11]$ ls -l diction getopt.c
-rwxr-xr-x 1 me me 37164 2009-03-05 06:24 diction
-rw-r--r-- 1 me me 33125 2009-03-05 06:23 getopt.c
Installing the Program
精心包装的源代码经常包含特殊的make目标为install。此 target 将会在系统目录中安装最终的产品来使用。通常目录为 /usr/local/bin。但是默认情况下一般用户没有权限来写入,因此使用超级用户来执行:
[me@linuxbox diction-1.11]$ sudo make install
......
[me@linuxbox diction-1.11]$ which diction
/usr/local/bin/diction
[me@linuxbox diction-1.11]$ man diction