應(yīng)用程序如果想要設(shè)置/獲取驅(qū)動(dòng)層的數(shù)據(jù),一般是驅(qū)動(dòng)提供一個(gè)ioclt
接口,然后應(yīng)用層調(diào)用。因此,學(xué)會(huì)在驅(qū)動(dòng)中實(shí)現(xiàn)ioctl
接口是必要的一項(xiàng)技能。
ioctl命令編碼規(guī)則
想要定義一個(gè)自己的ioctl命令,必須要遵從ioctl的編碼規(guī)則 。
一個(gè)ioctl命令由32比特位表示,每個(gè)比特位都有不同的含義,不同版本的內(nèi)核定義可能有些差異,具體參考文檔“Documentation/ioctl/ioctl-deconding.txt”
.
比特位 | 含義 |
---|---|
31-30 | 00 - 命令不帶參數(shù)01 - 命令需要把數(shù)據(jù)寫入驅(qū)動(dòng),寫方向10 - 命令需要從驅(qū)動(dòng)中獲取數(shù)據(jù),讀方向11 - 命令既要寫入數(shù)據(jù)又要獲取數(shù)據(jù),讀寫方向 |
29-16 | 如果命令帶參數(shù),則指定參數(shù)所占用的內(nèi)存空間大小 |
15-8 | 每個(gè)驅(qū)動(dòng)全局唯一的幻數(shù)(魔數(shù)) |
7-0 | 命令碼 |
在內(nèi)核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)
構(gòu)造ioctl命令 :
_IO(type,nr)
:用于構(gòu)造無參數(shù)的命令號(hào)_IOR(type,nr,datetype)
:用于構(gòu)造從驅(qū)動(dòng)程序中讀取數(shù)據(jù)的命令號(hào)_IOW(type,nr,datatype)
:用于構(gòu)造向驅(qū)動(dòng)程序?qū)懭霐?shù)據(jù)的命令號(hào)_IORW(type,nr,datatype)
:用于構(gòu)造雙向傳輸?shù)拿钐?hào)
解析ioctl命令 :
_IOC_DIR(cmd)
:獲得傳輸方向位段的值_IOC_TYPE(cmd)
:獲得類型的值_IOC_NR(cmd)
:獲得編號(hào)的值_IOC_SIZE(cmd)
:獲得大小的值
具體示例 :
向驅(qū)動(dòng)寫入數(shù)據(jù),假設(shè)定義幻數(shù)為'a'
,命令碼為0
,傳入的數(shù)據(jù)為結(jié)構(gòu)體struct option
:
#define VS_MAGIC 's'//幻數(shù)
#define VS_SET_FFMT _IOW(VS_MAGIC, 0, struct option)
從驅(qū)動(dòng)獲得數(shù)據(jù),將命令碼修改為1,_IOW換成_IOR:
#define VS_MAGIC 's'//幻數(shù)
#define VS_GET_FFMT _IOR(VS_MAGIC, 1, struct option)
這里要注意,理想情況下定義的幻數(shù)在一種體系結(jié)構(gòu)下應(yīng)該是全局唯一的,雖然很難做到,但我們還是應(yīng)該遵從內(nèi)核所規(guī)定的這種定義形式。
ioctl系統(tǒng)調(diào)用過程
ioctl
本質(zhì)是一個(gè)系統(tǒng)調(diào)用 ,在應(yīng)用層使用ioctl函數(shù)時(shí),會(huì)使得系統(tǒng)從用戶態(tài)trap到內(nèi)核態(tài),即調(diào)用到內(nèi)核態(tài)的sys_ioctl
函數(shù)。
sys_ioctl
函數(shù)的定義位于內(nèi)核源碼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是一個(gè)系統(tǒng)調(diào)用宏,3代表這個(gè)系統(tǒng)調(diào)用需要3個(gè)參數(shù),具體的SYSCALL_DEFINE
宏定義可以參考include/linux/syscalls.h
。這里不對(duì)系統(tǒng)調(diào)用展開討論,只需要知道ioctl是一個(gè)系統(tǒng)調(diào)用就可以了。
展開之后,這個(gè)函數(shù)名字其實(shí)就是sys_ioctl
,sys_ioctl
函數(shù)首先調(diào)用了security_file_ioctl
,然后調(diào)用了do_vfs_ioctl
。
do_vfs_ioctl
函數(shù)是需要關(guān)注的,定義如下:
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;
}