简述:
ioctl是linux应用程序用来向设备发送特殊命令。如果,设备要响应应用程序的ioctl调用,那么设备驱动必须编写响应ioctl的接口,这个接口就是file_operations的unlocked_ioctl。
应用程序ioctl接口:
声明头文件:
#include
如是ubuntu系统,可以在/usr/include/sys/下面查看。
定义ioctl命令需要包含头文件:
#include
命令定义规则和驱动一样的,见下。
file_operations的定义:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
其中:
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
就是响应应用程序ioctl调用的接口。
主要用unlocked_ioctl,
compat_ioctl用来兼容老版本。
第一个参数:打开的文件指针
第二个参数:命令
第三个参数:可以是一个整数,也可以是数据起始地址。
ioctl命令定义(第2个参数):
定义命令需要包含头文件:
#include
命令定义分4个段,如下:
type:魔数,占8位(_IOC_TYPEBITS)
number:序号,占8位(_IOC_NRBITS)
direction:方向(读写),占2位(_IOC_DIRBITS)
size:数据大小,占14位(_IOC_SIZEBITS),当第三个参数是地址的时候,就可以启用这个size来表示数据大小(单位字节),一般情况下不用就是0。(假想下,当同一type下超过256个序号的命令,其实可以启用这个,只需要驱动与应用程序配合好,当然,一般情况也没有超过256的命令)
正好32位。命令类型主要由type和number决定,先看type,然后再看number。
/* 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))
_IOC_NONE:代表没有读写的命令,只执行动作。
_IOC_READ:代表读,即要返回数据给用户空间(地址是unlocked_ioctl第三个参数),数据大小为size。
_IOC_WRITE:代表写,即用户空间有送数据下来,数据起始地址是unlocked_ioctl第三个参数,大小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)
/* ...and for the drivers/sound files... */
#define IOC_IN (_IOC_WRITE << _IOC_DIRSHIFT)
#define IOC_OUT (_IOC_READ << _IOC_DIRSHIFT)
#define IOC_INOUT ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
#define IOCSIZE_MASK (_IOC_SIZEMASK << _IOC_SIZESHIFT)
#define IOCSIZE_SHIFT (_IOC_SIZESHIFT)
如,要判断一个命令是不是读:
_IOC_DIR(nr)==IOC_OUT
定义命令例子:
#include
#include
#define YU_CMD 'A' //其实就是大写A的ascii值,也可以直接写数字
#define TR_CMD 32
#define YU_CMD0 _IO(YU_CMD,0)
#define YU_CMD1 _IO(YU_CMD,1)
#define YU_CMD2 _IOW(YU_CMD,2,sizeof(int))
#define TR_CMD0 _IO(TR_CMD ,0)
#define TR_CMD1 _IO(TR_CMD ,1)
#define TR_CMD2 _IOR(TR_CMD ,2,20)
long exioctl(struct file *f, unsigned int cmd, unsigned long data);
struct file_operations fo = {
...
.unlocked_ioctl = exioctl,
.compat_ioctl = exioctl, //这行可要可不要,不考虑兼容老版本就不需要
...
}
long exioctl(struct file *f, unsigned int cmd, unsigned long data)
{
int err;
int ac;
char ch[20] = {'t'};
switch(_IOC_TYPE(cmd)|_IOC_NR(cmd))
{
case YU_CMD0:
...
break;
case YU_CMD1:
...
break;
case YU_CMD2:
if(_IOC_DIR(cmd) == IOC_IN)
{
err = get_user(ac,data);
printk("ac = %d\n",ac)
}
break;
case TR_CMD0:
...
break;
case TR_CMD1:
...
break;
case TR_CMD2:
if(_IOC_DIR(cmd) == IOC_OUT)
{
err = copy_to_user(data,ch,20);
}
break;
default:
err = -EINVAL;
}
...
return err;
}
unlocked_ioctl 返回值,当没有匹配到命令时一般返回-EINVAL(”Invalid argument”),没有带读写的一般返回0,有读写的,返回读写字节数。
对于内核和用户空间数据交互,尽量用提供的api:
单个数用:
get_user:从用户空间获取一个数
put_user:推送一个数到用户空间
多个数据用:
copy_from_user:从用户空间获取数据
copy_to_user:推送数据到用户空间
如果,不用这些API,要考虑用access_ok来检查用户空间地址的有效性:
access_ok(VERIFY_READ, ptr, sizeof(*ptr))
VERIFY_READ:表示对地址有没有读权限,写用权限VERIFY_WRITE
ptr:用户空间地址
sizeof(*ptr):地址空间大小
其实,命令也可以不按照这么定义,直接用数字代替也是可以的,只要驱动和应用程序配合好,但是兼容性和可读性,就没按照规定好,不建议。
还有些特殊的预定于命令,在:
#include
很少看到有用的。