linux下面的”队列“

 最近在学习linux内核相关的代码的时候,经常遇到跟"队列“相关的名词。感觉自己不能很清楚地说明白。
故现在将跟”队列“有关的总结如下:
1: 等待队列
2:工作队列
3: 请求队列

一:等待队列
在内核里面,等待队列是有很多用处的,尤其是在中断处理、进程同步、定时等场合。

可以使用等待队列在实现阻塞进程的唤醒。它以队列为基础数据结构,与进程调度机制紧密结合,
能够用于实现内核中的异步事件通知机制,同步对系统资源的访问等。
涉及到的数据结构包括:
struct __wait_queue {
 unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
 void *private;
 wait_queue_func_t func; //为一个函数指针typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
 struct list_head task_list; //用来将wait_queue_func_t链起来
};

struct __wait_queue_head {
 spinlock_t lock;
 struct list_head task_list; //双向循环链表,存放等待的进程。
};
typedef struct __wait_queue_head wait_queue_head_t; //定义了等待队列头
其中
 等待队列(wait_queue_t)和等待对列头(wait_queue_head_t)的区别是等待队列是等待队列头的成员。
也就是说等待队列头的task_list域链接的成员就是等待队列类型的(wait_queue_t)。
1、定义并初始化等待队列头:有俩种方法
  (1)
  wait_queue_head_t my_queue;
  init_waitqueue_head(&my_queue);
  直接定义并初始化。init_waitqueue_head()函数会将自旋锁初始化为未锁,等待队列初始化为空的双向循环链表。
  (2)
  DECLARE_WAIT_QUEUE_HEAD(my_queue);
  定义并初始化,相当于(1)。
2、定义等待队列项:
   DECLARE_WAITQUEUE(name,tsk);
        其定义如下:
       #define DECLARE_WAITQUEUE(name, tsk)      wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
       #define __WAITQUEUE_INITIALIZER(name, tsk) {    \
          .private = tsk,      \
          .func  = default_wake_function,   \
          .task_list = { NULL, NULL } }
  从上面的定义可以知道,DECLARE_WAITQUEUE(name,tsk)主要定义了变量name,并对private数据项进行了赋值。
3、(从等待队列头中)添加/移出等待队列项:

  (1)add_wait_queue()函数:
  void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
  函数将wait_queue_t添加到wait_queue_head_t中。
 
  (2)remove_wait_queue()函数:
  void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
  在等待的资源或事件满足时,进程被唤醒,使用该函数被从等待头中删除。

4、等待事件:
 
  (1)wait_event()宏:
  在等待队列中睡眠直到condition为真。在等待的期间,进程会被置为TASK_UNINTERRUPTIBLE进入睡眠,直到condition变量变为真。每次进程被唤醒的时候都会检查condition的值.
 
  (2)wait_event_interruptible()函数:
 
  和wait_event()的区别是调用该宏在等待的过程中当前进程会被设置为TASK_INTERRUPTIBLE状态.在每次被唤醒的时候,首先检查condition是否为真,如果为真则返回,否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码.如果是condition为真,则返回0.
 
  (3)wait_event_timeout()宏:
 
  也与wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0.
 
  (4)wait_event_interruptible_timeout()宏:
 
  与wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回ERESTARTSYS错误码.
 
  (5) wait_event_interruptible_exclusive()宏
 
  同样和wait_event_interruptible()一样,不过该睡眠的进程是一个互斥进程.

5、唤醒队列:
 
  (1)wake_up()函数:
  唤醒等待队列.可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE状态的进程,和wait_event/wait_event_timeout成对使用.
  
    (2)wake_up_interruptible()函数:
  #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
 
  和wake_up()唯一的区别是它只能唤醒TASK_INTERRUPTIBLE状态的进程,
    与wait_event_interruptible/wait_event_interruptible_timeout/ wait_event_interruptible_exclusive成对使用.
  (3)
 
  #define wake_up_all(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 0, NULL)
 
  #define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
 
  #define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
 
  这些也基本都和wake_up/wake_up_interruptible一样.
 
  6、在等待队列上睡眠:
 
  (1)sleep_on()函数:
 
  (2)sleep_on_timeout()函数:
 
  long __sched sleep_on_timeout(wait_queue_head_t *q, long timeout)
 
  与sleep_on()函数的区别在于调用该函数时,如果在指定的时间内(timeout)没有获得等待的资源就会返回。实际上是调用schedule_timeout()函数实现的。值得注意的是如果所给的睡眠时间(timeout)小于0,则不会睡眠。该函数返回的是真正的睡眠时间。
 
  (3)interruptible_sleep_on()函数:
 
  void __sched interruptible_sleep_on(wait_queue_head_t *q)
 
  该函数和sleep_on()函数唯一的区别是将当前进程的状态置为TASK_INTERRUPTINLE,这意味在睡眠如果该进程收到信号则会被唤醒。
 
  (4)interruptible_sleep_on_timeout()函数:
 
  类似于sleep_on_timeout()函数。进程在睡眠中可能在等待的时间没有到达就被信号打断而被唤醒,也可能是等待的时间到达而被唤醒。
 
以上四个函数都是让进程在等待队列上睡眠,不过是小有诧异而已。
在实际用的过程中,根据需要选择合适的函数使用就是了。
例如在对软驱数据的读写中,如果设备没有就绪则调用sleep_on()函数睡眠直到数据可读(可写),
在打开串口的时候,如果串口端口处于关闭状态则调用interruptible_sleep_on()函数尝试等待其打开。
在声卡驱动中,读取声音数据时,如果没有数据可读,就会等待足够常的时间直到可读取。

二:工作队列

在linux中断处理中,有上半部和下半部之分,在下半部中主要来处理比较耗时的操作,其主要由工作队列workqueue来完成。
Linux 2.6内核使用了不少工作队列来处理任务,他在使用上和 tasklet最大的不同是工作队列的函数可以使用休眠,
而tasklet的函数是不允许使用休眠的。
工作队列的使用又分两种情况,
一种是利用系统共享的工作队列来添加自己的工作,这种情况处理函数不能消耗太多时间,这样会影响共享队列中其他任务的处理;
另外一种是创建自己的工作队列并添加工作。
我们把推后执行的任务叫做工作(work),其数据结构为work_struct。
这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,
而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events,自己也可以创建自己的工作者线程。
首先来看看这俩个重要的数据结构:work_struct 和 workqueue_struct。

struct workqueue_struct {
 struct cpu_workqueue_struct *cpu_wq;
 struct list_head list;
 const char *name;
 int singlethread;
 int freezeable;  /* Freeze threads during suspend */
 int rt;
#ifdef CONFIG_LOCKDEP
 struct lockdep_map lockdep_map;
#endif
};

struct work_struct {
 atomic_long_t data;
#define WORK_STRUCT_PENDING 0  /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
 struct list_head entry;
 work_func_t func; //typedef void (*work_func_t)(struct work_struct *work);为函数指针
#ifdef CONFIG_LOCKDEP
 struct lockdep_map lockdep_map;
#endif
};
系统默认workqueue_struct定义如下
static struct workqueue_struct *keventd_wq __read_mostly; 
keventd_wq = create_workqueue("events"); //名字为events

工作队列的创建方法:将work_struct添加到系统默认的工作队列中,添加work_struct到自定义的队列中
(1):将work_struct添加到系统默认的工作队列中

a:声明或编写一个工作处理函数
   void my_func(void *data); //相当与一个任务处理函数task(),会周期性的执行。

b:在创建工作work_struct时候,有俩种方法,即编译时和运行时。

 编译时
 创建名为my_work的结构体变量并把函数入口地址和参数地址赋给它;
 创建一个工作结构体变量,并将处理函数和参数的入口地址赋给这个工作结构体变量
 DECLARE_WORK(my_work,my_func,&data);
 运行时
 struct work_struct my_work; //创建一个名为my_work的结构体变量,创建后才能使用INIT_WORK()
 INIT_WORK(&my_work,my_func,&data); //初始化已经创建的my_work,其实就是往这个结构体变量中添加处理函数的入口地址和data的地址,通常在驱动的open函数中完成


c:将工作结构体变量添加入系统的共享工作队列
 schedule_work(&my_work); //添加my_work到keventd_wq中,一旦其所在的处理器上的工作者线程被唤醒,它就会被执行。
 或有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。
 在这种情况下,可以调度它在指定的时间执行:
 schedule_delayed_work(&my_work,tick); //延时tick个滴答后再提交工作这时,&my_work指向的work_struct直到delay指定的时钟节拍用完以后才会执行。


(2):创建自己的工作队列来添加工作
a:声明工作处理函数和一个指向工作队列的指针
   void my_func(void *data); //相当与一个任务处理函数task(),会周期性的执行。

b:创建自己的工作队列和工作结构体变量(通常在open函数中完成)
 struct workqueue_struct *p_queue;
 p_queue=create_workqueue("my_queue"); //创建一个名为my_queue的工作队列并把工作队列的入口地址赋给声明的指针
 struct work_struct my_work;
 INIT_WORK(&my_work,my_func,&data); //创建一个工作结构体变量并初始化,和第一种情况的方法一样
c:将工作添加入自己创建的工作队列等待执行
   queue_work(p_queue,&my_work);
   //作用与schedule_work()类似,不同的是将工作添加入p_queue指针指向的工作队列而不是系统共享的工作队列
d:删除自己的工作队列
   destroy_workqueue(p_queue); //一般是在close函数中删除

其工作队列的使用方法,可以参考linux中scsi_tgt_if.c函数中的代码。

static void scsi_tgt_cmd_done(struct scsi_cmnd *cmd)
{
 struct scsi_tgt_cmd *tcmd = cmd->request->end_io_data;

 dprintk("cmd %p %u\n", cmd, rq_data_dir(cmd->request));

 scsi_tgt_uspace_send_status(cmd, tcmd->itn_id, tcmd->tag);

 scsi_release_buffers(cmd);

 queue_work(scsi_tgtd, &tcmd->work); // scsi_tgtd = create_workqueue("scsi_tgtd");
}

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

家有工程师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值