天天看點

linux 阻塞等待

        對于一個程序"睡眠"意味着什麼? 當一個程序被置為睡眠, 它被辨別為處于一個特殊的狀态并且從排程器的運作隊列中去除. 直到發生某些事情改變了那個狀态, 這個程序将不被在任何 CPU 上排程, 并且, 是以, 将不會運作. 一個睡着的程序已被擱置到系統的一邊, 等待以後發生事件.

        當你運作在原子上下文時不能休眠,如果在獲得自旋鎖情況下,在中斷處理函數中;當程序從休眠中醒來時,必須對等待的條件進行檢查,因為有可能是信号或逾時等機制喚醒休眠程序的。

在linux中,核心等待隊列由一個等待隊列頭來管理 ,即wait_queue_head_t類型結構;

定義和初始化等待隊列:

靜态:DECLARE_WAIT_HEAD(name);

動态:

wait_queue_head_t my_queue;

init_waitqueue_head(&my_queue);

休眠函數:

wait_event(my_queue,condition);       不可中斷休眠,建議不用;

 wait_event_interruptible(my_queue,condition);     可以被信号中斷休眠,傳回非零值表示休眠是被中斷的;

 wait_event_timeout(my_queue, condition, timeout);

  wait_event_interruptible_timeout(my_queue, condition, timeout);   逾時時,傳回0

        其中my_queue時要用的等待隊列頭,condition時休眠前後所要求值的布爾量,如果在休眠前條件為真,則不休眠,否則休眠,被喚醒時檢查該條件,如果條件為真則繼續執行,否則保持繼續休眠;在timeout中如果時間逾時,則會繼續運作接下來程式,而不會去管condition是否為真;

喚醒休眠函數:

 wake_up(&my_queue);    喚醒所有等待隊列上的等待程序;

 wake_up_interruptible(&my_queue);    喚醒可中斷休眠;

wait_event不能通過wake_up_interruptible來喚醒;

測試程式:

1.wait_queue

#include <linux/module.h>

#include <linux/init.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/cdev.h>

#include <linux/types.h>

#include <linux/sched.h>

#include <linux/device.h>

wait_queue_head_t my_queue;

int flag = 0;

struct cdev cdev;

dev_t num;

static int dev_open(struct inode * inode, struct file * file)

{

       printk(KERN_ERR"Open the cdev.\n");

       return 0;

}

static ssize_t dev_read( struct file *file, char *buf, size_t count, loff_t * pp)

{

       ssize_t ret = 0;

       printk("read .......\n");

       flag = 0;

       wait_event_interruptible(my_queue, flag != 0);

       flag = 0;

       if (ret < 0)

              printk("awake from sleep by signal.\n");

       printk("awake ...............\n");

       return 2;

}

static ssize_t dev_write(struct file *file, char *buf, size_t count , loff_t *pp)

{

       printk("wite...... and call wake up.....\n");

       flag = 1;

       wake_up(&my_queue);

       return 1;

}

static int dev_release(struct inode * inode, struct file *file)

{

       return 0;

}

struct file_operations ops = {

       .owner = THIS_MODULE,

       .open = dev_open,

       .read = dev_read,

       .write = dev_write,

       .release = dev_release,

};

static struct class myclass= {

       .name = "test_dev",

       .owner = THIS_MODULE,

};

static int __init dev_init(void)

{

       int val = 0;

        init_waitqueue_head(&my_queue);

       val = alloc_chrdev_region(&num,0,1, "test_dev");

       if (val)

              return 1;

       cdev_init(&cdev, &ops);

       cdev_add(&cdev, num,1);

       class_register(&myclass);

       device_create(&myclass, NULL, num,NULL,"test_dev");

       return 0;

}

static void __exit dev_exit(void)

{

       device_destroy(&myclass,num);

       class_unregister(&myclass);

       cdev_del(&cdev);

       unregister_chrdev_region(num, 1);

}

module_init(dev_init);

module_exit(dev_exit);

MODULE_LICENSE("GPL");

2.wait_write

#include <stdio.h>

#include <fcntl.h>

void main(void)

{

       int fd = 0;

       char buf[2];

       fd = open("/dev/test_dev", O_RDWR);

       if (fd < 0)

              printf("can not open test_dev.\n");

       write(fd, buf, sizeof(buf));

       close(fd);

}

3.wait_read

#include <stdio.h>

#include <fcntl.h>

void main(void)

{

       int fd;

       char buf[10];

       fd = open("/dev/test_dev",O_RDWR);

       if (fd < 0) {

              printf("can not open test_dev.\n");

       }

       read(fd,buf, sizeof(buf));

       close(fd);

}

休眠函數低層實作過程:

主要資料結構:

struct wait_queue_head_t 

{

spinlock_t lock;

struct list_head task_list;

}

struct wait_queue_t 

{

unsigned int flags;

void *private;

wait_queue_func_t func;

struct list_head task_list;

}

其中struct wait_queue_head_t為休眠隊列清單頭,面struct wait_queue_t為清單成員;

低層實作步驟:

1.通過DECLARE_WAIT_HEAD(queue)定義一個wait_queue_head_t 變量,通過DECLARE_WAITQUEUE(wait, current)來定義并初始化一個wait_queue_t變量;

#define DECLARE_WAITQUEUE(name, tsk)     (wait_queue_t  name= __WAITQUEUE_INITIALIZER(name, tsk))   

#define __WAITQUEUE_INITIALIZER(name , tsk)  {

.private = tak,
.func = default_wake_function,
task_list = {NULL, NULL},
}

2. 通過add_wait_queue(&queue, &wait)将wait加入到queue等待隊列中;

3.通過set_current_state函數設定當時程序狀态為休眠态,休眠态主要可以分為TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE.

4.運作任務排程函數schedule(),在任務高度前會先判斷休眠等待的條件是否滿足,如果已經滿足則不休眠,這樣可以防止丢掉這一次被喚醒的機會,進而進行更長時間休眠;對于可中斷休眠,在進行任務高度前還會判斷目前程序中是否有等待信号;

if (condition )
break;
if (!signal_pending(current)
schedule();

5.運作完休眠後将通過remove_wait_queue(&queue,&wait)将wait移出等待隊列,并将目前程序狀态設定回TASK_RUNNING;

schedule 函數是用來實作程序排程,它從運作隊列連結清單中找到一個程序,并将CPU配置設定給這個程序。schedule可分直接調用和延遲調用 方式 ;

直接調用方式:

如果目前程序因不能獲得必要的資源而要立刻被阻塞,就可以使用直接調用方式,直接調用方式 的步驟:

1.把目前current程序插入到合适的等待隊列;

2.把current程序狀态改成TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE; 

3.調用schedule();

4.檢查資源是否可用,如果不可用就轉到第二步;

5.一但資源可用,就從等待隊列中删除current程序;

這些過程一般會放在do while循環裡實作,核心會反複的檢測資源是否可用,如果不可以,就調用 schedule進行任務排程,當排程程式再次允許把CPU配置設定給這個程序時,要重新檢查資源是否可用,這個過程和wait_event過程 相似;