实验要求:
1.在源码中查看file_operations和vm_operations_struct结构定义及其操作对象的方法,重点查看mmap方法fault方法的参选类型。
2.设备模块代码的编写和调试,重新编写file_operations结构中的mmap方法,和vm_operations_struct结构中的fault方法。
3.在用户态通过mmap方法完成对映射内存驱动设备的读和写操作访问。
实验原理:
描述在用户态对内存设备访问过程(read和write)中的函数执行过程及功能:
用户态open函数用于打开字符设备,并且调用内核模块中的mapdrv_open函数,完成打印进程号功能。
用户进程在调用mmap函数系统调用之后,系统为其在当前进程的虚拟地址空间中寻址一段连续的空闲地址,这是通过遍历vm_area_struct链表来实现。当找到了合适的这样的一段区间之后,会为其建立一个vm_area_struct结构,完成这些之后,该进程就有了一个专门用于mmap映射的虚拟内存区了。
但是这个区域的虚拟地址都没有对应的物理页框,接着系统会调用内核空间的系统调用函数mapdrv_mmap,也就是需要我们在file operations(f_op)结构体中定义的这个mmap,它将要完成对vm_area_struct结构中的虚拟地址建立其相应的页表项,传入file指针和vm_area_struct结构体指针这两个参数。mapdrv_mmap把file_operations结构体map_vm_ops的地址赋值给vma的vm_ops指针。建立页表项具体实现需要调用内核中定义的map_fault函数,它在进程访问到这个映射空间中的虚拟地址时,发现虚拟地址的页表项为空,引起了缺页时才被调用,建立对应页表项。
参考代码:
map_driver.h:
#include <asm/atomic.h>
#include <linux/semaphore.h>
#include <linux/cdev.h>
struct mapdrvo
{
struct cdev mapdev;
atomic_t usage;
};
map_driver.c:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <asm/io.h>
#include <linux/mman.h>
#define MAP_PAGE_COUNT 10
#define MAPLEN (PAGE_SIZE*MAP_PAGE_COUNT)
#define MAP_DEV_MAJOR 240
#define MAP_DEV_NAME "mapnopage"
extern struct mm_struct init_mm;
void map_vopen(struct vm_area_struct *vma);
void map_vclose(struct vm_area_struct *vma);
/*device mmap */
static int mapdrv_mmap(struct file *file, struct vm_area_struct *vma);
static int mapdrv_open(struct inode *inode, struct file *file);
/* vm area nopage */
int map_fault(struct vm_fault *vmf);
static struct file_operations mapdrvo_fops = {
.owner = THIS_MODULE,
.mmap = mapdrv_mmap,
.open = mapdrv_open,
};
static struct vm_operations_struct map_vm_ops = {
.open = map_vopen,
.close = map_vclose,
.fault = map_fault,
};
static char *vmalloc_area = NULL;
MODULE_LICENSE("GPL");
static int __init mapdrv_init(void)
{
int result;
unsigned long virt_addr;
int i = 1;
result=register_chrdev(MAP_DEV_MAJOR,MAP_DEV_NAME,&mapdrvo_fops);
if(result<0){
return result;
}
vmalloc_area=vmalloc(MAPLEN);
virt_addr = (unsigned long)vmalloc_area;
for(virt_addr = (unsigned long)vmalloc_area; virt_addr < (unsigned long)vmalloc_area + MAPLEN; virt_addr += PAGE_SIZE)
{
SetPageReserved(vmalloc_to_page((void *)virt_addr));
sprintf((char *)virt_addr, "test %d",i++);
}
/* printk("vmalloc_area at 0x%lx (phys 0x%lx)\n",(unsigned long)vmalloc_area,(unsigned long)vmalloc_to_pfn((void *)vmalloc_area) << PAGE_SHIFT); */
printk("vmalloc area apply complate!");
return 0;
}
static void __exit mapdrv_exit(void)
{
unsigned long virt_addr;
/* unreserve all pages */
for(virt_addr = (unsigned long)vmalloc_area; virt_addr < ( unsigned long)vmalloc_area + MAPLEN; virt_addr += PAGE_SIZE)
{
ClearPageReserved(vmalloc_to_page((void *)virt_addr));
}
/* and free the two areas */
if (vmalloc_area)
vfree(vmalloc_area);
unregister_chrdev(MAP_DEV_MAJOR,MAP_DEV_NAME);
}
static int mapdrv_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
unsigned long size = vma->vm_end - vma->vm_start;
if (size > MAPLEN) {
printk("size too big\n");
return -ENXIO;
}
/* only support shared mappings. */
if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED)) {
printk("writeable mappings must be shared, rejecting\n");
return -EINVAL;
}
/* do not want to have this area swapped out, lock it */
vma->vm_flags |= VM_LOCKONFAULT;
if (offset == 0) {
vma->vm_ops = &map_vm_ops;
} else {
printk("offset out of range\n");
return -ENXIO;
}
return 0;
}
static int mapdrv_open(struct inode *inoe, struct file *file)
{
printk("process: %s (%d)\n", current->comm, current->pid);
return 0;
}
/* open handler for vm area */
void map_vopen(struct vm_area_struct *vma)
{
printk("mapping vma is opened..\n");
}
/* close handler form vm area */
void map_vclose(struct vm_area_struct *vma)
{
printk("mapping vma is closed..\n");
}
/* page fault handler */
int map_fault(struct vm_fault *vmf)
{
struct page *page;
void *page_ptr;
unsigned long offset, virt_start, pfn_start;
offset = vmf->address-vmf->vma->vm_start;
virt_start = (unsigned long)vmalloc_area + (unsigned long)(vmf->pgoff << PAGE_SHIFT);
pfn_start = (unsigned long)vmalloc_to_pfn((void *)virt_start);
printk("\n");
/*printk("%-25s %d\n","7)PAGE_SHIFT",PAGE_SHIFT);*/
page_ptr=NULL;
if((vmf->vma==NULL)||(vmalloc_area==NULL)){
printk("return VM_FAULT_SIGBUS!\n");
return VM_FAULT_SIGBUS;
}
if(offset >=MAPLEN){
printk("return VM_FAULT_SIGBUS!");
return VM_FAULT_SIGBUS;
}
page_ptr=vmalloc_area + offset;
page=vmalloc_to_page(page_ptr);
get_page(page);
vmf->page=page;
printk("%s: map 0x%lx (0x%016lx) to 0x%lx , size: 0x%lx, page:%ld \n", __func__, virt_start, pfn_start << PAGE_SHIFT, vmf->address,PAGE_SIZE,vmf->pgoff);
return 0;
}
module_init(mapdrv_init);
module_exit(mapdrv_exit);
map_driver.c对应Makefile文件:
ifneq ($(KERNELRELEASE),)
obj-m += map_driver.o
else
PWD := $(shell pwd)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
@rm -rf *.o *.mod.c *.mod.o *.ko *.order *.symvers .*.cmd .tmp_versions
endif
maptest_read.c:(读函数)
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#define LEN (10*4096)
int main(void)
{
int fd,loop;
char *vadr;
if ((fd = open("/dev/mapnopage", O_RDWR)) < 0) {
return 0;
}
vadr = mmap(0, LEN, PROT_READ, MAP_PRIVATE | MAP_LOCKED, fd, 0);
for(loop=0;loop<10;loop++){
printf("[%-10s----%lx]\n",vadr+4096*loop,vadr+4096*loop);
}
while(1)
{
sleep(1);
}
}
maptest_write.c:(写函数)
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#define LEN (10*4096)
int main(void)
{
int fd;
char *vadr;
if ((fd = open("/dev/mapnopage", O_RDWR)) < 0) {
return 0;
}
vadr = mmap(0, LEN, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, 0);
sprintf(vadr, "write from userspace");
while(1)
{
sleep(1);
}
return 0;
}