linux设备驱动程序中的阻塞机制
阻塞與非阻塞是設備訪問的兩種方式。在寫阻塞與非阻塞的驅動程序時,經常用到等待隊列。
一、阻塞與非阻塞
阻塞調用是指調用結果返回之前,當前線程會被掛起,函數只有在得到結果之后才會返回。
非阻塞指不能立刻得到結果之前,該函數不會阻塞當前進程,而會立刻返回。
對象是否處于阻塞模式和函數是不是阻塞調用有很強的相關性,但并不是一一對應的。阻塞對象上可以有非阻塞的調用方式,我們可以通過一定的API去輪詢狀態,在適當的時候調用阻塞函數,就可以避免阻塞。而對于非阻塞對象,調用的函數也可以進入阻塞調用。函數select()就是這樣一個例子。
二、等待隊列
在linux設備驅動程序中,阻塞進程可以使用等待隊列來實現。
在內核中,等待隊列是有很多用處的,尤其是在中斷處理,進程同步,定時等場合,可以使用等待隊列實現阻塞進程的喚醒。它以隊列為基礎數據結構,與進程調度機制緊密結合,能夠用于實現內核中的異步事件通知機制,同步對系統資源的訪問。
1、等待隊列的實現:
在linux中,等待隊列的結構如下:
struct __wait_queue_head { spinlock_t lock; //自旋鎖,用來對task_list鏈表起保護作用,實現了對等待隊列的互斥訪問 struct list_head task_list; //用來存放等待的進程 }; typedef struct __wait_queue_head wait_queue_head_t;?
2、等待隊列的使用
(1)定義和初始化等待隊列:
(2)添加或移除等待隊列:
(3)等待事件:
?
(4)睡眠:
sleep_on(wait_queue_head_t *q); interruptible_sleep_on(wait_queue_head_t *q); /* sleep_on作用是把目前進程的狀態置成TASK_UNINTERRUPTIBLE,直到資源可用,q引導的等待隊列被喚醒。 interruptible_sleep_on作用是一樣的, 只不過它把進程狀態置為TASK_INTERRUPTIBLE */?
(5)喚醒等待隊列:
//可喚醒處于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE狀態的進程; #define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)//只能喚醒處于TASK_INTERRUPTIBLE狀態的進程 #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)?
三、操作系統中睡眠、阻塞、掛起的區別形象解釋
首先這些術語都是對于線程來說的。對線程的控制就好比你控制了一個雇工為你干活。你對雇工的控制是通過編程來實現的。
掛起線程的意思就是你對主動對雇工說:“你睡覺去吧,用著你的時候我主動去叫你,然后接著干活”。
使線程睡眠的意思就是你主動對雇工說:“你睡覺去吧,某時某刻過來報到,然后接著干活”。
線程阻塞的意思就是,你突然發現,你的雇工不知道在什么時候沒經過你允許,自己睡覺呢,但是你不能怪雇工,肯定你這個雇主沒注意,本來你讓雇工掃地,結果掃帚被偷了或被鄰居家借去了,你又沒讓雇工繼續干別的活,他就只好睡覺了。至于掃帚回來后,雇工會不會知道,會不會繼續干活,你不用擔心,雇工一旦發現掃帚回來了,他就會自己去干活的。因為雇工受過良好的培訓。這個培訓機構就是操作系統。
四、阻塞與非阻塞操作
阻塞操作是指在執行設備操作時若不能獲得資源則掛起進程,直到滿足可操作的條件后在進行操作。
非阻塞操作的進程在不能進行設備操作時并不掛起,它或者被放棄,或者不停的查詢,直到可以進行操作為止。
回顧簡單字符設備驅動, 我們看到如何實現 read 和 write 方法. 在此, 但是, 我們跳過了一個重要的問題:一個驅動當它無法立刻滿足請求應當如何響應??一個對 read 的調用可能當沒有數據時到來, 而以后會期待更多的數據. 或者一個進程可能試圖寫, 但是你的設備沒有準備好接受數據, 因為你的輸出緩沖滿了. 調用進程往往不關心這種問題; 程序員只希望調用 read 或 write 并且使調用返回, 在必要的工作已完成后. 這樣, 在這樣的情形中, 你的驅動應當(缺省地)阻塞進程, 使它進入睡眠直到請求可繼續。
在我們看全功能的 read 和 write 方法的實現之前, 我們觸及的最后一點是決定何時使進程睡眠.?
(1)阻塞型驅動中,read實現方式:如果一個進程調用 read 但是沒有數據可用, 這個進程必須阻塞. 這個進程在有數據達到時被立刻喚醒, 并且那個數據被返回給調用者, 即便小于在給方法的 count 參數中請求的數量.
(2)阻塞型驅動中,write實現方式:如果一個進程調用 write 并且在緩沖中沒有空間, 這個進程必須阻塞, 并且它必須在一個與用作 read 的不同的等待隊列中. 當一些數據被寫入硬件設備, 并且在輸出緩沖中的空間變空閑, 這個進程被喚醒并且寫調用成功, 盡管數據可能只被部分寫入如果在緩沖只沒有空間給被請求的 count 字節.
(3)有時要求一個操作不阻塞, 即便它不能完全地進行下去.應用程序元可以調用 filp->f_flags 中的 O_NONBLOCK 標志來人為的設置讀寫操作為非阻塞方式. 這個標志定義于 <linux/fcntl.h>, 被 <linux/fs.h>自動包含.
?
五、阻塞型驅動測試程序:
1.memdev.h
#ifndef _MEMDEV_H_ #define _MEMDEV_H_#ifndef MEMDEV_MAJOR #define MEMDEV_MAJOR 0 /*預設的mem的主設備號*/ #endif#ifndef MEMDEV_NR_DEVS #define MEMDEV_NR_DEVS 2 /*設備數*/ #endif#ifndef MEMDEV_SIZE #define MEMDEV_SIZE 4096 #endif /*mem設備描述結構體*/ struct mem_dev { char *data; unsigned long size; wait_queue_head_t inq; };#endif /* _MEMDEV_H_ */
2.memdev.c
#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/cdev.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h>#include "memdev.h" static mem_major = MEMDEV_MAJOR; bool have_data = false; /*表明設備有足夠數據可供讀*/module_param(mem_major, int, S_IRUGO);struct mem_dev *mem_devp; /*設備結構體指針*/struct cdev cdev; /*文件打開函數*/ int mem_open(struct inode *inode, struct file *filp) {struct mem_dev *dev;/*獲取次設備號*/int num = MINOR(inode->i_rdev);if (num >= MEMDEV_NR_DEVS) return -ENODEV;dev = &mem_devp[num];/*將設備描述結構指針賦值給文件私有數據指針*/filp->private_data = dev;return 0; }/*文件釋放函數*/ int mem_release(struct inode *inode, struct file *filp) {return 0; }/*讀函數*/ static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) {unsigned long p = *ppos;unsigned int count = size;int ret = 0;struct mem_dev *dev = filp->private_data; /*獲得設備結構體指針*//*判斷讀位置是否有效*/if (p >= MEMDEV_SIZE)return 0;if (count > MEMDEV_SIZE - p)count = MEMDEV_SIZE - p;while (!have_data) /* 沒有數據可讀,考慮為什么不用if,而用while,中斷信號喚醒 */ {if (filp->f_flags & O_NONBLOCK)return -EAGAIN;wait_event_interruptible(dev->inq,have_data); }/*讀數據到用戶空間*/if (copy_to_user(buf, (void*)(dev->data + p), count)){ret = - EFAULT;}else{*ppos += count;ret = count;printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);}have_data = false; /* 表明不再有數據可讀 */return ret; }/*寫函數*/ static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) {unsigned long p = *ppos;unsigned int count = size;int ret = 0;struct mem_dev *dev = filp->private_data; /*獲得設備結構體指針*//*分析和獲取有效的寫長度*/if (p >= MEMDEV_SIZE)return 0;if (count > MEMDEV_SIZE - p)count = MEMDEV_SIZE - p;/*從用戶空間寫入數據*/if (copy_from_user(dev->data + p, buf, count))ret = - EFAULT;else{*ppos += count;ret = count;printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);}have_data = true; /* 有新的數據可讀 *//* 喚醒讀進程 */wake_up(&(dev->inq));return ret; }/* seek文件定位函數 */ static loff_t mem_llseek(struct file *filp, loff_t offset, int whence) { loff_t newpos;switch(whence) {case 0: /* SEEK_SET */newpos = offset;break;case 1: /* SEEK_CUR */newpos = filp->f_pos + offset;break;case 2: /* SEEK_END */newpos = MEMDEV_SIZE -1 + offset;break;default: /* can't happen */return -EINVAL;}if ((newpos<0) || (newpos>MEMDEV_SIZE))return -EINVAL;filp->f_pos = newpos;return newpos;}/*文件操作結構體*/ static const struct file_operations mem_fops = {.owner = THIS_MODULE,.llseek = mem_llseek,.read = mem_read,.write = mem_write,.open = mem_open,.release = mem_release, };/*設備驅動模塊加載函數*/ static int memdev_init(void) {int result;int i;dev_t devno = MKDEV(mem_major, 0);/* 靜態申請設備號*/if (mem_major)result = register_chrdev_region(devno, 2, "memdev");else /* 動態分配設備號 */{result = alloc_chrdev_region(&devno, 0, 2, "memdev");mem_major = MAJOR(devno);} if (result < 0)return result;/*初始化cdev結構*/cdev_init(&cdev, &mem_fops);cdev.owner = THIS_MODULE;cdev.ops = &mem_fops;/* 注冊字符設備 */cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);/* 為設備描述結構分配內存*/mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);if (!mem_devp) /*申請失敗*/{result = - ENOMEM;goto fail_malloc;}memset(mem_devp, 0, sizeof(struct mem_dev));/*為設備分配內存*/for (i=0; i < MEMDEV_NR_DEVS; i++) {mem_devp[i].size = MEMDEV_SIZE;mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);memset(mem_devp[i].data, 0, MEMDEV_SIZE);/*初始化等待隊列*/init_waitqueue_head(&(mem_devp[i].inq));}return 0;fail_malloc: unregister_chrdev_region(devno, 1);return result; }/*模塊卸載函數*/ static void memdev_exit(void) {cdev_del(&cdev); /*注銷設備*/kfree(mem_devp); /*釋放設備結構體內存*/unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設備號*/ }MODULE_AUTHOR("David Xie"); MODULE_LICENSE("GPL");module_init(memdev_init); module_exit(memdev_exit);
3.app-write.c
4.app-read.c
#include <stdio.h>int main() {FILE *fp = NULL;char Buf[128];/*初始化Buf*/strcpy(Buf,"memdev is char dev!");printf("BUF: %s\n",Buf);/*打開設備文件*/fp = fopen("/dev/memdev0","r+");if (fp == NULL){printf("Open memdev0 Error!\n");return -1;}/*清除Buf*/strcpy(Buf,"Buf is NULL!");printf("Read BUF1: %s\n",Buf);/*讀出數據*/fread(Buf, sizeof(Buf), 1, fp);/*檢測結果*/printf("Read BUF2: %s\n",Buf);fclose(fp);return 0; }總結
以上是生活随笔為你收集整理的linux设备驱动程序中的阻塞机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux设备驱动开发-linux驱动中
- 下一篇: Linux设备驱动之Ioctl控制