Linux内核学习笔记 -20 动手实践linux内存映射基础 - 中(驱动程序源码分析)

进入源码,看内存映射具体的实现过程。 驱动程序源码map_driver.c

驱动程序大概有三部分组成,1-模块的装载卸载; 2-file_operations结构体和mmap函数;3-vm_operations_struct结构体和fault函数。首先是模块的装载函数,它所要完成的工作是两个,设备的注册,在内核中为设备申请一块内存。

设备的注册由register_chrdev这个函数来实现,这里需要指定设备的主设备号MAP_DEV_MAJOR, 设备的名称MAP_DEV_NAME, 还有它所链接的file_operations 结构 &mapdrvo_fops);

这里如果主设备号为零,该设备将自己分配一个主设备号,返回给result。如果返回值为0,表示分配成功;返回值为负表示设备注册失败。

接下来是申请内存,此处用的是vmalloc函数,vmalloc函数的特点是申请的内存区域在内核的线性地址是连续的,但物理地址不连续。

这里我们看到这里还有为申请到的页框的PageReserved标志位置位,这样做是高诉系统,该物理页框已经被我使用。

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 complete!");
	return 0;
}

下面是模块的卸载函数。在模块的卸载函数中,要做的正相反。

首先是清理PageReserved标志位,接着是通过vfree释放我们申请的vmalloc线性区的线性地址,最后是通过unregister_chrdev注销掉这个设备。

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);
	
}

 接下来介绍的是file_operations结构体和mmap函数。如下为驱动程序中定义的file_operations结构体

驱动模块中只实现了三个功能,owner是用来指向该驱动module结构的指针

open函数在这里,我们用它来打印了调用该模块进程的pid

mmap函数,

static struct file_operations mapdrov_fops = {
	.owner = THIS_MODULE,
	.mmap = mapdrv_mmap,
	.open = mapdrv_open,
};

include/linux/fs.h

 内核源码中有关file_operations结构体的完整定义如下,设备的读取,写入,保持等等这些操作,都是由存储在file_operations结构体中的这些函数指针来处理的,这些函数指针所指向的函数都需要我们在驱动模块中将其实现,在这里我们可以看到file_operations结构体中提到了许多的函数指针,包括read,write,mmap,open等。但是我们可以不全使用它们。对于那些指向未实现函数的只是可以简单的设置为空。操作系统将负责实现该功能。

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iopoll)(struct kiocb *kiocb, bool spin);
	int (*iterate) (struct file *, struct dir_context *);
	int (*iterate_shared) (struct file *, struct dir_context *);
	__poll_t (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	unsigned long mmap_supported_flags;
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);
	loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
				   struct file *file_out, loff_t pos_out,
				   loff_t len, unsigned int remap_flags);
	int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;

mmap系统调用的执行过程。一个用户进程调用了mmap系统调用之后,它需要依次执行下面那么多函数

首先用户进程在调用mmap系统调用之后,系统会为其在当前进程的虚拟地址空间中寻址一段连续的空闲地址,这是通过遍历vm_area_struct链表来实现。当找到了合适的这样的一段区间之后,会为其建立一个vm_area_struct结构,完成这些之后,该进程就有了一个专门用于mmap映射的虚拟内存区了。但是这样还不够,因为在进程页表中,这个区域的线性地址都没有对应的物理页框,接着系统会调用内核空间的系统调用函数mmap,也就是需要我们在file operations(f_op)结构中定义的这mmap,它将要完成对vm_area_struct结构中的虚拟地址,为它们建立其相应的页表项

建立页表项的方法有两种,一种是remap_fpn_range方法,一种叫fault的方法,两者的区别是remap_fpn_range方法是在内核函数mmap,即file_operations中要完成的mmap函数,它在被调用时一次性的为vm_area_struct结构中的这些线性地址建立页表项,所以这也就要求了这些页表项所要映射的物理地址是连续的。

后者fault函数则不同,它是在进程访问到这个映射空间中的虚拟地址时,发现虚拟地址的页表项为空,引起了缺页时才被调用,所以它更适合于今天,我们对vmalloc分配的这些不连续的物理地址来进行映射,所以进行将使用fault方法。

回到驱动程序中,如下的一些判断,都是对异常情况的判断。

首先MAPLEN是之前定义的宏,它用来表示在内核中申请的物理页框的大小,如果进程中用来映射的线性地址区大于我们申请的物理地址if(size > MAPLEN),那么就可能访问到一些不该访问的地方,所以我们需要防止这种情况的发生。

接着这个判断if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED)),如果我们的vm区是可写入的,它的标志位是可写入的,vm_flags用来保存进程对该虚存空间的访问权限。如果我们当前的vma是可写入的话VM_WRITE,那么他也就必须有另一个标志位VM_SHARED,也就是可共享的,如果没有可共享的标志位的话,那么写入操作就应当是非法的,我们也要把这种情况判断出来。

    vma->vm_flags |= VM_LOCKONFAULT; 给vm_flags标志位增加了一个标志位,叫做VM_LOCKONFAULT,它是用来锁住该区域所映射的这些物理页框的。让它不用被操作系统交换出去

整个mmap函数最重要的,其实是这里,“vm->vm_ops = &map_vm_ops”,它将我们在驱动模块中定义的vm_operations_struct结构的地址赋值给了当前vma的vm_ops指针,也就是说它用我们在驱动模块中定义的vm_operations结构体替换掉了当前进程vma中的这样一个结构体,为什么要这么做呢?想想刚才提到的fault方法,fault方法在建立页表的时候,他是在进程访问到线性地址产生缺页时才建立页表的,所以我们在mmap函数被调用的时候,可能我们的用户进程还没有进行访问,所以呢,我们建立页表还不用现在就立即建立,所以将这个任务交给它的下一环节,即vm_operations结构体。

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 swappted out,lock it */
	vma->vm_flags |= VM_LOCKONFAULT;
	if(offset == 0){
		vm->vm_ops = &map_vm_ops;
	}else {
		printk("offset out of range\n");
		return -ENXIO;
	}
	return 0;
}

下面介绍vmoperation_struct结构体和fault 函数,以下是驱动模块中定义的vm_operation_struct结构体

static struct vm_operations_struct map_vm_ops = {
	.open = map_vopen,
	.close = map_vclose,
	.fault = map_fault,
};

在内核源码中,include/linux/mm.h

如下,发现vm_operations_struct结构体其实也定义很多函数指针,可以有选择的实现它们。进行主要实现fault函数。vm_fault_t (*fault)(struct vm_fault *vmf);

关于fault函数的功能前面已经说过,它是用来为进程中用来映射的线性地址建立相应的页表项的,这里看出,传入的参数只是一个指针,在不同内核版本中,fault函数的表现形式有所不同。与nopage功能类似。

fault函数体,传入指向vm_fault结构体的指针,

 

struct vm_operations_struct {
	void (*open)(struct vm_area_struct * area);
	void (*close)(struct vm_area_struct * area);
	int (*split)(struct vm_area_struct * area, unsigned long addr);
	int (*mremap)(struct vm_area_struct * area);
	vm_fault_t (*fault)(struct vm_fault *vmf);
	vm_fault_t (*huge_fault)(struct vm_fault *vmf,
			enum page_entry_size pe_size);
	void (*map_pages)(struct vm_fault *vmf,
			pgoff_t start_pgoff, pgoff_t end_pgoff);
	unsigned long (*pagesize)(struct vm_area_struct * area);

	/* notification that a previously read-only page is about to become
	 * writable, if an error is returned it will cause a SIGBUS */
	vm_fault_t (*page_mkwrite)(struct vm_fault *vmf);

	/* same as page_mkwrite when using VM_PFNMAP|VM_MIXEDMAP */
	vm_fault_t (*pfn_mkwrite)(struct vm_fault *vmf);

	/* called by access_process_vm when get_user_pages() fails, typically
	 * for use by special VMAs that can switch between memory and hardware
	 */
	int (*access)(struct vm_area_struct *vma, unsigned long addr,
		      void *buf, int len, int write);

	/* Called by the /proc/PID/maps code to ask the vma whether it
	 * has a special name.  Returning non-NULL will also cause this
	 * vma to be dumped unconditionally. */
	const char *(*name)(struct vm_area_struct *vma);

#ifdef CONFIG_NUMA
	/*
	 * set_policy() op must add a reference to any non-NULL @new mempolicy
	 * to hold the policy upon return.  Caller should pass NULL @new to
	 * remove a policy and fall back to surrounding context--i.e. do not
	 * install a MPOL_DEFAULT policy, nor the task or system default
	 * mempolicy.
	 */
	int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);

	/*
	 * get_policy() op must add reference [mpol_get()] to any policy at
	 * (vma,addr) marked as MPOL_SHARED.  The shared policy infrastructure
	 * in mm/mempolicy.c will do this automatically.
	 * get_policy() must NOT add a ref if the policy at (vma,addr) is not
	 * marked as MPOL_SHARED. vma policies are protected by the mmap_sem.
	 * If no [shared/vma] mempolicy exists at the addr, get_policy() op
	 * must return NULL--i.e., do not "fallback" to task or system default
	 * policy.
	 */
	struct mempolicy *(*get_policy)(struct vm_area_struct *vma,
					unsigned long addr);
#endif
	/*
	 * Called by vm_normal_page() for special PTEs to find the
	 * page for @addr.  This is useful if the default behavior
	 * (using pte_page()) would not find the correct page.
	 */
	struct page *(*find_special_page)(struct vm_area_struct *vma,
					  unsigned long addr);
};

 下面是内核中关于vm_fault结构体的定义,它是用来存放与缺页相关的参数。

这里我们可以看到首先是映射区vma指针struct vm_area_struct *vma

unsigned long address 就是产生缺页的线性地址

pmd/pud 就是线性地址所对应的页目录项

pte就是它所对应的页表项

这个结构体中,最重要的是page字段配置字段,struct page *page; 我们在fault函数中我们要为这个用了映射的线性地址建立页表项,那么我们首先要找到它所对应的物理地址,找到这个物理地址后,我们取到它的页描述符,将它的页描述符填写到vm_fault结构体的page字段中,剩下的建立页表项的工作就可以交由操作系统自动完成了。

struct vm_fault {
	struct vm_area_struct *vma;	/* Target VMA */
	unsigned int flags;		/* FAULT_FLAG_xxx flags */
	gfp_t gfp_mask;			/* gfp mask to be used for allocations */
	pgoff_t pgoff;			/* Logical page offset based on vma */
	unsigned long address;		/* Faulting virtual address */
	pmd_t *pmd;			/* Pointer to pmd entry matching
					 * the 'address' */
	pud_t *pud;			/* Pointer to pud entry matching
					 * the 'address'
					 */
	pte_t orig_pte;			/* Value of PTE at the time of fault */

	struct page *cow_page;		/* Page handler may use for COW fault */
	struct mem_cgroup *memcg;	/* Cgroup cow_page belongs to */
	struct page *page;		/* ->fault handlers should return a
					 * page here, unless VM_FAULT_NOPAGE
					 * is set (which is also implied by
					 * VM_FAULT_ERROR).
					 */
	/* These three entries are valid only while holding ptl lock */
	pte_t *pte;			/* Pointer to pte entry matching
					 * the 'address'. NULL if the page
					 * table hasn't been allocated.
					 */
	spinlock_t *ptl;		/* Page table lock.
					 * Protects pte page table if 'pte'
					 * is not NULL, otherwise pmd.
					 */
	pgtable_t prealloc_pte;		/* Pre-allocated pte page table.
					 * vm_ops->map_pages() calls
					 * alloc_set_pte() from atomic context.
					 * do_fault_around() pre-allocates
					 * page table to avoid allocation from
					 * atomic context.
					 */
};

了解了这些之后,再回到驱动代码

fault函数在驱动中是这样一个名字“.fault = map_fault,”

static struct vm_operations_struct map_vm_ops = {
	.open = map_vopen,
	.close = map_vclose,
	.fault = map_fault,
};

 这个驱动程序的目的是将内核空间的线性地址所对应的物理地址映射到用户空间的某一个线性地址中。所以我们首先需要找到这些物理页框。那么我们怎么找呢?用到的方法就是这两个函数,vmalloc_to_pfn和vmalloc_to_page,顾名思义,这两个函数可以将内核vmalloc区线性地址所指向的那个物理页框它找到,我们想要的是页描述符,那么我们就用vmalloc_to_page函数,我们想要找到页帧号,那么我们就有vmalloc_to_pfn函数。

我们从头看这个fault函数,首先offset(offset = vmf->address-vmf->vma->vm_start;),它是在我们产生缺页的线性地址,它是在vma映射区中的偏移量,我们用当前的缺页地址“vmf->address"减去vma的起始地址"vmf->vma->vm_start”得到它的offse

接下啦的virt_strat,它是内核中的线性地址,我们用的是vmalloc_area,也就是我们在模块装载函数中申请到的那个线性区的起始地址让它加上每一页的偏移“(vmf->pgoff << PAGE_SHIFT)”,就得到了内核中每一页的线性地址 

pfn_start就是这些对应的内核中的线性地址所对应的那些物理页框的页帧号,这里我们使用的是vmalloc_to_pfn函数来找到它们

下面的if语句,就是对异常情况的判断, 比如当前进程的vma为空(“if((vmf->vma==NULL)||(vmalloc_area==NULL))”),申请到的内核中的内存区域。内核中的内存区域为空,那么肯定是一种异常情况,我们将它判断出来。

接着,这个判断“if(offset >= MAPLEN)”是如果我们当前产生缺页的线性地址,它超过了我们在内核中用于映射的物理页框的大小,这样的异常我们需要将它判断出来。

这里的page_ptr(page_ptr=vmalloc_area + offset),它是内核中的线性地址,起始跟virt_start的值是一样的这里我们用vmalloc_area,即它的起始地址加上offset每一页的偏移量得到的

这里的page(page=vmalloc_to_page(page_ptr);),因为我们要取得是页描述符,所以我们就用vmalloc_to_page函数,将内核中的线性地址,找到它所对应的物理页框的页描述符,

接着需要将找到的每一页(get_page(page);),物理页框增加它的引用次数,用get_page(page)这样一个函数来实现

最后(vmf->page = page;)我们将找到的页描述符填写到vmf的page字段中,就完成了我们对一个产生缺页的线性地址的页表项的建立

“printk("%s: map 0x%lx (0x%016lx) to 0x%lx, sie:0x%lx, page:%ld \n",__func__,virt_start,pfn_start << PAGE_SJIFT, vmf->address,PAGE_SIZE,vmf->pgoff);” 取出内核中的线性地址所对应的物理地址,还有映射后的进程中的线性地址,统统打印出来

static 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!\n");
		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, sie:0x%lx, page:%ld \n",__func__,virt_start,pfn_start << PAGE_SJIFT, vmf->address,PAGE_SIZE,vmf->pgoff);

	return 0;
}

我们在内核空间中申请了10个 页面大小的内存空间,主设备号是240,设备名称是mapnopage

#define MAP_PAGE_COUNT 10
#define MAPLEN (PAGE_SIZE*MAP_PAGE_COUNT)
#define MAP_DEV_MAJOR 240
#define MAP_DEV_NAME "mapnopage"

 在设备注册函数中,我们不光申请了10个页面的内存“vmalloc_area=vmalloc(MAPLEN)”,并且在这里“sprintf((char *)virt_addr,"test %d",i++);}” ,在每一页的内存中都写入了一个test字符串,从第一页开始依次是test1,test2,test3一直到test10

 

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 complete!");
	return 0;
}

 

 

 

 

 

#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,sturct vm_area_struct *vma);
static int mapdrv_open(struct inode *inode,struct file *file);
/* vm area nopage*/
static int map_fault(struct vm_fault *vmf);

static struct file_operations mapdrov_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 complete!");
	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 swappted out,lock it */
	vma->vm_flags |= VM_LOCKONFAULT;
	if(offset == 0){
		vm->vm_ops = &map_vm_ops;
	}else {
		printk("offset out of range\n");
		return -ENXIO;
	}
	return 0;
}
static int mapdrv_open(struct inode *inode, 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 from vm area */
void map_vclose(struct vm_area_struct *vma)
{
	printk("mapping vma is closed..\n");	
}

/* page fault handler */

static 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!\n");
		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, sie:0x%lx, page:%ld \n",__func__,virt_start,pfn_start << PAGE_SJIFT, vmf->address,PAGE_SIZE,vmf->pgoff);

	return 0;
}
module_init(mapdrv_init);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.项目代码功能经验证ok,确保稳定可靠运行。欢迎下载使用!在使用过程,如有问题或建议,请及时私信沟通。 2.主要针对各个计算机相关专业,包括计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师或企业员工使用。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。 5.期待你能在项目找到乐趣和灵感,也欢迎你的分享和反馈! 【资源说明】 Linux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zipLinux内核与设备驱动程序学习资料笔记+源码.zip Linux内核与设备驱动程序学习资料笔记+源码.zip Linux内核与设备驱动程序学习资料笔记+源码.zip
PWM-GPIO驱动程序Linux内核的一个驱动模块,用于控制嵌入式系统的GPIO引脚产生PWM信号。该驱动程序允许开发人员通过编程的方式来控制GPIO引脚的电平变化,从而产生不同占空比的PWM信号。 在Linux内核,PWM-GPIO驱动程序通过向用户空间提供了相应的接口来实现PWM信号的控制。开发人员可以通过打开相应的设备节点,并使用相应的系统调用函数来设置PWM的频率、占空比等参数,从而实现对GPIO引脚的PWM信号的控制。 驱动程序的核心部分是一个PWM子系统,它与GPIO子系统紧密集成。PWM子系统负责管理PWM信号的生成和控制,而GPIO子系统负责管理GPIO引脚的配置和操作。PWM-GPIO驱动程序在这两个子系统之间起着桥梁的作用。 PWM-GPIO驱动程序的实现方式与硬件平台相关,每个平台可能有不同的具体实现。在驱动程序的初始化过程,必须先配置GPIO引脚的功能为PWM模式,并将相应的寄存器映射到内核,以便能够通过对寄存器的操作来控制GPIO引脚。驱动程序还需要初始化PWM子系统,为每个GPIO引脚分配相应的PWM通道,并根据需求设置PWM的频率、占空比等参数。 通过PWM-GPIO驱动程序,开发人员可以方便地利用Linux内核的功能来实现对嵌入式系统GPIO引脚产生PWM信号的控制。这为开发PWM驱动、控制舵机、LED等应用提供了便捷的方式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值