作者cole945 (躂躂..)
看板LinuxDev
标题Re: [问题] 连结&载入器,分段分页,Binding关系
时间Sat Jul 11 23:13:31 2015
※ 引述《mshockwave (夏克维夫)》之铭言:
: ※ 引述《gigigigi (gigigigi)》之铭言:
: : 何谓Binding
: : Def: 决定程式执行的起始位址。
: : 即:程式要在内存的哪个地方开始执行。
好奇问一下, 这个定义是哪来的?
就如 mshockwave 所说, 一般讲 binding 是指 name binding.
而 binding time 就是指什麽时候决定 "name" 与他 "所指之事"
算是较 high-level 的事情, 偏软体的问题, 虽然是跟位址有关,
但其实位址本身不是重点, 和 OS 也比较没有直接关系,
这里的定义不太正确, 所以後面的讨论好像整个歪掉 orz
: : 可能的Binding时期有三个:
: : 1. Compiling Time
: : 2. Loading Time
: : 3. Execution Time
: : 3-1 : Dynamic Binding
: : 3-2 : Dynamic Loading
: : 链接器( Linker )是把不同部分的代码和数据,收集、组合成为一个可加载、可执行的文
: : 件。
: : 加载器( Loader )把可执行文件从外存装入内存并进行执行
补充一下, 平常讲 loader, 一般应该是指 dynamic linker,
在 load time 时 resolve symbol 与 address 的东西.
相对於 static 的 linker, 是 link-time 时 resovle symble
你这边讲的 loader 是指把 executable 从 file system 放到 memory 的东西,
并不会做 symbol resolving, 功能比较单纯. 一般 loader 不是指这个.
loader (dynamic linker) 在 GNU/linux 是 ld.so 或 ld-linux.so
linker 像是 GNU binutils 里的 ld 或 gold (gold linker)
你提到的 loader 会是想 linux kernel 的 binfmt_elf 之类的东西,
http://lxr.free-electrons.com/source/fs/binfmt_elf.c
还有像 binfmt_script 是 linux 用来 load shell script 执行的东西
: : MMU : 分段 + 分页
: : 分段 - 逻辑位址 -> 线性位址
: : 分页 - 线性位址 -> 实体位址
: : _________________________________________________________________________________
: : 我被上面情况给搞的有点乱 , 有下面几点疑惑
: : 1.
: : Binging 三个时期程式位址都算是虚拟位址?
: 是的 除非你玩的是没MMU的处理器
: : Compiling Time 位址是由编译器计算出来?
: 不算是 其实是由连结器那边设定的
: : Loading Time 是由 加载器 计算出位址?
: : Execution Time : 位址是 Local Address + Base Register ?
承前面所说, binding 并不是在指位址上的问题
拿这一小段 C code 来说
static int foo (int a, int b) {
return a + b;
}
int bar () {
return foo (1, 2);
}
int qux () {
return bar ();
}
当讲 xxx-time 做 binding, 也就是说在 xxx-time 後, 这个 binding 就不能再改变
但是可以通过重新 xxx 改变 binding.
"通常" 越早 binding 的话, 效率越好, overhead 越低, optimization 越容易介入
* compile-time
bar call foo, 静态就能决定是上面那个 static int foo
例如这个阶段可以做 inline optimization.
但若如果 foo 的内容改了, 那就只有重新编译一途, 不然 bar 会和 foo 不一致
* link-time,
现在程式通常会分档编译, 如果不在同一个档有定义,
那可能在 link-time 从其他的 .o 或 .a 系结
* load-time
可以想成从程式 load 到 memory, 到他真的能开始跑前的时间.
例如, 程式中有用到 libc.so (standard C library) 的东西 (printf, etc),
那就是 load-time 才会决定要 call 哪个版本.
这时才决定的东西, 可以透过重新执行来改变 binding,
例如 printf 有 bug, 可以更新 libc.so 再重新执行,
或是可以透过 LD_PRELOAD 来影响 ld.so (loader) 要使用哪个 shard object
来的定义.
另外, qux call bar, 与 bar 之间的 binding, 在现在 GNU/Linux toolchain
的情况下, 若是 position indepedent code (PIC) 的 shared object
会是 load-time, 而不是 link-time.
* execution-time, 又指 run-time
是指程式正在 run 到时才能知道决定的, 不同语言的状况很不一样,
例如像 C++ 的 virtual function.
load-time 和 run-time 有时况状很像, 但还是有差异.
很多 script 类的 language 可以直接呼叫 foo function,
foo funtion 根本就不存在 (例如 typo 的 bug), 但 run 到时才会跟你
抱怨找不到 foo. 若是 load-time 做 binding, 就会在一开始执行时就
说无法 resolving foo
而一般 C/C++ 的 PIC code 通常会经过 GOT/PLT 查表来执行,
但 run-time 查 vtable 的状况 (overhead) 类似.
但能 optimize 的策略方法不同, load-time 在程式一开始就决定了不会改变,
若用基本的 JIT 就可以避开这个 overhead. 但 run-time 的 overhead
可能就要再透过 run-time profile 和 inline-cache 等方式
: 小弟不才 可能不完全正确 但其实元PO问的事情没那麽复杂
: 用一句话回答的话就是:把一切交给虚拟位址就对了!
: 基本上会考虑到实体位址的就只有一位:核心
: 包括编译器连结器在内 都是用虚拟位址在思考
: 而我刚刚讲的 编译完的位址 其实是由叫做linker script的东西设定的
: 这些script是ld在编译的链结时期读取的
: (script路径可由 ld --verbose | grep SEARCH_DIR 得知)
: 决定的事情包括最重要也最基本的:执行档的开头要载到哪一个位址(虚拟位址)
: 也多亏了虚拟位址 每一个执行档 档案里写的开始执行位址都可以一样
: 反正实际在记忆体中的位址是由核心分配的嘛
: linker script其实常常用在一些很hack的地方
: 例如linux kernel 会把某些符号在链结时期改成另外一个名字
: Mozilla B2G (Firfox OS)也利用linker script
: 把一些重要的libc符号 映射到他们自己实作的版本 说可以避免concurrency(?
以上好像有点复杂化这个问题XD
: : 2.
: : 目前Linux 是用MMU 段式 + 页式 ?
: 这个问题蛮好玩的 因为x86大力鼓吹段式(segment) 但Linux为了跨平台着想
: 因为很多RISC家族根本没有segment的概念 所以是采用页式(page)
segment 还留着只是因为相容的问题, 现在算是没在用了,
在 8086 16-bit 的年代, 利用 segment 是一个简单有效存取超过
64K 位址的一个方法 --- address = segment + offset
但现在直接都有 32/64-bit 的 (offset) register, 所以 segment 都直接
设成 0 (例如 code/data segment). 其他 segment 也拿去做别的特殊用途,
例如拿 FS (还是GS?) 当做 thread pointer 用来加速存取 thead-local-storage
: : Linux 跟 Binding三个时期有关系嘛?
: : Binding三个时期技术是早期的技术嘛? 目前有机会使用到嘛?
: 其实我不太知道你这边的Binding是什麽意思
: 因为小弟是搞编译器的 第一个就想到Name Binding XDD
自我介绍一下, 小弟我之前是搞 assembler, linker, debugger 和一点点 loader
因为要写能 run linux user space program 的 emulator,
所以对 linux 怎麽 load/mapping executable 那边有花些时间研究,
最近几年也是在搞 compiler XD
: : 3.
: : 链接器( Linker )是把不同部分的代码和数据,收集、组合成为一个可加载、可执行的文
: : 件。
: : 我认知编译出执行文件使用 objdump -d 就可以看到虚拟位址 , 就位址是ld Linker
: : 计算出来的嘛? 如果是它是属於哪个Binding?
: : gcc -g test.c
: : 使用 objdump -d ./a.out
: : 08048414 <main>:
: : 8048414: 55 push %ebp
: : 8048415: 89 e5 mov %esp,%ebp
: : 8048417: 6a 03 push $0x3
: : 8048419: 6a 02 push $0x2
: : 804841b: e8 e1 ff ff ff call 8048401 <foo>
: : 8048420: 83 c4 08 add $0x8,%esp
: : 8048423: b8 00 00 00 00 mov $0x0,%eax
: : 8048428: c9 leave
: : 8048429: c3 ret
: : 804842a: 66 90 xchg %ax,%ax
: : 804842c: 66 90 xchg %ax,%ax
: : 804842e: 66 90 xchg %ax,%ax
: : 加载器( Loader )把可执行文件从外存装入内存并进行执行 <-- 这过程有经过虚拟位址
: : 映射实体位址转换嘛?
: 虚拟位址的映射(到实体位址)完全是执行的时候做的事喔
: : Linux 系统的加载器( Loader ) 这是位於 linux kernel 里面?
: 是的 加载执行档一定是作业系统的事
: ld.so的角色呢(不是编译时期的ld)?他是负责解析动态函式库(.so)的相关事情
: 例如帮忙resolve现在执行需要的so并加以载入
: 那那个so载入的位址呢?前面讲过 每个执行档编译出来 开始的虚拟位址可以一样
: 但so的虚拟位址并不是写死的
: 其中的技术就是PIC(Position independent code) 也就是编译so时下的 -fPIC
: 就如字面上讲的 他并不是绝对位址 而是相对位址
: 因此ld.so就可以把他载到执行位址空间的任何一个地方
: 详细的技术比较复杂一点 这边写不下 推荐原PO去读 程式设计师的自我修养
: 那本书真的很珍贵 因为我竟然发现 这麽重要的技术 竟然很少原文书
那本书的内容很针对特定环境的实作, 建议还是要多 trace/survey
现在不同平台的实作, 才不会被局限住..
另外推荐两本书, 都是 Morgan Kaufmann 出版
想往 linker/loader 看的话, 有一本 Linkers and Loaders
作者看已的网页有 draft 可以抓
要往 language 看的话, 可以看
Programming Language Pragmatics
: 上述回答可能有误 请各位大大多多指教了<(_ _)>
: : 谢谢
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 123.110.214.155
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/LinuxDev/M.1436627615.A.07B.html
1F:推 cobrasgo: 搞compiler的,我要跪着看文章了… 07/16 21:59