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