0%

设备驱动的阻塞和非阻塞IO, 轮询编程

前言

Linux IO中有阻塞和非阻塞之分。阻塞就是在执行操作的过程中,如果不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。非阻塞则不会挂起,直接返回结果,然后可以不停的来查询直到可以进行操作,也可以放弃操作。

驱动的阻塞和非阻塞IO

驱动实现的read和write操作,可以支持阻塞和非阻塞的操作。需要在read和write接口实现代码中,根据文件的flag实现不同的流程。阻塞的话,进程会挂起,调用schedule让出cpu,直到中断返回才继续执行。非阻塞就直接返回。

driver_io

是否阻塞,需要在打开文件的时候,传入对应的标记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); // 创建一个等待队列,保存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的进一步执行。

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; /* 读/写文件描述符集 */

/* 以非阻塞方式打开/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,沟通交流。

博客地址: https://fishmwei.github.io

掘金主页: https://juejin.cn/user/2084329776486919