驱动之路-高级字符设备驱动程序

高级字符设备驱动在简单字符驱动的基础上添加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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#definememdev_major251
#definememdev_num2
#definememdev_size1024
//定义设备ioctl命令
#definememdev_ioc_magic'k'
#definememdev_ioc_nr2
#definememdev_ioc_print_io(memdev_ioc_magic,0)
#definememdev_ioc_rd_ior(memdev_ioc_magic,1,int)
#definememdev_ioc_wt_iow(memdev_ioc_magic,2,char)
structmem_dev
{
unsignedintsize;
char*data;
structsemaphoresem;
wait_queue_head_tinque;
};
staticintmem_major=memdev_major;
structcdevmem_cdev;
structmem_dev*mem_devp;
boolhavedata=false;
staticintmem_open(structinode*inode,structfile*filp)
{
structmem_dev*dev;
unsignedintnum;
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;
}
staticintmem_release(structinode*inode,structfile*filp)
{
printk(mem_release.\n);
return0;
}
staticssize_tmem_read(structfile*filp,char__user*buf,size_tsize,loff_t*ppos)
{
intret=0;
structmem_dev*dev;
unsignedlongp;
unsignedlongcount;
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(readytogosleep);
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(copyfromuserfailed\n);
}
else{
*ppos+=count;
ret=count;
printk(read%ldbytesfromdev\n,count);
havedata=false;//数据已经读出
}
up(&dev->sem);//解锁互斥信号量
returnret;
}
staticssize_tmem_write(structfile*filp,constchar__user*buf,size_tsize,loff_t*ppos)//注意:第二个参数和read方法不同
{
intret=0;
structmem_dev*dev;
unsignedlongp;
unsignedlongcount;
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(copyfromuserfailed\n);
}
else{
*ppos+=count;
ret=count;
printk(write%ldbytestodev\n,count);
havedata=true;
wake_up_interruptible(&dev->inque);//唤醒等待数据的队列
}
up(&dev->sem);//解锁互斥信号量
returnret;
}
staticloff_tmem_llseek(structfile*filp,loff_toffset,intwhence)
{
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(memdev_size-1)))
return-einval;
filp->f_pos=newpos;
returnnewpos;
}
staticintmem_ioctl(structinode*inode,structfile*filp,unsignedintcmd,unsignedlongarg)
{
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)
{
casememdev_ioc_print:
printk(memdevioctlprintexcuting...\n);
break;
casememdev_ioc_rd:
ioarg=1024;
ret=__put_user(ioarg,(int*)arg);//用户空间向内核空间获得数据
printk(memdevioctlreadexcuting...\n);
break;
casememdev_ioc_wt:
ret=__get_user(rdarg,(char*)arg);//用户空间向内核空间传输数据
printk(memdevioctlwriteexcuting...arg:%c\n,rdarg);
break;
default:
return-enotty;
}
returnret;
}
staticunsignedintmem_poll(structfile*filp,poll_table*wait)
{
structmem_dev*dev;
unsignedintmask=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;
}
staticconststructfile_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,
};
staticint__initmemdev_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'tgetmajordevno:%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(addcdevfaild,erris%d\n,err);
//分配设备内存
mem_devp=kmalloc(memdev_num*(sizeof(structmem_dev)),gfp_kernel);
if(!mem_devp){
result=-enomem;
gotofail_malloc;
}
memset(mem_devp,0,memdev_num*(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_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;
}
staticvoidmemdev_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);

两级触发器同步,就能消除亚稳态吗?
华硕ROG4工程机曝光:后盖配备菱形副屏 显示游戏及来电等场景的光效
数字IC与数字IC前端设计
Transphorm和贸泽电子宣布达成全球分销协议
基于LTC5548的新型微波混频器解决方案
驱动之路-高级字符设备驱动程序
电源额定值与测量技术的使用
超重车无法驶入高速公路,ETC将实现不停车收费称重功能
九阳净热一体机JYW-RF960,抑菌净热新选择
苹果因未具体说明iPhone手机具有防水性能遭到罚款
在硬件/软件接口调试
分布式光伏储能系统远程监控运维解决方案
计数器的LED显示电路
MAX8896 Dual buck converter po
NVIDIARTX2080Super显卡公版谍照曝光
各大厂商倾力开发,芯片立体堆叠技术应用在即
芯片是什么时候诞生的
机器人时代来了,语音交互准备好了吗?
5G领域,为什么美国干不过华为?
三星GalaxyS23系列国行版通过3C认证