Linux设备驱动工程师之路——高级字符设备驱动程序
K-Style
转载请注明来自于衡阳师范学院08电2 K-Style http://blog.csdn.net/ayangke,QQ:843308498 邮箱:yangkeemail@qq.com
高级字符设备驱动在简单字符驱动的基础上添加ioctl方法、阻塞非阻塞读写、poll方法、和自动创建设备文件的功能。
一、重要知识点
1.ioctl
ioctl命令:使用4个字段定义一个ioctl命令,包括
type: 幻数,一般使用一个字符定义,在内核中唯一。
number: 序数。
direction: 数据传输方向,当不涉及数据传输时,此字段无效。
size: 所涉及用户数据的大小,当不涉及数据传输时,此字段无效。
_IOC_NONE
_IOC_READ
_IOC_WRITE
“方向”字段的可能值。“读”和“写”是不同的位,可以用“OR”在一起指定读写。
_IOC(dir, type, size)
_IO(type,nr)
_IOR(type, nr, size)
_IOW(type, nr, size)
用于生产ioctl命令的宏
_IOC_DIR(cmd)
_IOC_TYPE(cmd)
_IOC_NR(cmd)
_IOC_SIZE(cmd)
用于解码ioctl命令的宏
intaccess_ok(int type, const void *addr, unsigned long size)
这个函数验证指向用户空间的指针是否可用,如果允许访问,access_ok返回非0值。
int put_user(datum, ptr)
int get_user(local, ptr)
int __put_user(datum, ptr)
int __get_user(local, ptr)
用于向(或从)用户空间保存(或获取)单个数据项的宏。传送的字节数目由sizeof(*ptr)决定。前两个要先调用access_ok,后两个(__put_user和__get_user)则假设access_ok已经被调用过了。
2.阻塞型I/O
typedef struct {/*…..*/} wait_queue_head_t
void init_waitqueue_head(wait_queue_head_t*queue)
DECLARE_WAIT_QUEUE_HEAD(queue)
预先定义的Linux内核等待队列类型。wait_queue_head_t类型必须显示地初始化,初始化方法可以在运行时调用init_waitqueue_head,或在编译时DECLARE_WAIT_QUEUE_HEAD。
void wait_event((wait_queue_head_t q, intcondition)
int wait_event_interruptible(wait_queue_head_tq, int condition)
int wait_event_timeout(wait_queue_head_t q,int condition, int time)
int wait_event_interruptible_timeout(wait_queue_head_tq, int condition, int time)
使进程在指定的队列上休眠,直到给定的condition值为真。
void wake_up(struct wait_queue **q)
void wake_up_interruptible(structwait_queue **q)
这些函数唤醒休眠在队列q上的进程。_interruptible形式的函数只能唤醒可中断的进程。在实践中约定做法是在使用wait_event时用wake_up,而在使用wait_event_interruptible时使用wake_up_interruptible。
3.poll方法
poll方法分两步处理,第一步调用poll_wait指定等待队列,第二步返回是否可操作的掩码。
POLLIN表示设备可读的掩码,POLLRDORM表示数据可读的掩码。POLLOUT表示设备可写的掩码,POLLWRNORM表示数据可读的掩码。一般同时返回POLLIN和POLLRDORM或者POLLOUT和POLLWRNORM。
4.select系统调用
原型为intselect(int mafdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set*restrict exceptfds, struct timeval *restrict tvptr)
返回值:就绪的描述符数,若超时则返回0,若出错则返回-1
void FD_ISSET(int fd, fd_set *fdset)
void FD_CLR(int fd, fd_set *fdset)
void FD_SET(int fd, fd_set *fdset)
void FD_ZERO(fd_set *fdset)
调用FD_ZERO将一个指定的fd_set变量的所有位设置为0。调用FD_SET设置一个fd_set变量指定位。调用FD_CLR则将一指定位清除。最后,调用FD_ISSET测试一指定位是否设置。
5.自动创建设备文件
struct class *class_create(struct module*owner, const char *name)
struct device *device_create(struct class*class, struct device *parent, dev_t devt, const char *fmt, ...)
通过这两个函数可以专门用来创建一个字符设备文件节点,class_create 第一个参数指定所有者,第二参数指定类得名字。class_device_create第一个参数指定第一个参数指定所要创建的设备所从属的类,第二个参数是这个设备的父设备,如果没有就指定为NULL,第三个参数是设备号,第四个参数是设备名称。
二、驱动代码
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/ioctl.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/device.h>
#define MEMDEV_MAJOR 251
#define MEMDEV_NUM 2
#define MEMDEV_SIZE 1024
//定义设备IOCTL命令
#define MEMDEV_IOC_MAGIC 'k'
#define MEMDEV_IOC_NR 2
#define MEMDEV_IOC_PRINT_IO(MEMDEV_IOC_MAGIC, 0)
#define MEMDEV_IOC_RD_IOR(MEMDEV_IOC_MAGIC, 1, int)
#define MEMDEV_IOC_WT_IOW(MEMDEV_IOC_MAGIC, 2, char)
struct mem_dev
{
unsignedint size;
char*data;
structsemaphore sem;
wait_queue_head_t inque;
};
static int mem_major = MEMDEV_MAJOR;
struct cdev mem_cdev;
struct mem_dev *mem_devp;
bool havedata = false;
static int mem_open(struct inode *inode,struct file *filp)
{
structmem_dev *dev;
unsignedint num;
printk("mem_open.\n");
num= MINOR(inode->i_rdev);//获得次设备号
if(num> (MEMDEV_NUM -1)) //检查次设备号有效性
return-ENODEV;
dev= &mem_devp[num];
filp->private_data= dev; //将设备结构保存为私有数据
return0;
}
static int mem_release(struct inode *inode,struct file *filp)
{
printk("mem_release.\n");
return0;
}
static ssize_t mem_read(struct file *filp,char __user *buf, size_t size, loff_t *ppos)
{
intret = 0;
structmem_dev *dev;
unsignedlong p;
unsignedlong count;
printk("mem_read.\n");
dev= filp->private_data;//获得设备结构
count= size;
p= *ppos;
//检查偏移量和数据大小的有效性
if(p> MEMDEV_SIZE)
return0;
if(count> (MEMDEV_SIZE-p))
count= MEMDEV_SIZE - p;
if(down_interruptible(&dev->sem))//锁定互斥信号量
return-ERESTARTSYS;
while(!havedata)
{
up(&dev->sem);
if(filp->f_flags& O_NONBLOCK)
return-EAGAIN;
printk("readyto go sleep");
if(wait_event_interruptible(dev->inque,havedata))//等待数据
return-ERESTARTSYS;
if(down_interruptible(&dev->sem))
return-ERESTARTSYS;
}
//读取数据到用户空间
if(copy_to_user(buf,dev->data+p, count)){
ret= -EFAULT;
printk("copyfrom user failed\n");
}
else{
*ppos+= count;
ret= count;
printk("read%ld bytes from dev\n", count);
havedata= false;//数据已经读出
}
up(&dev->sem);//解锁互斥信号量
returnret;
}
static ssize_t mem_write(struct file *filp,const char __user *buf, size_t size, loff_t *ppos)//注意:第二个参数和read方法不同
{
intret = 0;
structmem_dev *dev;
unsignedlong p;
unsignedlong count;
printk("mem_write.\n");
dev= filp->private_data;
count= size;
p= *ppos;
if(p> MEMDEV_SIZE)
return0;
if(count> (MEMDEV_SIZE-p))
count= MEMDEV_SIZE - p;
if(down_interruptible(&dev->sem))//锁定互斥信号量
return-ERESTARTSYS;
if(copy_from_user(dev->data+p,buf, count)){
ret= -EFAULT;
printk("copyfrom user failed\n");
}
else{
*ppos+= count;
ret= count;
printk("write%ld bytes to dev\n", count);
havedata= true;
wake_up_interruptible(&dev->inque);//唤醒等待数据的队列
}
up(&dev->sem);//解锁互斥信号量
returnret;
}
static loff_t mem_llseek(struct file *filp,loff_t offset, int whence)
{
intnewpos;
printk("mem_llseek.\n");
switch(whence)
{
case0://从文件头开始
newpos= offset;
break;
case1://从文件当前位置开始
newpos= filp->f_pos + offset;
break;
case2://从文件末尾开始
newpos= MEMDEV_SIZE - 1 + offset;
break;
default:
return-EINVAL;
}
if((newpos<0)|| (newpos>(MEMDEV_SIZE - 1)))
return-EINVAL;
filp->f_pos= newpos;
returnnewpos;
}
static int mem_ioctl(struct inode *inode,struct file *filp, unsigned int cmd, unsigned long arg)
{
interr = 0, ret = 0;
intioarg = 0;
charrdarg = '0';
//参数检查
if(_IOC_TYPE(cmd)!= MEMDEV_IOC_MAGIC)//参数类型检查
return-ENOTTY;
if(_IOC_NR(cmd)> MEMDEV_IOC_NR)//参数命令号检查
return-ENOTTY;
//用户空间指针有效性检查
if(_IOC_DIR(cmd)& _IOC_READ)
err= !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
elseif(_IOC_DIR(cmd) & _IOC_WRITE)
err= !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
if(err)
return-ENOTTY;
//根据命令执行操作
switch(cmd)
{
case MEMDEV_IOC_PRINT:
printk("memdevioctl print excuting...\n");
break;
caseMEMDEV_IOC_RD:
ioarg= 1024;
ret = __put_user(ioarg, (int *)arg);//用户空间向内核空间获得数据
printk("memdevioctl read excuting... \n");
break;
caseMEMDEV_IOC_WT:
ret= __get_user(rdarg, (char *)arg);//用户空间向内核空间传输数据
printk("memdevioctl write excuting... arg:%c\n", rdarg);
break;
default:
return-ENOTTY;
}
returnret;
}
static unsigned int mem_poll(struct file*filp, poll_table *wait)
{
structmem_dev *dev;
unsignedint mask = 0;
dev= filp->private_data;
if(down_interruptible(&dev->sem))//锁定互斥信号量
return-ERESTARTSYS;
poll_wait(filp,&dev->inque, wait);
if(havedata)
mask|= POLLIN | POLLRDNORM;//返回可读掩码
up(&dev->sem);//释放信号量
returnmask;
}
static const struct file_operationsmem_fops = {
.owner= THIS_MODULE,
.open= mem_open,
.write= mem_write,
.read= mem_read,
.release= mem_release,
.llseek= mem_llseek,
.ioctl= mem_ioctl,
.poll= mem_poll,
};
static int __init memdev_init(void)
{
intresult;
interr;
inti;
structclass *memdev_class;
//申请设备号
dev_tdevno = MKDEV(mem_major, 0);
if(mem_major)
result= register_chrdev_region(devno, MEMDEV_NUM, "memdev");//注意静态申请的dev_t参数和动态dev_t参数的区别
else{ //静态直接传变量,动态传变量指针
result= alloc_chrdev_region(&devno, 0, MEMDEV_NUM, "memdev");
mem_major= MAJOR(devno);
}
if(result< 0){
printk("can'tget major devno:%d\n", mem_major);
returnresult;
}
//注册设备驱动
cdev_init(&mem_cdev,&mem_fops);
mem_cdev.owner= THIS_MODULE;
err= cdev_add(&mem_cdev, MKDEV(mem_major, 0), MEMDEV_NUM);//如果有N个设备就要添加N个设备号
if(err)
printk("add cdev faild,err is%d\n", err);
//分配设备内存
mem_devp= kmalloc(MEMDEV_NUM*(sizeof(struct mem_dev)), GFP_KERNEL);
if(!mem_devp){
result = - ENOMEM;
goto fail_malloc;
}
memset(mem_devp,0, MEMDEV_NUM*(sizeof(struct mem_dev)));
for(i=0;i<MEMDEV_NUM; i++){
mem_devp[i].size= MEMDEV_SIZE;
mem_devp[i].data= kmalloc(MEMDEV_SIZE, GFP_KERNEL);
memset(mem_devp[i].data,0, MEMDEV_SIZE);
init_MUTEX(&mem_devp[i].sem);//初始化互斥锁
//初始化等待队列
init_waitqueue_head(&mem_devp[i].inque);
}
//自动创建设备文件
memdev_class= class_create(THIS_MODULE, "memdev_driver");
device_create(memdev_class,NULL, MKDEV(mem_major, 0), NULL, "memdev0");
returnresult;
fail_malloc:
unregister_chrdev_region(MKDEV(mem_major,0), MEMDEV_NUM);
returnresult;
}
static void memdev_exit(void)
{
cdev_del(&mem_cdev);
unregister_chrdev_region(MKDEV(mem_major,0), MEMDEV_NUM);//注意释放的设备号个数一定要和申请的设备号个数保存一致
//否则会导致设备号资源流失
printk("memdev_exit\n");
}
module_init(memdev_init);
module_exit(memdev_exit);
MODULE_AUTHOR("Y-Kee");
MODULE_LICENSE("GPL");
分享到:
相关推荐
Linux设备驱动程序学习(1)-字符设备驱动程序 - Linux设备驱动程序
Linux设备驱动程序学习(6)-高级字符驱动程序操作[(3)设备文件的访问控制] - Linux设备驱动程序
·Linux设备驱动程序学习(6)-高级字符驱动程序操作[(3)设备文件的访问控制] ·Linux设备驱动程序学习(7)-内核的数据类型 ·Linux设备驱动程序学习(9)-与硬件通信 ·Linux设备驱动程序学习(8)-分配内存 ...
Linux设备驱动程序开发简介 Linux设备驱动程序结构 Linux设备驱动程序加载方式 实验:编写一个字符设备驱动程序(LED或蜂鸣器) 分别用静态编译,模块动态加载方法实现加入内核
Linux设备驱动程序学习(4)-高级字符驱动程序操作[(1)ioctl and llseek] - Linux设备驱动程序
Linux 设备驱动程序是为特定的硬件提供给用户程序的 一组标准化接口,它隐藏了设备工作的细节。Linux 系统下 驱动程序是运行在内核态的,是和内核连接在一起的程序。 如果运行在用户态的应用程序想控制硬件设备,...
《LINUX设备驱动程序(第3版)》已针对Linux内核的2610版本彻底更新过了。内核的这个版本针对常见任务完成了合理化设计及相应的简化,如即插即用、利用sysfs文件系统和用户空间交互,以及标准总线上的多设备管理等等。...
Linux设备驱动程序学习(1)-字符设备驱动程序.pdf
一个虚拟的linux字符设备驱动实例,包括对/sys, 及/dev下设备文件的自动生成
Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动...
字符设备、块设备、TTY设备、I2C设备、LCD设备、音频设备、USB设备、网络设备、PCI设备等Linux设备驱动的架构和框架中各个复杂数据架构和函数的关系,并讲解了Linux驱动开发的大量实例,使读者能够独立开发各类Linux...
本文深入探讨了Linux设备驱动程序的内核机制,并提供了一个简单的字符设备驱动程序示例。通过源码示例,详细讲解了驱动程序注册与注销、文件操作函数的实现、设备号分配等关键概念和操作方法。 通过学习本文,您将...
本书是linux设备驱动程序开发领域的权威著作。全书基于2.6内核,不仅透彻讲解了基本概念和技术,更深入探讨了其他书没有涵盖或浅尝辄止的许多重要主题和关键难点,如pcmcia、i2c和usb等外部总线以及视频、音频、...
嵌入式Linux下字符型设备驱动程序的开发,驱动开发入门首选~
主要通过介绍字符设备scull(Simple Character Utility for Loading Localities,区域装载的简单字符工具)的驱动程序编写,来学习Linux设备驱动的基本知识。scull可以为真正的设备驱动程序提供样板。
第5章 字符设备驱动程序 第6章 串行设备驱动程序 第7章 输入设备驱动程序 第8章 I2C协议 第9章 PCMCIA和CF 第10章 PCI 第11章 USB 第12章 视频驱动程序 第13章 音频驱动程序...
python项目——DIY字符画.zip python项目——DIY字符画.zip python项目——DIY字符画.zip python项目——DIY字符画.zip python项目——DIY字符画.zip python项目——DIY字符画.zip python项目——DIY字符画.zip ...
第5章 字符设备驱动程序 第6章 串行设备驱动程序 第7章 输入设备驱动程序 第8章 I2C协议 第9章 PCMCIA和CF 第10章 PCI 第11章 USB 第12章 视频驱动程序 第13章 音频驱动程序 第14章 块设备驱动程序 第15章...