阻塞与非阻塞IO与进程的调度密切相关,但是深入的进程调度的算法还不到时间去研究,因为个人的方针是先以设备模型和文件系统为突破口,循序渐进。
这个并不妨碍目前对设备驱动中的阻塞与非阻塞IO的理解。
阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。
非阻塞操作是在这种情况下,进程并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。
休眠对进程意味着什么?当一个进程被置入休眠时,它会被标记为一种特殊状态并从调度器的与行队列中移走。直到某些情况下修改了这个状态,进程才会在任意CPU上调度。进程常用的状态如下:TASK_RUNNING表示进程可运行;TASK_INTERRUPTIBLE和TASK_UNINTERRUPUTIBLE都表示进程处于休眠状态。它们的惟一不同点是处于TASK_UNINTERRUPTIBLE状态的进程会忽略信号,而处于TASK_INTERRUPTIBLE状态的进程如果收到信号会被唤醒并处理信号(然后再次进入等待睡眠状态)。两种状态的进程位于同一个等待队列上,等待某些事件,不能够运行。
要实现阻塞进程的唤醒,需要维护一个称为等待队列(wait queue)的数据结构。wait queue是以linux中经典的双向链表为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制。
下面看下等待队列:
等待队列包含两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个list_head类型的域作为"连接件"。它通过一个双链表和把等待task的头,和等待的进程列表链接起来。等待队列结构体中包含了休眠进程的信息以及其期望被唤醒的相关细节信息。如果要实现一个等待队列,首先要有两个部分。队列头和队列项。下面看他们的数据结构。
struct list_head {
struct list_head *next, *prev;
};
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
void *private;//2.6版本是采用void指针,而以前的版本是struct task_struct * task;
//实际在用的时候,仍然把private赋值为task
wait_queue_func_t func;
struct list_head task_list;
};
等待序列头的初始化:
#define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((q), &__key); \
} while (0)
void __init_waitqueue_head(wait_queue_head_t *q, struct lock_class_key *key)
{
spin_lock_init(&q->lock);
lockdep_set_class(&q->lock, key);
INIT_LIST_HEAD(&q->task_list);
}
其中INIT_LIST_HEAD 就是在list.h中双向链表的初始化表头的函数。
定义等待队列:
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
#define __WAITQUEUE_INITIALIZER(name, tsk) { \
.private = tsk, \
.func = default_wake_function, \
.task_list = { NULL, NULL } }
添加等待队列:
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{
list_add(&new->task_list, &head->task_list);
}
这里还是用linux给出的双向链表的基本操作list_add函数来添加。
移除等待队列:
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__remove_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
static inline void __remove_wait_queue(wait_queue_head_t *head,
wait_queue_t *old)
{
list_del(&old->task_list);
}
同样地,这里也是用双向链表的基本操作list_del函数来做移除操作。
唤醒队列:
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
wake_up会唤醒队列上所有非独占等待的进程,以及单个独占等待者(如果存在)。wake_up_interruptible完成相同的工作,只是它会跳过不可中断休眠的那些进程。
其实这两个函数最终都是用默认的唤醒函数(default_wake_function)来将进程设置为可运行状态。
进程休眠的一般步骤:
1、初始化一个等待队列头;
2、分配并初始化一个wait_queue_t结构,然后将其加入到对应的wait_queue_head_t上。在完成这些工作后,不管谁负责唤醒该进程,都能找到正确的进程;
3、设置进程的状态。将进程设置为休眠状态(TASK_INTERRUPTIBLE or TASK_UNINTERRUPUTIBLE);
4、调用schedule()函数,重新调度;
5、将进程从等待队列中移除,如果不再期望休眠,将进程状态设为TASK_RUNNING。
最后,将scull中的内存改写为FIFO,只有当FIFO中有数据时,读进程才能把数据读出;只有当FIFO非满时,写进程才能往这个FIFO中写入数据。
代码是在linux设备模型之字符设备 和linux设备驱动--并发与竞态之原子操作
中改动的。
改动如下:
struct scull_dev {
unsigned char mem[SCULL_SIZE];
struct cdev cdev; /* Char device structure */
unsigned int current_len;
struct semaphore sem;
wait_queue_head_t r_wait;
wait_queue_head_t w_wait;
};
ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
unsigned long p = *f_pos;
int ret = 0;
struct scull_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current);
down(&dev->sem);
add_wait_queue(&dev->r_wait, &wait);
while(dev->current_len == 0){
if(filp->f_flags & O_NONBLOCK){
ret = - EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
up(&dev->sem);
schedule();
if(signal_pending(current)){
ret = - ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
if(count > dev->current_len)
count = dev->current_len;
if(copy_to_user(buf, dev->mem,count)){
ret = - EFAULT;
goto out;
}else{
memcpy(dev->mem,dev->mem + count, dev->current_len - count);
dev->current_len -= count;
printk(KERN_INFO "read %d bytes,current_len: %d\n",count,dev->current_len);
wake_up_interruptible(&dev->w_wait);
ret = count;
}
out:up(&dev->sem);
out2:remove_wait_queue(&dev->r_wait, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
unsigned long p = *f_pos;
int ret = 0;
struct scull_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current);
down(&dev->sem);
add_wait_queue(&dev->w_wait, &wait);
while(dev->current_len == SCULL_SIZE){
if(filp->f_flags & O_NONBLOCK){
ret = - EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
up(&dev->sem);
schedule();
if(signal_pending(current)){
ret = - ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
if(count > SCULL_SIZE - dev->current_len)
count = SCULL_SIZE - dev->current_len;
if(copy_from_user(dev->mem + dev->current_len, buf, count))
{
ret = -EFAULT;
goto out;
}
else
{
dev->current_len += count;
ret = count;
printk(KERN_WARNING "write %d bytes current_len: %d\n", count , dev->current_len);
wake_up_interruptible(&dev->r_wait);
}
out:up(&dev->sem);
out2:remove_wait_queue(&dev->w_wait,&wait);
set_current_state(TASK_RUNNING);
return ret;
}
int scull_init_module(void)
{
int result, i;
dev_t dev = 0;
/*
* Get a range of minor numbers to work with, asking for a dynamic
* major unless directed otherwise at load time.
*/
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
"scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}
/*
* allocate the devices -- we can't have them static, as the number
* can be specified at load time
*/
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_devices) {
result = -ENOMEM;
goto fail; /* Make this more graceful */
}
memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
/* Initialize each device. */
for (i = 0; i < scull_nr_devs; i++) {
scull_setup_cdev(&scull_devices[i], i);
}
for (i = 0; i < scull_nr_devs; i++) {
init_MUTEX(&scull_devices[i].sem);
init_waitqueue_head(&scull_devices[i].r_wait);
init_waitqueue_head(&scull_devices[i].w_wait);
}
/* At this point call the init function for any friend device */
dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
//dev += scull_p_init(dev);
//dev += scull_access_init(dev);
#ifdef SCULL_DEBUG /* only when debugging */
//scull_create_proc();
#endif
return 0; /* succeed */
fail:
scull_cleanup_module();
return result;
}
利用文件编程:
一个read,一个write:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
void main(void)
{
int fd;
void *buf = malloc(100);
int count;
fd = open("/dev/scull",O_RDWR);
if( fd < 0 )
{
perror("open");
return;
}
count = read( fd, buf, 100);
printf("read %d bytes: %s\n",count,buf);
sleep(100);
close(fd);
exit(0);
}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
void main(void)
{
int fd;
void *buf = "hello world";
int count;
fd = open("/dev/scull",O_RDWR);
if( fd < 0 )
{
perror("open");
return;
}
count = write( fd, buf, 100);
printf("write %d bytes: %s\n",count,buf);
sleep(100);
close(fd);
exit(0);
}
可以展示这个fifo。