Linux高级字符设备驱动.docx

上传人:b****7 文档编号:23579269 上传时间:2023-05-18 格式:DOCX 页数:22 大小:60KB
下载 相关 举报
Linux高级字符设备驱动.docx_第1页
第1页 / 共22页
Linux高级字符设备驱动.docx_第2页
第2页 / 共22页
Linux高级字符设备驱动.docx_第3页
第3页 / 共22页
Linux高级字符设备驱动.docx_第4页
第4页 / 共22页
Linux高级字符设备驱动.docx_第5页
第5页 / 共22页
点击查看更多>>
下载资源
资源描述

Linux高级字符设备驱动.docx

《Linux高级字符设备驱动.docx》由会员分享,可在线阅读,更多相关《Linux高级字符设备驱动.docx(22页珍藏版)》请在冰豆网上搜索。

Linux高级字符设备驱动.docx

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))

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 总结汇报 > 学习总结

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1