linux 等待隊列
inux內核中等待隊列
在Linux內核中等待隊列有很多用途,可用於中斷處理、進程同步及定時。我們在這裏只說,進程經常必須等待某些事件的發生。等待隊列實現了在事件上的條件等待: 希望等待特定事件的進程把自己放進合適的等待隊列,並放棄控制全。因此,等待隊列表示一組睡眠的進程,當某一條件為真時,由內核喚醒它們。
等待隊列由循環鏈表實現,其元素包括指向進程描述符的指針。每個等待隊列都有一個等待隊列頭(wait queue head),等待隊列頭是一個類型為wait_queue_head_t的數據結構
(1)定義等待隊列頭(相關內容可以在linux/include/wait.h中找到)
等待隊列頭結構體的定義:
struct __wait_queue_head {
spinlock_t lock; //自旋鎖變量,用於在對等待隊列頭
struct list_head task_list; // 指向等待隊列的list_head
};
typedef struct __wait_queue_head wait_queue_head_t;
使用等待隊列時首先需要定義一個wait_queue_head,這可以通過DECLARE_WAIT_QUEUE_HEAD宏來完成,這是靜態定義的方法。該宏會定義一個wait_queue_head,並且初始化結構中的鎖以及等待隊列。當然,動態初始化的方法也很簡單,初始化一下鎖及隊列就可以了。
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
.task_list = { &(name).task_list, &(name).task_list } }
將lock賦為unlocked, 將等待隊列頭指向的等待隊列鏈表指向name,從而將等待隊列頭和等待隊列連起來
一般在寫程序的時候將DECLARE_WAIT_QUEUE_HEAD分成兩步來完成:
聲明:
wait_queue_head_t wait_que;
初始化:
init_waitqueue_head( &wait_que);
Linux中等待隊列的實現思想如下圖所示,當一個任務需要在某個wait_queue_head上睡眠時,將自己的進程控制塊信息封裝到wait_queue中,然後掛載到wait_queue的鏈表中,執行調度睡眠。當某些事件發生後,另一個任務(進程)會喚醒wait_queue_head上的某個或者所有任務,喚醒工作也就是將等待隊列中的任務設置為可調度的狀態,並且從隊列中刪除。
(2)等待隊列中存放的是在執行設備操作時不能獲得資源而掛起的進程
定義等待對列:
struct __wait_queue {
unsigned int flags; //prepare_to_wait()裏有對flags的操作,查看以得出其含義
#define WQ_FLAG_EXCLUSIVE 0x01 //一個常數,在prepare_to_wait()用於修改flags的值
void * private //通常指向當前任務控制塊
wait_queue_func_t func; //喚醒阻塞任務的函數 ,決定了喚醒的方式
struct list_head task_list; // 阻塞任務鏈表
};
typedef struct __wait_queue wait_queue_t;
//聲明一個等待隊列並初始化為name
#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 } }
//下列兩個函數用於對特定的成員進行賦值(當傳入不同類型的參數時);
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
q->flags = 0;
q->private = p; //私有數據指針
q->func = default_wake_function; //使用默認的喚醒函數
}
static inline void init_waitqueue_func_entry(wait_queue_t *q, wait_queue_func_t func)
{
q->flags = 0;
q->private = NULL;
q->func = func; // 自定義的喚醒函數
}
(3)對等待隊列進行操作
static inline int waitqueue_active(wait_queue_head_t *q)
{
return !list_empty(&q->task_list);
}
判斷等待對列頭是否為空,當一個進程訪問設備而得不到資源時就會被放入等待隊列頭指向的等待隊列中。
static inline void __add_wait_queue(wait_queue_head_t *head,\ wait_queue_t *new) /
{
list_add(&new->task_list, &head->task_list);
}
//增加一個等待隊列new到等待隊列頭head指向的等待隊列鏈表中;
static inline void __add_wait_queue_tail(wait_queue_head_t *head, wait_queue_t *new)
{
list_add_tail(&new->task_list, &head->task_list);
}
增加一個等待隊列到表尾
static inline void __remove_wait_queu (wait_queue_head_t *head, wait_queue_t *old)
{
list_del(&old->task_list);
}
//wq: 在等待事件的等待隊列,condition: 等待的條件
#define __wait_event(wq, condition) \
do { \
DEFINE_WAIT(__wait); \ //定義並初始化一個wait_queue_t結構
for (;;) { \
prepare_to_wait(&wq, &__wait,TASK_UNINTERRUPTIBLE); \
if (condition) \ //看wait_queue:wq要等的condition是否滿足
break; \
schedule(); \ //condition不成立,放棄cpu重新調度一個task
} \
finish_wait(&wq, &__wait); \
} while (0)
上面程序的執行過程:
1.用當前的進程描述塊(PCB)初始化一個wait_queue描述的等待任務。
2.在等待隊列鎖資源的保護下,將等待任務加入等待隊列。
3.判斷等待條件是否滿足,如果滿足,那麽將等待任務從隊列中移出,退出函數。
4.如果條件不滿足,那麽任務調度,將CPU資源交與其它任務。
5.當睡眠任務被喚醒之後,需要重復(2)、(3)步驟,如果確認條件滿足,退出等待事件函數。
我在一個程序中因為使用使用wait_event_interruptible()遇到了很大的麻煩,原因就是不知道condition在函數中具體起個什麽作用,通過分析源碼終於搞清楚。(後面會有專門的文章來介紹那個問題。)
等待隊列編程接口:
wait_event(wq, condition)
這是一個宏,讓當前任務處於等待事件狀態。輸入參數如下:
@wq:等待隊列
@conditions:等待條件
wait_event_timeout(wq, condition, timeout)
功能與wait_event類似,多了一個超時機制。參數中多了一項超時時間。
wait_event_interruptible(wq, condition)
這是一個宏,與前兩個宏相比,該宏定義的等待能夠被消息喚醒。如果被消息喚醒,那麽返回- ERESTARTSYS。輸入參數如下:
@wq:等待隊列
@condition:等待條件
@rt:返回值
wait_event_interruptible_timeout(wq, condition, timeout)
與上一個相比,多了超時機制
wake_up(x)
喚醒等待隊列中的一個任務
wake_up_interruptible(x)
用於喚醒wake_event_interruptible()睡眠的進程
wake_up_all(x)
喚醒等待隊列中的所有任務
Linux將進程狀態描述為如下五種:
TASK_RUNNING:可運行狀態。處於該狀態的進程可以被調度執行而成為當前進程。
TASK_INTERRUPTIBLE:可中斷的睡眠狀態。處於該狀態的進程在所需資源有效時被喚醒,也可以通過信號或定時中斷喚醒(因為有signal_pending()函數)。
TASK_UNINTERRUPTIBLE:不可中斷的睡眠狀態。處於該狀態的進程僅當所需資源有效時被喚醒。
TASK_ZOMBIE:僵屍狀態。表示進程結束且已釋放資源,但其task_struct仍未釋放。
TASK_STOPPED:暫停狀態。處於該狀態的進程通過其他進程的信號才能被喚醒。
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/miscdevice.h> 4 #include <linux/fs.h> 5 #include <linux/interrupt.h> 6 #include <linux/gpio.h> 7 #include <mach/gpio.h> 8 #include <asm-generic/ioctl.h> 9 #include <asm/uaccess.h> 10 #include <linux/wait.h> 11 #include <linux/sched.h> 12 13 #define MAGIC ‘B‘ 14 #define GET_BTN_VAL _IOR(MAGIC, 1, unsigned long) 15 16 typedef struct 17 { 18 unsigned int gpio; 19 unsigned int btn; 20 unsigned int irq; 21 char* name; 22 }irq_typedef; 23 24 static irq_typedef irqsource[] = 25 { 26 {EXYNOS4_GPX3(2),1,0,"btn1"}, 27 {EXYNOS4_GPX3(3),2,0,"btn2"}, 28 {EXYNOS4_GPX3(4),3,0,"btn3"}, 29 {EXYNOS4_GPX3(5),4,0,"btn4"} 30 }; 31 32 DECLARE_WAIT_QUEUE_HEAD(btn_wait_que); 33 static int condition; 34 35 static unsigned long btn_num; 36 37 static irqreturn_t gpio_irq_handler(int irq, void *dev) 38 { 39 irq_typedef* curdev = (irq_typedef *)dev; 40 41 printk("%s\n",curdev->name); 42 printk("btn(drv):%d\n",curdev->btn); 43 44 btn_num = curdev->btn; 45 46 condition=1; 47 wake_up(&btn_wait_que); 48 49 return 0; 50 } 51 52 static long btn_ioctl(struct file *fil, unsigned int cmd, unsigned long arg) 53 { 54 unsigned long ret; 55 void __user *argp = (void __user *)arg; 56 57 wait_event_interruptible(btn_wait_que, condition); 58 condition = 0; 59 60 switch(cmd) 61 { 62 case GET_BTN_VAL: 63 ret = copy_to_user((void __user *) argp, (void *)&btn_num,4); 64 // put_user(btn_num, (unsigned int __user *) argp); 65 break; 66 default:return -EINVAL; 67 } 68 69 return ret; 70 } 71 72 73 static struct file_operations btn_irq_fops = 74 { 75 .owner = THIS_MODULE, 76 .unlocked_ioctl = btn_ioctl, 77 }; 78 79 static struct miscdevice btn_irq_misc = 80 { 81 .minor = 255, 82 .name = "btn", 83 .fops = &btn_irq_fops, 84 }; 85 86 static __init int btn_irq_init(void) 87 { 88 int i,ret; 89 90 misc_register(&btn_irq_misc); 91 92 for(i=0;i<4;i++) 93 { 94 irqsource[i].irq = gpio_to_irq(irqsource[i].gpio); 95 ret = request_irq(irqsource[i].irq,gpio_irq_handler,IRQF_TRIGGER_FALLING,irqsource[i].name, &irqsource[i]); 96 } 97 98 return ret; 99 } 100 101 static __exit void btn_irq_exit(void) 102 { 103 int i; 104 105 for(i=0;i<4;i++) 106 free_irq(irqsource[i].irq, &irqsource[i]); 107 108 misc_deregister(&btn_irq_misc); 109 } 110 111 module_init(btn_irq_init); 112 module_exit(btn_irq_exit); 113 MODULE_LICENSE("GPL");
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <sys/ioctl.h> #define MAGIC ‘B‘ #define GET_BTN_VAL _IOR(MAGIC, 1, unsigned long) int main(int argc,char *argv[]) { int btn_fd,btn_val=0; if(argc!=2) { printf("Usage:<%s> </dev/?node>\n",argv[0]); return -1; } btn_fd = open(argv[1],O_RDONLY); while(1) { // ioctl(btn_fd,GET_BTN_VAL,btn_val); ioctl(btn_fd,GET_BTN_VAL,&btn_val); // sleep(1); printf("btn_val(irq):%d\n",btn_val); } }
1 obj-m += btnirq.o 2 SOURCE =btntest.o 3 4 CROSS_COMPLIE = arm-linux- 5 CC = $(CROSS_COMPLIE)gcc 6 7 KERNDIR = /file/samsung/linux-3.5 8 #CURDIR = $(shell pwd) 9 CURDIR = `pwd` 10 11 .PHONY: module clean 12 13 all: module $(SOURCE:.o=) 14 15 module: 16 $(MAKE) -C $(KERNDIR) M=$(CURDIR) modules 17 18 $(SOURCE:.o=):$(SOURCE) 19 $(CC) -o $@ $^ 20 21 %.o:%.c 22 $(CC) -c $< 23 24 clean: 25 $(MAKE) -C $(KERNDIR) M=$(CURDIR) clean 26 rm $(SOURCE:.o=)
linux 等待隊列