Chinaunix首页 | 论坛 | 博客
  • 博客访问: 901810
  • 博文数量: 113
  • 博客积分: 3160
  • 博客等级: 少校
  • 技术积分: 1801
  • 用 户 组: 普通用户
  • 注册时间: 2011-08-19 10:09
文章分类

全部博文(113)

分类: C/C++

2012-06-03 22:13:58

作者:佚名     一、指令集和CPU(Instruction set and central process unit)  

1.计算机只认识01.01的排列组合构成指令。不一样的CPU,会有不一样的指令集构架。指令集构架分为RISCCISC构架(RISC, reduced instruction set computer,精简指令集。CISC, complex instruction set computer,复杂指令集。)RISC 设计者把主要精力放在那些经常使用的指令上,尽量使它们具有简单高效的特色。对不常用的功能,常通过组合指令来完成。CISC指令集的指令系统比较丰富,有专用指令来完成特定的功能。常用的PCCISC构架,也即X86指令构架集是CISC构架。

 

 

2.除了intel/AMDx86之外,还有很多很多种CPU。如DECAlpha21364IBMPower PC G4HPPA8900SGIR12000ASUN Microsystem公司的Ultra SPARC。这些CPU多采用RISC构架。

 

 

3.手机之类的掌上智能用品,一般采用ARM技术的处理器。ARM处理器是RISC指令集构架。

 

 

4.除了指令集构架之外,CPU通用构架体系有冯诺依曼体系结构和哈佛结构。还有其他的结构。还有硬件支持函数式编程的CPU体系结构和指令集...

 

 

5.龙芯采用什么指令集构架?呵呵,当然不是x86,人家那个是有专利(Proprietary)的。也就是说,龙芯认识的01的排列组合是和Intel芯片认识的01的排列组合是不一样的。据说龙芯大概抄的是MIPS的方案,指令集构架也是RISC的。龙芯上面是不能跑windows操作系统的,只能跑Linux和其他的开源操作系统。为什么?因为windows系统源代码是不开放的,只有Microsoft公司自己知道,Microsoft想让windows操作系统在什么芯片上跑,那种芯片才能跑。而Linux系统的源代码是开放的,所以开发龙芯的团队可以把Linux操作系统移植到自己的芯片上去。

 

 

 

二、计算机怎么认识程序(Why computer can read my program)    

1.由于计算机只认识01,所以要写一个程序让计算机认识的话,只能用二进制代码。所以最早期的程序员用01编程。这是真的。据说那个时代最常用的编程工具是纸带和打孔机。我没弄懂输出终端是用什么,难道是显示器吗?如果输出终端也是纸带和打孔机的话,我想发展计算机技术的美国人那时的这种举动真的是太搞笑了,哈哈。

 

 

2.01编程实在令人无法忍受。于是汇编语言出现了。汇编语言允许程序员用一个助记符而不是一串01代表一个指令。

 

 

 

三、编译器(Complier)    

1.汇编语言为什么能用助记符代表一串01?这是因为有汇编编译器。汇编编译器会将那些助记符转换为对应的01的串。也就是说,你可以用"mov eax, 0xa"这样的东西去代替一串形如"0011010101011100..."这样的东西。

 

 

2.汇编编译器本身也是一个程序。它只是一个特殊的程序。既然是程序,那么这个程序本身是用什么语言写的呢?答案是,第一个汇编编译器只能用机器语言写。

 

 

3.当你有了第一个汇编编译器,那么第二个汇编编译器(需要第二个汇编编译器可能是它会比第一个汇编编译器更好更强大吧)用什么来写呢?这个时候能用汇编语言了(因为你有了第一个汇编编译器了),你可以选择用汇编语言写,也可以用机器语言写。如果用汇编语言写,这是一个无穷的过程,你可以写第N个汇编编译器。这个无穷的过程叫做编译器的自举(bootstrap. 自举这个词容易让人联想到其它的东西...对不起我邪恶了)

 

 

4.编译原理方面的书籍,“龙书(Dragon book)”《compiIersPrinciplesTechniques and Tools》,“鲸书(Whale book)”《Advanced Compiler Design and Implementation》,“虎书(Tiger book)”《Modern Compiler Implementation in Java/C++/ML,Second Edition》。

 

 

 

 

四、移植问题(The trouble of Transplanting)    

1.如果程序员写了一个在x86指令集构架的CPU上运行的程序,想要放到IBMPower PC CPU上运行怎么办?甚或想要放到龙芯上面跑要怎么办?既然有不一样的指令集构架和CPU,自然完成同样功能的指令非常非常可能是完全不一样的(事实上这确实是事实,同一条指令CPU设计者爱用什么01的组合就用什么01的组合,对吧。何况CPU之间的差距不单表现在这里。CPU有不一样的体系,会有不一样的寄存器设定,对吧。专业一点说吧,不一样的CPU有不一样的编程模型(programming model)和编程范式(programming paradigm)。总之这是个非常复杂和令人头痛的问题,CPU实在是太复杂了^_^)。

 

 

2. 每种CPU都会有使用说明书,不过是非常令人困惑的使用说明书而已。这个说明书会提炼常用的指令和接口、以及寄存器设定给程序员使用。一般把这个说明书的这一部分叫做相应的CPU的编程模型。很显然,不同类的不一样的CPU有完全不同的编程模型。Intelx86芯片的说明书有三卷,其中第三卷是讲CPU编程模型的,有兴趣的不妨去读一下^_^.

 

 

3.在只有机器语言和汇编语言的时代,移植等于重写程序。同样的程序,在x86指令集上要这样写,在某些RISC指令集上要那样写。这意味着程序员为了让同一个程序在不同的CPU上跑就要看不同的令人困惑的CPU使用说明书。

 

 

 

五、C语言和移植性(C programming and Transplanting)      

1.还有其他的高级语言。要知道C之所以流行起来,除了它本身的优良设计之外,还和UNIX操作系统有密切关系。简单的说,如果不是UNIX操作系统,C不会像今天这样具有绝对的统治地位(操作系统级的代码)。从编程范式(programming paradigm)上来说,C是属于命令式编程范畴、属于函数式/过程式编程设计、适用于模块化编程的语言。

 

 

 

2.C的经典和地位毋庸置疑。程序员可以写同一段C程序,放到不一样的CPU上运行。当然还是会比较麻烦,可是这毕竟比在不同的CPU上用不同的汇编语言强多了,对吧。所以C有很强的移植性。C还有很强的底层操作能力,能操作到内存字节级别,也有字节位操作能力。嗯,汇编语言的硬件操作能力能到达CPU寄存器(事实上除了硬件连线没有汇编语言无法操作的东西)C确实很难做到这一点,不过如果你真的需要操纵CPU的寄存器的话,那么就去看CPU说明书吧。C有刚好够的流程控制(顺序,判断,循环)和函数功能(调用,递归),有刚好够的运算符,允许自定义数据结构,它把内存划分为栈和堆...总之就是C可以让你把计算机看成是一个“C计算机”。C在这一点上做得刚刚好,它就像是一台抽象的计算机,而且抽象得不浓不淡恰到好处。

 

 

 

3.为什么程序员可以写同一段C程序放到不一样的CPU上运行?C做了什么有这么大的魔力?因为每个CPU厂商都会提供C语言的编译器。这是CPU厂商的共识,没办法,...呵呵。所以,x86指令集构的CPU上有C语言的编译器,SPRAC CPU上也有C语言的编译器,这两个编译器是不一样的,同一段C程序,最后通过编译器转换出来的二进制代码是不一样的,但是它们提供一样的C语言编程范式接口。打个比喻。这些编译器就像一个接头,接头的A端是一样的,都是C语言,提供给C程序员的是一致的C语言编程语法,而接头的B端是不一样的,是不一样的CPU汇编代码。对了,这里提一下,一般来说,C编译器只把C代码转换为汇编代码(而不是直接转换成机器代码),而汇编代码又通过汇编编译器转换成最终的机器代码。因为强大的IDE(Integrated Development Environment.嗯,我指的大概是visual studio)隐藏了这些底层细节,这导致一些初学者以为C语言就是某个IDE,这有些遗憾(Linux程序员一般都知道这个事实,因为他们编译程序之前可能写gcc -S main.c -o mian.s这样一个Shell命令去查看汇编代码,所以Linux程序员相对来说更清楚这些细节。这并不是诟病windows操作系统,只是有些老师没有向他的学生强调这一点)。我想要猜出第一个C编译器是用汇编语言写的应该不是很难...第二个C编译器么...可以猜猜看。

 

 

 

4.所以程序员就小小的幸福了一下,程序员只要会用C,可以不管跑程序的CPU是什么种类的。这等于说移植问题可以不用管了,呵呵。可是实际上还是要管的...很多程序员都知道,turbo C编译器定义一个int数据类型等于2字节,gcc编译器定义一个int数据等于4字节...

 

 

 

六、操作系统(Oprating System)    

1.这个名词令我很有压力。不过我没打算谈什么高深的话题,哈哈。

 

 

2.一般来说我们写的程序叫做“应用程序”。这表明我们不是在写操作系统。是的,CPU上电执行第一条代码,跳到BIOS,引导磁盘扇区,驱动各种各样的硬件,这些事情让操作系统去做吧。你打算自己做?好吧...Linux源代码是开放的,还有,中国有个叫于渊的家伙,写了本书,叫《自己动手写操作系统》,最近这本书更新版本了,叫《一个操作系统的实现》。试一下还是不错的,如果有时间的话。

 

 

3.操作系统不是只有Microsoft才能做的,操作系统也不等于windows,虽然我知道对于个人PC来说,尤其是在中国,大部分情况下它等于windows。除了windows之外,还有Linux,有Solaris,各种各样的UNIX like(其实现在应该称为Linux like了哈哈)操作系统,它们运行在各种各样不用CPU不同构架的大型中小型服务器上。还有DOS,有AIXL4QNX...而且必须要知道的一点是,windows并不是最强大的操作系统,Linux的安全管理,网络服务,系统进程调度,内存管理,文件系统都十分优秀,尤其前面两项令windows望尘莫及。

 

 

4.操作系统的内核大体分为这样的模块:内存管理;进程调度;进程间通信;文件系统;网络协议栈;设备驱动。

 

 

 

5.操作系统管理了一切。包括我们写的程序。我们写的程序,运行起来之后,不过是操作系统上的一个进程而已。操作系统允许你这样做,不允许你那样做。操作系统首先运行在CPU上,我们的程序是由操作系统和运行库启动的。写程序必须遵守操作系统的规则。在Linux系统下写C程序,第一行代码可能是int main(int argc, char **argv, char *env[]),在windows操作系统下写C程序,第一行代码可能是int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )。就是这样。没什么其他的好谈的了。

 

 

6.关于操作系统方面的书,《深入理解操作系统》,《一个操作系统的实现》,《操作系统的设计与实现》都很好很强大。

 

 

 

七、链接器与装载器(Linker and Loader)      

1.在编译器(compiler)一节里面提到了编译器。那么除了编译器之外,还有非常重要的程序工具,就是链接器和装载器。这两个工具十分重要和底层,事实上这两个工具出现的历史比编译器要早,基本上计算机一出现它们就几乎是必须出现的工具了。链接器和装载器的发展历史跟计算机操作系统的发展史一样悠久而且彼此密切相关。

 

 

2.链接器解决的问题就是将模块化了的各个.o.obj文件链接起来,这涉及到符号(symbol resolve protocal)解析协议和地址重定位(Address Relocation)

 

 

只能简单说一下概念(其实一个好的计算机程序员对一些重要的东西要有清楚的概念,而且只要有概念就OK),同样拿C语言来说事(很抱歉我只会C学得太烂不好意思说会,Java很好很强大但我只能无聊时看看>),而且说这些好像也只有拿C来说比较方便),比如你写了一个main.c, 一个playlist.c,然后编译它们,经过编译器编译之后,它们成为mian.oplaylist.o(windows系统下扩展名为.obj,因此它们可能是main.objplaylist.obj),这两个.o/.obj文件中存在相互引用的部分,比如你在mian.c里面写了这样一句: extern struct ListItem item1;  item1的定义在playlist.cstruct ListItem item1;

 

 

那么在main.o中是不知道item1实际的地址的(其他的地址都被编译器确定下来),它只知道它引用了一个外部符号,编译器先将这个外部符号的地址填成0,而这个外部符号的地址在链接过程中确定(被重写)。当链接完成之后,整个程序的内存布局和函数、全局变量、静态变量的内存地址才都被确定下来(确定的地址是虚拟地址。这个程序认为它独占了所有的计算机内存资源,在32位机上就是2G操作系统>3G操作系统>)。这时程序(就是这个可执行文件,windows系统下为.exelinux系统下为.elf的那个东东)还没有被装载(还没有被装载器读到内存中去,它还在硬盘上放着呢),也就是说,它还没有被运行,因此这里所说的地址在装载到内存中之后还可能会有一些变化。

 

 

 

3.你一定很奇怪,你写了一个程序,这个程序被编译成.o.obj文件,然后它们被链接器链接起来,然后你只要在Shell下执行一个./app(假设你写的这个程序叫app)指令(Linux system)或者在cmd(开始->运行->cmd)中执行一个app指令,这个程序就运行起来了。这背后其实有一个非常复杂的装载过程。装载器是一个程序,它能够把另一个程序载入内存并运行。每次你启动一个程序,标准的操作系统装载器在幕后替你做这项工作。是的,这时硬盘上的可执行程序文件才被读入到内存当中,代码的内存地址被完全确定,一个进程在操作系统中注册,你的程序,嗯,它终于运行起来了,...虽然那些步骤表面上看起来根本没这么复杂...

 

 

 

4.想了解细节,请阅读《程序员的自我修养》。这本书介绍了很多这方面的细节,是难得的一本好书(我拿到之后爱不释手,立马买了本收藏,结果同住的一个家伙不小心抓掉了封皮......)。书中的内容大多来自于一本国外的叫《Linker and Loader》的书。

 

 

 

 

八、操作系统运行库(Runtime Library)      

1.程序员想要写一个应用程序,这个应用程序想要在屏幕上显示图形,想要读写硬盘上的文件,想要与远程计算机进行通信,程序员会怎么做?想要在显示终端上显示字符看上去很简单(嗯,不是只要敲键盘就可以了吗?),事实上,这相当复杂,程序员首先要初始化显示终端,要设置显示终端控制芯片的寄存器,控制显存芯片...等等,而读写硬件寄存器有可能只有汇编语言才能够做到,而且还要程序员对CPU的编程模型非常了解。如果这些东西都让某一个程序员去做,那他一定会疯掉...所以事实上,操作系统调用硬件驱动程序替程序员管理了所有的硬件,并提供一致的编程接口给程序员,这些接口被叫做API(Application Programming Interface,应用编程接口)。所以如果C程序员想要读写linux系统上的程序的话,只要简单的调用一个read函数或write函数就可以了。

 

 

 

2.C运行库是代码重用的初等形式。C运行库是对系统API的一层封装,比如freadfwrite函数,而有些则没有封装API而是直接的重用代码,比如memcpystrcpy函数。

 

 

 

3.库分为静态库和动态库。静态库链接库就是当应用程序被编译器编译之后,通过链接器直接和应用程序链接在一起,这样静态库就相当于成为了应用程序中的一个模块,或者说,经过链接器的链接之后,静态库成为了应用程序的一部分。可以想象,如果每个应用程序都链接了同一个静态库,那么这个静态库就会被多个应用程序所复制,相当于每个应用程序中都有一个这样的静态库副本存在,每个应用程序都多了一部分静态库的内容,这当然是十分占用硬盘空间和内存的。为了解决这个弊端,动态链接库出现了,动态链接库在应用程序需要的时候被加载器加载到内存中,与应用程序一起运行。当第二个应用程序启动也需要这个库的时候,这个时候内存中这个动态链接库已经在运行了,它不需要被复制成为副本而是直接运行。动态链接库技术其实跟静态库非常不一样,静态库相对来说比较简单,需要的工具只是链接器,而操作系统会管理动态链接库的运行,管理的机制比较复杂。

 

 

 

阅读(2226) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~