驱动程序可以使用ioctl执行硬件控制。
两种原型:
1.在用户空间
int ioctl(int fd,unsigned long cmd,...);
fd:文件描述符
cmd:控制命令
,,,:可选参数:插入*argp,具体内容依赖于cmd
2.驱动程序
int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);
inode与filp两个指针对应于应用程序传递的文件描述符fd,这和传递open方法的参数一样。
cmd 由用户空间直接不经修改的传递给驱动程序
arg 可选
cmd:
四个位字段:type,number,direction,size
type:幻数,8位
number:序数,8位
direction:涉及内容包括_IOC_NONE(无数据传输),_IOC_READ(从设备中读),_IOC_WRITE,_IOC_READ|_IOC_WRITE(双向数据传输)
size:表示所涉及的用户数据大小,通常为13位或是14位,具体可通过宏_IOC_SIZEBITS找到针对特定体系结构的具体数值。内核不会检查这个位字段,对该字段的检查可以帮助我们检测用户空间的错误。
另外,<asm/ioctl.h>定义了一些构造命令编号的宏,
_IOR(type,nr,datetype) 构造从驱动程序中读取数据的命令
_IO(type,nr) 用于构造无参数的命令编号
_IOW(type,nr,datetype) 用于写入命令的编号
_IOWR(type,nr,datatype) 双向传输
type,number通过参数传入,size通过对datatype参数取sizeof获取
还有一些解开位字段的宏:_IOC_DIR(nr),_IOC_TYPE(nr),_IOC_NR(nr),_IOC_SIZE(nr)
对非法的ioctl命令一般会返回-EINVAL
预定义命令
在使用ioctl命令编号时,一定要避免与预定义命令重复,否则,命令冲突,设备不会响应
下列ioctl命令对任何文件(包括设备特定文件)都是预定义的:
FIOCTLX 设置执行时关闭标志
FIONCLEX 清除执行时关闭标志
FIOASYNC 设置或复位文件异步通知
FIOQSIZE 返回文件或目录大小
FIONBIO 文件非阻塞型IO,file ioctl non-blocking i/o
如何使用ioctl的附加参数:arg
1:arg是个整数,那简单,直接用
2:arg是个指针,麻烦点,需检测后才能用
分析:使用指针,首先得保证指针指向的地址合法。因此,在使用这个指针之前,我们应该使用
<asm/uaccess.h>中声明的 int access_ok(int type,const void *addr,unsigend long size) 返回值为1(成功)或0(失败),如果返回失败,驱动程序通常返回-EFAULT给调用者。
来验证地址的合法性。
type: VERIFY_READ 或是 VERIFY_WRITE,取决于是读取还是写入用户空间内存区。
addr: 一个用户空间的地址
size: 如果要读取或写入一个int型数据,则为sizeof(int)
如果在该地址处既要读取,又要写入,则应该用:VERIFY_WRITE,因为它是VERIFY_READ的超集
注意:首先, access_ok不做校验内存存取的完整工作; 它只检查内存引用是否在这个进程有合理权限的内存范围中,且确保这个地址不指向内核空间内存。其次,大部分驱动代码不需要真正调用 access_ok,而直接使用put_user(datum, ptr)和get_user(local, ptr),它们带有校验的功能,确保进程能够写入给定的内存地址,成功时返回 0, 并且在错误时返回 -EFAULT.。
使用举例:
int err=0,tmp; int retval; /*抽取类型和编号位字段,并拒绝错误的命令号:在调用access_ok之前返回ENOTTY(不恰当的ioctl)*/ if(_IOC_TYPE(cmd)!=SCULL_IOC_MAGIC) return -ENOTTY; if(_IOC_NR(cmd)>SCULL_IOC_MAXNR) return -ENOTTY; /*方向是一个位掩码,而VERIFY_WRITE用于R/W*传输。“类型”是针对用户空间而言,而access_ok面向内核,因此,读取和写入,恰好相反*/ if(_IOC_DIR(cmd) & _IOC_READ) err=!access_ok(VERIFY_WRITE,(void __user *)arg,_IOC_SIZE(cmd)); else if(_IOC_DIR(cmd) & _IOC_WRITE) err=!access_ok(VERIFY_READ,(void __user *)arg,_IOC_SIZE(cmd)); if(err) return -EFAULT;
<asm/uaccess.h>
put_user(datum,ptr);
__put_user(datum,ptr);
使用时,速度快,不做类型检查,使用时可以给ptr传递任意类型的指针参数,只要是个用户空间的地址就行,传递的数据大小依赖于ptr参数的类型。
put_user vs __put_user:使用前做的检查,put_user多些,__put_user少些,
一般用法:实现一个读取方法时,可以调用__put_user来节省几个时钟周期,或者在复制多项数据之前调用一次access_ok,像上面代码一样。
get_user(datum.ptr);
__get_user(datum,ptr);
接收的数据被保存在局部变量local中,返回值说明其是否正确。同样,__get_user应该在操作地址被access_ok后使用。
权能与受限操作
来由:驱动程序必须进行附加的检查以确认用户是否有权进行请求的操作
权能作用:基于权能的系统抛弃了那种要么全有,要么全无的特权分配方式,而是把特权操作划分成了独立的组。
<linux/capability.h>
int capable(int capability);
在执行一项特权之前,应先检查其是否具有这个权利
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
CAP_DAC_OVERRIDE/*越过在文件和目录上的访问限制(数据访问控制或 DAC)的能力。*/
CAP_NET_ADMIN /*进行网络管理任务的能力, 包括那些能够影响网络接口的任务*/
CAP_SYS_MODULE /*加载或去除内核模块的能力*/
CAP_SYS_RAWIO /*进行 "raw"(裸)I/O 操作的能力. 例子包括存取设备端口或者直接和 USB 设备通讯*/
CAP_SYS_ADMIN /*截获的能力, 提供对许多系统管理操作的途径*/
CAP_SYS_TTY_CONFIG /*执行 tty 配置任务的能力*/
switch(cmd) { case SCULL_IOCRESET: break; case SCULL_IOCTQUANTUM: /* Tell: arg is the value */ case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */ retval = __put_user(scull_quantum, (int __user *)arg); case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */ case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */ case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */ case SCULL_IOCTQSET: case SCULL_IOCGQSET: case SCULL_IOCQQSET: case SCULL_IOCXQSET: case SCULL_IOCHQSET: /* case SCULL_P_IOCTSIZE: case SCULL_P_IOCQSIZE: }
scull_quantum = SCULL_QUANTUM;
scull_qset = SCULL_QSET;
break;
case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
if (! capable (CAP_SYS_ADMIN)) //capable
return -EPERM;
retval = __get_user(scull_quantum, (int __user *)arg);//_get_user
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_quantum = arg;
break;
break;
return scull_quantum;
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
retval = __get_user(scull_quantum, (int __user *)arg);
if (retval == 0)
retval = __put_user(tmp, (int __user *)arg);
break;
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
scull_quantum = arg;
return tmp;
case SCULL_IOCSQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_qset, (int __user *)arg);
break;
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_qset = arg;
break;
retval = __put_user(scull_qset, (int __user *)arg);
break;
return scull_qset;
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
retval = __get_user(scull_qset, (int __user *)arg);
if (retval == 0)
retval = put_user(tmp, (int __user *)arg);
break;
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
scull_qset = arg;
return tmp;
* The following two change the buffer size for scullpipe.
* The scullpipe device uses this same ioctl method, just to
* write less code. Actually, it's the same driver, isn't it?
*/
scull_p_buffer = arg;
break;
return scull_p_buffer;
default: /* redundant(???), as cmd was checked against MAXNR */
return -ENOTTY;
}
return retval;