ioctl

除了读取和写入设备之外,大部分驱动程序还需要另外一种能力,即通过设备驱动程序执行各种类型的硬件控制,通常这种需求使用ioctl方法支持,该方法实现了同名的系统调用;

在用户空间,ioctl系统调用的原型如下:

 int ioctl(int d, int request, ...);

原型中的可变参数不是数目不定的一串参数,而只是一个可选参数;可选参数的具体格式依赖于控制命令,也就是第二个参数;某些控制命令不需要参数,某些需要一个整数参数,某些需要一个指针参数;使用指针参数可以向ioctl传递任意数据,这样设备可以与用户空间交换任意数量的数据;

ioctl系统调用内核中的定义如下:

 SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)

ioctl在file_operations中的函数原型如下:

 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

大多数ioctl的实现都包含了一个switch语句来根据cmd参数选择对应的操作;不同的命令被赋予不同的数值,为了简化代码,通常会在代码中使用符号名代替数值,这些符号名由c语言的预处理语句定义,订制设备驱动程序通常会在它们的头文件中声明这些符号;

选择ioctl命令

ioctl命令号码定义使用了4个为字段,其含义如下:

type:幻数;选择一个号码,并在整个驱动程序中使用这个号码;这个字段是8位宽(_IOC_TYPEBITS);通常使用一个英文字母;比如#define MY_IOC_MAGIC ‘k’;需要注意避免命令号冲突;

number:序数(顺序编号);是8位宽(_IOC_NRBITS);通常从0开始顺序编号;

direction:如果命令涉及到数据的传输,则该位字段定义数据传输的方向;可以使用的值包括_IOC_NONE(没有数据传输)、_IOC_READ、_IOC_WRITE、_IOC_READ|_IOC_WRITE(双向传输);数据传输是从应用程序的角度来看的,也就是说,IOC_READ意味着从设备中读取数据,所以驱动程序必须向用户空间写入数据;注意,该字段是一个位掩码,因此可以使用逻辑AND操作从中分解出_IOC_READ和_IOC_WRITE;

size:所涉及的用户数据大小;这个字段的宽度与体系结构有关,通常是13或者14位,具体可以通过宏_IOC_SIZEBIT找到针对特定体系结构的具体数值;系统并不强制只用这个字段,也就是说,内核不会检查这个字段;对该字段的正确使用可以帮助我们检测用户程序的错误,并且如果我们从不改变相关的数据项大小的话,这个位字段哈可以帮我们实现向后的兼容性;但是,如果需要很大的数据传输,则可以忽略这个位字段;

用于构造命令编号的宏如下,其中type和number位字段通过参数传入,而size位字段通过对datatype参数取sizeof获取的;

_IO(type,nr)用于构造无参数的命令编号;

_IOR(type,nr,size)用于构造从驱动程序读取数据的命令编号;

_IOW(type,nr,size)用于构造用用户空间写入数据的命令;

_IOWR(type,nr,size)用于双向传输;

用于解开位字段的宏如下:

_IOC_DIR(nr)、_IOC_TYPE(nr)、_IOC_NR(nr)、_IOC_SIZE(nr);

返回值

ioctl实现通常就是一个基于命令号的switch语句;当命令号不合法时,有些内核函数返回-ENVAL(非法参数);POSIX标准规定,应该返回-ENOTTY,C库将这个错误码解释为不合适的设备ioctl;但是普遍做法是返回-EINVAL;

使用ioctl参数

ioctl的附加参数,如果是个整数,直接使用就可以了,如果是个指针,就需要注意一些问题;

当用一个指针指向用户空间时,必须确保指向的用户空间是合法的;对未验证的用户空间指针的访问,可能导致内核oops,系统崩溃或者安全问题;驱动程序应该负责对每个用到的用户空间地址做适当的检查,如果是非法地址则应该返回一个错误;

copy_from_user和copy_to_user可以安全的与用户空间交换数据,这两个函数也可以在ioctl中使用,但是因为ioctl通常涉及较小的数据项,因此可以通过其他方法更有效的操作;为此,我们首先要通过access_ok函数验证地址,而不传输数据,函数声明如下:<asm-generic/uaccess.h>

 #define access_ok(type, addr, size) __access_ok((unsigned long)(addr),(size))

第一个参数type应是VERIFY_READ或者VERIFY_RIWTE,取决于要执行的动作是读取还是写入用户空间内存区;addr参数是一个用户空间地址,size是字节数,如ioctl要从用户空间读取一个整数,则size是sizeof(int);如果在指定地址处既要读取又要写入,则应该用VERIFY_WRITE,因为它是VERIFY_READ的超集;

与大多数函数不同,access_ok返回1标识成功,0标识失败;如果返回失败,则通常需要返回-EFAULT给调用者;

关于该函数,需要注意两点:第一,它并没有完成验证内存的全部工作,而只是检查了所引用的内存是否位于进程有对应访问权限的区域中,特别是要确保访问的地址没有指向内核空间的内存区;第二,大多数驱动程序代码中都不需要真正调用access_ok,因为内存管理程序会处理它;

数据传送

除了copy_from_user和copy_to_user函数之外,内核还提供了常用的数据大小为1,2,4,8字节优化过的一组函数;

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

其中put_user函数把数据x写到用户空间;它们相对比较快,当需要传递单个数据时,使用这些宏而不是用copy_to_user;由于这些宏在展开时不做类型检查,所以可以传递给put_user任意类型的指针,只要是个用户空间地址就行;传递数据大小依赖于ptr参数的类型,在编译时由编译器的内建指令sizeof和typeof确定;总之,若ptr是一个字符指针,就传递1个字节,2,4,8字节的情况类似;

put_user已经进行了检查确保进程可以写入指定的内存地址,并在成功时返回0,出错是返回-EFAULT;__put_user则做的检查少些,它不调用access_ok,但是如果地址指向的用户不能写入内存,会出现操作失败,因为__put_user要在已经使用过access_ok检查后使用;

get_user从用户空间接收一个数据,接收的数值保存在局部变量x中,返回值指明了操作是否成功;通用,__get_user应该在操作地址已经被access_ok检查通过后使用;

如果是不满足上述的传递大小的数值,则必须使用copy_to_user和copy_from_user;

权能与受限操作

权限相关的定义在<uapi/linux/capability.h>中,其中包含了系统能够理解的所有权能;不修改内核源码,驱动程序无法定义新的权能;对驱动程序开发来讲有意义的权能如下:

CAP_DAC_OVERRIDE

越过文件或者目录的访问限制的能力;

CAP_NET_ADMIN

执行网络管理任务的能力,包括哪些能影响网络接口的任务;

CAP_SYS_MODULE

载入或者卸载内核模块的能力

CAP_SYS_RAWIO

执行裸IO操作的能力,例如,访问设备端口或者直接与USB设备通信;

CAP_SYS_ADMIN

截获的能力,它提供了访问许多系统管理操作的途径;

在执行某项特权操作之前,需要检查调用进程是否有合适的权能;如果不进行这类检查,将导致用户进程执行非授权操作,从而影响系统稳定性和安全性;权能检查通过capable实现;在<linux/capability.h>中;

 bool capable(int cap);

Linux设备驱动程序 之 ioctl的更多相关文章

  1. Linux设备驱动之Ioctl控制

    大部分驱动除了需要具备读写设备的能力之外,还需要具备对硬件控制的能力. 一.在用户空间,使用ioctl系统调用来控制设备,原型如下: int ioctl(int fd,unsigned long cm ...

  2. 嵌入式Linux设备驱动程序:用户空间中的设备驱动程序

    嵌入式Linux设备驱动程序:用户空间中的设备驱动程序 Embedded Linux device drivers: Device drivers in user space Interfacing ...

  3. 嵌入式Linux设备驱动程序:编写内核设备驱动程序

    嵌入式Linux设备驱动程序:编写内核设备驱动程序 Embedded Linux device drivers: Writing a kernel device driver 编写内核设备驱动程序 最 ...

  4. linux设备驱动程序该添加哪些头文件以及驱动常用头文件介绍(转)

    原文链接:http://blog.chinaunix.net/uid-22609852-id-3506475.html 驱动常用头文件介绍 #include <linux/***.h> 是 ...

  5. 【转】linux设备驱动程序中的阻塞机制

    原文网址:http://www.cnblogs.com/geneil/archive/2011/12/04/2275272.html 阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经 ...

  6. Linux设备驱动程序 第三版 读书笔记(一)

    Linux设备驱动程序 第三版 读书笔记(一) Bob Zhang 2017.08.25 编写基本的Hello World模块 #include <linux/init.h> #inclu ...

  7. Linux设备驱动程序学习之分配内存

    内核为设备驱动提供了一个统一的内存管理接口,所以模块无需涉及分段和分页等问题. 我已经在第一个scull模块中使用了 kmalloc 和 kfree 来分配和释放内存空间. kmalloc 函数内幕 ...

  8. 教你写Linux设备驱动程序:一个简短的教程

    教你写Linux设备驱动程序:一个简短的教程 http://blog.chinaunix.net/uid-20799298-id-99675.html

  9. linux设备驱动程序_hello word 模块编译各种问题集锦

    在看楼经典书籍<linux设备驱动程序>后,第一个程序就是编写一个hello word 模块. 原以为非常easy,真正弄起来,发现问题不少啊.前两天编过一次,因为没有记录,今天看的时候又 ...

随机推荐

  1. Matlab函数kmeans

    Matlab函数kmeans K-means聚类算法采用的是将N*P的矩阵X划分为K个类,使得类内对象之间的距离最大,而类之间的距离最小. 使用方法:Idx=Kmeans(X,K)[Idx,C]=Km ...

  2. 7.SpringMVC 配置式开发-ModelAndView和视图解析器

    ModelAndView 1.Model(模型) 1.model的本质就是HashMap,向模型中添加数据,就是往HashMap中去添加数据 2.HashMap 是一个单向查找数组,单向链表数组 3. ...

  3. Java基础加强-日志

    /*日志*/ 从功能上来说,日志API本身所需求的功能非常简单,只需要能够记录一段文本即可 API的使用者在需要记录时,根据当前的上下文信息构造出相应的文本信息,调用API完成记录.一般来说,日志AP ...

  4. 分布式任务队列 Celery —— Task对象

    转载至 JmilkFan_范桂飓:http://blog.csdn.net/jmilk  目录 目录 前文列表 前言 Task 的实例化 任务的名字 任务的绑定 任务的重试 任务的请求上下文 任务的继 ...

  5. cent os 7.0 出现的问题解决方法

    https://www.jb51.net/article/34012.htm       python重编译,并进行安装 https://www.jb51.net/os/RedHat/211444.h ...

  6. docker images 导入和导出

    目录 docker images 导入和导出 1.前言 2.docker image 的保存 3.docker image 的导入 docker images 导入和导出 1.前言 前提是现在有一个可 ...

  7. Maven 安装依赖包

    Guide to installing 3rd party JARs Although rarely, but sometimes you will have 3rd party JARs that ...

  8. LoadRunner(7)

    一.参数化策略 1.Select next row(How? 如何取?)取值方式 选择下一行 1)Sequential:顺序的 每个VU都从第一行开始,顺序依次向下取值: 数据取完可以从头循环重复使用 ...

  9. Tenka1 Programmer Contest 2019 D - Three Colors

    Three Colors 思路:dp 设sum为所有边的总和 不能组成三角形的情况:某条边长度>=ceil(sum/2),可以用dp求出这种情况的方案数,然后用总方案数减去就可以求出答案. 注意 ...

  10. 域知识深入学习二:建立AD DS域

    2.1 建立AD DS域前的准备工作 先安装一台服务器,然后将其升级(promote)为域控 2.1.1 选择适当的DNS域名 AD DS域名采用DNS的架构与命名方式 2.1.2 准备好一台支持AD ...