(2008-08-08 15:02:19)

标签:

it

linux

kernel

driver

分类: Linux
序言
设备驱动程序的一个基本功能就是管理和控制设备,同时为用户应用程序提供管理和控制设备的接口。我们前面的“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(nr)
_IOC_TYPE(nr)
_IOC_NR(nr)
_IOC_SIZE(nr)

这些宏都定义在<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机制。

Linux驱动程序开发 - 设备控制接口的更多相关文章

  1. BeagleBone Black Linux驱动程序开发入门(1): LED驱动程序

    这篇文章展示如何在BBB平台上编写LED驱动程序,本文的程序是根据国嵌S3C2440的LED驱动的例子并结合内核中OMAP系列的gpio操作来改的.本文中的程序包括驱动程序模块和用户空间程序.废话不多 ...

  2. 【驱动】linux驱动程序开发及环境搭建

    1.mystery引入 1)设备驱动程序对外提供如下的功能:        1)设备初始化:对硬件设备进行初始化操作        2)数据交换:数据交换包括由内核层向硬件层传送数据.从硬件层读取数据 ...

  3. BeagleBone Black Linux驱动程序开发入门(0): 开发环境

    搭建arm-linux交叉编译环境的教程有很多,这里只作简要说明.Host宿主机是Ubuntu10.04,我把它装在Windows XP的VirtualBox虚拟机中,这样相当于一台主机有两个操作系统 ...

  4. linux设备驱动程序--sysfs用户接口的使用

    linux sysfs文件系统 本文部分内容参考自官方文档 自2.6版本开始,linux内核开始使用sysfs文件系统,它的作用是将设备和驱动程序的信息导出到用户空间,方便了用户读取设备信息,同时支持 ...

  5. 设备控制接口ioctl详解

    [转]Linux设备控制接口 序言设备驱动程序的一个基本功能就是管理和控制设备,同时为用户应用程序提供管理和控制设备的接口.我们前面的“Hello World”驱动程序已经可以提供读写功能了,在这里我 ...

  6. 【DSP开发】【Linux开发】Linux下PCI设备驱动程序开发

    PCI是一种广泛采用的总线标准,它提供了许多优于其它总线标准(如EISA)的新特性,目前已经成为计算机系统中应用最为广泛,并且最为通用的总线标准.Linux的内核能较好地支持PCI总线,本文以Inte ...

  7. Linux驱动程序接口

    §1. Linux驱动程序接口 系统调用是操作系统内核与应用程序之间的接口,设备驱动程序则是操作系统内核与机器硬件的接口.几乎所有的系统操作最终映射到物理设备,除了CPU.内存和少数其它设备,所有的设 ...

  8. Linux下PCI设备驱动程序开发 --- PCI驱动程序实现(三)

    三.PCI驱动程序实现 1. 关键数据结构 PCI设备上有三种地址空间:PCI的I/O空间.PCI的存储空间和PCI的配置空间.CPU可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给 ...

  9. 【ARM-Linux开发】【CUDA开发】NVIDIA TEGRA X1:LINUX驱动程序包多媒体用户指南

    NVIDIA TEGRA X1:LINUX驱动程序包多媒体用户指南 转载请注明作者和出处:http://blog.csdn.net/u011475210 嵌入式平台:NVIDIA Jetson TX1 ...

随机推荐

  1. apache 设置404页面

    这几天用xampp搭建了一套环境,后来发现在网页访问出现404的时候xampp显示的内容不安全,把apache.php还有一些其它的版本都会显示 出来,所以想自己设置一个404的页面,在网上找了一些资 ...

  2. 再说CSS3渐变——线性渐变

    渐变背景一直以来在Web页面中都是一种常见的视觉元素.但一直以来,Web设计师都是通过图形软件设计这些渐变效果,然后以图片形式或者背景图片的形式运用到页面中.Web页面上实现的效果,仅从页面的视觉效果 ...

  3. C# 当double数值较大且小数位过多时转化成字符串并保留小数位

    今天在C#中碰到了一个问题,需要将double转换成字符串显示,要求保留小数位. 在网上查询了一下相关的文章 具体如下: double temp=3.1415926; (F)Fixed point:s ...

  4. 跨平台网络通信与服务器框架 acl 3.2.0 发布

    acl 3.2.0 版本发布了,acl 是 one advanced C/C++ library 的简称,主要包括网络通信库以及服务器框架库等功能,支持 Linux/Windows/Solaris/F ...

  5. CSS中的高度

    https://developer.mozilla.org/en-US/docs/Web/API/element.clientHeight Element.clientHeight是只读属性,以像素为 ...

  6. Codeforces 715A & 716C Plus and Square Root【数学规律】 (Codeforces Round #372 (Div. 2))

    C. Plus and Square Root time limit per test 2 seconds memory limit per test 256 megabytes input stan ...

  7. supesite 连 discuz 论坛记录

    上一篇,网站supesite里 有 config.php  这里记录访问地址,到时候可以更改这里变更域名或者外网地址. 这里同样也是 下载discuz,解压,bbs 下的文件为有效的内容,放到supe ...

  8. Summary Ranges —— LeetCode

    Given a sorted integer array without duplicates, return the summary of its ranges. For example, give ...

  9. HDOJ(HDU) 2192 MagicBuilding(用Java的Map做了下)

    Problem Description As the increase of population, the living space for people is becoming smaller a ...

  10. 对MMU段式转换的理解

    本文将详细介绍MMU段式转换的过程,并在文末附上一篇讲MMU比较详细的文章.具体什么是MMU,什么时段是转换就不在本文讲了,直接戳文末的链接. 首先,进行段式转换的条件.我们要拥有一个虚拟地址,还有一 ...