一步步設(shè)計(jì)自己的驅(qū)動(dòng)程序
實(shí)驗(yàn)?zāi)康模?br>通過一個(gè)簡單的設(shè)備驅(qū)動(dòng)的實(shí)現(xiàn)過程。學(xué)會(huì)Linux中設(shè)備驅(qū)動(dòng)程序的編寫
實(shí)驗(yàn)內(nèi)容: 設(shè)計(jì)和實(shí)現(xiàn)一個(gè)虛擬命名管道(FIFO)的字符設(shè)備。寫一個(gè)模塊化的字符設(shè)備驅(qū)動(dòng)程序
實(shí)驗(yàn)提示: 一、設(shè)備的功能 設(shè)計(jì)和實(shí)現(xiàn)一個(gè)虛擬命名管道(FIFO)的字符設(shè)備。我們知道,管道是進(jìn)程間通信的一種 方式:一個(gè)進(jìn)程向管道中寫數(shù)據(jù),另一個(gè)進(jìn)程從管道中讀取數(shù)據(jù),先寫入的數(shù)據(jù)先讀出。我 們的驅(qū)動(dòng)程序要實(shí)現(xiàn)N(N=4)個(gè)管道,每個(gè)管道對(duì)應(yīng)兩個(gè)設(shè)備,次設(shè)備號(hào)是偶數(shù)的設(shè)備是只 寫設(shè)備,次設(shè)備號(hào)是奇數(shù)的是只讀設(shè)備。寫入設(shè)備i(i是偶數(shù))的字符可以從設(shè)備i+1讀出。 這樣,我們一共就需要2N 個(gè)次設(shè)備號(hào)。 我們的目標(biāo)是寫一個(gè)模塊化的字符設(shè)備驅(qū)動(dòng)程序。設(shè)備所使用的主設(shè)備號(hào)可以從尚未分 配的主設(shè)備號(hào)中任選一個(gè),/Documentation/devices.txt 記錄了當(dāng)前版本內(nèi)核的主設(shè)備號(hào)分配 情況。如果設(shè)備文件系統(tǒng)(devfs)尚未激活,我們?cè)诩虞d模塊之后,還必須用mknod 命令創(chuàng) 建相應(yīng)的設(shè)備文件節(jié)點(diǎn)。 如果FIFO 的寫入端尚未打開,F(xiàn)IFO 中就不會(huì)有數(shù)據(jù)可讀,所以此時(shí)試圖從FIFO 中讀 取數(shù)據(jù)的進(jìn)程應(yīng)該返回一個(gè)錯(cuò)誤碼。如果寫入端已經(jīng)打開,為了保證對(duì)臨界區(qū)的互斥訪問, 調(diào)用讀操作的進(jìn)程必須被阻塞。如果存在被阻塞的讀者,在寫操作完成后(或者關(guān)閉一個(gè)寫 設(shè)備時(shí))必須喚醒它。 如果寫入的數(shù)據(jù)太多,超出了緩沖區(qū)中空閑塊的大小,調(diào)用寫操作的進(jìn)程必須睡眠,以 等待緩沖區(qū)中有新的空閑塊。
二、設(shè)備的實(shí)現(xiàn) 1. 數(shù)據(jù)結(jié)構(gòu): 首先,我們要包含一些必要的頭文件、宏和全局變量。 vfifo.c #ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #define __NO_VERSION__ #include<linux/config.h> #include<linux/module.h> #include<linux/kernel.h> #include<linux/malloc.h> #include<linux/fs.h> #include<linux/proc_fs.h> #include<linux/errno.h> #include<linux/types.h> #include<linux/fcntl.h> #include<linux/init.h> #include<asm/system.h> #include<asm/uaccess.h> #ifndef VFIFO_MAJOR #define VFIFO_MAJOR 241 #endif #ifndef VFIFO_NR_DEVS #define VFIFO_NR_DEVS 4 #endif #ifndef VFIFO_BUFFER #define VFIFO_BUFFER 4000 #endif #include<linux/devfs_fs_kernel.h> devfs_handle_t vfifo_devfs_dir; struct file_operations vfifo_fops; int vfifo_major=VFIFO_MAJOR; int vfifo_nr_devs=VFIFO_NR_DEVS; int vfifo_buffer=VFIFO_BUFFER; MODULE_PARM(vfifo_major,"i"); MODULE_PARM(vfifo_nr_devs,"i"); MODULE_PARM(vfifo_buffer,"i"); MODULE_AUTHOR("EBUDDY");
每個(gè)實(shí)際的FIFO 設(shè)備都對(duì)應(yīng)于一個(gè)Vfifo_Dev{ }結(jié)構(gòu)體。其中,rdq 是阻塞讀的等待 隊(duì)列,wrq 是阻塞寫的等待隊(duì)列,base 是所分配緩沖區(qū)的起始地址,buffersize 是緩沖區(qū)的 大小,len表示管道中已有數(shù)據(jù)塊的長度,start 表示當(dāng)前應(yīng)該讀取的緩沖區(qū)位置相對(duì)于base 的偏移量,即緩沖區(qū)起始數(shù)據(jù)的偏移量,readers和writers分別表示VFIFO 設(shè)備當(dāng)前的讀者 個(gè)數(shù)和寫者個(gè)數(shù),sem是用于互斥訪問的信號(hào)量,r_handle和w_handle用于保存設(shè)備文件系 統(tǒng)的注冊(cè)句柄,r_handle對(duì)應(yīng)的是只讀設(shè)備,w_handle對(duì)應(yīng)的是同一管道的只寫設(shè)備。具體 的定義如下所示: vfifo.c typedef struct Vfifo_Dev{ wait_queue_head rdq,wrq; char* base; unsigned int buffersize; unsigned int len; unsigned int start; unsigned int readers,writers; struct semaphore sem; devfs_handle_t r_handle,w_handle; }Vfifo_Dev;
2.設(shè)備操作接口 (1).注冊(cè)與注銷 注冊(cè)時(shí),我們必須考慮到兩種管理方式(傳統(tǒng)方式與devfs方式)的兼容性。在這里, 我們用條件編譯來解決這個(gè)問題。由于許多主設(shè)備號(hào)已經(jīng)靜態(tài)地分配給了公用設(shè)備,Linux 提供了動(dòng)態(tài)分配機(jī)制以獲取空閑的主設(shè)備號(hào)。傳統(tǒng)方式下,如果調(diào)用devfs_register_chrdev( ) 時(shí)的major 為零的話,它所調(diào)用的register_chrdev( )函數(shù)就會(huì)選擇一個(gè)空閑號(hào)碼作為返回值 返回。主設(shè)備號(hào)總是正的,因此不會(huì)和錯(cuò)誤碼混淆。在devfs方式下,如果devfs_register( ) 的flags 參數(shù)值為DEVFS_FL_AUTO_DEVNUM,注冊(cè)時(shí)就會(huì)自動(dòng)生成設(shè)備號(hào)。 動(dòng)態(tài)分配的缺點(diǎn)是:由于分配的主設(shè)備號(hào)不能保證總是一樣的,無法事先創(chuàng)建設(shè)備節(jié)點(diǎn)。 但是這并不是什么問題,因?yàn)橐坏┓峙淞嗽O(shè)備號(hào),我們就可以從/proc/devices 讀到。為了加 載一個(gè)設(shè)備驅(qū)動(dòng)程序,我們可以用一個(gè)簡單的腳本替換對(duì)insmod的調(diào)用,它通過/proc/devices 獲得新分配的主設(shè)備號(hào),并創(chuàng)建節(jié)點(diǎn)。加載動(dòng)態(tài)分配主設(shè)備號(hào)驅(qū)動(dòng)程序的腳本可以利用awk 這類工具從/proc/devices 中獲取信息,并在/dev中創(chuàng)建文件。在我們的實(shí)例程序中,為了簡 單起見,仍然使用靜態(tài)分配的主設(shè)備號(hào)。 你也許會(huì)注意到我們并沒有使用統(tǒng)一的函數(shù)名init_module( )和cleanup_module( ),這是 由于內(nèi)核編程風(fēng)格的變化。自從2.3.13 版的內(nèi)核以來,Linux提供了兩個(gè)宏module_init( )和 module_exit( )來顯式地命名模塊的注冊(cè)和注銷函數(shù)。通常在源文件的末尾寫上這兩個(gè)宏,例 如: module_init(vfifo_init_module); module_exit(vfifo_exit_module); 注意,在使用這兩個(gè)宏之前必須先包含頭文件<linux/init.h>。這樣做的好處是,內(nèi)核中 的每一個(gè)注冊(cè)和注銷函數(shù)都有一個(gè)唯一的名字,有助于調(diào)試。我們知道驅(qū)動(dòng)程序既可以設(shè)計(jì) 成模塊,又可以靜態(tài)地編譯進(jìn)內(nèi)核,用了這兩個(gè)宏后就能更方便地支持這兩種方式。實(shí)際上, 對(duì)于模塊來說,它們所做的工作僅僅是把給出的函數(shù)名重新命名為 init_module( )和 cleanup_module( )。當(dāng)然,如果你已使用了init_module( )和cleanup_module( )作為函數(shù)名, 那就沒必要再使用這兩個(gè)宏了。 在函數(shù)名之前,我們可以看到一個(gè)表示屬性的詞“__init”,加了這個(gè)屬性之后,系統(tǒng) 會(huì)在初始化完成之后丟棄初始化函數(shù),收回它所占用的內(nèi)存。這樣可以減小內(nèi)核所占用的內(nèi) 存空間。但它只對(duì)內(nèi)建的驅(qū)動(dòng)程序有用,對(duì)于模塊則沒有影響。
vfifo.c char vfifoname[8]; static int __init vfifo_init_module(void) { int result,i; SET_MODULE_OWNER(&vfifo_fops); #ifdef CONFIG_DEVFS_FS vfifo_devfs_dir=devfs_mk_dir(NULL,"vfifo",NULL); if(!vfifo_devfs_dir) return -EBUSY; #endif result=devfs_register_chrdev(vfifo_major,"vfifo",&vfifo_fops); if(result<0){ printk(KERN_WARNING "vfifo: can‘t get major %d",vfifo_major); return result; } if(vfifo_major==0) vfifo_major=result; vfifo_devices = kmalloc(vfifo_nr_devs*sizeof(Vfifo_Dev),GFP_KERNEL); if(!vfifo_devices){ return -ENOMEM; } memset(vfifo_devices,0,vfifo_nr_devs*sizeof(Vfifo_Dev)); for(i=0;i<vfifo_nr_devs;i++){ init_waitqueue_head(&vfifo_devices[i].rdq); init_waitqueue_head(&vfifo_devices[i].wrq); sema_init(&vfifo_devices[i].sem,1); #ifdef CONFIG_DEVFS_FS sprintf(vfifoname,"vfifo%d",2*i); vfifo_devices[i].w_handle= devfs_register(vfifo_devfs_dir,vfifoname, DEVFS_FL_NON, vfifo_major,2*i,S_IFCHR|S_IRUGO|S_IWUGO, &vfifo_fops,vfifo_device+i); sprintf(vfifoname,"vfifo%d",2*i+1); vfifo_devices[i].r_handle= devfs_register(vfifo_devfs_dir,vfifoname, DEVFS_FL_NON, vfifo_major,2*i+1,S_IFCHR|S_IRUGO|S_IWUGO, &vfifo_fops,vfifo_device+i); if(!vfifo_devices[i].r_handle||!vfifo_devices[i].w_handle){
printk(KERN_WARNING "vfifo: can‘t register vfifo device nr %i\n",i); } #endif } #ifdef VFIFO_DEBUG create_proc_read_entry("vfifo",0,NULL,vfifo_read_mem,NULL); #endif return 0;
注銷的工作相對(duì)簡單。需要注意的是在卸載驅(qū)動(dòng)程序之后要?jiǎng)h除設(shè)備節(jié)點(diǎn)。如果設(shè)備節(jié) 點(diǎn)是在加載時(shí)創(chuàng)建的,可以寫一個(gè)簡單的腳本在卸載時(shí)刪除它們。如果動(dòng)態(tài)節(jié)點(diǎn)沒有從/dev 中刪除,就可能造成不可預(yù)期的錯(cuò)誤:系統(tǒng)可能會(huì)給另一個(gè)設(shè)備分配相同的主設(shè)備號(hào),這樣 在打開設(shè)備時(shí)就會(huì)出錯(cuò)。 我們可以看到在函數(shù)名前標(biāo)有屬性“__exit”,它的作用類似于“__init”,即使內(nèi)建的 驅(qū)動(dòng)程序忽略它所標(biāo)記的函數(shù)。同樣的,它對(duì)模塊也沒有影響。 vfifo.c static void __exit vfifo_cleanup_module(void) { int i; devfs_unregister_chrdev(vfifo_major,"vfifo"); #ifdef VFIFO_DEBUG remove_proc_entry("vfifo",NULL); #endif if(vfifo_devices){ for(i=0;i<vfifo_nr_devs;i++){ if(vfifo_devices[i].base) kfree(vfifo_devices[i].base);
devfs_unregister(vfifo_devices[i].r_handle);
devfs_unregister(vfifo_devices[i].w_handle); } kfree(vfifo_devices); devfs_unregister(vfifo_devfs_dir); } } }
(2). 打開與釋放 打開設(shè)備主要是完成一些初始化工作,以及增加引用計(jì)數(shù),防止模塊在設(shè)備關(guān)閉前被注 銷。我們知道內(nèi)核用主設(shè)備號(hào)區(qū)分不同類型的設(shè)備,而驅(qū)動(dòng)程序用次設(shè)備號(hào)識(shí)別具體的設(shè)備。 利用這一特性,我們可以用不同的方式打開同一個(gè)設(shè)備。 vfifo.c static int vfifo_open(struct inode *inode,struct file *filp) { Vfifo_Dev *dev; int num=MINOR(inode->i_rdev); /*檢查讀寫權(quán)限是否合法 */
if((flip->f_mode&FMODE_READ)&&!(num%2)||(filp->f_mode&FMODE_WRITE)&&(num%2)) return -EPERM; if(!filp->private_data){ if(num>=vfifo_nr_devs*2)
return -ENODEV; dev=&vfifo_nr_devices[num/2]; filp->private_data=dev; } else{ dev=filp->private_data; } /*獲得互斥訪問的信號(hào)量 */ if(down_interruptible(&dev->sem)) return -ERESTARTSYS; /*如果尚未分配緩沖區(qū),則分配并初始化 */ if(!dev->base){ dev->base=kmalloc(vfifo_buffer,GFP_KERNEL); if(!dev->base){
up(&dev->sem);
return -ENOMEN; } dev->buffersize=vfifo_buffer; dev->len=dev->start=0; } if(filp->mode&FMODE_READ) dev->readers++; if(filp->mode&FMODE_WRITE) dev->writers++; filp->private_data=dev; MOD_INC_USE_COUNT; return 0; }
釋放(或關(guān)閉)設(shè)備就是打開設(shè)備的逆過程。 vfifo.c static int vfifo_release(struct inode *inode,struct file *filp) { Vfifo_Dev *dev=filp->private_data; /*獲得互斥訪問的信號(hào)量 */ down(&dev->sem); if(filp->f_mode&FMODE_READ) dev->readers--; if(filp->f_mode&FMODE_WRITE){ dev->writes--; wake_up_interruptible(&dev->sem); } if((dev->readers+dev->writers==0)&&(dev->len==0)){ kfree(dev->base); dev->base=NULL; } up(&dev->sem); MOD_DEC_USE_COUNT; return 0; }
讀寫的操作:3
讀寫設(shè)備也就意味著要在內(nèi)核地址空間和用戶地址空間之間傳輸數(shù)據(jù)。由于指針只能在
當(dāng)前地址空間操作,而驅(qū)動(dòng)程序運(yùn)行在內(nèi)核空間,數(shù)據(jù)緩沖區(qū)則在用戶空間,跨空間復(fù)制就
不能通過通常的方法,如利用指針或通過memcpy來完成。在Linux中,跨空間復(fù)制是通過
定義在里的特殊函數(shù)實(shí)現(xiàn)的。你既可以用通用的復(fù)制函數(shù),也可以用針對(duì)不
同數(shù)據(jù)大小(char,short,int,long)進(jìn)行了優(yōu)化的復(fù)制函數(shù)。為了能傳輸任意字節(jié)的數(shù)據(jù),
你可以用copy_to_user( )和copy_from_user( )兩個(gè)函數(shù)。
盡管上面的兩個(gè)函數(shù)看起來很像正常的memcpy函數(shù),但是當(dāng)你在內(nèi)核代碼中訪問用戶
空間時(shí)必須額外注意一些問題:正在被訪問的用戶頁面現(xiàn)在可能不在內(nèi)存中,而且缺頁處理
函數(shù)有可能在傳輸頁面的時(shí)候讓進(jìn)程進(jìn)入睡眠狀態(tài)。例如,當(dāng)必須從交換區(qū)讀取頁面時(shí)就會(huì)
發(fā)生這種情況。驅(qū)動(dòng)程序編寫者在設(shè)計(jì)時(shí)必須注意,任何訪問用戶空間的函數(shù)都必須是可重
入的,而且能夠與驅(qū)動(dòng)程序內(nèi)的其它函數(shù)并發(fā)執(zhí)行。這就是我們用信號(hào)量來控制并發(fā)訪問的
原因。
上述這兩個(gè)函數(shù)的作用并不局限于傳輸數(shù)據(jù),它們也可以檢查用戶空間的指針是否有
效。如果指針無效,復(fù)制不會(huì)進(jìn)行;如果在復(fù)制過程中遇到了無效地址,則只復(fù)制部分?jǐn)?shù)據(jù)。
在這兩種情況下,函數(shù)的返回值都是尚未復(fù)制數(shù)據(jù)的字節(jié)數(shù)。如果你不需要檢查用戶空間指
針的有效性,你可以直接調(diào)用__copy_to_user( )和__copy_from_user( )。例如,你已經(jīng)知道參
數(shù)是有效的,這樣做就可以提高效率。
就實(shí)際的設(shè)備操作而言,讀的任務(wù)是把數(shù)據(jù)從設(shè)備復(fù)制到用戶空間(用copy_to_user( )),
而寫操作則必須把數(shù)據(jù)從用戶空間復(fù)制到設(shè)備(用copy_from_user( ))。每一個(gè)read或write
系統(tǒng)調(diào)用都會(huì)要求傳輸一定字節(jié)數(shù)的數(shù)據(jù),但驅(qū)動(dòng)程序可以隨意傳輸其中一部分?jǐn)?shù)據(jù)。
如果有錯(cuò)誤發(fā)生,read和write都會(huì)返回一個(gè)負(fù)值。一個(gè)大于等于零的返回值會(huì)告訴調(diào)
用程序成功傳輸了多少字節(jié)的數(shù)據(jù)。如果某個(gè)數(shù)據(jù)成功地傳輸了,隨后發(fā)生了錯(cuò)誤,返回值
必須是成功傳輸?shù)淖止?jié)數(shù),只有到下次函數(shù)被調(diào)用時(shí)才會(huì)報(bào)告錯(cuò)誤。
雖然內(nèi)核函數(shù)返回一個(gè)負(fù)值標(biāo)識(shí)錯(cuò)誤,該數(shù)的數(shù)值表示已發(fā)生的錯(cuò)誤種類,但是運(yùn)行在
用戶空間的程序只能看到錯(cuò)誤返回值-1。只有訪問變量errno,程序才能知道發(fā)生了什么錯(cuò)
誤。這兩方面的不同行為,一方面是靠系統(tǒng)調(diào)用的POSIX 調(diào)用標(biāo)準(zhǔn)強(qiáng)加的,另一方面是內(nèi)
核不處理errno的優(yōu)點(diǎn)導(dǎo)致的。
具體的read代碼如下:
vfifo.c
static ssize_t vfifo_read(struct file *filp,char *buf,size_t count,loff_t *f_pos)
{
Vfifo_Dev *dev=filp->private_data;
ssize_t read=0;
/*不允許進(jìn)行定位操作 */
if(f_pos!=&filp->f_pos)
return -ESPIPE;
/*獲得互斥訪問的信號(hào)量 */
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
do_more_read:
/*沒有數(shù)據(jù)可讀,則進(jìn)入循環(huán)等待 */
while(dev->len==0){
if(!dev->writers){
up(&dev->sem);
return -EAGAIN;
}
up(&dev->sem);
if(filp->f_flags&O_NONBLOCK)
return -EAGAIN;
printk("%s reading:going to sleep\n",current->comm);
if(wait_event_interruptible(dev->rdq,(dev->len>0)))
return -ERESTARTSYS;
printk("%s has been waken up\n",current->comm);
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
/*讀數(shù)據(jù) */
while(count>0&&dev->len){
char *pipebuf=dev->base+dev->start;
/*(buffersize – start)是可以一次性讀取的最大數(shù)據(jù)量 */
ssize_t chars=dev->buffersize-dev->start;
if(chars>count) chars=count;
if(chars>dev->len) chars=dev->len;
if(copy_to_user(buf,pipebuf,chars)){
up(&dev->sem);
return -EFAULT;
}
read+=chars;
dev->start+=chars;
dev->start%=dev->buffersize;
dev->len-=chars;
count-=chars;
buf+=chars;
}
/*Cache behavior optimizition*/
if(!dev->len) dev->start=0;
if(count&&dev->writers&&!(filp->flags&O_NONBLOCK)){
up(&dev->sem);
wake_up_interruptible(&dev->wrq);
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
goto do_more_read;
}
up(&dev->sem);
wake_up_interruptible(&dev->wrq);
printk("%s did read %li bytes\n",current->comm, (long)read);
return read;
}
具體的write代碼如下:
vfifo.c
static ssize_t vfifo_write(struct file *filp,const char *buf,size_t count,loff_t *f_pos)
{
Vfifo_Dev *dev=filp->private_data;
ssize_t written=0;
/*不允許進(jìn)行定位操作 */
if(f_pos!=&filp->f-pos||count==0)
return -ESPIPE;
/*獲得互斥訪問的信號(hào)量 */
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
do_more_write:
/*緩沖區(qū)已滿,則循環(huán)等待 */
while(dev->len==dev->buffersize){
up(&dev->sem);
if(filp->f_flags&O_NONBLOCK)
return -EAGAIN;
printk("%s writting:going to sleep\n",current->comm);
if(wait_event_interruptible(dev->wrq,(dev->lenbuffersize)))
return -ERESTARTSYS;
printk("%s has been waken up\n",current->comm);
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
/*寫數(shù)據(jù) */
while(count>0){
char *pipebuf=dev->base+(dev->len+dev->start)%dev->buffersize;
/*下面兩行計(jì)算可以一次性寫入的最大數(shù)據(jù)量 */
ssize_t chars=dev->buffersize-(dev->len+dev->start);
if(chars<0) chars+=dev->start;
if(chars!=0){
if(chars>count) chars=count;
if(copy_from_user(buf,pipebuf,chars)){
up(&dev->sem);
return -EFAULT;
}
written+=chars;
dev->len+=chars;
count-=chars;
buf+=chars;
}
}
if(count&&!(filp->f_flags&O_NONBLOCK)){
up(&dev->sem);
wake_up_interruptible(&dev->rdq);
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
goto do_more_write;
}
up(&dev->sem);
wake_up_interruptible(&dev->rdq);
printk("%s did write %li bytes\n",current->comm, (long)written);
return written;
}
poll方法.4
使用非阻塞型I/O 的應(yīng)用程序經(jīng)常要用到poll 和select 系統(tǒng)調(diào)用。poll 和select 本質(zhì)上
具有相同的功能:它們都允許一個(gè)進(jìn)程決定它是否能無阻塞地從一個(gè)或多個(gè)打開的文件中讀
數(shù)據(jù),或者向這些文件中寫數(shù)據(jù)。這兩個(gè)系統(tǒng)調(diào)用還可用來實(shí)現(xiàn)在無阻塞情況下的不同源輸
入的多路復(fù)用。同樣的功能為什么要由兩個(gè)不同的函數(shù)提供呢?這是因?yàn)樗鼈儙缀跏窃谕?br>
時(shí)間由兩個(gè)不同的團(tuán)體引入U(xiǎn)nix 系統(tǒng)中的:BSD Unix引入了select,System V引入了poll。
在Linux 2.0 版本的內(nèi)核中只支持select,從2.1.23 版本的內(nèi)核開始,系統(tǒng)提供了對(duì)兩種調(diào)用
的支持。我們的驅(qū)動(dòng)程序是基于poll系統(tǒng)調(diào)用,因?yàn)閜oll提供了比select更詳細(xì)的支持。
poll的實(shí)現(xiàn)可以執(zhí)行poll和select兩種系統(tǒng)調(diào)用,它的原型如下:
unsigned int (*poll) (struct file *,poll_table * )
驅(qū)動(dòng)程序中的poll主要完成兩個(gè)任務(wù):
l。 在一個(gè)可能在將來喚醒它的等待隊(duì)列中將當(dāng)前進(jìn)程排隊(duì)。通常,這意味著同時(shí)在輸
入和輸出隊(duì)列中對(duì)進(jìn)程排隊(duì)。函數(shù)poll_wait( )就用于這個(gè)目的,其工作方式說
select_wait( )非常類似。
2。 構(gòu)造一個(gè)位掩碼描述設(shè)備的狀態(tài),并將其返回給調(diào)用者。這個(gè)位掩碼描述了能立即
被無阻塞執(zhí)行的操作。
這兩個(gè)操作通常是很簡單的,在每個(gè)驅(qū)動(dòng)程序中的實(shí)現(xiàn)都非常相似。然而,它們依賴于
一些只有驅(qū)動(dòng)程序才能提供的信息,因此必須在每個(gè)驅(qū)動(dòng)程序中分別實(shí)現(xiàn)。
poll_table 結(jié)構(gòu)是在
中聲明的,要使用poll 調(diào)用,你必須在源程序中包含
這個(gè)頭文件。需要提醒的是,你無需了解它的內(nèi)部結(jié)構(gòu),你只要調(diào)用操作該結(jié)構(gòu)的函數(shù)就行
了。當(dāng)然,如果你想了解的話,你可以自己去看源代碼。
poll部分標(biāo)志位的列表如下:
POLLIN 如果設(shè)備可以被無阻塞地讀,那么該位必須被設(shè)置。
POLLRDNORM 如果“普通”數(shù)據(jù)可以被讀,該位必須被設(shè)置。一個(gè)可讀設(shè)備返回
(POLLIN | POLLRDNORM)。
POLLOUT 如果設(shè)備可以被無阻塞地寫,則該位在返回值中被設(shè)置。
POLLWRNORM 該位與POLLOUT,有時(shí)甚至的確為同一個(gè)數(shù)。一個(gè)可寫的設(shè)備返回
(POLLOUT | POLLWRNORM)。
具體的poll實(shí)現(xiàn)代碼如下:
vfifo.c
unsigned int vfifo_poll(struct file *filp, poll_table *wait)
{
Vfifo_Dev *dev = filp->private_data;
unsigned int mask = 0;
poll_wait(filp, &dev->rdq, wait);
poll_wait(filp, &dev->wrq, wait);
if (dev->len > 0) mask |= POLLIN | POLLRDNORM; /* readable */
if (dev->len != dev->buffersize) mask |= POLLOUT | POLLWRNORM; /* writable */
return mask;
}
三、設(shè)備的安裝
采用下面的命令可以對(duì)vfifo.c進(jìn)行編譯:
#gcc –c vfifo.c –D__KERNEL__ -DMODULE –O2 –g -Wall
如果沒有出錯(cuò)的話,將會(huì)在本目錄下生成一個(gè)vfifo.o 文件。
下面的操作必須是以root身份進(jìn)行的:
先執(zhí)行module的插入操作,
#insmod vfifo.o
如果設(shè)備文件系統(tǒng)已經(jīng)應(yīng)用起來的話,此時(shí)在設(shè)備文件系統(tǒng)掛接的目錄(通常是/dev)
下,就可以找到vfifo 文件節(jié)點(diǎn)了。如果沒有應(yīng)用設(shè)備文件系統(tǒng),則需要手工為設(shè)備添加文
件節(jié)點(diǎn)。首先進(jìn)入dev目錄,再執(zhí)行如下命令:
[root Linux /dev]#mknod vfifo c 241 0
[root Linux /dev]#mknod vfifo c 241 1
……
[root Linux /dev]#mknod vfifo c 241 7
此時(shí)就可以對(duì)設(shè)備進(jìn)行讀、寫、ioctl等操作了。
當(dāng)不再需要對(duì)設(shè)備進(jìn)行操作時(shí),可以采用下面的命令卸載module:
[root Linux /dev]# rmmod vfifo
四、設(shè)備的使用
設(shè)備安裝好之后就可以使用了。你可以用cp、dd等命令以及輸入/輸出重定向機(jī)制來測(cè)
試這個(gè)驅(qū)動(dòng)程序。為了更清晰地了解程序是如何運(yùn)行的,你可以在適當(dāng)?shù)奈恢眉尤雙rintk( ),
通過它來跟蹤程序。另外,你還可以用專門的調(diào)試工具如strace來監(jiān)視程序使用的系統(tǒng)調(diào)用。
例如,你可以這樣來寫vfifo設(shè)備:
#strace ls /dev/vfifo* > /dev/vfifo0
#strace cat /dev/vfifo1
到此為止,我們已經(jīng)完成了對(duì)Linux設(shè)備驅(qū)動(dòng)的分析,并且自己設(shè)計(jì)了一個(gè)與具
體設(shè)備無關(guān)的特殊設(shè)備的驅(qū)動(dòng)程序。但還有一些我們并沒有涉及到的內(nèi)容,如ioctl、I/O 端
口等,如有興趣可以自己去深入鉆研。
(驅(qū)動(dòng)程序開發(fā))
|