前言
Linux IO中有阻塞和非阻塞之分。阻塞就是在执行操作的过程中,如果不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。非阻塞则不会挂起,直接返回结果,然后可以不停的来查询直到可以进行操作,也可以放弃操作。
驱动的阻塞和非阻塞IO
驱动实现的read和write操作,可以支持阻塞和非阻塞的操作。需要在read和write接口实现代码中,根据文件的flag实现不同的流程。阻塞的话,进程会挂起,调用schedule让出cpu,直到中断返回才继续执行。非阻塞就直接返回。
是否阻塞,需要在打开文件的时候,传入对应的标记O_NONBLOCK。也可以通过ioctl, fcntl改变属性。
1 2 3 4 5 6 7 8 9 10
| 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的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
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);
mutex_lock(&dev->mutex); 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); mutex_unlock(&dev->mutex);
schedule(); 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的进一步执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| __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函数监听文件了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
#define FIFO_CLEAR 0x1 #define BUFFER_LEN 20 void main(void) { int fd, num; char rd_ch[BUFFER_LEN]; fd_set rfds, wfds;
fd = open("/dev/globalfifo", O_RDONLY | O_NONBLOCK); if (fd != -1) { 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,沟通交流。
博客地址: https://fishmwei.github.io
掘金主页: https://juejin.cn/user/2084329776486919