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,一经查实,立即删除!

相关文章

Python中xrange和range异同

https://www.cnblogs.com/zhuzhubaoya/p/6381045.html

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

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

Python的threading多线程

https://www.cnblogs.com/tkqasn/p/5700281.html threading — 基于线程的并行 threading用于提供线程相关的操作&#xff0c;线程是应用程序中工作的最小单元。python当前版本的多线程库没有实现优先级、线程组&#xff0c;线程也不能被停止、暂停、恢复、中断。 threading模…

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

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

angularJS+requireJS实现controller及directive的按需加载

最近因为项目的比较大&#xff0c;需要加载的js文件较多&#xff0c;为了提高首屏页面的加载速度&#xff0c;需要对js文件进行按需加载&#xff0c;然后网上参考了一些资料&#xff0c;自己也深入研究一番之后&#xff0c;实现了按需加载控制器js文件及指令js文件的效果&#…

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

Python的multiprocessing多进程

https://www.cnblogs.com/tkqasn/p/5701230.html 由于GIL的存在&#xff0c;python中的多线程其实并不是真正的多线程&#xff0c;如果想要充分地使用多核CPU的资源&#xff0c;在python中大部分情况需要使用多进程。Python提供了非常好用的多进程包multiprocessing&#xff0…

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

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

Python的gevent协程及协程概念

https://www.cnblogs.com/tkqasn/p/5705338.html 何为协程 协程&#xff0c;又称微线程。英文名Coroutine。 协程最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换&#xff0c;而是由程序自身控制&#xff0c;因此&#xff0c;没有线程切换的开销&#xff0c;…

技术人生:三亚之行

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

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

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

Python数据库使用-SQLite

https://www.liaoxuefeng.com/wiki/897692888725344/926177394024864 SQLite是一种嵌入式数据库&#xff0c;它的数据库就是一个文件。由于SQLite本身是C写的&#xff0c;而且体积很小&#xff0c;所以&#xff0c;经常被集成到各种应用程序中&#xff0c;甚至在iOS和Android的…

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

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

Python数据库使用MySQL

https://www.liaoxuefeng.com/wiki/897692888725344/932709047411488 MySQL是Web世界中使用最广泛的数据库服务器。SQLite的特点是轻量级、可嵌入&#xff0c;但不能承受高并发访问&#xff0c;适合桌面和移动应用。而MySQL是为服务器端设计的数据库&#xff0c;能承受高并发访…

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

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

Python的time模块和datatime模块

https://www.cnblogs.com/tkqasn/p/6001134.html

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

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

Linux 驱动开发之内核模块开发 (二)—— 内核模块编译 Makefile 入门

一、模块的编译 我们在前面内核编译中驱动移植那块&#xff0c;讲到驱动编译分为静态编译和动态编译&#xff1b;静态编译即为将驱动直接编译进内核&#xff0c;动态编译即为将驱动编译成模块。 而动态编译又分为两种&#xff1a; a -- 内部编译 在内核源码目录内编译 b -- 外部…

Exynos4412 文件系统制作(三)—— 文件系统移植

根文件系统一直以来都是所有类Unix操作系统的一个重要组成部分&#xff0c;也可以认为是嵌入式Linux系统区别于其他一些传统嵌入式操作系统的重要特征&#xff0c;它给Linux带来了许多强大和灵活的功能&#xff0c;同时也带来了一些复杂性。我们需要清楚的了解根文件系统的基本…