【linux】驱动-13-阻塞与非阻塞

时间:2021-06-21 20:38:47   收藏:0   阅读:0


前言

13. 阻塞与非阻塞

本章内容为驱动基石之一
驱动只提供功能,不提供策略

阻塞与非阻塞 都是应用程序主动访问的。从应用角度去解读阻塞与非阻塞。

原文:https://www.cnblogs.com/lizhuming/p/14912496.html

13.1 阻塞与非阻塞

阻塞

非阻塞

实现阻塞的常用技能包括:(目的其实就是阻塞)

13.2 休眠与唤醒

若需要实现阻塞式访问,可以使用休眠与唤醒机制。

相关函数其实在 等待队列 小节有说明了,现在只是函数汇总。

13.2.1 内核休眠函数

内核源码路径:include\linux\wait.h。

函数名 描述
wait_event(wq, condition) 休眠,直至 condition 为真;休眠期间不能被打断。
wait_event_interruptible(wq, condition) 休眠,直至 condition 为真;休眠期间可被打断,包括信号。
wait_event_timeout(wq, condition, timeout) 休眠,直至 condition 为真或超时;休眠期间不能被打断。
wait_event_interruptible_timeout(wq, condition, timeout) 休眠,直至 condition 为真或超时;休眠期间可被打断,包括信号。

13.2.2 内核唤醒函数

内核源码路径:include\linux\wait.h。

函数名 描述
wake_up_interruptible(x) 唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”的线程,只唤醒其中的一个线程
wake_up_interruptible_nr(x, nr) 唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”的线程,只唤醒其中的 nr 个线程
wake_up_interruptible_all(x) 唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”的线程,唤醒其中的所有线程
wake_up(x) 唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”或“TASK_UNINTERRUPTIBLE”的线程,只唤醒其中的一个线程
wake_up_nr(x, nr) 唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”或“TASK_UNINTERRUPTIBLE”的线程,只唤醒其中 nr 个线程
wake_up_all(x) 唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”或“TASK_UNINTERRUPTIBLE”的线程,唤醒其中的所有线程

13.3 等待队列(阻塞)

等待队列

使用方法

  1. 定义等待队列头部。
  2. 初始化等待队列头部。
  3. 定义等待队列元素。
  4. 添加/移除等待队列。
  5. 等待事件。
  6. 唤醒队列。

另外一种使用方法就是 在等待队列上睡眠

等待队列头部结构体

struct wait_queue_head {
	spinlock_t		lock;
	struct list_head	head;
};
typedef struct wait_queue_head wait_queue_head_t;

等待队列元素结构体

struct wait_queue_entry {
	unsigned int		flags;
	void			*private;
	wait_queue_func_t	func;
	struct list_head	entry;
};

13.3.1 定义等待队列头部

定义等待队列头部方法:wait_queue_head_t my_queue;

13.3.2 初始化等待队列头部

初始化等待队列头部源码:void init_waitqueue_head(wait_queue_head_t *q);

定义&初始化等待队列头部:使用宏 DECLARE_WAIT_QUEUE_HEAD

13.3.3 定义等待队列元素

定义等待队列元素源码:#define DECLARE_WAITQUEUE(name, tsk);

13.3.4 添加/移除等待队列元素

添加等待队列元素源码:void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry);

移除等待队列元素源码:void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry);

13.3.5 等待事件

睡眠,直至事件发生:wait_event(wq_head, condition)

/**
 * wait_event - sleep until a condition gets true
 * @wq_head: the waitqueue to wait on
 * @condition: a C expression for the event to wait for
 *
 * The process is put to sleep (TASK_UNINTERRUPTIBLE) until the
 * @condition evaluates to true. The @condition is checked each time
 * the waitqueue @wq_head is woken up.
 *
 * wake_up() has to be called after changing any variable that could
 * change the result of the wait condition.
 */
#define wait_event(wq_head, condition)						do {											might_sleep();									if (condition)										break;									__wait_event(wq_head, condition);					} while (0)

睡眠,直至事件发生或超时:wait_event_timeout(wq_head, condition, timeout)

等待事件发生,且可被信号中断唤醒:wait_event_interruptible(wq_head, condition)
等待事件发生或超时,且可被信号中断唤醒:wait_event_interruptible_timeout(wq_head, condition, timeout)

io_wait_event()

/*
 * io_wait_event() -- like wait_event() but with io_schedule()
 */
#define io_wait_event(wq_head, condition)					do {											might_sleep();									if (condition)										break;									__io_wait_event(wq_head, condition);					} while (0)

13.3.6 唤醒队列

以下两个函数对应等待事件使用

13.3.7 在等待队列上睡眠

函数源码:

13.4 轮询

当用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式。
pollepollselect 可以用于处理轮询。这三个 API 均在 应用层 使用。

注意,轮询也是在APP实现轮询的。

13.4.1 select 函数

select()

struct timeval{
    long tv_sec;    // 秒
    long tv_usec;   // 微妙
};
FD_CLR(int fd, fd_set *set); // 把 fd 对应的 set bit 清空。
FD_ISSET(int fd, fd_set *set); // 查看 d 对应的 set bit 是否被置 **1**。
FD_SET(int fd, fd_set *set); // 把 fd 对应的 set bit 置 **1**。
FD_ZERO(fd_set *set); // 把 set 全部清空。

fd_set 是有限制的,可以查看源码,修改也可。但是改大会影响系统效率。

13.4.2 poll 函数

由于 fd_set 是有限制的,所以当需要监测大量文件时,便不可用。
这时候,poll() 函数就应运而生。

poll()select() 没什么区别,只是前者没有最大文件描述符限制。

被监视的文件描述符格式

struct pollfd{
    int fd; /* 文件描述符 */
    short events; /* 请求的事件 */
    short revents; /* 返回的时间 */
}

可请求的事件 events

说明
POLLIN 有数据可读
POLLPRI 有紧急的数据需要读取
POLLOUT 可以写数据
POLLERR 指定的文件描述符发生错误
POLLHUP 指定的文件描述符被挂起
POLLNVAL 无效的请求
POLLRDNORM 等同于 POLLIN

13.4.3 epoll 函数

select()poll() 会随着监测的 fd 数量增加,而出现效率低下的问题。
poll() 每次监测都需要历遍所有被监测的描述符。

epoll() 函数就是为大量并大而生的。在网络编程中比较常见。

epoll() 使用方法:

  1. 创建一个 epoll 句柄:
    • 函数原型:int epoll_creat(int size);
    • size:随便大于 0 即可。 Linux2.6.8 后便不再维护了。
    • 返回
      • epoll 句柄。
      • -1:创建失败。
  2. epoll 添加要监视的文件及监测的事件。
    • 函数原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    • epfdepoll 句柄。
    • op:操作标识。
      • EPOLL_CTL_ADD:向 epfd 添加 fd 表示的描述符。
      • EPOLL_CTL_MOD:修改 fdevent 时间。
      • EPOLL_CTL_DEL:从 epfd 中删除 fd 描述符。
    • fd:要监测的文件。
    • event:要监测的事件类型。
  3. 等待事件发生。
    • 函数原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    • epfdepoll 句柄。
    • events:指向 epoll_event 结构体数组。
    • maxeventsevents 数组大小,必须大于 0。
    • timeout:超时时间。

epoll_event 结构体

struct epoll_event{
    uint32_t events; /* epoll 事件 */
    epoll_data_t data; /* 用户数据 */
}

可请求的事件 events

说明
EPOLLIN 有数据可读
EPOLLPRI 有紧急的数据需要读取
EPOLLOUT 可以写数据
EPOLLERR 指定的文件描述符发生错误
EPOLLHUP 指定的文件描述符被挂起
EPOLLET 设置 epoll 为边沿触发,默认触发模式为水平触发
EPOLLONESHOT 一次性的监视,当监视完成后,还需要监视某个 fd,那就需要把 fd 重新添加到 epoll

13.5 驱动中的 poll 函数

当应用程序调用 select() 函数和 poll() 函数时,驱动程序会调用 file_operations 中的 poll

poll_wait()

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!