linux设备驱动程序中的阻塞机制
阻塞與非阻塞是設(shè)備訪問(wèn)的兩種方式。在寫阻塞與非阻塞的驅(qū)動(dòng)程序時(shí),經(jīng)常用到等待隊(duì)列。
一、阻塞與非阻塞
阻塞調(diào)用是指調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起,函數(shù)只有在得到結(jié)果之后才會(huì)返回。
非阻塞指不能立刻得到結(jié)果之前,該函數(shù)不會(huì)阻塞當(dāng)前進(jìn)程,而會(huì)立刻返回。
對(duì)象是否處于阻塞模式和函數(shù)是不是阻塞調(diào)用有很強(qiáng)的相關(guān)性,但并不是一一對(duì)應(yīng)的。阻塞對(duì)象上可以有非阻塞的調(diào)用方式,我們可以通過(guò)一定的API去輪詢狀態(tài),在適當(dāng)?shù)臅r(shí)候調(diào)用阻塞函數(shù),就可以避免阻塞。而對(duì)于非阻塞對(duì)象,調(diào)用的函數(shù)也可以進(jìn)入阻塞調(diào)用。函數(shù)select()就是這樣一個(gè)例子。
二、等待隊(duì)列
在linux設(shè)備驅(qū)動(dòng)程序中,阻塞進(jìn)程可以使用等待隊(duì)列來(lái)實(shí)現(xiàn)。
在內(nèi)核中,等待隊(duì)列是有很多用處的,尤其是在中斷處理,進(jìn)程同步,定時(shí)等場(chǎng)合,可以使用等待隊(duì)列實(shí)現(xiàn)阻塞進(jìn)程的喚醒。它以隊(duì)列為基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),與進(jìn)程調(diào)度機(jī)制緊密結(jié)合,能夠用于實(shí)現(xiàn)內(nèi)核中的異步事件通知機(jī)制,同步對(duì)系統(tǒng)資源的訪問(wèn)。
1、等待隊(duì)列的實(shí)現(xiàn):
在linux中,等待隊(duì)列的結(jié)構(gòu)如下:
struct __wait_queue_head { spinlock_t lock; //自旋鎖,用來(lái)對(duì)task_list鏈表起保護(hù)作用,實(shí)現(xiàn)了對(duì)等待隊(duì)列的互斥訪問(wèn) struct list_head task_list; //用來(lái)存放等待的進(jìn)程 }; typedef struct __wait_queue_head wait_queue_head_t;?
2、等待隊(duì)列的使用
(1)定義和初始化等待隊(duì)列:
(2)添加或移除等待隊(duì)列:
(3)等待事件:
?
(4)睡眠:
sleep_on(wait_queue_head_t *q); interruptible_sleep_on(wait_queue_head_t *q); /* sleep_on作用是把目前進(jìn)程的狀態(tài)置成TASK_UNINTERRUPTIBLE,直到資源可用,q引導(dǎo)的等待隊(duì)列被喚醒。 interruptible_sleep_on作用是一樣的, 只不過(guò)它把進(jìn)程狀態(tài)置為TASK_INTERRUPTIBLE */?
(5)喚醒等待隊(duì)列:
//可喚醒處于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE狀態(tài)的進(jìn)程; #define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)//只能喚醒處于TASK_INTERRUPTIBLE狀態(tài)的進(jìn)程 #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)?
三、操作系統(tǒng)中睡眠、阻塞、掛起的區(qū)別形象解釋
首先這些術(shù)語(yǔ)都是對(duì)于線程來(lái)說(shuō)的。對(duì)線程的控制就好比你控制了一個(gè)雇工為你干活。你對(duì)雇工的控制是通過(guò)編程來(lái)實(shí)現(xiàn)的。
掛起線程的意思就是你對(duì)主動(dòng)對(duì)雇工說(shuō):“你睡覺(jué)去吧,用著你的時(shí)候我主動(dòng)去叫你,然后接著干活”。
使線程睡眠的意思就是你主動(dòng)對(duì)雇工說(shuō):“你睡覺(jué)去吧,某時(shí)某刻過(guò)來(lái)報(bào)到,然后接著干活”。
線程阻塞的意思就是,你突然發(fā)現(xiàn),你的雇工不知道在什么時(shí)候沒(méi)經(jīng)過(guò)你允許,自己睡覺(jué)呢,但是你不能怪雇工,肯定你這個(gè)雇主沒(méi)注意,本來(lái)你讓雇工掃地,結(jié)果掃帚被偷了或被鄰居家借去了,你又沒(méi)讓雇工繼續(xù)干別的活,他就只好睡覺(jué)了。至于掃帚回來(lái)后,雇工會(huì)不會(huì)知道,會(huì)不會(huì)繼續(xù)干活,你不用擔(dān)心,雇工一旦發(fā)現(xiàn)掃帚回來(lái)了,他就會(huì)自己去干活的。因?yàn)楣凸な苓^(guò)良好的培訓(xùn)。這個(gè)培訓(xùn)機(jī)構(gòu)就是操作系統(tǒng)。
四、阻塞與非阻塞操作
阻塞操作是指在執(zhí)行設(shè)備操作時(shí)若不能獲得資源則掛起進(jìn)程,直到滿足可操作的條件后在進(jìn)行操作。
非阻塞操作的進(jìn)程在不能進(jìn)行設(shè)備操作時(shí)并不掛起,它或者被放棄,或者不停的查詢,直到可以進(jìn)行操作為止。
回顧簡(jiǎn)單字符設(shè)備驅(qū)動(dòng), 我們看到如何實(shí)現(xiàn) read 和 write 方法. 在此, 但是, 我們跳過(guò)了一個(gè)重要的問(wèn)題:一個(gè)驅(qū)動(dòng)當(dāng)它無(wú)法立刻滿足請(qǐng)求應(yīng)當(dāng)如何響應(yīng)??一個(gè)對(duì) read 的調(diào)用可能當(dāng)沒(méi)有數(shù)據(jù)時(shí)到來(lái), 而以后會(huì)期待更多的數(shù)據(jù). 或者一個(gè)進(jìn)程可能試圖寫, 但是你的設(shè)備沒(méi)有準(zhǔn)備好接受數(shù)據(jù), 因?yàn)槟愕妮敵鼍彌_滿了. 調(diào)用進(jìn)程往往不關(guān)心這種問(wèn)題; 程序員只希望調(diào)用 read 或 write 并且使調(diào)用返回, 在必要的工作已完成后. 這樣, 在這樣的情形中, 你的驅(qū)動(dòng)應(yīng)當(dāng)(缺省地)阻塞進(jìn)程, 使它進(jìn)入睡眠直到請(qǐng)求可繼續(xù)。
在我們看全功能的 read 和 write 方法的實(shí)現(xiàn)之前, 我們觸及的最后一點(diǎn)是決定何時(shí)使進(jìn)程睡眠.?
(1)阻塞型驅(qū)動(dòng)中,read實(shí)現(xiàn)方式:如果一個(gè)進(jìn)程調(diào)用 read 但是沒(méi)有數(shù)據(jù)可用, 這個(gè)進(jìn)程必須阻塞. 這個(gè)進(jìn)程在有數(shù)據(jù)達(dá)到時(shí)被立刻喚醒, 并且那個(gè)數(shù)據(jù)被返回給調(diào)用者, 即便小于在給方法的 count 參數(shù)中請(qǐng)求的數(shù)量.
(2)阻塞型驅(qū)動(dòng)中,write實(shí)現(xiàn)方式:如果一個(gè)進(jìn)程調(diào)用 write 并且在緩沖中沒(méi)有空間, 這個(gè)進(jìn)程必須阻塞, 并且它必須在一個(gè)與用作 read 的不同的等待隊(duì)列中. 當(dāng)一些數(shù)據(jù)被寫入硬件設(shè)備, 并且在輸出緩沖中的空間變空閑, 這個(gè)進(jìn)程被喚醒并且寫調(diào)用成功, 盡管數(shù)據(jù)可能只被部分寫入如果在緩沖只沒(méi)有空間給被請(qǐng)求的 count 字節(jié).
(3)有時(shí)要求一個(gè)操作不阻塞, 即便它不能完全地進(jìn)行下去.應(yīng)用程序元可以調(diào)用 filp->f_flags 中的 O_NONBLOCK 標(biāo)志來(lái)人為的設(shè)置讀寫操作為非阻塞方式. 這個(gè)標(biāo)志定義于 <linux/fcntl.h>, 被 <linux/fs.h>自動(dòng)包含.
?
五、阻塞型驅(qū)動(dòng)測(cè)試程序:
1.memdev.h
#ifndef _MEMDEV_H_ #define _MEMDEV_H_#ifndef MEMDEV_MAJOR #define MEMDEV_MAJOR 0 /*預(yù)設(shè)的mem的主設(shè)備號(hào)*/ #endif#ifndef MEMDEV_NR_DEVS #define MEMDEV_NR_DEVS 2 /*設(shè)備數(shù)*/ #endif#ifndef MEMDEV_SIZE #define MEMDEV_SIZE 4096 #endif /*mem設(shè)備描述結(jié)構(gòu)體*/ 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; /*表明設(shè)備有足夠數(shù)據(jù)可供讀*/module_param(mem_major, int, S_IRUGO);struct mem_dev *mem_devp; /*設(shè)備結(jié)構(gòu)體指針*/struct cdev cdev; /*文件打開函數(shù)*/ int mem_open(struct inode *inode, struct file *filp) {struct mem_dev *dev;/*獲取次設(shè)備號(hào)*/int num = MINOR(inode->i_rdev);if (num >= MEMDEV_NR_DEVS) return -ENODEV;dev = &mem_devp[num];/*將設(shè)備描述結(jié)構(gòu)指針賦值給文件私有數(shù)據(jù)指針*/filp->private_data = dev;return 0; }/*文件釋放函數(shù)*/ int mem_release(struct inode *inode, struct file *filp) {return 0; }/*讀函數(shù)*/ 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; /*獲得設(shè)備結(jié)構(gòu)體指針*//*判斷讀位置是否有效*/if (p >= MEMDEV_SIZE)return 0;if (count > MEMDEV_SIZE - p)count = MEMDEV_SIZE - p;while (!have_data) /* 沒(méi)有數(shù)據(jù)可讀,考慮為什么不用if,而用while,中斷信號(hào)喚醒 */ {if (filp->f_flags & O_NONBLOCK)return -EAGAIN;wait_event_interruptible(dev->inq,have_data); }/*讀數(shù)據(jù)到用戶空間*/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; /* 表明不再有數(shù)據(jù)可讀 */return ret; }/*寫函數(shù)*/ 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; /*獲得設(shè)備結(jié)構(gòu)體指針*//*分析和獲取有效的寫長(zhǎng)度*/if (p >= MEMDEV_SIZE)return 0;if (count > MEMDEV_SIZE - p)count = MEMDEV_SIZE - p;/*從用戶空間寫入數(shù)據(jù)*/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; /* 有新的數(shù)據(jù)可讀 *//* 喚醒讀進(jìn)程 */wake_up(&(dev->inq));return ret; }/* seek文件定位函數(shù) */ 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;}/*文件操作結(jié)構(gòu)體*/ 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, };/*設(shè)備驅(qū)動(dòng)模塊加載函數(shù)*/ static int memdev_init(void) {int result;int i;dev_t devno = MKDEV(mem_major, 0);/* 靜態(tài)申請(qǐng)?jiān)O(shè)備號(hào)*/if (mem_major)result = register_chrdev_region(devno, 2, "memdev");else /* 動(dòng)態(tài)分配設(shè)備號(hào) */{result = alloc_chrdev_region(&devno, 0, 2, "memdev");mem_major = MAJOR(devno);} if (result < 0)return result;/*初始化cdev結(jié)構(gòu)*/cdev_init(&cdev, &mem_fops);cdev.owner = THIS_MODULE;cdev.ops = &mem_fops;/* 注冊(cè)字符設(shè)備 */cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);/* 為設(shè)備描述結(jié)構(gòu)分配內(nèi)存*/mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);if (!mem_devp) /*申請(qǐng)失敗*/{result = - ENOMEM;goto fail_malloc;}memset(mem_devp, 0, sizeof(struct mem_dev));/*為設(shè)備分配內(nèi)存*/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);/*初始化等待隊(duì)列*/init_waitqueue_head(&(mem_devp[i].inq));}return 0;fail_malloc: unregister_chrdev_region(devno, 1);return result; }/*模塊卸載函數(shù)*/ static void memdev_exit(void) {cdev_del(&cdev); /*注銷設(shè)備*/kfree(mem_devp); /*釋放設(shè)備結(jié)構(gòu)體內(nèi)存*/unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設(shè)備號(hào)*/ }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);/*打開設(shè)備文件*/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);/*讀出數(shù)據(jù)*/fread(Buf, sizeof(Buf), 1, fp);/*檢測(cè)結(jié)果*/printf("Read BUF2: %s\n",Buf);fclose(fp);return 0; }總結(jié)
以上是生活随笔為你收集整理的linux设备驱动程序中的阻塞机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux设备驱动开发-linux驱动中
- 下一篇: Linux设备驱动之Ioctl控制