700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Linux 字符设备驱动开发基础(四)—— ioctl() 函数解析

Linux 字符设备驱动开发基础(四)—— ioctl() 函数解析

时间:2018-10-23 04:49:02

相关推荐

Linux 字符设备驱动开发基础(四)—— ioctl() 函数解析

解析完open、close、read、write 四个函数后,终于到我们的 ioctl() 函数了

一、 什么是ioctl

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。下面是其源代码定义:

函数名: ioctl

功 能: 控制I/O设备

用 法:int ioctl(int handle, int cmd,[int *argdx, int argcx]);

参数:fd是用户程序打开设备时使用open函数返回的文件标示符,cmd是用户程序对设备的控制命令,后面是一些补充参数,一般最多一个,这个参数的有无和cmd的意义相关。

include/asm/ioctl.h中定义的宏的注释:

[cpp]view plaincopy#define_IOC_NRBITS8//序数(number)字段的字位宽度,8bits #define_IOC_TYPEBITS8//幻数(type)字段的字位宽度,8bits #define_IOC_SIZEBITS14//大小(size)字段的字位宽度,14bits #define_IOC_DIRBITS2//方向(direction)字段的字位宽度,2bits #define_IOC_NRMASK((1<<_IOC_NRBITS)-1)//序数字段的掩码,0x000000FF #define_IOC_TYPEMASK((1<<_IOC_TYPEBITS)-1)//幻数字段的掩码,0x000000FF #define_IOC_SIZEMASK((1<<_IOC_SIZEBITS)-1)//大小字段的掩码,0x00003FFF #define_IOC_DIRMASK((1<<_IOC_DIRBITS)-1)//方向字段的掩码,0x00000003 #define_IOC_NRSHIFT0//序数字段在整个字段中的位移,0 #define_IOC_TYPESHIFT(_IOC_NRSHIFT+_IOC_NRBITS)//幻数字段的位移,8 #define_IOC_SIZESHIFT(_IOC_TYPESHIFT+_IOC_TYPEBITS)//大小字段的位移,16 #define_IOC_DIRSHIFT(_IOC_SIZESHIFT+_IOC_SIZEBITS)//方向字段的位移,30 #define_IOC_NONE0U//没有数据传输 #define_IOC_WRITE1U//向设备写入数据,驱动程序必须从用户空间读入数据 #define_IOC_READ2U//从设备中读取数据,驱动程序必须向用户空间写入数据 #define_IOC(dir,type,nr,size)\ (((dir)<<_IOC_DIRSHIFT)|\ ((type)<<_IOC_TYPESHIFT)|\ ((nr)<<_IOC_NRSHIFT)|\ ((size)<<_IOC_SIZESHIFT)) //构造无参数的命令编号 #define_IO(type,nr)_IOC(_IOC_NONE,(type),(nr),0) //构造从驱动程序中读取数据的命令编号 #define_IOR(type,nr,size)_IOC(_IOC_READ,(type),(nr),sizeof(size)) //用于向驱动程序写入数据命令 #define_IOW(type,nr,size)_IOC(_IOC_WRITE,(type),(nr),sizeof(size)) //用于双向传输 #define_IOWR(type,nr,size)_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size)) //从命令参数中解析出数据方向,即写进还是读出 #define_IOC_DIR(nr)(((nr)>>_IOC_DIRSHIFT)&_IOC_DIRMASK) //从命令参数中解析出幻数type #define_IOC_TYPE(nr)(((nr)>>_IOC_TYPESHIFT)&_IOC_TYPEMASK) //从命令参数中解析出序数number #define_IOC_NR(nr)(((nr)>>_IOC_NRSHIFT)&_IOC_NRMASK) //从命令参数中解析出用户数据大小 #define_IOC_SIZE(nr)(((nr)>>_IOC_SIZESHIFT)&_IOC_SIZEMASK) #defineIOC_IN(_IOC_WRITE<<_IOC_DIRSHIFT) #defineIOC_OUT(_IOC_READ<<_IOC_DIRSHIFT) #defineIOC_INOUT((_IOC_WRITE|_IOC_READ)<<_IOC_DIRSHIFT) #defineIOCSIZE_MASK(_IOC_SIZEMASK<<_IOC_SIZESHIFT) #defineIOCSIZE_SHIFT(_IOC_SIZESHIFT)

二、ioctl的必要性

如果不用ioctl的话,也可以实现对设备I/O通道的控制。例如,我们可以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令(一般在socket编程中常常这样做)。但是如果这样做的话,会导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码(cmd)告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情

三、 ioctl如何实现

在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情。因为设备都是特定的,这里也没法说。关键在于怎样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。

命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。所以在Linux核心中是这样定义一个命令码的:

| 设备类型 | 序列号 | 方向 |数据尺寸|

|-------------|----------|-------|------------|

| 8 bit | 8 bit | 2 bit | 8~14 bit |

|-------------|----------|-------|-------------|

这样一来,一个命令就变成了一个整数形式的命令码;但是命令码非常的不直观,所以Linux Kernel中提供了一些宏。这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。

比如上面展现的:

[cpp]view plaincopy//构造无参数的命令编号 #define_IO(type,nr)_IOC(_IOC_NONE,(type),(nr),0) //构造从驱动程序中读取数据的命令编号 #define_IOR(type,nr,size)_IOC(_IOC_READ,(type),(nr),sizeof(size)) //用于向驱动程序写入数据命令 #define_IOW(type,nr,size)_IOC(_IOC_WRITE,(type),(nr),sizeof(size)) //用于双向传输 #define_IOWR(type,nr,size)_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

我们在前面PWM驱动程序中也定义了命令宏:

[cpp]view plaincopy#defineMAGIC_NUMBER'k' #defineBEEP_ON_IO(MAGIC_NUMBER,0) #defineBEEP_OFF_IO(MAGIC_NUMBER,1) #defineBEEP_FREQ_IO(MAGIC_NUMBER,2)

这里必须要提一下的,就是"幻数"MAGIC_NUMBER,"幻数"是一个字母,数据长度也是8,用一个特定的字母来标明设备类型,这和用一个数字是一样的,只是更加利于记忆和理解。

四、 cmd参数如何得出

这里确实要说一说,cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过switch{case}结构进行相应的操作。

实例时刻,当然只是部分代码:

[cpp]view plaincopy#defineMAGIC_NUMBER'k' #defineBEEP_ON_IO(MAGIC_NUMBER,0) #defineBEEP_OFF_IO(MAGIC_NUMBER,1) #defineBEEP_FREQ_IO(MAGIC_NUMBER,2) #defineBEPP_IN_FREQ100000 staticvoidbeep_freq(unsignedlongarg) { writel(BEPP_IN_FREQ/arg,timer_base+TCNTB0); writel(BEPP_IN_FREQ/(2*arg),timer_base+TCMPB0); } staticlongbeep_ioctl(structfile*filep,unsignedintcmd,unsignedlongarg) { switch(cmd) { caseBEEP_ON: fs4412_beep_on(); break; caseBEEP_OFF: fs4412_beep_off(); break; caseBEEP_FREQ: beep_freq(arg); break; default: return-EINVAL; } } 测试代码如下:[cpp]view plaincopy#include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdio.h> #include<sys/ioctl.h> #defineMAGIC_NUMBER'k' #defineBEEP_ON_IO(MAGIC_NUMBER,0) #defineBEEP_OFF_IO(MAGIC_NUMBER,1) #defineBEEP_FREQ_IO(MAGIC_NUMBER,2) main() { intfd; fd=open("/dev/beep",O_RDWR); if(fd<0) { perror("openfail\n"); return; } ioctl(fd,BEEP_ON); sleep(6); ioctl(fd,BEEP_OFF); close(fd); }

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