03 正向理解:从源代码到运行结果
从一段“Hello,World!”程序说起
说起“Hello,World!”,所有人都会记得由布莱恩·克尼汉(又译为布赖恩·克尼汉,Brian Kernighan)和丹尼斯·里奇(Dennis Ritchie)二人撰写的经典书籍《C程序设计语言》。图2.2所示是布莱恩当年手写的C语言版“hello,world”程序语句[1]。自从他们发明了“Hello,World!”实例之后,几十年来,所有的语言介绍开篇都要以这个例子为起点,如我们上面介绍网页浏览器的时候就是执行的这个惯例。
图2.2 布莱恩手写的C语言版
“hello,world”程序语句
首先介绍的就是如何运行一个“Hello,World!”程序。
源代码如下:
#include <stdio.h>
int main(void)
{
printf("Hello,World!\n");
return 0;
}
这种代码是受过训练的人类——程序员可以读懂的,接近于人类的自然语言。但是想要计算机执行这段程序,代码必须通过如下语句被编译为二进制可执行程序:
$gcc hello.c -o hello
在终端下我们可以看到有了一个可以执行的程序:
$ ls -l
total 32
-rwxr-xr-x 1 lee staff 8432 Aug 7 23:37 hello
-rw-r--r-- 1 lee staff 79 Aug 7 23:34 hello.c
但是这个叫作hello的二进制可执行文件是人类无法读懂的,而机器可以执行它:
$ file hello
hello: Mach-O 64-bit executable x86_64
我们知道C语言是一门高级语言。在计算机编程语言的发展历史上,还经历过汇编时代、十六进制时代。为了向大家进一步说明问题,我们将这段程序的汇编语言代码和十六进制代码分别列出,供有兴趣者参考。
/* 汇编语言代码
$gcc -S hello.c > hello.s
$cat hello.s
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 14 sdk_version 10, 14
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $0, -4(%rbp)
leaq L_.str(%rip), %rdi
movb $0, %al
callq _printf
xorl %ecx, %ecx
movl %eax, -8(%rbp) ## 4-byte Spill
movl %ecx, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "Hello,World!\n"
.subsections_via_symbols
汇编语言代码是不是已经非常难以看懂了?但这仍然是供人类阅读的,我们再来看看十六进制代码编写的程序:
/* 十六进制的代码,不进行链接
$gcc -c hello.s -o hello.o
$ file hello.o
.o: Mach-O 64-bit object x86_64
$ od -h hello.o
0000000 facf feed 0007 0100 0003 0000 0001 0000
0000020 0004 0000 0208 0000 2000 0000 0000 0000
0000040 0019 0000 0188 0000 0000 0000 0000 0000
0000060 0000 0000 0000 0000 0000 0000 0000 0000
0000100 0098 0000 0000 0000 0228 0000 0000 0000
0000120 0098 0000 0000 0000 0007 0000 0007 0000
0000140 0004 0000 0000 0000 5f5f 6574 7478 0000
0000160 0000 0000 0000 0000 5f5f 4554 5458 0000
0000200 0000 0000 0000 0000 0000 0000 0000 0000
0000220 002a 0000 0000 0000 0228 0000 0004 0000
0000240 02c0 0000 0002 0000 0400 8000 0000 0000
0000260 0000 0000 0000 0000 5f5f 7363 7274 6e69
0000300 0067 0000 0000 0000 5f5f 4554 5458 0000
0000320 0000 0000 0000 0000 002a 0000 0000 0000
0000340 000e 0000 0000 0000 0252 0000 0000 0000
0000360 0000 0000 0000 0000 0002 0000 0000 0000
0000400 0000 0000 0000 0000 5f5f 6f63 706d 6361
0000420 5f74 6e75 6977 646e 5f5f 444c 0000 0000
0000440 0000 0000 0000 0000 0038 0000 0000 0000
0000460 0020 0000 0000 0000 0260 0000 0003 0000
0000500 02d0 0000 0001 0000 0000 0200 0000 0000
0000520 0000 0000 0000 0000 5f5f 6865 665f 6172
0000540 656d 0000 0000 0000 5f5f 4554 5458 0000
0000560 0000 0000 0000 0000 0058 0000 0000 0000
0000600 0040 0000 0000 0000 0280 0000 0003 0000
0000620 0000 0000 0000 0000 000b 6800 0000 0000
0000640 0000 0000 0000 0000 0032 0000 0018 0000
0000660 0001 0000 0e00 000a 0e00 000a 0000 0000
0000700 0002 0000 0018 0000 02d8 0000 0002 0000
0000720 02f8 0000 0010 0000 000b 0000 0050 0000
0000740 0000 0000 0000 0000 0000 0000 0001 0000
0000760 0001 0000 0001 0000 0000 0000 0000 0000
0001000 0000 0000 0000 0000 0000 0000 0000 0000
*
0001040 0000 0000 0000 0000 4855 e589 8348 10ec
0001060 45c7 00fc 0000 4800 3d8d 0014 0000 00b0
0001100 00e8 0000 3100 89c9 f845 c889 8348 10c4
0001120 c35d 6548 6c6c 2c6f 6f57 6c72 2164 000a
0001140 0000 0000 0000 0000 002a 0000 0000 0100
0001160 0000 0000 0000 0000 0000 0000 0000 0000
0001200 0014 0000 0000 0000 7a01 0052 7801 0110
0001220 0c10 0807 0190 0000 0024 0000 001c 0000
0001240 ff88 ffff ffff ffff 002a 0000 0000 0000
0001260 4100 100e 0286 0d43 0006 0000 0000 0000
0001300 0019 0000 0001 2d00 0012 0000 0002 1500
0001320 0000 0000 0001 0600 0001 0000 010f 0000
0001340 0000 0000 0000 0000 0007 0000 0001 0000
0001360 0000 0000 0000 0000 5f00 616d 6e69 5f00
0001400 7270 6e69 6674 0000
0001410
这就是只有机器可以读懂的内容,不在人类的理解范围之内了。
继续正向理解:超级复杂的程序/软件
如果仅仅是对“Hello,World!”的显示的话,编程对人类本身的帮助是不大的,它需要将代码进行组合,让其执行更加复杂的功能,如前文描述的社交聊天、支付理财、浏览信息、播放电影等。限于本书所承载的内容,向导仅列举了一个人们使用较广泛的软件——Web浏览器,它也是我们在前文中访问开源之道网站的工具。
注意
如果大家对其他开源软件感兴趣,比如FFmpeg,可以仿照下面提到的方式进行类似的体验之旅。
无论是浏览器,还是网站,如今都成了大家日常使用和访问的内容。下面我们就分别来看看目前世界上比较流行的两款开放源代码软件:Chromium和Apache httpd。
安装Chromium的两种方法
想要正确安装和使用Chromium软件,通常有两个选择。
(1)直接下载其已经编译的最新的二进制代码程序,直接运行即可。
(2)按照其指示(通常是必须的),克隆源代码到对应的平台(操作系统、CPU),自行编译、消除故障。
第一种比较简单,现在大家计算机上的Chromium软件可能是从官方网站上下载的,也可能是操作系统发行版自带的,又或者是由第三方的分发渠道获得的。总而言之,你看到的Chromium是一个较大的软件。关于具体的Chromium的信息,可以在其输入框中输入以下内容获取:
chrome://about
或者是:
chrome://version
接下来,向导重点为大家描述一下获取源代码并对其进行编译的过程。
系统需求
● 基于Intel架构的64位CPU,最小8GB内存;
● 硬盘至少有100GB的空闲空间;
● 最好是已经安装了Git与Python 2这样的版本控制系统和软件环境。
安装Google提供的工具depot_tools
在任意的Linux或macOS操作系统中,打开终端输入以下命令:
$ git clone https://github.com/rust-skia/depot_tools
$ export PATH="$PATH:/path/to/depot_tools"
$ export PATH="$PATH:${HOME}/depot_tools"
获取代码
在任意的Linux或macOS操作系统中,打开终端输入以下命令:
$ mkdir ~/chromium && cd ~/chromium
$ fetch --nohooks chromium
$ cd src
接下来要安装一些编译构建Chromium之前的相关依赖:
$ ./build/install-build-deps.sh
$ gclient runhooks
然后是编译构建前的基本设置:
$ gn gen out/Default
一些额外的设置,例如是否使用Intel C++编译器(Intel C++ Compiler,ICC)并行编译、是否采用临时文件系统(tmpfs)等,都可以由用户自行决定。
接下来开始编译构建:
$ autoninja -C out/Default chrome
$ gn ls out/Default
最后输入如下代码开始运行:
$ out/Default/chrome
保持和上游代码同步
$ git rebase-update
$ gclient sync
最后一步非常关键,我们称之为开源开发的精髓所在。上游优先(upstream first)作为参与开源的重要原则,向导会在《开源之道》中进行详述。而作为开发者,或者是对源代码充满兴趣的工作者,要时刻漫游在上游,因为上游是事情真正发生的地方,更何况除了代码,这里还有更为重要的内容。
以上便是通过源代码构建Chromium的主要步骤。要完成这个构建过程,需要操作者具备非常多的计算机相关知识,同时也是一个漫长而枯燥的过程。但是,这个过程非常详细地“讲述”了一款软件从源代码到可执行软件的完整实现步骤,胜过任何的自然语言描述。当然,为了呼应本节的题目,这个思考过程还是应该逆过来:软件是由源代码所构建出来的。
Apache httpd从源代码到Web服务器
完整的万维网不仅需要有客户端,还必须有服务器端。httpd使用C语言编写,从源代码到可执行文件的构建过程,需执行如下代码:
$ wget https://dlcdn.apache.org//httpd/httpd-2.4.51.tar.bz2 //下载
$ gzip -d httpd-NN.tar.gz
$ tar xvf httpd-NN.tar
$ CC="pgcc" CFLAGS="-O2" ./configure --prefix=/sw/pkg/apache --enable-ldap=shared --enable-lua=shared
$ make
$ make install
$ PREFIX/bin/apachectl -k start //启动
在上段代码中,apachectl运行的程序就是你所访问的网站内容的服务提供者:Web服务器。apachectl会每周7×24小时一直运行,等待来自客户端的访问,一般对它的专业称呼是:“守护进程”,或“服务进程”。
[1] 手写版中,“hello”和“world”首字母均为小写,不同于如今我们常见的“Hello,World!”打印形式。