高级字符驱动程序操作(Linux设备驱动程序)

来源:互联网 发布:linux下部署jira 编辑:程序博客网 时间:2024/05/17 22:08
一个实际可用的设备除了提供同步读取和写入之外,还会提供更多的功能。
ioctl系统调用是用于设备控制的公共接口。
除了读取和写入设备之外,大部分驱动程序还需要另外一种能力,即通过设备驱动程序执行各种类型的硬件控制。
比如,用户空间经常会请求设备锁门、弹出介质、报告错误信息、改变波特率或者执行自破坏等等。
这些操作通常通过ioctl方法支持,该方法实现了同名的系统调用。


在用户空间,ioctl系统调用具有如下原型:
int ioctl(int fd, unsigned long cmd, ...);


有许多需求要求通过其他途径实现繁杂的控制操作,可能的方式包括:将命令嵌入到数据流中,或者使用虚拟文件系统,比如sysfs或者设备相关的文件系统。
但现实情况下,对真正的设备操作来说,ioctl仍然是最简单且最直接的选择。


驱动程序的ioctl方法原型和用户空间的版本存在一些不同:
int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);


对schedule的调用将调用调度器,并让出CPU。无论何时调用这个函数,都将告诉内核重新选择其他进程运行,并在必要时将控制切换到那个进程。


“独自等待”选项
等待队列入口设置了WQ_FLAG_EXCLUSIEV标志时,则会被添加到等待队列的尾部,而没有这个标志的入口会被添加到头部。
在某个等待队列上调用wake_up时,它会在唤醒第一个具有WQ_FLAG_EXECLUSIEVE标志的进程之后停止唤醒其他进程。

执行独占等待的进程每次只会被唤醒其中一个(以某种有序的方式),但内核每次仍然会唤醒所有非独占等待进程。


poll、select和epoll的功能本质上是一样的:都允许进程决定是否可以对一个或多个打开的文件做阻塞的读取或写入。
常用于要使用多个输入或输出流而又不会阻塞于其中任何一个流的应用程序中。
select在BSD Unix中引入;
poll由System V引入。
epool将poll函数扩展到能够处理数千个文件描述符。


对上述系统调用的支持需要来自设备驱动程序的相应支持。
通过驱动程序的poll方法提供:
unsigned int (*poll)(struct file *filp, poll_table *wait);
当用户空间程序在驱动程序关联的文件描述符上执行poll、select或epoll系统调用时,该驱动程序方法将被调用。
该设备方法分为两步处理:
1. 在一个或多个可指示poll状态变化的等待队列上调用poll_wait。如果当前没有文件描述符可用来执行I/O,则内核将使进程在传递到该系统调用的所有文件描述符对应的等待队列上等待。
2. 返回一个用来描述操作是否可以立即无阻塞执行的位掩码。


poll_table结构被传递给驱动程序方法,以使每个可以唤醒进程和修改poll操作状态的等待队列都可以被驱动程序装载。

通过poll_wait函数,驱动程序向poll_table结构添加一个等待队列:
void poll_wait(struct file*, wait_queue_head_t *, poll_table *);


对大多数真是的设备,在目前没有数据可获得时,poll方法应该返回POLLHUP。
在真实的FIFO实现中,读取进程在所有的写入进程都关闭了文件后就能看到文件尾。


poll和select的更重要的用途是它们可以使应用程序同时等待多个数据流。


对poll和select系统调用,poll_table结构是包含poll_table_entry结构的内存页链表。
每个poll_table_entry结构包括一个指向被打开设备的struct file类型的指针、一个wati_queue_head_t指针以及一个关联的等待队列入口。

如果轮询(poll)时没有一个驱动程序指明可以进行非阻塞I/O,这个poll调用就进入休眠,直到休眠在其上的某个(或多个)等待队列唤醒它为止。

驱动程序的poll方法在被调用时为poll_table参数传递NULL指针。

在任何被轮询的驱动程序执行可进行I/O之后,poll_table指针也会立即被设置为NULL。



异步通知
为了启动文件的异步通知机制,用户程序必须执行两个步骤。
首先,指定一个进程作为文件的“属主(owner)”。
当进程使用fcntl系统调用执行F_SETOWN命令时,属主进程的进程ID号被保存在filp->f_owner中。

这一步的目的是为了让内核知道应该通知哪个进程。
然后,在设备中设置FASYNC标志,这通过fcntl的F_SETFL命令完成。

signal(SIGIO, &input_handler);
fcntl(STDIN_FILENO, F_SETOWN, getpid());
oflags = fcntl(STDIN_FILENO, F_GETFL);
fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);

应用程序通常假设只有套接字和终端才有异步通知能力。



驱动程序怎样实现异步信号:
1. F_SETOWN被调用时对filp->f_owner赋值。
2. 在执行F_SETFL启用FASYNC时,调用驱动程序的fasync方法。
只要filp->f_flags中的FASYNC标志发生了变化,就会调用fasync方法,以便把这个变化通知驱动程序,使其能正确响应。
文件打开时,FASYNC标志被默认为是清除的。
3. 当数据到达时,所有注册为异步通知的进程都会被发送一个SIGIO信号。

内核维护一个动态数据结构,以跟踪不同的异步读取进程。
含有相关声明的头文件是<linux/fs.h>,基于一个数据结构和两个函数。
数据结构struct fasync_struct,和处理等待队列的方式类似,需要把该类型的指针插入设备指定的数据结构中。
驱动程序要调用的两个函数原型:
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);
void kill_fasync(struct fasync_struct **fa, int sig, int band);

当一个打开的文件的FASYNC标志被修改时,调用fasync_helper以便从相关的进程列表中增加或删除文件。
在数据到达时,可使用kill_fasync通知所有的相关进程,它的参数包括要发送的信号(通常是SIGIO)和带宽(band)。


return fasync_helper(fd, filp, mode, &dev->async_queue);
虽然所有工作都是由fasync_helper完成,但辅助函数需要访问正确的struct fasync_struct *类型(这里是&dev->async_queue)的指针,只有驱动程序才能提供这一信息。


当数据到达时,必须执行下面的语句来通知异步读取进程。
if(dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);


当文件关闭时必须调用fasync方法,以便从活动的异步读取进程列表中删除该文件。


异步通知所使用的数据结构和struct wait_queue使用的几乎是相同的,两种情况都涉及等待事件。


不同之处在于前者用struct file替换了struct task_struct。队列中的file结构用来获取f_owner,以便给进程发送信号。





0 0
原创粉丝点击