这是大三下读的第一本书了。这本书在我准备面试之余还能零零碎碎读了一些。目前看了几章来总结一下。
实体书上面做笔记和翻阅还是挺舒服的,这里总结的话就不按照书中章节顺序了,而是按照自己选择阅读的顺序。
第一部分 - 你看程序跑起来了!!!
命令行一敲, 回车一按, 一个程序就运行起来了,这之间都做了哪些操作?
程序.o
中有什么?
程序是如何装入内存的?
装入内存后如何启动程序?
启动程序是从main开始的吗?
程序 .o
中有什么? 程序是如何装入内存的?
目前不考虑静态和动态链接库相关的内容.
static int static_int1 = 1;
static int static_int2;
int main()
{
const char* str = "Hello World!";
return 0;
}
上面的一小段代码, 包含了static 已初始化变量
, static 未初始化变量
, 以及字符串常量str
, 一段main函数
然后使用objdump命令查看这个文件, 这个命令可以解析出ELF文件的头部分。
相关ELF文件的内容这里就不展开说了,ELF文件里面存在头和数据部分,头部分有一张类似下面的表,其中有偏移量等信息指向实际数据部分
[root@fish ~] objdump -h g-main # h表示
g-main: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn # Size段的大小 FileOff段的实际数据在段内的偏移
9 .init 0000001b 0000000000400450 0000000000400450 00000450 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
10 .text 00000175 0000000000400470 0000000000400470 00000470 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
11 .fini 0000000d 00000000004005e8 00000000004005e8 000005e8 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .rodata 0000001d 00000000004005f8 00000000004005f8 000005f8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
20 .data 00000008 0000000000601018 0000000000601018 00001018 2**2
CONTENTS, ALLOC, LOAD, DATA
21 .bss 00000008 0000000000601020 0000000000601020 00001020 2**2
ALLOC # 没有 CONTENTS ?
我删除了很多的内容, 只留下了目前我所学到的主要6段。后面内容基本根据这几段来说明
当在shell按下回车后,fork出的子进程去读取这个可执行文件的头
的头部
这里面包含着这是个什么样的可执行文件, ELF文件就调用ELF装载程序,
sh文件就调用相关的bash来执行.
装载程序执行解析ELF的操作得到上面的表,然后根据表将实际数据装入虚拟内存
当然虚拟内存也不是立刻就全部装载进去,但为了便于解析这里先不考虑分页相关内容
虚拟内存 存在保留段, 是从0x08048000开始的 这样还能够防止 0x0这个地址对指针造成影响
然后从0x08048000
开始装载 .init
段 然后.text
段 再之后.fini
段, 之后是.rodata
接着.data
和.bss
.
这几段分别是什么?
.init
包含着初始化相关代码 这些代码是自动加进来的 用于程序运行的初始化操作
.text
是我们自己编写的代码
.fini
则是终结相关代码
.rodata
我们看到表中的大小是1d
偏移是000005f8
我们使用hexdump来查看下这块内容
[root@fish ~] hexdump -s 0x5f8 -n 0x1d -C g-main # -s 跳过指定字节 -n 显示指定字节 -C 显示对应字符
000005f8 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000608 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 00 |Hello World!.|
00000615
我们看到了Hello World!\0
这段内容说明这正是全局const变量和字符串常量的内容
.data
可以看到这里存储的是static int static_int1 = 1;
这个带有初始值的static变量
[root@fish ~] hexdump -s 0x1018 -n 0x8 -C g-main
00001018 00 00 00 00 01 00 00 00 |........|
00001020
.bss
这段实际上是没有内容的 虽然存在size和off_set 但可以看到他没有CONTENTS
这里存储的是不带初始值的static变量static int static_int2;
方便进行统一的初始化为0, 同时不占用文件大小.
这样上面相关的6段就装载完成了, 除此之外还有堆段和栈段, 最终布局如下
内存空间 |
---|
内核占用3-4GB |
栈段 向低地址增长 |
? |
堆段 向高地址增长 |
.bss |
.data |
.rodata |
.fini |
.text |
.init |
从0x0x08048000开始 |
下面是图片对应
装入内存后如何启动程序?
装载完毕后会设定相关的内容使程序从.init
段开始执行代码
启动程序是从main开始的吗?
到这里应该有个简单的答案了, 不是从这个函数开始.
在.init
段中的初始化代码_start
函数(汇编的形式保存在/usr/lib64/crt1.o
)首先执行, 程序传入的参数是保存在栈中的
_start
函数会调用__libc_start_main
, 实现从栈中读取设定环境变量指针,设定argc和argv. 然后调用``/usr/lib64/crtbegin.o`中的代码进行全局初始化比如static变量的设定等
然后调用main函数
返回后调用.finit
段中的代码进行收尾操作, 其中就调用了``/usr/lib64/crtend.o中的代码进行全局变量的析构之类的收尾工作, 之后调用atexit设定的函数(通过链表链接的多个atexit函数退出时执行), 再之后将main的返回值传给
exit`程序退出
malloc相关实现
简单来说malloc会在分配小于128KB的空间时在堆段通过brk
函数上移esp寄存器
中的地址实现堆段的扩大
如果大于128KB则会在上面彩图中的Memory Mapping Segment段中
的anonymous mappings
开辟空间, 通过的是mmap
函数, mmap
函数将分配到的虚拟内存不
映射到具体的文件,这样称之为匿名空间,匿名空间则可以作为堆空间使用