用了这么久的电脑,你一定很好奇我们使用的软件是怎么来的。那么今天茶叶就来简单科普一下这个过程,同时也将介绍开源软件是怎么一回事。
(图为Helloworld小程序od出来的数据以16进制显示)
软件是什么
我们知道计算机靠执行像“10010000”这样的二进制指令(茶叶电脑中“什么也不做”指令)来运行。早期的电脑中,攻城狮们要靠查表来逐个输入这样的二进制代码。
比如下面程序(只是为了做个样子,没有意义,请直接拖动右边的滚动条):
helloworld.c.out: file format elf32-i386
Disassembly of section .init:
080482b0 <_init>: 80482b0:53 push %ebx 80482b1:83 ec 08 sub $0x8,%esp 80482b4:e8 00 00 00 00 call 80482b9 <_init+0x9> 80482b9:5b pop %ebx 80482ba:81 c3 3b 1d 00 00 add $0x1d3b,%ebx 80482c0:8b 83 fc ff ff ff mov -0x4(%ebx),%eax 80482c6:85 c0 test %eax,%eax 80482c8:74 05 je 80482cf <_init+0x1f> 80482ca:e8 31 00 00 00 call 8048300 <gmon_start@plt> 80482cf:e8 dc 00 00 00 call 80483b0 <frame_dummy> 80482d4:e8 97 01 00 00 call 8048470 <__do_global_ctors_aux> 80482d9:83 c4 08 add $0x8,%esp 80482dc:5b pop %ebx 80482dd:c3 ret
Disassembly of section .plt:
080482e0 <puts@plt-0x10>: 80482e0:ff 35 f8 9f 04 08 pushl 0x8049ff8 80482e6:ff 25 fc 9f 04 08 jmp *0x8049ffc 80482ec:00 00 add %al,(%eax)
...
080482f0 <puts@plt>: 80482f0:ff 25 00 a0 04 08 jmp *0x804a000 80482f6:68 00 00 00 00 push $0x0 80482fb:e9 e0 ff ff ff jmp 80482e0 <_init+0x30>
08048300 <gmon_start@plt>: 8048300:ff 25 04 a0 04 08 jmp *0x804a004 8048306:68 08 00 00 00 push $0x8 804830b:e9 d0 ff ff ff jmp 80482e0 <_init+0x30>
08048310 <__libc_start_main@plt>: 8048310:ff 25 08 a0 04 08 jmp *0x804a008 8048316:68 10 00 00 00 push $0x10 804831b:e9 c0 ff ff ff jmp 80482e0 <_init+0x30>
Disassembly of section .text:
08048320 <_start>: 8048320:31 ed xor %ebp,%ebp 8048322:5e pop %esi 8048323:89 e1 mov %esp,%ecx 8048325:83 e4 f0 and $0xfffffff0,%esp 8048328:50 push %eax 8048329:54 push %esp 804832a:52 push %edx 804832b:68 60 84 04 08 push $0x8048460 8048330:68 f0 83 04 08 push $0x80483f0 8048335:51 push %ecx 8048336:56 push %esi 8048337:68 d4 83 04 08 push $0x80483d4 804833c:e8 cf ff ff ff call 8048310 <__libc_start_main@plt> 8048341:f4 hlt 8048342:90 nop 8048343:90 nop 8048344:90 nop 8048345:90 nop 8048346:90 nop 8048347:90 nop 8048348:90 nop 8048349:90 nop 804834a:90 nop 804834b:90 nop 804834c:90 nop 804834d:90 nop 804834e:90 nop 804834f:90 nop
08048350 <__do_global_dtors_aux>: 8048350:55 push %ebp 8048351:89 e5 mov %esp,%ebp 8048353:53 push %ebx 8048354:83 ec 04 sub $0x4,%esp 8048357:80 3d 14 a0 04 08 00 cmpb $0x0,0x804a014 804835e:75 3f jne 804839f <__do_global_dtors_aux+0x4f> 8048360:a1 18 a0 04 08 mov 0x804a018,%eax 8048365:bb 20 9f 04 08 mov $0x8049f20,%ebx 804836a:81 eb 1c 9f 04 08 sub $0x8049f1c,%ebx 8048370:c1 fb 02 sar $0x2,%ebx 8048373:83 eb 01 sub $0x1,%ebx 8048376:39 d8 cmp %ebx,%eax 8048378:73 1e jae 8048398 <__do_global_dtors_aux+0x48> 804837a:8d b6 00 00 00 00 lea 0x0(%esi),%esi 8048380:83 c0 01 add $0x1,%eax 8048383:a3 18 a0 04 08 mov %eax,0x804a018 8048388:ff 14 85 1c 9f 04 08 call *0x8049f1c(,%eax,4) 804838f:a1 18 a0 04 08 mov 0x804a018,%eax 8048394:39 d8 cmp %ebx,%eax 8048396:72 e8 jb 8048380 <__do_global_dtors_aux+0x30> 8048398:c6 05 14 a0 04 08 01 movb $0x1,0x804a014 804839f:83 c4 04 add $0x4,%esp 80483a2:5b pop %ebx 80483a3:5d pop %ebp 80483a4:c3 ret 80483a5:8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi 80483a9:8d bc 27 00 00 00 00 lea 0x0(%edi,%eiz,1),%edi
080483b0 <frame_dummy>: 80483b0:55 push %ebp 80483b1:89 e5 mov %esp,%ebp 80483b3:83 ec 18 sub $0x18,%esp 80483b6:a1 24 9f 04 08 mov 0x8049f24,%eax 80483bb:85 c0 test %eax,%eax 80483bd:74 12 je 80483d1 <frame_dummy+0x21> 80483bf:b8 00 00 00 00 mov $0x0,%eax 80483c4:85 c0 test %eax,%eax 80483c6:74 09 je 80483d1 <frame_dummy+0x21> 80483c8:c7 04 24 24 9f 04 08 movl $0x8049f24,(%esp) 80483cf:ff d0 call *%eax 80483d1:c9 leave 80483d2:c3 ret 80483d3:90 nop
080483d4 <main>: 80483d4:55 push %ebp 80483d5:89 e5 mov %esp,%ebp 80483d7:83 e4 f0 and $0xfffffff0,%esp 80483da:83 ec 10 sub $0x10,%esp 80483dd:c7 04 24 c0 84 04 08 movl $0x80484c0,(%esp) 80483e4:e8 07 ff ff ff call 80482f0 <puts@plt> 80483e9:b8 00 00 00 00 mov $0x0,%eax 80483ee:c9 leave 80483ef:c3 ret
080483f0 <__libc_csu_init>: 80483f0:55 push %ebp 80483f1:57 push %edi 80483f2:56 push %esi 80483f3:53 push %ebx 80483f4:e8 69 00 00 00 call 8048462 <__i686.get_pc_thunk.bx> 80483f9:81 c3 fb 1b 00 00 add $0x1bfb,%ebx 80483ff:83 ec 1c sub $0x1c,%esp 8048402:8b 6c 24 30 mov 0x30(%esp),%ebp 8048406:8d bb 20 ff ff ff lea -0xe0(%ebx),%edi 804840c:e8 9f fe ff ff call 80482b0 <_init> 8048411:8d 83 20 ff ff ff lea -0xe0(%ebx),%eax 8048417:29 c7 sub %eax,%edi 8048419:c1 ff 02 sar $0x2,%edi 804841c:85 ff test %edi,%edi 804841e:74 29 je 8048449 <__libc_csu_init+0x59> 8048420:31 f6 xor %esi,%esi 8048422:8d b6 00 00 00 00 lea 0x0(%esi),%esi 8048428:8b 44 24 38 mov 0x38(%esp),%eax 804842c:89 2c 24 mov %ebp,(%esp) 804842f:89 44 24 08 mov %eax,0x8(%esp) 8048433:8b 44 24 34 mov 0x34(%esp),%eax 8048437:89 44 24 04 mov %eax,0x4(%esp) 804843b:ff 94 b3 20 ff ff ff call *-0xe0(%ebx,%esi,4) 8048442:83 c6 01 add $0x1,%esi 8048445:39 fe cmp %edi,%esi 8048447:75 df jne 8048428 <__libc_csu_init+0x38> 8048449:83 c4 1c add $0x1c,%esp 804844c:5b pop %ebx 804844d:5e pop %esi 804844e:5f pop %edi 804844f:5d pop %ebp 8048450:c3 ret 8048451:eb 0d jmp 8048460 <__libc_csu_fini> 8048453:90 nop 8048454:90 nop 8048455:90 nop 8048456:90 nop 8048457:90 nop 8048458:90 nop 8048459:90 nop 804845a:90 nop 804845b:90 nop 804845c:90 nop 804845d:90 nop 804845e:90 nop 804845f:90 nop
08048460 <__libc_csu_fini>: 8048460:f3 c3 repz ret
08048462 <__i686.get_pc_thunk.bx>: 8048462:8b 1c 24 mov (%esp),%ebx 8048465:c3 ret 8048466:90 nop 8048467:90 nop 8048468:90 nop 8048469:90 nop 804846a:90 nop 804846b:90 nop 804846c:90 nop 804846d:90 nop 804846e:90 nop 804846f:90 nop
08048470 <__do_global_ctors_aux>: 8048470:55 push %ebp 8048471:89 e5 mov %esp,%ebp 8048473:53 push %ebx 8048474:83 ec 04 sub $0x4,%esp 8048477:a1 14 9f 04 08 mov 0x8049f14,%eax 804847c:83 f8 ff cmp $0xffffffff,%eax 804847f:74 13 je 8048494 <__do_global_ctors_aux+0x24> 8048481:bb 14 9f 04 08 mov $0x8049f14,%ebx 8048486:66 90 xchg %ax,%ax 8048488:83 eb 04 sub $0x4,%ebx 804848b:ff d0 call *%eax 804848d:8b 03 mov (%ebx),%eax 804848f:83 f8 ff cmp $0xffffffff,%eax 8048492:75 f4 jne 8048488 <__do_global_ctors_aux+0x18> 8048494:83 c4 04 add $0x4,%esp 8048497:5b pop %ebx 8048498:5d pop %ebp 8048499:c3 ret 804849a:90 nop 804849b:90 nop
Disassembly of section .fini:
0804849c <_fini>: 804849c:53 push %ebx 804849d:83 ec 08 sub $0x8,%esp 80484a0:e8 00 00 00 00 call 80484a5 <_fini+0x9> 80484a5:5b pop %ebx 80484a6:81 c3 4f 1b 00 00 add $0x1b4f,%ebx 80484ac:e8 9f fe ff ff call 8048350 <__do_global_dtors_aux> 80484b1:83 c4 08 add $0x8,%esp 80484b4:5b pop %ebx 80484b5:c3 ret
吓坏了,是不是?这样来看吧,最后一段中:
53 83 ec 08 e8 00 00 00 00 5b 81 c3 4f 1b 00 00 e8 9f fe ff ff 83 c4 08 5b c3
机器码
这样的代码,称为“机器码”,就是前辈们要手动输入的二进制代码转换成16进制后显示出来的样子。这样编程,需要考虑CPU的具体执行情况,比如“寄存器”等等,冗长而麻烦,十分艰苦。
后来有人坐不住了,他们提出:与其让人查表、手动输入数字,不如让机器代劳。于是他们发明了汇编语言。还是看最后一段:
push %ebx sub $0x8,%esp call 80484a5 <_fini+0x9> pop %ebx add $0x1b4f,%ebx call 8048350 <__do_global_dtors_aux> add $0x8,%esp pop %ebx ret
汇编语言
这样写程序,我们就可以直接输入英文,在让预先“辛苦”写好的程序来帮助我们转换成机器码。是不是相比“10010000”形象了许多?什么?什么叫很苍白?“还是看不懂啊,有木有!?”——其实我也这么觉得。让我们再次回到最后一段:
53 push %ebx 83 ec 08 sub $0x8,%esp e8 00 00 00 00 call 80484a5 <_fini+0x9> 5b pop %ebx 81 c3 4f 1b 00 00 add $0x1b4f,%ebx e8 9f fe ff ff call 8048350 <__do_global_dtors_aux> 83 c4 08 add $0x8,%esp 5b pop %ebx c3 ret
请注意,可以看到,每条汇编代码实际上是和每条机器码一一对应的。我们依然要关心CPU的具体执行状态,比如“pop %ebx”,就是从ebx寄存器中取出数据。所以要使用汇编编程依然十分艰难。
于是有人提出:能不能写一个程序来把更抽象的代码转换成机器码呢?
高级语言
以C语言为例,以下是刚才那一大段程序的C语言源代码:
#include <stdio.h> int main() { printf("Hello world!\n"); return 0; }
就这么长?没错,就这么长。它的唯一作用就是输出“Hello world!”以及一个回车。(囧)在这个例子中,代码很抽象,调用打印函数,输出“Hello world!”,你可以直接从代码的英文中读出它的意思,而不是不断纠结CPU的各种运行状态。这样编程就很简单了。我们只需写少量代码,就可转换成可以直接执行的程序了,而且不用关心CPU如何运行。这样的语言称为高级语言。
但这个做法有个缺点:由于编译器智能程度有限,无法生成最精干的程序,产生了大量冗余代码。比如上面的例子,如果直接使用汇编来写,其实完全可以在10行代码内搞定。
CPU的一个核心在一个时间段内可以运行一定数量的指令,这种概念称为指令周期,取决于你CPU的频率。比如说茶叶的电脑每秒钟可以运行2.3Gx2这么多代码(双核)。而茶叶家里的旧电脑可以执行2G行机器码。比如我们刚才的程序很显然比直接使用汇编写成的程序要慢。那么这样做有其他好处吗?为什么这些大公司这么有钱,都不用汇编写程序?那样不是更快吗?
小知识“指令集”
那么这么做有没有什么其他的好处呢?有。因为各种CPU的结构是不同的,他们的指令集也不同。就像中国人讲中文,英国人讲英文一样,你告诉一个英国人(假设他不动中文)“吃”,他一定弄不明白,而你告诉他“eat”,他就懂了。
我们常用的电脑使用“x86”指令集,比较通用,内容较多。而智能手机就使用了各种“ARM”指令集。比如“10010000”在茶叶的电脑上代表“什么也不做”,拷到手机上应该就不能执行了,因为ARM手机不懂这是什么意思。
那么为何我们的手机没有使用x86指令集呢?难道英特尔这样的大公司不能造出手机处理器吗?原因恰好出在Intel处理器(包括AMD)所使用的x86指令集上。 x86这种指令集包含了很多命令,称为“复杂指令集”。假设吃这个动作在x86上可以由“吃”这个命令代表。
ARM这类指令集(各种ARM处理器不完全兼容)称为“精简指令集”,包含较少指令。刚才我们实现的“吃”这个动作,在ARM处理器上往往要表示成“拿起筷子”、“夹起指定食物”、“放在嘴里”、“咀嚼”、“下咽”。虽然麻烦,但精简指令集有个好处:它只包含我们常用的指令。 人类的语言中常用的不过其中20%~30%,精简指令集也如此。这样的处理器核心可以做得更小、使用更少的电能以延长手机的待机时间。
但Intel也没有放弃手机市场,终于造出了x86的移动版。虽然更耗电(已经相差无几了),但浮点运算速度完虐同主频的ARM处理器,这就是我们刚才说的“吃”的问题。
小知识:为什么安卓手机更耗费硬件? 我们现在知道不同CPU由于指令集不同,程序不都是通用的。由于面向移动市场的处理器架构繁多,光是ARM就有好几种指令集而且并不是都互相兼容。
在我们通用的x86电脑上,程序直接使用系统里面的功能便能运行。但是这个方法在移动市场是行不通的,因为不同的CPU无法读取相同程序。
所以谷歌想了一个折中的办法:在硬件上运行Linux,再在这个Linux上运行一套Java语言来调用系统的各种功能。Java有个特性“一次编译到处运行”(其实往往是一次编译到处排错),Java虚拟机虽然被编译成不同的机器码,但它们可以读取同样的Java程序,解释其功能并执行(所以叫做虚拟机),且表现一致。这时Java语言所写成的程序来使唤这个Java虚拟机。由于中间间隔了一个虚拟机,算上虚拟机读取、解释、转接程序所带来的开销,Java程序往往花掉与直接运行在硬件上的C程序相比数个数量级的时间。这直接导致了安卓手机更吃硬件。
这幅暴走漫画可以形象地说明这个过程:
(没弄明白谁是原作者,先感谢下)
什么是“开源”? 进来开源这个词语越来越流行了。比如我们熟悉的Android就是一个开源软件。开源具体是什么呢?回到文章中的例子:
#include <stdio.h> int main() { printf("Hello world!\n"); return 0; }
这个程序是一个最简单、最著名的C语言程序:Hello World。这里以文本方式显示出来,称为“源代码”,经过编译器“编译”后才能生成可以运行的程序。我们使用的很多软件,比如茶叶喜欢玩的《孤岛危机》的源代码是“闭源”的。看过前文应该知道,源代码在编译过后生成的机器码像天书一样难懂。所以我们是无法通过阅读源代码这一最方便的办法来探知闭源软件的原理的。
与闭源相对,“开源”的意思就算“开放源代码”。意为将源代码拿出来让大家自由下载、编辑。
软件许可证&开源许可证
一般我们安装程序时有一个“阅读法律协议”的步骤,严格地说我们需要阅读并接受这些协议才能安装软件。当然,茶叶和很多同学一样,很多时候看都不看就点了“下一步”,所以错过一些很重要的信息,如“微软不为Windows崩溃带来的损失负责”之类的。 在开源世界,安装的软件同样要以一些协议发布。不过考虑到个人开发者不容易得到全面的法律参考,自行编写协议难免有漏洞,所以开源软件往往会选择一些早就写好的开源协议而不是自行撰写。
比较著名的协议有GPL、Apache等。比如GPL在规定任何人都可以使用在此协议下发布的软件,也规定了发布者必须放弃该软件的版权,后来的修改者必须将改进返回到原项目中等义务。
GNU计划
GNU计划是最负胜名、争议最多的开源计划。以下是茶叶偷懒从GNU计划官网复制的介绍:
GNU是什么?
GNU 是一个由 自由软件 构成的类 Unix 操作系统 — 自由软件尊重你的自由。你可以选择安装一个完全由自由软件构成的 基于 Linux 内核的 GNU 系统。 GNU 工程 创始于一九八四年,旨在开发一个完整 GNU 系统。GNU这个名字是 “GNU's Not Unix!” 的递归首字母缩写词。 "GNU" 的发音为 g'noo,只有一个音节,发音很像 “grew”,但需要把其中的 r 音替换为 n 音。 类 Unix 操作系统是由一系列应用程序、系统库和开发工具构成的 软件集合 , 并加上用于资源分配和硬件管理的内核。 GNU 自己的内核 Hurd 仍在开发中,离实用还有一定的距离。因此,现在的 GNU 通常使用 Linux 内核。这样的组合即为 GNU/Linux 操作系统。已经有上百万人在使用 GNU/Linux,但他们中的很多人把它误称为“Linux”。
自由软件是什么?
“自由软件” 是权利问题,不是价格问题。要理解这个概念,自由应该是“言论自由”中的“自由”,而不是“免费啤酒”中的“免费”。【注释】 自由软件关乎使用者运行、复制、发布、研究、修改和改进该软件的自由。 更精确地说,自由软件赋予软件使用者四种自由: 不论目的为何,有运行该软件的自由(自由之零)。 有研究该软件如何运行,以及按需改写该软件的自由(自由之一)。取得该软件源代码为达成此目的之前提。 有重新发布拷贝的自由,这样你可以借此来敦亲睦邻(自由之二)。 有改进该软件,以及向公众发布改进的自由,这样整个社群都可受惠(自由之三)。取得该软件源码为达成此目的之前提。 【注释】自由——Free——可以理解为“免费”
GPL公共许可证就是GNU计划的一部分。以GPL作为一个法律协议来发布的软件就是自由软件了。如Linux内核便是以GPL协议发布的,GPL协议中“必须返回所做改进”的规定有效保证了Linux内核在二十一年的漫长时间里没有被不仅没有出现闭源的碎片版本反而具备了越来越多的功能。
开源的好处
茶叶体会到的到了开源软件的种种好处。
一、省钱(至少对茶叶而言)。很多开源软件都是免费的,比如茶叶电脑里面的Linux,用来上网十分方便。当然,也有一些开源软件是收费的,GPL实际上没有禁止出售开源软件,比如GNU计划的启动资金就是创始人大胡子理查德贩卖开源编程神器Emacs赚来的。当然,他也提供了一些附加服务,比如人员培训。(茶叶喜欢Vim)
二、便于学习。原因显而易见,开源软件的源代码是开放的,所以容易学习其原理。而且就算是茶叶这样的穷学生也能够方便地获得开源软件。一个典型的例子是:市场上涌现了大量分析Linux及其他开源软件原理的书籍。
三、开源软件性能强劲。很多人是抱着这个想法去使用开源软件的,比如我们的度娘。但是大胡子理查德很是反感这个。作为自由软件运动的领导人,他认为:软件是自由的,所以才被我们采用。比如说前些日子Value公司的Linux小组欣喜地发现《求生之路》在linux上运行得更快。然后他们费尽心血还拉来Nvidia也没能在Windows上跑出相同的帧率。
四、开源软件凝结了大量人类劳动,可以保证其质量和性能。由于开源,开源项目往往能够获得大量黑客的关注和他们贡献的源代码,他们可以借此获得荣誉、声望、经验、朋友等等东西。一个NB的开源项目耗费的工时往往是同等闭源项目的数十倍,这解答了为什么Linux内核远远强与Windows内核。
五、安全。茶叶用的Linux从来没有遭受过病毒、木马的困扰。有人说:Linux使用范围太窄,所以没有病毒。那好,我问你:既然能轻易黑掉Linux,为什么黑客还要编制Virus for Windows来窃取QQ?直接从腾讯那里拖数据库多好。开源软件的安全是由于开源软件具有灵活性,可以及时部署比较先进的安全策略(请围观Windows那纠结的权限设置)。而且开源软件的代码直接暴露在黑客面前,使他们可以迅速地发现每一个BUG并修补。什么?你发现了BUG打算留着自己利用?对不起,在你找到有效的漏洞利用方案之前,这个BUG很可能已经被别人修补了。看源码的并不只有你一个人。
五、防止重做车轮。想想:如果你可以在项目中使用开源软件完美实现一个功能,你还会自己开发一个吗?看看安卓的内核是什么,你就明白了。
开源的缺点
万事万物都有反面。开源软件也收到一些制约。比如:
一、资金。绝大多数开源软件都不收费,那么他们的资金从哪里来?毕竟能够获得赞助的项目不多,能挣钱的也不多。不是谁都可以称为“Linux”(此处指 Linux Kernel ,而不是 GNU/Linux)。影响了项目的可持续性(其实很多经典的开源软件寿命要远远长于它们的闭源伙伴)
二、受一些奇葩专利的限制。比如Ubuntu默认没有使用Coverflow来切换窗口,这是水果公司的专利。看看安卓里面那个Java就知道纠结程度了,甲骨文多作恶。而且Linux平台下许多解码器就是受到限制的。
三、消费化不足。因为做开源的公司很少,开源软件商品化就比较欠缺。比如说我们可以看到虽然Ubuntu挺漂亮,但是Ubuntu的软件商店里面却没有多少好玩的游戏。我们可以看到那些经典的游戏都是闭源的。
四、更多关于开源优缺点的文章:教育技术资源网
五、暂时想不起来,有待维护。(Linux茶馆的文章是动态维护的哦)
那么,到这里,你应该对软件有一点认识了。
附:金山卫士开源计划~金山也开源啦~
(如有错误和补充,请予以指出)
|