设备控制接口(ioctl 函数)

来自:http://www.cnblogs.com/noaming1900/archive/2010/10/20/1856581.html

http://www.cnblogs.com/geneil/archive/2011/12/04/2275372.html

 

序言
设备驱动程序的一个基本功能就是管理和控制设备,同时为用户应用程序提供管理和控制设备的接口。我们前面的“Hello World”驱动程序已经可以提供读写功能了,在这里我们将扩展我们的驱动以支持设备控制接口,在Linux中这个接口是通过ioctl函数来实现的。


设备控制接口(ioctl 函数
回想一下我们在
字符设备驱动中介绍的struct file_operations结构,这里我们将介绍一个新的方法:

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

这是驱动程序设备控制接口函数(ioctl函数)的内核原型定义,struct inode *struct file* 描述了操作的文件,unsigned int描述了ioctl命令号,这是一个重要的参数,我们稍后会对它做详细介绍。最后一个参数是unsigned long数据类型,描述了ioctl命令可能带有的参数,它可能是一个整数或指针数据。

  • ioctl命令号

    ioctl命令号是这个函数中最重要的参数,它描述的ioctl要处理的命令。Linux中使用一个32位的数据来编码ioctl命令,它包含四个部分:dir,type,nr,size。

    • dir

    代表数据传输的方向,占2位,可以是_IOC_NONE(无数据传输,0U),_IOC_WRITE(向设备写数据,1U)或_IOC_READ(从设备读数据,2U)或他们的逻辑或组合,当然只有_IOC_WRITE和_IOC_READ的逻辑或才有意义。

    • type

    描述了ioctl命令的类型,8位。每种设备或系统都可以指定自己的一个类型号,ioctl用这个类型来表示ioctl命令所属的设备或驱动。一般用ASCII码字符来表示,如 'a'。

    • nr

    ioctl命令序号,一般8位。对于一个指定的设备驱动,可以对它的ioctl命令做一个顺序编码,一般从零开始,这个编码就是ioctl命令的序号。

    • size

    ioctl命令的参数大小,一般14位。ioctl命令号的这个数据成员不是强制使用的,你可以不使用它,但是我们建议你指定这个数据成员,通过它我们可以检查用户空间数据的大小以避免错误的数据操作,也可以实现兼容旧版本的ioctl命令。

    我们可以自己来直接指定一个ioctl命令号,它可能仅仅是一个整数集,但Linux中的ioctl命令号都是有特定含义的,因此通常我们不推荐这么做。其实Linux内核已经提供了相应的宏来自动生成ioctl命令号:

    _IO(type,nr)
    _IOR(type,nr,size)
    _IOW(type,nr,size)
    _IOWR(type,nr,size) 

    宏_IO用于无数据传输,宏_IOR用于从设备读数据,宏 _IOW用于向设备写数据,宏_IOWR用于同时有读写数据的IOCTL命令。相对的,Linux内核也提供了相应的宏来从ioctl命令号中解码相应的域值:

    _IOC_DIR(cmd)
    _IOC_TYPE(cmd)
    _IOC_NR(cmd)
    _IOC_SIZE(cmd)


     这些宏都定义在<asm/ioctl.h>头文件中(一般在<asm-generic.h>头文件中)。一般在使用中,先指定各个IOCTL命令的顺序编号(一般从0开始),然后根据使用的环境用这些宏来自动生成IOCTL命令号,在后面的例子中你可以了解实际的使用场景。

  •  

    • ioctl返回值

    ioctl函数的返回值是一个整数类型的值,如果命令执行成功,ioctl返回零,如果出现错误,ioctl函数应该返回一个负值。这个负值会作为errno值反馈给调用此ioctl的用户空间程序。关于返回值的具体含义,请参考<linux/errno.h>和<asm/errno.h>头文件

  •  

    • ioctl参数

    这里有必要说明一下ioctl命令的参数,因为它很容易犯错误。如果ioctl命令参数仅仅是一个整数,那么事情很简单了,我们可以在ioctl函数中直接使用它。但如果它是一个指针数据,那么使用上就要小心了。首先要说明这个参数是由用户空间的程序传递过来的,因此这个指针指向的地址是用户空间地址,在Linux中,用户空间地址是一个虚拟地址,在内核空间是无法直接使用它的。为了解决在内核空间使用用户空间地址的数据,Linux内核提供了以下函数,它们用于在内核空间访问用户空间的数据,定义在<asm/uaccess.h>头文件中:

    unsigned long __must_check copy_to_user(void __user *to,
    const void *from, unsigned long n);
    unsigned long __must_check copy_from_user(void *to,
    const void __user *from, unsigned long n);


    copy_from_user和copy_to_user一般用于复杂的或大数据交换,对于简单的数据类型,如int或char,内核提供了简单的宏来实现这个功能:

    #define get_user(x,ptr)
    #define put_user(x,ptr)

    其中,x是内核空间的简单数据类型地址,ptr是用户空间地址指针。
    我们需要牢记:在内核中是无法直接访问用户空间地址数据的。因此凡是从用户空间传递过来的指针数据,务必使用内核提供的函数来访问它们。这里有必要再一次强调的是,在内核模块或驱动程序的编写中,我们强烈建议你使用内核提供的接口来生成并操作ioctl命令号,这样可以对命令号赋予特定的含义,使我们的程序更加的健壮;另一方面也可以提高程序的可移植性。

    举例
    好了,是时候举个例子了。我们将扩展我们的helloworld驱动添加ioctl函数。
    首先,我们添加一个头文件来定义ioctl接口需要用到的数据(hello.h):

    #ifndef _HELLO_H
    #define _HELLO_H
    #include <asm/ioctl.h>
    #define MAXBUF 20
    typedef struct _buf_data{
        int size;
        char data [MAXBUF];
    }buf_data;
    
    #define HELLO_IOCTL_NR_BASE            0
    #define HELLO_IOCTL_NR_SET_DATA     (HELLO_IOCTL_NR_BASE + 1)
    #define HELLO_IOCTL_NR_MAX             (HELLO_IOCTL_NR_GET_BUFF + 1)
    
    #define HELLO_IOCTL_SET_DATA           _IOR('h', HELLO_IOCTL_NR_SET_DATA, buf_data*)
    
    #endif

    然后为我们的驱动程序添加ioctl接口hello_ioctl,并实现这个函数:

    static int hello_ioctl (struct inode *inode, struct file *filp,
    unsigned int cmd, unsigned long arg)
    {
        int cmd_nr;
        int err;
        buf_data buff;
    
        err = 0;
        cmd_nr = _IOC_NR (cmd);
        switch (cmd_nr){
        case HELLO_IOCTL_NR_SET_DATA:
            if (copy_from_user(&buff, (unsigned char *)arg, sizeof(buf_data)))
            {
                err = -ENOMEM;
                goto error;
            }
            memset(hello_buf, 0, sizeof(hello_buf));
            memcpy(hello_buf, buff.data, buff.size);
            break;
        default:
            printk("hello_ioctl: Unknown ioctl command (%d)\n", cmd);
            break;
        }
    
        error:
        return err;
    }
    
    static struct file_operations hello_fops = {
        .read = hello_read,
        .write = hello_write,
        .open = hello_open,
        .ioctl = hello_ioctl,
        .release = hello_release,
    };

    后记
    到这里我们已经向您展示了Linux内核驱动程序的设备控制接口(ioctl接口),详细的介绍了它的使用,并给出了一个实际的例子,尽管它很简单,但已经足够了。到这里你可以写出一个标准的Linux驱动程序了。不过这里还有个问题,那就是我们不得不从/proc/devices文件里读取设备号然后手动创建设备节点。我们是否可以让系统自动的创建这个设备节点文件呢?当然可以。不过在那之前,我们必须深入了解Linux的设备驱动模型。后面的章节我们就详细的介绍Linux的设备驱动模型及Hotplug机制。
     

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值