Linux设备驱动程序 之 ioctl
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的更多相关文章
- Linux设备驱动之Ioctl控制
大部分驱动除了需要具备读写设备的能力之外,还需要具备对硬件控制的能力. 一.在用户空间,使用ioctl系统调用来控制设备,原型如下: int ioctl(int fd,unsigned long cm ...
- 嵌入式Linux设备驱动程序:用户空间中的设备驱动程序
嵌入式Linux设备驱动程序:用户空间中的设备驱动程序 Embedded Linux device drivers: Device drivers in user space Interfacing ...
- 嵌入式Linux设备驱动程序:编写内核设备驱动程序
嵌入式Linux设备驱动程序:编写内核设备驱动程序 Embedded Linux device drivers: Writing a kernel device driver 编写内核设备驱动程序 最 ...
- linux设备驱动程序该添加哪些头文件以及驱动常用头文件介绍(转)
原文链接:http://blog.chinaunix.net/uid-22609852-id-3506475.html 驱动常用头文件介绍 #include <linux/***.h> 是 ...
- 【转】linux设备驱动程序中的阻塞机制
原文网址:http://www.cnblogs.com/geneil/archive/2011/12/04/2275272.html 阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经 ...
- Linux设备驱动程序 第三版 读书笔记(一)
Linux设备驱动程序 第三版 读书笔记(一) Bob Zhang 2017.08.25 编写基本的Hello World模块 #include <linux/init.h> #inclu ...
- Linux设备驱动程序学习之分配内存
内核为设备驱动提供了一个统一的内存管理接口,所以模块无需涉及分段和分页等问题. 我已经在第一个scull模块中使用了 kmalloc 和 kfree 来分配和释放内存空间. kmalloc 函数内幕 ...
- 教你写Linux设备驱动程序:一个简短的教程
教你写Linux设备驱动程序:一个简短的教程 http://blog.chinaunix.net/uid-20799298-id-99675.html
- linux设备驱动程序_hello word 模块编译各种问题集锦
在看楼经典书籍<linux设备驱动程序>后,第一个程序就是编写一个hello word 模块. 原以为非常easy,真正弄起来,发现问题不少啊.前两天编过一次,因为没有记录,今天看的时候又 ...
随机推荐
- boost random library的使用
生成满足一定分布的随机数,是统计模拟.系统仿真等应用中最基本的要求.matlab中提供了函数可以生成各种常见分布的随机数,c++使用boost random库也可以很容易实现. 一.例子 boos ...
- 查找最大和次大元素(JAVA版)(分治法)
问题描述:对于给定的含有n个元素的无序序列,求这个序列中最大和次大的两个不同元素. 问题求解分析(分治法):先给出无序序列数组a[low...high].第一种情况为当数组中只有一个元素时,此时只存在 ...
- 移植 Linux 内核
目录 更新记录 1.Linux 版本及特点 2.打补丁.编译.烧写.启动内核 3.内核源码文件结构 4.内核架构分析 4.1 内核配置 4.2 Makefile架构分析 4.3 Kconfig 架构文 ...
- 使用Vue CLI脚手架搭建vue项目
本次是使用@vue/cli 3.11.0版本搭建的vue项目 1. 首先确保自己的电脑上的Node.js的版本是8.9版本或者以上 2. 全局安装vue/cli npm install @vue/cl ...
- git冲突Pull is not possible because you have unmerged files
本地的push和merge会形成MERGE-HEAD(FETCH-HEAD), HEAD(PUSH-HEAD)这样的引用.HEAD代表本地最近成功push后形成的引用.MERGE-HEAD表示成功pu ...
- LEANGOO成员
转自:https://www.leangoo.com/leangoo_guide/leangoo_guide_member.html 1. 看板成员及权限 一个看板上的最大成员限制为200个. 看板的 ...
- JasperReport笔记
参考: https://blog.csdn.net/dullchap/article/details/51799070 关于 ireport的初步使用 ,笔记记录
- Delphi 执行线程对象
- (一)Android jni打印到logcat
#include <stdio.h> #include <android/log.h> int main(void) { int a = 0x10,b = 0x20; __an ...
- TcxComboBoxProperties下拉框填充
原文地址:https://www1.devexpress.com/Support/Center/Question/Details/CQ30369 Actually, the corresponding ...