应用程序如果想要设置/获取驱动层的数据,一般是驱动提供一个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;
}