分享

我们使用的软件是怎么来的&软件

 astrotycoon 2013-08-17

用了这么久的电脑,你一定很好奇我们使用的软件是怎么来的。那么今天茶叶就来简单科普一下这个过程,同时也将介绍开源软件是怎么一回事。

(图为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茶馆的文章是动态维护的哦)

那么,到这里,你应该对软件有一点认识了。

附:金山卫士开源计划~金山也开源啦~

(如有错误和补充,请予以指出)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多