一、基礎知識
1.打開設備文件:
mem是一個字符設備文件,是計算機主存的一個映像。通常只有root用戶對其有讀寫權限。因此只有root用戶能進行這些操作。
如果要打開設備文件/dev/mem,需要系統調用open()函數,作用是打開一個文件或設備,其函數原型為:
int open(const char *path, int flags);
返回值:如果操作成功則返回一個文件描述符,否則返回-1
形 參:
path 被打開文件的路徑即文件名描述。
flags 文件的訪問模式描述,可常用的選項見下:
O_RDONLY 只讀方式
O_WRONLY 只寫方式
O_RDWR 可讀寫方式
說 明:此函數用於打開文件或者設備
頭文件:#include <fcntl.h> #include <stat.h> 定義在/usr/include/fcntl.h中
2.讀取內存映像:
內存映像其實在內存中創建一個與外存中文件完全相同的映像。用戶可以將整個文件映射到內存中,也可以將文件的一部分映射到內存中。
使用操作內存的方法對文件進行操作。系統會將內存映像文件所做的改動反映到真實文件中去。
在內存映像I/O的實現過程中需要用到一些系統調用:
首先是創建內存映像文件的系統調用mmap()函數,其函數原型為:
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
返回值:成功時,返回值為指向內存映像起始地址的指針,當調用失敗時,返回值為-1
形 參:
start 一個void指針,表示希望將文件映射到此指針指向的位置,通常為NULL。
length 定義內存映像文件所占用的內存空間大小,以字節計。
prot 內存映像文件的安全屬性,注意和open函數中的flags屬性保持一致。它的可使用的選項如下:
Flags 含義
PROT_EXEC 被映像內存可能含義機器碼,可被執行
PROT_NONE 映像內存不允許訪問
PROT_READ 映像內存可讀
PROT_WRITE 映像內存可寫
flags 內存映像的標志,選項如下:
Flags 含義
MAP_FIXED 指定映射起始地址,如果由start和len指定的內存區重疊於現存的映射空間,重疊部分將會被丟棄。
如果指定的起始地址不可用,操作將會失敗。並且起始地址必須落在頁的邊界上。
MAP_SHARED 與其它所有映射這個對象的進程共享映射空間。對共享區的寫入,相當於輸出到文件。
直到msync()或者munmap()被調用,文件實際上不會被更新。
MAP_PRIVATE 建立一個寫入時拷貝的私有映射。內存區域的寫入不會影響到原文件。
這個標志和以上標志是互斥的,只能使用其中一個。
MAP_NORESERVE 不要為這個映射保留交換空間。當交換空間被保留,對映射區修改的可能會得到保證。
當交換空間不被保留,同時內存不足,對映射區的修改會引起段違例信號。
MAP_LOCKED 鎖定映射區的頁面,從而防止頁面被交換出內存。
MAP_GROWSDOWN 用於堆棧,告訴內核VM系統,映射區可以向下擴展。
MAP_ANONYMOUS 匿名映射,映射區不與任何文件關聯。
MAP_ANON MAP_ANONYMOUS的別稱,不再被使用。
MAP_FILE 兼容標志,被忽略。
MAP_32BIT 將映射區放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略。
當前這個標志只在x86-64平台上得到支持。
MAP_POPULATE 為文件映射通過預讀的方式准備好頁表。隨后對映射區的訪問不會被頁違例阻塞。
MAP_NONBLOCK 僅和MAP_POPULATE一起使用時才有意義。不執行預讀,只為已存在於內存中的頁面建立頁表入口。
fd 要映射的文件的描述符。
offset 所映射的數據內容 距離文件頭的偏移量。
說 明:此函數用於將一個文件或它的一部分映射到內存中。
頭文件:#include <sys/mman.h> #include <sys/types.h> 函數定義在/usr/include/sys/mman.h
3.撤銷內存映像的修改
另外我們使用完內存映像文件后,要用系統調用函數munmap()函數來撤銷,其函數原型為:
int munmap(void *start, size_t length);
返回值:成功時,返回值為0;調用失敗時返回值為 -1,並將errno設置為相應值。
形 參:
start 要撤銷的內存映像文件的起始地址。
length 要撤銷的內存映像文件的大小。
說 明:當進程結束或利用exec相關函數來執行其他程序時,映射內存會自動解除,但關閉對應的文件描述詞時不會解除映射。
頭文件:#include <sys/mman.h>
4.將內存映像的改動保存到外存中
最后,如果我們要將內存映像的改動保存到外存中,還需要系統調用msync()函數,其函數原型為:
int msync(const void *start,size_t length,int flags);
返回值:成功時,返回值為0;調用失敗時返回值為 -1,並將errno設置為相應值。
形 參:
start 要保存到外存的那些源文件的起始地址。
length 表示內存映像文件的大小。
flags 設置了函數的相應操作,其具體選項如下:
Flags 含義
MS_ASYNC 調用一個寫操作並返回
MS_INVALIDATE 映像到相同文件的內存映像數據更新
MS_SYNC 完成寫操作后函數返回
說 明:進程在映射空間的對共享內容的改變並不直接寫回到磁盤文件中,往往在調用munmap()后才執行該操作。
可以通過調用msync()函數來實現磁盤文件內容與共享內存區中的內容一致,即同步操作。
頭文件:#include <sys/mman.h>
5.通過/dev/mem設備文件和mmap系統調用,可以將線性地址描述的物理內存映射到進程的地址空間,然后就可以直接訪問這段內存了。
二、一個例子
-
-
-
-
-
-
-
int main (int args, char* arg[])
-
-
-
-
-
-
-
if((fd = open ("/dev/mem", O_RDWR)) < 0)
-
-
-
-
-
-
-
mem = mmap (
0, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-
-
-
-
-
-
-
-
-
-
printf("\nold mem[%d]:%d", i, mem[i]);
-
-
-
printf(The value is 0x%x\n, *((int *)mem));
-
-
-
-
-
-
-
printf("\nnew mem[%d]:%c", i, mem[i]);
-
-
-
-
-
-
-
}
使用 hexedit /dev/mem 可以顯示所有物理內存中的信息。 運用mmap將/dev/mem map出來,然后直接對其讀寫可以實現用戶空間的內核操作。
以下是我寫的一個sample
#include<stdio.h>
#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
unsigned char * map_base;
FILE *f;
int n, fd;
fd = open("/dev/mem", O_RDWR|O_SYNC);
if (fd == -1)
{
return (-1);
}
map_base = mmap(NULL, 0xff, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0x20000);
if (map_base == 0)
{
printf("NULL pointer!\n");
}
else
{
printf("Successfull!\n");
}
unsigned long addr;
unsigned char content;
int i = 0;
for (;i < 0xff; ++i)
{
addr = (unsigned long)(map_base + i);
content = map_base[i];
printf("address: 0x%lx content 0x%x\t\t", addr, (unsigned int)content);
map_base[i] = (unsigned char)i;
content = map_base[i];
printf("updated address: 0x%lx content 0x%x\n", addr, (unsigned int)content);
}
close(fd);
munmap(map_base, 0xff);
return (1);
}
上面的例子將起始地址0x20000(物理地址), 長度為0xff映射出來。 然后就可以像普通數組一樣操作內存。
下面是輸出結果
address: 0x7f3f95391000 content 0x0 updated address: 0x7f3f95391000 content 0x0
address: 0x7f3f95391001 content 0x0 updated address: 0x7f3f95391001 content 0x1
address: 0x7f3f95391002 content 0x0 updated address: 0x7f3f95391002 content 0x2
address: 0x7f3f95391003 content 0x0 updated address: 0x7f3f95391003 content 0x3
address: 0x7f3f95391004 content 0x0 updated address: 0x7f3f95391004 content 0x4
。。。
我的測試機器是64位機。 該例子將物理地址0x20000映射到了虛擬地址0x7f3f95391000。
首先將當前地址下的內容輸出, 然后寫入新值。
可以通過 hexedit /dev/mem 驗證新值已經寫入。
如果想在用戶態處理kernel分配的地址可以這么做。 首先用virt_addr = get_free_pages(GFP_KERNEL, order)分配內存,通過phy_addr = __pa(virt_addr)得到物理地址,然后在用戶態將/dev/mem用mmap 映射出來, offset就是phy_addr, length設為 2^order。 此時就可以在用戶態讀寫內核分配的內存了。
注:該操作需要有root權限。
————————————————
版權聲明:本文為CSDN博主「zhanglei4214」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zhanglei4214/article/details/6653568