I/O的阻塞操作与等待队列
1,基本概念
什么是阻塞操作?
这个好理解,其实就是对设备的操作没成功,进程进入等待状态,等待系统唤醒。
在代码的层面理解: 运行到某代码(如某read()操作), 进程挂在那了,不再往下运行。
下面是我对等待队列的理解:
如上图,一个FIFO设备的读写,需要两个wait_queue_head,分别对应读、写。
等待读、写的进程分别挂在这两个wait_queue_head后面。
|
2,实例
如果不关心具体实现,只需要了解怎么使用等待队列的话,所需要做的工作还是很少的。(原理后续再研究)
假设一个my_fifo设备,实现: 如果没有进程写,则试图读的进程会被阻塞
1) 该设备的结构体
- structmyfifo_dev
- {
- /*其他成员这里就不列出了*/
- wait_queue_head_t r_write; // 阻塞写的等待队列头
- wait_queue_head_t r_read; // 阻塞读的等待队列头
- };
2) 等待队列的初始化
从上文我们知道,这个就是等待队列3类操作中的第1类了 :)
问1:在哪里初始化呢?
答:当然是在内核模块加载函数中。
问2: 怎么初始化?
答:调用init_waitqueue_head( r_wait )。 (这个r_wait是怎么来的?,呵呵,设备结构体中)
问3:关于初始化还有别的东西吗?
答:好像没有了,就这么简单。
3) 将一个进程加入等待队列(阻塞)
根据前面的功能描述,因为是read()操作被阻塞,所以需要在read()函数中做修改,如下:
- static ssize_t mydev_read(struct file*filp, char* buf, size_t len,loff_t* off)
- {
- if(wait_event_interruptible(r_read,flag!=0) ) // 就是这里
- return-ERESTARTSYS;
- if(down_interruptible(&sem) )
- return-ERESTARTSYS;
- flag= 0;
- if(copy_to_user(buf,data,len) )
- {
- up(&sem);
- return-EFAULT;
- }
- up(&sem);
- returnlen;
- }
4) 将一个进程从等待队列移出(唤醒)
根据前面的功能,只要有进程执行write()操作,则就会唤醒被read()阻塞的进程。所以,需要在write()函数中做修改:
- ssize_t mydev_write(struct file *file,const char __user *buf, size_t count, loff_t *f_pos)
- {
- ssize_t ret = 0;
- pr_info("mydev_write!\n");
- pr_info("writing %d bytes\n", count);
- if( down_interruptible(&sem) )
- return-ERESTARTSYS;
- if (count > 127)
- return -ENOMEM;
- if (count < 0)
- return -EINVAL;
- if (copy_from_user(data, buf, count))
- {
- up(&sem);
- return -EFAULT;
- }
- else {
- data[127] = '\0';
- pr_info("kernel received: %s\n", data);
- ret = count;
- }
- up(&sem);
- flag = 1;
- wake_up_interruptible(&r_read); //就是这里
- return ret;
- }
新手学习有啥不对的地方,欢迎砸板砖! 谢谢~