1、设备ioctl
2、内核等待队列
3、阻塞型字符设备
4、poll设备操作
5、自动创建设备文件
1、设备ioctl
作用:个人认为就是用来传递参数和命令到内核驱动,内核驱动程序接收到这个命令之后进行控制设备。
用户态函数原型:
int ioctl(int fd, unsigned long cmd, ...)
内核空间驱动原型:
int (*ioctl)(struct inode *inode, struct file *filep, unsigned int cmd, unsigned long arg);
cmd参数就是用户传递下来的参数cmd,arg是一个整数或者一个指针,反正只能以整数的形式表示。
使用:如果你很懒的话,直接在用户态下int ioctl(fd, 0x1),内核态下直接读cmd的值
int chardev_ioctl(struct inode *inode, struct file *filep, unsigned int cmd, unsigned long arg) { switch(cmd) { case 0x1; 。。。 } }
注意:如果cmd是一个整数,可以直接使用。如果是指针,我们必须确保这个用户地址是有效的,因此使用前需要进行正确性检查。
如果arg是一个地址:有两种使用情况:
一种不需要检查:copy_from_user copy_to_user,get_user,put_user(因为函数本身已经帮你检查了)
另一种情况需要检查:__get_user,__put_user(此时函数未进行检查)
怎么检查呢??
int access_ok(int type,const void*addr,unsigned long size).第一个参数用宏VERIFY_READ或者VERIFY_WRITE,来检查这个用户空间是否能读或者写。所以说用户空间一般需要先malloc一个空间,然后在把地址下发进来。如果ioctl需要从用户空间读一个整数,那么size参数等于sizeof(int),就是检查一个整数长度的空间的有效性。注意acess_ok返回1是成功的,0是失败的。
但是一般不建议这样使用,这样是不合规的,我们需要符合规则的定义使用这些命令:
定义命令:就是定义一个稍微有意义的整数作为命令,比如说不同的位表示不同的意义:类型(幻数)8位,序号8位,传送方向,参数的大小。
内核提供了下列宏来帮助定义命令:
_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)
实例代码memdev.c
#include <linux/config.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/types.h> #include <linux/proc_fs.h> #include <asm/system.h> #include <asm/uaccess.h> /*使用copy_to_user,copy_from_user必须要包含该文件*/ #include "memdev.h" MODULE_LICENSE("GPL"); Mem_Dev *mem_devices; int memdev_major = MEMDEV_MAJOR; /*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 -ENOTTY; if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR) return -ENOTTY; /* 根据命令类型,检测参数空间是否可以访问 */ 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 -ENOTTY; } return ret; } int memdev_open(struct inode *inode, struct file *filp) { Mem_Dev *dev; /*获取次设备号*/ int num = MINOR(inode->i_rdev); dev = (Mem_Dev *)filp->private_data; if (!dev) { if (num >= MEMDEV_NR_DEVS) return -ENODEV; dev = &mem_devices[num]; filp->private_data = dev; } /*增加模块引用计数*/ MOD_INC_USE_COUNT; return 0; } /*设备关闭*/ int memdev_release(struct inode *inode, struct file *filp) { MOD_DEC_USE_COUNT; return 0; } /* * The following wrappers are meant to make things work with 2.0 kernels */ struct file_operations memdev_fops = { ioctl: memdev_ioctl, open: memdev_open, release: memdev_release, }; /*卸载函数*/ void memdev_cleanup_module(void) { int i; /*注销字符设备*/ unregister_chrdev(memdev_major, "memdev"); /*释放内存*/ if (mem_devices) { for (i=0; i<MEMDEV_NR_DEVS; i++) kfree(mem_devices[i].data); kfree(mem_devices); } } /*加载函数*/ int memdev_init_module(void) { int result, i; /*设置模块owner*/ SET_MODULE_OWNER(&memdev_fops); /*注册字符设备*/ result = register_chrdev(memdev_major, "memdev", &memdev_fops); if (result < 0) { printk(KERN_WARNING "mem: can't get major %d\n",memdev_major); return result; } if (memdev_major == 0) memdev_major = result; /*为设备描述结构分配内存*/ mem_devices = kmalloc(MEMDEV_NR_DEVS * sizeof(Mem_Dev), GFP_KERNEL); if (!mem_devices) { result = -ENOMEM; goto fail; } memset(mem_devices, 0, MEMDEV_NR_DEVS * sizeof(Mem_Dev)); /*为设备分配内存*/ for (i=0; i < MEMDEV_NR_DEVS; i++) { mem_devices[i].size = MEMDEV_SIZE; mem_devices[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL); memset(mem_devices[i].data, 0, MEMDEV_SIZE); sema_init(&mem_devices[i].sem, 1); } return 0; fail: mem_cleanup_module(); return result; } module_init(memdev_init_module); module_exit(memdev_cleanup_module);
memdev.h
#ifndef _MEMDEV_H_ #define _MEMDEV_H_ #include <linux/ioctl.h> #ifndef MEMDEV_MAJOR #define MEMDEV_MAJOR 0 #endif #ifndef MEMDEV_NR_DEVS #define MEMDEV_NR_DEVS 3 #endif #ifndef MEMDEV_SIZE #define MEMDEV_SIZE 4096 #endif typedef struct Mem_Dev { char *data; struct Mem_Dev *next; /* next listitem */ unsigned long size; struct semaphore sem; /* 定义信号量 */ } Mem_Dev; /* 定义幻数 */ #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_ */
ioctl_app.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; } /* 调用命令MEM_IOCPRINT */ printf("<--- Call MEM_IOCPRINT --->\n"); cmd = MEM_IOCPRINT; if (ioctl(fd, cmd, &arg) < 0) { printf("Call cmd MEM_IOCPRINT fail\n"); return -1; } /* 调用命令MEM_IOCSETDATA */ printf("<--- Call MEM_IOCSETDATA --->\n"); cmd = MEM_IOCSETDATA; arg = 2007; if (ioctl(fd, cmd, &arg) < 0) { printf("Call cmd MEM_IOCSETDATA fail\n"); return -1; } /* 调用命令MEM_IOCGETDATA */ printf("<--- Call MEM_IOCGETDATA --->\n"); cmd = MEM_IOCGETDATA; if (ioctl(fd, cmd, &arg) < 0) { printf("Call cmd MEM_IOCGETDATA fail\n"); return -1; } printf("<--- In User Space MEM_IOCGETDATA Get Data is %d --->\n\n",arg); close(fd); return 0; }