(一)阻塞和非阻塞
阻塞:執行設備操作時,若不能獲得資源,則掛起進程進入休眠直到滿足可操作的條件后再操作。
非阻塞:進程在不能進行設備操作時,并不掛起,它要么放棄,要么不停地查詢,直至可以進行操作為止。
(二)為什么學習等待隊列
在講解等待隊列的作用之前先來看一下內核的休眠機制:
正在運行的進程讓出CPU,休眠的進程會被內核擱置在一邊,只有當內核再次把休眠的進程喚醒,
進程才會重新在CPU運行,這是內核中的進程調度
一個CPU在同一時間只能有一個進程在運行,內核將所有的進程按一定的算法將CPU輪流的給每個
進程使用,而休眠就是進程沒有被運行時的一種形式。在休眠下,進程不占用CPU,等待被喚醒。
當一個進程休眠時,其他進程為了能夠喚醒休眠的進程,它必須知道休眠的進程在哪里,出于這樣的原因,需要有一個稱為等待隊列的結構體。等待隊列是一個存放著等待某個特定事件進程鏈表。所以等待隊列相當于休眠進程的鏈表,需要手動進行操作
它的作用主要是:實現中斷處理、進程同步及延時進程
(三)等待隊列的相關接口
等待隊列頭數據結構:
struct __wait_queue_head {spinlock_t lock; //自旋鎖機制struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
wait_queue_head_t:即為等待隊列頭的數據類型
初始化等待隊列頭:
靜態定義等待隊列頭并初始化:
#define DECLARE_WAIT_QUEUE_HEAD(name) \wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)動態定以并初始化:#define init_waitqueue_head(q) \do { \static struct lock_class_key __key; \\__init_waitqueue_head((q), #q, &__key); \} while (0)
休眠等待隊列wait_event或wait_event_interruptible:
/*** wait_event - sleep until a condition gets true* @wq: the waitqueue to wait on* @condition: a C expression for the event to wait for** The process is put to sleep (TASK_UNINTERRUPTIBLE) until the* @condition evaluates to true. The @condition is checked each time* the waitqueue @wq is woken up.** wake_up() has to be called after changing any variable that could* change the result of the wait condition.*/
#define wait_event(wq, condition) \
do { \if (condition) //判斷條件是否滿足,如果滿足則退出等待 \break; \__wait_event(wq, condition); //如果不滿足,則進入__wait_event宏 \
} while (0)#define __wait_event(wq, condition) \
do { \DEFINE_WAIT(__wait); \/*定義并且初始化等待隊列項,后面我們會將這個等待隊列項加入我們的等待隊列當中,同時在初始化的過程中,會定義func函數的調用函數autoremove_wake_function函數,該函數會調用default_wake_function函數。*/ \for (;;) { \prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \/*調用prepare_to_wait函數,將等待項加入等待隊列當中,并將進程狀態置為不可中斷TASK_UNINTERRUPTIBLE;*/ if (condition) //繼續判斷條件是否滿足 \break; \schedule(); //如果不滿足,則交出CPU的控制權,使當前進程進入休眠狀態 \} \finish_wait(&wq, &__wait);/**如果condition滿足,即沒有進入休眠狀態,跳出了上面的for循環,便會將該等待隊列進程設置為可運行狀態,并從其所在的等待隊列頭中刪除 */ \
} while (0)#define wait_event_interruptible(wq, condition) \
({ /**如果condition為false,那么__wait_event_interruptible將會被執行*/ \int __ret = 0;
\if (!(condition)) \__wait_event_interruptible(wq, condition, __ret); \__ret; \
})
喚醒等待隊列節點wake_up_interruptible或wake_up:
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr, void *key);
/*** __wake_up - wake up threads blocked on a waitqueue.* @q: the waitqueue* @mode: which threads* @nr_exclusive: how many wake-one or wake-many threads to wake up* @key: is directly passed to the wakeup function** It may be assumed that this function implies a write memory barrier before* changing the task state if and only if any tasks are woken up.*//**定義wake_up函數宏,同時其需要一個wait_queue_head_t的結構體指針,在該宏中調用__wake_up方法。*/
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
加interruptible接口標識喚醒可中斷
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, 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)
#define wake_up_interruptible_sync(x) __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)
(四)如何實現等待隊列
1、創建等待隊列頭
//1.靜態定義并初始化
#define DECLARE_WAIT_QUEUE_HEAD(name) \wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)//2.動態定以并初始化:wait_queue_head_t name;init_waitqueue_head(q)
默認情況下會把當前進程作為等待任務放到等待隊列中
2、在需要休眠的地方調用休眠操作
wait_event 、 wait_event_timeout wait_event_interruptible(首選)
3、在滿條件的地方喚醒等待隊列
wake_up 、wake_up_interruptible
(五)實例代碼
chrdev.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/uaccess.h>#define CDEVCOUNT 5
#define CDEVNAME "cdevdevice"
#define INODENAME "mycdev"
int count=0;
dev_t dev=0;
int keyflag;//等待隊列第二個參數
char keyvalue[4]={-1,-1,-1,-1};
struct cdev * cdev =NULL;
struct class * cdevclass =NULL;
wait_queue_head_t waithead;struct Key
{unsigned int gpios;char * name;int num;unsigned int irq;};struct Key key[]={{EXYNOS4_GPX3(2),"K1",0},{EXYNOS4_GPX3(3),"K2",1},{EXYNOS4_GPX3(4),"K3",2},{EXYNOS4_GPX3(5),"K4",3},};
irqreturn_t key_handler(int irq, void * dev)
{struct Key * tmp =(struct Key *)dev;int value[4];value[tmp->num]= gpio_get_value(tmp->gpios);gpio_set_value(EXYNOS4X12_GPM4(tmp->num),value[tmp->num]);keyflag = 1;keyvalue[tmp->num] = value[tmp->num];wake_up_interruptible(&waithead);printk("key%d value is %d\n",tmp->num,value[tmp->num]);printk("the current cpu is %d\n",smp_processor_id());return IRQ_HANDLED;}int cdev_open (struct inode *node, struct file *file)
{printk("cdev_open is install\n");return 0;
}
ssize_t cdev_read (struct file *fp, char __user *buf, size_t size, loff_t *offset)
{int ret =0;if((fp->f_flags & O_NONBLOCK)== O_NONBLOCK)//非阻塞{if(!keyflag)return -EAGAIN;}else//阻塞{wait_event_interruptible(waithead,keyflag);}keyflag =0;ret = copy_to_user(buf,keyvalue,4);if(ret <0)return -EFAULT;printk("cdev_read is install\n");return 0;
}
ssize_t cdev_write (struct file *fp, const char __user * buf, size_t size, loff_t *offset)
{printk("cdev_write is install\n");return 0;
}
int cdev_release (struct inode *node, struct file *fp)
{printk("cdev_release is install\n");return 0;
}
struct file_operations fop={.open=cdev_open,.read=cdev_read,.write=cdev_write,.release=cdev_release,
};void mycdev_add(void)
{//1.申請設備號--動態int ret =alloc_chrdev_region(&dev,0, CDEVCOUNT, CDEVNAME);if(ret)return ;//初始化cdev結構體cdev = cdev_alloc();if(!cdev){goto out;}cdev_init(cdev,&fop);//添加字符設備到系統中ret =cdev_add(cdev,dev, CDEVCOUNT);if(ret){goto out1;}//創建設備類cdevclass = class_create(THIS_MODULE, INODENAME);if(IS_ERR(cdevclass)){goto out2;}for (count=0;count<CDEVCOUNT;count++)device_create(cdevclass, NULL, dev+count, NULL, "mydevice%d",count);out:unregister_chrdev_region(dev,CDEVCOUNT); return ;
out1:unregister_chrdev_region(dev,CDEVCOUNT);kfree(cdev);return ;
out2:cdev_del(cdev);unregister_chrdev_region(dev,CDEVCOUNT);kfree(cdev);return ;}static int __init dev_module_init(void)
{int ret=0,i=0;unsigned long flags= IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING|IRQF_SHARED;for(i=0;i<4;i++){key[i].irq = gpio_to_irq(key[i].gpios);ret =request_irq(key[i].irq, key_handler,flags,key[i].name,(void *) &key[i]);}init_waitqueue_head(&waithead);//創建等待隊列頭mycdev_add();printk("this is dev_module_init \n");return 0;
}static void __exit dev_module_cleanup(void)
{int i=0;for(i=0;i<4;i++){key[i].irq = gpio_to_irq(key[i].gpios);free_irq(key[i].irq,(void *)&key[i]);}for(count=0;count<CDEVCOUNT;count++){device_destroy(cdevclass, dev+count);}class_destroy(cdevclass);cdev_del(cdev);unregister_chrdev_region(dev, CDEVCOUNT);kfree(cdev);printk("this is dev_module_cleanup\n");
}module_init(dev_module_init);
module_exit(dev_module_cleanup);
MODULE_LICENSE("GPL");
chr_app.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>char status[]={-1,-1,-1,-1};
char key[]={-1,-1,-1,-1};
int main(int argc, char *argv[])
{int i=0,fd= open(argv[1],O_RDWR);if(fd== -1){perror("open");return -1;}while(1){if(read(fd,status,4)<0){printf("按鍵狀態未改變\n");sleep(1);continue;}for(i=0;i<4;i++){if(status[i] != key[i] ){printf("key%d is %s\n",i,status[i]?"up":"down");status[i]=key[i];}}}close(fd);return 0;
}
Makefile
CFLAG =-C
TARGET = chrdev
TARGET1 = chr_app
KERNEL = /mydriver/linux-3.5
obj-m += $(TARGET).oall:make $(CFLAG) $(KERNEL) M=$(PWD)arm-linux-gcc -o $(TARGET1) $(TARGET1).c
clean:make $(CFLAG) $(KERNEL) M=$(PWD) clean
本文章僅供學習交流用禁止用作商業用途,文中內容來水枂編輯,如需轉載請告知,謝謝合作
微信公眾號:zhjj0729
微博:文藝to青年