日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

一步步設(shè)計(jì)自己的驅(qū)動(dòng)程序

 todaytomo 2006-12-30
一步步設(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í)行如下命令:
[rootLinux /dev]#mknod vfifo c 241 0
[rootLinux /dev]#mknod vfifo c 241 1
……
[rootLinux /dev]#mknod vfifo c 241 7
此時(shí)就可以對(duì)設(shè)備進(jìn)行讀、寫、ioctl等操作了。

當(dāng)不再需要對(duì)設(shè)備進(jìn)行操作時(shí),可以采用下面的命令卸載module:
[rootLinux /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ā))



    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多