1.ioctl
用户空间:int ioctl(int fd, unsigned long cmd, ...);
驱动程序的ioctl原型:int (*ioctl) (struct inode *inode ,struct file *filp, unsigned int cmd, unsigned long arg);
2.ioctl命令号
2.1
命令号在系统范围内应该 唯一。
老方法:16位 高8位为与设备相关的magic number, 低8位为设备内部唯一的一个序列号码
新方法:4位段。 参考include/asm/ioctl.h 和 Documentation/ioctl-number.txt
type 8位宽 magic number
number 8位宽 序数
direction 位掩码 传输方向
size 涉及的用户数据大小,体系结构相关
2.2构造命令号的一些宏
<linux/ioctl.h>中包含的<asm/ioctl.h>中有这些宏的定义
构造命令号的宏 :_IO(type,nr) ; _IOR(type,nr,datatype) ; _IOW(type,nr,datatype) ; _IOWR(type,nr,datatype)
解开命令号的宏 :_IOC_DIR(nr) ;_IOC_TYPE(nr) ; _IOC_NR(nr) ; _IOC_SEZE(nr);
2.3预定义命令号
如果ioctl编号冲突,应用程序的行为将无法预测。
预定义命令分为三组:
(1)可以用于任何文件(普通、设备、FIFO和套接字)的命令
(2)只用于普通文件的命令
(3)特定于文件系统类型的命令
设备驱动只需考虑组(1):magic number 为 “T”
FIOCLEX ; FIONCLEX ; FIOASYNC ;FIOQSIZE ;FIONBIO
3.使用ioctl参数
地址验证 <asm/uacces.h>中声明
int access_ok(int type , const void *addr , unsigned long size );
除了copy_from_usr和copy_to_usr函数外,还可以使用为最常用的数据大小(1,2,4,8字节)优化过的一组函数:
<asm/uaccess.h>中定义
put_user(datum,ptr) ;__put_user(datum,ptr) 会根据ptr的类型,视情况 传递1,2,4,8字节
get_user(local,ptr) ; __get_ptr(local,ptr)
4.权能(capability)与受限操作
内核专为许可管理使用权能,导出了两个系统调用capget和capset,这样就可以从用户空间来管理权能。
对驱动程序开发者来说,有意义的权能:
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)
5.阻塞型I/O
5.1 休眠的几条规则
第一,不要在原子上下文中(如 拥有自旋锁、seqlock 等 ,但拥有信号量时允许)进入睡眠
第二,不能对唤醒之后的状态进行假设,休眠中一切皆有可能。
第三,确定某个地方会唤醒,否则进程不能进入休眠。
5.2等待队列
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);
5.3简单休眠
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为值传递,condition为布尔表达式。
休眠的另一个过程为唤醒。用来唤醒的基本函数是wake_up,有多种形式,其中两个如下
void wake_up (wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
实践中,一般配对使用 ,使用wait_event时使用wake_up ,使用wait_event_interruptible时用wake_up_interruptible
5.4
阻塞和非阻塞型操作
显示的非阻塞I/O由filep->f_flags中的O_NONBLOCK标志决定。这个标志在<linux/fcntl.h>中定义,此头文件包含于<linux/fs.h>. 阻塞与非阻塞可看下面的 一个阻塞I/O示例 的代码
一个阻塞I/O示例 P153
5.5高级休眠
将简单休眠中的宏,函数的实习进行拆分,用一些更低层的函数来实现,需要对Linux的等待队列机制有更深入的理解。
5.5.1进程如何休眠
step1:分配并初始化一个wait_queue_t结构,然后将其加入到对应的等待队列。
step2:设置进程的状态(<linux/sched.h>中定义),将其标记为休眠。void set_current_state(int new_state);
step3:放弃处理器。这之前,我们要检查休眠等待的条件,防止引入竞态。if(!condition) schdule();
5.5.2手工休眠过程
step1:建立并初始化一个等待队列入口。
通常 DEFINE_WAIT(my_wait);
或者wait_queue_t my_wait; init_wait(&my_wait);
step2:将进程的等待队列入口添加到等待队列中,并设置进程状态。
void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state);
step3:在调用prepare_to_wait之后,进程即可调用schedule,当然这之前,为避免竞态,需要判断休眠等待条件。
P158程序实例
5.5.3独占等待
避免“疯狂群兽”行为。p160
5.5.4唤醒的相关细节
当一个进程被唤醒时,实际的结果有等的队列入口中的一个函数控制。默认的唤醒函数将进程设置为可运行状态,并且如果该进程具有更高优先级,则会执行一次上下文切换程序来切换到该进程。
6.poll和select