Chinaunix首页 | 论坛 | 博客
  • 博客访问: 281412
  • 博文数量: 59
  • 博客积分: 1346
  • 博客等级: 中尉
  • 技术积分: 461
  • 用 户 组: 普通用户
  • 注册时间: 2011-01-06 17:17
文章分类

全部博文(59)

文章存档

2012年(9)

2011年(50)

分类: LINUX

2011-01-23 16:25:17

Linux设备驱动程序学习(5)
-高级字符驱动程序操作[(2)阻塞型I/O和休眠]
 

这一部分主要讨论:如果驱动程序无法立即满足请求,该如何响应?(65865346)

一、休眠

进程被置为休眠,意味着它被标识为处于一个特殊的状态并且从调度器的运行队列中移走。这个进程将不被在任何 CPU 上调度,即将不会运行。 直到发生某些事情改变了那个状态。安全地进入休眠的两条规则:

(1) 永远不要在原子上下文中进入休眠,即当驱动在持有一个自旋锁、seqlock或者 RCU 锁时不能睡眠;关闭中断也不能睡眠。持有一个信号量时休眠是合法的,但你应当仔细查看代码:如果代码在持有一个信号量时睡眠,任何其他的等待这个信号量的线程也会休眠。因此发生在持有信号量时的休眠必须短暂,而且决不能阻塞那个将最终唤醒你的进程。

(2)当进程被唤醒,它并不知道休眠了多长时间以及休眠时发生什么;也不知道是否另有进程也在休眠等待同一事件,且那个进程可能在它之前醒来并获取了所等待的资源。所以不能对唤醒后的系统状态做任何的假设,并必须重新检查等待条件来确保正确的响应

除非确信其他进程会在其他地方唤醒休眠的进程,否则也不能睡眠。使进程可被找到意味着:需要维护一个称为等待队列的数据结构。它是一个进程链表,其中饱含了等待某个特定事件的所有进程。在 Linux 中, 一个等待队列由一个wait_queue_head_t 结构体来管理,其定义在中。wait_queue_head_t 类型的数据结构非常简单:

struct __wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

它包含一个自旋锁和一个链表。这个链表是一个等待队列入口,它被声明做 wait_queue_t。wait_queue_head_t包含关于睡眠进程的信息和它想怎样被唤醒。

简单休眠(其实是高级休眠的宏)

Linux 内核中最简单的休眠方式是称为 wait_event的宏(及其变种),它实现了休眠和进程等待的条件的检查。形式如下:

wait_event(queue, condition)/*不可中断休眠,不推荐*/
wait_event_interruptible(queue, condition)/*推荐,返回非零值意味着休眠被中断,且驱动应返回 -ERESTARTSYS*/
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
/*有限的时间的休眠;若超时,则不管条件为何值返回0,*/

唤醒休眠进程的函数称为 wake_up,形式如下:

void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);

惯例:用 wake_up 唤醒 wait_event ;用 wake_up_interruptible 唤醒wait_event_interruptible。

简单休眠实验
模块程序链接:sleepy
模块测试程序链接sleepy-test

国嵌实验代码   5-2-2.rar   

 

阻塞和非阻塞操作

全功能的 read 和 write 方法涉及到进程可以决定是进行非阻塞 I/O还是阻塞 I/O操作。明确的非阻塞 I/O 由 filp->f_flags 中的 O_NONBLOCK 标志来指示(定义再 ,被 自动包含)。浏览源码,会发现O_NONBLOCK 的另一个名字:O_NDELAY ,这是为了兼容 System V 代码。O_NONBLOCK 标志缺省地被清除,因为等待数据的进程的正常行为只是睡眠.

其实不一定只有read 和 write 方法有阻塞操作

  • 如果一个进程调用 read 但是没有数据可用(尚未), 这个进程必须阻塞. 这个进程在有数据达到时被立刻唤醒, 并且那个数据被返回给调用者, 即便小于在给方法的 count 参数中请求的数量.

  • 如果一个进程调用 write 并且在缓冲中没有空间, 这个进程必须阻塞, 并且它必须在一个与用作 read 的不同的等待队列中. 当一些数据被写入硬件设备, 并且在输出缓冲中的空间变空闲, 这个进程被唤醒并且写调用成功, 尽管数据可能只被部分写入如果在缓冲只没有空间给被请求的 count 字节.

poll和sele

 

Select系统调用用于多路监控,当没有一个文件

满足要求时,select将阻塞调用进程。

int select(int maxfd, fd_set *readfds, fd_set *writefds, fe_set

*exceptfds, const struct  timeval *timeout)

v Maxfd:

文件描述符的范围,比待检测的最大文件描述符大1

v Readfds:

被读监控的文件描述符集

v Writefds:

被写监控的文件描述符集

v Exceptfds:

被异常监控的文件描述符集;

v Timeout:

定时器

 

select使用方法

1. 将要监控的文件添加到文件描述符集

2. 调用Select开始监控

3. 判断文件是否  生变化

 

系统提供了4个宏对描述符集进行操作:

#include

void FD_SET(int fd, fd_set *fdset)

void FD_CLR(int fd, fd_set *fdset)

void FD_ZERO(fd_set *fdset)

void FD_ISSET(int fd, fd_set *fdset)

宏FD_SET将文件描述符fd添加到文件描述符集fdset中;

宏FD_CLR从文件描述符集fdset中清除文件描述符fd;

宏FD_ZERO清空文件描述符集fdset;

在调用select后使用FD_ISSET来检测文件描述符集fdset中的文件的变化

 

例子:

D_SET(fd1,&fds); //设置描述符

FD_SET(fd2,&fds); //设置描述符

maxfdp=fd1+1;  //描述符最大值加1,假设fd1>fd2  

switch(select(maxfdp,&fds,NULL,NULL,&timeout))

case -1: exit(-1);break; //select错误,退出程序  

case 0:break;

default:

if(FD_ISSET(fd1,&fds)) //测试fd1是否可读FD_ZERO(&fds); //清空集合

 

 

POLL使用

 

 

应用程序常常使用select系统调用,它可能会阻塞

进程。这个调用由驱动的 poll方法实现,原型为:

unsigned int (*poll)(struct file *filp,poll_table *wait)

 

Poll设备方法

1. 使用poll_wait将等待队列添加到poll_table中。

2. 返回描述设备是否可读或可写的掩码

 

位掩码

 

v POLLIN

设备可读

v POLLRDNORM

数据可读

v POLLOUT

设备可写

v POLLWRNORM

数据可写

设备可读通常返回(POLLIN|POLLRDNORM )

设备可写通常返回(POLLOUT|POLLWRNORM )

 

例子:

 

static unsigned int mem_poll(struct file *filp,poll_table *wait)

{

struct scull_pipe *dev =filp->private_data;

unsigned int mask =0;

/* 把进程添加到等待队列 */

poll_wait(filp,&dev->inq,wait);

}

/*返回掩码*/

if (有数据可读)

mask = POLLIN |POLLRDNORM;/*设备可读*/

return mask;

 

 

国嵌实验代码 5-2-3.rar   

 

 

 

自动创建文件设备

分两步,下面用一个例子来说明

struct class *myclass = class_create(THIS_MODULE,

"my_device_driver");

device_create(myclass, NULL, MKDEV(major_num, 0), NULL,

"my_device");

当驱动被加载时,udev( mdev )就会自动在/dev下创建 my_device 文件。

 

 

llseek 实现

 

llseek 方法实现了 lseek 和 llseek 系统调用. 我们已经说了如果 llseek 方法从设备的操作中缺失, 内核中的缺省的实现进行移位通过修改 filp->f_pos, 这是文件中的当前读写位置. 请注意对于 lseek 系统调用要正确工作, 读和写方法必须配合, 通过使用和更新它们收到的作为的参数的 offset 项.

你可能需要提供你自己的方法, 如果移位操作对应一个在设备上的物理操作. 一个简单的例子可在 scull 驱动中找到:

loff_t scull_llseek(struct file *filp, loff_t off, int whence) { struct scull_dev *dev = filp->private_data; loff_t newpos; switch(whence) { case 0: /* SEEK_SET */ newpos = off; break; case 1: /* SEEK_CUR */ newpos = filp->f_pos + off; break; case 2: /* SEEK_END */ newpos = dev->size + off; break; default: /* can't happen */ return -EINVAL; } if (newpos < 0) return -EINVAL; filp->f_pos = newpos; return newpos; }


 

阅读(1328) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~