一、ioctl功能简介
open、write函数的功能无非就是为了进行用户空间和内核空间的数据交换,而ioctl呢?
大部分驱动除了需要具备读写设备的能力之外,还需要对设备具有控制能力,比如要求设备报告错误信息,弹出介质,设置波特率等。
这些操作通常是通过ioctl来实现的。
用户空间和内核空间实现ioctl的方法
1.在用户空间中
int ioctl(int fd, unsigned long cmd, ...);
...表示的是编译器不对此参数进行类型检查,所以在我们传参的时候,尤其是传递指针的时候,需要注意了,一定要进行地址检查,否则可能产生致命的漏洞
fd是表示打开文件时得到的文件操作符,他讲会对应我们内核空间中inod和file两个参数,参数cmd表示一个ioctl指令,第三个...表示的是第二个参数cmd命令
所需要的参数,值得注意的是,该参数并不像printf中的可变参数列表,这里的...至多可以指定一个参数。当然你也可以不指定参数。当我们指定了一个参数的
时候,该参数要么是一个指针,要么是一个整数。
2.在内核空间中
int (*ioctl) (struct inode * node, struct file *filp, unsigned int cmd, unsigned long arg);
参数node和filp来源于用户空间的fd,cmd,arg来源于用户空间的cmd和arg,只是类型不同而已!
下面看网友总结的一张图
二、如何实现ioctl方法
1.定义cmd命令
2.实现ioctl底层驱动函数
下面仔细介绍这两步
三、关于cmd
从cmd的定义就可以知道,cmd是一个unsigned int的变量,所以只不过一个数而已,所以我们先来看一个简单的示例程序
1.首先在mem_pool.h文件中定义如下命令
#define MEM_CLEAR 0x1 /*清0全局内存*/
2.在底层驱动文件中mem_pool.c中的ioctl函数
/* ioctl设备控制函数 */
static int mem_pool_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
/*获得设备结构体指针*/
struct mem_pool_dev *dev = filp->private_data;
switch (cmd)
{
case MEM_CLEAR:
memset(dev->mem, 0, mem_pool_SIZE);
printk( "mem_pool is set to zero\n");
break;
default:
printk( "cmd is error \n");
return - EINVAL;
}
return 0;
}
功能是将内存清零了
3.再看一个应用测试程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*0. 包含命令定义 */
#include "mem_pool.h"
int main()
{
int fd = 0;
int cmd;
int arg = 0;
char buf[4096] = "xbDeng";
/*1. 打开设备文件*/
fd = open("/dev/mem_pool",O_RDWR);
if (fd < 0)
{
printf("Open Dev Mem0 Error!\n");
return -1;
}
printf("%s \n",buf);
/*2. *写文件*/
if(write(fd,buf,sizeof(buf)) < 0 )
{
printf("write error !! \n");
}
/*3. 注意,要读取内容,一定要记得定位*/
lseek(fd,0,SEEK_SET);
/*4. 读文件*/
if(read(fd,buf,sizeof(buf)) < 0 )
{
printf("read error !! \n");
}
printf("read fd is %s \n",buf);
/*5. 调用命令MEM_CLEAR */
printf("<--- Call MEM_CLEAR --->\n");
cmd = MEM_CLEAR;
if (ioctl(fd, cmd, &arg) < 0)
{
printf("Call cmd MEM_CLEAR fail\n");
return -1;
}
lseek(fd,0,SEEK_SET);
/*6. 再读文件*/
if(read(fd,buf,sizeof(buf)) < 0 )
{
printf("read error !! \n");
}
printf("%s \n",buf);
/*7. 关闭文件*/
close(fd);
return 0;
}
最后能够看到打印的内容如下所示
内存清理掉后就无法读出数据了。
其实按照上面的示例来做就已经可以让我们的程序正常运行了,但是不怕一万,就怕万一,万一哪天打开错了设备文件,并且调用了相同的ioctl,这样就完蛋了。因为这个文件里面同样有cmd对应实现。为了防止这样的事情发生,内核对cmd又有了新的定义,规定了cmd都应该不一样。
四、内核中cmd的前世今生
关于cmd的前世今生,以下内容出自某位网友,个人感觉这部分他说得很好。
以下来自http://blog.chinaunix.net/uid-25014876-id-59419.html
一个cmd被分为了4个段,每一段都有各自的意义,cmd的定义在<linux/ioctl.h>。注:但实际上<linux/ioctl.h>中只是包含了<asm/ioctl.h>,这说明了这是跟平台相关的,
ARM的定义在<arch/arm/include/asm/ioctl.h>,但这文件也是包含别的文件<asm-generic/ioctl.h>,千找万找,终于找到了。
在<asm-generic/ioctl.h>中,cmd拆分如下:
解释一下四部分,全部都在<asm-generic/ioctl.h>和ioctl-number.txt这两个文档有说明。
1)幻数:说得再好听的名字也只不过是个0~0xff的数,占8bit(_IOC_TYPEBITS)。这个数是用来区分不同的驱动的,像设备号申请的时候一样,内核有一个文档给出一
些推荐的或者已经被使用的幻数。
/*Documentation/ioctl/ioctl-number.txt*/
164 'w' all CERN SCI driver
165 'y' 00-1F packet based user level communications
166 <mailto:zapman@interlan.net>
167 'z' 00-3F CAN bus card
168 <mailto:hdstich@connectu.ulm.circular.de>
169 'z' 40-7F CAN bus card
170 <mailto:oe@port.de>
可以看到'x'是还没有人用的,我就拿这个当幻数!
2)序数:用这个数来给自己的命令编号,占8bit(_IOC_NRBITS),我的程序从1开始排序。
3)数据传输方向:占2bit(_IOC_DIRBITS)。如果涉及到要传参,内核要求描述一下传输的方向,传输的方向是以应用层的角度来描述的。
1)_IOC_NONE:值为0,无数据传输。
2)_IOC_READ:值为1,从设备驱动读取数据。
3)_IOC_WRITE:值为2,往设备驱动写入数据。
4)_IOC_READ|_IOC_WRITE:双向数据传输。
4)数据大小:与体系结构相关,ARM下占14bit(_IOC_SIZEBITS),如果数据是int,内核给这个赋的值就是sizeof(int)。
强调一下,内核是要求按这样的方法把cmd分类,当然你也可以不这样干,这只是为了迎合内核的要求,让自己的程序看上去很正宗。上面我的程序没按要求照样运行。
既然内核这样定义cmd,就肯定有方法让用户方便定义:
_IO(type,nr) //没有参数的命令
_IOR(type,nr,size) //该命令是从驱动读取数据
_IOW(type,nr,size) //该命令是从驱动写入数据
_IOWR(type,nr,size) //双向数据传输
上面的命令已经定义了方向,我们要传的是幻数(type)、序号(nr)和大小(size)。在这里szie的参数只需要填参数的类型,如int,上面的命令就会帮你检测类型的正确然后赋值sizeof(int)。
有生成cmd的命令就必有拆分cmd的命令:
_IOC_DIR(cmd) //从命令中提取方向
_IOC_TYPE(cmd) //从命令中提取幻数
_IOC_NR(cmd) //从命令中提取序数
_IOC_SIZE(cmd) //从命令中提取数据大小
越讲就越复杂了,既然讲到这,随便就讲一下预定义命令。
预定义命令是由内核来识别并且实现相应的操作,换句话说,一旦你使用了这些命令,你压根也不要指望你的驱动程序能够收到,因为内核拿掉就把它处理掉了。
分为三类:
1)可用于任何文件的命令
2)只用于普通文件的命令
3)特定文件系统类型的命令
以上来自
http://blog.chinaunix.net/uid-25014876-id-59419.html五、高级cmd的诞生
cmd的基础知识看完了,那么就应该小试牛刀用一用,
1.首先还是看test.h
#ifndef __TEST_H
#define __TEST_H
#include <linux/ioctl.h>
#define TEST_MAGIC 'x'
#define MEM_CLEAR _IO(TEST_MAGIC, 0) /*清0全局内存*/
#endif
2.然后看mem_pool.h
#ifndef __MEM_POOL_H
#define __MEM_POOL_H
#include <linux/ioctl.h>
#define mem_pool_SIZE 0x2000 /*全局内存最大8K字节*/
/*mem_pool设备结构体*/
struct mem_pool_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[mem_pool_SIZE]; /*全局内存*/
};
#define TEST_MAGIC 'x'
#define MEM_CLEAR _IO(TEST_MAGIC, 0) /*清0全局内存*/
#define mem_pool_MAJOR 250 /*预设的mem_pool的主设备号*/
#endif
3.看我们的ioctl函数
/* ioctl设备控制函数 */
static int mem_pool_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
/*获得设备结构体指针*/
struct mem_pool_dev *dev = filp->private_data;
/*检验命令是否有效*/
if(_IOC_TYPE(cmd) != TEST_MAGIC) return - EINVAL;
switch (cmd)
{
case MEM_CLEAR:
memset(dev->mem, 0, mem_pool_SIZE);
printk( "mem_pool is set to zero\n");
break;
default:
printk( "cmd is error \n");
return - EINVAL;
}
return 0;
}
4.最后就是应用程序检测了test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*0. 包含命令定义 */
#include "test.h"
int main()
{
int fd = 0;
int cmd;
int arg = 0;
char buf[4096] = "xbdeng";
/*1. 打开设备文件*/
fd = open("/dev/mem_pool",O_RDWR);
if (fd < 0)
{
printf("Open Dev Mem0 Error!\n");
return -1;
}
printf("%s \n",buf);
/*2. *写文件*/
if(write(fd,buf,sizeof(buf)) < 0 )
{
printf("write error !! \n");
}
/*3. 注意,要读取内容,一定要记得定位*/
lseek(fd,0,SEEK_SET);
/*4. 读文件*/
if(read(fd,buf,sizeof(buf)) < 0 )
{
printf("read error !! \n");
}
printf("read fd is %s \n",buf);
/*5. 调用命令MEM_CLEAR */
printf("<--- Call MEM_CLEAR --->\n");
cmd = MEM_CLEAR;
if (ioctl(fd, cmd, &arg) < 0)
{
printf("Call cmd MEM_CLEAR fail\n");
return -1;
}
lseek(fd,0,SEEK_SET);
/*6. 再读文件*/
if(read(fd,buf,sizeof(buf)) < 0 )
{
printf("read error !! \n");
}
printf("%s \n",buf);
/*7. 关闭文件*/
close(fd);
return 0;
}
最后我们看结果,一模一样的
六、关于第三个参数arg
之前都没有讨论传参的问题,现在分析一下参数传递的问题
首先要明确的一点的是:在内核空间来说,任何从用户空间传进来的地址指针都要进行严格的检查,否则有可能出现意想不到的结果,就像我们之前的read和write函数一样
它是通过copy_from_user来进行数据交互的,毫无疑问,在这个函数中也有进行指针的检查。
1.首先是在mem_pool.h中
#ifndef __MEM_POOL_H
#define __MEM_POOL_H
#include <linux/ioctl.h>
#define mem_pool_SIZE 0x2000 /*全局内存最大8K字节*/
/*mem_pool设备结构体*/
struct mem_pool_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[mem_pool_SIZE]; /*全局内存*/
};
struct ioctl_data{
unsigned int size;
char buf[10];
};
#define TEST_MAGIC 'x'
#define MEM_SET _IO(TEST_MAGIC, 0) /*清0全局内存*/
#define mem_pool_MAJOR 250 /*预设的mem_pool的主设备号*/
#endif
声明了一个结构体,后面将用到
2.然后是在test.h中
#ifndef __TEST_H
#define __TEST_H
#include <linux/ioctl.h>
#define TEST_MAGIC 'x'
#define MEM_SET _IO(TEST_MAGIC, 0) /*清0全局内存*/
struct ioctl_data{
unsigned int size;
char buf[10];
};
#endif
这里我把用户空间和内核空间的东西分开了
3.在test.c中
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*0. 包含命令定义 */
#include "test.h"
struct ioctl_data mydata = {
.size = 10,
.buf = "my name is",
};
int main()
{
int fd = 0;
char buf[4096] = " xbdeng";
/*1. 打开设备文件*/
fd = open("/dev/mem_pool",O_RDWR);
if (fd < 0)
{
printf("Open Dev Mem0 Error!\n");
return -1;
}
/*5. 调用命令MEM_SET */
printf("<--- Call MEM_SET --->\n");
if (ioctl(fd, MEM_SET, &mydata) < 0)
{
printf("Call cmd MEM_SET fail\n");
return -1;
}
printf("%s \n",buf);
/*7. 关闭文件*/
close(fd);
return 0;
}
4.最后在底层驱动文件中
/* ioctl设备控制函数 */
static int mem_pool_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
/*获得设备结构体指针*/
struct mem_pool_dev *dev = filp->private_data;
struct ioctl_data pdata;
printk("mem_pool_ioctl \n");
/*检验命令是否有效*/
if(_IOC_TYPE(cmd) != TEST_MAGIC) return - EINVAL;
switch (cmd)
{
case MEM_SET:
if (copy_from_user(&pdata, (struct ioctl_data*)arg, sizeof(struct ioctl_data)))
//进行指针参数检测
return - EFAULT;
printk( "pdata.size = %d , pdata.buf = %s \n",pdata.size,pdata.buf);
break;
default:
printk( "cmd is error \n");
return - EINVAL;
}
return 0;
}
最后看完打印信息了,上面是内核打印信息,下面是用户空间打印信息,消息就是这么传进来的。
分享到:
相关推荐
linux设备驱动归纳总结(三):4.ioctl的实现 文章相关代码
Linux驱动drm_ioctl分析
linux3.16 驱动 unlocked_ioctl实现
zynq的linux驱动10-ioctl架构来控制led灯
Linux设备驱动程序学习(4)-高级字符驱动程序操作[(1)ioctl and llseek] - Linux设备驱动程序
·Linux设备驱动程序学习(6)-高级字符驱动程序操作[(3)设备文件的访问控制] ·Linux设备驱动程序学习(7)-内核的数据类型 ·Linux设备驱动程序学习(9)-与硬件通信 ·Linux设备驱动程序学习(8)-分配内存 ...
linux设备驱动的IOCTL功能介绍,学习总结,包含代码讲解
·Linux设备驱动程序学习(6)-高级字符驱动程序操作[(3)设备文件的访问控制] ·Linux设备驱动程序学习(7)-内核的数据类型 ·Linux设备驱动程序学习(9)-与硬件通信 ·Linux设备驱动程序学习(8)-分配内存 ...
linux内核驱动模块编写ioctl[归纳].pdf
linux下ioctl函数,这里说的ioctl函数是在驱动程序里的
主要实现从驱动到Android应用程序的逐层调用过程。关键是file_operations!! 通过Android应用程序,能够控制GPIO LED,写入数据,以及去读已写入的数据。 Android应用程序中有两个BUTTON用不上,无关紧要,这就不删掉...
第一天 1.Linux驱动简介 2.字符设备驱动程序设计 3.驱动调试技术 4. 并发与竞态 第二天 1.Ioctl型驱动 2.内核等待队列 3. 阻塞型驱动程序设计 4.Poll设备操作 第三天 1.Mmap设备操作 2. 硬件访问 3. 混杂...
写一完整驱动, 加上read, write, ioctl, polling等各种函数的驱动实现。 在ioctl里完成从用户空间向内核空间传递结构体的实现。 6. 写一block驱动, 加上read,write,ioctl,poll等各种函数实现。 7. 简单学习下内存...
linux的rtl8188gu无线网卡驱动,可以解决8188gu芯片在linux无法识别的问题。解压后使用make命令
十分适合linux驱动初学者,为今后Android驱动开发打下坚实的基础 (一):内核的相关基础概念 (二):模块的相关基础概念 (三):1_字符型设备之设备申请 (三):2_字符型设备的操作open、close、read、write ...
rtl81888eu linux驱动 deepin,startos完美编译 ubuntu14.1不知为何编译失败```
关于如何为Linux创建IOCTL驱动程序的简单示例。 这可以用于简单的测试目的: 从内核模式访问特殊寄存器以在用户模式下获得结果。 例如,CP15处理器的Arm寄存器。 访问某些内存映射区域 ... 此仓库包含以下文件夹...
在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源),可能会引发\"竞态\",因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量...
详细阐述Linux下驱动开发的ioctl函数的使用和注意事项
在Linux内核中,Framebuffer(帖缓冲)驱动...其中Frmaebuffer驱动接口为fbmem.c,此文件提供了LCD驱动的通用文件操作接口,如read 、write、 ioctl等应用程序可能应用到的文件接口,特定平台的LCD驱动程序可以实现自己的