程序编译和链接原理理解

一、本书解决的问题
本书主要介绍系统软件的运行机制和原理,涉及在Windows和Linux两个系统平台下,一个应用程序在编译、链接、和运行时所做的事,具体如下:
1.Windows和Linux操作系统下各自的可执行文件、目标文件格式?
2.普通的C/C++程序代码如何编译成目标文件及程序的目标文件如何存储?
3.目标文件如何被链接器链接到一起,并且形成可执行文件?
4.目标文件在链接时符号处理、重定位和地址分配如何进行?
5.可执行文件如何被装载,装载到哪,并且执行?
6.可执行文件与进程的虚拟地址空间之间如何映射?虚拟地址和物理地址之间的映射由系统完成,怎样完成?
7.什么是静态链接,动态链接,两者的区别,为啥要使用动态链接?
8。Windows和Linux如何进行动态链接及相关问题?
9.堆,栈,代码段,数据段,BSS段的理解?
10.运行库,系统调用与API的理解?

二、相关书籍介绍
1.《Linkers and Loaders》                         --介绍链接和装在方面最为完整和权威的理论著作
2.《Linux内核源代码情景分析》                     --基于Linux2.4内核的,对于内核很多细节描述详细
3.《深入理解计算机系统》                          --对整个计算机硬件体系结构进行了深入浅出的介绍,是理解系统底层不可多得的书籍
4.《深入理解Windows操作系统》                     --深入理解Windons内核最好的书籍
5.《Advanced Programming in the UNIX Environment》--理解UNIX系统内核,运行库和运行环境最好的选择

三、相关知识链接
1.计算机硬件的最为关键的三个部件:CPU,内存,I/O设备
1)CPU及其架构的发展
1>CPU早期的核心频率不高,跟内存的频率一样,故采用了用一个总线(BUS)将两者联系起来。
2>由于很多的I/O设备速度与CPU和内存慢很多,为了协调CPU和I/O设备间的通信,每个设备都会有一个相应的控制器,其相当于解决了不同速度之间的协议问题。
3>其实CPU的发展速度很快,导致内存的速度跟不上,故产生了与内存频率一致的,CPU通过分频和系统总线通信;
4>北桥-满足图形设备,CPU和内存之间的通信,利用北桥芯片实现;
南桥-主要解决低速设备和CPU之间的通信

2)内存及其发展
1>早期程序访问的都是物理内存,直接对内存地址进行操作,但存在很多问题:
地址空间不隔离
内存使用率较低
程序运行地址不确定
内存大小受限
2>采用的最基本的方法就是加入中间层,及引入虚拟地址概念
3>同时采用分段的方式,从应用程序的角度来分段存储
4>为了提高内存的使用率,采用分页的方式来实现

3)I/O设备,采用分层的思想解决设备多样性的问题,其中会设计到很多设备驱动架构,其主要是尽量多层次的挑出设备的共同属性,综合使用。
2.线程
1)多线程问题
Windows:内核有明确的进程和线程的概念并且有一系列的API来操作它;
Linux:对多线程的支持比较贫乏,其并没有真正意义上的线程概念,所有的实体都是任务,每一个任务就可以理解成一个单线程的进程,具有内存空间,执行尸体,文件资源,当共享了同一个内存空间的多个任务构成了一个进程。
2)关于线程访问权限的理解
线程私有 线程之间共享(进程所有)
局部变量 全局变量
函数的参数 堆上的数据
TLS数据 函数里面的静态变量
程序代码,任何线程都有权利读取并执行任何代码
代开的文件
3)用户线程和内核线程
一般情况下,用户线程都会有对应的内核线程,但并不是一一对应的,存在三种对应模型:一对一模型,一对多模型,多对多模型
3.系统软件(用于管理计算机本身的软件)
1)平台性的;如操作系统内核,驱动程序,运行库,系统工具
2)用于开发性的:编译器,汇编器,链接器等开发工具


四、静态链接
1.编译和链接
以下四个过程就是编译器掩藏起来帮我们处理掉的过程
1)预编译(生成*.i文件)
1>将所有的“#define”删除,并且展开所有宏;
2>处理掉所有条件预编译指令,如:“#if”、“#ifdef”、“#elif”、“#else”、“#endif”;
3>处理“#include”指令,这是一个递归过程;
4>删除所有的注释“//”和“/* */”;
5>天剑行号和文件名标示
6>保留所有的#pragma编译器指令,待编译器使用;

2)编译(生成*.o文件,也叫目标文件)
把预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相对应的汇编代码文件

3)汇编
汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令

4)链接(生成*.exe文件,也叫可执行文件)
1>地址和空间分配
2>符号决议
3>重定位

2.目标文件
1)可执行文件
1>Windows下面PE格式
2>Linux下面是ELF格式,其格式分类见附录:ELF文件类型
3>目标文件、动态链接库、静态链接库都是按照可执行文件格式存储的
2)目标文件
1>目标文件的组成:
a.目标文件属性:是否可执行,动态或静态链接,段信息等等
b.程序指令
c.程序数据:数据段和.bss段
2>程序和数据分段的目的
a.可以标记为不同属性,分开存储:程序为只读,数据为可读可写
b.可以分开进行缓存
c.指令可以重复使用,即指令共享
3>ELF文件详细介绍
a.文件头:ELF魔数,文件机器字节长度,数据存储方式,版本,运行平台,硬件平台,入口地址,程序头入口和长度,段表的位置和长度及数量等
b.段表:保存这些段的基本信息,段名,长度,在文件中的偏移,读写权限及段的其他属性
c.重定位表:即为“.rel.text”段
d.字符串表
3.静态链接
静态链接就是将几个目标文件链接起来形成一个可执行文件
1)空间和地址分配
1>按序合并:就是将各个目标文件依次合并,这个问题是当目标文件过多时,就会产生很多零散的段,地址对齐时会浪费空间
2>相似段合并:就是将相同段分配到一起,
2)符号解析和重定位
1>重定位通过分析重定位表来实现重定位


五、装载和动态链接
1.可执行文件的装载
1)装载方式
1>覆盖装入:直接按照执行文件段大小进行映射即可
2>页映射:
2)链接视图和执行视图
1>链接视图
按可执行文件进行分段,但考虑到执行视图参考的属性不同,故后面会对segment进行section转化,其中还会涉及到段对齐等问题
2>执行视图
a.可读可执行段:
b.可读可写段:
c.可读段:
3)窥探Linux的进程内存映射
1>可执行文件到VMA的映射:
在创建进程时,即建立了可执行文件的段到VMA的映射,但没有真正映射到物理地址
2>VMA到物理地址空间的映射:
在Linux进程内存访问时,当没有物理地址页时就会产生页错误,此时调用操作系统进行物理地址到VMA的映射
2.动态链接
1)动态链接与静态链接对比分析:
1>静态链接:
a.内存空间的限制,链接进所有需要的静态文件,会使执行文件过大;
b.会存在链接进去很多重复文件,浪费内存空间;
c.对于程序的部署,每次需要整个重新更新整个执行文件。
2>动态链接;
a.增强了程序的可扩展性;
b.增强了程序的可执行性。
2)地址无关代码:
1>执行视图中分开存储代码和数据,提高代码的使用率;
2>地址重映射时会有问题,多个线程可能对应同意代码段,不能进行地址重映射;
3>基于以上原因,产生了地址无关码,每个线程都有自己对应的数据区,故可以那些需要重映射的地址和数据保存在一起,这样就产生了地址无关代码
3.内存布局
1)程序的内存分配
1>对于Linux而言,内存布局
kernel space
stack
dynamic libraries
heap
data
code
reserved
2)栈
1>定义:一个动态的内存区域,函数执行完了之后释放,遵循规则:FIFO
2>调用惯性:程序在进行调用时,其数据、地址、寄存器变量等均要遵循一定的规则
3>函数返回值:实现接口之间的通信。
3)堆
1>定义:栈会释放,静态区域只能在编译时建立,结合两者的优势,产生了可以动态生成和释放的堆
2>Linux进程堆管理:
a.brk()系统调用
b.mmep()系统调用
3>堆算法
a.链表:链接链接所有的内存,然后按需查询分配
b.位图:内存大小全部固定,按需分配
c.内存池:结合两者优势


注:以上内容都是学习<程序员的自我修养-链接,装载和库>


  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux驱动编程与编译的实验原理涉及以下几个方面: 1. 内核模块编程:Linux驱动程序以内核模块的形式存在,内核模块是一段可以被动态加载和卸载的代码。驱动程序需要使用适当的内核API函数来与硬件设备进行通信,以实现设备的控制和数据传输等功能。 2. 内核编译系统:Linux操作系统由内核和各种用户空间工具组成。内核编译系统负责构建和编译内核源代码,生成可加载模块和内核镜像。内核编译系统通常使用Makefile和一些配置文件来管理构建过程。 3. Makefile:Makefile是一个包含了构建规则和命令的文本文件,用于自动化构建过程。在驱动程序的开发中,Makefile用于指定编译器、链接器、源文件和目标文件等信息,并定义了编译链接的规则。 4. 编译驱动程序:使用Makefile和相应的内核编译系统,可以将驱动程序源代码编译为可加载模块。编译过程会将源代码翻译成机器代码,并生成与内核版本匹配的模块文件。 5. 加载和卸载驱动程序:通过insmod命令可以将驱动程序加载到内核中,使其成为内核的一部分。加载驱动程序后,它可以与硬件设备进行交互。卸载驱动程序使用rmmod命令,将其从内核中移除。 6. 调试和日志:在驱动程序的开发和调试过程中,通常会使用调试工具和日志来定位问题和记录运行信息。Linux内核提供了printk函数来打印日志信息,可以通过dmesg命令查看系统日志。 以上是Linux驱动编程与编译的实验原理的基本概述。要深入学习和理解这些原理,需要进一步研究Linux内核开发和驱动程序开发的相关文档和资料。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值