## 第 18 章 编译工具链
**目录**
[](ch18.html#id3083823)
[标准编译安装](ch18s02.html)
[为什么要编译安装](ch18s02.html#id3083834)
[编译环境](ch18s02.html#id3083233)
[标准编译安装](ch18s02.html#id3083952)
[编译过程](ch18s03.html)
[gcc 编译器](ch18s04.html)
[自动化编译](ch18s05.html)
[autoconf](ch18s05.html#id3084783)
[automake](ch18s05.html#id3084909)
[Makefile](ch18s05.html#id3084989)
[使用 make](ch18s06.html)
[emerge](ch18s07.html)
[设置 USE标记](ch18s07.html#id3085722)
[编译选项](ch18s07.html#id3085838)
[微调](ch18s07.html#id3086063)
[使用 emerge](ch18s07.html#id3086226)
## 标准编译安装
### 为什么要编译安装
包管理系统是绝大多数发行版的必备组件,也是一个发行版区别于其它发行版的主要特征。但是有些软件,并不能通过包管理系统安装,这就需要下载源码编译安装。
一个软件可能有许多功能,但是发行版中提供的安装包,通常只具有一些常见的功能。如果提供所有功能,那么无疑会占用更多的资源,而这些功能,大多数用户不会用到;[[24](ch18s02.html#ftn.id3083206)]而你会用到的功能,可能安装包中刚好没有。编译安装可以灵活地定制软件,选择自己需要的,取消自己不需要的。
编译安装还可以针对特定的硬件进行优化,以获得更好的性能表现。[[25](ch18s02.html#ftn.id3083225)]
### 编译环境
编译环境包括多个工具,它们环环相扣,称作编译工具链。主要包括以下工具:
| 工具 | 简介 |
| --- | --- |
| binutils | 连接器、汇编器和其他用于目标文件和档案的工具 |
| gcc | 编译器,将源代码转换为机器代码 |
| glibc | C库,提供标准例程(C函数) |
还有一些工具,能够调用工具链,实现自动化编译:
| | |
| --- | --- |
| autoconf | 自动生成 `Makefile` 文件 |
| automake |
| make | 按照 `Makefile` 文件中的规则编译程序 |
在后面的部分将分别介绍这些工具
### 标准编译安装
首先,下载源代码,通常是压缩包,如:`xxx.tar.gz` 或者 `xxx.tar.bz2`,解包:
| 压缩包格式 | 命令 |
| --- | --- |
| .tar.gz | tar zxvf xxx.tgz |
| .tgz |
| .tar.bz2 | tar jxvf xxx.tar.bz2 |
通常解包后会在当前位置得到一个 `xxx/` 目录,进入这个目录
```
cd xxx/
```
使用下列命令编译安装:
```
./configure --prefix=/opt/xxx
make
sudo make install
make clean
```
> [![1](https://box.kancloud.cn/2015-10-12_561bcb76795ae.png)](ch18s02.html#build-01) 配置软件特性,检查编译环境,生成 `Makefile文件`
> [![2](https://box.kancloud.cn/2015-10-12_561bcb768596e.png)](ch18s02.html#build-05) 最常用配置选项:指定软件的安装路径
> [![3](https://box.kancloud.cn/2015-10-12_561bcb94ee26f.png)](ch18s02.html#build-02) 根据 `Makefile` 编译源代码
> [![4](https://box.kancloud.cn/2015-10-12_561bcb9508b82.png)](ch18s02.html#build-03) 将编译完成的程序安装到系统中。通常需要 root权限
> [![5](https://box.kancloud.cn/2015-10-12_561bcb95133e7.png)](ch18s02.html#build-04) 清除源代码目录中的编译结果
* * *
> [[24](ch18s02.html#id3083206)] Windows 系统下的一些经典软件,如 ACDsee、Nero、Winamp 等,集成了越来越多的功能,使它们越来越臃肿。而且不能够只选择自己喜欢的功能,要么全盘接收,要么改寻它途
> [[25](ch18s02.html#id3083225)] 通常发行版提供的安装包,已经进行了优化。自己编译的软件,性能未必更好
## 编译过程
将下面代码保存为 `Hello.c`:
```
#include <stdio.h>
int main(void)
{
printf("Hello World!\n");
return 0;
}
```
> [![1](https://box.kancloud.cn/2015-10-12_561bcb76795ae.png)](ch18s03.html#build-20) **printf()** 函数
执行命令 `cc Hello.c`[[26](ch18s03.html#ftn.id3084190)],得到一个可执行文件 `a.out`,执行它 `./a.out`
可以看到,C的源代码(`Hello.c`)是纯文本,不能够直接执行。可执行代码是计算机的本机语言或机器语言表示的代码,这种语言是由数字代码表示的详细指令组成,不同的计算机具有不同的机器语言。
编译器是一个程序,其工作是将源代码转换为可执行代码。
* 编译器用来将 C语言 转换成特定的机器语言。
* 编译器还从C的库中向最终程序加入代码。[[27](ch18s03.html#ftn.id3084254)]
* 编译器还检查源代码是否为有效的C语言程序。如果编译器发现错误,将报告错误,而且不生成可执行文件
编译器分三步完成这个工作:
| | |
| --- | --- |
| 预处理 | 调用预处理器 cpp 对源代码文件中的文件包含(include)、预编译语句(如宏定义 define 等) 进行分析 |
| 编译 | 调用编译器 cc 将源代码转换为中间代码 |
| 链接 | 调用链接器 ld 将中间代码与其它代码结合起来生成可执行文件 |
* 这种方法使用程序便于模块化。分别编译各个模块,然后使用链接器将编译过的模块结合起来。这样,如果需要改变一个模块,则不必重新编译所有其它模块。
可执行文件包含`目标文件`、`库例程`和`启动代码`
编译器将源代码转换为机器语言代码(中间代码),将结果放置在目标文件(`*.o`)中。虽然目标文件包含机器代码,但该文件还不能运行,它还不是一个完整的程序。
启动代码(start-up code)相当于程序和操作系统之间的接口。[[28](ch18s03.html#ftn.id3084399)]
库例程为函数的实现。几乎所有C程序都利用标准C库中所包含的例程,目标代码文件不包含这一函数的代码,它只包含调用函数的指令。实际代码存储在一个称为“库”的文件中。库文件中包含许多函数的目标代码
链接器的作用是将这3个元素(目标代码、系统的标准启动代码和库代码[[29](ch18s03.html#ftn.id3084420)])结合在一起,并将它们存放在可执行文件中。
* * *
> [[26](ch18s03.html#id3084190)] 在 Linux 系统中,编译器为gcc,cc为它的链接
> [[27](ch18s03.html#id3084254)] 库中包含许多标准例程供您使用,例如**printf()**。更准确的说,是一个被称为链接器(linker)的程序将库例程引入的,但在多数系统上,编译器为您运行链接器。
> [[28](ch18s03.html#id3084399)] 硬件相同的情况下,在 DOS 或 Linux 下可以使用同样的目标代码,但 DOC 与 Linux 要使用不同的启动代码,因为这两种系统处理程序的方式是不同的。
> [[29](ch18s03.html#id3084420)] 程序有两种方法来使用这些库函数,如果静态连接一个程序,这些函数就会被复制到可执行程序中,这就是`lib*.a`函数库的作用
> 如果你动态的连接一个程序(默认),那么当程序运行时需要库中的代码,它就会调用`lib*.so`中的内容。
## gcc 编译器
gcc 是 GNU 推出的功能强大、性能优越的多平台编译器,是 GNU 的代表作品之一。它能将C、C++语言源程序、汇编语言源程序和目标程序编译、链接成可执行文件,如果没有给出可执行文件的名字,gcc 将生成一个名为 `a.out` 的文件。
gcc 通过后缀来区分输入文件的类型:
| 后缀 | 类型 |
| --- | --- |
| .c | C语言源代码文件 |
| .a | 由目标文件构成的档案库文件 |
| .C|.cc|.cxx | C++源代码文件 |
| .h | 程序所包含的头文件 |
| .i | 预处理过的C源代码文件 |
| .ii | 预处理过的C++源代码文件 |
| .m | Objective-C源代码文件 |
| .o | 编译后的目标文件 |
| .s | 汇编语言源代码文件 |
| .S | 预编译的汇编语言源代码文件 |
前面我们已经使用 gcc 编译了一个程序:`cc Hello.c`
gcc 还有许多选项:
| | |
| --- | --- |
| -c | 只编译,不链接成为可执行文件 |
| -o 文件名 | 设定输出文件名。默认为`a.out` |
| -g | 加入调试符号(默认)。![1](https://box.kancloud.cn/2015-10-12_561bcb76795ae.png) |
| -O | 编译、链接时进行优化,耗时比较多,但产生的可执行文件执行效率更高 |
| -O2 | 更高的优化级别,耗时更多 |
> [![1](https://box.kancloud.cn/2015-10-12_561bcb76795ae.png)](ch18s04.html#build-21) 可以使用 gdb 进行调试使用下面的命令去掉调试符号:
> ```
> strip --strip-unneeded a.out
> strip --strip-debug a.out
> ```
> 不要在库文件上使用 **--strip-unneeded**
## 自动化编译
在前面的[标准编译安装](ch18s02.html#std-build)中,第一步是`./configure`[[30](ch18s05.html#ftn.id3084729)],它会根据`Makefile.in`生成`Makefile`文件,然后make根据`Makefile`自动编译软件
通常在一个源码包中,已经包含了`configure`脚本和`Makefile`文件,作为课外知识,我们大致了解一下怎么生成这两个文件
### autoconf
autoconf用来生成`configure`脚本,它可以检查系统特性、编译环境、环境变量、软件参数、依赖关系等
autoconf需要用到 m4
1. 用autoscan描源代码目录生成`configure.scan`文件
2. 将`configure.scan`改名为`configure.in`
3. 用aclocal根据`configure.in`文件的内容,自动生成`aclocal.m4`文件
4. 使用autoconf,根据`configure.in`和`aclocal.m4`来产生`configure`文件
### automake
automake可以从`Makefile.am`文件自动生成`Makefile.in`,它主要用来配置源代码
automake需用到perl
* 手工写`Makefile.am`
* 使用`automake`,根据`configure.in`和`Makefile.am`来产生`Makefile.in`
### Makefile
使用`configure`脚本,配合`Makefile.in`可以生成`Makefile`文件,然后用make自动化的编译软件
这里有一张生成`Makefile`的流程图:
![](https://box.kancloud.cn/2016-08-07_57a6a934d835a.gif)
`Makefile`的用途不只是编译软件,还可以利用它完成一些琐碎的工作,只要最后输出一个文件,都可以用make来完成
这是一个最简单的`Makefile`
```
filelist:*
ls -lF > filelist
```
> [![1](https://box.kancloud.cn/2015-10-12_561bcb76795ae.png)](ch18s05.html#build-31) 输出的目标文件,不能省略。如果有多个文件,可以使用**all**
> [![2](https://box.kancloud.cn/2015-10-12_561bcb768596e.png)](ch18s05.html#build-32) 分隔符,不能省略
> [![3](https://box.kancloud.cn/2015-10-12_561bcb94ee26f.png)](ch18s05.html#build-33) 输入文件,可以省略
> [![4](https://box.kancloud.cn/2015-10-12_561bcb9508b82.png)](ch18s05.html#build-34) 这一行必须以`TAB`字符起始,不能使用空格代替
> [![5](https://box.kancloud.cn/2015-10-12_561bcb95133e7.png)](ch18s05.html#build-35) make的命令
可以使用变量代替命令,便于维护
```
TARGET = filelist
SOURCE = *
ARG = -lF
APPLICATION = ls
$(TARGET):$(SOURCE)
$(APPLICATION) $(ARG) $(SOURCE) > $(TARGET)
```
> [![1](https://box.kancloud.cn/2015-10-12_561bcb76795ae.png)](ch18s05.html#build-41) 定义变量,传统上用大写
> [![2](https://box.kancloud.cn/2015-10-12_561bcb768596e.png)](ch18s05.html#build-42) 使用变量写`Makefile`
`Makefile`可以有多个目标文件,我们前面提到,gcc编译时先生成目标文件,再把目标文件链接成可执行文件,Makefile应该是这样的:
```
OBJECTS = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
exe : $(OBJECTS)
cc -o exe $(OBJECTS)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm exe $(OBJECTS)
```
> [![1](https://box.kancloud.cn/2015-10-12_561bcb76795ae.png)](ch18s05.html#build-51) 如果写在多行,要用脱字符换行
> [![2](https://box.kancloud.cn/2015-10-12_561bcb768596e.png)](ch18s05.html#build-52) 如何生成中间文件
> [![3](https://box.kancloud.cn/2015-10-12_561bcb94ee26f.png)](ch18s05.html#build-53) 伪目标文件,`make clean`并不生成`clean`文件,而是清理编译结果
`Makefile`还有很多强大的机制,这里就不详细介绍了
* * *
> [[30](ch18s05.html#id3084729)] 执行当前目录下的`configure`脚本
## 使用 make
利用 `configure`所产生的`Makefile`文件有几个预先设定的目标可供使用:
| 目标 | 用途 |
| --- | --- |
| make all | 产生预设的目标,只敲入`make`也可以 |
| make clean | 清除编译结果 |
| make distclean | 除了清除编译结果,也把configure所产生的 `Makefile` 清除掉 |
| make install | 将程序安装到系统中 |
| make dist | 将程序和相关的文档打包为一个压缩文档以供发布 |
| make distcheck | 打包并检验 |
## emerge
虽然我们能够使用autoconf、automake、make等工具实现自动化编译,但这种针对单个软件包的编译系统,在编译多个软件时仍然十分繁琐
假设需要编译emacs和vim,使用 xft字体、图形界面支持,并去掉调试符号,需要分别作如下配置
```
emacs
./configure --prefix=/usr/local/ \
`--no-debug` \
`--with-xft --with-x-toolkit=gtk` \
--with-freetype
vim
./configure --prefix=/usr/local/ \
`--no-debug` \
`--with-xft --with-x-toolkit=gtk`
```
这就像点菜时,你必须告诉厨师:鱼香肉丝(多放辣椒、不放蒜)、宫保鸡丁(多放辣椒、不放蒜)、麻婆豆腐(多放辣椒、不放蒜)……
实际上,大多数人这样点菜:鱼香肉丝、宫保鸡丁、麻婆豆腐……多放辣椒、不放蒜
emerge就是这样一种点菜方式,它是gentoo的包管理系统,提供了更为现代化的编译方式
可以通过指定USE标记`xft`、`gtk`、`-debug`来确定所有软件的编译方式
### 设置 USE标记
以下方法按优先级由低到高排列:
`/etc/make.profile/`目录是一个符号链接,里面包含一些`make.defaults`文件,放置开发者设置的 USE标记[[31](ch18s07.html#ftn.id3085747)]:
```
/usr/portage/profile/base/make.defaults
/usr/portage/profile/default-linux/make.defaults
/usr/portage/profile/default-linux/x86/make.defaults
/usr/portage/profile/default-linux/x86/2008.0/make.defaults
```
在`/etc/make.conf`文件中声明永久 USE标记(推荐)
```
USE="nptl nptlonly nls cjk php mysql -kde -qt3 -qt4"
```
* 带`-`的 USE标记,表示排除
在`/etc/portage/package.use`文件中为单个包声明 USE标记
```
app-editors/emacs-cvs xft
www-servers/lighttpd fastcgi
dev-lang/php mysqli cgi gd ctype pcre session unicode pic posix
dev-db/phpmyadmin vhosts
app-shells/zsh doc
net-ftp/pure-ftpd -ldap mysql pam ssl vchroot
```
使用环境变量声明临时 USE标记
```
USE="-java"
emerge seamonkey
```
查看使用的 USE标记:
```
merge --pretend --verbose seamonkey
Calculating dependencies ...done!
[ebuild R ] www-client/seamonkey-1.0.7 USE="crypt gnome java -debug -ipv6
-ldap -mozcalendar -mozdevelop -moznocompose -moznoirc -moznomail -moznopango
-moznoroaming -postgres -xinerama -xprint" 0 kB
```
### 编译选项
`/etc/make.conf`
```
CFLAGS="-O2 -march=i686 -pipe"
CXXFLAGS="-O2 -march=i686 -pipe"
CHOST="i686-pc-linux-gnu"
MAKEOPTS="-j2"
FEATURES="parallel-fetch ccache"
CCACHE_DIR="/var/tmp/ccache"
CCACHE_SIZE="2G"
ACCEPT_KEYWORDS="x86"
USE="nptl nptlonly nls cjk php mysql"
FETCHCOMMAND="/usr/bin/axel -a -n4 \${URI} -o \${DISTDIR}"
RESUMECOMMAND="/usr/bin/axel -a -n4 \${URI} -o \${DISTDIR}"
```
> [![1](https://box.kancloud.cn/2015-10-12_561bcb76795ae.png)](ch18s07.html#build-e21) 针对C语言的优化选项,**-march=**设置目标架构
> [![2](https://box.kancloud.cn/2015-10-12_561bcb768596e.png)](ch18s07.html#build-e22) 针对C++语言的优化选项
> [![3](https://box.kancloud.cn/2015-10-12_561bcb94ee26f.png)](ch18s07.html#build-e23) 进行编译工作的机器架构
> [![4](https://box.kancloud.cn/2015-10-12_561bcb9508b82.png)](ch18s07.html#build-e24) 编译选项
> [![5](https://box.kancloud.cn/2015-10-12_561bcb95133e7.png)](ch18s07.html#build-e25) emerge 特性。并行下载、使用 ccache 缓冲编译结果
> [![6](https://box.kancloud.cn/2016-01-06_568cdb4f32753.png)](ch18s07.html#build-e26) ccache 缓存目录
> [![7](https://box.kancloud.cn/2016-01-06_568cdb4f41fcc.png)](ch18s07.html#build-e27) ccache 缓存大小
> [![8](https://box.kancloud.cn/2016-01-06_568cdb4f5948a.png)](ch18s07.html#build-e28) 通过关键字选择分支。`x86`表示 x86 架构的稳定分支,`~x86`表示 x86 架构的不稳定分支
> [![9](https://box.kancloud.cn/2016-01-06_568cdb4f68749.png)](ch18s07.html#build-e29) USE标记
> [![10](https://box.kancloud.cn/2016-01-06_568cdb4f83cf0.png)](ch18s07.html#build-e30) 使用axel加速下载
gentoo支持多种架构:x86、 sparc、 amd64、 ppc、 ppc64、 alpha、 hppa、 mips、 ia64、 arm,我们使用的 PC 多为 x86 架构
假设你主要使用 x86 稳定分支,但少数软件要使用最新版本,在`/etc/portage/package.keywords`文件中为单个包设置关键字
```
app-editors/emacs-cvs ~x86
x11-misc/emacs-desktop ~x86
app-i18n/fcitx ~x86
app-editors/vim x86
app-editors/vim-core x86
media-video/mplayer ~x86
media-libs/win32codecs ~x86
app-i18n/man-pages-zh_CN ~x86
```
### 微调
在`/etc/portage/`目标下包含一些文件,可以在软件包级别上进行调节。前面已经介绍了`package.use`和`package.keywords`
package.keywords
还未被确认适合你的系统或架构,但是你希望能安装的软件包
package.use
特定软件包而不是整个系统使用的 USE标记
package.provided
屏蔽的软件包(需要指明版本号)
package.mask
永远不希望 Portage 安装的软件包。
package.unmask
被 Gentoo 开发者屏蔽的软件包,但是你希望能安装的软件包。
软件包可能由于以下原因被屏蔽
| | |
| --- | --- |
| ~架构 keyword | 意味着这个软件没有经过充分的测试,不能进入稳定分支,请等待一段时间后在尝试使用它 |
| -架构 keyword 或 -* keyword | 意味着这个软件不能工作在您机器的体系结构中 |
| missing keyword | 意味着这个软件还没有在您机器的体系结构中进行过测试 |
| package.mask | 意味着这个软件被认为是损坏的,不稳定的或者有更严重的问题,它被故意标识为“不应使用” |
| profile | 意味着这个软件不适用于您的 profile。安装这样的应用软件可能会破坏您的系统,或者只是不能与您使用的 profile 相兼容 |
例如:
```
gnome-base/gnome-2.8.0_pre1 (masked by: ~x86 keyword)
lm-sensors/lm-sensors-2.8.7 (masked by: -sparc keyword)
sys-libs/glibc-2.3.4.20040808 (masked by: -* keyword)
dev-util/cvsd-1.0.2 (masked by: missing keyword)
games-fps/unreal-tournament-451 (masked by: package.mask)
sys-libs/glibc-2.3.2-r11 (masked by: profile)
```
### 使用 emerge
> 注意:本部分内容来源于[gentoo 中文手册](http://www.gentoo.org/doc/zh_cn/handbook/)
**查找. **
查找名字包含 pdf 的软件包
```
emerge --search pdf
```
查找与 pdf 相关的软件包
```
emerge --searchdesc pdf
emerge -S pdf
```
查看软件拥有的 USE标记
```
emerge -vp 软件包名称
```
**管理. **
安装软件包
```
emerge 软件包名称
```
模拟安装软件包
```
emerge --pretend 软件包名称
```
下载软件包的源代码包
```
emerge --fetchonly 软件包名称
```
从系统中删除软件包
```
emerge --unmerge 软件包名称
```
**更新. **
更新系统
```
emerge --update --ask world
emerge -ua world
```
更新整个系统
```
emerge --update --deep world
emerge -uD world
```
使用新的 USE标记 重新构建系统
```
emerge --update --deep --newuse world
emerge -uDN world
```
移除孤立依赖的软件包
```
emerge --update --deep --newuse world
emerge --depclean
revdep-rebuild
```
> [![1](https://box.kancloud.cn/2015-10-12_561bcb76795ae.png)](ch18s07.html#build-e31) 重新构建系统
> [![2](https://box.kancloud.cn/2015-10-12_561bcb768596e.png)](ch18s07.html#build-e32) 清除孤立依赖包
> [![3](https://box.kancloud.cn/2015-10-12_561bcb94ee26f.png)](ch18s07.html#build-e33) 重新构建依赖关系
revdep-rebuild工具由gentoolkit包提供;使用前别忘了首先 emerge 它:
```
emerge gentoolkit
```
* * *
> [[31](ch18s07.html#id3085747)] 在升级 Portage 的时候,这些文件将会被覆盖,请不要在这里设置
- 开源世界旅行手册
- 授权
- 致谢
- 序言
- 更新纪录
- 导读
- 如何写作科技文档
- 部分 I. 气候
- 第 1 章 GUI? CLI?
- 第 2 章 UNIX 缩写风格
- 第 3 章 版本号的迷雾
- 第 4 章 Vim 还是 Emacs
- 第 5 章 DocBook 还是 TeX
- 第 6 章 完全用 Gnu/Linux 工作
- 第 7 章 病毒
- 第 8 章 磁盘 分区
- 第 9 章 文件系统
- 第 10 章 发行版介绍
- 第 11 章 编程语言
- 第 12 章 无根的根:无名师的 Unix 心传
- 部分 II. 地理
- 第 13 章 基础知识
- 第 14 章 命令系统
- 第 15 章 基本系统
- 第 16 章 软件管理
- 第 17 章 核心工具集
- 第 18 章 编译工具链
- 第 19 章 图形界面
- 第 20 章 国际化
- 第 21 章 内核
- 第 22 章 Grub
- 第 23 章 服务器
- 第 24 章 Vim 编辑器
- 第 25 章 Emacs 入门
- 第 26 章 正则表达式
- 第 27 章 docbook 指南
- 第 28 章 Git 版本控制系统
- 第 29 章 ConTeXt 入门指南
- 部分 III. 景观
- 第 30 章 终极 Shell -- ZSH
- 第 31 章 完美工作站 Archlinux
- 第 32 章 组织你的意念:Emacs org mode
- 第 33 章 Zsh+screen
- 第 34 章 gentoo stage3
- 第 35 章 硬件问题
- 第 36 章 网络设置
- 第 37 章 自制 LiveCD
- 第 38 章 awesome
- 第 39 章 openbox 工作环境
- 第 40 章 Emacs muse
- 第 41 章 写作工具链
- 第 42 章 使用 lftp
- 第 43 章 Firefox 使用技巧
- 第 44 章 FVWM
- 部分 IV. 地质
- 第 45 章 Unix
- 第 46 章 Gnu
- 第 47 章 软件业自由之神——Richard Stallman
- 第 48 章 Linux
- 第 49 章 GNOME与KDE的战争
- 第 50 章 Vim Emacs
- 第 51 章 年代纪
- 第 52 章 我的选择
- 第 53 章 补遗