linux驱动中的ioctrl

我这里说的ioctl函数是在驱动程序里的,因为我不知道还有没有别的场合用到了ioctl,

      所以就规定了我们讨论的范围。为什么要写篇文章呢,是因为我前一阵子被ioctl给搞混

      了,这几天才弄明白它,于是在这里清理一下头脑。

     

      一、 什么是ioctl。

      ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就

      是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数

      如下:

      int ioctl(int fd, ind cmd, …);

      其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设

      备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和

      cmd的意义相关的。

      ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支

      持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。

     

      二、 ioctl的必要性

      如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那就是蛮拧了。例如,我们可

      以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,

      那么后面就跟着控制命令(一般在socket编程中常常这样做)。但是如果这样做的话,会

      导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。

      所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码告

      诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要

      做的事情。

     

      三、 ioctl如何实现

      这是一个很麻烦的问题,我是能省则省。要说清楚它,没有四五千字是不行的,所以我这

      里是不可能把它说得非常清楚了,不过如果有读者对用户程序怎么和驱动程序联系起来感

      兴趣的话,可以看我前一阵子写的《write的奥秘》。读者只要把write换成ioctl,就知

      道用户程序的ioctl是怎么和驱动程序中的ioctl实现联系在一起的了。

      我这里说一个大概思路,因为我觉得《Linux设备驱动程序》这本书已经说的非常清楚

      了,但是得化一些时间来看。

      在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对

      应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事

      情,因为设备都是特定的,这里也没法说。关键在于怎么样组织命令码,因为在ioctl中

      命令码是唯一联系用户程序命令和驱动程序支持的途径。

      命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不

      会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的

      命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇

      怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。

      所以在Linux核心中是这样定义一个命令码的:

      ____________________________________

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

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

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

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

 

      这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以

      Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从

      命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数

      据传送方向和数据传输尺寸。

      

      这些宏我就不在这里解释了,具体的形式请读者察看Linux核心源代码中的和,文件里给

      除了这些宏完整的定义。这里我只多说一个地方,那就是"幻数"。

      幻数是一个字母,数据长度也是8,所以就用一个特定的字母来标明设备类型,这和用一

      个数字是一样的,只是更加利于记忆和理解。就是这样,再没有更复杂的了。

      更多的说了也没有,读者还是看一看源代码吧,推荐各位阅读《Linux 设备驱动程序》所

      带源代码中的short一例,因为它比较短小,功能比较简单,可以看明白ioctl的功能和细

      节。

 

      四、 cmd参数如何得出

      这里确实要说一说,cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、

      数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解

      码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过

      switch{case}结构进行相应的操作。

      要透彻理解,只能是通过阅读源代码,我这篇文章实际上只是一个引子。Cmd参数的组织

      还是比较复杂的,我认为要搞熟它还是得花不少时间的,但是这是值得的,驱动程序中最

      难的是对中断的理解。

 

      五、 小结

      ioctl其实没有什么很难的东西需要理解,关键是理解cmd命令码是怎么在用户程序里生成

      并在驱动程序里解析的,程序员最主要的工作量在switch{case}结构中,因为对设备的

      I/O控制都是通过这一部分的代码实现的。


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
`ioctl()` 函数是 Linux 实现设备驱动程序与用户空间程序交互的一种方式。它允许用户空间程序向设备驱动程序发送各种控制指令,以便于设备驱动程序对设备进行配置、状态查询等操作。 `ioctl()` 函数的原型为: ```c #include <sys/ioctl.h> int ioctl(int fd, unsigned long request, ...); ``` 其,`fd` 是文件描述符,`request` 是请求码,后面的可变参数是指向数据的指针。 `request` 通常是一个 32 位的整数值,它被分为四个部分: ``` | type | number | size | direction | ``` 其: - `type` 表示请求的类型,通常是一个字符,用来区分请求的功能; - `number` 是请求的编号,用来标识请求的具体类型; - `size` 表示请求的数据大小; - `direction` 表示请求的数据传输方向,可以是输入、输出或双向。 在设备驱动程序,可以使用 `ioctl()` 函数来处理用户空间程序发送过来的请求。具体的实现方式可以根据不同的请求类型进行定制。 下面是一个简单的 `ioctl()` 使用示例: ```c #include <fcntl.h> #include <stdio.h> #include <sys/ioctl.h> #include <unistd.h> #define MY_IOCTL_TYPE 'k' #define MY_IOCTL_RESET _IO(MY_IOCTL_TYPE, 0) #define MY_IOCTL_READ _IOR(MY_IOCTL_TYPE, 1, int) #define MY_IOCTL_WRITE _IOW(MY_IOCTL_TYPE, 2, int) int main() { int fd = open("/dev/mydevice", O_RDWR); if (fd == -1) { printf("Failed to open device.\n"); return -1; } // 执行 RESET 操作 if (ioctl(fd, MY_IOCTL_RESET) == -1) { printf("Failed to send RESET command.\n"); return -1; } // 执行 READ 操作 int data; if (ioctl(fd, MY_IOCTL_READ, &data) == -1) { printf("Failed to read data.\n"); return -1; } printf("Data read from device: %d\n", data); // 执行 WRITE 操作 data = 123; if (ioctl(fd, MY_IOCTL_WRITE, &data) == -1) { printf("Failed to write data.\n"); return -1; } printf("Data written to device: %d\n", data); close(fd); return 0; } ``` 在上面的示例,我们通过 `ioctl()` 函数执行了三个操作:RESET、READ 和 WRITE。其: - RESET 操作不需要传递任何参数,因此使用 `_IO()` 宏定义; - READ 操作需要从设备读取一个整数值,并将其存储到 `data` 变量,因此使用 `_IOR()` 宏定义; - WRITE 操作需要向设备写入一个整数值,因此使用 `_IOW()` 宏定义。 设备驱动程序的 `ioctl()` 实现可以参考以下示例: ```c #include <linux/fs.h> #include <linux/init.h> #include <linux/ioctl.h> #include <linux/kernel.h> #include <linux/module.h> #define MY_IOCTL_TYPE 'k' #define MY_IOCTL_RESET _IO(MY_IOCTL_TYPE, 0) #define MY_IOCTL_READ _IOR(MY_IOCTL_TYPE, 1, int) #define MY_IOCTL_WRITE _IOW(MY_IOCTL_TYPE, 2, int) static long mydevice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int data = 0; switch (cmd) { case MY_IOCTL_RESET: // 处理 RESET 操作 break; case MY_IOCTL_READ: // 处理 READ 操作 data = 123; copy_to_user((int *) arg, &data, sizeof(int)); break; case MY_IOCTL_WRITE: // 处理 WRITE 操作 copy_from_user(&data, (int *) arg, sizeof(int)); break; default: return -EINVAL; } return 0; } static struct file_operations mydevice_fops = { .owner = THIS_MODULE, .unlocked_ioctl = mydevice_ioctl, }; static int __init mydevice_init(void) { // 注册设备驱动程序 register_chrdev(0, "mydevice", &mydevice_fops); return 0; } static void __exit mydevice_exit(void) { // 注销设备驱动程序 unregister_chrdev(0, "mydevice"); } module_init(mydevice_init); module_exit(mydevice_exit); ``` 在上面的示例,我们实现了 `mydevice_ioctl()` 函数来处理用户空间程序发送过来的请求。具体的实现方式可以根据不同的请求类型进行定制。在示例,我们实现了 RESET、READ 和 WRITE 三种操作。 需要注意的是,在处理 READ 操作时,我们需要将设备的数据拷贝到用户空间程序;而在处理 WRITE 操作时,我们需要将用户空间程序的数据拷贝到设备。这里使用了 `copy_to_user()` 和 `copy_from_user()` 函数来完成数据拷贝操作。 最后,在 `mydevice_init()` 函数,我们注册了设备驱动程序,并在 `mydevice_exit()` 函数注销了设备驱动程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值