装载,链接和库

相关command:

1)readelf -s *.o :产看.o文件符号表
2)ar -t libc.a :查看库中所包含的目标文件
3) ar -x libc.a : 解压库
4) gcc -static --verbose -fnobuiltin *.c : -verbose 将编译连接过程所有的过程打印出来,
5) ar -t xx.a 查看库文件信息

2014/11/27 19:17

chapter 1

南桥(ISA)、北桥(PCI)
多道系统->分时系统->多任务系统    

2014-11-29 22:23
单线程-》多线程-》
多线程共享数据问题-》
解决方案:
简单:使用原子操作(有++i问题引入)
复杂:
1:互斥、临界区、信号量
2:保证函数可重入
-》cpu 及编译器优化带来新问题:不能保证多线程安全
编译优化:disassemble 将变量放入register --》加volatile 关键字解决
cpu优化:不相关语句换序–》barrier指令,建立指令拦水坝

多线程内部模型

一对一

chapter 2 静态链接

process

(preprocess)->.i–>(compile)–>.s–>(assemble)->.o–>(linker)->.out

词法分析

lex
(lex)->token

grammar parsing

contex-free grammar
(yacc)->语法树

语义分析(semantic analyzer)

static semantic --compiler
dynamic semantic–running

code optimization

(编译前端)->中间代码–>(编译后端)->目标机器代码

链接器

链接过程主要包括:

  • 重定位: 编译得到的目标文件并不为外部引用分配地址,仅仅标记一个重定位入口,待链接时,通过重定位分配地址
  • 地址和空间分配、
  • 符号决议(symbol resolution)
    编译过程会为交互符号提供重定位入口(relocation entry)
  1. chapter 3:目标文件
    2014-12-14 20:39
    对应于.txt段和.data段,分别有相应的重定位表:rel.txt, rel.data

目标文件的格式:

Note:.bss段只是为ZI data预留空间而已,不占用文件空间(Rom size)

分析elf文件格式:
objdump -d test.o

readelf -h test.o

readelf -S test.o

查看symbol:
nm test

3.3.3 .bss
.bss: 未初始化的全局变量和静态局部变量

2014-12-20 21:00
4. chapter 4 静态链接
4.1
* 链接过程:
空间和地址的分配:读取各.o文件的段信息,合并建立映射关系
符号解析和重定位:使用段信息、重定位信息,调整代码中得地址。

 *连接器默认的程序入口地址为:_start
 *

4.3 common 块
note:由于连接器不能识别符号类型,无法区分符号的大小,进而不能分辨出不同的弱符号,因此,引入common块机制,选取占用common块最大的弱符号作为最后的选择。

*未初始化的全局变量当做弱符号进行处理,在编译阶段通过common块进行处理(不在.bss分配空间),在链接过程后,比较各个弱符号common快的大小,将占用common块最大的全局变量放入bss分配空间,

4.4 C++

*模板问题(模板膨胀):通过将模板单独放入一个段中,相同的模板最终链接到同一个段中。

*函数级别链接:将未使用的函数从最终生成的文件中剔除。

**构造和析构
elf文件提供了两个独立的段执行main函数前的初始化工作(全局对象构造函数在main函数进行调用,析构函数在main函数之后被执行)
.init 段
.fint 段

*链接不同编译器的目标文件,保证ABI相同(application binary interface):
1.采用同样的目标文件格式;
2.同样的符号修饰标注;
3.变量的内存分配方式相同;
4.函数的调用方式相同。

*c++的结构复杂造成其生成的二进制代码在不同的编译器下会产生较c更大的差异,移植起来更加复杂和艰难。

4.5静态链接
编译连接过程: cc1–>as–>collect
*静态链接库中只包含一个函数,作用??

链接静态库的过程是以目标文件为单位,因此每个目标文件中只包含一个函数,以期达到减少链接冗余的目的

2015-4-25 21:08
chapter 06 程序装载

程序的动态装载方法:
1.直接覆盖法

  1. 页映射

可执行文件加载的过程:

程序装载过程:
CPU从入口地址开始执行memory中指令,当发现内存中没有相应的指令页时–>page error–>操作系统从进程获得控制权–>从对应的虚拟地址反向映射(查相应的操作系统数据结构索引)寻找可执行文件的指令地址–>装载相应指令至物理内存页中–>操作系统将控制权交还给相应进程

image和VMA 的映射关系:

映射过程:

  • 相同属性的不同section,被映射到同一个segment,以期减少空间浪费, 因为最小的映射单位为一个page
  • image的VMA地址在链接完成时即已确定.

页管理:
Linux采用两级页表结构—— 页目录表和页表实现地址 映射. 当前正在运行进程的页 目录表的地址被保存在控制寄
存器 CR3 中。 由上面转换机制所得到的线性地址可以分为3 部分 , 高 10位是 DI R域—— 页 目录表的索引值 . 它与 CR3 中的地址一起 计算得到页表的物理地址 . 中间1O位保 存相对于页表的索引 值 . 通过它得到所需的物理页号。 物理页号与低1 2 位页内偏移 组合得到物理地址 。 其结构如图2 所示 。

每个用户进程都可以有4 GB的虚存空 间. 为了更好地管 理这部分虚存空间.Linux主要定义了如下三个数据结构 :
struct vm_area_struct ,
struct vm_operations_struct

struct vmm_struct

   虚存段( vm_area_struct ) . 简称 vma是某个进程的一段 连续的虚存空间. 一个进程通常占用几个vma段 . 例如代码段 、 数据段、 堆栈段等 。 vma不仅可以代表一段内存区间, 也可 以对应于一个文件、 共享内存或者对换设备。
   每一个进程的所有vma由一个双向链表管理。 为了提高对vma的查询、 插入、 删除等操作的效率 .Linux把系统中所 有进程的 vma组成了一棵 AVL树。 这是一棵平衡二叉树 . 当 vma数量特别 大时。 利用这棵 AVL树查找 v ma的效率得到 明显提 高 。
    不同的 vma可能需要不同的操作处理方式 . 但同时考虑到统接口的统 一 性 . Linux采 用vm_operations_struct结 构和面向对象的思想来定义操作方式 . 一个vm_operations_struct结构体是一组 函数指针 , 对于不同的 vma . 它可能指向 不同的处理 函数.例如当发生缺页错误时 . 共 享内存和代码 段 的 readpage所 指 向的页面读入函数可能就不同 。
    内存管理中另外 一个 非 常重要的数 据 结 构是vmm_struct 结构体 .进程 的 task_struct中的mm成员指向 它. 当前运行进程的整个虚拟空间都 由它来管理和描述 . 它不仅包含该进程的映像信 息. 而且它的 mma p成员项指向该进 程所有vma组成的链 表 。 它的 mmap_avl 成 员 项 指 向整个系统 的 AVL树 。
    这三个数据结构之间相互关联. 共同管理虚拟内存 . 它们之 间的 关 系如图 3所 示 。

do_mmap(
struct file *file,
unsigned long addr
unsigned long len ,
unsigned long prot ,
unsigned long flags ,
unsigned long off
);
find_vma (
struct mm_struct mm ,
unsigned long addr
);

  do _mmap函数实现了 内存映射 。 find_vma函数的功能 是找到包含参数 addr指定的虚拟地址所属的 vma 。  当要运行一个可执行映像时 . 调用  do _mmap将其装入 到该进程 的虚拟地址空间 . 并且产生一组 vma结构 . 如前所 述该进程的整个虚拟空间由  vmm_struct  结构管理 . 但是此时 可执行文件仅仅被连接到进程的虚拟空间中. 只有一小部分 页面被装入到物理 内存 . 其余大部分并没有被真正装入到物 理内存 . 在进程的运行过程 中. 产生缺页错误 . 操作 系统首先调用 find_vma. 找到该虚拟地 址所在的 vma . 然后根据该 vma的成员变量 vm_ops指向的vm_operations_struct结构 中的缺页操作 函数。 把页装入物理内存。

Linux使用分页管理机制来更加有效地利用物理内存.当创建一个进程时.仅仅把当前进程的一小部分真正装入内

存.其余部分需要访问时.处理器产生一个页故障.由缺页中断服务程序根据缺页虚拟地址和出错码调用写拷贝函数do—wp—page、此地址所属的vma的vm—ops指向的nopage、do—swap—page.swap—in等函数将需要的页换入物理内存。随着可执行映像的运行和页面的换入.系统中的内存有可能变得不足.这时Linux核心就必须调用kswapd守护进程释放部分物理内存。kswapd在系统启动时由init进程建立。在系统的运行过程中。它被定期唤醒。检查系统中的空闲物理内存是否很少。如果是.则释放一部分内存.或者将一些页面换出到对换空间。然后继续睡眠。

kswapd 守护进程负责确保内存保持可用空闲空间。它监测内核中的 pages_high 和pages_low 标记,如果空闲内存空间值小于 pages_low 值, kswwapd 进程开始扫描并尝试每次回收 32 个页面,如此重复直至空闲内存空间大于 pages_high 值。
kswapd 进程履行以下操作:
* 假如页面未改变,它将该页面放入 free list。
* 假如页面发生改变且被文件系统回写,它将页面内容写入磁盘。
* 假如页面发生改变且未被文件系统回写(无名页) ,它将页面内容写入 swap 设备。

缺页中断和页面换入

  页面换入主要由缺页中断服务入口函数do—page—fault来实现。当系统中产生页面故障时.如果虚拟内存地址有效.则产生错误的原因有如下两种:

虚拟内存地址对应的物理页不在内存中。那么它必然在磁盘或对换空间中.如果在磁盘上.那么我们调用do—nO—
page函数.而do—no—page调用vma一>vm—ops一>nopage()函数建立页面映射.从对换空间或磁盘中调入页面.或者通过do—swap—page()函数调用swap—in()来换入页面。

该虚拟地址对应的物理页在内存。但是被写保护.如果这种情况发生在一个共享页面上.则需要“写拷贝“函数do—
wp—page来换入页面.do—wp—page函数首先调用一get—free—page获得一新页面.然后调用copy—COW—page拷贝页面的内容.当然还要调用相应的刷新函数刷新TLB和缓存等。

页交换进程和页面换出
正如我们上面所描述的.系统使用kswapd守护进程来定期地换出页面。使系统中有足够的空闲物理内存页。

kswapd进程定期地检查系统中的空闲页面数.如果少于一定值.则按照以下三中途径获得空闲页面:
①减少缓冲区和页面高速缓存的大小;
②把共享内存占用的页面置换到对换空间;
③换出或丢弃物理内存页。

SWAP 空间

32位Linux系统的每个进程可以有4 GB的虚拟 内存空间 . 而且系统中还要同时存在多个进程 ,但是 ,事实上大多数计算机都没有这么多物理内存空间 , 当系统中的物理内存紧缺时 . 就需要利用对换空间把一部分未来可能不用的页面从物理内存中移 到对换设备或对换文件中。
Linux采用两种方式保存换出的页面 :
一种是利用整个块设备 , 如硬盘的一个分区 . 即对换设备,
另一种是利用文件系统中固定长度的文件 . 即对换文件。 它们统称为对换空间。
这两种方式的相同之处是它们的内部格式一致. 但是在执行效率方面 . 对换设备要好一些. 这是因为对换设备上同一页面 的数据块是连续存放的 . 故而可以顺序存取 , 而在对换文件中 。 同一页面的数据块实际的物理位置可能是不连续的 . 需要通过对换文件的 inode检索. 这就降低了存取效率 。
每个对换文件或对换设备 由 struct swap — info — struct 结构来描述。 有关对换设备的 函数主要是 get — swap — page ( …) . 当内存中的页面需要被换出时 . 调用 get — swap —page函数 申请得到一个对换空间中的物理页面 。 如果成功, 就返 回一个非零代 码 . 否则返 回0。

2015-4-26 16:34
chapter 07 动态链接

  1. 动态链接在运行时动态链接目标文件或库(keil 在运行前??)
  2. 软件的增量更新如何完成。??

动态链接的思想:
在运行时不进行链接,减小image文件size, 在运行过程的装载环节进行动态链接。
优点:
1. 减少物理页面的换入换出;
2. 增加CPU缓存的命中率;
3. 程序升级带来便利(只需替换需更新的目标文件即可);

7.1
windows : dll
linux: dso(dynamic shared object)
静态链接:ld
动态链接:ld-version.so

7.2
动态链接库在编译过程的地址是不固定的,在装载时进行分配

7.3
动态链接重定位:装载时重定位
PIC (地址无关代码)-fpic
指令中需要修改的部分单独提取出来,放在数据段,这样就可以使这部分指令在各个进程中保持不变,而数据部分可以在每个进程中拥有一个副本。
模块间数据访问:
GOT(global offset table):存储外部变量的地址,在装载时重新加载填充,处于.data 段

chapter 08 共享库
linux 共享库:
libname.so.x.y.z(lib+name.so.主版本号.次版本号.发布版本号)

 linux下通过以下function,在运行时加载和链接共享库:     
 void *dlopen(const char *filename, int flag)

(深入理解计算机系统)
linux共享库的动态加载过程:
编译器在.data端创建一个表,全局偏移量表(GOT),在加载时,动态连接器会重定位GOT中的每个条目。
PIC(position indepedent code)数据引用:
call L1
L1:popl %ebx //ebx contains the current PC
add1 $VAROFF, %ebx //ebx points to the GOT entry for the var
movl (%ebx),%ebx //reference indirect through the GOT
movl (%ebx),%ebx

 PIC 函数调用
 call L1

L1:popl %ebx //ebx contains the current PC
add1 $PROCOFF, %ebx //ebx points to the GOT entry for the proc
call *(%ebx)
conclusion: 数据引用需要五条指令,函数调用需要额外三条指令,没次调用或引用均如此。

引入lazy bonding 机制,通过PLT(procedure linkage table)与GOT进行交互,以下例子说明:
GOT (.DATA)
地址 条目 内容 描述
08049674 GOT[0] 0804969C .dynamic 节的地址
08049678 GOT[1] 4000A9F8 链接器的标识信息
0804967C GOT[2] 4000596F 动态链接器的入口点
08049680 GOT[3] 0804969C PLT[1]push1地址(printf)
08049684 GOT[4] 0804946A PLT[2]push1地址(addvec)

PLT(.Text)
PLT[0]
08048444: FF 35 78 96 04 08 pushl 0x8049678 //push &GOT[1]
0804844A: FF 25 7C 96 04 08 JMP *0X804967C //JMP to *GOT[2] (linker)
08048450: 00 00 //padding
08048452: 00 00 //padding

PLT[1]
08048454: FF 25 80 96 04 08 JMP *0X8049680 //jmp to *GOT[3]
0804844A: 68 00 00 00 00 PUSHL $0X0 //ID for printf
0804845f: e9 e0 ff ff ff jmp 8048444 //jmp to PLT[0]

PLT[2]
08048464: FF 25 84 96 04 08 jmp *0x8049684 //jmp to *GOT[4]
0804846A: 68 08 00 00 00 pushl $0x8 //ID for addvec
0804846F: e9 d0 ff ff ff jmp 8048444 //jmp to PLT[0]

when call 8048464
first jmp to PLT[2] 第一条指令,
跳转至GOT 表中查找到0804946A 的地址,即PLT中第二条地址,
入栈addvec Id,
跳转PLT[0]第一条指令,入栈连接器标识信息,
跳转至动态连接器入口点,动态连接器通过栈中两条信息定位addvec地址,将其更新至GOT[4]中,往后,进行调用时,即直接进行一次查GOT表及函数地址跳转操作。

chapter 10 library and run-time library
linux 程序的内存布局

linux 进程管理:
int brk(void *end_data_segment):设置进程数据段的结束地址,(数据段的结束地址向高地址移动,堆大小增大???)
mmap():

  • malloc
    <128KB: 现有堆空间,分配一块空间

    128KB: 调用mmap,获得一块匿名空间,在匿名空间中为用户分配
    mmap(void *start, //
    size_t length,
    int port,
    int flags,
    int fd,
    off_t offset)

  • Linux内存管理的基本思想之一,是只有在真正访问一个地址的时候才建立这个地址的物理映射。

windows 内存地址空间:

堆分配算法

chapter 11 运行库

  • bounded 指针
    2003年推出历史舞台,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值