《使用ioctl与内核交换数据》由会员分享,可在线阅读,更多相关《使用ioctl与内核交换数据(4页珍藏版)》请在金锄头文库上搜索。
1、使用使用 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(incl
2、ude/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 虽然是
3、一 个整数,但是有一定的参数格式的,下面再详细说明;第三个参数是数据起始位置指针, 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) #d
4、efine NEWCHAR_GET _IO(NEWCHAR_IOC_MAGIC, 1) #define NEWCHAR_IOC_MAXNR 1要定义自己的 ioctl 操作,可以有两个方式,一种是在现有的内核代码中直接添加相关代码 进行支持,比如想通过 socket 描述符进行 ioctl 操作,可在 net/ipv4/af_inet.c 中的 inet_ioctl()函数中添加自己定义的命令和相关的处理函数,重新编译内核即可, 不过这种 方法一般不推荐;第二种方法是定义自己的内核设备,通过设备的 ioctl()来操作,可以编 成模块,这样不影响原有的内核,这是最通常的做法。4. 内核设备为进
5、行 ioctl 操作最通常是使用字符设备来进行,当然定义其他类型的设备也可以。在用户 空间,可使用 mknod 命令建立一个字符类型设备文件,假设该设备的主设备号为 123,次 设备号为 0: mknode /dev/newchar c 123 0 如果是编程的话,可以用 mknode(2)函数来建立设备文件。建立设备文件后再将该设备的内核模块文件插入内核,就可以使用 open(2)打开 /dev/newchar 文件,然后调用 ioctl(2)来传递数据,最后用 close(2)关闭设备。而如果内核 中还没有插入该设备的模块,open(2)时就会失败。由于内核内存空间和用户内存空间不同,要将
6、内核数据拷贝到用户空间,要使用专用拷贝 函数 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 *f
7、ilep); 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 #def
8、ine 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;
9、 进行 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
10、 KNEWCHAR_SET: / 设置操作,将数据从用户空间拷贝到内核空间 struct newchar nc; if(copy_from_user( ret = do_set_newchar( break; case KNEWCHAR_GET: / GET 操作通常会在数据缓冲区中先传递部分初始值作为数据查找条件,获取全部 / 数据后重新写回缓冲区 / 当然也可以根据具体情况什么也不传入直接向内核获取数据 struct newchar nc; if(copy_from_user( ret = do_get_newchar( if(ret = 0) if(copy_to_user(unsign
11、ed char *)arg, 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, if (result 0) printk(KERN_WARNING _FUNCTION_ “: failed register character device
12、 for /dev/newcharn“); 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/newcharn“);return; module_init(_init); module_exit(_cleanup);5. 结论用 ioctl()在用户空间和内核空间传递数据是最常用方法之一,比较简单方便,而且可以在 同一个 ioctl 中对不同的命令传送不同的数据结构,本文只是为描述方便而在不同命令中使 用了相同的数据结构。