您的位置:首页 > 运维架构 > Linux

linux设备驱动程序中的阻塞机制

2015-01-27 15:41 120 查看
阻塞与非阻塞是设备访问的两种方式。在写阻塞与非阻塞的驱动程序时,经常用到等待队列。

一、阻塞与非阻塞

  阻塞调用是指调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才会返回。

  非阻塞指不能立刻得到结果之前,该函数不会阻塞当前进程,而会立刻返回。

  对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但并不是一一对应的。阻塞对象上可以有非阻塞的调用方式,我们可以通过一定的API去轮询状态,在适当的时候调用阻塞函数,就可以避免阻塞。而对于非阻塞对象,调用的函数也可以进入阻塞调用。函数select()就是这样一个例子。

二、等待队列

  在linux设备驱动程序中,阻塞进程可以使用等待队列来实现。

  在内核中,等待队列是有很多用处的,尤其是在中断处理,进程同步,定时等场合,可以使用等待队列实现阻塞进程的唤醒。它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,同步对系统资源的访问。

1、等待队列的实现:

  在linux中,等待队列的结构如下:

struct __wait_queue_head {

spinlock_t lock; //自旋锁,用来对task_list链表起保护作用,实现了对等待队列的互斥访问

struct list_head task_list; //用来存放等待的进程

};

typedef struct __wait_queue_head wait_queue_head_t;



2、等待队列的使用

(1)定义和初始化等待队列:

wait_queue_head_t wait;//定义等待队列

init_waitqueue_head(&wait);//初始化等待队列

定义并初始化等待队列:

#define DECLARE_WAIT_QUEUE_HEAD(name) wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

(2)添加或移除等待队列:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);//将等待队列元素wait添加到等待队列头q所指向的等待队列链表中。

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

(3)等待事件:

wait_event(wq, condition);//在等待队列中睡眠直到condition为真。

wait_event_timeout(wq, condition, timeout);

wait_event_interruptible(wq, condition) ;

wait_event_interruptible_timeout(wq, condition, timeout) ;

/*

*  queue:作为等待队列头的等待队列被唤醒

* conditon:必须满足,否则阻塞

* timeout和conditon相比,有更高优先级

*/

(4)睡眠:

sleep_on(wait_queue_head_t *q);

interruptible_sleep_on(wait_queue_head_t *q);

/*

sleep_on作用是把目前进程的状态置成TASK_UNINTERRUPTIBLE,直到资源可用,q引导的等待队列被唤醒。

interruptible_sleep_on作用是一样的, 只不过它把进程状态置为TASK_INTERRUPTIBLE

*/

(5)唤醒等待队列:

//可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程;

#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)

//只能唤醒处于TASK_INTERRUPTIBLE状态的进程

#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

三、操作系统中睡眠、阻塞、挂起的区别形象解释

  首先这些术语都是对于线程来说的。对线程的控制就好比你控制了一个雇工为你干活。你对雇工的控制是通过编程来实现的。

  挂起线程的意思就是你对主动对雇工说:“你睡觉去吧,用着你的时候我主动去叫你,然后接着干活”。

  使线程睡眠的意思就是你主动对雇工说:“你睡觉去吧,某时某刻过来报到,然后接着干活”。

  线程阻塞的意思就是,你突然发现,你的雇工不知道在什么时候没经过你允许,自己睡觉呢,但是你不能怪雇工,肯定你这个雇主没注意,本来你让雇工扫地,结果扫帚被偷了或被邻居家借去了,你又没让雇工继续干别的活,他就只好睡觉了。至于扫帚回来后,雇工会不会知道,会不会继续干活,你不用担心,雇工一旦发现扫帚回来了,他就会自己去干活的。因为雇工受过良好的培训。这个培训机构就是操作系统。

四、阻塞与非阻塞操作

  阻塞是指没有获得资源则挂起进程,直到获得资源为止。被挂起的进程进入休眠状态,被调度器的运行队列移走,直到等待条件被满足。

非阻塞是不能进行设备操作时不挂起,或放弃,或反复查询,直到可以进行操作为止。


驱动程序常需要这种能力:当应用程序进行read(),write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应该在设备驱动程序的xxx_read(),xxx_write()等操作中将进程阻直到资源可以获取,以后,应用程序的read(),write()等调用返回,整个过程仍然进行了正确的设备访问,用户并没有感知到;若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的xxx_read(),xxx_write()等操作应立即返回,read(),write()等系统调用也随即被访问。

阻塞不是低效率,如果设备驱动不阻塞, 用户想获取设备资源只能不断查询,消耗CPU资源,阻塞访问时,不能获取资源的进程将进入休眠,将CPU资源让给其他资源。

阻塞的进程会进入休眠状态,因此,必须确保有一个地方能唤醒休眠的进程。唤醒进程的地方最大可能发生在中断里面,因为硬件资源获得的同时往往伴随着一个中断

eg1:阻塞地读取串口一个字符

[html]
view plaincopy

char buf;
fd=open("/dev/ttyS1",O_RDWR);
...
res=read(fd,&buf,1);//当串口有输入时才返回
if(res=1)
printf("%c\n",buf);

eg2:非阻塞地读取串口一个字符

[html]
view plaincopy

char buf;
fd=open("/dev/ttyS1",O_RDWR|O_NONBLOCK);
...
while(read(fd,&buf,1)!=1);//串口上无输入也返回,所以要循环尝试读取串口
printf("%c\n",buf);

五,等待队列

在linux驱动程序中,可使用等待队列(wait queue)来实现阻塞进程的唤醒,以队列为基础数据结构,与进程调度机制紧密结合,用于实现内核中的异步事件通知机制,也可用于同步对系统资源的访问。

定义"等待队列头"

[html]
view plaincopy

wait_queue_head_t my_queue;

初始化"等待队列头"

[html]
view plaincopy

init_waitqueue_head(&my_queue);

宏名用于定义并初始化,相当于"快捷方式"

[html]
view plaincopy

DECLARE_WAIT_QUEUE_HEAD (name);

定义等待队列

[html]
view plaincopy

DECLARE_WAITQUEUE(name,tsk);定义并初始化一个名为name的等待队列

添加/移除等待队列

[html]
view plaincopy

void fastcall add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);
void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);

等待事件

[html]
view plaincopy

wait_event(queue,condition);等待以queue为等待队列头等待队列被唤醒,condition必须满足,否则阻塞
wait_event_interruptible(queue,condition);可被信号打断
wait_event_timeout(queue,condition,timeout);阻塞等待的超时时间,时间到了,不论condition是否满足,都要返回
wait_event_interruptible_timeout(queue,condition,timeout)

唤醒队列

[html]
view plaincopy

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

上述操作唤醒以queue作为等待队列头的所有等待队列中所有属于该等待队列头的等待队列对应的进程。

wake_up()与wake_event()或者wait_event_timeout成对使用,

wake_up_intteruptible()与wait_event_intteruptible()或者wait_event_intteruptible_timeout()成对使用。


六,轮询操作

在用户程序中,select()和poll()也是与设备阻塞与非阻塞访问的,使用非阻塞I/O的应用程序常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问。

select()和poll()系统调用最终会引发设备驱动中的poll()函数被执行。

应用程序中的轮询编程

[html]
view plaincopy

int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,
struct timeval *timeout);

readfds,writefds,exceptfds是被select监视的读,写,异常处理的文件描述符集合。

numfds是需要检查的号码最高的文件描述符加1,timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout时间后若没有文件描述符准备好则返回。

[html]
view plaincopy

struct timeval
{
int tv_sec;
int tv_usec;
};

[html]
view plaincopy

FD_ZERO(fd_set *set);//清除一个文件描述符
FD_SET(int fd,fd_set *set);//将一个文件描述符加入文件描述符集中
FD_CLR(int fd,fd_set *set);//将一个文件描述符从文件描述符集中清除
FD_ISSET(int fd,fd_set *set);//判断文件描述符是否被置位

设备驱动中的轮询编程

设备驱动中poll()函数:

[html]
view plaincopy

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

主要进行两项工作:

1.对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头添加到poll_table;

2.返回表示是否能对设备进行无阻塞读,写访问的掩码。

用于向poll_table注册等待队列的poll_wait()函数的原型

[html]
view plaincopy

void poll_wait(struct file *filp,wait_queue_head_t *queue,poll_table *wait);

此函数用于将当前进程添加到wait参数指定的等待列表(poll_table)中。

驱动程序poll()返回设备资源的可获取状态,POLLIN(定义为0x0001)意味着设备可以无阻塞地读,POLLOUT(定义为0x0004)意味着可以无阻塞的写。

eg3:poll()函数典型模板

[html]
view plaincopy

static unsigned int xxx_poll(struct file *filp,poll_table *wait)
{
unsigned int mask=0;
struct xxx_dev *dev=filp->private_data;
...
poll_wait(filp,&dev->r_wait,wait);
poll_wait(filp,&dev->w_wait,wait);
if(...)//可读
{
mask|= POLLIN |POLLRDNORM;//数据可获得
}
if(...)//可写
{
mask|= POLLOUT|POLLWRNORM;//数据可写入
}
...
return mask;
}

[html]
view plaincopy

eg4:globalfifo设备驱动的poll()函数

[html]
view plaincopy

static unsigned int globalfifo_poll(struct file *filp,poll_table *wait)
{
unsigned int mask=0;
struct globalfifo_dev *dev=filp->private_data;
down(&dev->sem);
poll_wait(filp,&dev->r_wait,wait);
poll_wait(filp,&dev->w_wait,wait);
//fifo非空
if(dev->current_len!=0)
{
mask|=POLLIN |POLLRDNORM;//标示数据可获得
}
//fifo非满
if(dev->current_len!=GLOBALFIFO_SIZE)
{
mask|=POLLOUT |POLLWRNORM;
}
up(&dev->sem);
return mask;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: