设备驱动中的ioctl函数详解

18 篇文章 8 订阅

        ioctl是iocontrol的缩写,就是IO控制。

        行为上

        简单来说,如果你在写驱动程序时候,碰到一些IO操作,在逻辑上不能归类到read,不能归类到write,那就可以认为是ioctl的部分。

        read和write应该是写入和读出数据的,应该是作为单纯的数据交换的方式来处理。而ioctl则是控制read和write一些选项的。

        比如:你做了一个通用的读写IO端口的驱动模块。read和write是从端口读写数据的,但是更改读写的端口,这个操作应该如何处理呢?显然用ioctl来实现比较合理。

        比如你的read和write是可以阻塞的,或者不能阻塞的,或者对设备文件的读写是可以并发的,或者是不可以并发的,这些都可以写成可以用ioctl来配置的情况。后面为了可以用ioctl来实现模块不同的IO特点。

        参数上

        ioctl的一般参数格式就是命令字(常量)+命令参数的方式。

        read和write的参数格式都是数据缓冲区+数据目的地指针+长度。

一、 什么是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中定义的宏的注释:

#define   _IOC_NRBITS        8                               //序数(number)字段的字位宽度,8bits
#define   _IOC_TYPEBITS      8                               //幻数(type)字段的字位宽度,8bits
#define   _IOC_SIZEBITS      14                              //大小(size)字段的字位宽度,14bits
#define   _IOC_DIRBITS       2                               //方向(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_NRSHIFT     0                                //序数字段在整个字段中的位移,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_NONE    0U     //没有数据传输
#define _IOC_WRITE   1U     //向设备写入数据,驱动程序必须从用户空间读入数据
#define _IOC_READ    2U     //从设备中读取数据,驱动程序必须向用户空间写入数据
#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)
 
 
#define IOC_IN            (_IOC_WRITE << _IOC_DIRSHIFT)
#define IOC_OUT           (_IOC_READ << _IOC_DIRSHIFT)
#define IOC_INOUT         ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
#define IOCSIZE_MASK      (_IOC_SIZEMASK << _IOC_SIZESHIFT)
#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中提供了一些宏。这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。

比如上面展现的:

//构造无参数的命令编号
#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驱动程序中也定义了命令宏:

#define   MAGIC_NUMBER    'k'
#define   BEEP_ON    _IO(MAGIC_NUMBER    ,0)
#define   BEEP_OFF   _IO(MAGIC_NUMBER    ,1)
#define   BEEP_FREQ  _IO(MAGIC_NUMBER    ,2)

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

四、 cmd参数如何得出 

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

以下为一个示例的部分代码:

#define  MAGIC_NUMBER    'k'
#define  BEEP_ON    _IO(MAGIC_NUMBER    ,0)
#define  BEEP_OFF   _IO(MAGIC_NUMBER    ,1)
#define  BEEP_FREQ  _IO(MAGIC_NUMBER    ,2)
#define  BEPP_IN_FREQ 100000
 
static void beep_freq(unsigned long arg)
{
    writel(BEPP_IN_FREQ/arg, timer_base +TCNTB0  );
    writel(BEPP_IN_FREQ/(2*arg), timer_base +TCMPB0 );
}
 
static long beep_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
    switch(cmd)
    {
        case BEEP_ON:
            fs4412_beep_on();
            break;
        case BEEP_OFF:
            fs4412_beep_off();
            break;
        case BEEP_FREQ:
            beep_freq( arg );
            break;
        default :
            return -EINVAL;
    }
}


测试代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
 
#define  MAGIC_NUMBER    'k'
#define   BEEP_ON    _IO(MAGIC_NUMBER    ,0)
#define   BEEP_OFF   _IO(MAGIC_NUMBER    ,1)
#define   BEEP_FREQ   _IO(MAGIC_NUMBER    ,2)
 
main()
{
    int fd;
 
    fd = open("/dev/beep",O_RDWR);
    if(fd<0)
    {
        perror("open fail \n");
        return ;
    }
 
    ioctl(fd,BEEP_ON);
 
    sleep(6);
    ioctl(fd,BEEP_OFF);    
 
    close(fd);
}


原文链接:https://blog.csdn.net/zqixiao_09/article/details/50859302

  • 6
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ioctl函数Linux系统用于设备控制的函数,它可以用来向设备发送控制命令,或者获取设备状态信息等。 ioctl函数的原型如下: ```c int ioctl(int fd, unsigned long request, ...); ``` 其,fd表示设备文件描述符,request表示设备控制命令,后面的可变参数用于传递控制命令的参数。 request参数一般是一个32位的整数,由四个部分组成: ```c ioctl命令 = (魔数 << 8) | 命令序号 | 方向 | 大小 ``` 其,魔数是一个16位的数,用于标识该ioctl命令所属的设备类型;命令序号是一个8位的数,用于标识该ioctl命令的具体含义;方向用于表示该ioctl命令是读操作还是写操作;大小用于表示该ioctl命令的参数大小。 例如,下面是一个ioctl命令: ```c #define MY_IOCTL _IOW('k', 1, int) ``` 其,'k'是魔数,1是命令序号,_IOW表示该ioctl命令是写操作,int表示该ioctl命令参数的大小。 在应用程序调用ioctl函数时,需要传入一个指向控制命令参数的指针,如下: ```c int val = 10; ioctl(fd, MY_IOCTL, &val); ``` 在设备驱动,可以通过switch语句来处理ioctl命令,如下: ```c long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int val; switch (cmd) { case MY_IOCTL: if (copy_from_user(&val, (int __user *)arg, sizeof(val))) return -EFAULT; // 处理MY_IOCTL命令 break; // 处理其他命令 default: return -ENOTTY; } return 0; } ``` 其,copy_from_user函数用于将用户空间的数据拷贝到内核空间,sizeof(val)表示拷贝的数据大小。处理完命令后,需要返回0代表成功,或者返回一个负数代表错误。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值