Linux设备驱动之Ioctl控制
大部分驱动除了需要具备读写设备的能力之外,还需要具备对硬件控制的能力。
一、在用户空间,使用ioctl系统调用来控制设备,原型如下:
- int ioctl(int fd,unsigned long cmd,...);
- /*
- fd:文件描述符
- cmd:控制命令
- ...:可选参数:插入*argp,具体内容依赖于cmd
- */
用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。
二、驱动ioctl方法:
- int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);
- /*
- inode与filp两个指针对应于应用程序传递的文件描述符fd,这和传递open方法的参数一样。
- cmd 由用户空间直接不经修改的传递给驱动程序
- arg 可选。
- */
在驱动程序中实现的ioctl函数体内,实际上是有一个switch {case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。
在Linux核心中是这样定义一个命令码的:
- | 设备类型 | 序列号 | 方向 | 数据尺寸 |
- |----------|--------|------|-------- |
- | bit | bit | bit |~ bit|
- |----------|--------|------|-------- |
这里介绍一个CMD解析结果:
cmd的大小为 32位,共分 4 个域:
bit31~bit30 2位为 “区别读写” 区,作用是区分是读取命令还是写入命令。
bit29~bit16 14位为 "数据大小" 区,表示 ioctl()中的 arg 变量传送的内存大小。
bit15~bit08 8位为 “魔数"(也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
bit07~bit00 8位为 "区别序号" 区,是区分命令的命令顺序序号。
这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。
1、定义命令:
内核提供了一些宏来帮助定义命令:
- //nr为序号,datatype为数据类型,如int
- _IO(type, nr ) //没有参数的命令
- _IOR(type, nr, datatype) //从驱动中读数据
- _IOW(type, nr, datatype) //写数据到驱动
- _IOWR(type,nr, datatype) //双向传送
定义命令例子:
- #define MEM_IOC_MAGIC 'm' //定义类型
- #define MEM_IOCSET _IOW(MEM_IOC_MAGIC,0,int)
- #define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC, 1, int)
2、实现命令:
定义好了命令,下一步就是要实现ioctl函数了,ioctl的实现包括三个技术环节:
1)返回值;
ioctl函数的实现是根据命令执行的一个switch语句,但是,当命令不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(非法参数);
2)参数使用;
用户使用 int ioctl(int fd,unsinged long cmd,...) 时,...就是要传递的参数;
再通过 int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) 中的arg传递;
如果arg是一个整数,可以直接使用;
如果是指针,我们必须确保这个用户地址是有效的,因此,使用之前需要进行正确检查。
内部有检查的,不需要检测的:
- copy_from_user
- copy_to_user
- get_user
- put_user
需要检测的:
- __get_user
- __put_user
检测函数access_ok():
- static inline int access_ok(int type, const void *addr, unsigned long size)
- /*
- type :是VERIFY_READ 或者VERIFY_WRITE用来表明是读用户内存还是写用户内存;
- addr:是要操作的用户内存地址;
- size:是操作的长度。如果ioctl需要从用户空间读一个整数,那么size参数就等于sizeof(int);
- 返回值:Access_ok返回一个布尔值:1,是成功(存取没问题);0,是失败,ioctl返回-EFAULT;
- */
3)命令操作;
- switch(cmd)
- {
- case:
- ... ...
- }
三、ioctl实例分析:
(1)memdev.h:
- /*mem设备描述结构体*/
- struct mem_dev
- {
- char *data;
- unsigned long size;
- };
- /* 定义幻数 */
- #define MEMDEV_IOC_MAGIC 'k'
- /* 定义命令 */
- #define MEMDEV_IOCPRINT _IO(MEMDEV_IOC_MAGIC, 1)
- #define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)
- #define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)
- #define MEMDEV_IOC_MAXNR 3
- #endif /* _MEMDEV_H_ */
(2)memdev.c:(驱动程序)
- static int mem_major = MEMDEV_MAJOR;
- module_param(mem_major, int, S_IRUGO);
- struct mem_dev *mem_devp; /*设备结构体指针*/
- struct cdev cdev;
- /*文件打开函数*/
- int mem_open(struct inode *inode, struct file *filp)
- {
- struct mem_dev *dev;
- /*获取次设备号*/
- int num = MINOR(inode->i_rdev);
- if (num >= MEMDEV_NR_DEVS)
- return -ENODEV;
- dev = &mem_devp[num];
- /*将设备描述结构指针赋值给文件私有数据指针*/
- filp->private_data = dev;
- return 0;
- }
- /*文件释放函数*/
- int mem_release(struct inode *inode, struct file *filp)
- {
- return 0;
- }
- /*IO操作*/
- int memdev_ioctl(struct inode *inode, struct file *filp,
- unsigned int cmd, unsigned long arg)
- {
- int err = 0;
- int ret = 0;
- int ioarg = 0;
- /* 检测命令的有效性 */
- if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)
- return -EINVAL;
- if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR)
- return -EINVAL;
- /* 根据命令类型,检测参数空间是否可以访问 */
- if (_IOC_DIR(cmd) & _IOC_READ)
- err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
- else if (_IOC_DIR(cmd) & _IOC_WRITE)
- err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
- if (err)
- return -EFAULT;
- /* 根据命令,执行相应的操作 */
- switch(cmd) {
- /* 打印当前设备信息 */
- case MEMDEV_IOCPRINT:
- printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");
- break;
- /* 获取参数 */
- case MEMDEV_IOCGETDATA:
- ioarg = 1101;
- ret = __put_user(ioarg, (int *)arg);
- break;
- /* 设置参数 */
- case MEMDEV_IOCSETDATA:
- ret = __get_user(ioarg, (int *)arg);
- printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);
- break;
- default:
- return -EINVAL;
- }
- return ret;
- }
- /*文件操作结构体*/
- static const struct file_operations mem_fops =
- {
- .owner = THIS_MODULE,
- .open = mem_open,
- .release = mem_release,
- .ioctl = memdev_ioctl,
- };
- /*设备驱动模块加载函数*/
- static int memdev_init(void)
- {
- int result;
- int i;
- dev_t devno = MKDEV(mem_major, 0);
- /* 静态申请设备号*/
- if (mem_major)
- result = register_chrdev_region(devno, 2, "memdev");
- else /* 动态分配设备号 */
- {
- result = alloc_chrdev_region(&devno, 0, 2, "memdev");
- mem_major = MAJOR(devno);
- }
- if (result < 0)
- return result;
- /*初始化cdev结构*/
- cdev_init(&cdev, &mem_fops);
- cdev.owner = THIS_MODULE;
- cdev.ops = &mem_fops;
- /* 注册字符设备 */
- cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
- /* 为设备描述结构分配内存*/
- mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
- if (!mem_devp) /*申请失败*/
- {
- result = - ENOMEM;
- goto fail_malloc;
- }
- memset(mem_devp, 0, sizeof(struct mem_dev));
- /*为设备分配内存*/
- for (i=0; i < MEMDEV_NR_DEVS; i++)
- {
- mem_devp[i].size = MEMDEV_SIZE;
- mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
- memset(mem_devp[i].data, 0, MEMDEV_SIZE);
- }
- return 0;
- fail_malloc:
- unregister_chrdev_region(devno, 1);
- return result;
- }
- /*模块卸载函数*/
- static void memdev_exit(void)
- {
- cdev_del(&cdev); /*注销设备*/
- kfree(mem_devp); /*释放设备结构体内存*/
- unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
- }
- MODULE_AUTHOR("David Xie");
- MODULE_LICENSE("GPL");
- module_init(memdev_init);
- module_exit(memdev_exit);
(3)app-ioctl.c(应用程序)
- #include <stdio.h>
- #include<sys/types.h>
- #include<sys/stat.h>
- #include<fcntl.h>
- #include "memdev.h" /* 包含命令定义 */
- int main()
- {
- int fd = 0;
- int cmd;
- int arg = 0;
- char Buf[4096];
- /*打开设备文件*/
- fd = open("/dev/memdev0",O_RDWR);
- if (fd < 0)
- {
- printf("Open Dev Mem0 Error!\n");
- return -1;
- }
- /* 调用命令MEMDEV_IOCPRINT */
- printf("<--- Call MEMDEV_IOCPRINT --->\n");
- cmd = MEMDEV_IOCPRINT;
- if (ioctl(fd, cmd, &arg) < 0)
- {
- printf("Call cmd MEMDEV_IOCPRINT fail\n");
- return -1;
- }
- /* 调用命令MEMDEV_IOCSETDATA */
- printf("<--- Call MEMDEV_IOCSETDATA --->\n");
- cmd = MEMDEV_IOCSETDATA;
- arg = 2007;
- if (ioctl(fd, cmd, &arg) < 0)
- {
- printf("Call cmd MEMDEV_IOCSETDATA fail\n");
- return -1;
- }
- /* 调用命令MEMDEV_IOCGETDATA */
- printf("<--- Call MEMDEV_IOCGETDATA --->\n");
- cmd = MEMDEV_IOCGETDATA;
- if (ioctl(fd, cmd, &arg) < 0)
- {
- printf("Call cmd MEMDEV_IOCGETDATA fail\n");
- return -1;
- }
- printf("<--- In User Space MEMDEV_IOCGETDATA Get Data is %d --->\n\n",arg);
- close(fd);
- return 0;
- }
Linux设备驱动之Ioctl控制的更多相关文章
- 【转】Linux设备驱动之Ioctl控制
原文网址:http://www.cnblogs.com/geneil/archive/2011/12/04/2275372.html 大部分驱动除了需要具备读写设备的能力之外,还需要具备对硬件控制的能 ...
- Linux设备驱动之Ioctl控制【转】
转自:http://www.cnblogs.com/geneil/archive/2011/12/04/2275372.html 大部分驱动除了需要具备读写设备的能力之外,还需要具备对硬件控制的能力. ...
- linux设备驱动归纳总结(三):4.ioctl的实现【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-59419.html linux设备驱动归纳总结(三):4.ioctl的实现 一.ioctl的简介: 虽 ...
- linux设备驱动归纳总结(三):4.ioctl的实现
linux设备驱动归纳总结(三):4.ioctl的实现 一.ioctl的简单介绍: 尽管在文件操作结构体"struct file_operations"中有非常多相应的设备操作函数 ...
- 【Linux开发】linux设备驱动归纳总结(三):4.ioctl的实现
linux设备驱动归纳总结(三):4.ioctl的实现 一.ioctl的简介: 虽然在文件操作结构体"struct file_operations"中有很多对应的设备操作函数,但是 ...
- linux设备驱动归纳总结(十一):写个简单的看门狗驱动【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-112879.html linux设备驱动归纳总结(十一):写个简单的看门狗驱动 xxxxxxxxxxx ...
- Smart210学习记录-------Linux设备驱动结构
cdev结构体 1 struct cdev { 2 struct kobject kobj; /* 内嵌的 kobject 对象 */ 3 struct module *owner; /*所属模块*/ ...
- Linux 设备驱动 Edition 3
原文网址:http://oss.org.cn/kernel-book/ldd3/index.html Linux 设备驱动 Edition 3 By Jonathan Corbet, Alessand ...
- linux 设备驱动概述
linux 设备驱动概述 目前,Linux软件工程师大致可分为两个层次: (1)Linux应用软件工程师(Application Software Engineer): 主要利用C库函数和 ...
随机推荐
- struts2 中怎样获取HttpServletReqest
struts2 中怎样获取HttpServletRequest 和HttpServletResponse 提供两种方法 第一种通过调用ServletActionContext这个类源代码中提供这个对象 ...
- Task 4.3 求环形数组的最大子数组和
任务要求:输入一个整形数组,数组里有正数也有负数. 数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和. 如果数组A[0]……A[j-1]首尾相邻,允许A[i-1], …… A[n- ...
- 《Spring1之第十次站立会议》
<第十次站立会议> 昨天:试着把用C#写的代码转换为java语言. 今天:已基本转换为java语言了,也能够实现视频聊天这个功能了. 遇到的问题:在进行视频通话时没有考虑到声音优化功能,实 ...
- C++自学笔记(3)
类和对象 对象 具体指代一个事物 类 为了便于管理,将信息抽象. 目的不同,抽象的信息也不同(选择性暴露),也就是封装. 通过访问限定符,选择想要暴露和隐藏的信息. 对象实例化 计算机根据一个类的设计 ...
- Matlab图像匹配问题
已知一个任意形状,查找在大图像中最接近的形状位置. 输入:一个小图形状和一张大图 输出:最接近的形状在大图中的位置 假设: (1)已知形状与目标形状有一定的形变. (2)形状与大图像均为二值图像,图中 ...
- Docker的volume机制实现容器数据的持久性存储
1:可以启动一个容器的时候,临时指定挂载的volume,这个volume会自动创建,无需在宿主机上事先创建 docker run -it -v /busybox busybox:latest 登入到b ...
- DotNetty 跨平台的网络通信库
长久以来,.Net开发人员都非常羡慕Java有Netty这样,高效,稳定又易用的网络通信基础框架.终于微软的Azure团队,使用C#实现的Netty的版本发布.不但使用了C#和.Net平台的技术特点, ...
- ACM数论之旅16---母函数(又名生成函数)(痛并快乐着(╭ ̄3 ̄)╭)
(前排出售零食瓜子) 前言: 母函数是个很难的东西,难在数学 而ACM中所用的母函数只是母函数的基础 应该说除了不好理解外,其他都是非常简单的 母函数即生成函数,是组合数学中尤其是计数方面的一个重要理 ...
- html template & iframe
html template & iframe https://bbs.csdn.net/topics/390123946 据说可以利用某些浏览器bug绕过跨域限制,可以也研究下; 由于浏览器对 ...
- 快速用梯度下降法实现一个Logistic Regression 分类器
前阵子听说一个面试题:你实现一个logistic Regression需要多少分钟?搞数据挖掘的人都会觉得实现这个简单的分类器分分钟就搞定了吧? 因为我做数据挖掘的时候,从来都是顺手用用工具的,尤其是 ...