加入收藏 | 设为首页 | 会员中心 | 我要投稿 晋中站长网 (https://www.0354zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

Linux的mmap函数与动态库的加载

发布时间:2022-10-25 13:31:01 所属栏目:Linux 来源:未知
导读: mmap()函数是Linux的一个系统调用,用于实现文件到内存的映射。
在文件的网络传输、共享内存、动态库的加载等系统底层的关键功能上有重要的应用。
在x64上,mmap的系统调用号是9,定义在如

mmap()函数是Linux的一个系统调用,用于实现文件到内存的映射。

在文件的网络传输、共享内存、动态库的加载等系统底层的关键功能上有重要的应用。

在x64上,mmap的系统调用号是9,定义在如下的头文件里:

/usr/include/x86_64-linux-gnu/asm/unistd_64.h

linux动态库_ppt动态素材库_android 动态加载so库

可以看到,read()的调用号是0,write()的是1,open()的是2。

Linux的系统调用号,都是以__NR_开始的宏常量。

系统调用,是Linux内核与用户程序的唯一接口。

实际代码中,一般不会直接使用系统调用,而是使用glibc库对它的封装函数。

Linux的man手册,就是C库封装函数的说明书。其中关于mmap的部分如下:

ppt动态素材库_linux动态库_android 动态加载so库

它是参数最多的常用函数,6个参数。加上系统调用号,就是7个。

mmap()的最后一个参数offset,是文件的起始偏移量。从这个偏移量开始映射内存,它必须是一个内存页大小的整数倍(Linux的是4096字节)。

如果offset不按4096对齐,mmap()函数会失败,并设置错误码是EINVAL,即无效的参数。

Linux内核是通过分页来管理内存的,内存权限管理的最小单位是页。Linux并不能为小于1页的多少字节单独设置权限。

更细的管理,意味着索引数据要占据更多的内存,挤压有效数据的可用空间。

第1个参数void* addr,通常传NULL,让Linux内核自己去选要映射到哪个内存地址。

Linus肯定比咱更懂进程的内存布局,让他选(笑)。

第2个参数length,是要映射的文件长度,它与最后一个参数offset一起确定映射的文件区域。

android 动态加载so库_ppt动态素材库_linux动态库

对于比较小的文件,一般length设置为文件的总长度,offset设置为0,即映射整个文件。

第3个参数prot,是映射的内存权限,分为读、写、执行三种。大多数情况下,只使用读写权限,但是在加载动态库的时候,也会使用执行权限。

第4个参数flags,用于确定映射的内存在多个进程之间的共享权限。

如果要用于进程间通信,那么flags要设置为MAP_SHARED。

否则,flags设置为MAP_PRIVATE,即只有当前进程自己用。

第5个参数fd,是打开的文件描述符,在mmap()之前要先打开文件,并且获取文件的长度(fstat()函数)。

android 动态加载so库_linux动态库_ppt动态素材库

Linux系统上的内存可以粗略分为两类,物理内存和虚拟内存。

物理内存,是可以实际读写的内存。

虚拟内存,只是个内存范围的占位符。在用户程序实际使用(这块内存)之前,Linux系统并不为它分配物理内存。

所以,在mmap()调用的时候,Linux内核要选的只是进程的一块虚拟内存空间。

这块空间的起始地址要按4096字节对齐,这样在使用的时候正好在一个页的边界上,而不至于出现横跨两个页的情况。

同时,这块空间的大小要能放下需要映射的文件区域。因为mmap()之后就可以把文件当内存读写了,memcpy()函数只会连续拷贝linux动态库,所以这块区域必须是连续的。

选好这块虚拟内存空间之后,Linux只需要记录文件和这块内存的对应关系,并不需要把文件的数据读到内存里。

在用户实际要用的时候再读文件数据,这就是需求加载。

每个进程都有自己的虚拟内存空间,它们通过各自的页表实现虚拟内存与物理内存的对应关系。

如果一个物理内存页,同时对应到了多个进程的虚拟内存空间,就是共享内存。

两个进程打开同一个文件,使用MAP_SHARED方式映射相同的文件区域,就能以这块共享内存实现进程间通信了。Linux有一个专门的函数shm_open(),打开共享内存的文件描述符,专门用于这种情况。

如果两个进程共享的内存特别多,除了栈之外都共享,那么它们就是线程。

线程是可以并发运行的,它们有各自的栈,所以函数的局部变量并不会受到多线程的影响。

但malloc()分配的堆内存,以及全局变量、静态变量,必须使用锁保护,它们在线程之间是共享的。

如果是动态库,它也可以在多个进程之间共享。只要把动态库所在的物理内存页,同时映射到多个进程的虚拟空间就行。

与普通文件的区别是,动态库的代码段在用mmap()时要添加执行权限,PROT_EXEC。

android 动态加载so库_linux动态库_ppt动态素材库

如图,这个例子用的是.o文件,在mmap()时设置了读和执行权限,然后把代码段所在的地址当作一个函数指针来调用。

android 动态加载so库_linux动态库_ppt动态素材库

从readelf -a的信息可以看出,代码段.text的偏移量是0x40,即函数add()从这个elf文件的0x40字节开始。

linux动态库_ppt动态素材库_android 动态加载so库

函数的内容很简单,就是把两个整数相加,并返回。

两个输入参数在rdi和rsi,输出在rax。

ppt动态素材库_linux动态库_android 动态加载so库

运行的结果如上图。

f(1, 2)的返回值是3,打印出来的二进制码也与objdump的输出一致。

如果mmap()没有选择执行权限,在把add当作函数调用时,Linux就会报段错误,导致进程core dump。

动态库也是使用mmap()加载的,与这个加载.o文件的例子类似。

只不过动态库的内容更复杂,要通过分析它的elf文件来确定它包含哪些函数,依赖哪些(其他)动态库。

如果它还依赖其他动态库,那么在加载时还要做动态连接。

(编辑:晋中站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!