Linux高级字符设备驱动.docx
《Linux高级字符设备驱动.docx》由会员分享,可在线阅读,更多相关《Linux高级字符设备驱动.docx(22页珍藏版)》请在冰豆网上搜索。
Linux高级字符设备驱动
Linux高级字符设备驱动
设备Ioctl控制
1.Ioctl用来做什么?
大部分驱动除了需要具备读写设备的能力外,还需要具备对硬件控制的能力。
例如,要求设备报告错误信息,改变波特率,这些操作常常通过ioctl方法来实现。
1.1用户使用方法
在用户空间,使用ioctl系统调用来控制设备,原型如下:
intioctl(intfd,unsignedlongcmd,...)
原型中的点表示这是一个可选的参数,存在与否依赖于控制命令(第2个参数)是否涉及到与设备的数据交互。
1.2驱动ioctl方法
ioctl驱动方法有和用户空间版本不同的原型:
int(*ioctl)(structinode*inode,structfile*filp,unsignedintcmd,unsignedlongarg)
cmd参数从用户空间传下来,可选的参数arg以一个unsignedlong的形式传递,不管它是一个整数或一个指针。
如果cmd命令不涉及数据传输,则第3个参数arg的值无任何意义。
2.Ioctl实现
2.1实现Ioctl方法的步骤:
1) 定义命令
2.) 实现命令
2.2定义命令
在编写ioctl代码之前,首先需要定义命令。
为了防止对错误的设备使用正确的命令,命令号应该在系统范围内是唯一的。
ioctl命令编码被划分为几个位段,include/asm/ioctl.h中定义了这些位字段:
类型(幻数),序号,传送方向,参数的大小。
Documentation/ioctl-number.txt文件中罗列了在内核中已经使用了的幻数。
定义ioctl命令的正确方法是使用4个位段,这个列表中介绍的符号定义在中:
1) Type
幻数(类型):
表明哪个设备的命令,在参考了ioctlnumber.txt之后选出,8位宽。
2) Number
序号,表明设备命令中的第几个,8位宽
3) Direction
数据传送的方向,可能的值是_IOC_NONE(没有数据传输),_IOC_READ,_IOC_WRITE。
数据传送是从应用程序的观点来看待的,_IOC_READ意思是从设备读。
4) Size
用户数据的大小。
(13/14位宽,视处理器而定)
内核提供了下列宏来帮助定义命令:
1) _IO(type,nr)
没有参数的命令
2) _IOR(type,nr,datatype)
从驱动中读数据
3) _IOW(type,nr,datatype)
写数据到驱动
4) _IOWR(type,nr,datatype)
双向传送,type和number成员作为参数被传递。
定义命令(范例)
#defineMEM_IOC_MAGIC‘m’//定义幻数
#defineMEM_IOCSET
_IOW(MEM_IOC_MAGIC,0,int)
#defineMEM_IOCGQSET
_IOR(MEM_IOC_MAGIC,1,int)
2.3Ioctl函数实现
定义好了命令,下一步就是要实现Ioctl函数了,Ioctl函数的实现包括如下3个技术环节:
1) 返回值
2)参数使用
3)命令操作
2.3.1Ioctl函数实现(返回值)
Ioctl函数的实现通常是根据命令执行的一个switch语句。
但是,当命令号不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(“非法参数”)。
2.3..2Ioctl函数实现(参数)
如何使用Ioctl中的参数?
如果是一个整数,可以直接使用。
如果是指针,我们必须确保这个用户地址是有效的,因此使用前需进行正确的检查。
2.3.3Ioctl函数实现(参数检查)
不需要检测:
1) copy_from_user
2) copy_to_user
3) get_user
4) put_user
需要检测:
1) __get_user
2) __put_user
intaccess_ok(inttype,constvoid*addr,unsignedlongsize)
第一个参数是VERIFY_READ或者VERIFY_WRITE,用来表明是读用户内存还是写用户内存。
addr参数是要操作的用户内存地址,size是操作的长度。
如果ioctl需要从用户空间读一个整数,那么size参数等于sizeof(int)。
access_ok返回一个布尔值:
1是成功(存取没问题)和0是失败(存取有问题),如果该函数返回失败,则Ioctl应当返回–EFAULT。
3.Ioctl函数实现范例
if(_IOC_DIR(cmd)&_IOC_READ)
err=!
access_ok(VERIFY_WRITE,(void__user*)arg,_IOC_SIZE(cmd)); //why_IOC_READ对应VERIFY_WRITE?
?
?
elseif(_IOC_DIR(cmd)&_IOC_WRITE)
err=!
access_ok(VERIFY_READ,(void__user*)arg,_IOC_SIZE(cmd));
if(err)
return-EFAULT;
switch(cmd)
{
caseMEM_IOCSQUANTUM:
/*Set:
argpointstothevalue*/
retval=__get_user(scull_quantum,(int*)arg);
break;
caseMEM_IOCGQUANTUM:
/*Get:
argispointertoresult*/
retval=__put_user(scull_quantum,(int*)arg);
break;
default:
return–EINVAL;
}
背景:
阅读新闻
Linux高级字符设备驱动
内核等待队列
[日期:
2012-05-17]
来源:
Linux社区 作者:
yinjiabin
[字体:
大 中 小]
在Linux驱动程序设计中,可以使用等待队列来实现进程的阻塞,等待队列可看作保存进程的容器,在阻塞进程时,将进程放入等待队列,当唤醒进程时,从等待等列中取出进程。
Linux2.6内核提供了如下关于等待队列的操作:
1、定义等待队列
wait_queue_head_tmy_queue
2、初始化等待队列
init_waitqueue_head(&my_queue)
3、定义并初始化等待队列
DECLARE_WAIT_QUEUE_HEAD(my_queue)
4、有条件睡眠
1)wait_event(queue,condition)
当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂在queue参数所指定的等待队列上。
2)wait_event_interruptible(queue,condition)
当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。
3)intwait_event_killable(wait_queue_tqueue,condition)
当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_KILLABLE的睡眠,并挂在queue参数所指定的等待队列上。
5、无条件睡眠(老版本,建议不再使用)
1)sleep_on(wait_queue_head_t*q)
让进程进入不可中断的睡眠,并把它放入等待队列q。
2)interruptible_sleep_on(wait_queue_head_t*q)
让进程进入可中断的睡眠,并把它放入等待队列q。
6、从等待队列中唤醒进程
1)wake_up(wait_queue_t*q)
从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE的所有进程。
2)wake_up_interruptible(wait_queue_t*q)
从等待队列q中唤醒状态为TASK_INTERRUPTIBLE的进程。
背景:
阅读新闻
Linux高级字符设备驱动
阻塞型字符设备驱动
[日期:
2012-05-17]
来源:
Linux社区 作者:
yinjiabin
[字体:
大 中 小]
1、阻塞型字符设备驱动的功能
当一个设备无法立刻满足用户的读写请求时应当如何处理?
例如:
调用read时没有数据可读,但以后可能会有;或者一个进程试图向设备写入数据,但是设备暂时没有准备好接收数据。
应用程序通常不关心这种问题,应用程序只是调用read或write并得到返回值。
驱动程序应当(缺省地)阻塞进程,使它进入睡眠,直到请求可以得到满足。
2、阻塞方式
1)在阻塞型驱动程序中,Read实现方式如下:
如果进程调用read,但设备没有数据或数据不足,进程阻塞。
当新数据到达后,唤醒被阻塞进程。
2)在阻塞型驱动程序中,Write实现方式如下:
如果进程调用了write,但设备没有足够的空间供其写入数据,进程阻塞。
当设备中的数据被读走后,缓冲区中空出部分空间,则唤醒进程。
3、非阻塞方式
阻塞方式是文件读写操作的默认方式,但应用程序员可通过使用O_NONBLOCK标志来人为的设置读写操作为非阻塞方式(该标志定义在中,在打开文件时指定)。
如果设置了O_NONBLOCK标志,read和write的行为是不同的。
如果进程在没有数据就绪时调用了read,或者在缓冲区没有空间时调用了write,系统只是简单地返回-EAGAIN,而不会阻塞进程。
4、实例分析
程序实现的功能当进程读文件时,没有数据可读,则该进程阻塞。
1)memdev.h源代码
#ifndef_MEMDEV_H_
#define_MEMDEV_H_
#ifndefMEMDEV_MAJOR
#defineMEMDEV_MAJOR0 /*预设的mem的主设备号*/
#endif
#ifndefMEMDEV_NR_DEVS
#defineMEMDEV_NR_DEVS2 /*设备数*/
#endif
#ifndefMEMDEV_SIZE
#defineMEMDEV_SIZE4096
#endif
/*mem设备描述结构体*/
structmem_dev
{
char*data;
unsignedlongsize;
wait_queue_head_tinq;
};
#endif/*_MEMDEV_H_*/
2)阻塞型字符驱动memdev.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"memdev.h"
staticmem_major=MEMDEV_MAJOR;
boolhave_data=false;/*表明设备有足够的数据可供读*/
module_param(mem_major,int,S_IRUGO);
structmem_dev*mem_devp;/*设备结构体制针*/
structcdevcdev;
/*文件打开函数*/
intmem_open(structinode*inode,structfile*filp)
{
structmem_dev*dev;
/*获取次设备号*/
intnum=MINOR(inode->i_rdev);
if(num>=MEMDEV_NR_DEVS)
return-ENODEV;
dev=&mem_devp[num];
/*将设备描述结构指针赋值给文件私有数据指针*/
filp->private_data=dev;
return0;
}
/*release函数*/
intmem_release(structinode*inode,structfile*filp)
{
return0;
}
/*读函数*/
staticssize_tmem_read(structfile*filp,char__user*buf,size_tsize,loff_t*ppos)
{
unsignedlongp= *ppos;
unsignedintcount=size;
intret=0;
structmem_dev*dev=filp->private_data;/*获得设备结构体指针*/
/*判断读位置是否有效*/
if(p>=MEMDEV_SIZE)
return0;
if(count>MEMDEV_SIZE-p)
count=MEMDEV_SIZE-p;
while(!
have_data)/* 没有数据可读,考虑为什么不用if,而用while。
答:
为了排除由于中断唤醒等待队列,但此时并没有数据可读,故次用while和interruptible配合的原因*/
{
/*判断用户是否设置了非阻塞方式*/
if(filp->f_flags&O_NONBLOCK)
return-EAGAIN;/*设置了非阻塞方式*/
/*当设置了阻塞方式*/
wait_event_interruptible(dev->inq,have_data);/**当have_data为真时,立即返回,否则让进程进入TASK_KILL
的睡眠并挂在dev->inq队列上*/
}
/*读数据到用户空间*/
if(copy_to_user(buf,(void*)(dev->data+p),count))
{
ret= -EFAULT;
}
else
{
*ppos+=count;
ret=count;
printk(KERN_INFO"read%dbytes(s)from%d\n",count,p);
}
have_data=false;/*表明不再有数据可读*/
returnret;
}
/*写函数*/
staticssize_tmem_write(structfile*filp,constchar__user*buf,size_tsize,loff_t*ppos)
{
unsignedlongp= *ppos;
unsignedintcount=size;
intret=0;
structmem_dev*dev=filp->private_data;/*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if(p>=MEMDEV_SIZE)
return0;
if(count>MEMDEV_SIZE-p)
count=MEMDEV_SIZE-p;
/*从用户空间写数据*/
if(copy_from_user(dev->data+p,buf,count))
ret= -EFAULT;
else
{
*ppos+=count;
ret=count;
printk(KERN_INFO"written%dbytes(s)from%d\n",count,p);
}
have_data=true;/*有新的数据可读*/
/*唤醒读进程*/
wake_up(&(dev->inq));
returnret;
}
/*seek函数*/
staticloff_tmem_llseek(structfile*filp,loff_toffset,intwhence)
{
loff_tnewpos;
switch(whence){
case0:
/*SEEK_SET*/
newpos=offset;
break;
case1:
/*SEEK_CUR*/
newpos=filp->f_pos+offset;
break;
case2:
/*SEEK_END*/
newpos=MEMDEV_SIZE-1+offset;
break;
default:
/*can'thappen*/
return-EINVAL;
}
if((newpos<0)||(newpos>MEMDEV_SIZE))
return-EINVAL;
filp->f_pos=newpos;
returnnewpos;
}
/*?
文件操作结构体*/
staticconststructfile_operationsmem_fops=
{
.owner=THIS_MODULE,
.llseek=mem_llseek,
.read=mem_read,
.write=mem_write,
.open=mem_open,
.release=mem_release,
};
/*设备驱动模块加载函数*/
staticintmemdev_init(void)
{
intresult;
inti;
dev_tdevno=MKDEV(mem_major,0);
/*静态申请设备号*/
if(mem_major)
result=register_chrdev_region(devno,2,"memdev");
else /*动态分配设备号*/
{
result=alloc_chrdev_region(&devno,0,2,"memdev");
mem_major=MAJOR(devno);
}
if(result<0)
returnresult;
/*初始化cdev结构*/
cdev_init(&cdev,&mem_fops);
cdev.owner=THIS_MODULE;
cdev.ops=&mem_fops;
/*注册字符设备*/
cdev_add(&cdev,MKDEV(mem_major,0),MEMDEV_NR_DEVS);
/*为设备描述结构分配内存*/
mem_devp=kmalloc(MEMDEV_NR_DEVS*sizeof(structmem_dev),GFP_KERNEL);
if(!
mem_devp) /*申请失败*/
{
result= -ENOMEM;
gotofail_malloc;
}
memset(mem_devp,0,sizeof(structmem_dev));
/*为设备分配内存*/
for(i=0;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_waitqueue_head(&(mem_devp[i].inq))