一、ioctl接口
函数原型:
int ioctl(int fd, unsigned long cmd, ...);
ioctl驱动方法有和用户空间版本不同的原型:
int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
为帮助程序员创建唯一的ioctl命令代码,这些编码已被划分为几个段位,Linux的第一个版本使用16-位数:
高8位是关联这个设备的“魔”数,低8位是一个顺序号,在设备内唯一。
根据Linux内核管理来选择驱动ioctl号,在include/asm/ioctl.h和Documentation/ioctl-number.txt文件列举了在内核中使用的魔数。
定义ioctl命令号的正确方法使用4个位段,新符号定义在<linux/ioctl.h>
- type:魔数,只是一个选择数(参考ioctl-number.txt),并且使用它在整个驱动中,8位宽(_IOC_TYPEBITS)
- number:序号,它是8位(_IOC_NRBITS)宽
- direction:数据传送方向,如果这个特殊的命令设计数据传送。可能的值_IOC_NONE(没有数据传输),_IOC_READ, _IOC_WRITE, IOC_READ|_IOC_WRITE。
- size:涉及到的用户数据的大小
在<linux/ioctl.h>中包含的<asm/ioctl.h>头文件定义了一些构造命令编号的宏:
- _IO(type, nr)用于构造无参数的命令编号
- _IOR(type, nr, datatype)用于构造从驱动程序中读取数据的命令编号
- _IOW(type, nr datatype)用于写入数据的命令
- _IOWR(type, nr, datatype)用于双向传输
- type和number位字段通过参数传入,而size位字段通过对datatype参数取sizeof获取
还有头文件定义了用于解开位字段的宏:
- _IOC_DIR(nr)、_IOC_TYPE(nr)、_IOC_NR(nr)和_IOC_SIZE(nr)
scull的使用例子:
/* 使用"k"作为幻数 */ #define SCULL_IOC_MAGIC 'k' /* 在你自己的代码中,请使用不同的8位数字 */ #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) #define SCULL_IOC_MAXNR 14
1.1 预定义的命令
- 可对任何文件发出的(常规,设备,FIFO,或者socket)的那些
- 只对常规文件发出的那些
- 对文件系统类型特殊的那些
ioctl命令是预定义给任何文件,包括设备特殊的文件:
- FIOCLEX :设置close-on-exec标志
- FIONCLEX :清除close-no-exec标志
- FIOQSIZE :返回一个文件或者目录的大小
- FIONBIO :在“阻塞和非阻塞操作”一节中描述
头文件<asm/ioctl.h>,它包含在<linux/ioctl.h>中,定义宏来帮助建立命令号。
1.2 使用ioctl参数
ioctl调用常常包含小数据项,可通过其他方法更有效的操作。地址校验由函数access_ok,在头文件<asm/uaccess.h>:
int access_ok(int type, const void *addr, unsigned long size);
第一个参数应当是VERIFY_READ或者VERIFY_WRITE
addr参数持有一个用户空间地址
size是一个字节量
头文件<asm/uaccess.h>
put_user(datum, ptr)
__put_user(datum, ptr)
get_user(local, ptr)
__get_user(local, ptr)
1.3 兼容性和受限操作
在头文件中<linux/capability.h>中找到
- 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 配置任务的能力
能力检查通过capable函数来进行的(定义在<linux/sched.h>):
int capable(int capability); 例子: if(!capable(CAP_SYS_ADMIN)) return -EPERM;
ioctl命令实现
switch(cmd) { case SCULL_IOCRESET: scull_quantum = SCULL_QUANTUM; scull_qset = SCULL_QSET; break; case SCULL_IOSQUANTUM: /* 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: user arg as pointer */ 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; case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */ if(!capable(CAP_SYS_ADMIN)) return -EPERM; tmp = scull_quantum; scull_qaumtum = arg; return tmp; default: return -ENOTTY; } return retval:
二、阻塞I/O
如何使进程睡眠并且之后再次唤醒他。一个驱动当它无法立刻满足请求应当如何响应?
程序员只希望调用read和write并且使调用返回,在必要的工作已完成后。这样,你的驱动应当(缺省地)阻塞进程,使它进入睡眠直到请求可继续。
2.1 睡眠的介绍
当一个进程被置为睡眠,它被标识为处于一个特殊的状态并且从调度器的运行队列中去除。直到发生谋陷事情改变了那个状态,这个进程将不被在任何CPU上调度,并且将不会运行。一个睡着的进程被搁置系统的一边,直到以后发生事件。
有几个规则必须记住,促使安全的方式的睡眠:
1.当你运行在原子上下文时不能睡眠,这意味着驱动持有自旋锁,seqlock或者RCU锁时不能睡眠。
2.当你进程醒来,不能关于醒后的系统状态做任何的假设,并且必须检查来确保你在等待的条件有效。
3.你的进程不能睡眠除非确信其他人,某处将唤醒它。做唤醒工作的代码必须也能够找到你的进程来做它工作。
一个等待队列由一个“等待队列头”来管理,定义在<linux/wait.h>,wait_queue_head_t类型,使用:
DECLARE_WAIT_QUEUE_HEAD(name);
或者动态的,如下:
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
2.2 简单睡眠
Linux内核中睡眠的最简单方式是一个宏定义。称为wait_event(有几个变体),形式是:
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
queue是要用的等待队列头,条件是一个被这个宏在睡眠前后锁求值的任一的布尔表达式。
如果你使用wait_event进程被置为不可中断地睡眠
唤醒函数:
void wake_up(wait_queue_head_t *queue); void wake_up_interruptible(wait_queue_head_t *queue);
这个例子,任何试图从这个设备读取的进程都被置为睡眠。无论何时一个进程写这个设备,所有的睡眠进程被唤醒:
static DECLARE_WAIT_QUEUE_HEAD(wq); static int flag = 0; ssize_t sleepy_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) { printk(KERN_DEBUG "process %i (%s) going to sleep ", current-pid, current-comm); wait_event_interruptible(wq, flag != 0); flag = 0; printk(KERN_DEBUG "awoken %i (%s) ", current->pid, current->comm); return 0; /* EOF */ } ssize_t sleepy_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos) { printk(KERN_DEBUG "process %i (%s) awakening the readers... ", current->pid, current->comm); flag = 1; wake_up_interruptible(&wq); return count; /* succedd, to avoid retrial */ }
注意这个例子里 flag 变量的使用. 因为 wait_event_interruptible 检查一个必须变为真的条件, 我们使用 flag 来创建那个条件。
2.3 阻塞和非阻塞操作
明确的非阻塞I/O由filp->f_flags中的O_NONBLOCK标志来指示。这个标志定义于<linux/fcntl.h>被<linux/fs.h>自动包含。
struct scull_pipe { wait_queue_head_t inq, outq; /* read and write queues */ char *buffer, *end; /* begin of buf, end of buf */ int buffersize; /* used in pointer arithmetic */ char *rp, *wp; /* where to read, where to write */ int nreaders, nwriters; /* number of openings for r/w */ struct fasync_struct *async_queue; /* asynchronous readers */ struct semaphore sem; /* mutual exclusion semaphore */ struct cdev cdev; /* Char devie structure */ };
read实现既管理阻塞也管理非阻塞输入:
static ssize_t scull_p_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct scull_pipe *dev = filp->private_data; if(down_interruptible(&dev->sem)) return -ERESTARTSYS; while(dev->rp == dev->wp) { /* nothing to read */ up(&dev->sem); /* release the lock */ if(filp->f_flags & O_NONBLOCK) return -EAGAIN; PDEBUG(""%s" reading: going to sleep ", current->comm); if(wait_event_interruptible(dev->inq, (dev->rp != dev->wp))) return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ /* otherwise loop, but first reacquire the lock */ if(down_interruptible(&dev->sem)) return -ERESTARTSYS; } /* ok, data is there, return something */ if(dev->wp > dev->rp) count = min(count, (size_t)(dev->wp - dev->rp)); else /* the write pointer has wrapped, return data up to dev->end */ count = min(count, (size_t)(dev->end - dev->rp)); if(copy_to_user(buf, dev->rp, count)) { up(&dev->sem); return -EFAULT; } dev->rp += count; if(dev->rp == dev->end) dev->rp = dev->buffer; /*wrapped */ up(&dev->sem); /* finally, awake any writers and return */ wake_up_interruptible(&dev->outq); PDEBUG(""%s" did read %li bytes ", current->comm, (long)count); return count; }