关闭

第六章:高级字符驱动程序操作

132人阅读 评论(0) 收藏 举报

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);

inodefilp两个指针的值对应于应用程序传递的文件描述符fdcmd是用户空间传递过来的值。可选的arg,无论用户传递的是整数还是指针,都以unsigned long的形式传给arg

通常ioctl是一个switch语句,根据传递进来的cmd执行相应的操作。

选择ioctl命令

为驱动程序选择一个ioctl编号,应该首先看看include/asm/ioctl.hDocumention/ioctl-number.txt这两个文件。

cmdtypenumberdirectionsize四个部分组成。四部分定义在<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的命令时,需要注意避免与内核已有的命令相冲突。内核已有的命令有:FIOCLEXFIONCLEXFIOASYNCFIOQSIZEFIONBIO

使用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)。可以用来与用户空间交换1248字节内容。若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只会唤醒哪些执行可中断休眠的进程。

阻塞和非阻塞型操作

显式的非阻塞IOfilp->f_flags中的O_NONBLOCK标志决定,这个标志定义在<linux/fcntl.h>

在驱动程序中实现输出缓冲区可以提高性能。

只有readwriteopen文件操作受非阻塞标志影响。

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方法。

 

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:13826次
    • 积分:289
    • 等级:
    • 排名:千里之外
    • 原创:15篇
    • 转载:1篇
    • 译文:0篇
    • 评论:0条
    文章存档