Linux驱动开发 IO模型:阻塞IO

1、阻塞IO模型是什么

当应用程序发起读取数据(read)的时候,如果数据没有准备好,就会阻塞等待(进程休眠),如果与硬件的数据准备好了,就会产生硬件中断,在中断处理函数中唤醒休眠的进程,然后将准备好的数据拷贝至用户空间。

2、应用程序

应用程序默认是以阻塞方式打开,所以不需要特别设置

open("/dev/mycdev",O_RDWR|O_NONBLOCK);

3、驱动程序

3.1 阻塞相关的步骤

1、定义等待队列头

2、初始化等待队列头

3、如果数据没有准备好,就进行休眠

4、如果数据准备好了,就唤醒

3.2 阻塞相关的API

//1、定义等待队列头
wait_queue_head_t wq;

struct __wait_queue_head {
	spinlock_t		lock;
	struct list_head	task_list;
};
//2、初始化等待队列头
init_waitqueue_head(&wq);
//3.1、休眠操作,一个函数实现
//如果数据没有准备好,就进行休眠
wait_event(wq, condition);   //进入不可中断的休眠态
wait_event_interruptible(wq, condition) // 让进程进入可中断的休眠态
//wq: 等待队列头
//condition: 代表数据是否准备好,如果为真,代表数据准备好了,不需要休眠
//                              如果为假,代表数据没有准备好,进程需要休眠
//3.2分步实现等待队列
//等待队列项操作函数
//定义并初始化一个等待队列项
DECLARE_WAITQUEUE(name, tsk)
//name:就是等待队列项的名字, 
//tsk:表示这个等待队列项属于哪个任务(进程),一般设置为current,
//在Linux内核中 current相当于一个全局变量,表示当前进程。
//因此宏DECLARE_WAITQUEUE就是给当前正在运行的进程创建并初始化了一个等待队列项。

//等待队列项添加
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
//q:等待队列项要加入的等待队列头。
//wait:要加入的等待队列项。

//等待队列项移除
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
//q:要删除的等待队列项所处的等待队列头。
//wait:要删除的等待队列项。

struct __wait_queue {
	unsigned int		flags;
	void			*private;
	wait_queue_func_t	func;
	struct list_head	task_list;
};
//4、如果数据准备好了,就唤醒
condition = 1;   //设置为真,代表数据准备好了
void wake_up(wait_queue_head_t *q) 
void wake_up_interruptible(wait_queue_head_t *q)

3.2.1 wait_event_interruptible()函数解析

这个函数做了上述自己定义等待队列项,添加等待队列项的操作,所以稍微解析一下

wait_event_interruptible(wq, condition)
->
__wait_event_interruptible(wq, condition);
->
___wait_event(wq, condition, TASK_INTERRUPTIBLE, 0, 0, schedule())
{
    //定义了一个等待队列项
    wait_queue_t __wait;

    //初始化了一个等待队列项
    init_wait_entry(&__wait, exclusive ? WQ_FLAG_EXCLUSIVE : 0);

    for (;;) {
        //将等待队列项放在队列头的队列尾
        //将task_struct->state = TASK_INTERRUPTIBLE
        prepare_to_wait_event(&wq, &__wait, state);    

        //等待数据到来,退出循环
        if (condition)						
		    break;
        //传入的cmd为schedule(),即主动放弃CPU
        cmd;
    }
    //删除等待队列项
    //将进程状态设置为运行态
    finish_wait(&wq, &__wait);
}

typedef struct __wait_queue wait_queue_t;

void init_wait_entry(wait_queue_t *wait, int flags)
{
	wait->flags = flags;
	wait->private = current; //current是当前进程的结构体
	wait->func = autoremove_wake_function; //唤醒的函数
	INIT_LIST_HEAD(&wait->task_list); //初始化队列的指针
}

long prepare_to_wait_event(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
	unsigned long flags;
	long ret = 0;

	spin_lock_irqsave(&q->lock, flags);
    //判断是否为信号引起的唤醒
	if (unlikely(signal_pending_state(state, current))) {
		list_del_init(&wait->task_list);
		ret = -ERESTARTSYS;
	} else {
		if (list_empty(&wait->task_list)) {
			if (wait->flags & WQ_FLAG_EXCLUSIVE)
				__add_wait_queue_tail(q, wait);
			else
                //将等待队列项加入等待队列
				__add_wait_queue(q, wait);
		}
        //设置传入状态TASK_INTERRUPTIBLE
		set_current_state(state);
	}
	spin_unlock_irqrestore(&q->lock, flags);

	return ret;
}

void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
	unsigned long flags;

    //将进程状态设置为运行态
	__set_current_state(TASK_RUNNING);
	
	if (!list_empty_careful(&wait->task_list)) {
		spin_lock_irqsave(&q->lock, flags);
        //删除等待队列项
		list_del_init(&wait->task_list);
		spin_unlock_irqrestore(&q->lock, flags);
	}
}

3.3 阻塞驱动程序

一般来说,都是在中断函数中去唤醒,但是写个中断又有点麻烦,所以这里采用的是另起一个进程,在read函数中去唤醒进程。

4、将非阻塞打开的文件设置阻塞

int flags = fcntl(fd, F_GETFL); 
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); /* 阻塞方式 */

你可能感兴趣的:(Linux应用开发,Linux驱动开发,linux,驱动开发,IO模型,Linux应用程序开发,阻塞IO)