ioctl
大多数ioctl的实现中都包括一个switch语句来根据cmd参数选择对应的操作。
用户空间,ioctl原型如下:
int ioctl(int fd, unsigned long cmd, …);
最后省略号一般表示可变参数,但在实际系统中,系统调用不会真正的使用可变数目的参数。它只是为了在编译时防止编译器进行类型检查。因为有时它是一个整形数,有时是一个指针。
驱动程序的ioctl原型:
int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
inode和filp两个指针的值对应于应用程序传递的文件描述符fd。cmd是用户空间传递过来的值。可选的arg,无论用户传递的是整数还是指针,都以unsigned long的形式传给arg。
通常ioctl是一个switch语句,根据传递进来的cmd执行相应的操作。
选择ioctl命令
为驱动程序选择一个ioctl编号,应该首先看看include/asm/ioctl.h和Documention/ioctl-number.txt这两个文件。
cmd由type,number,direction,size四个部分组成。四部分定义在<linux/ioctl.h>中。
<linux/ioctl.h>包含的<asm/ioctl.h>包含了大量的构造以及解析cmd的宏。如
_IO(type, nr); //构造无参数的命令编号
_IOR(type, nr, datatype); //构造从驱动程序读取数据的命令编号
_IOW(type, nr, datatype); //给驱动程序写数据的命令编号
_IOWR(type, nr, datatype); //双向传输
解析cmd的宏:_IOC_DIR(nr),_IOC_TYPE(nr),_IOC_NR(nr),_IOC_SIZE(nr)。
预定义命令
在定义我们用于ioctl的命令时,需要注意避免与内核已有的命令相冲突。内核已有的命令有:FIOCLEX,FIONCLEX,FIOASYNC,FIOQSIZE,FIONBIO。
使用ioctl参数
在驱动程序中,如果需要与用户空间地址数据做交换,可以首先使用定义在<asm/uaccess.h>中的access_ok来验证地址。
int access_ok(int type, const void *addr, unsigned long size);
第一个参数是VERIFY_READ或者VERIFY_WRITE,取决于是读取还是写入用户空间。如果既要读取又要写入,则应该是VERIFY_WRITE。返回1表示成功,0表示失败。
定义在<asm/uaccess.h>中的函数:put_user(data, ptr),__put_user(data, ptr),get_user(data, ptr),__get_user(data, ptr)。可以用来与用户空间交换1,2,4,8字节内容。若ptr是一个指向字符的指针,则传递1个字节的内容。
权能和受限操作
对设备的访问由设备文件的权限控制,驱动程序一般不进行检查。
全部的权能操作可以在<linux/capability.h>中找到,对驱动程序开发者有意义的权能有:
CAP_DAC_OVERRIDE, CAP_NET_ADMIN, CAP_SYS_MODULE, CAP_SYS_RAWIO, CAP_SYS_ADMIN, CAP_SYS_TTY_CONFIG。
权能的检查通过capable函数实现(定义在<sys/sched.h>中)。
int capable(int capability);
休眠
休眠进程被从调度器的运行队列移走,直到某情况下修改这个状态,进程才会在CPU上调度。
² 永远不要在原子上下文进入休眠。
² 除非我们知道有其他人在其他地方会唤醒我们,否则进程不能进入休眠。
在linux中,一个等待队列通过一个“等待队列头(wait queue head)”管理。等待队列头是一个类型为wait_queue_head_t的结构体。定义在<linux/wait.h>中。
静态初始化一个等待队列头:
DECLARE_WAIT_QUEUE_HEAD(name);
动态初始化一个等待队列头:
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
linux内核中最简单的休眠是wait_event宏。如下:
wait_event(queue, condition);
wait_event_interruptible(queue, condition);
wait_event_timeout(queue, condition, timeout);
wait_event_iterruptible_timeout(queue, condition, timeout);
queue是等待队列头,它通过“值”传递。condition是一个布尔表达式,此表达式会被多次求值,因此对该表达式的求值不能带来任何副作用。wait_event_interruptible返回一个整数值,非零值表示休眠被某个信号中断。
唤醒的宏有:
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
wake_up会唤醒queue上的所有进程,wake_up_interruptible只会唤醒哪些执行可中断休眠的进程。
阻塞和非阻塞型操作
显式的非阻塞IO由filp->f_flags中的O_NONBLOCK标志决定,这个标志定义在<linux/fcntl.h>。
在驱动程序中实现输出缓冲区可以提高性能。
只有read,write和open文件操作受非阻塞标志影响。
schedule函数:告诉内核重新选择其他进程运行。
高级休眠----手工休眠
1. 建立并初始化一个等待队列入口:
DEFINE_WAIT(my_wait);
也可以通过下步骤建立:
wait_queue_t my_wait;
init_wait(&my_wait);
2. 将我们的等待队列入口加到队列中。
void prepare_to_wait( wait_queue_head_t *queue,
wait_queue_t *wait,
int state);
3. 在prepare_to_wait之后,进程即可调用schedule,当然在这之前,应确保仍有必要等待。
4. 最后清理。
void finish_wait(wait_queue_t *queue, wait_queue_t *wait);
独占等待
void prepare_to_wait_exclusive( wait_queue_head_t *queue,
wait_queue_t *wait,
int state);
设置等待队列入口的“独占”标志,并将进程添加到等待队列的尾部。
查漏补缺
_sync版本函数一般在函数返回前不会重新调度CPU。
#include <linux/poll.h>
void poll_wait(struct file *filp, wait_queue_head_t *q, poll_table *p);
将当前进程置于某个等待队列但不立即调度,该函数主要用于设备驱动程序的poll方法。