• 开源之迷
  • 适兕
  • 2444字
  • 2023-06-20 19:01:02

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!”打印形式。