Linux驱动的等待队列、轮询及内核线程

1、简介

根据不同需求,linux内核有不同I/O操作模型:
非阻塞: 进程在不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直到可以进行操作为止
阻塞: 指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作条件后再进行各项操作

等待队列:
用来阻塞或唤醒一个进程,实现阻塞I/O访问
轮询操作:
阻塞I/O访问的应用程序通常使用select()和poll()系统调用查询机制来实现的。
2、等待队列

等待队列在Linux内核中用来阻塞或唤醒一个进程,也可以用来同步对系统资源的访问,还可以实现延迟功能
2.1 主要方法

#include <linux/wait.h>

1.定义、初始化等待队列(指向等待队列链表)
//定义一个等待队列头
wait_queue_head_t my_queue;
//初始一个等待队列头
init_waitqueue_head(&my_queue);
//定义并初始化一个等待队列头
DECLARE_WAIT_QUEUE_HEAD(my_queue);

2.进程的睡眠操作——条件睡眠
//判断condition条件,决定是否将当前进程推入等待队列
wait_event(wait_queue_head_t wq, int condition);
wait_event_interruptible(wait_queue_head_t wq,int condition);
wait_event_timeout(wait_queue_head_t wq, int condition, long timeout);
wait_event_interruptiblble_timeout(wait_queue_head_t wq, int condition, long timeout);
数wq:表示等待队列头
数condition:阻塞条件,为假(0)则进入休眠直到wake_up且condition为真条件成立才退出
数timeout:表示睡眠指定时长(时钟滴答度量,eg.延时2秒=2*HZ)后,自动转入唤醒状态

3.进程的睡眠操作——无条件睡眠
//将当前进程推入等待队列将其睡眠,wake_up唤醒
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
long sleep_on_timeout(wait_queue_head_t *q, long timeout);
long interruptible_sleep_on_timeout(wait_queue_head_t *q, long timeout);
wq:表示等待队列头
timeout:表示睡眠指定时长后,自动转入唤醒状态

4.进程唤醒函数
wake_up(wait_queue_head_t *wq);
wake_up_interruptible(wait_queue_head_t *wq)

  

注意事项:
1.唤醒函数和导致睡眠函数要配对使用,如果导致睡眠函数使用带interruptible的,则唤醒函数也要使用interruptible的。
2.在使用wake_up唤醒进程之前要将wait_event中的condition变量的值赋为真,否则该进程被唤醒后会立即再次进入睡眠
3、轮询操作

轮询操作:应用程序通常会使用select()系统调用查询是否可对设备进行无阻塞的访问。
该函数通过系统调用最终会引发设备驱动中的poll()函数被执行
该机制还可以实现一个用户进程对多个设备驱动文件的监测
3.1 驱动层方法

//poll是file_operation的回调方法 为file_operation成员之一
static unsigned int poll(struct file *file, struct poll_table_struct *wait)
参数file:是文件结构指针
参数wait:轮询表指针,管理着一系列等待列表

    1
    2
    3
    4

//添加等待队列到wait参数指定的轮询列表中
void poll_wait(struct file *filp, wait_queue_heat_t *wq, poll_table *wait);
poll_wait()将可能引起文件状态变化的进程添加到轮询列表,由内核去监听进程状态的变化,不会阻塞进程
一旦进程有变化(wake_up),内核就会自动去调用poll() 而poll()是返回给select()的
所以当进程被唤醒以后,poll()应该将状态掩码返回给select(),从而select()退出阻塞。
完成一次监测,poll函数被调用一次或两次:
第一次为用户执行select函数时被执行
第二次调用poll为内核监测到进程的wake_up操作时或进程休眠时间到唤醒再或被信号唤醒时

    1
    2
    3
    4
    5
    6
    7
    8

    poll函数返回的状态掩码
    可读状态掩码
    POLLIN:有数据可读
    POLLRDNORM:有普通数据可读
    POLLRDBAND:有优先数据可读
    POLLPRI:有紧迫数据可读

    可写状态掩码
    POLLOUT:写数据不会导致阻塞
    POLLWRNORM:写普通数据不会导致阻塞
    POLLWRBAND:写优先数据不会导致阻塞
    POLLMSG/SIGPOLL:消息可用

    错误状态掩码
    POLLER:指定的文件描述符发生错误
    POLLHUP:指定的文件描述符挂起事件
    POLLNVAL:指定的文件描述符非法

3.2 应用层方法

//文件描述符集合的变量的定义
fd_set fds;
//清空描述符集合
FD_ZERO(fd_set *set);
//加入一个文件描述符到集合中
FD_SET(int fd, fd_set *set);
//从集合中清除一个文件描述符
FD_CLR(int fd, fd_set *set);
//判断文件描述符是否被置位
FD_ISSET(int fd, fd_set *set);
返回非0,表示置位(该文件描述集合中有文件可进行读写操作,或产
生错误)

#include <sys/select.h>
//在应用程序中调用的文件描述符监测函数
int select(
int numfds,         //待监听中最大描述符值加一
fd_set *readfds,    //监听读操作的文件描述符集合
fd_set *writefds,   //监听写操作的文件描述符集合
fd_set *exceptfds,  //监听异常处理的文件描述符集
struct timeval *timeout);// 监听等待超时退出select()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

4、内核线程

1 内核线程类似于用户进程,通常用于并发处理些工作,它是一种在内核空间实现后台任务的方式,并且可以参与时间片轮转调度
2 内核线程可以进行繁忙的异步事件处理,也可以睡眠等待某事件的发生,内核线程可以访问内核函数和数据结构
3 很多设备驱动程序都是用了内核线程来完成辅助任务
4 用户使用ps命令可以查看系统中正在运行的内核线程(也称内核进程)
4.2、使用流程

1 定义一个线程指针
2 编写线程函数
3 创建线程
4 开启线程
5 内核线程的实现流程
6 停止线程(需接受)
4.3、主要方法

#include <linux/kthread.h>
1 内核线程的创建及开启
struct task_struct *kernel_thread; //定义线程指针创建内核线程( 方法一),返回值为创建线程的指针
struct task_struct *kthread_create(int (*threadfn)(void *data),void *data,const char namefmt[], ...);
threadfn:线程函数指针,线程开启后将运行此函数
data:函数参数data,传递给线程函数
namefmt:线程名称,这个函数可以像printk一样传入某种格式的线程名
内核线程创建后不会马上运行,需要通过以下函数启动
//启动线程
int wake_up_process(struct task_struct *p);

创建内核线程函数并运行( 方法二)
kthread_run(threadfn, data, namefmt, ...); //宏

2 内核线程的停止
//停止线程检测函数 (线程函数内使用)
int kthread_should_stop(void)
接收到停止信号,返回true
//停止内核线程函数 (线程函数外使用)
int kthread_stop(struct task_struct *k);
1.该函数发送信号给内核线程,如果线程函数不检测信号也不返回,那么此函数它将一直进行等待
2.在调用kthread_stop函数时,线程函数不能已经运行结束,否则,kthread_stop函数会一直进行等待。

3 线程函数的运行
//线程函数,内核线程开启后会运行该函数
int threadfunc(void *data);
1.该函数由用户自己实现,函数格式如上所示
2.该函数必须能让出CPU,以便其他线程能够得到执行,也必须能重新得到调度
---------------------

原文链接:https://blog.csdn.net/with_dream/article/details/77829698

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值