本文写了一个非常简易的remap驱动,该驱动将捕获用户调用的mmap(),并修改其分配内存映射关系:将对应虚拟地址映射到指定的物理地址。

remap.c

驱动模块源码,其中在remap_pfn_mmap函数中调用内核提供的函数remap_pfn_range来更改映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <linux/mm.h>
#include <linux/sched/signal.h>
#include <linux/export.h>
#include <linux/scatterlist.h>
#include <linux/debugfs.h>
#include <linux/highmem.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/bitmap.h>
#include <linux/sched.h>
#include <linux/sched/mm.h>
#include <linux/sched/task.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/in.h>
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>

#define MSG "read symbol"

char* symbol;



static int remap_pfn_mmap(struct file *file, struct vm_area_struct *vma)
{
printk(KERN_ALERT "np_remap \n");

unsigned long pfn,phy_addr;

int ret;

char * private_data = (char *)kzalloc(PAGE_SIZE, GFP_KERNEL);
if (private_data == NULL)
return -1;
strcpy(private_data, MSG);
printk(KERN_ALERT "private_data: %s\n",private_data);

if(find_vma(current->mm,private_data)==NULL)
printk(KERN_ALERT "在内核中,private_data's vm_area_struct is null\n");



if(vma==NULL)
{
printk(KERN_ALERT "vma==NULL\n");
return -1;
}
printk(KERN_ALERT "vma: 0x%lx\n",vma);
printk(KERN_ALERT "vm_start: 0x%lx\n",vma->vm_start);
printk(KERN_ALERT "virt_to_phys: 0x%lx\n",virt_to_phys(private_data));
int len=vma->vm_end - vma->vm_start;
printk(KERN_ALERT "len: %d\n",len);
printk(KERN_ALERT "vm_page_prot: %d\n",vma->vm_page_prot);

ret=remap_pfn_range( vma, vma->vm_start, virt_to_phys(private_data)>>PAGE_SHIFT,vma->vm_end - vma->vm_start, vma->vm_page_prot );


if (ret)
printk("%s: remap_pfn_range failed at [0x%lx 0x%lx]\n",
__func__, vma->vm_start, vma->vm_end);
else
printk(KERN_ALERT "remap addr:%s\n", vma->vm_start);

return ret;
}
static ssize_t symbol_ptr_read(struct file *file, char __user * ptr, size_t count, loff_t *ppos){
ptr=symbol;
return 0;
}
static const struct file_operations remap_pfn_fops = {
.owner = THIS_MODULE,
.mmap = remap_pfn_mmap,
.read = symbol_ptr_read,
};

static struct miscdevice remap_pfn_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "remap",
.fops = &remap_pfn_fops,
};



static int __init remap_pfn_init(void)
{
int ret = 0;


ret = misc_register(&remap_pfn_misc); // 注册一个misc设备
if (unlikely(ret)) {
pr_err("failed to register misc device!\n");
return -1;
}

return 0;

err:
return ret;
}


static void __exit remap_pfn_exit(void)
{
printk("%s,%d\r\n", __func__, __LINE__);
misc_deregister(&remap_pfn_misc);

return 0;
}

module_init(remap_pfn_init);
module_exit(remap_pfn_exit);

MODULE_LICENSE("GPL");


main.c

用户程序,使用自定义remap驱动小测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <inttypes.h>
#include <endian.h>
#include <byteswap.h>
#include <getopt.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <netdb.h>

#define PAGE_SIZE (4*1024)
#define BUF_SIZE getpagesize()
#define OFFSET (0)

int main(int argc, const char *argv[])
{
int fd;
char *addr = "not null";
fd = open("/dev/remap", O_RDWR);

printf("mmap before==>addr: %s\n", addr);
addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET);
printf("mmap after==>addr: %s\n", addr);

return 0;
}

makefile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#内核的源码路径, ?= 条件赋值, uname -r  得到内核的版本号
KERNELDIR = /lib/modules/$(shell uname -r)/build


# 生成模块
obj-m := remap.o

# := 立即赋值, 得到当前的绝对路径
PWD := $(shell pwd)


# -C 切换工作路径, make -c
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules* *.mod

.PHONY: modules clean

运行run!

加载驱动模块

ps: 上面三个文件需要在同一个目录下

1
2
3
4
5
6
7
8
9
10
# 转root用户,在文件目录下
# 编译remap驱动源码
make

# 将驱动模块加载到内核中
# 需要提前安装包:apt install module-init-tools
insmod remap.ko

# 将驱动模块从内核中移除
rmmod remap.ko

编译运行用户程序

1
2
3
# 转root用户,在文件目录下
gcc -o main main.c
./main