应用程序如果想要设置/获取驱动层的数据,一般是驱动提供一个ioclt
接口,然后应用层调用。因此,学会在驱动中实现ioctl
接口是必要的一项技能。
ioctl命令编码规则
想要定义一个自己的ioctl命令,必须要遵从ioctl的编码规则。
一个ioctl命令由32比特位表示,每个比特位都有不同的含义,不同版本的内核定义可能有些差异,具体参考文档“Documentation/ioctl/ioctl-deconding.txt”
.
比特位 | 含义 |
31-30 | 00 - 命令不带参数 01 - 命令需要把数据写入驱动,写方向 10 - 命令需要从驱动中获取数据,读方向 11 - 命令既要写入数据又要获取数据,读写方向 |
29-16 | 如果命令带参数,则指定参数所占用的内存空间大小 |
15-8 | 每个驱动全局唯一的幻数(魔数) |
7-0 | 命令码 |
在内核include/uapi/asm-generic/ioctl.h
头文件中提供了一组宏来定义、提取命令中的字符信息:
#define _IOC(dir,type,nr,size) \ (((dir) << _IOC_DIRSHIFT) | \ ((type) << _IOC_TYPESHIFT) | \ ((nr) << _IOC_NRSHIFT) | \ ((size) << _IOC_SIZESHIFT)) #ifndef __KERNEL__ #define _IOC_TYPECHECK(t) (sizeof(t)) #endif /* 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)) /* 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命令:
_IO(type,nr)
:用于构造无参数的命令号_IOR(type,nr,datetype)
:用于构造从驱动程序中读取数据的命令号_IOW(type,nr,datatype)
:用于构造向驱动程序写入数据的命令号_IORW(type,nr,datatype)
:用于构造双向传输的命令号
解析ioctl命令:
_IOC_DIR(cmd)
:获得传输方向位段的值_IOC_TYPE(cmd)
:获得类型的值_IOC_NR(cmd)
:获得编号的值_IOC_SIZE(cmd)
:获得大小的值
具体示例:
向驱动写入数据,假设定义幻数为'a'
,命令码为0
,传入的数据为结构体struct option
:
#define VS_MAGIC 's'//幻数 #define VS_SET_FFMT _IOW(VS_MAGIC, 0, struct option)
从驱动获得数据,将命令码修改为1,_IOW换成_IOR:
#define VS_MAGIC 's'//幻数 #define VS_GET_FFMT _IOR(VS_MAGIC, 1, struct option)
这里要注意,理想情况下定义的幻数在一种体系结构下应该是全局唯一的,虽然很难做到,但我们还是应该遵从内核所规定的这种定义形式。
ioctl系统调用过程
ioctl
本质是一个系统调用,在应用层使用ioctl函数时,会使得系统从用户态trap到内核态,即调用到内核态的sys_ioctl
函数。
sys_ioctl
函数的定义位于内核源码fs/ioctl.c
中:
SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg) { int error; struct fd f = fdget(fd); if (!f.file) return -EBADF; error = security_file_ioctl(f.file, cmd, arg); if (!error) error = do_vfs_ioctl(f.file, fd, cmd, arg); fdput(f); return error; }
SYSCALL_DEFINE3是一个系统调用宏,3代表这个系统调用需要3个参数,具体的SYSCALL_DEFINE
宏定义可以参考include/linux/syscalls.h
。这里不对系统调用展开讨论,只需要知道ioctl是一个系统调用就可以了。
展开之后,这个函数名字其实就是sys_ioctl
,sys_ioctl
函数首先调用了security_file_ioctl
,然后调用了do_vfs_ioctl
。
do_vfs_ioctl
函数是需要关注的,定义如下:
int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd, unsigned long arg) { int error = 0; int __user *argp = (int __user *)arg; struct inode *inode = file_inode(filp); switch (cmd) { case FIOCLEX: set_close_on_exec(fd, 1); break; case FIONCLEX: set_close_on_exec(fd, 0); break; case FIONBIO: error = ioctl_fionbio(filp, argp); break; case FIOASYNC: error = ioctl_fioasync(fd, filp, argp); break; case FIOQSIZE: if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode) || S_ISLNK(inode->i_mode)) { loff_t res = inode_get_bytes(inode); error = copy_to_user(argp, &res, sizeof(res)) ? -EFAULT : 0; } else error = -ENOTTY; break; case FIFREEZE: error = ioctl_fsfreeze(filp); break; case FITHAW: error = ioctl_fsthaw(filp); break; case FS_IOC_FIEMAP: return ioctl_fiemap(filp, arg); case FIGETBSZ: return put_user(inode->i_sb->s_blocksize, argp); case FICLONE: return ioctl_file_clone(filp, arg, 0, 0, 0); case FICLONERANGE: return ioctl_file_clone_range(filp, argp); case FIDEDUPERANGE: return ioctl_file_dedupe_range(filp, argp); default: if (S_ISREG(inode->i_mode)) error = file_ioctl(filp, cmd, arg); else error = vfs_ioctl(filp, cmd, arg); break; } return error; }
可以看到,do_vfs_ioctl
函数会对一些特殊的命令进行处理,因此,我们在定义自己的ioctl
命令时,要避免和这些已有的命令冲突。
只有非特殊命令的情况,才会进入switch的default,vfs_ioctl
函数最终会调用驱动中实现的unlocked_ioctl
函数:
long vfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int error = -ENOTTY; if (!filp->f_op->unlocked_ioctl) goto out; error = filp->f_op->unlocked_ioctl(filp, cmd, arg); if (error == -ENOIOCTLCMD) error = -ENOTTY; out: return error; }
驱动层实现
在驱动中,我们只需要实现struct file_operations
结构体里的unlocked_ioctl
函数即可,这个函数用于处理ioctl
命令,基本结构如下:
static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { switch(cmd) { case VS_SET_BAUD: vsdev.baud = arg; break; case VS_SET_FFMT: if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option))) return -EFAULT; break; default: break; }; return 0; } static struct file_operations vser_ops = { .owner = THIS_MODULE, .unlocked_ioctl = vser_ioctl, };
cmd
就是传入ioctl
命令,一般在unlocked_ioctl
函数的实现中,通过switch
语句判断不同ioctl
命令。第三个参数arg
代表输入的数据,如果传入的是一个指针,可以对arg进行强制类型转换。
vser.c
#include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/kfifo.h> #include <linux/ioctl.h> #include <linux/uaccess.h> #include <linux/device.h> static struct class *class; struct option{ unsigned int datab; unsigned int parity; unsigned int stopb; }; #define VS_MAGIC 's' #define VS_SET_BAUD _IOW(VS_MAGIC, 0, unsigned int) #define VS_GET_BAUD _IOR(VS_MAGIC, 1, unsigned int) #define VS_SET_FFMT _IOW(VS_MAGIC, 2, struct option) #define VS_GET_FFMT _IOR(VS_MAGIC, 3, struct option) #define VSER_MAJOR 256 #define VSER_MINOR 0 #define VSER_DEV_CNT 1 #define VSER_DEV_NAME "vser" struct vser_dev{ unsigned int baud; struct option opt; struct cdev cdev; }; DEFINE_KFIFO(vsfifo, char, 32); static struct vser_dev vsdev; static int vser_open(struct inode *inode, struct file *filp) { return 0; } static int vser_release(struct inode *inode, struct file *flip) { return 0; } static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) { int ret; unsigned int copied = 0; ret = kfifo_to_user(&vsfifo, buf, count, &copied); return ret == 0 ? copied : ret; } static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos) { int ret; unsigned int copied = 0; ret = kfifo_from_user(&vsfifo, buf, count, &copied); return ret == 0? copied : ret; } static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { if (_IOC_TYPE(cmd) != VS_MAGIC) return -ENOTTY; switch(cmd) { case VS_SET_BAUD: vsdev.baud = arg; break; case VS_GET_BAUD: arg = vsdev.baud; break; case VS_SET_FFMT: if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option))) return -EFAULT; break; case VS_GET_FFMT: if (copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option))) return -EFAULT; break; default: break; }; return 0; } static struct file_operations vser_ops = { .owner = THIS_MODULE, .open = vser_open, .release = vser_release, .read = vser_read, .write = vser_write, .unlocked_ioctl = vser_ioctl, }; static int __init vser_init(void) { int ret; dev_t dev; dev = MKDEV(VSER_MAJOR, VSER_MINOR); ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME); if (ret) goto reg_err; cdev_init(&vsdev.cdev, &vser_ops); vsdev.cdev.owner = THIS_MODULE; vsdev.baud = 115200; vsdev.opt.datab = 8; vsdev.opt.parity = 0; vsdev.opt.stopb = 1; ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT); if (ret) goto add_err; /* 自动创建设备节点 */ class = class_create(THIS_MODULE, "my_ioctl"); /* /sys/class/my_ioctl */ device_create(class, NULL, dev, NULL, "vser0"); /* /dev/vser0 */ return 0; add_err: unregister_chrdev_region(dev, VSER_DEV_CNT); reg_err: return ret; } static void __exit vser_exit(void) { dev_t dev; dev = MKDEV(VSER_MAJOR, VSER_MINOR); cdev_del(&vsdev.cdev); device_destroy(class, dev); class_destroy(class); unregister_chrdev_region(dev, VSER_DEV_CNT); } module_init(vser_init); module_exit(vser_exit); MODULE_LICENSE("GPL");
应用层示例
test.c
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <fcntl.h> #include <errno.h> struct option{ unsigned int datab; unsigned int parity; unsigned int stopb; }; #define VS_MAGIC 's' #define VS_SET_BAUD _IOW(VS_MAGIC, 0, unsigned int) #define VS_GET_BAUD _IOR(VS_MAGIC, 1, unsigned int) #define VS_SET_FFMT _IOW(VS_MAGIC, 2, struct option) #define VS_GET_FFMT _IOR(VS_MAGIC, 3, struct option) int main(int argc, char *argv[]) { int fd; int ret; unsigned int baud; struct option opt = {8,1,1}; fd = open("/dev/vser0", O_RDWR); if (fd == -1) goto fail; baud = 9600; ret = ioctl(fd, VS_SET_BAUD, baud); if (ret == -1) goto fail; ret = ioctl(fd, VS_GET_BAUD, baud); if (ret == -1) goto fail; ret = ioctl(fd, VS_SET_FFMT, &opt); if (ret == -1) goto fail; ret = ioctl(fd, VS_GET_FFMT, &opt); if (ret == -1) goto fail; printf("baud rate:%d\n", baud); printf("frame format: %d%d%d\n", opt.datab, opt.parity, opt.stopb); close(fd); exit(EXIT_SUCCESS); fail: perror("ioctl test"); exit(EXIT_FAILURE); }
运行结果:
end
猜你喜欢: