Linux APP查询驱动的方式归类总
前言
一、具体方式是什么?
1、休眠与唤醒
2、阻塞与非阻塞
3、POLL机制
4、异步通知
tips:等待队列用于进程等待条件,工作队列用于异步任务处理。
二、使用步骤
休眠与唤醒
tips:
在中断处理函数中,不能休眠,也就不能调用会导致休眠的函数。
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。
上半部和下半部使用参考点:
①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
④、除了上述三点以外的其他任务,优先考虑放到下半部。
下半部机制类别`
1、软终端 softirq_action
struct softirq_action
{
void (*action)(struct softirq_action *);
};
softirq_vec 数组
static struct softirq_action softirq_vec[NR_SOFTIRQS]; NR_SOFTIRQS = 10;
enum
{
HI_SOFTIRQ=0, /* 高优先级软中断 */
TIMER_SOFTIRQ, /* 定时器软中断 */
NET_TX_SOFTIRQ, /* 网络数据发送软中断 */
NET_RX_SOFTIRQ, /* 网络数据接收软中断 */
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ, /* tasklet 软中断 */
SCHED_SOFTIRQ, /* 调度软中断 */
HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */
RCU_SOFTIRQ, /* RCU 软中断 */
NR_SOFTIRQS
};
2、TASKLET
tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet 之间,建议大家使用 tasklet。 Linux 内核使用 tasklet_struct 结构体来表示 tasklet:
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 执行的函数 */
unsigned long data; /* 函数 func 的参数 */
}`
使用 :
DECLARE_TASKLET(name, func, data); //name 为要定义的 tasklet 名字,这个名字就是一个 tasklet_struct 类型的时候变量, func就是 tasklet 的处理函数, data 是传递给 func 函数的参数
tasklet_init(&testtasklet, testtasklet_func, data); //初始化
3、工作队列
工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软终端和tasklet。
#define DECLARE_WORK(n, f) //创建 和 初始化n 表示定义的工作(work_struct), f 表示工作对应的处理函数。
bool schedule_work(struct work_struct *work) //调度
POLL机制
使用休眠-唤醒的方式等待某个事件发生时,有一个缺点: 等待的时间可能
很久。我们可以加上一个超时时间,这时就可以使用 poll 机制。
调用 poll 之后,进入内核态;致驱动程序的 drv_poll 被调用,drv_poll 还会判断一下有没有数据?返回这个状态,当前没有数据,则休眠一会要把自己这个线程挂入等待队列 wq 中,从休眠中被唤醒,继续执行 for 循环,唤醒可能是超时或者是poll队列唤醒。
eg:
static unsigned int xxx_drv_poll(struct file *fp, poll_table * wait)
{
poll_wait(fp, &gpio_key_wait, wait);
return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}
阻塞与非阻塞
阻塞
谓阻塞,就是等待某件事情发生。比如调用 read 读取按键时,如果没有
按键数据则 read 函数不会返回,它会让线程休眠等待。
使用 poll 时,如果传入的超时时间不为 0,这种访问方法也是阻塞的。
使用 poll 时,可以设置超时时间为 0,这样即使没有数据它也会立刻返回,
非阻塞
非阻塞就是等待队列和poll select 和epoll的结合使用
等待队列
要在驱动中使用等待队列,必须创建并初始化一个等待队列头,等待队列头使用结构体wait_queue_head_t表示
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
使用 init_waitqueue_head 函数初始化等待队列头
void init_waitqueue_head(wait_queue_head_t *q)
也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义的初始化
结构体 wait_queue_t 表示等待队列项
等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项
struct __wait_queue {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
使用宏 DECLARE_WAITQUEUE 定义并初始化一个等待队列项,宏的内容如下:
DECLARE_WAITQUEUE(name, tsk)
name 就是等待队列项的名字, tsk 表示这个等待队列项属于哪个任务(进程),一般设置为current , Linux 内 核 中 current 相 当 于 一 个 全 局 变 量 , 表 示 当 前 进 程。
当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可
void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
等待唤醒
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
参数 q 就是要唤醒的等待队列头,这两个函数会将这个等待队列头中的所有进程都唤醒。wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的进程,而 wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程
异步通知
对于APP:
注册signal函数和sigio挂钩, signal(SIGIO, sig_func);
打开驱动 fd = open(argv[1], O_RDWR);
将进程ID告诉驱动 fcntl(fd, F_SETOWN, getpid());
设置flags有 fasync标志位 flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
对于驱动程序
调用 fasync_helper就行
static struct fasync_struct *button_async;
static int drv_fasync (int fd, struct file *filp, int on)
{
return fasync_helper (fd, filp, on, &button_async);
}
fasync_helper 函 数 会 分 配 、 构 造 一 个 fasync_struct 结 构 体
驱动发送信号
kill_fasync (&button_async, SIGIO, POLL_IN);
button_async->fa_file 非空时,可以从中得到 PID,表示发给哪一个 APP;
第 2 个参数表示发什么信号: SIGIO;
第 3 个参数表示为什么发信号: POLL_IN,有数据可以读了