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
在CODE上查看代码片派生到我的代码片
  1. #define   _IOC_NRBITS        8                               //序数(number)字段的字位宽度,8bits  
  2. #define   _IOC_TYPEBITS      8                               //幻数(type)字段的字位宽度,8bits  
  3. #define   _IOC_SIZEBITS      14                              //大小(size)字段的字位宽度,14bits  
  4. #define   _IOC_DIRBITS       2                               //方向(direction)字段的字位宽度,2bits  
  5. #define   _IOC_NRMASK       ((1 << _IOC_NRBITS)-1)    //序数字段的掩码,0x000000FF  
  6. #define   _IOC_TYPEMASK     ((1 << _IOC_TYPEBITS)-1)  //幻数字段的掩码,0x000000FF  
  7. #define   _IOC_SIZEMASK     ((1 << _IOC_SIZEBITS)-1)   //大小字段的掩码,0x00003FFF  
  8. #define   _IOC_DIRMASK      ((1 << _IOC_DIRBITS)-1)    //方向字段的掩码,0x00000003  
  9. #define   _IOC_NRSHIFT     0                                //序数字段在整个字段中的位移,0  
  10. #define   _IOC_TYPESHIFT   (_IOC_NRSHIFT+_IOC_NRBITS)       //幻数字段的位移,8  
  11. #define   _IOC_SIZESHIFT   (_IOC_TYPESHIFT+_IOC_TYPEBITS)   //大小字段的位移,16  
  12. #define   _IOC_DIRSHIFT    (_IOC_SIZESHIFT+_IOC_SIZEBITS)   //方向字段的位移,30  
  13. #define _IOC_NONE    0U     //没有数据传输  
  14. #define _IOC_WRITE   1U     //向设备写入数据,驱动程序必须从用户空间读入数据  
  15. #define _IOC_READ    2U     //从设备中读取数据,驱动程序必须向用户空间写入数据  
  16. #define _IOC(dir,type,nr,size) \  
  17.        (((dir)  << _IOC_DIRSHIFT) | \  
  18.         ((type) << _IOC_TYPESHIFT) | \  
  19.         ((nr)   << _IOC_NRSHIFT) | \  
  20.         ((size) << _IOC_SIZESHIFT))  
  21.    
  22. //构造无参数的命令编号  
  23. #define _IO(type,nr)             _IOC(_IOC_NONE,(type),(nr),0)  
  24. //构造从驱动程序中读取数据的命令编号  
  25. #define _IOR(type,nr,size)     _IOC(_IOC_READ,(type),(nr),sizeof(size))  
  26. //用于向驱动程序写入数据命令  
  27. #define _IOW(type,nr,size)    _IOC(_IOC_WRITE,(type),(nr),sizeof(size))  
  28. //用于双向传输  
  29. #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))  
  30.    
  31. //从命令参数中解析出数据方向,即写进还是读出  
  32. #define _IOC_DIR(nr)          (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)  
  33. //从命令参数中解析出幻数type  
  34. #define _IOC_TYPE(nr)              (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)  
  35. //从命令参数中解析出序数number  
  36. #define _IOC_NR(nr)           (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)  
  37. //从命令参数中解析出用户数据大小  
  38. #define _IOC_SIZE(nr)         (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)  
  39.   
  40.   
  41. #define IOC_IN            (_IOC_WRITE << _IOC_DIRSHIFT)  
  42. #define IOC_OUT           (_IOC_READ << _IOC_DIRSHIFT)  
  43. #define IOC_INOUT         ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)  
  44. #define IOCSIZE_MASK      (_IOC_SIZEMASK << _IOC_SIZESHIFT)  
  45. #define IOCSIZE_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
在CODE上查看代码片派生到我的代码片
  1. //构造无参数的命令编号  
  2. #define _IO(type,nr)             _IOC(_IOC_NONE,(type),(nr),0)  
  3. //构造从驱动程序中读取数据的命令编号  
  4. #define _IOR(type,nr,size)     _IOC(_IOC_READ,(type),(nr),sizeof(size))  
  5. //用于向驱动程序写入数据命令  
  6. #define _IOW(type,nr,size)    _IOC(_IOC_WRITE,(type),(nr),sizeof(size))  
  7. //用于双向传输  
  8. #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))  

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

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. #define   MAGIC_NUMBER    'k'  
  2. #define   BEEP_ON    _IO(MAGIC_NUMBER    ,0)  
  3. #define   BEEP_OFF   _IO(MAGIC_NUMBER    ,1)  
  4. #define   BEEP_FREQ  _IO(MAGIC_NUMBER    ,2)  

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


四、 cmd参数如何得出 

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


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

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. #define  MAGIC_NUMBER    'k'  
  2. #define  BEEP_ON    _IO(MAGIC_NUMBER    ,0)  
  3. #define  BEEP_OFF   _IO(MAGIC_NUMBER    ,1)  
  4. #define  BEEP_FREQ  _IO(MAGIC_NUMBER    ,2)  
  5. #define  BEPP_IN_FREQ 100000  
  6.   
  7. static void beep_freq(unsigned long arg)  
  8. {  
  9.     writel(BEPP_IN_FREQ/arg, timer_base +TCNTB0  );  
  10.     writel(BEPP_IN_FREQ/(2*arg), timer_base +TCMPB0 );  
  11. }  
  12.   
  13. static long beep_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)  
  14. {  
  15.     switch(cmd)  
  16.     {  
  17.         case BEEP_ON:  
  18.             fs4412_beep_on();  
  19.             break;  
  20.         case BEEP_OFF:  
  21.             fs4412_beep_off();  
  22.             break;  
  23.         case BEEP_FREQ:  
  24.             beep_freq( arg );  
  25.             break;  
  26.         default :  
  27.             return -EINVAL;  
  28.     }  
  29. }  
测试代码如下:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. #include <sys/types.h>  
  2. #include <sys/stat.h>  
  3. #include <fcntl.h>  
  4. #include <stdio.h>  
  5. #include <sys/ioctl.h>  
  6.   
  7. #define  MAGIC_NUMBER    'k'  
  8. #define   BEEP_ON    _IO(MAGIC_NUMBER    ,0)  
  9. #define   BEEP_OFF   _IO(MAGIC_NUMBER    ,1)  
  10. #define   BEEP_FREQ   _IO(MAGIC_NUMBER    ,2)  
  11.   
  12. main()  
  13. {  
  14.     int fd;  
  15.   
  16.     fd = open("/dev/beep",O_RDWR);  
  17.     if(fd<0)  
  18.     {  
  19.         perror("open fail \n");  
  20.         return ;  
  21.     }  
  22.   
  23.     ioctl(fd,BEEP_ON);  
  24.   
  25.     sleep(6);  
  26.     ioctl(fd,BEEP_OFF);   
  27.   
  28.     close(fd);  
  29. }  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/402133.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android自动化框架简要剖析(一):运行原理+基本框架

android自动化测试原理&#xff1a; 1、将测试apk和被测试apk&#xff0c;运行在一个进程中&#xff1b;通过instrumentation进行线程间的通信 2、通过android.test.AndroidTestCase及其子类&#xff0c;控制android系统对象 3、通过android.test.InstrumentationTestCase 及其…

Linux 字符设备驱动开发基础(一)—— 编写简单 LED 设备驱动

现在&#xff0c;我们来编写自己第一个字符设备驱动 —— 点亮LED。&#xff08;不完善&#xff0c;后面再完善&#xff09; 硬件平台&#xff1a;Exynos4412&#xff08;FS4412&#xff09; 编写驱动分下面几步&#xff1a; a -- 查看原理图、数据手册&#xff0c;了解设备的操…

Linux 字符设备驱动结构(四)—— file_operations 结构体知识解析

前面在 Linux 字符设备驱动开发基础 &#xff08;三&#xff09;—— 字符设备驱动结构&#xff08;中&#xff09; &#xff0c;我们已经介绍了两种重要的数据结构 struct inode{...}与 struct file{...} &#xff0c;下面来介绍另一个比较重要数据结构 struct _file_operatio…

Android开发群

为什么80%的码农都做不了架构师&#xff1f;>>> 我的自建Android应用开发群&#xff0c;欢迎大家来聊聊呀&#xff01;201427584 转载于:https://my.oschina.net/catia/blog/176384

Linux 字符设备驱动结构(三)—— file、inode结构体及chardevs数组等相关知识解析

前面我们学习了字符设备结构体cdev Linux 字符设备驱动开发 &#xff08;一&#xff09;—— 字符设备驱动结构&#xff08;上&#xff09; 下面继续学习字符设备另外几个重要的数据结构。 先看下面这张图&#xff0c;这是Linux 中虚拟文件系统、一般的设备文件与设备驱动程序…

技术人生:三亚之行

人生收获 此次是公司组团去的三亚&#xff0c;地接的导游非常热情&#xff0c;如同大多数人一样&#xff0c;导游也会在这短短的几天内&#xff0c;尽可能的表现自己&#xff0c;此文聊聊导游说的三句话。 旅游三不“较”&#xff1a; 不比较不计较不睡觉人生何尝不是如此&…

Linux 字符设备驱动结构(二)—— 自动创建设备节点

上一篇我们介绍到创建设备文件的方法&#xff0c;利用cat /proc/devices查看申请到的设备名&#xff0c;设备号。 第一种是使用mknod手工创建&#xff1a;mknod filename type major minor 第二种是自动创建设备节点&#xff1a;利用udev&#xff08;mdev&#xff09;来实现设备…

Linux 字符设备驱动结构(一)—— cdev 结构体、设备号相关知识解析

一、字符设备基础知识 1、设备驱动分类 linux系统将设备分为3类&#xff1a;字符设备、块设备、网络设备。使用驱动程序&#xff1a; 字符设备&#xff1a;是指只能一个字节一个字节读写的设备&#xff0c;不能随机读取设备内存中的某一数据&#xff0c;读取数据需要按照先后数…

Linux 驱动开发之内核模块开发(四)—— 符号表的导出

Linux内核头文件提供了一个方便的方法用来管理符号的对模块外部的可见性,因此减少了命名空间的污染(命名空间的名称可能会与内核其他地方定义的名称冲突),并且适当信息隐藏。 如果你的模块需要输出符号给其他模块使用,应当使用下面的宏定义: EXPORT_SYMBOL(name); EXPORT_SYMBO…

Linux 驱动开发之内核模块开发 (三)—— 模块传参

一、module_param() 定义 通常在用户态下编程&#xff0c;即应用程序&#xff0c;可以通过main()的来传递命令行参数&#xff0c;而编写一个内核模块&#xff0c;则通过module_param() 来传参。 module_param()宏是Linux 2.6内核中新增的&#xff0c;该宏被定义在include/linux…

Exynos4412 文件系统制作(二)—— 文件系统简介

一、Linux磁盘分区和目录 Linux发行版本之间的差别很少&#xff0c;差别主要表现在系统管理的特色工具以及软件包管理方式的不同。目录结构基本上都是一样的。 Windows的文件结构是多个并列的树状结构&#xff0c;最顶部的是不同的磁盘&#xff08;分区&#xff09;&#xff0c…

MM引擎新应用——爱车加油记

基于MM应用引擎开发的EXTJS应用&#xff0c;车主每次加完汽油后&#xff0c;记录加油时的里程数以及加油金额和汽油价格&#xff0c;就可计算出上次加油后行驶的里程数、上次加油的平均油耗。点击刷新按钮&#xff0c;即可计算有记录以来的行驶公里数和再次期间加油金额和平均油…

### 阅读之痕-2013/11

2019独角兽企业重金招聘Python工程师标准>>> 阅读之痕-2013/11 Andy erpingwugmail.com 2013/11/01-2013/11/30 2013/11/01-2013/11/30 The story of rocksdb - Embedded key-value store for Flash and RAM http://rocksdb.org/intro.pdf High Performance Network…

Exynos4412 内核移植(六)—— 设备树解析

一、描述ARM Device Tree起源于OpenFirmware (OF)&#xff0c;在过去的Linux中&#xff0c;arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码&#xff0c;相当多数的代码只是在描述板级细节&#xff0c;而这些板级细节对于内核来讲&#xff0c;不过是垃圾&#xff…

Github Page创建个人主页以及绑定域名

2019独角兽企业重金招聘Python工程师标准>>> 在github中 我们可以通过github page创建个人主页 以及绑定域名 据说有300m空间 以及无限流量 不过只能支持静态html 以及一些脚本语言 顺便吐槽一下 阿里云最低配那个云空间服务器 512m内存 启动web服务器后 mys…

Exynos4412 内核移植(五)—— 驱动的移植

以移植自己制作的驱动&#xff0c;学习内核移植中的驱动移植&#xff0c;及 驱动程序的动态编译和静态编译 硬件环境&#xff1a; Linux 内核版本&#xff1a;Linux 3.14 主机&#xff1a;Ubuntu 12.04发行版 目标机&#xff1a;FS4412平台 交叉编译工具&#xff1a;arm-none-l…

FTP文件共传输服务

FTP文件共传输服务一&#xff0c;vsftpd服务基础&#xff08;1&#xff09;&#xff0c;FTP服务概述FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09;是典型的C/S结构的应用层协议&#xff0c;需要由服务端软件、客户端软件共同实现文件传达输功能…

Exynos4412 内核移植(四)—— MMU 相关知识解析

一、MMU的产生 许多年以前&#xff0c;当人们还在使用DOS或是更古老的操作系统的时候&#xff0c;计算机的内存还非常小&#xff0c;一般都是以K为单位进行计算&#xff0c;相应的&#xff0c;当时的程序规模也不大&#xff0c;所以内存容量虽然小&#xff0c;但还是可以容纳当…

Mysql limit 子查询

为什么80%的码农都做不了架构师&#xff1f;>>> &#xff08;1&#xff09;mysql limit 不支持子查询像下面这条语句无法执行 SELECT * FROM b_c_s1 where CT_ID IN (SELECT DISTINCT CT_ID FROM b_c_s1 LIMIT 0,2); &#xff08;2&#xff09;应该在只查询外面再…