ioctl(fdAcodec, ACODEC_GET_ADCL_VOL, &vol_ctrl)//从内核驱动中获取或者设置数据
//内核驱动中也使用ACODEC_GET_ADCL_VOL进行case,因为这个cmd中的魔数和基数都是系统函数IOWR定义的,内核驱动就会知道。
#define ACODEC_GET_ADCL_VOL \ //ACODEC_GET_ADCL_VOL这个CMD等同于_IOWR(... ),使用该函数定义CMD _IOWR(IOC_TYPE_ACODEC, IOC_NR_GET_ADCL_VOL, ACODEC_VOL_CTRL)
_IOWR(IOC_TYPE_ACODEC, IOC_NR_GET_DACL_VOL, ACODEC_VOL_CTRL)//IOC_TYPE_ACODEC 魔数 IOC_NR_GET_DACL_VOL基数 ...
//ACODEC_VOL_CTRL变量型,使用 arg 变量指定传送的数据大小,但是不直接代入输入,而是代入变量或者是变量的类型
int ioctl(int fd, int cmd, void *data)
_IO (魔数, 基数);
_IOR (魔数, 基数, 变量型)
_IOW (魔数, 基数, 变量型)
_IOWR (魔数, 基数,变量型 )
设备驱动程序从传递进来的命令获取魔数,然后与自身处理的魔数想比较,如果相同则处理,不同则不处理
基数用于区别各种命令。通常,从 0开始递增
变量型使用 arg 变量指定传送的数据大小,但是不直接代入输入,而是代入变量或者是变量的类型

ioctl与内核交换数据
1. 前言

使用ioctl系统调用是用户空间向内核交换数据的常用方法之一,从ioctl这个名称上看,本意是针对I/O设备进 行的控制操作,但实际并不限制是真正的I/O设备,可以是任何一个内核设备即可。

2. 基本过程

在内核空间中ioctl是很多内核操作结构的一个成员函数,如文件操作结构struct file_operations(include/linux/fs.h)、协议操作结构struct proto_ops(include/linux/net.h)等、tty操作结构struct tty_driver(include/linux/tty_driver.h)等,而这些操作结构分别对应各种内核设备,只要在用户空间打开这些设备, 如I/O设备可用open(2)打开,网络协议可用socket(2)打开等,获取一个文件描述符后,就可以在这个描述符上调用ioctl(2)来向内核 交换数据。

3. ioctl(2)

ioctl(2)函数的基本使用格式为:
int ioctl(int fd, int cmd, void *data)
第一个参数是文件描述符;cmd是操作命令,一般分为GET、SET以及其他类型命令,GET 是用户空间进程从内核读数据,SET是用户空间进程向内核写数据,cmd虽然是一个整数,但是有一定的参数格式的,下面再详细说明;第三个参数是数据起始 位置指针,
cmd命令参数是个32位整数,分为四部分:
dir(2b) size(14b) type(8b) nr(8b)
详 细定义cmd要包括这4个部分时可使用宏_IOC(dir,type,nr,size)来定义,而最简单情况下使用_IO(type, nr)来定义就可以了,这些宏都在include/asm/ioctl.h中定义
本文cmd定义为:
#define NEWCHAR_IOC_MAGIC 'M'
#define NEWCHAR_SET _IO(NEWCHAR_IOC_MAGIC, 0)
#define NEWCHAR_GET _IO(NEWCHAR_IOC_MAGIC, 1)
#define NEWCHAR_IOC_MAXNR 1

要定义自己的ioctl操作,可以有两个方式,一种是在现有的内核代码中直接添加相关代码进行支持,比如想通过socket描述符进行 ioctl操作,可在net/ipv4/af_inet.c中的inet_ioctl()函数中添加自己定义的命令和相关的处理函数,重新编译内核即可, 不过这种方法一般不推荐;第二种方法是定义自己的内核设备,通过设备的ioctl()来操作,可以编成模块,这样不影响原有的内核,这是最通常的做法。

4. 内核设备

为进行ioctl操作最通常是使用字符设备来进行,当然定义其他类型的设备也可以。在用户空间,可使用mknod命令建立一个 字符类型设备文件,假设该设备的主设备号为123,次设备号为0:
mknode /dev/newchar c 123 0
如果是编程的 话,可以用mknode(2)函数来建立设备文件。

建立设备文件后再将该设备的内核模块文件插入内核,就可以使用open(2)打开 /dev/newchar文件,然后调用ioctl(2)来传递数据,最后用close(2)关闭设备。而如果内核中还没有插入该设备的模 块,open(2)时就会失败。

由于内核内存空间和用户内存空间不同,要将内核数据拷贝到用户空间,要使用专用拷贝函数 copy_to_user();要将用户空间数据拷贝到内核,要使用copy_from_user()。
要最简单实现以上功能,内核模块只需要实 现设备的open, ioctl和release三个函数即可,
下面介绍程序片断:
static int newchar_ioctl(struct inode *inode, struct file *filep,
unsigned int cmd, unsigned long arg);
static int newchar_open(struct inode *inode, struct file *filep);
static int newchar_release(struct inode *inode, struct file *filep);
// 定义文件操作结构,结构中其他元素为空
struct file_operations newchar_fops =
{
owner: THIS_MODULE,
ioctl: newchar_ioctl,
open: newchar_open,
release: newchar_release,
};
// 定义要传输的数据块结构
struct newchar{
int a;
int b;
};
#define MAJOR_DEV_NUM 123
#define DEVICE_NAME "newchar"

打开设备,非常简单,就是增加 模块计数器,防止在打开设备的情况下删除模块,
当然想搞得复杂的话可进行各种限制检查,如只允许指定的用户打开等:
static int newchar_open(struct inode *inode, struct file *filep)
{
MOD_INC_USE_COUNT;
return 0;
}

关闭设备,也很简单,减模块计数器:
static int newchar_release(struct inode *inode, struct file *filep)
{
MOD_DEC_USE_COUNT;
return 0;
}

进行ioctl调用的基本处理函数
static int newchar_ioctl(struct inode *inode, struct file *filep,
unsigned int cmd, unsigned long arg)
{
int ret;
// 首先检查cmd是否合法
if (_IOC_TYPE(cmd) != NEWCHAR_IOC_MAGIC) return -EINVAL;
if (_IOC_NR(cmd) > NEWCHAR_IOC_MAXNR) return -EINVAL;
// 错误情况下的缺省返回值
ret = EINVAL;
switch(cmd)
{
case KNEWCHAR_SET:
// 设置操作,将数据从用户空间拷贝到内核空间
{
struct newchar nc;
if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)
return -EFAULT;
ret = do_set_newchar(&nc);
}
break;
case KNEWCHAR_GET:
// GET操作通常会在数据缓冲区中先传递部分初始值作为数据查找条件,获取全部
// 数据后重新写回缓冲区
// 当然也可以根据具体情况什么也不传入直接向内核获取数据
{
struct newchar nc;
if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)
return -EFAULT;
ret = do_get_newchar(&nc);
if(ret == 0){
if(copy_to_user((unsigned char *)arg, &nc, sizeof(nc))!=0)
return -EFAULT;
}
}
break;
}
return ret;
}
模块初始化函数,登记字符设备
static int __init _init(void)
{
int result;
// 登记该字符设备,这是2.4以前的基本方法,到2.6后有了些变化,
// 是使用MKDEV和cdev_init()来进行,本文还是按老方法
result = register_chrdev(MAJOR_DEV_NUM, DEVICE_NAME, &newchar_fops);
if (result < 0) {
printk(KERN_WARNING __FUNCTION__ ": failed register character device for /dev/newchar\n");
return result;
}
return 0;
}
模块退出函数,登出字符设备
static void __exit _cleanup(void)
{
int result;
result = unregister_chrdev(MAJOR_DEV_NUM, DEVICE_NAME);
if (result < 0)
printk(__FUNCTION__ ": failed unregister character device for /dev/newchar\n");
return;
}
module_init(_init);
module_exit(_cleanup);

5. 结论

用ioctl()在用户空间和内核空间传递数据是最常用方法之一,比较简单方便,而且可以在同一个ioctl中对不同的命令传送不 同的数据结构,本文只是为描述方便而在不同命令中使用了相同的数据结构。

cmd
在驱动程序里, ioctl() 函数上传送的变量 cmd 是应用程序用于区别设备驱动程序请求处理内容的值。cmd除了可区别数字外,还包含有助于处理的几种相应信息。 cmd的大小为 32位,共分 4 个域:
bit31~bit30 2位为 “区别读写” 区,作用是区分是读取命令还是写入命令。
bit29~bit15 14位为 "数据大小" 区,表示 ioctl() 中的 arg 变量传送的内存大小。
bit20~bit08 8位为 “魔数"(也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
bit07~bit00 8位为 "区别序号" 区,是区分命令的命令顺序序号。
像 命令码中的 “区分读写区” 里的值可能是 _IOC_NONE (0值)表示无数据传输,_IOC_READ (读), _IOC_WRITE (写) , _IOC_READ|_IOC_WRITE (双向)。
内核定义了 _IO() , _IOR() , IOW() 和 _IOWR() 这 4 个宏来辅助生成上面的 cmd 。下面分析 _IO() 的实现,其它的类似:< xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />

在 asm-generic/ioctl.h 里可以看到 _IO() 的定义:

#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)

再看 _IOC() 的定义:

#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))

可见,_IO() 的最后结果由 _IOC() 中的 4 个参数移位组合而成。
再看 _IOC_DIRSHIT 的定义:

#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)

_IOC_SIZESHIFT 的定义:

#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)

_IOC_TYPESHIF 的定义:

#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)

_IOC_NRSHIFT 的定义:

#define _IOC_NRSHIFT 0

_IOC_NRBITS 的定义:

#define _IOC_NRBITS 8

_IOC_TYPEBITS 的定义:

#define _IOC_TYPEBITS 8

由上面的定义,往上推得到:

引 用

_IOC_TYPESHIFT = 8

_IOC_SIZESHIFT = 16

_IOC_DIRSHIFT = 30

所以,(dir) << _IOC_DIRSHIFT) 表是 dir 往左移 30 位,即移到 bit31~bit30 两位上,得到方向(读写)的属性;
(size) << _IOC_SIZESHIFT) 位左移 16 位得到“数据大小”区;
(type) << _IOC_TYPESHIFT) 左 移 8位得到"魔数区" ;
(nr) << _IOC_NRSHIFT) 左移 0 位( bit7~bit0) 。
这样,就得到了 _IO() 的宏值。

这几个宏的使用格式为:
?_IO (魔数, 基数);
?_IOR (魔数, 基数, 变量型)
?_IOW (魔数, 基数, 变量型)
?_IOWR (魔数, 基数,变量型 )

魔数 (magic number)
魔数范围为 0~255 。通常,用英文字符 "A" ~ "Z" 或者 "a" ~ "z" 来表示。设备驱动程序从传递进来的命令获取魔数,然后与自身处理的魔数想比较,如果相同则处理,不同则不处理。魔数是拒绝误使用的初步辅助状态。设备驱动 程序可以通过 _IOC_TYPE (cmd) 来获取魔数。不同的设备驱动程序最好设置不同的魔数,但并不是要求绝对,也是可以使用其他设备驱动程序已用过的魔数。
基(序列号)数
基数用于区别各种命令。通常,从 0开始递增,相同设备驱动程序上可以重复使用该值。例如,读取和写入命令中使用了相同的基数,设备驱动程序也能分辨出来,原因在于设备驱动程序区分命令时 使用 switch ,且直接使用命令变量 cmd值。创建命令的宏生成的值由多个域组合而成,所以即使是相同的基数,也会判断为不同的命令。设备驱动程序想要从命令中获取该基数,就使用下面的宏:
_IOC_NR (cmd)
通常,switch 中的 case 值使用的是命令的本身。
变量型
变量型使用 arg 变量指定传送的数据大小,但是不直接代入输入,而是代入变量或者是变量的类型,原因是在使用宏创建命令,已经包含了 sizeof() 编译命令。比如 _IOR() 宏的定义是:

引用

#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))

而 _IOC_TYPECHECK() 的定义正是:

引用

#define _IOC_TYPECHECK(t) (sizeof(t))

设备驱动程序想要从传送的命令获取相应的值,就要使用下列宏函数:
_IOC_SIZE(cmd)

_IO 宏

该宏函数没有可传送的变量,只是用于传送命令。例如如下约定:

引用

#define TEST_DRV_RESET _IO ('Q', 0)

此时,省略由应用程序传送的 arg 变量或者代入 0 。在应用程序中使用该宏时,比如:

ioctl (dev, TEST_DEV_RESET, 0) 或者 ioctl (dev, TEST_DRV_RESET) 。
这是因为变量的有效因素是可变因素。只作为命令使用时,没有必要判 断出设备上数据的输出或输入。因此,设备驱动程序没有必要执行设备文件大开选项的相关处理。

_IOR 宏
该函数用 于创建从设备读取数据的命令,例如可如下约定:

引用

#define TEST_DEV_READ _IRQ('Q', 1, int)

这说明应用程序从设备读取数据的大小为 int 。下面宏用于判断传送到设备驱动程序的 cmd 命令的读写状态:
_IOC_DIR (cmd)
运行该宏时,返回值的类型 如下:
?_IOC_NONE : 无属性
?_IOC_READ : 可读属性
?_IOC_WRITE : 可写属性
?_IOC_READ | _IOC_WRITE : 可读,可写属性

使用该命令时,应用程序的 ioctl() 的 arg 变量值指定设备驱动程序上读取数据时的缓存(结构体)地址。
_IOW 宏
用于创建设 备上写入数据的命令,其余内容与 _IOR 相同。通常,使用该命令时,ioctl() 的 arg 变量值指定设备驱动程序上写入数据时的缓存(结构体)地址。
_IOWR 宏
用于创建设备上读写数据的命令。其余内 容与 _IOR 相同。通常,使用该命令时,ioctl() 的 arg 变量值指定设备驱动程序上写入或读取数据时的缓存 (结构体) 地址。
_IOR() , _IOW(), IORW() 的定义:
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

Linux之ioctl20160705的更多相关文章

  1. Linux 驱动开发

    linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...

  2. Linux 内核概述 - Linux Kernel

    Linux 内核学习笔记整理. Unix unix 已有40历史,但计算机科学家仍认为其是现存操作系统中最大和最优秀的系统,它已成为一种传奇的存在,历经时间的考验却依然声名不坠. 1973 年,在用 ...

  3. 死磕内存篇 --- JAVA进程和linux内存间的大小关系

    运行个JAVA 用sleep去hold住 package org.hjb.test; public class TestOnly { public static void main(String[] ...

  4. NodeJs在Linux下使用的各种问题

    环境:ubuntu16.04 ubuntu中安装NodeJs 通过apt-get命令安装后发现只能使用nodejs,而没有node命令 如果想避免这种情况请看下面连接的这种安装方式: 拓展见:Linu ...

  5. [linux]阿里云主机的免登陆安全SSH配置与思考

    公司服务器使用的第三方云端服务,即阿里云,而本地需要经常去登录到服务器做相应的配置工作,鉴于此,每次登录都要使用密码是比较烦躁的,本着极速思想,我们需要配置我们的免登陆. 一 理论概述 SSH介绍 S ...

  6. Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part3:db安装和升级

    Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part3:db安装和升级 环境:OEL 5.7 + Oracle 10.2.0.5 RAC 5.安装Database软件 5. ...

  7. Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part1:准备工作

    Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part1:准备工作 环境:OEL 5.7 + Oracle 10.2.0.5 RAC 1.实施前准备工作 1.1 服务器安装操 ...

  8. SQL Server on Linux 理由浅析

    SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...

  9. Microsoft Loves Linux

    微软新任CEO纳德拉提出的“Microsoft Loves Linux”,并且微软宣布.NET框架的开源,近期Microsoft不但宣布了Linux平台的SQL Server,还宣布了Microsof ...

随机推荐

  1. Siki_Unity_1-3_Unity零基础入门_古迹探险

    1-3 Unity零基础入门 古迹探险 任务1/2:资料下载 链接:https://pan.baidu.com/s/1jHVymNk 密码:rbob 任务3:工程的创建和打开 Project:古迹探险 ...

  2. [JSON].set(keyPath, value)

    语法:[JSON].set( keyPath, value ) 返回:[True | False] 说明:设置键值 参数: keyPath    [keyPath 必需] 键名路径字符串 value ...

  3. Android开发-API指南-<permission-tree>

    <permission-tree> 英文原文:http://developer.android.com/guide/topics/manifest/permission-tree-elem ...

  4. Android开发-API指南-<permission>

    <permission> 英文原文:http://developer.android.com/guide/topics/manifest/permission-element.html 采 ...

  5. 线性代数之——对角化和 A 的幂

    利用特征向量的属性,矩阵 \(A\) 可以变成一个对角化矩阵 \(\Lambda\). 1. 对角化 假设一个 \(n×n\) 的矩阵 \(A\) 有 \(n\) 个线性不相关的特征向量 \(x_1, ...

  6. spring boot 下使用@ConponentScan注解遇到的问题

    问题描述 如果你心急看结果,请直接到本文末尾 今天使用了注解操作spring boot,一开始程序无法启动,提示无法找到一个注解注入的类,查询网上,有人说使用@ConponetScan注解,可以指定需 ...

  7. selenium元素定位不到之iframe---基于python

    我们在使用selenium的18中定位方式的时候,有时会遇到定位不上的问题,今天我们就来说说导致定位不上的其中一个原因---iframe 问题描述:通过firebug查询到相应元素的id或name等, ...

  8. centos环境配置(nginx,node.js,mysql)

    1.安装 Install GCC and Development Tools on a CentOS yum group install "Development Tools" n ...

  9. 如何在Python 2.X中也达到类似nonlocal关键字的效果

    nonlocal关键字时Python 3.X中引入的,目的是让内层函数可以修改外层函数的变量值,而该关键字在Python 2.X中是不存在的.那么,要在Python 2.X中达到类型达到类似nonlo ...

  10. C++基础和STL,Effective C++笔记

    这个作者总结的c++基础,特别好. 可以看看. http://blog.csdn.net/tham_/article/details/51169792