天天看点

第81部分- Linux x86 64位汇编 内存映射文件第81部分- Linux x86 64位汇编 内存映射文件

第81部分- Linux x86 64位汇编 内存映射文件

上个例子中,如果执行如下:

#readchange readchange.s readchange.s

则会发现readchange.s会空了。

因为系统不能在读取一个文件的同时把数据写入到同一个文件。

但是很多应用程序中涉及到更新文件,一种方法称为内存映射文件。

系统调用号文件:arch/x86/entry/syscalls/syscall_64.tbl

内存映射文件

内存映射文件调用mmap把部分文件映射到系统的内存中。程序可以使用标准的内存指令访问内存位置,还可以修改,可以在多个进程之间共享内存位置并同时更新。

内存映射文件是保存在内存中的,没有同步到原始文件,如果要同步到原始文件,需要调用msync/munmap系统调用。

mmap

这里涉及到的系统调用是mmap,系统调用号是9.

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr是内存中的什么位置映射文件。可以为0,系统选择位置。

length是加载到内存中的字节数量,设置为0,长度为文件长度。如果使用了offset值,则必须是系统页面长度的倍数。例如4k/8K……

prot是内存保护设置,可以是PROT_NONE/PROT_READ/PROT_WRITE/PROT_EXEC。

flags是创建的映射对象的类型,可以是MAP_SHARE/MAP_PRIVATE。

fd是要映射到内存的文件的文件句柄

offset是文件中要复制到内存的数据的起点。

msync

int msync(void *addr, size_t length, int flags);

输入值addr是内存映射文件在内存中的开始位置。调用mmap返回这个值。

Length是要写入原始文件的字节数量。

flags定义如何更新原始文件

            MS_ASYNC:下次可以写入文件时安排更新,并且系统调用返回。

      MS_SYNC:系统调用等待,知道做出更新,然后再返回调用程序。

内存映射文件是不能改动原始文件的长度的。

            系统调用号是26.

munmap

int munmap(void *addr, size_t length);

是mmap系统调用的逆向操作。

系统调用号是11.

mmap示例

通过系统调用mmap吧整个文件映射到内存中、修改内存映射文件中的数据以及数据写回原始文件。

先通过lseek获取文件的长度。

lseek系统调用号是8.

off_t lseek(int fd, off_t offset, int whence);

整体逻辑如下:

  • 使用读/写访问打开文件
  • 使用函数sizefunc确定文件长度
  • 使用系统调用mmap把文件映射到内存中
  • 把内存映射文件的全部内容转化为大写字母
  • 使用munmap把内存映射文件写入原始文件
  • 关闭原始文件并且推出。

代码如下:

.code64;//本来没有这个伪代码,开发过程发现有个movq指令被截断成移动4个字节了,所以加上可以规避这个问题。
.section .bss
   .lcomm filehandle, 8
   .lcomm size, 8
   .lcomm mappedfile, 8
.section .text
.globl _start
_start:
   # 获取文件名字并以读写模式打开
   movl $2, %eax;//打开系统调用
   movq 16(%rsp), %rdi;//获取第一个参数,前两个是参数数量和程序名字
   movq $0102, %rsi;//读写访问权限
   movq $0644, %rdx;//644文件权限
   syscall
   test %eax, %eax
   js badfile
   movl %eax, filehandle;//保存到filehandle中

   # 寻找文件的大小
   movl filehandle,%edi
   call sizefunc;//调用函数sizefunc
   movl %eax,size;//通过eax返回,并赋值给size中。

   # 映射文件到内存中,void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

   movq $0,%rdi;//由系统分配内存空间
   movq size,%rsi;//长度length
   movq $3,%rdx;//prot是 PROT_READ | PROT_WRITE
   movq $1,%r10;// flags是MAP_SHARED,函数调用的第四个参数是rcx的,但是系统调用的话就是r10。
   movq filehandle,%r8;//句柄
   movq $0,%r9;//offset
   movq $9, %rax;//系统调用mmap
   syscall
   test %rax, %rax
   js badfile
   movq %rax, mappedfile;//文件句柄,这里被截断成movl指令了,加上.code64规避

   # 将内存映射文件中的字符变为大写的
   movq mappedfile,%rdi
   movq size,%rsi
   call convert;//调用convert函数

   # 使用munmap将修改的文件同步到原来文件中。
   movl $11, %eax
   movq mappedfile, %rdi
   movq size, %rsi
   syscall
   test %eax, %eax
   jnz badfile

  # 关闭文件句柄
   movl $3, %eax
   movl filehandle, %edi
   syscall

badfile:
   movl %eax, %ebx
   movl $60, %eax
   syscall

.type sizefunc, @function;//函数sizefunc
sizefunc:
movl $8, %eax;//lseek系统调用号是8
movq $0, %rsi;//第一个参数是rdi,第二个参数是rsi,表示offset,从0开始。
movq $2, %rdx;//第三个参数,到达文件结尾SEEK_END即是2.
syscall
ret

.type convert,@function;//convert函数
convert:
   movq %rsi,%rcx;//第二个参数是字符数量,赋值为rcx.
   movq %rdi,%rsi;//第二个参数是rdi指向源字符串的内存位置,要赋值给rsi

convert_loop:
   lodsb;//加载字符串字节到al寄存器,rsi指向源字符串的内存位置
   cmpb $0x61,%al;//是否小于0x61,即97,小于则跳过
   jl skip
   cmpb $0x7a,%al;//是否大于于0x7a,即122,大于也跳过
   jg skip
   sub $0x20,%al;//改成小写
skip:
   stosb;//重新加载al到rdi的字符串,就是源字符串地址。
   loop convert_loop
   ret
           

as -g -o mmapdemo.o mmapdemo.s

ld -o mmapdemo mmapdemo.o

执行如下:

#cp mmapdemo.s mmaptest.s

#./mmapdemo mmaptest.s

在使用gdb调试前,可以使用strace ./mmapdemo mmaptest.s来看下系统调用情况。

这里要注意的是mmap的系统调用第四个参数被放到了r10寄存器了。

继续阅读