Linux 讀寫memory操作,devmem直接訪問物理內存地址

1 說明

由於開發需要,需要通過memory傳輸數據,所以使用devmem 方式讀寫數據,操作linux 內存數據。devmem的方式是提供給驅動開發人員,在應用層能夠偵測內存地址中的數據變化,以此來檢測驅動中對內存或者相關配置的正確性驗證。

2 開發環境

軟件環境: ubuntu 虛擬機、arm-xilinx 交叉編譯工具鏈
硬件環境: ZYNQ7010

3 內存地址說明

基本上的內存物理地址都可以訪問,但是如果需要ZYNQ的PS 和PL 都能讀寫數據,需要查看芯片的datasheet,確定哪個地址可以互相讀寫數據。
通過《ug585-Zynq-7000-TRM.pdf》 的 29章表格“Table 29‐1: Initial OCM/DDR Address Map” 可以得到地址分配。
圖3-1 ZYNQ7010地址
表3-1 ZYNQ7010 芯片地址分配
從表格3-1得知,DDR的物理地址對應爲 0x0010_0000 - 3FFF_FFFF

4 devmem 工具

工具的原理也比較簡單,就是應用程序通過mmap函數實現對/dev/mem驅動中mmap方法的使用,映射了設備的內存到用戶空間,實現對這些物理地址的讀寫操作。
代碼如下:

/**
 * @addtogroup module_devmem
 * @{
 */

 /**
 * @file
 * @brief 內存管理工具,仿照標準linux devmem 工具進行改良,可以自由讀寫linux內存數據。
 * @details 驅動接口。
 * @version 1.1.0
 * @author sky.houfei
 * @date 2019-8-6
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <stdbool.h>

//*****************************************************************************
#define FATAL do { fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", \
  __LINE__, __FILE__, errno, strerror(errno)); exit(1); } while(0)
 
#define MAP_SIZE 4096UL  			//映射的內存區大小(一般爲一個葉框大小)
#define MAP_MASK (MAP_SIZE - 1)   	//MAP_MASK = 0XFFF

//*****************************************************************************
/**
* @brief 直接寫入到內存實際的物理地址。
* @details 通過 mmap 映射關係,找到對應的內存實際物理地址對應的虛擬地址,然後寫入數據。
* 寫入長度,每次最低4字節
* @param[in] writeAddr, unsigned long, 需要操作的物理地址。
* @param[in] buf,unsigned long *, 需要寫入的數據。
* @param[in] len,unsigned long, 需要寫入的長度,4字節爲單位。
* @return ret, int, 如果發送成功,返回0,發送失敗,返回-1。
*/
static int Devmem_Write(unsigned long writeAddr, unsigned long* buf, unsigned long len)
{
	int i = 0;
	int ret = 0;
    int fd;
	void *map_base, *virt_addr; 
	unsigned long addr = writeAddr;
    int offset_len = 0;

	if(len == 0)
	{
        printf("%s %s %d, len = 0\n", __FILE__, __func__, __LINE__);
        return -1;
    }
	
	if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1)
	{
		fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
		return -1;
    }
	
	/* Map one page */ //將內核空間映射到用戶空間
    map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & ~MAP_MASK);
    if(map_base == (void *) -1)
	{
		fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
		close(fd);
		return -1;
    }
	
		// 發送實際數據內容
 	for (i = 0; i < len; i++)
 	{
		// 翻頁處理
        if(offset_len >= MAP_MASK)
        { 
            offset_len = 0;
            if(munmap(map_base, MAP_SIZE) == -1)
        	{
        		fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
        		close(fd);
        		return -1;
        	}
            map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & ~MAP_MASK);
            if(map_base == (void *) -1)
        	{
        		fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
        		close(fd);
        		return -1;
            }
            printf("map_base over 4k = [%p].\n", map_base);		// 翻頁打印提示
        }
    
		virt_addr = map_base + (addr & MAP_MASK);	// 映射地址
		*((unsigned long *) virt_addr) = buf[i]; 	// 寫入數據
		addr += 4;
        offset_len += 4;
	}
	
	    
	if(munmap(map_base, MAP_SIZE) == -1)
	{
		fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
		return -1;
	}
	
    close(fd);
	return 0;
}


/**
* @brief 從實際物理地址讀取數據。
* @details 通過 mmap 映射關係,找到對應的實際物理地址對應的虛擬地址,然後讀取數據。
* 讀取長度,每次最低4字節。
* @param[in] readAddr, unsigned long, 需要操作的物理地址。
* @param[out] buf,unsigned char *, 讀取數據的buf地址。
* @param[in] bufLen,unsigned long , buf 參數的容量,4字節爲單位,如 unsigned long buf[100],那麼最大能接收100個4字節。
* 用於避免因爲buf容量不足,導致素組越界之類的軟件崩潰問題。
* @return len,unsigned long, 讀取的數據長度,字節爲單位。如果讀取出錯,則返回0,如果正確,則返回對應的長度。
*/
static int Devmem_Read(unsigned long readAddr, unsigned long* buf, unsigned long len)
{
	int i = 0;
    int fd,ret;
    int offset_len = 0;
    void *map_base, *virt_addr; 
	off_t addr = readAddr;
	unsigned long littleEndianLength = 0;
	
	if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1)
	{
		fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
		return 0;
    }
	
    /* Map one page */ //將內核空間映射到用戶空間
    map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & ~MAP_MASK);
    if(map_base == (void *) -1)
	{
		fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
		close(fd);
		return 0;
    }
	
	for (i = 0; i < len; i++)
 	{
		// 翻頁處理
        if(offset_len >= MAP_MASK)
        {
            offset_len = 0;
            if(munmap(map_base, MAP_SIZE) == -1)
        	{
        		fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
        		close(fd);
        		return 0;
        	}
            map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & ~MAP_MASK);
            if(map_base == (void *) -1)
        	{
        		fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
        		close(fd);
        		return 0;
            }
        }
    
		virt_addr = map_base + (addr & MAP_MASK);	// 將內核空間映射到用戶空間操作
		buf[i] = *((unsigned long *) virt_addr);	// 讀取數據
 		addr += 4;
        offset_len += 4;
	}
	
	if(munmap(map_base, MAP_SIZE) == -1)
	{
		fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
		close(fd);
		return 0;
	}
    close(fd);
	return i;
}


static void Devmem_usage(void)
{
	printf("Usage ./devmem_tool read/write 0xfa0000 20\n");
	printf("The read/write is the command type, read or write data to memory\n");
	printf("The 0xfa2000 is the memory physical address\n");
	printf("The 20 is read/write data length, uint is 4 byte, so it is total 20 * 4 = 80 bytes\n");
	printf("Usage:  ./devmem_tool read 0xfa0000 20\n");
	printf("Usage:  ./devmem_tool write 0xfa0000 20\n");
}


/**
* @brief 內存工具主函數。
* @details 操作方法,請參考Devmem_usage 函數。
* 讀取內存數據: ./devmem_tool read 0xfa0000 20
* 讀取內存物理地址 0xfa0000作爲起始地址,一共讀取20個4字節,共計 20 * 4 = 80 字節。
* 寫入內存數據: ./devmem_tool write 0xfa0000 20
* 寫入內存物理地址 0xfa0000作爲起始地址,一共寫入20個4字節,共計 20 * 4 = 80 字節。
*/
int main(int argc, char** argv)
{
	unsigned long len = 0;
	unsigned long writeData[8192];
	unsigned long readData[8192];
	unsigned long addr = 0;
	unsigned long i = 0;
	
	if (argc != 4)
	{
		Devmem_usage();
		return 0;
	}
	
	addr = strtoul(argv[2], 0, 0);
	len = strtoul(argv[3], 0, 0);
	if (strcmp(argv[1], "read") == 0)
	{
		printf("read data\n");
		memset(readData, 0, len);				
		Devmem_Read(addr, readData, len);		// 讀取數據
		for (i = 0; i < len; i++)
		{
			printf("address = 0x%08x, data = 0x%08x\n", (addr + i * 4), readData[i]);
		}
	}
	else if (strcmp(argv[1], "write") == 0)
	{
		printf("write data\n");
		memset(writeData, 0, len);
		for (i = 0; i < len; i++)
		{
			writeData[i] = i;
		}
		Devmem_Write(addr, writeData, len);		// 寫入數據
	}
	else
	{
		printf("error command type\n");
		Devmem_usage();
	}
}

5 編譯調試

使用交叉編譯工具鏈進行編譯

arm-xilinx-linux-gnueabi-gcc -o devmem_tool devmem.c

將編譯好的 devmem_tool 下載到開發板,運行測試,本工具一次操作4個字節。
測試命令如下:

* 讀取內存數據: ./devmem_tool read 0xfa0000 20
* 讀取內存物理地址 0xfa0000作爲起始地址,一共讀取20個4字節,共計 20 * 4 = 80 字節。
* 寫入內存數據: ./devmem_tool write 0xfa0000 20
* 寫入內存物理地址 0xfa0000作爲起始地址,一共寫入20個4字節,共計 20 * 4 = 80 字節。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章