【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 轮询

当用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式。
poll、epoll 和 select 可以用于处理轮询。这三个 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);。
    • epfd:epoll 句柄。
    • op:操作标识。
      • EPOLL_CTL_ADD:向 epfd 添加 fd 表示的描述符。
      • EPOLL_CTL_MOD:修改 fd 的 event 时间。
      • EPOLL_CTL_DEL:从 epfd 中删除 fd 描述符。
    • fd:要监测的文件。
    • event:要监测的事件类型。
  3. 等待事件发生。
    • 函数原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);。
    • epfd:epoll 句柄。
    • events:指向 epoll_event 结构体数组。
    • maxevents:events 数组大小,必须大于 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
迷上了代码!