前言
Linux IO中有阻塞和非阻塞之分。阻塞就是在执行操作的过程中,如果不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。非阻塞则不会挂起,直接返回结果,然后可以不停的来查询直到可以进行操作,也可以放弃操作。
驱动的阻塞和非阻塞IO
驱动实现的read和write操作,可以支持阻塞和非阻塞的操作。需要在read和write接口实现代码中,根据文件的flag实现不同的流程。阻塞的话,进程会挂起,调用schedule让出cpu,直到中断返回才继续执行。非阻塞就直接返回。
是否阻塞,需要在打开文件的时候,传入对应的标记O_NONBLOCK。也可以通过ioctl, fcntl改变属性。
fd = open("/path/to/file", O_RDWR | O_NON_BLOCK);
fcntl(fd, F_SETFL, O_NONBLOCK);
ioctl(m_sock, FIONBIO , &has);
阻塞实现
阻塞实现是基于等待队列来实现的, 等待队列是linux的一个机制。在函数实现时,创建一个等待队列元素,保存当前任务的task结构,然后添加等待队列到等待队列头中。然后在需要阻塞的时候,调用schedule让出CPU。在满足条件的地方调用wake_up函数来调度之前阻塞的任务。
下面分析一下read的代码
static ssize_t globalfifo_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
int ret;
struct globalfifo_dev *dev = filp->private_data; // 获取驱动设备信息
DECLARE_WAITQUEUE(wait, current); // 创建一个等待队列,保存current task
mutex_lock(&dev->mutex);
add_wait_queue(&dev->r_wait, &wait); // 添加到等待队列头r_wait中
while (dev->current_len == 0) { // 只有当没有数据时 需要阻塞住
if (filp->f_flags & O_NONBLOCK) { // 非阻塞直接返回
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
schedule(); // 让出CPU
if (signal_pending(current)) { // 被信号中断 直接返回
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
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(s),current_len:%d\n", count,
dev->current_len);
wake_up_interruptible(&dev->w_wait); // 通知潜在的可能的写阻塞任务
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->r_wait, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
轮询编程
非阻塞的IO可以通过select、poll等接口查询当前文件是否可进行读写操作。实际系统调用的是驱动的poll函数。poll函数的工作主要是调用poll_wait将等待队列添加到poll_table中,并返回对应的掩码标识。这样将触发select或者poll的进一步执行。
__poll_t (*poll) (struct file *, struct poll_table_struct *);
static unsigned int globalfifo_poll(struct file *filp, poll_table * wait)
{
unsigned int mask = 0;
struct globalfifo_dev *dev = filp->private_data;
mutex_lock(&dev->mutex);
poll_wait(filp, &dev->r_wait, wait);
poll_wait(filp, &dev->w_wait, wait);
if (dev->current_len != 0) {
mask |= POLLIN | POLLRDNORM;
}
if (dev->current_len != GLOBALFIFO_SIZE) {
mask |= POLLOUT | POLLWRNORM;
}
mutex_unlock(&dev->mutex);
return mask;
}
在用户空间就可以写对应的poll函数监听文件了。
#define FIFO_CLEAR 0x1
#define BUFFER_LEN 20
void main(void)
{
int fd, num;
char rd_ch[BUFFER_LEN];
fd_set rfds, wfds; /* 读/写文件描述符集 */
/* 以非阻塞方式打开/dev/globalfifo设备文件 */
fd = open("/dev/globalfifo", O_RDONLY | O_NONBLOCK);
if (fd != -1) {
/* FIFO清0 */
if (ioctl(fd, FIFO_CLEAR, 0) < 0)
printf("ioctl command failed\n");
while (1) {
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(fd, &rfds);
FD_SET(fd, &wfds);
select(fd + 1, &rfds, &wfds, NULL, NULL);
/* 数据可获得 */
if (FD_ISSET(fd, &rfds)) {
printf("Poll monitor:can be read\n");
int len = read(fd, rd_ch, BUFFER_LEN);
rd_ch[len-1] = 0;
printf("read data %s\r\n", rd_ch);
}
/* 数据可写入 */
if (FD_ISSET(fd, &wfds))
printf("Poll monitor:can be written\n");
sleep(1000);
}
} else {
printf("Device open failure\n");
}
}
更多
了解了驱动阻塞和非阻塞IO的实现,以及处理用户空间select/poll/epoll的并发IO处理的poll函数实现。
行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。
博客地址: fishmwei.github.io
掘金主页: juejin.cn/user/208432…