写在前面
本文记录自己的学习生涯,学一点记一点,做好准备随时能够提桶。
注意: 该文内容为知识点的总结:参考《程序员的自我修养–链接、装载与库》一书,感谢作者,如有侵权,联系我删除,非常谢谢。
一、内存管理
1、分段
程序所需要的内存空间大小的虚拟空间映射到某个物理地址空间。
问题:无法高效的使用整个内存,容易造成内存的浪费(为程序分配物理内存,程序并未完全使用物理内存)。
2、分页
分页:1、为了解决分段所带来的问题,即内存的高效使用;1、保护作用,可以单独设置每个页的属性、权限。
将内存分成一个个固定大小的页,如1K or 4K(由硬件决定),在此基础上,可以将程序的内存进一步细分。将程序使用的部分内存分配到物理内存,对于暂未使用的部分内存先不分配实际物理内存,后续使用到后再分配实际的物理地址。
进程当前正在使用的VP0、VP1分配到物理地址PP2、PP0,另一部分VP3、VP2分配到磁盘上,还有暂未使用的VP4、VP6、VP7不进行分配。
现在虚拟地址到物理地址的转换有专门的硬件完成(MMU):
二、线程管理
三、静态库
1、编译
a.c文件用于下列结果步骤演示
为了简化显示内容,文件尽可能的进行了精简。
#define PI (111)
int main()
{
if(PI);
return 0;
}
1.1、预处理
gcc -E a.c -o a.i
pi@NanoPi-NEO2:~/project/test$ cat b.i
# 1 "b.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "b.c"
int main()
{
if((111));
return 0;
}
展开宏,替换头文件,去掉注释,添加行号,保留编译命令–>xx.i
1.2、编译
gcc -S a.i -o a.s
pi@NanoPi-NEO2:~/project/test$ cat b.s
.arch armv8-a
.file "b.c"
.text
.align 2
.global main
.type main, %function
main:
.LFB0:
.cfi_startproc
mov w0, 0
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
.section .note.GNU-stack,"",@progbits
生成汇编文件–>xx.s
1.3、汇编
gcc -c a.s -o a.o
将汇编文件生成机器代码–>xx.o
1.4、链接
gcc a.o -o a
将文件之前的引用(函数变量)链接在一起。
在不同的文件中会生成一张符号表,表中明确指出了文件中的所有符号(函数和变量),方便其他文件引用。
–>xx.out
2、编译器
3、目标文件
.text
代码段
.data
已经初始化的非0数据段(全局变量,局部静态变量,已经分配空间占实际内存)
.bss
未初始化或初始化为0的数据段(未初始化可能默认为0,没有必要在这个阶段分配空间,因此为空不分配空间,只保留符号表)
.rodata
只读数据段,const修饰的或者字符串常量
举个栗子:
#include"stdio.h"
#include"stdint.h"
uint16_t temp_1 = 222;
uint16_t temp_2;
int mian()
{
static uint16_t temp_3 = 111;
static uint16_t temp_4;
temp_1 = temp_3++;
temp_2 = temp_1++;
printf("this is test:%d\r\n",temp_1);
return 0;
}
编译上文.c:
gcc -c main.c
使用objdump查看上述代码编译生成的.o文件的信息。
objdump -x -s -d main.o
输出文件如下:
pi@NanoPi-NEO2:~/project/test$ objdump -x -s -d main.o
main.o: file format elf64-littleaarch64
main.o
architecture: aarch64, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x0000000000000000
private flags = 0:
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000088 0000000000000000 0000000000000000 00000040 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000004 0000000000000000 0000000000000000 000000c8 2**1
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000002 0000000000000000 0000000000000000 000000cc 2**1
ALLOC
3 .rodata 00000012 0000000000000000 0000000000000000 000000d0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002b 0000000000000000 0000000000000000 000000e2 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 0000010d 2**0
CONTENTS, READONLY
6 .eh_frame 00000038 0000000000000000 0000000000000000 00000110 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000002 l O .data 0000000000000002 temp_3.3838
0000000000000000 l O .bss 0000000000000002 temp_4.3839
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g O .data 0000000000000002 temp_1
0000000000000002 O *COM* 0000000000000002 temp_2
0000000000000000 g F .text 0000000000000088 mian
0000000000000000 *UND* 0000000000000000 printf
Contents of section .text:
0000 fd7bbfa9 fd030091 00000090 00000091 .{..............
0010 00004079 01040011 223c0012 01000090 ..@y...."<......
0020 21000091 22000079 01000090 21000091 !..."..y....!...
0030 20000079 00000090 00000091 00004079 ..y..........@y
0040 01040011 223c0012 01000090 21000091 ...."<......!...
0050 22000079 01000090 210040f9 20000079 "..y....!.@. ..y
0060 00000090 00000091 00004079 e103002a ..........@y...*
0070 00000090 00000091 00000094 00008052 ...............R
0080 fd7bc1a8 c0035fd6 .{...._.
Contents of section .data:
0000 de006f00 ..o.
Contents of section .rodata:
0000 74686973 20697320 74657374 3a25640d this is test:%d.
0010 0a00 ..
Contents of section .comment:
0000 00474343 3a202855 62756e74 7520392e .GCC: (Ubuntu 9.
0010 332e302d 31377562 756e7475 317e3230 3.0-17ubuntu1~20
0020 2e303429 20392e33 2e3000 .04) 9.3.0.
Contents of section .eh_frame:
0000 10000000 00000000 017a5200 04781e01 .........zR..x..
0010 1b0c1f00 20000000 18000000 00000000 .... ...........
0020 88000000 00410e10 9d029e01 60dedd0e .....A......`...
0030 00000000 00000000 ........
Disassembly of section .text:
0000000000000000 <mian>:
0: a9bf7bfd stp x29, x30, [sp, #-16]!
4: 910003fd mov x29, sp
8: 90000000 adrp x0, 0 <mian>
8: R_AARCH64_ADR_PREL_PG_HI21 .data+0x2
c: 91000000 add x0, x0, #0x0
c: R_AARCH64_ADD_ABS_LO12_NC .data+0x2
10: 79400000 ldrh w0, [x0]
14: 11000401 add w1, w0, #0x1
18: 12003c22 and w2, w1, #0xffff
1c: 90000001 adrp x1, 0 <mian>
1c: R_AARCH64_ADR_PREL_PG_HI21 .data+0x2
20: 91000021 add x1, x1, #0x0
20: R_AARCH64_ADD_ABS_LO12_NC .data+0x2
24: 79000022 strh w2, [x1]
28: 90000001 adrp x1, 0 <mian>
28: R_AARCH64_ADR_PREL_PG_HI21 temp_1
2c: 91000021 add x1, x1, #0x0
2c: R_AARCH64_ADD_ABS_LO12_NC temp_1
30: 79000020 strh w0, [x1]
34: 90000000 adrp x0, 0 <mian>
34: R_AARCH64_ADR_PREL_PG_HI21 temp_1
38: 91000000 add x0, x0, #0x0
38: R_AARCH64_ADD_ABS_LO12_NC temp_1
3c: 79400000 ldrh w0, [x0]
40: 11000401 add w1, w0, #0x1
44: 12003c22 and w2, w1, #0xffff
48: 90000001 adrp x1, 0 <mian>
48: R_AARCH64_ADR_PREL_PG_HI21 temp_1
4c: 91000021 add x1, x1, #0x0
4c: R_AARCH64_ADD_ABS_LO12_NC temp_1
50: 79000022 strh w2, [x1]
54: 90000001 adrp x1, 2 <mian+0x2>
54: R_AARCH64_ADR_GOT_PAGE temp_2
58: f9400021 ldr x1, [x1]
58: R_AARCH64_LD64_GOT_LO12_NC temp_2
5c: 79000020 strh w0, [x1]
60: 90000000 adrp x0, 0 <mian>
60: R_AARCH64_ADR_PREL_PG_HI21 temp_1
64: 91000000 add x0, x0, #0x0
64: R_AARCH64_ADD_ABS_LO12_NC temp_1
68: 79400000 ldrh w0, [x0]
6c: 2a0003e1 mov w1, w0
70: 90000000 adrp x0, 0 <mian>
70: R_AARCH64_ADR_PREL_PG_HI21 .rodata
74: 91000000 add x0, x0, #0x0
74: R_AARCH64_ADD_ABS_LO12_NC .rodata
78: 94000000 bl 0 <printf>
78: R_AARCH64_CALL26 printf
7c: 52800000 mov w0, #0x0 // #0
80: a8c17bfd ldp x29, x30, [sp], #16
84: d65f03c0 ret
.data段为初始化非0的数据,
Contents of section .data:
0000 de006f00
temp_1(de)和temp_3(6f)。
而temp_2和temp_4并未分配空间,存放于bss。
CONTENTS:表示文件中实际存在
ALLOC:表示文件中不存在
File off:表示偏移地址。
Algn:表示对齐方式,2n表示2^2对齐,注意比如前面data段是0x0000c2地址结束,但是bss段是22,那bss的起始地址不能是c3,只能是c4。
__attribute __
__attribute __ ((section(“dame”))),在函数或者变量前加上这个,表示将函数或者变量放置在name段内。如下,新增一个dame段
#include"stdio.h"
#include"stdint.h"
__attribute__((section(".demo"))) uint8_t tttt;
int mian()
{
printf("this is test\r\n");
return 0;
}
pi@NanoPi-NEO2:~/project/test$ objdump -x -s -d main_1.o
main_1.o: file format elf64-littleaarch64
main_1.o
architecture: aarch64, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x0000000000000000
private flags = 0:
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000020 0000000000000000 0000000000000000 00000040 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 0000000000000000 0000000000000000 00000060 2**0
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 0000000000000000 0000000000000000 00000060 2**0
ALLOC
3 .demo 00000001 0000000000000000 0000000000000000 00000060 2**0
CONTENTS, ALLOC, LOAD, DATA
4 .rodata 0000000e 0000000000000000 0000000000000000 00000068 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .comment 0000002b 0000000000000000 0000000000000000 00000076 2**0
CONTENTS, READONLY
6 .note.GNU-stack 00000000 0000000000000000 0000000000000000 000000a1 2**0
CONTENTS, READONLY
7 .eh_frame 00000038 0000000000000000 0000000000000000 000000a8 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
3.1、符号
查看文件的符号表:
新建main_1.c文件
#include"stdio.h"
#include"stdint.h"
__attribute__((section(".demo"))) uint8_t tttt;
uint8_t temp_1 = 0;
uint8_t temp_2 = 0;
void fun(uint8_t test)
{
printf("this is test:%d\r\n",test);
}
int mian()
{
temp_1 = temp_2++;
fun(temp_1);
return 0;
}
编译并查看文件内容:
pi@NanoPi-NEO2:~/project/test$ readelf -s main_1.o
Symbol table '.symtab' contains 21 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main_1.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 NOTYPE LOCAL DEFAULT 5 $d
7: 0000000000000000 0 NOTYPE LOCAL DEFAULT 4 $d
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000000 0 NOTYPE LOCAL DEFAULT 6 $d
10: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 $x
11: 0000000000000000 0 SECTION LOCAL DEFAULT 8
12: 0000000000000014 0 NOTYPE LOCAL DEFAULT 9 $d
13: 0000000000000000 0 SECTION LOCAL DEFAULT 9
14: 0000000000000000 0 SECTION LOCAL DEFAULT 7
15: 0000000000000000 1 OBJECT GLOBAL DEFAULT 5 tttt
16: 0000000000000000 1 OBJECT GLOBAL DEFAULT 4 temp_1
17: 0000000000000001 1 OBJECT GLOBAL DEFAULT 4 temp_2
18: 0000000000000000 44 FUNC GLOBAL DEFAULT 1 fun
19: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
20: 000000000000002c 80 FUNC GLOBAL DEFAULT 1 mian
上述文件有fun和printf函数,fun函数能找到对应的定义,但是printf无法找到,前面ndx为UND(未定义)。
3.2、兼容C语言 – extern C
extern "C" {
int func(int);
int var;
}
为什么这么做?
C++中编译后会将函数名或者变量名进行重新封装修饰,即:fun编译后会生成符号_ZN3fun3barE。
在C中会生成_fun 或者 fun,具体看编译器的支持。
使用extern C后C++会将括号中的文件安装C语言的编译格式生成符号表。
/*d.cpp*/
#include<stdio.h>
#define PI (111)
extern "C" {void funci(){;}}
static void func(float){;}
void ffunc(int){;}
int main()
{
if(PI);
return 0;
}
编译调试查看:
pi@NanoPi-NEO2:~/project/test$ g++ -c d.cpp
pi@NanoPi-NEO2:~/project/test$ readelf -s d.o
Symbol table '.symtab' contains 14 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS d.cpp
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 $x
6: 0000000000000008 20 FUNC LOCAL DEFAULT 1 _ZL4funcf
7: 0000000000000000 0 SECTION LOCAL DEFAULT 5
8: 0000000000000014 0 NOTYPE LOCAL DEFAULT 6 $d
9: 0000000000000000 0 SECTION LOCAL DEFAULT 6
10: 0000000000000000 0 SECTION LOCAL DEFAULT 4
11: 0000000000000000 8 FUNC GLOBAL DEFAULT 1 func
12: 000000000000001c 20 FUNC GLOBAL DEFAULT 1 _Z5ffunci
13: 0000000000000030 8 FUNC GLOBAL DEFAULT 1 main
func没有使用C++的符号命名方式。fun使用了C++的符号命名。
_Z5ffunci:
_Z:固定字符
5:函数名有5个字符
i:int类型(f:float,v:void。。。)
4、链接 – ld
根据上文,每个文件都会生成一张符号表,如a.o和b.o两个文件,如何将a.o和b.o链接在一起生成可执行文件?
链接方法有两种:
1、按照文件的顺序依次合在一起,这样每个文件都会有重复的.text、.data。。。等等。
2、将所有的相同属性的段合在一起,即text在一起,data在一起。(主流方案)
举例:
#include"stdio.h"
#include"stdint.h"
void fun(uint8_t test)
{
;
}
int test()
{
fun(1);
return 0;
}
#include"stdio.h"
#include"stdint.h"
int main()
{
test();
return 0;
}
gcc -c main.c main_1.c
ld main.o main_1.o -e main -o ab
-e main 表示将 main 函数作为程序入口,ld 链接器默认的程序入口为_start。
-o ab 表示链接输出文件名为 ab,默认为 a.out。
执行后生成的ab文件中所有的相同属性对会对应在一起。
pi@NanoPi-NEO2:~/project/test$ objdump -h main.o
main.o: file format elf64-littleaarch64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000018 0000000000000000 0000000000000000 00000040 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 0000000000000000 0000000000000000 00000058 2**0
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 0000000000000000 0000000000000000 00000058 2**0
ALLOC
pi@NanoPi-NEO2:~/project/test$ objdump -h main_1.o
main_1.o: file format elf64-littleaarch64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000030 0000000000000000 0000000000000000 00000040 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 0000000000000000 0000000000000000 00000070 2**0
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 0000000000000000 0000000000000000 00000070 2**0
ALLOC
pi@NanoPi-NEO2:~/project/test$ objdump -h ab
ab: file format elf64-littleaarch64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 000000c4 0000000000400120 0000000000400120 00000120 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .eh_frame 00000060 00000000004001e8 00000000004001e8 000001e8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .got 00000010 0000000000410fd8 0000000000410fd8 00000fd8 2**3
CONTENTS, ALLOC, LOAD, DATA
3 .got.plt 00000018 0000000000410fe8 0000000000410fe8 00000fe8 2**3
CONTENTS, ALLOC, LOAD, DATA
4 .data 00000004 0000000000411000 0000000000411000 00001000 2**1
CONTENTS, ALLOC, LOAD, DATA
5 .bss 0000000c 0000000000411004 0000000000411004 00001004 2**1
ALLOC
6 .comment 0000002a 0000000000000000 0000000000000000 00001004 2**0
CONTENTS, READONLY
查看生成的文件符号表:
pi@NanoPi-NEO2:~/project/test$ readelf -s ab
Symbol table '.symtab' contains 20 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000004000b0 0 SECTION LOCAL DEFAULT 1
2: 00000000004000f8 0 SECTION LOCAL DEFAULT 2
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
5: 00000000004000b0 0 NOTYPE LOCAL DEFAULT 1 $x
6: 000000000040010c 0 NOTYPE LOCAL DEFAULT 2 $d
7: 0000000000000000 0 FILE LOCAL DEFAULT ABS main_1.c
8: 00000000004000c8 0 NOTYPE LOCAL DEFAULT 1 $x
9: 0000000000400130 0 NOTYPE LOCAL DEFAULT 2 $d
10: 0000000000410fe8 0 NOTYPE GLOBAL DEFAULT 2 _bss_end__
11: 0000000000410fe8 0 NOTYPE GLOBAL DEFAULT 2 __bss_start__
12: 00000000004000c8 20 FUNC GLOBAL DEFAULT 1 fun
13: 0000000000410fe8 0 NOTYPE GLOBAL DEFAULT 2 __bss_end__
14: 00000000004000dc 28 FUNC GLOBAL DEFAULT 1 test
15: 0000000000410fe8 0 NOTYPE GLOBAL DEFAULT 2 __bss_start
16: 00000000004000b0 24 FUNC GLOBAL DEFAULT 1 main
17: 0000000000410fe8 0 NOTYPE GLOBAL DEFAULT 2 __end__
18: 0000000000410fe8 0 NOTYPE GLOBAL DEFAULT 2 _edata
19: 0000000000410fe8 0 NOTYPE GLOBAL DEFAULT 2 _end
每一个函数都对应唯一的地址。对于单个文件的编译中找不到的符号会临时用一个假地址代替,直到链接的时候才会查找真实地址并替换。
比如:没有链接前,main.c找不到test函数,就会设置test函数地址为0,
链接后的文件会填充对应的地址。
四、可执行程序
下列示例都在linux中举例:
1、linux中的进程地址
上文已知,在linux中考虑到实际的使用效率与安全,使用虚拟内存,对进程而言不直接接触物理地址,对应的使4G(32位系统,2^32,所以是4G,当然64位,目前也是采用的4G虚拟内存,实际暂时不会用 62这么大的内存空间,后续就不一定了)的虚拟地址。
系统 | 1G | 0XFFFFFFFF~0XC0000000 |
栈 | ||
数据 | ||
代码 | ||
保留 | 128M | 0X08048000 ~ 0X00000000 |
问题:如果程序需要用的地址大于4G怎么办??
1、对于虚拟空间地址而言,32位就是4G无法改变。
2、如果是对于程序的实际使用空间大小而言,可以调整,比如物理空间是8G,那实际可用的是超过虚拟空间的4G的。这时候怎么处理?
预留一部分虚拟地址作为映射窗口:比如:0X10000000~0X20000000作为窗口,这一段256MB的地址可以映射到超过4G的物理地址上,比如ABCD四段物理地址,可以根据需要分别映射到这一段虚拟地址上。
在linux中可以使用mmap做映射。
2、程序的装载
程序的装载目前主要使用页映射的方式。页大小根据实际的使用平台有一定差异:4096、8192…等等。
如前文所述,程序运行时只需要装载当前运行所需要的页即可,不要全部装载,极大的节省空间。
2.1、程序装载步骤
总结一下:程序子在linux上运行(有MMU的系统中),主要有三个步骤:
- 为进程创建虚拟空间0~4G------建立页目录
- 读取可自行文件,并建立物理地址和虚拟空间的映射关系-------建立物理空间到虚拟空间的映射关系(触发缺页中断),虚拟内存区域(VMA Virtual Memory Area),即linux中进程虚拟空间中的一段,比如虚拟空间中的0X80000000~0X800010000。
- 找到可执行文件的地址,设置寄存器,开始运行程序--------类属于单片机的PC寄存器,把程序的运行地址赋值给PC。
2.2、页错误
在虚拟内存中这是一个很重要的概念,页错误,缺页中断-----程序运行时需要读取的数据所在的物理地址还没有映射到虚拟地址时候,触发的中断建立两者之前的映射关系,进而通过虚拟地址直接读取物理地址的数据。
触发流程:
当CPU的进程创建后,开始执行elf程序了,执行到程序的起始地址:比如从0X80000000(进程的虚拟地址)开始,发现地址是空的(没有和物理地址建立映射关系),此时会触发页错误,进而有专门的程序接手,建立elf文件的物理地址和虚拟地址的映射关系,(以页为单位)。建立关系后,会返回正常的进程执行,此时建立好物理地址和虚拟地址的关系,程序正常执行。
程序运行的时候会不断地触发缺页,映射物理地址和虚拟地址,以供程序的运行(存储地址映射关系的内存,毕竟是有限的,对于暂时不用的映射系统会解除关系,替换成常用的映射关系)。需要注意的一点,这里又涉及到很多的东西了,比如:些文件的映射关系可能会被提前加载到某些缓存中,这样程序运行就不用频繁的触发中断,可以直接在缓存中读取。这有涉及到很多东西了,不展开讲了,我也知道些皮毛。
2.3、可执行文件的分段加载
编译后的可自行文件,会被操作系统装载,会分别对程序的不同段进行装载,假设有三个段(三段在物理空间上是连续的):
bytes | addr | |
---|---|---|
SEG0 | 101 | 0x08000000 |
SEG1 | 10000 | |
SEG2 | 101 |
前文提到过,linux中物理地址和虚拟地址之间经过转换,转换加载的最小单位页,页的大小不一定,不同平台会有差异,假设是4096。
ULINX下会对页进行优化,防止三个段中间浪费太多空间。如下图:
SEG0不够一个页,SEG1一页装不下,因此SEG0和SEG1部分(和SEG0加起来刚好凑够一页的部分)分别映射到两个连续的虚拟地址中,保证空间的最大化利用。即一个物理的page会根据段的情况分别通过MMU映射到两个虚拟地址,操作系统加载的时候直接读取对应的虚拟地址进行加载。(注意 映射后的地址需要字节对齐)比如:SEG0为101bytes,但是对齐为4bytes,那SEG0至少需要占用0X68的空间,即SEG1起始地址:0X08000068。
2.4、程序运行的堆栈
举个栗子:查找当前正在运行的a.out程序,查看对应的内存分配:
上图中,heap和stack就是进程对应的堆和栈。
3、linux加载可执行文件
在bash中输入命令执行某个程序:比如:./a.out or ./a.elf ----- .out和.elf区别
1、bash调用fork创建一个进程----应用层
2、进程调用execve(),持续程序,bash等待用户的下一个命令----内核
3、sys_execve(),参数的检查和复制
4、do_execve(),读取可执行文件的头部128字节,(头部保存有可执行文件的类型,方便系统的进一步解析执行)
5、search_binary_handle(),判断当前的文件是什么类型,调用对应的入口函数:elf文件调用load_elf_binary(),out文件调用load_out_binary()。
6、load_elf_binary(),
- 检查elf的有效性等等
- 查找".interp"段,
- 对elf文件进行映射,代码、数据等
- 初始化进程环境
- 返回地址设置为elf程序的入口
7、返回do_execve()、返回sys_execve(),内核态返回用户态。此时程序的运行地址加载到ELF程序的入口地址,elf程序开始执行
可以在bash中输入参数,(argc 和 argv)传递给main函数。bash会把数据放在堆栈中,执行程序的时候会将数据传递到main。
五、动态链接
为什么要动态链接?
静态链接文章来源:https://www.toymoban.com/news/detail-513622.html
- 优点:不依赖其他库,放进系统就能执行
- 缺点:占用空间大,多个程序可能依赖相同的lib,导致库重复
动态链接文章来源地址https://www.toymoban.com/news/detail-513622.html
- 优点:占用空间小,很多可执行文件,共享动态库
- 缺点:不同的环境中需要依赖外部的库,库更新后如果接口不适配会很麻烦
到了这里,关于【Linux】程序员的基本素养学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!