Linux内核模块编程-与设备文件对话
2015-11-15 16:39
477 查看
与设备文件对话
在上一篇博文中,我们编写了一个字符设备驱动,简单的回顾下我们编写的流程:实现open/close/read/write四个操作设备文件的函数
填充file_operations结构体
注册设备和指明操作设备的file_operations
与设备之间可以通过上面提到的几个函数来进行通信,但是对于某些设备来说,设备的专有特性操作无法通过上面提到的几个函数来实现
ioctl的一些简介:
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的参数个数如下:int ioctl(int fd, int cmd, …);其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就能在用户程序中使用ioctl函数控制设备的I/O通道。
来自百度百科
ioctl的功能:
控制I/O设备 ,提供了一种获得设备信息和向设备发送控制参数的手段。用于向设备发控制和配置命令 ,有些命令需要控制参数,这些数据是不能用read/write 读写的,称为Out-of-band数据。也就是说,read/ write 读写的数据是in-band数据,是I/O操作的主体,而ioctl 命令传送的是控制信息,其中的数据是辅助的数据。
来自百度百科
要想全面的去控制一个设备,那么在内核中实现ioctl和标准的VFS文件操作接口,这都应该是必须的。那么本篇博文其实主要讲的就是如何通过ioctl来和设备文件通信。
开始去实现
ioctl
ioctl中的其实最重要的就是cmd和参数,分别对比下用户空间和内核空间关于ioctl相关的函数原型用户空间: #include <sys/ioctl.h> int ioctl(int fd, int cmd, ...); fd是设备文件的描述符 cmd是发送给设备的命令,如果这个命令带有参数,后面还会接参数。 内核空间: int (struct file *file,unsigned int ioctl_num,unsigned long ioctl_param) file对应打开的设备文件 ioctl_num 对应用户空间的cmd ioctl_param则对应于cmd指定的参数
那么就存在一个问题了,怎么知道哪些cmd带有参数,哪写不带参数,以及参数的类型。
cmd可以随便定义吗?
其实cmd的定义是有规定的,cmd大小为32位,这32位分成了四个域:
bit31~bit30 2位为 “区别读写” 区,作用是区分是读取命令还是写入命令。
bit29~bit15 14位为 “数据大小” 区,表示 ioctl() 中的 arg 变量传送的内存大小。
bit20~bit08 8位为 “魔数”(也称为”幻数”)区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
bit07~bit00 8位为 “区别序号” 区,是区分命令的命令顺序序号。
那么如果要去定义个cmd,那么就的按照这个规定来定义,需要自己去计算。幸好在内核空间提供了一系列的
函数帮我们生成cmd,还有一些列的函数可以帮我们解析出cmd的每一个域。
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1) #define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1) #define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1) #define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1) #define _IOC_NRSHIFT 0 #define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) #define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) #define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) #define _IOC(dir,type,nr,size) \ (((dir) << _IOC_DIRSHIFT) | \ ((type) << _IOC_TYPESHIFT) | \ ((nr) << _IOC_NRSHIFT) | \ ((size) << _IOC_SIZESHIFT)) //一系列用来定义cmd的函数 /* used to create numbers */ #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0) #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))) #define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size)) #define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size)) #define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size)) //一系列用来解析cmd的函数 /* used to decode ioctl numbers.. */ #define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK) #define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK) #define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK) #define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
介绍完ioctl后,那就开始去定义一些cmd吧
chardev1.h #ifndef CHARDEV_H #define CHARDEV_H #include <asm-generic/ioctl.h> #define IO_MAGIC 'z' //这是魔数,参照上面的解释,通常是a-z A-Z范围内 #define IOCTL_SET_MSG _IOR(IO_MAGIC,0,char*) //定义了一个接受char*参数的命令码 #define IOCTL_GET_MSG _IOR(IO_MAGIC,1,char*) #define IOCTL_GET_NTH_BYTE _IOWR(IO_MAGIC,2,int) //定义了一个接受int型参数的命令码 #define DEVICE_FILE_NAME "char_dev" #endif
这个chardev1.h 不仅是给内核模块使用的,用户空间也要使用,因为要给设备发ioctl cmd的时候,需要
知道cmd的定义。所以这个文件很重要。
老生常谈的模块初始化和注销
和我上一篇博文,关于字符设备驱动的代码基本一样,只不过这里是通过ioctl来控制读写的。#define SUCCESS 0 #define DEVICE_NAME "char_dev" #define BUF_LEN 80 static int Device_Open = 0; static int MAJOR_NUM; static char Message[BUF_LEN]; static char *Message_Ptr; int init_module(void) { //随机生成 MAJOR_NUM = register_chrdev(0,DEVICE_NAME,&Fops); if(MAJOR_NUM < 0) { printk("%s failed with %d\n", "Sorry,registering the character device",MAJOR_NUM); return MAJOR_NUM; } printk("%s The major device number is %d\n", "Register is a success",MAJOR_NUM); printk("If you want to talk to the device driver,\n"); printk("you'll have to create a device file\n"); printk("we suggest you use:\n"); printk("mknod %s c %d 0\n",DEVICE_NAME,MAJOR_NUM); printk("the device file name is important,because\n"); printk("the ioctl program assumes that's the \n"); printk("file you'll use\n"); return 0; } void cleanup_module(void) { unregister_chrdev(MAJOR_NUM,DEVICE_NAME); }
read/write/open/release的实现
static int device_open(struct inode *inode,struct file *file) { printk("device_open(%p)\n",file); if (Device_Open) return -EBUSY; Device_Open++; Message_Ptr = Message; try_module_get(THIS_MODULE); return SUCCESS; } static int device_release(struct inode *inode,struct file *file) { printk("device_release(%p,%p)\n",inode,file); Device_Open--; module_put(THIS_MODULE); return SUCCESS; } static ssize_t device_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) { int bytes_read = 0; printk("device_read(%p,%p,%d)\n",file,buffer,length); if(*Message_Ptr == 0) return 0; while(length && *Message_Ptr) { put_user(*(Message_Ptr++),buffer++); length--; bytes_read++; } return bytes_read; } static ssize_t device_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) { int i; for(i = 0;i < length && i < BUF_LEN;i++) get_user(Message[i],buffer+i); Message_Ptr = Message; return i; }
最重要的ioctl的实现
int device_ioctl(struct file *file, unsigned int ioctl_num, unsigned long ioctl_param) { int i; char *temp; char ch; //典型的switch case分发命令 switch(ioctl_num) { case IOCTL_SET_MSG: temp = (char*)ioctl_param; //获取到参数 get_user(ch,temp); //不断读取数据到ch中,统计ioctl_param的长度用i表示 for(i = 0;ch && i < BUF_LEN;i++,temp++) get_user(ch,temp); //最终调用write来将用户态的数据写入到设备中 device_write(file,(char*)ioctl_param,i,0); break; case IOCTL_GET_MSG: i = device_read(file,(char*)ioctl_param,99,0);//写入数据,最多写入99字节的数据 put_user('\0',(char*)ioctl_param+i); //设置用户空间bu中的第i个位置为'\0'用来结束 break; case IOCTL_GET_NTH_BYTE: return Message[ioctl_param];//获取指定位置处的字符 } return SUCCESS; }
注册VFS接口
struct file_operations Fops = { .read = device_read, .write = device_write, .unlocked_ioctl = device_ioctl, //这里需要注意,用的是unlocked_ioctl而不是ioctl //因为在kernel 2.6.36 中已经完全删除了struct file_operations中的ioctl 函数指针,取而代之的是unlocked_ioctl .open = device_open, .release = device_release, };
用户空间程序测试
首先需要编译,产生内核模块。然后载入内核模块,根据打印的信息获取分配的主设备号,然后创建该设备insmod chardev1.ko dmesg查看打印信息 Register is a success The major device number is 247 If you want to talk to the device driver, you'll have to create a device file we suggest you use: mknod char_dev c 247 0 the device file name is important,because the ioctl program assumes that's the file you'll use 创建字符设备 mknod /dev/char_dev c 247 0
用户空间程序测试
#include "chardev1.h" #include <fcntl.h> #include <sys/ioctl.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> void ioctl_set_msg(int file_desc,char *message) { int ret_val; ret_val = ioctl(file_desc,IOCTL_SET_MSG,message); if (ret_val < 0) { printf("ioctl_set_msg failed:%d\n",ret_val); exit(-1); } } void ioctl_get_msg(int file_desc) { int ret_val; char message[100]; ret_val = ioctl(file_desc,IOCTL_GET_MSG,message); if (ret_val < 0) { printf("ioctl_get_msg failed:%d\n",ret_val); exit(-1); } printf("get_msg message:%s\n",message); } void ioctl_get_nth_byte(int file_desc) { int i; char c='m'; //最好给个初值,在我的机器上居然好几次初值都是0.导致while循环跳出 printf("get_nth_byte message:"); i = 0; while(c != 0) { c = ioctl(file_desc,IOCTL_GET_NTH_BYTE,i++); if(c < 0) { printf("ioctl_get_nth_byte failed at the %d'th byte:\n",i); exit(-1); } putchar(c); } putchar('\n'); } int main() { int file_desc,ret_val; char *msg = "Message passwd by ioctl\n"; file_desc = open("/dev/char_dev",0); if(file_desc < 0) { printf("can't open device file: %s\n",DEVICE_FILE_NAME); exit(-1); } // ioctl_get_nth_byte(file_desc); // ioctl_get_msg(file_desc); // ioctl_set_msg(file_desc,msg); close(file_desc); } 对于上面注释的三条语句,可以依次打开 查看效果,ioctl_get_msg获取设备数据的,但是第一次获取是空 通过ioctl_set_msg设置后,就可以获取到msg指向的内容。ioctl_get_nth_byte则是一个字符字符的获取打印。