【转】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机制。

设备控制接口ioctl详解的更多相关文章

  1. Java语言Socket接口用法详解

    Socket接口用法详解   在Java中,基于TCP协议实现网络通信的类有两个,在客户端的Socket类和在服务器端的ServerSocket类,ServerSocket类的功能是建立一个Serve ...

  2. c#接口使用详解

    c#接口使用详解 c#中接口隐式与显示实现 c#中接口可以隐式实现.显示实现,隐式实现更常使用.显示实现较少使用 其区别在于 显示实现避免接口函数签名冲突 显示实现只可以以接口形式调用 显示实现其子类 ...

  3. Java之函数式接口@FunctionalInterface详解(附源码)

    Java之函数式接口@FunctionalInterface详解 函数式接口的定义 在java8中,满足下面任意一个条件的接口都是函数式接口: 1.被@FunctionalInterface注释的接口 ...

  4. SpringMVC(4.2):Controller接口控制器详解(2)

    原文出处: 张开涛 4.5.ServletForwardingController 将接收到的请求转发到一个命名的servlet,具体示例如下: package cn.javass.chapter4. ...

  5. WebApi 接口参数详解

    WebApi 接口参数不再困惑:传参详解   阅读目录 一.get请求 1.基础类型参数 2.实体作为参数 3.数组作为参数 4.“怪异”的get请求 二.post请求 1.基础类型参数 2.实体作为 ...

  6. 微信JS-SDK之图像接口开发详解

    由于现在手头的项目中有一个上传证件照认证的功能(手机端),之前的思路是直接点击上传,然后直接将图片上传到服务器去,这篇文章有讲到(http://www.cnblogs.com/it-cen/p/453 ...

  7. python接口自动化(六)--发送get请求接口(详解)

    简介 如果想用python做接口测试,我们首先有不得不了解和学习的模块.它就是第三方模块:Requests. 虽然Python内置的urllib模块,用于访问网络资源.但是,它用起来比较麻烦,而且,缺 ...

  8. Controller 接口控制器详解

    Controller 控制器,是 MVC 中的部分 C,为什么是部分呢?因为此处的控制器主要负责功能处理部分:1.收集.验证请求参数并绑定到命令对象:2.将命令对象交给业务对象,由业务对象处理并返回模 ...

  9. SCSI接口图文详解

    目前存储设备的接口有五大类:IDE.SCSI.USB,并行口,串口,其中并行口与串口的速度非常慢,不提也罢,最主要的就是IDE,usb,SCSI.IDE(Integrated Drive Electr ...

随机推荐

  1. 探索Javascript 异步编程

    在我们日常编码中,需要异步的场景很多,比如读取文件内容.获取远程数据.发送数据到服务端等.因为浏览器环境里Javascript是单线程的,所以异步编程在前端领域尤为重要. 异步的概念 所谓异步,是指当 ...

  2. Android 进阶12:进程通信之 Socket (顺便回顾 TCP UDP)

    不要害怕困难,这是你进步的机会! 读完本文你将了解: OSI 七层网络模型 TCPIP 四层模型 TCP 协议 TCP 的三次握手 TCP 的四次挥手 UDP 协议 Socket 简介 Socket ...

  3. HDU - 5297:Y sequence (迭代&容斥)

    Yellowstar likes integers so much that he listed all positive integers in ascending order,but he hat ...

  4. HihoCoder 1053 : 居民迁移 二分+贪心+双指针(好题)

    居民迁移 时间限制:3000ms 单点时限:1000ms 内存限制:256MB 描述 公元2411年,人类开始在地球以外的行星建立居住点.在第1326号殖民星上,N个居住点分布在一条直线上.为了方便描 ...

  5. 无线网破解 跑字典 EWSA使用教程

    当我们用奶瓶抓到包,就可以再windowsxp&7&8下跑字典 工具/原料 EWSA4.0完美汉化破解版 字典 方法/步骤 1.打开程序 2.导入握手包 3.配置EWSA ,1.选项 ...

  6. leetcode_sql_4,196

    196. Delete Duplicate Emails Write a SQL query to delete all duplicate email entries in a table name ...

  7. 组件与.NET互操作

    组件 1.何谓组件技术? 组件技术就是利用某种编程手段,将一些人们所关心的,但又不便于让最终用户去直接操作的细节进行了封装,同时对各种业务逻辑规则进行了实现,用于处理用户的内部操作细节,甚至于将安全机 ...

  8. is is not == !=之间的区别

    简单来说,python中的is与is not运算符可判断两个对象是否为同一对象.若为同一个对象,则对象1 is 对象2为True .反正,若非同一个对象,则对象1 is not 对象2为True 要理 ...

  9. 学习动态性能表(3)--v$sql&v$sql_plan

    学习动态性能表 第三篇-(1)-v$sq 2007.5.25 V$SQL中存储具体的SQL语句. 一条语句可以映射多个cursor,因为对象所指的cursor可以有不同用户(如例1).如果有多个cur ...

  10. JAVA内置注解 基本注解

    温故而知新,可以为师矣! 每天复习,或者学习一点小东西,也能水滴石穿! 今天复习5个JAVA内置基本注解(贴代码胜过千言万语): package com.lf.test; import java.ut ...