【Linux】静态库 动态库
🌻个人主页:路飞雪吖~
🌠专栏:Linux
目录
一、👑静态库和动态库
静态库:
动态库:
🌠手动制作静态库 && 手动调用一下我们自己写的静态库
1> 安装到系统里面
✨生成静态库
2> 和源文件一起
3> 使用带路径的库
🌠手动制作动态库 && 手动调用一下我们自己写的动态库
✨生成动态库
编辑
如何给系统指定路径,查找自己的动态库?(4种方法)
🌠动静态库都有程序如何选择?
🌠动态库的理解 && 动态库加载 && 进程地址空间
二、👑可执行程序加载 && 地址空间
1.可执行程序格式
2. 地址空间 、可执行程序、加载
3. 动态库加载的简单理解
一、👑静态库和动态库
什么是库? 库是写好的现有的,成熟的,可以复⽤的代码。现实中每个程序都要依赖很多基础的底层库,不可能 每个⼈的代码都从零开始,因此库的存在意义⾮同寻常。
静态库
概念:
- 在程序编译时,链接器会从静态链接库获取所有被引用的函数,并将库同代码一起放到可执行文件中。
- 工作原理:在编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
优点:
- 可执行文件不依赖外部库文件,便于移植和分发,在不同环境中运行时不会因缺少库文件而导致错误。
- 代码装载速度快,执行速度略比动态链接库快。
缺点:
- 如果多个程序都使用了同一个静态库,那么每个程序都会包含一份该静态库的代码,这会导致可执行文件体积较大,浪费磁盘空间。
- 当静态库中的代码需要更新时,必须重新编译所有使用该静态库的程序。
动态库
概念:
- 动态库是在程序运行时才被加载和链接的库文件,其文件扩展名为.so(在 Unix/Linux 系统中)或.dll(在 Windows 系统中)。
- 工作原理:在编译时,程序只是记录了对动态库中函数的引用信息,而不会将动态库的代码复制到可执行文件中。当程序运行时,操作系统会根据这些引用信息加载相应的动态库,并将程序中的函数调用与动态库中的实际函数进行链接,从而实现函数的调用。
优点:
- 多个程序可以共享同一个动态库,从而大大节省了磁盘空间。
- 当动态库中的代码需要更新时,只要更新动态库文件即可,不需要重新编译使用该动态库的程序,方便了代码的维护和升级。
- 动态链接节省内存,减少交换操作,节省磁盘空间。
缺点:
- 由于动态库是在程序运行时加载的,所以程序的运行依赖于动态库的存在。如果动态库文件丢失或版本不匹配,程序将无法正常运行。
- 速度比静态链接慢。当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,可能会出现问题。
🌠手动制作静态库 && 手动调用一下我们自己写的静态库
1> 安装到系统里面
• 库文件名称 和 引入库的名称,如:libc.so --> c库, 去掉前缀 lib,去掉后缀 .so,.a
• ar -rc libmystdio.a my_stdio.o my_string.o // 创建静态库libmystdio.a
• sudo cp *.h /usr/include/ // 将所有.h的头文件拷贝到 /usr/include 下
• sudo cp libmystdio.a /lib64/ // 把自己创建的静态库拷贝到 /lib64 目录下
• gcc main.c -o main -lmystdio // -l 引入指定的第三方库名称【lmystdo-->去头去尾】 【安装到系统上】
库里面不能有main函数,在发布的时候所有的.h头文件放到include目录下,库放到lib目录下。
✨生成静态库
// my_stdio.c#include "my_stdio.h" #include <string.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdlib.h> #include<unistd.h>mFILE *mfopen(const char *filename, const char *mode) {int fd = -1;if(strcmp(mode, "r") == 0){fd = open(filename, O_RDONLY);}else if(strcmp(mode, "w") == 0){fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);}else if(strcmp(mode, "a") == 0){fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);}if(fd < 0) return NULL;mFILE *mf = (mFILE*)malloc(sizeof(mFILE));if(!mf){close(fd);return NULL;}mf->fileno = fd; // 文件描述符mf->flag = FFLUSH_LINE; // 刷新方式mf->size = 0;mf->cap = SIZE;return mf; }void mfflush(mFILE *stream) {if(stream->size > 0){// 写到内核文件的文件缓冲区中!write(stream->fileno, stream->outbuffer, stream->size);// 刷新到外设fsync(stream->fileno);stream->size = 0;} }int mfwrite(const void *ptr, int num, mFILE *stream) {// 1.拷贝memcpy(stream->outbuffer+stream->size, ptr, num);stream->size += num;// 2.检查是否要刷新if(stream->flag == FFLUSH_LINE && stream->size > 0 && stream->outbuffer[stream->size-1] == '\n'){mfflush(stream);}return num; }void mfclose(mFILE *stream) {// 在关闭文件前,要对缓冲区文件的数据进行刷新if(stream->size > 0){mfflush(stream);}close(stream->fileno);// 关闭底层的文件描述符 }
2> 和源文件一起
小贴士:本地库和系统库同名且方法也一样,系统先找到哪一个就用哪一个。
• gcc main.c -o main -L. -lmystdio // -L 编译的时候查找库,-l 表明库名称
3> 使用带路径的库
-Istdc/include
:指定 include 包含文件的搜索目录为stdc/include
。-Lstdc/lib
:把stdc/lib
加入到搜索库文件的路径列表中。-lmystdio
:进行连接时搜索名为mystdio
的库// Makefilelibmystdio.a:my_stdio.o my_string.o@ar -rc $@ $^@echo "build $^ to $@ ... done" %.o:%.c@gcc -c $<@echo "compling $< to $@ ... done".PHONY:clean clean:@rm -rf *.a *.o stdc.*@echo "clean ... done".PHONY:output output:@mkdir -p stdc/include@mkdir -p stdc/lib@cp -f *.h stdc/include@cp -f *.a stdc/lib@tar -czf stdc.tgz stdc@echo "output stdc ... done"
🌠手动制作动态库 && 手动调用一下我们自己写的动态库
• sudo cp *.h /usr/include/ 把所需要的.h头文件都拷贝到/usr/include目录下
• sudo cp libmystdio.so /lib64 把生成的库拷贝到/lib64目录下
• gcc main.c -lmystdio 编译文件要链接所生成的库
✨生成动态库
🎉动态库不要随便删除!!!!
当可执行程序的动态库找不到:
库能编译并发布,要提供完整的库,库要有自己完整的路径结构,动静态库都要发布。
系统是如何找到动态库的?在 /lib64 目录下找。
如何给系统指定路径,查找自己的动态库?(4种方法)
1> (.so)拷贝到系统默认路径下,比如 /lib64;
2> 在系统路径下,建立软链接;
3> 在Linux系统中,OS查找动态库,会有默认的规定路径,也会存在环境变量LD_LIBRARY_PATH;【将当前所在的库路径添加到环境变量列表当中】
4> 添加配置文件
🌠动静态库默认情况下都不会在当前目录下查找库,都要把库的路径告诉编译器【-L】,还要进行链接【-l】;
🌠动静态库都有程序如何选择?
• 如果同时给程序提供.a静态库和.so动态库,程序会优先使用动态库!!
• 如果要强制使用静态链接,必须提供对应的静态库!
• 如果只提供静态库,但是链接方式是动态链接的,gcc、g++没得选,只能针对你的.a静态库局部性采用静态链接。
🌠动态库的理解 && 动态库加载 && 进程地址空间
动静态也是文件也有自己的inode编号,在执行程序./main,程序加载到内存里面【二进制】,内存 ---页表 --- 进程 形成映射关系,可执行程序知道自己依赖哪些库,动态库也会加载到内存里面,新加载的库 --- 页表 --- 进程 形成映射,映射到进程的虚拟地址空间的共享区,当代码在编译时,需要调库就直接跳转到共享区执行所对应的方法,执行完再返回正文代码中,因此整个进程执行库方法,全都是在自己的地址空间中跳转运行的。在Linux系统里,一个动态库可以被多个可执行程序依赖,当其他的可执行程序要使用这个库时,这个库不用重新加载到内存里了,新的可执行程序通过自己的页表与加载到内存的库进行映射到新的进程虚拟地址空间的共享区,进行使用。在Linux系统里,动态库只需要加载一份到内存上,所需要的动态库的可执行程序都可以共享这个动态库 ----- 共享库。这就是为什么动静态库都有的时候,会优先使用动态库的原因【节省内存空间】。
共享库加载到内存 和 共享库被映射到进程地址空间的共享区 里面的什么位置都可以 --- 与地址无关。
二、👑可执行程序加载 && 地址空间
1.可执行程序格式
一个可执行程序的 堆、栈、命令行参数环境变量都是程序被加载到内存时,被系统动态创建出来的,不需要在可执行程序里进行特定的构成。在编译形成可执行程序的时候,它的可执行程序生成二进制文件是有一定规则的形成的。在Linux系统当中形成的可执行程序,是有特定格式的 --- ELF格式。可执行程序编译好就是一个二进制文件,形成的二进制有自己的格式。
形成的可执行程序、我们自己形成的动/静库、形成的目标文件.o,它们最终编译好之后,形成的格式全都是EIF格式。
所以静态库就是多个.o文件,合并的时候就是把所有静态库的.o和自己的.o所需要的section进行合并,形成大的section,统一叫做代码段和数据段。当code1.o里面有函数的声明,code2.o有函数的定义,当数据进行合并的时候,就可以形成一个大的.txt,最后整个代码的每一个函数的每一个代码函数调用的地址/实现,就在最终的可执行程序里面全都有了。当我们在合并的时候,所以在C语言写多文件的时候,不能出现命名冲突的函数,不然合并的时候也会起冲突。
可执行程序里有多少个section?这些信息存在【ELF Header】。
每个section是只读/只写的?【Program Header Table optional】是程序加载到内存时,在内存里的整体布局。
每个section的大小是多少呢?对于任何一个文件,文件的内容就是一个巨大的 “一维数组” ,标识文件任何一个区域,寻找文件的方式 偏移量 + 文件大小 的方式。
【Section Header Table】详细描述每个节的开始和结束是多少。里面存的实则是一个数组,每个数组都是特定的数据结构。
2. 地址空间 、可执行程序、加载
一个可执行程序在进行加载的时候,前提条件是操作系统是要认识你的可执行程序的。
进程地址空间是由谁来初始化的?
可执行程序有没有地址的存在呢?有地址【反汇编】,有具体的数据节。
// main.c
#include "my_stdio.h"
#include "my_string.h"
#include <stdio.h>int main()
{const char *s = "abcdefg";printf("%s: %d\n", s, my_strlen(s));mFILE *fp = mfopen("./log.txt", "a");if(fp == NULL) return 1;mfwrite(s, my_strlen(s), fp);mfwrite(s, my_strlen(s), fp);mfwrite(s, my_strlen(s), fp);mfclose(fp);return 0;
}
// 反汇编
zxl@Luffy:~/study/study0419/other$ objdump -S main > main.s
zxl@Luffy:~/study/study0419/other$ vim main.smain: file format elf64-x86-64Disassembly of section .init:0000000000001000 <_init>:1000: f3 0f 1e fa endbr641004: 48 83 ec 08 sub $0x8,%rsp1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__@Base>100f: 48 85 c0 test %rax,%rax1012: 74 02 je 1016 <_init+0x16>1014: ff d0 call *%rax1016: 48 83 c4 08 add $0x8,%rsp101a: c3 retDisassembly of section .plt:0000000000001020 <.plt>:1020: ff 35 7a 2f 00 00 push 0x2f7a(%rip) # 3fa0 <_GLOBAL_OFFSET_TABLE_+0x8>1026: ff 25 7c 2f 00 00 jmp *0x2f7c(%rip) # 3fa8 <_GLOBAL_OFFSET_TABLE_+0x10>102c: 0f 1f 40 00 nopl 0x0(%rax)1030: f3 0f 1e fa endbr641034: 68 00 00 00 00 push $0x01039: e9 e2 ff ff ff jmp 1020 <_init+0x20>103e: 66 90 xchg %ax,%ax1040: f3 0f 1e fa endbr641044: 68 01 00 00 00 push $0x11049: e9 d2 ff ff ff jmp 1020 <_init+0x20>104e: 66 90 xchg %ax,%ax1050: f3 0f 1e fa endbr641054: 68 02 00 00 00 push $0x21059: e9 c2 ff ff ff jmp 1020 <_init+0x20>105e: 66 90 xchg %ax,%ax1060: f3 0f 1e fa endbr641064: 68 03 00 00 00 push $0x31069: e9 b2 ff ff ff jmp 1020 <_init+0x20>106e: 66 90 xchg %ax,%ax1070: f3 0f 1e fa endbr641074: 68 04 00 00 00 push $0x41079: e9 a2 ff ff ff jmp 1020 <_init+0x20>107e: 66 90 xchg %ax,%axDisassembly of section .plt.got:0000000000001080 <__cxa_finalize@plt>:1080: f3 0f 1e fa endbr641084: ff 25 6e 2f 00 00 jmp *0x2f6e(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>108a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)Disassembly of section .plt.sec:0000000000001090 <my_strlen@plt>:1090: f3 0f 1e fa endbr641094: ff 25 16 2f 00 00 jmp *0x2f16(%rip) # 3fb0 <my_strlen@Base>109a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)00000000000010a0 <mfopen@plt>:10a0: f3 0f 1e fa endbr6410a4: ff 25 0e 2f 00 00 jmp *0x2f0e(%rip) # 3fb8 <mfopen@Base>10aa: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)00000000000010b0 <mfwrite@plt>:10b0: f3 0f 1e fa endbr6410b4: ff 25 06 2f 00 00 jmp *0x2f06(%rip) # 3fc0 <mfwrite@Base>10ba: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)00000000000010c0 <printf@plt>:10c0: f3 0f 1e fa endbr6410c4: ff 25 fe 2e 00 00 jmp *0x2efe(%rip) # 3fc8 <printf@GLIBC_2.2.5>10ca: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)00000000000010d0 <mfclose@plt>:10d0: f3 0f 1e fa endbr6410d4: ff 25 f6 2e 00 00 jmp *0x2ef6(%rip) # 3fd0 <mfclose@Base>10da: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)Disassembly of section .text:00000000000010e0 <_start>:10e0: f3 0f 1e fa endbr6410e4: 31 ed xor %ebp,%ebp10e6: 49 89 d1 mov %rdx,%r910e9: 5e pop %rsi10ea: 48 89 e2 mov %rsp,%rdx10ed: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp10f1: 50 push %rax10f2: 54 push %rsp10f3: 45 31 c0 xor %r8d,%r8d10f6: 31 c9 xor %ecx,%ecx10f8: 48 8d 3d ca 00 00 00 lea 0xca(%rip),%rdi # 11c9 <main>10ff: ff 15 d3 2e 00 00 call *0x2ed3(%rip) # 3fd8 <__libc_start_main@GLIBC_2.34>1105: f4 hlt1106: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)110d: 00 00 000000000000001110 <deregister_tm_clones>:1110: 48 8d 3d f9 2e 00 00 lea 0x2ef9(%rip),%rdi # 4010 <__TMC_END__>1117: 48 8d 05 f2 2e 00 00 lea 0x2ef2(%rip),%rax # 4010 <__TMC_END__>111e: 48 39 f8 cmp %rdi,%rax1121: 74 15 je 1138 <deregister_tm_clones+0x28>1123: 48 8b 05 b6 2e 00 00 mov 0x2eb6(%rip),%rax # 3fe0 <_ITM_deregisterTMCloneTable@Base>112a: 48 85 c0 test %rax,%rax112d: 74 09 je 1138 <deregister_tm_clones+0x28>112f: ff e0 jmp *%rax1131: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)1138: c3 ret1139: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)0000000000001140 <register_tm_clones>:1140: 48 8d 3d c9 2e 00 00 lea 0x2ec9(%rip),%rdi # 4010 <__TMC_END__>1147: 48 8d 35 c2 2e 00 00 lea 0x2ec2(%rip),%rsi # 4010 <__TMC_END__>114e: 48 29 fe sub %rdi,%rsi1151: 48 89 f0 mov %rsi,%rax1154: 48 c1 ee 3f shr $0x3f,%rsi1158: 48 c1 f8 03 sar $0x3,%rax115c: 48 01 c6 add %rax,%rsi115f: 48 d1 fe sar $1,%rsi1162: 74 14 je 1178 <register_tm_clones+0x38>1164: 48 8b 05 85 2e 00 00 mov 0x2e85(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable@Base>116b: 48 85 c0 test %rax,%rax116e: 74 08 je 1178 <register_tm_clones+0x38>1170: ff e0 jmp *%rax1172: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)1178: c3 ret1179: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)0000000000001180 <__do_global_dtors_aux>:1180: f3 0f 1e fa endbr641184: 80 3d 85 2e 00 00 00 cmpb $0x0,0x2e85(%rip) # 4010 <__TMC_END__>118b: 75 2b jne 11b8 <__do_global_dtors_aux+0x38>118d: 55 push %rbp118e: 48 83 3d 62 2e 00 00 cmpq $0x0,0x2e62(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>1195: 001196: 48 89 e5 mov %rsp,%rbp1199: 74 0c je 11a7 <__do_global_dtors_aux+0x27>119b: 48 8b 3d 66 2e 00 00 mov 0x2e66(%rip),%rdi # 4008 <__dso_handle>11a2: e8 d9 fe ff ff call 1080 <__cxa_finalize@plt>11a7: e8 64 ff ff ff call 1110 <deregister_tm_clones>11ac: c6 05 5d 2e 00 00 01 movb $0x1,0x2e5d(%rip) # 4010 <__TMC_END__>11b3: 5d pop %rbp11b4: c3 ret11b5: 0f 1f 00 nopl (%rax)11b8: c3 ret11b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)00000000000011c0 <frame_dummy>:11c0: f3 0f 1e fa endbr6411c4: e9 77 ff ff ff jmp 1140 <register_tm_clones>00000000000011c9 <main>:11c9: f3 0f 1e fa endbr6411cd: 55 push %rbp11ce: 48 89 e5 mov %rsp,%rbp11d1: 48 83 ec 10 sub $0x10,%rsp11d5: 48 8d 05 28 0e 00 00 lea 0xe28(%rip),%rax # 2004 <_IO_stdin_used+0x4>11dc: 48 89 45 f0 mov %rax,-0x10(%rbp)11e0: 48 8b 45 f0 mov -0x10(%rbp),%rax11e4: 48 89 c7 mov %rax,%rdi11e7: e8 a4 fe ff ff call 1090 <my_strlen@plt>11ec: 89 c2 mov %eax,%edx11ee: 48 8b 45 f0 mov -0x10(%rbp),%rax11f2: 48 89 c6 mov %rax,%rsi11f5: 48 8d 05 10 0e 00 00 lea 0xe10(%rip),%rax # 200c <_IO_stdin_used+0xc>11fc: 48 89 c7 mov %rax,%rdi11ff: b8 00 00 00 00 mov $0x0,%eax1204: e8 b7 fe ff ff call 10c0 <printf@plt>1209: 48 8d 05 04 0e 00 00 lea 0xe04(%rip),%rax # 2014 <_IO_stdin_used+0x14>1210: 48 89 c6 mov %rax,%rsi1213: 48 8d 05 fc 0d 00 00 lea 0xdfc(%rip),%rax # 2016 <_IO_stdin_used+0x16>121a: 48 89 c7 mov %rax,%rdi121d: e8 7e fe ff ff call 10a0 <mfopen@plt>1222: 48 89 45 f8 mov %rax,-0x8(%rbp)1226: 48 83 7d f8 00 cmpq $0x0,-0x8(%rbp)122b: 75 07 jne 1234 <main+0x6b>122d: b8 01 00 00 00 mov $0x1,%eax1232: eb 71 jmp 12a5 <main+0xdc>1234: 48 8b 45 f0 mov -0x10(%rbp),%rax1238: 48 89 c7 mov %rax,%rdi123b: e8 50 fe ff ff call 1090 <my_strlen@plt>1240: 89 c1 mov %eax,%ecx1242: 48 8b 55 f8 mov -0x8(%rbp),%rdx1246: 48 8b 45 f0 mov -0x10(%rbp),%rax124a: 89 ce mov %ecx,%esi124c: 48 89 c7 mov %rax,%rdi124f: e8 5c fe ff ff call 10b0 <mfwrite@plt>1254: 48 8b 45 f0 mov -0x10(%rbp),%rax1258: 48 89 c7 mov %rax,%rdi125b: e8 30 fe ff ff call 1090 <my_strlen@plt>1260: 89 c1 mov %eax,%ecx1262: 48 8b 55 f8 mov -0x8(%rbp),%rdx1266: 48 8b 45 f0 mov -0x10(%rbp),%rax126a: 89 ce mov %ecx,%esi126c: 48 89 c7 mov %rax,%rdi126f: e8 3c fe ff ff call 10b0 <mfwrite@plt>1274: 48 8b 45 f0 mov -0x10(%rbp),%rax1278: 48 89 c7 mov %rax,%rdi127b: e8 10 fe ff ff call 1090 <my_strlen@plt>1280: 89 c1 mov %eax,%ecx1282: 48 8b 55 f8 mov -0x8(%rbp),%rdx1286: 48 8b 45 f0 mov -0x10(%rbp),%rax128a: 89 ce mov %ecx,%esi128c: 48 89 c7 mov %rax,%rdi128f: e8 1c fe ff ff call 10b0 <mfwrite@plt>1294: 48 8b 45 f8 mov -0x8(%rbp),%rax1298: 48 89 c7 mov %rax,%rdi129b: e8 30 fe ff ff call 10d0 <mfclose@plt>12a0: b8 00 00 00 00 mov $0x0,%eax12a5: c9 leave12a6: c3 retDisassembly of section .fini:00000000000012a8 <_fini>:12a8: f3 0f 1e fa endbr6412ac: 48 83 ec 08 sub $0x8,%rsp12b0: 48 83 c4 08 add $0x8,%rsp12b4: c3
在逻辑上只要找到第一个地址,整个程序从头往后就可以运行了。只要找到第一个地址,执行完了之后,第一个地址加上第一个的地址长度就是下一个指令的起始地址。ELF在没有加载到内存的时候,就已经安装[00...00, ff...ff]全部进行编址了 ---- 虚拟地址。
虚拟地址不仅仅是在操作系统内存形成进程时形成的,虚拟地址是在编译器编译时,就已经形成了。----- 编译器编译时,就已经形成虚拟地址了。
• 逻辑地址(磁盘ELF) == 虚拟地址(加载到内存中说的)
🌠进程在新建时,加载可执行程序时,就要初始化PCB里面的【mm_struct虚拟地址】地址空间,[未初始化、初始化、正文代码]各种数据区,里面的字段都是从读取的可执行程序中,加载可执行程序的各个数据节【section】所对应的具体地址得来的,所以就形成了mm_struct。
🌠只要按绝对编址,在函数调用所需地址, 程序内部用到的地址,可以直接使用虚拟地址来进行跳转。
我们自己的程序内部,使用的就是虚拟地址,所以虚拟地址在磁盘上就有了。
当可执行程序加载到内存,
1、根据程序的ELF加载到内存里面,
2、初始化mm_struct鹅数据结构[未初始化数据、初始化数据、正文代码],
3、整个可执行程序最开始的入口地址,直接放入到CPU里面的PC指针里就可以直接进行执行了。
执行程序的时候,使用的是什么地址?虚拟地址!
程序加载到内存的时候,虚拟地址也被加载到物理内存里面,PC指针指向的也是虚拟地址,可执行程序加载到物理内存的时候,每条指令都要占据内存的物理地址,所以可执行程序加载到内存后既有内部函数调用所需要的虚拟地址,在物理内存上也有物理地址,虚拟地址被加载到CPU内部。
既有虚拟地址也有物理地址,此时就可以完善页表,完善物理地址和虚拟地址的映射关系,同时在CPU内部同时存在一个CR3寄存器【CPU自己使用的寄存器,不会暴露给外部】,它会保存页表的物理起始地址,CPU内部还存在MMU【硬件】,内存管理单元【MMU】,MMU【硬件】+ CR3【寄存器】可以完成虚拟地址到物理地址的转换 --- 查表【页表】。CPU还有EIP【指令寄存器,把PC虚拟地址的指令读到CPU内部】。
完整过程:可执行程序在磁盘上会有每条指令相应的虚拟地址,当可执行程序加载到内存上时,虚拟地址也会加载到物理内存上【内存上的代码和数据块会加载到PCB上的mm_struct虚拟地址上,整个可执行程序最开始的地址也会加载到CPU的PC寄存器上】, 此时物理内存上也会给每条指令分配相应的物理地址,内存上虚拟地址和物理地址都有时,就会完善页表。CPU上的PC指针拿着整个程序的起始虚拟地址,直接到 MMU硬件 + CR3 上 完成虚拟地址和物理地址的转换,找到虚拟地址在内存上所对应的物理地址上的要执行的指令,读到 EIP【指令寄存器】上去执行这个指令,PC根据 EIP 指令的长度进行 增加【起始地址 + 偏移量】,读到下一条指令的地址,再次以同样的方式进行执行,直到程序结束。
虚拟地址空间是操作系统、CPU、编译器共同协作的产物。虚拟地址和虚拟地址空间,使得编译器在编译代码的时候不用考虑物理内存的情况,编译器只需要把代码进行编址的时候从平坦模式【00..00,ff..ff】一路往下编址,编址完成之后,在进行执行代码的时候,直接统一使用虚拟地址,以线性的方式来看待整个代码和数据,此时 操作系统 和 编译器 就进行 解耦 了。
区域划分:
堆区、栈区、共享区虚拟地址到物理地址的映射怎么来?在mm_struct虚拟地址空间里面的struct vm_area_struct 存有一个链表,链表里面存有【start、end】。
3. 动态库加载的简单理解
在系统当中,物理内存可能会同时存在非常多的库,所以内存会对库进行管理【struct libso{}】。
库加载到内存里,库如何映射到进程的地址空间上呢?在 mm_struct构建一个struct vm_area_struct结构体,里面存有【libstart、libend】库的虚拟地址,虚拟地址经过页表映射到库,把库映射到进程的地址空间。正文代码里面的函数调用【call libc.so】通过把。
程序被加载到内存上,物理地址和虚拟地址都有, 把库的虚拟地址和物理地址映射到页表,映射到PCB里面共享区虚拟地址空间里面,对应的共享区里面的虚拟起始地址空间,就被动态确定了,确定了之后,只要把正文代码里面曾经库的地址,拿库真实的虚拟起始地址【动态确定的地址】进行替换,此时我们访问库当中的方法就变成了起始地址加偏移量【实际当可执行程序和动态库链接的时候,只需要把可执行程序调用库中的目标方法在库当中的偏移量拷贝到可执行程序里面,即地址拷贝】,执行完就可以返回来【完成跳转运行】。库被映射到堆栈之间的任何地址处,都可以被正文代码调用【与位置无关】--- 地址重定位。
可执行程序里有 代码段【.txt】 和 .GOT【全局偏移量表】,.GOT表格存放的是偏移量和下标,代码段中包含有代码【代码里面会调用库函数】,当可执行程序的代码调用库函数进行编译时【call printf()】,实际使用的是.GOT这个段里面的地址和段里面的下标【call .got:index】,当可执行程序加载到内存的时候【.txt 和 .GOT 都要加载进来,.GOT加载到内存是可读写的】,代码段会加载到PCB里面的虚拟地址空间里面的正文代码,整个ELF是统一编址的,所以正文代码里面库的地址和偏移量就已经被加载好了且是固定不变的【.got:index】,当我们要映射库的时候,只需要把加载进来的.got里面所有的地址替换成虚拟地址即可,所以我们在执行某个库方法时,要跳转到物理内存当中的.got表,通过这个表找到方法,然后向下执行。因此当我们要进行地址重定位时,我们不需要改正文代码,只需要改.got表即可 ----- GOT + 库方法偏移量 使得 库加载到共享区的位置与地址无关。
静态库不存在GOT,因为静态库全是 【.o的文件】在链接的时候,就是把所有的 section 和 text 进行合并,静态库的section也进行合并起来。所有静态库的方法和体积很大。
相关文章:
【Linux】静态库 动态库
🌻个人主页:路飞雪吖~ 🌠专栏:Linux 目录 一、👑静态库和动态库 静态库: 动态库: 🌠手动制作静态库 && 手动调用一下我们自己写的静态库 1> 安装到系统里面 ✨生成静…...
Java转Go日记(六):TCP黏包
服务端代码如下: // socket_stick/server/main.gofunc process(conn net.Conn) {defer conn.Close()reader : bufio.NewReader(conn)var buf [1024]bytefor {n, err : reader.Read(buf[:])if err io.EOF {break}if err ! nil {fmt.Println("read from client…...
(51单片机)LCD显示温度(DS18B20教程)(LCD1602教程)(延时函数教程)(单总线教程)
演示视频: LCD显示温度 源代码 如上图将9个文放在Keli5 中即可,然后烧录在单片机中就行了 烧录软件用的是STC-ISP,不知道怎么安装的可以去看江科大的视频: 【51单片机入门教程-2020版 程序全程纯手打 从零开始入门】https://www.…...
【通过Docker快速部署Tomcat9.0】
文章目录 前言一、部署docker二、部署Tomcat2.1 创建存储卷2.2 运行tomcat容器2.3 查看tomcat容器2.4 查看端口是否监听2.5 防火墙开放端口 三、访问Tomcat 前言 Tomcat介绍 Tomcat 是由 Apache 软件基金会(Apache Software Foundation)开发的一个开源 …...
云原生--基础篇-3--云原生概述(云、原生、云计算、核心组成、核心特点)
1、什么是云和原生 (1)、什么是云? “云”指的是云计算环境,代表应用运行的基础设施和资源。依赖并充分利用云计算的弹性、分布式和资源池化能力。 核心含义: 1、云计算基础设施 云原生应用的设计和运行完全基于云…...
Spark-Streaming
Spark-Streaming概述 DStream实操 案例一:WordCount案例 需求:使用 netcat 工具向 9999 端口不断的发送数据,通过 SparkStreaming 读取端口数据并统计不同单词出现的次数 实验步骤: 添加依赖 <dependency> <gro…...
乐视系列玩机------乐视2 x620红灯 黑砖刷写教程以及新版刷写工具的详细释义
乐视x620在上期解析了普通黑砖情况下的救砖刷机过程。但在一些例外的情况下。使用上面的步骤会一直刷写报错 。此种情况就需要另外一种强制刷写方法来救砖 通过博文了解💝💝💝 1💝💝💝-----详细解析乐视2 x620系列 红灯 黑砖线刷救砖的步骤 2💝💝💝----图…...
若依SpringCloud项目-定制微服务模块
若依SpringCloud项目-定制微服务模块 关于微服务先不过多介绍,刚开始熟悉并不能讲的很彻底,成熟的微服务项目-若依SpringCloud就是一个典型的微服务架构工程(网上有很多教程了,不明白的可以学习一下)。 我正在看的视…...
【扫描件批量改名】批量识别扫描件PDF指定区域内容,用识别的内容修改PDF文件名,基于C++和腾讯OCR的实现方案,超详细
批量识别扫描件PDF指定区域内容并重命名文件方案 应用场景 本方案适用于以下场景: 企业档案数字化管理:批量处理扫描的合同、发票等文件,按内容自动分类命名财务票据处理:自动识别票据上的关键信息(如发票号码、日期)用于归档医疗记录管理:从扫描的检查报告中提取患者I…...
学习Docker遇到的问题
目录 1、拉取hello-world镜像报错 1. 检查网络连接 排查: 2. 配置 Docker 镜像加速器(推荐) 具体解决步骤: 1.在服务器上创建并修改配置文件,添加Docker镜像加速器地址: 2. 重启Docker 3. 拉取hello-world镜像 2、删除镜像出现异常 3、 容器内部不能运行ping命令 …...
Docker 数据卷
目录 一、数据卷(Data Volume) 二、使用 1、单独建立数据卷 2、挂载主机数据卷 3、数据卷容器挂载 基本语法: 工作原理: 主要用途: 使用事例: 一、数据卷(Data Volume) 数据卷的使用,类似于 Linux 下对目录或文件进行 mount 数据卷(Data Volume)是一个可供一个或多…...
【数据结构】励志大厂版·初级(二刷复习)双链表
前引:今天学习的双链表属于链表结构中最复杂的一种(带头双向循环链表),按照安排,我们会先进行复习,如何实现双链表,如基本的头插、头删、尾删、尾插,掌握每个细节,随后进…...
通过dogssl申请ssl免费证书
SSL证书作为实现HTTPS加密的核心工具,能够确保用户与网站之间的数据传输安全。尤其是在小程序之类的开发时,要求必须通过https发起请求的情况下。学会如何免费申请一个ssl证书就很有必要了。这里我分享一下,我通过dogssl如何申请ssl的。 一&…...
路由与路由器
路由的概念 路由是指在网络通讯中,从源设备到目标设备路径的选择过程。路由器是实现这一过程的关键设备,它通过转发数据包来实现网络的互联。路由工作在OSI参考模型的第三层,‘网络层’。 路由器的基本原理 路由器通过维护一张路由表来决定…...
Docker底层原理浅析 | namespace+cgroups+文件系统
本文目录 1. Linux NamespaceLinux系统里是否只能有一个pid为1的进程?namespace机制查看namespacenamespace机制测试使用Docker验证namespace机制 2. Dcoerk网络模式3.Control groups4.文件系统(联合文件系统)5. 容器格式 1. Linux Namespace…...
【无人机】使用扩展卡尔曼滤波 (EKF) 算法来处理传感器测量,各传感器的参数设置,高度数据融合、不同传感器融合模式
目录 #1、IMU #2、磁力计 #3、高度 #典型配置 #4、气压计 #静压位置误差修正 #气压计偏置补偿 #5、全球导航系统/全球定位系统--GNSS/GPS #位置和速度测量 #偏航测量 #GPS 速度的偏航 #双接收器 #GNSS 性能要求 #6、测距 #条件范围辅助-Conditional range aidin…...
常见的raid有哪些,使用场景是什么?
RAID(Redundant Array of Independent Disks,独立磁盘冗余阵列)是一种将多个物理硬盘组合成一个逻辑硬盘的技术,目的是通过数据冗余和/或并行访问提高性能、容错能力和存储容量。不同的 RAID 级别有不同的实现方式和应用场景。以下…...
《 C++ 点滴漫谈: 三十四 》从重复到泛型,C++ 函数模板的诞生之路
一、引言 在 C 编程的世界里,类型是一切的基础。我们为 int 写一个求最大值的函数,为 double 写一个相似的函数,为 std::string 又写一个……看似合理的行为,逐渐堆积成了难以维护的 “函数墙”。这些函数逻辑几乎一致࿰…...
EasyRTC打造无人机低延迟高清实时通信监控全场景解决方案
一、方案背景 随着无人机技术的飞速发展,其在航拍、物流配送、农业监测、应急救援等多个领域的应用日益广泛。然而,无人机在实际作业过程中面临着诸多挑战,如通信延迟、数据传输不稳定、监控范围有限等。EasyRTC作为一种高效、低延迟的实时通…...
【MATLAB第117期】#源码分享 | 基于MATLAB的SSM状态空间模型多元时间序列预测方法(多输入单输出)
【MATLAB第117期】#源码分享 | 基于MATLAB的SSM状态空间模型多元时间序列预测方法(多输入单输出) 引言 本文使用状态空间模型实现失业率递归预测,状态空间模型(State Space Model, SSM)是一种用于描述动态系统行为的…...
关于大数据的基础知识(三)——数据安全与合规
成长路上不孤单😊😊😊😊😊😊 【14后😊///计算机爱好者😊///持续分享所学😊///如有需要欢迎收藏转发///😊】 今日分享关于大数据的基础知识(三&a…...
从信息泄露到内网控制
0x01 背景 之前常见用rce、文件上传等漏洞获取webshell,偶然遇到一次敏感信息泄露获取权限的渗透,简单记录一下过程。 0x02 信息泄露 发现系统某端口部署了minio服务,经过探测发现存在minio存储桶遍历 使用利用工具把泄露的文件全部整理一…...
【Qt】QDialog类
🌈 个人主页:Zfox_ 🔥 系列专栏:Qt 目录 一:🔥 对话框 - QDialog 🦋 基本介绍🦋 对话框分类🦋 Qt 内置对话框🎀 QMessageBox -消息对话框🎀 QColo…...
【Spring Boot基础】MyBatis的基础操作:增删查改、列名和属性名匹配 -- XML实现
MyBatis的基础操作 1. MyBatis XML配置文件1.1 简单介绍1.2 配置连接字符串和MyBatis1.3 XMl文件实现--分层1.4 XMl文件实现--举例 2.增删改查操作2.1 增(insert)2.1.1 不使用Param2.1.2 用Param2.1.3 返回自增键 2.2 删(delete)2…...
谷歌推出探索型推荐新范式:双LLM架构重塑用户兴趣挖掘
文章目录 1. 背景1.1 闭环困境1.2 谷歌的两次失败尝试1.2.1 尝试一:轻量微调1.2.2 尝试二:RLHF 强化学习微调 1.3 双LLM范式的提出1.3.1 模型1:Novelty LLM — 负责生成“探索方向”1.3.2 模型2:Alignment LLM — 负责评估“相关性…...
Linux kernel signal原理(下)- aarch64架构sigreturn流程
一、前言 在上篇中写到了linux中signal的处理流程,在do_signal信号处理的流程最后,会通过sigreturn再次回到线程现场,上篇文章中介绍了在X86_64架构下的实现,本篇中介绍下在aarch64架构下的实现原理。 二、sigaction系统调用 #i…...
使用 LangChain + Higress + Elasticsearch 构建 RAG 应用
RAG(Retrieval Augmented Generation,检索增强生成) 是一种结合了信息检索与生成式大语言模型(LLM)的技术。它的核心思想是:在生成模型输出内容之前,先从外部知识库或数据源中检索相关信息&…...
【Linux】46.网络基础(3.3)
文章目录 5. 其他重要协议或技术5.1 DNS(Domain Name System)5.1.1 DNS背景5.1.2 域名简介 5.2 ICMP协议5.2.1 ICMP功能5.2.2 ICMP的报文格式5.2.3 ping命令5.2.4 一个值得注意的坑5.2.5 traceroute命令 5.3 NAT技术5.3.1 NAT技术背景5.3.2 NAT IP转换过程5.3.3 NAPT5.3.4 NAT技…...
【Unity笔记】Unity + OpenXR项目无法启动SteamVR的排查与解决全指南
图片为AI生成 一、前言 随着Unity在XR领域全面转向OpenXR标准,越来越多的开发者选择使用OpenXR来构建跨平台的VR应用。但在项目实际部署中发现:打包成的EXE程序无法正常启动SteamVR,或者SteamVR未能识别到该应用。本文将以“Unity OpenXR …...
【sylar-webserver】重构 增加内存池
文章目录 内存池设定结构ThreadCacheCentralCachePageCache allocatedeallocate测试 参考 https://github.com/youngyangyang04/memory-pool 我的代码实现见 https://github.com/star-cs/webserver 内存池 ThreadCache(线程本地缓存) 每个线程独立的内存…...
云账号安全事件分析:黑客利用RAM子账户发起ECS命令执行攻击
事件背景 某企业云监控系统触发高危告警,提示API请求中包含黑客工具特征(cf_framework),攻击者试图通过泄露的RAM子账户凭据调用ECS高危API。以下是攻击关键信息整理: 字段详情告警原因API请求包含黑客工具特征(cf_framework)攻击实体RAM子账户 mq泄露凭证AccessKey ID…...
Node.js 模块导入的基本流程
Node.js 模块导入的基本流程,主要是 CommonJS 模块加载机制(即使用 require())的内部执行步骤。下面我用清晰的结构给你梳理一下这个过程: ✅ Node.js 模块导入的基本流程(使用 require()) const someModu…...
Unitest和pytest使用方法
unittest 是 Python 自带的单元测试框架,用于编写和运行可重复的测试用例。它的核心思想是通过断言(assertions)验证代码的行为是否符合预期。以下是 unittest 的基本使用方法: 1. 基本结构 1.1 创建测试类 继承 unittest.TestC…...
wps批量修改字体
选择这个小箭头 找到需要修改的字体如正文,右击修改选择合适的字体确定即可...
【Linux网络】各版本TCP服务器构建 - 从理解到实现
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
航电系统多模态融合技术要点
一、技术要点 1. 多模态数据特性分析 异构数据对齐:需处理不同传感器(如雷达、摄像头、IMU、ADS-B等)在时间、空间、精度和采样率上的差异,需设计同步机制(如硬件时钟同步、软件插值对齐)。 数据预处…...
【Git】branch合并分支
在 Git 中,将分支合并到 main 分支是一个常见的操作。以下是详细的步骤和说明,帮助你完成这个过程。 1. 确保你在正确的分支上 首先,你需要确保当前所在的分支是 main 分支(或者你要合并到的目标分支)。 检查当前分支…...
uniapp-商城-33-shop 布局搜索页面以及u-search
shop页面上有一个搜索,可以进行商品搜索,这里我们先做一个页面布局,后面再来进行数据i联动。 1、shop页面的搜索 2、搜索的页面代码 <navigator class"searchView" url"/pagesub/pageshop/search/search"> …...
蓝桥杯常考的找规律题
目录 灵感来源: B站视频链接: 找规律题具有什么样的特点: 报数游戏(Java组): 题目描述: 题目链接: 思路详解: 代码详解: 阶乘求和(Java组…...
全球化2.0 | 云轴科技ZStack亮相2025香港国际创科展
4月13-16日,由香港特别行政区政府、香港贸发局主办的2025香港国际创科展(InnoEX)在香港会议展览中心举办,作为亚洲最具影响力的科技盛会之一,本届展会吸引了来自17个国家和地区的500余家顶尖科技企业、科研机构及行业先…...
【Python进阶】数据可视化:Matplotlib从入门到实战
Python数据可视化:Matplotlib完全指南 前言技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块说明技术选型对比 二、实战演示环境配置要求核心代码实现案例1:折线图(股票趋势&#…...
操作系统——堆与栈详解:内存结构全面科普
文章目录 堆与栈详解:内存结构全面科普一、程序内存结构总览二、各段介绍及特点1. 代码段 .text2. 数据段 .data3. BSS段 .bss4. 堆区 Heap5. 栈区 Stack 三、C语言实例分析四、深入理解:为什么堆空间可能不连续?1. 堆内部结构:链…...
Mysql面试知识点详解
Mysql面试知识点详解 Mysql 是 Java 开发领域中常用的持久层框架,在面试和实际开发中都占据重要地位。本文将深入剖析 Mysql的核心知识点,并结合实战案例,帮助读者全面掌握相关技能。 一、慢查询定位与分析 (一)定位…...
数智读书笔记系列030《曲折的职业道路:在终身工作时代找准定位》与《做自己的教练:战胜工作挑战掌控职业生涯》
书籍简介 《曲折的职业道路:在终身工作时代找准定位》由英国职业发展专家海伦塔珀(Helen Tupper)和莎拉埃利斯(Sarah Ellis)合著,旨在帮助读者应对现代职场中日益普遍的“非直线型”职业路径。两位作者是“神奇的如果”(Amazing If)公司的联合创始人,曾为李维斯、沃达…...
Linux内核之文件驱动随笔
前言 近期需要实现linux系统文件防护功能,故此调研了些许知识,如何实现文件防护功能从而实现针对文件目录防护功能。当被保护的目录,禁止增删改操作。通过内核层面实现相关功能,另外在通过跟应用层面交互从而实现具体的业务功能。…...
【python】如何将文件夹及其子文件夹下的所有word文件汇总导出到一个excel文件里?
根据你的需求,这里提供一套完整的Python解决方案,支持递归遍历子文件夹、提取Word文档内容(段落+表格),并整合到Excel中。以下是代码实现及详细说明: 一个单元格一个word的全部内容 完整代码 # -*- coding: utf-8 -*- import os from docx import Document import pand…...
IDEA中如何统一项目名称/复制的项目如何修改根目录名称
1、问题概述? 在开发中,有时候为了方便,我们会复制一个新的项目,结果出现如下提示: 会在工程的后面提示工程原来的名字。 这种情况就是复制之后名字修改不彻底造成的。 2、彻底的修改工程的名字 2.1、修改pom.xml中…...
Ubuntu-Linux中vi / vim编辑文件,保存并退出
1.打开文件 vi / vim 文件名(例: vim word.txt )。 若权限不够,则在前方添加 sudo (例:sudo vim word.txt )来增加权限; 2.进入文件,按 i 键进入编辑模式。 3.编辑结…...
如何在idea里创建注释模版
✅ 步骤:创建一个类注释的 Live Template(缩写为 cls) ① 打开设置 IDEA 菜单栏点击:File > Settings(或按快捷键 Ctrl Alt S) ② 进入 Live Templates 设置 在左侧菜单找到:Editor > …...
IntelliJ IDEA 新版本中 Maven 子模块不显示的解决方案
一、问题现象与背景 在使用 IntelliJ IDEA 2024 版本开发 Maven 多模块项目时,我发现一个令人困惑的现象:父模块的子模块未在右侧 Maven 工具窗口中显示,仅显示父模块名称(且无 (root) 标识)。而此前在 IntelliJ IDEA…...