最近在看韦老师的视频,讲解了很多种字符设备的驱动写法。经过自己的研究之后,我发现还有另外一种写法,直接在应用层操作,省去了内核中的地址映射部分,使得用户可以在应用层直接操作LED。
      
mmap方法是把设备物理地址直接映射到用户空间的一种系统调用方法,他使得用户可以在应用层直接操作硬件设备,而不必在驱动里使用ioremap做地址映射。这在一定程度上实现了传说中的“零拷贝”技术。即,设备的数据不用经过内核的转存,再向应用层提交数据。由于设备物理地址直接映射到了用户空间,所以就相当于省去了内核的中间媒介,用户空间直接去操作硬件设备。
   
总结一下,mmap方法的用处是把设备(文件)内容直接映射到进程虚拟空间,通过对这个虚拟地址的读写修改,实现对设备(文件)的读写和修改,从而不必使用read、write等系统调用即可实现对设备的操作。

mmap的内核态函数为:
int (*mmap)(struct file *filp,struct vm_area_struct *vma)
结构体struct vm_area_struct *vma是我们在使用mmap系统调用的时候内核帮我们找到的虚拟地址区间,它的主要成员是:
    vma->vm_start:  映射后的用户态虚拟地址起始地址;
    vma->vm_end:   映射后的用户态虚拟地址结束地址;
    vma->vm_pgoff:   物理地址所在的页帧号,它的值由用户空间传进来的物理地址右移PAGE_SHIFT位得到,PAGE_SHIFT值为12,那么它右移12位就得到物理地址的页帧号(一页大小为4KB)。

下面是我写的内核驱动程序,在TQ2440开发板上运行:

#include
linux/kernel.h

#include linux/init.h

#include linux/module.h

#include linux/fs.h

#include linux/cdev.h

#include linux/mm.h

#include linux/device.h

#define DEV_NAME  mmapled

struct mmapled

{

dev_t devno;

struct cdev mcdev;

struct class *mmap_class;

};

struct
mmapled *mpt;

int
mmapled_open(struct inode *inode, struct file *filp)

{

printk(In kernel open,major =%d,minor =
%d\n,MAJOR(mpt->devno),MINOR(mpt->devno));

return 0;

}

int
mmapled_close(struct inode *inode,struct file *filp)

{

return 0;

}

/*
mmap系统调用函数 */

int
mmapled_mmap(struct file *filp,structvm_area_struct *vma)

{

int ret;

vma->vm_flags |= VM_RESERVED;

vma->vm_flags |= VM_IO;

vma->vm_page_prot =
pgprot_noncached(vma->vm_page_prot);

/* vma->vm_pgoff为用户层off, PAGE_SHIFT,即物理地址的页帧号,映射大小必为PAGE_SIZE整数倍
*/

ret
=remap_pfn_range(vma,vma->vm_start,vma->vm_pgoff,vma->vm_end
-vma->vm_start,vma->vm_page_prot);

if(ret)

{

printk(remap_pfn_range err!\n);

return -EAGAIN;

}

printk(In
%s,pgoff = %lx, start= %lx,end = %lx\n,__func__,vma->vm_pgoff,vma->vm_start,vma->vm_end);

return
0;

}

/* 文件操作结构体 */

struct
file_operations mmapled_fops =

{

.owner  
=THIS_MODULE,

.open    
=mmapled_open,

.release 
= mmapled_close,

.mmap 
= mmapled_mmap,

};

/* 驱动程序入口函数 */

int
mmapled_init(void)

{

int ret;

mpt = kzalloc(sizeof(struct
mmapled),GFP_KERNEL);

if(!mpt)

{

printk(kzalloc mpt err!\n);

return -ENOMEM;

}

/* 动态分配主设备号,起始次设备号为0 */

ret
= alloc_chrdev_region(mpt->devno,0,1,DEV_NAME);

if(ret)

{

printk(register chrdev err!\n);

kfree(mpt);

return ret;

}

/* 创建类,用于自动创建设备节点 */

mpt->mmap_class=class_create(THIS_MODULE,
DEV_NAME);

if(IS_ERR(mpt->mmap_class))

{

printk(KERN_ALERTabsmem_class create
failed.\n);

kfree(mpt);

unregister_chrdev_region(mpt->devno,1);

return -1;

}

cdev_init(mpt-mcdev,
mmapled_fops);

mpt->mcdev.owner=THIS_MODULE;

cdev_add(mpt->mcdev,mpt->devno,1);

/* 创建设备节点mmapled0 */

device_create(mpt->mmap_class,NULL,mpt->devno,NULL,mmapled%d,0);

return
0;

}

/* 驱动程序出口函数 */

void
mmapled_exit(void)

{

device_destroy(mpt->mmap_class,mpt->devno);

cdev_del(mpt->mcdev);

class_destroy(mpt->mmap_class);

unregister_chrdev_region(mpt->devno,1);

kfree(mpt);

}

module_init(mmapled_init);

module_exit(mmapled_exit);

MODULE_LICENSE(GPL);

用户态的mmap函数接口为:
  void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t
offset)
     函数参数的意义如下:
     addr:指定映射的起始地址,即进程虚拟空间的虚拟地址,人为的指定;通常设置为NULL,让内核帮我们去指定;
     len:  要映射的区间大小;
     prot: 映射区的保护方式,可以取以下值:
              PROC_EXEC:映射区可被执行;
              PROC_READ:映射区可被读取;
              PROC_WRITE:映射区可写;
              PROC_NONE:映射区不能存取。
     flags是映射区的特性,可以取以下值:
             MAP_SHARED:写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享;
             MAP_PRIVATE:对映射区的写入会产生一个映射区的复制(COPY_ON_WRITE),对此映射区的修改不会写入源文件;
    fd:由open函数返回的文件描述符;
    offset:文件开始处的偏移量,必须是分页大小的整数倍。
    函数返回值:映射得到的用户虚拟地址;

下面是我写的用户态的程序,供大家参考(在TQ2440开发板上运行,如果是其他开发板,可以参考原理图做一些修改):

/* 函数功能:实现4个LED灯的同时亮灭,间隔为1秒 */

#include
stdio.h

#include
string.h

#include
fcntl.h

#include
sys/mman.h

#define
DEV_NAME  /dev/mmapled0

int
main()

{

int fd,k;

void *start, *reg=NULL;

fd = open(DEV_NAME,O_RDWR);

if(fd&0)

{

printf(Open device err!\n);

return -1;

}

/*参数解释:

* NULL:映射到的内核虚拟地址,设置为NULL由内核决定

* 1024*4:映射大小,最小一页,必为页大小的整数倍

* 映射区的权限

* 对映射区的修改改变映射区的内容

* fd:open返回的文件描述符

* 物理地址,一个页的起始物理地址,它PAGE_SHIFT之后传给驱动的vma结构体的vm_pgoff

*/

/*0x56000000是LED等所在的GPIO口的BANK起始物理地址*/

/*start是得到的虚拟地址*/

start
=mmap(NULL,1024*4,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0x56000000);

if(start
== NULL)

{

printf(mmap err!\n);

return -1;

}

reg
= start + 0x10;                        //
GPBCON,控制寄存器

*(unsigned*)reg
= 0xfffc03ff;        // [17:10]清零

*(unsigned*)reg
|= 0x00015400;  // [17:10]=01010101,输出功能

reg
= start + 0x14;                       //
GPBDAT

/* 量灭k次,实现对LED的操作 */

k=25;

while(k--)

{

*(unsigned*)reg & = ~(0x1e0); //
[8:5], set 0,led on

sleep(1);

*(unsigned*)reg |= 0x1e0;    // [8:5], set 1,led off

sleep(1);

printf(k= %d\n,k);

}

/* 取消映射 */

munmap(start,1024*4);

close(fd);

return
0;

}

完。

稍微修改代码即可在jz2440上运行,需要代码的学员请留下邮箱,我们发给您。

字符设备驱动另一种写法—mmap方法操作LED的更多相关文章

  1. linux设备驱动第三篇:如何实现一个简单的字符设备驱动

    在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...

  2. linux设备驱动第三篇:如何写一个简单的字符设备驱动?

    在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...

  3. linux设备驱动第三篇:写一个简单的字符设备驱动

          在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分 ...

  4. 【转】深入浅出:Linux设备驱动之字符设备驱动

    深入浅出:Linux设备驱动之字符设备驱动 一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据 ...

  5. 蜕变成蝶~Linux设备驱动之字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流 ...

  6. 深入浅出:Linux设备驱动之字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流 ...

  7. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

  8. Linux驱动设计——字符设备驱动(一)

    Linux字符设别驱动结构 cdev结构体 struct cdev { struct kobject kobj; struct module *owner; const struct file_ope ...

  9. Linux内核分析(五)----字符设备驱动实现

    原文:Linux内核分析(五)----字符设备驱动实现 Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷, ...

随机推荐

  1. CodeForces - 462B Appleman and Card Game

    是一道简单题 将字母从个数多到小排序 然后 再按题目算法得到最多 但是注意 数据类型声明 money要为long long #include <iostream> #include < ...

  2. BZOJ1700: [Usaco2007 Jan]Problem Solving 解题

    每月m<=1000块钱,有n<=300道题,要按顺序做,每月做题要花钱,花钱要第一个月预付下个月立即再付一次,给出预付和再付求最少几个月做完题,第一个月不做. 神奇的DP..竟没想出来.. ...

  3. web移动端小tip,box-flex

    1,移动端页面 最重要的标签: <meta content="width=device-width,initial-scale=1.0,minimum-scale=1,maximum- ...

  4. equals() 和 hashCode()

    equals() 和 hashCode()这两个方法在java.lang.Object中,所有的类都可以继承这两个方法: 但是,这两个方法在Object类中的实现一般没什么用,所以你通常需要自己重载这 ...

  5. iOS 混合变换旋转 CGAffineTransform 的使用

    在ios 中, Core Graphics 提供了一系列的函数可以在一个变换的基础上做深层次的变换,如果做一个既要缩放又要旋转的变换,以下的方法比较实用. CGAffineTransformScale ...

  6. FF,chrome与IE的事件处理程序

    今天学习了js的事件处理程序,IE与FF,chrome,safari,opera的处理事件方法不同,FF,chrome,safari,opera支持addEventLisener,而addEventL ...

  7. SpringDataJPA入门2

    SpringDataJPA实体概述 JPA提供了一种简单高效的方式来管理Java对象(POJO)到关系型数据库的映射,此类Java对象成为JPA实体或简称实体.实体通常与底层数据库中的单个关系表相关联 ...

  8. guava缓存设置return null一直报错空指针

    guava缓存设置return null一直报错空指针 因为缓存不允许返回为空

  9. 【stl学习笔记】deques

    deque双向队列是一种双向开口的连续线性空间,可以高效的在头尾两端插入和删除元素,deque在接口上和vector非常相似.deque的实现比较复杂,内部会维护一个map(注意!不是STL中的map ...

  10. 《javascript设计模式》读书笔记二(封装和隐藏信息)

    1.为什么要封装和信息隐藏 做过编程的朋友们知道"耦合"这个词.事实上封装的效果就是为了解耦,让类和类之间没有太多的联系,防止某一天改动某一类的时候,产生"多米骨诺牌效应 ...