Chinaunix首页 | 论坛 | 博客
  • 博客访问: 170493
  • 博文数量: 84
  • 博客积分: 3742
  • 博客等级: 中校
  • 技术积分: 870
  • 用 户 组: 普通用户
  • 注册时间: 2009-09-20 19:38
文章分类

全部博文(84)

文章存档

2012年(6)

2011年(21)

2010年(54)

2009年(3)

分类: LINUX

2011-07-20 17:03:36

在理解一个源代码是如何成为可执行文件时,我简单的回顾下硬件层面、操作系统层面的知识。

开机启动

一 BIOS扫描基本设备,cpu、memory、display etc,从硬盘启动,读盘面1磁道1扇区1的内容进入内存,这段内容是操作系统引导程序

二 cpu的任务是计算,不同的cpu制定了一套instruction set,通过调用指令集,输入数据,输出数据

三 DMA directly memory access 主存,数据存储介质,读写速率高,满足对cpu的数据输入输出

四 操作系统 负责用户资源(进程、内存、文件系统、硬件设备etc)的管理、调度、安全

以上是我们在开机后做的工作,大量的、复杂的工作,而我们确实享受者。

我们开始编写源代码,然后编译执行。我们感觉到自己非常厉害,其实我们仅仅做了一点点东西。

1 编译器

源代码通过编译器变成汇编文件。

编译器做的主要工作就是根据语言,词法、语法分析,将面向对象的、面向过程的高级语言翻译成汇编语言。

我认为编译器应该是建立在操作系统上的,因为不同的cpu的汇编语言存在差异,所以编译器无法跨硬件平台,需要与操作系统匹配。

(java的编译与c c++的编译,我们称之为传统的编译,是不同的,java的编译是生成字节代码,也就是JVM能够读懂的代码,这是一种中间代码。)

在编译的过程中,所有的全局变量在内存中的标识是虚拟地址,而不是我们在开发过程中定义的名称。例如int a = 1;这里的a在汇编代码中就不存在了,取而代之的是一个地址。在汇编文件中有一个符号表,它指明了这个地址的名称为a,以及其他信息,用于以后的debug。由于并非是可执行文件(在可执行文件中所有变量、调用的地址才能真正确定),这些地址是未确定的,所以对于这些数据(变量、函数)有relocation table,需要在最后的链接过程中对全局变量、函数做relocation

2 汇编器

通过汇编器,将之前的汇编代码,转行成机器语言。但格式并非是纯执行代码。

这个时候生成目标文件,文件有不同的段组成,head、text、date、symbol、string、relocat等等

3 linker 链接

linker 就是将目标文件合并,符号解析、重定向。

合并,就是多个obj组合为一个,一个lib或者elf执行文件

重定向,由于地址程序执行代码的地址可以确定了(多亏了操作系统的虚拟内存,每个程序的虚拟内存空间地址都是一样的),之前我们无法确定地址的变量、函数以及由于合并而受到影响的变量、函数都可以给一个地址了。

符号解析,前面的地址都变化了,符号表中内容要更新

4 loader 加载

最后我们运行程序,加载之前linker好的elf文件。

在内存中画一片空间,几个重要的区域。静态code文件区,全局变量区,heap区,stack区。

stack区:是程序运行的动态执行流。我们平时看的程序在做些什么,就打threaddump、processdump实际上就是看stack中的内容。

如果这个时候符号表中的内容在gcc编译时保留了,那么我们可以看到详细的系统调用过程。在linux中我们用lstack和strace可以看程序的stack。

heap区:这个是在程序中deveploer制定分配的区间,用来保存数据,c是面向过程的,所以对内存的分配比较容易理解,nmap函数,分配多少字节等等,最后还要释放这些空间,否则会出现内存泄露。对于面向对象语言java,可能了解起来就比较麻烦,因为java编译器为我们做了一个面向对象到面向过程的转换。但是不变的是我们实例化一个对象,就必须nmap一个区间给这个实力。对于这个实例的attribute属性进行保存,而method方法则无需,因为他是执行流,也就是code,通一个类的所有的实例的method是一样的,在静态code区有method的执行代码。

stack和heap的关系:当然stack中持有了大量heap的reference,比如java,两个类的执行流一样,但是reference不同,只想heap不同的区域,代表不同的实例。

stack中要注意的就是push and pop,他是函数调用、参数传递的主要手段。

动态链接:动态链接库是通过loader来做重定向。在加载时我们并不将库放入内存,而是在运行时通过虚拟内存将一份代码映射到多个程序中。

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