《Linux Device Driver》——高级字符驱动程序操作

本文详细介绍了Linux设备驱动中的ioctl系统调用,包括命令选择、返回值处理、参数使用、权能检查以及休眠与唤醒机制。通过实例解析了如何在驱动程序中实现ioctl命令,以及阻塞型I/O操作,重点讨论了休眠、唤醒、等待队列等关键概念。此外,还涵盖了非ioctl设备控制、poll和select机制,以及设备文件的访问控制策略。
摘要由CSDN通过智能技术生成

首先实现ioctl系统调用,它是用来设备控制的公共接口。
然后,内核态与用户态保持同步。
掌握进程休眠和唤醒、实现阻塞I/O,以及在设备可读取或写入时通知用户空间。
最后,在驱动程序中实现设备访问策略。


ioctl

驱动程序通过ioctl执行各种类型的硬件控制。

1.在用户空间,ioctl系统调用具有如下原型:

int ioctl(int fd,unsigned long cmd,...);
参数 描述
fd 文件描述符
cmd 控制命令
表示可变数目的参数表,在实际使用中是一个可选参数,习惯上用char* argp定义。

每个ioctl命令就是一个独立的系统调用,而且是非公开的。

2.驱动程序中

int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);
参数 描述
inode 对应于应用程序传递的文件描述符fd,这与传给open方法的参数一致
filp 对应于应用程序传递的文件描述符fd,这与传给open方法的参数一致
cmd 由用户空间直接不经修改的传递给驱动程序
arg 可选 以unsigned long的形式传递给驱动程序

选择ioctl命令

#include/asm/ioctl.h和Documentation/ioctl-number.txt
ioctl命令号定义:type、number、direction、size

参数 描述 位数
type 幻数。选择一个号码,并在整个驱动程序中使用这个号码 8bit(_IOC_TYPEBITS)
number 序数。顺序编号 8bits(_IOC_NRBITS)
direction 涉及数据传输时该字段定义数据传输的方向,涉及内容包括_IOC_NONE(无数据传输),_IOC_READ(从设备中读),_IOC_WRITE,_IOC_READ|_IOC_WRITE(双向数据传输)。数据传输是从应用程序的角度来看的,_IOC_READ意味着从设备中读取数据,所以应用程序必须向用户空间写入数据
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)
_IOC_NR : 读取基数域值 (bit0~ bit7)
_IOC_TYPE : 读取魔数域值 (bit8 ~ bit15)
_IOC_SIZE : 读取数据大小域值 (bit16 ~ bit29)
_IOC_DIR : 获取读写属性域值 (bit30 ~ bit31)

scull的scull.h中有一些关于ioctl命令定义,这些命令是用来设置或获取驱动程序的配置参数:

#define SCULL_IOC_MAGIC  'k'
/* Please use a different 8-bit number in your code */

#define SCULL_IOCRESET    _IO(SCULL_IOC_MAGIC, 0)

/*
 * S means "Set" through a ptr,
 * T means "Tell" directly with the argument value
 * G means "Get": reply by setting through a pointer
 * Q means "Query": response is on the return value
 * X means "eXchange": switch G and S atomically
 * H means "sHift": switch T and Q atomically
 */
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC,  1, int)
#define SCULL_IOCSQSET    _IOW(SCULL_IOC_MAGIC,  2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC,   3)
#define SCULL_IOCTQSET    _IO(SCULL_IOC_MAGIC,   4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC,  5, int)
#define SCULL_IOCGQSET    _IOR(SCULL_IOC_MAGIC,  6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC,   7)
#define SCULL_IOCQQSET    _IO(SCULL_IOC_MAGIC,   8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET    _IOWR(SCULL_IOC_MAGIC,10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC,  11)
#define SCULL_IOCHQSET    _IO(SCULL_IOC_MAGIC,  12)

通过指针和显式的数值来实现整数参数的传递。

通过指针和显式的数值来实现返回值。

返回值 描述
返回值为正 正常工作
返回值为负 错误,设置用户空间的errno变量

返回值

对非法的ioctl命令一般会返回-EINVAL


预定义命令

在使用ioctl命令编号时,一定要避免与预定义命令重复,否则,命令冲突,设备不会响应。

下列ioctl命令对任何文件(包括设备特定文件)都是预定义的:

预定义命令 描述
FIOCTLX 设置执行时关闭标志
FIONCLEX 清除执行时关闭标志
FIOASYNC 设置或复位文件异步通知
FIOQSIZE 返回文件或目录大小,用于设备文件时会导致ENOTTY错误的返回
FIONBIO 文件非阻塞型IO,file ioctl non-blocking i/o

使用ioctl参数

参数类型 描述
arg整数 直接用
arg指针 需检测后才能用

使用指针,首先得保证指针指向的地址(用户空间)合法。因此,在使用这个指针之前,我们应该使用access_ok函数来验证地址的合法性:

#include<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。

scull中main.c通过分析ioctl编号的位字段来检查参数:

int scull_ioctl(struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long arg)
{
   

        int err = 0, tmp;
        int retval = 0;

        /*
         * extract the type and number bitfields, and don't decode
         * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
         */
        if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
        if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;

        /*
         * the direction is a bitmask, and VERIFY_WRITE catches R/W
         * transfers. `Type' is user-oriented, while
         * access_ok is kernel-oriented, so the concept of "read" and
         * "write" is reversed
         */
        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;
		...
}
#include<asm/uaccess.h>
put_user(datum,ptr);
/*进行检查以确保进程可以写入指定的内存地址,成功返回0,出错返回-EFAULT*/
__put_user(datum,ptr);
/*使用时,速度快,不做类型检查,使用时可以给ptr传递任意类型的指针参数,只要是个用户空间的地址就行,传递的数据大小依赖于ptr参数的类型*/

get_user(datum.ptr);
__get_user(datum,ptr);
/*接收的数据被保存在局部变量local中,返回值说明其是否正确。同样,__get_user应该在操作地址被access_ok后使用*/

put_user vs __put_user:使用前做的检查,put_user多些,__put_user少些,
这些宏把datum写入到用户空间。当传递单个数据时,应用这些宏而不是copy_to_user。
一般用法:实现一个读取方法时,可以调用__put_user来节省几个时钟周期,或者在复制多项数据之前调用一次access_ok,像上面代码一样。


权能与受限操作

#include<linux/capability.h>
来由:驱动程序必须进行附加的检查以确认用户是否有权进行请求的操作
权能作用:基于权能的系统抛弃了那种要么全有,要么全无的特权分配方式,而是把特权操作划分成了独立的组。

#include<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 配置任务的能力

ioctl命令的实现

scull的ioctl实现只传递设备的可配置参数:

switch(cmd) {
   

          case SCULL_IOCRESET:
                scull_quantum = SCULL_QUANTUM;
                scull_qset = SCULL_QSET;
                break;

          case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
                if (! capable (CAP_SYS_ADMIN))
                        return -EPERM;
                retval = __get_user(scull_quantum, (int __user *)arg);
                break;

          case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
                if (! capable (CAP_SYS_ADMIN))
                        return -EPERM;
                scull_quantum = arg;
                break;

          case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
                retval = __put_user(scull_quantum, (int __user *)arg);
                break;

          case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
                return scull_quantum;

          case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
                if (! capable (CAP_SYS_ADMIN))
                
           case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
                if (! capable (CAP_SYS_ADMIN))
                        return -EPERM;
                scull_quantum = arg;
                break;

          case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
                retval = __put_user(scull_quantum, (int __user *)arg);
                break;

          case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
                return scull_quantum;

          case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
                if (! capable (CAP_SYS_ADMIN))
                        return -<
This is, on the surface, a book about writing device drivers for the Linux system. That is a worthy goal, of course; the flow of new hardware products is not likely to slow down anytime soon, and somebody is going to have to make all those new gadgets work with Linux. But this book is also about how the Linux kernel works and how to adapt its workings to your needs or interests. Linux is an open system; with this book, we hope, it is more open and accessible to a larger community of developers. This is the third edition of Linux Device Drivers. The kernel has changed greatly since this book was first published, and we have tried to evolve the text to match. This edition covers the 2.6.10 kernel as completely as we are able. We have, this time around, elected to omit the discussion of backward compatibility with previous kernel versions. The changes from 2.4 are simply too large, and the 2.4 interface remains well documented in the (freely available) second edition. This edition contains quite a bit of new material relevant to the 2.6 kernel. The discussion of locking and concurrency has been expanded and moved into its own chapter. The Linux device model, which is new in 2.6, is covered in detail. There are new chapters on the USB bus and the serial driver subsystem; the chapter on PCI has also been enhanced. While the organization of the rest of the book resembles that of the earlier editions, every chapter has been thoroughly updated. We hope you enjoy reading this book as much as we have enjoyed writing it.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值