700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > linux设备驱动程序的编程思路 Linux设备驱动工程师之路——高级字符设备驱动程序...

linux设备驱动程序的编程思路 Linux设备驱动工程师之路——高级字符设备驱动程序...

时间:2021-04-10 20:15:06

相关推荐

linux设备驱动程序的编程思路 Linux设备驱动工程师之路——高级字符设备驱动程序...

高级字符设备驱动在简单字符驱动的基础上添加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<0)||(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");

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。