天天看點

第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寄存器了。

繼續閱讀