创建字符设备在用户态读取物理地址内存

  • 思路
    • 内核创建一个字符设备文件/dev/devmemhqb。
    • 用户态程序接受一个cmd(指示读取1/2/4/8 byte)和物理phy_addr,open字符设备/dev/devmemhqb,通过ioctl将cmd和phy_addr传给内核态ko。
    • 内核态ko的unlocked_ioctl接口中,使用phys_to_virt将phy_addr转换为virt_addr,按照cmd指示读取对应数量字节的内存数据。
  • 目录结构

  • 内核驱动代码
// https://blog.csdn.net/m0_59416558/article/details/127453236
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <asm/page_64.h>
#include "devmem.h"

//设计结构体类型来表示整个驱动对象(描述设备驱动信息)
struct led_drv
{
	unsigned int major;//主设备号
	dev_t devno;//设备号
	struct class * cls;//设备文件信息结构体
	struct device* dev;//设备文件信息
};
//定义全局设备对象(存储信息)
struct led_drv  led;

//驱动与应用程序函数关联
int led_open(struct inode * inode, struct file * file)
{
	printk("hqb led_open\n");
	return 0;
}

int led_close(struct inode * inode, struct file * file)
{
	printk("hqb led_close\n");
	return 0;
}

ssize_t led_write(struct file * file, const char __user * buf, size_t size, loff_t * ops)
{
	int num = 0;
	void *p = (void *)copy_from_user(&num, buf, size);
	printk("addr is = %p num:%#x size:%ld.\n", p, num, size);

	return 0;
}

static long dev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	unsigned long value = 0xAAAAAAAA55555555;
	void *vaddr = NULL;
	unsigned long paddr = 0;
	/* 从用户空间读取新的工作模式 */
	if (copy_from_user(&paddr, (const void __user *)arg, sizeof(paddr))) {
		printk("copy_from_user failed.\n");
		return -1;
	}

	printk("cmd:%d user paddr:%#lx arg:%#lx.\n", cmd, paddr, arg);
	vaddr = phys_to_virt(paddr);
	switch (cmd)
	{
		case READ_BYTE_FROM_PADDR:
			value = *(char *)vaddr;
			break;
		case READ_WORD_FROM_PADDR:
			value = *(short int *)vaddr;
			break;
		case READ_DWORD_FROM_PADDR:
			value = *(int *)vaddr;
			break;
		case READ_DOUBLE_DWORD_FROM_PADDR:
			value = *(unsigned long *)vaddr;
			break;
		default:
			printk("cmd:%d not supported.\n", cmd);
			break;
	}

	printk("cmd:%d user vaddr:%p paddr:%#lx value:%#lx.\n", cmd, vaddr, paddr, value);

	if (copy_to_user((void __user *)arg, &value, sizeof(value))) {
		printk("copy_to_user failed.\n");
		return -1;
	}

	return 0;
}

const struct file_operations fops = {
	.owner          = THIS_MODULE,
	.open = led_open,
	.release = led_close,
	.write = led_write,
	.unlocked_ioctl = dev_ioctl,
};

static int __init devmemhqb_init(void)
{
	int ret = -1;

	//申请设备号
	led.major = 100; //250; wsl中设备号250已经被其它设备占用
	led.devno = led.major << 20 | 0;
	ret = register_chrdev(led.major, "devmemhqb drv", &fops);
	if (ret < 0)
	{
		printk("register devno error\n");
		goto err_1;
	}

	//创建设备文件
	led.cls = class_create(THIS_MODULE, "devmemhqb cls");
	if (IS_ERR(led.cls))
	{
		printk("class create error\n");
		goto err_2;
	}

	led.dev = device_create(led.cls, NULL, led.devno, NULL, "devmemhqb");
	if (IS_ERR(led.dev))
	{
		printk("device create erroe\n");
		goto err_3;
	}

	return 0;

err_3:
	class_destroy(led.cls);

err_2:
	unregister_chrdev(led.major, "devmemhqb drv");

err_1:
	return -1;
}

static void __exit devmemhqb_exit(void)
{
	// 卸载驱动内容
	// 1、释放设备文件
	device_destroy(led.cls, led.devno);

	// 2、释放设备文件结构体
	class_destroy(led.cls);

	// 3、释放设备号
	unregister_chrdev(led.major,"devmemhqb drv");
}

module_init(devmemhqb_init);
module_exit(devmemhqb_exit);
MODULE_LICENSE("GPL");
#ifndef _DEVMEM_H_
#define _DEVMEM_H_

typedef enum {
	READ_BYTE_FROM_PADDR = 1,
	READ_WORD_FROM_PADDR = 2,
	READ_DWORD_FROM_PADDR = 3,
	READ_DOUBLE_DWORD_FROM_PADDR = 4,
} user_cmd_type;

#endif
#KERNLE_PATH=/home/ubuntu/code/kernel/linux-3.14

obj-m += devmem.o

all:
	make -j $(nproc) -C /root/hqb/projects/test/linux-image-bsk M=$(PWD) modules

clean:
	rm *.cmd *.o *.ko

install:
	#cp *.ko $(APP) /nfsdir/rootfs
	@modprobe -r $(TARGETNAME)
	@install $(MODULE) /lib/modules/$(shell uname -r)/kernel/drivers/hid
	@depmod
	@modprobe $(TARGETNAME)
  • 用户态程序
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include "virt_to_phy.h"
#include "devmem.h"

// 使用mmap从内核申请一段连续的空间
// int main() {
//     // 请求映射的字节数
//     size_t mapping_size = 1 * 1024 * 1024;  // 1 MiB

//     // 创建匿名映射,映射类型为私有、可写、可执行、连续(默认属性即可保证连续性)
//     void *mapped_memory = mmap(NULL, mapping_size,
//                                PROT_READ | PROT_WRITE | PROT_EXEC,
//                                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

//     if (mapped_memory == MAP_FAILED) {
//         perror("mmap");
//         return EXIT_FAILURE;
//     }

//     // ... 使用映射的内存 ...
//     printf("Successfully mapped %zu bytes of continuous memory at address %p.\n",
//            mapping_size, mapped_memory);

//     // 当不再需要映射时,释放内存
//     int munmap_result = munmap(mapped_memory, mapping_size);
//     if (munmap_result == -1) {
//         perror("munmap");
//         return EXIT_FAILURE;
//     }

//     printf("Memory unmapped successfully.\n");
//     return EXIT_SUCCESS;
// }

// malloc的内存转换出物理地址到内核态读取数据,再返回用户态
// int main() {
//     // 请求映射的字节数
//     size_t mapping_size = 1 * 1024 * 1024;  // 1 MiB

//     uintptr_t vaddr, paddr = 0;
//     pid_t pid= getpid();
//     vaddr = (uintptr_t)malloc(mapping_size);
//     if (vaddr == 0) {
//         printf("malloc failed.");
//         return EXIT_FAILURE;
//     }

//     if( mlock((void *)vaddr, mapping_size) == -1) {
//         perror("mlock");
//     }

//     // if (virt_to_phys_user(&paddr, pid, (uintptr_t)mapped_memory)) {
//     if (virt_to_phys_user(&paddr, pid, vaddr)) {
//         fprintf(stderr, "error: virt_to_phys_user\n");
//         return EXIT_FAILURE;
//     };

//     printf("Successfully malloc %zu bytes of continuous memory at address %p paddr %p.\n",
//            mapping_size, (void *)vaddr, (void *)paddr);

//     int fd = open("/dev/devmemhqb",O_WRONLY);
//     if (fd < 0) {
//         perror("open file failed");
//         return -1;
//     }

//     *(uint64_t *)vaddr = 0x55555555AAAAAAAA;

//     uint64_t value = (uint64_t)paddr;
//     int ret = ioctl(fd, READ_DOUBLE_DWORD_FROM_PADDR, &value);
//     if (ret != 0) {
//         printf("ioctl failed ret:%d.\n", ret);
//         return EXIT_FAILURE;
//     }

//     printf("ioctl ret:%d value: %#lx \n", ret, value);

//     return EXIT_SUCCESS;
// }

int main(int argc, char **argv) {
    uintptr_t paddr = 0;
    uint64_t cmd = 0;

    if (argc < 3) {
        printf("Usage: ./devmem cmd(1:one byte/2:two byte/3:four byte/4:eight byte) paddr\n");
        return EXIT_FAILURE;
    }
    cmd = strtoull(argv[1], NULL, 0);
    paddr = strtoull(argv[2], NULL, 0);

    printf("Read data at address paddr %p, cmd:%ld(1:one byte/2:two byte/3:four byte/4:eight byte).\n",
        (void *)paddr, cmd);
    if (cmd < READ_BYTE_FROM_PADDR  || cmd > READ_DOUBLE_DWORD_FROM_PADDR) {
        printf("cmd:%ld value is err.\n", cmd);
        return EXIT_FAILURE;
    }

    int fd = open("/dev/devmemhqb",O_WRONLY);
    if (fd < 0) {
        perror("open file failed");
        return -1;
    }

    uint64_t value = (uint64_t)paddr;
    int ret = ioctl(fd, cmd, &value);
    if (ret != 0) {
        printf("ioctl failed ret:%d.\n", ret);
        return EXIT_FAILURE;
    }

    printf("ioctl ret:%d value: %#lx \n", ret, value);

    return EXIT_SUCCESS;
}

#https://blog.csdn.net/men_wen/article/details/75272585
# https://www.jianshu.com/p/3cfe11e54293
.PHONY:clean

CC = gcc

CFLAGS = -Wall -g
CFLAGS += -I../virt_to_phy
CFLAGS += -I./devmem_drv
CPPFLAGS += -I../virt_to_phy
LDFLAGS += -L../virt_to_phy

OBJ = mmap_mm.o

mmap_mm:$(OBJ)
	$(CC) $(LDFLAGS) $^ -o $@ -lvirt_to_phy
%.o: %.c
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

clean:
	rm -f *.o mmap_mm
  • 地址转换
// gcc -g -c virt_to_phy.c
// ar rcs libvirt_to_phy.a virt_to_phy.o
#include <fcntl.h> /* open */
#include <stdint.h> /* uint64_t  */
#include <stdio.h> /* printf */
#include <stdlib.h> /* size_t */
#include <unistd.h> /* pread, sysconf */
#include<sys/mman.h>

typedef struct {
    uint64_t pfn : 55;
    unsigned int soft_dirty : 1;
    unsigned int file_page : 1;
    unsigned int swapped : 1;
    unsigned int present : 1;
} PagemapEntry;

/* Parse the pagemap entry for the given virtual address.
 *
 * @param[out] entry      the parsed entry
 * @param[in]  pagemap_fd file descriptor to an open /proc/pid/pagemap file
 * @param[in]  vaddr      virtual address to get entry for
 * @return 0 for success, 1 for failure
 */
int pagemap_get_entry(PagemapEntry *entry, int pagemap_fd, uintptr_t vaddr)
{
    size_t nread;
    ssize_t ret;
    uint64_t data;
    uintptr_t vpn;

    vpn = vaddr / sysconf(_SC_PAGE_SIZE);
    nread = 0;
    while (nread < sizeof(data)) {
        ret = pread(pagemap_fd, ((uint8_t*)&data) + nread, sizeof(data) - nread,
                vpn * sizeof(data) + nread);
        nread += ret;
        if (ret <= 0) {
            return 1;
        }
    }
    entry->pfn = data & (((uint64_t)1 << 55) - 1);
    entry->soft_dirty = (data >> 55) & 1;
    entry->file_page = (data >> 61) & 1;
    entry->swapped = (data >> 62) & 1;
    entry->present = (data >> 63) & 1;
    return 0;
}

/* Convert the given virtual address to physical using /proc/PID/pagemap.
 *
 * @param[out] paddr physical address
 * @param[in]  pid   process to convert for
 * @param[in] vaddr virtual address to get entry for
 * @return 0 for success, 1 for failure
 */
int virt_to_phys_user(uintptr_t *paddr, pid_t pid, uintptr_t vaddr)
{
    char pagemap_file[BUFSIZ];
    int pagemap_fd;

    snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%ju/pagemap", (uintmax_t)pid);
    pagemap_fd = open(pagemap_file, O_RDONLY);
    if (pagemap_fd < 0) {
        return 1;
    }
    PagemapEntry entry;
    if (pagemap_get_entry(&entry, pagemap_fd, vaddr)) {
        return 1;
    }
    close(pagemap_fd);
    *paddr = (entry.pfn * sysconf(_SC_PAGE_SIZE)) + (vaddr % sysconf(_SC_PAGE_SIZE));

    return 0;
}
#ifndef _VIRT_TO_PHY_
#define _VIRT_TO_PHY_

#include <stdint.h>

int virt_to_phys_user(uintptr_t *paddr, pid_t pid, uintptr_t vaddr);

#endif
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值