字符设备驱动另一种写法—mmap方法操作LED
最近在看韦老师的视频,讲解了很多种字符设备的驱动写法。经过自己的研究之后,我发现还有另外一种写法,直接在应用层操作,省去了内核中的地址映射部分,使得用户可以在应用层直接操作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的更多相关文章
- linux设备驱动第三篇:如何实现一个简单的字符设备驱动
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...
- linux设备驱动第三篇:如何写一个简单的字符设备驱动?
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...
- linux设备驱动第三篇:写一个简单的字符设备驱动
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分 ...
- 【转】深入浅出:Linux设备驱动之字符设备驱动
深入浅出:Linux设备驱动之字符设备驱动 一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据 ...
- 蜕变成蝶~Linux设备驱动之字符设备驱动
一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流 ...
- 深入浅出:Linux设备驱动之字符设备驱动
一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流 ...
- Linux字符设备驱动框架
字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...
- Linux驱动设计——字符设备驱动(一)
Linux字符设别驱动结构 cdev结构体 struct cdev { struct kobject kobj; struct module *owner; const struct file_ope ...
- Linux内核分析(五)----字符设备驱动实现
原文:Linux内核分析(五)----字符设备驱动实现 Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷, ...
随机推荐
- CSU1030素数槽
Description 处于相邻的两个素数p和p + n之间的n - 1个连续的合数所组成的序列我们将其称为长度为n的素数槽.例如,‹24, 25, 26, 27, 28›是处于素数23和素数29之间 ...
- 转载:K-means聚类算法
转载地址:http://www.cnblogs.com/jerrylead/archive/2011/04/06/2006910.html K-means也是聚类算法中最简单的一种了,但是里面包含的思 ...
- 【POJ3254】Corn Fields(状压DP)
题意: 一个M x N矩阵里有很多格子,每个格子有两种状态,可以放牧和不可以放牧,可以放牧用1表示,否则用0表示,在这块牧场放牛,要求两个相邻的方格不能同时放牛,即牛与牛不能相邻.问有多少种放牛方案( ...
- 运行hadoop自带的计算圆周率异常
运行hadoop2 自带的圆周率计算方法时,报错,找了半天,原来是在配置hadoop临时目录时,没有给权限,找到配置的hadoop临时目录文件夹,修改权限即可 Application applicat ...
- 谈谈APP架构选型:React Native还是HBuilder
原文链接 导读:最近公司的一款新产品APP要进行研发,老大的意思想用H5来做混合APP以达到高效敏捷开发的目的.我自然就开始进行各种技术选型的调研,这里重点想说的是我最后挑选出的2款hybrid ap ...
- 关于用String Calender类 计算闰年的Demo
package cn.zmh.zuoye; import java.util.Calendar; public class StringRun { public static void main(St ...
- 信号量学习 & 共享内存同步
刚刚这篇文章学习了共享内存:http://www.cnblogs.com/charlesblc/p/6142139.html 里面也提到了共享内存,自己不进行同步,需要其他手段比如信号量来进行.那么现 ...
- OpenWrt 安装python-sqlite3失败
https://dev.openwrt.org/ticket/12239 #12239 reopened defect Sqlite3 missing in python 汇报人: dgspai@- ...
- HDU3926Hand in Hand(搜索 或 并查集)
Problem Description In order to get rid of Conan, Kaitou KID disguises himself as a teacher in the k ...
- Office EXCEL 如何为宏命令指定快捷键或者重新设置快捷键
1 工具-宏-宏,打开宏窗口 2 鼠标单击任意宏将其选中,点击宏选项,即可修改或为他增加快捷键. 3 注意,你直接在Visual Basci编辑器里面改是不行的,因为加了'的只是注释而已