鍵盤在所有的驅(qū)動之中最為簡單的一種,但它卻包含了驅(qū)動的基本框架,對以后繼續(xù)深入學(xué)習(xí)其他復(fù)雜的驅(qū)動大有裨益,以下便為你逐步剖析驅(qū)動的開發(fā)。采用的是查詢方式。一.內(nèi)核模塊的注冊和撤銷 在加載模塊的時候,首先運行的是內(nèi)核模塊的注冊函數(shù)。它的功能包括內(nèi)核注冊設(shè)備以及變量的初始化。static int head,tail;int _init Keypad_init(void){ int result; result=register_chrdev(KEY_LED_MAJOR,KEY_LED_NAME,&Keypad_fops); Keypad_clear(); init_waitqueue_head(&queue); prink("%s %s initialized.\n",KEY_LED_NAME,KEY_LED_VERSION);//不能用prinf return 0;}module_init(Keypad_init);//加載模塊void _exit Keypad_cleanup(void){ del_timer(&timer); unregister_chrdev(KEY_LED_MAJOR,KEY_LED_NAME); prink("Keypad driver removed \n");}module_exit(Keypad_cleanup);//卸載該模塊二.虛擬文件系統(tǒng)與硬件驅(qū)動的接口static struct file_operations Keypad_fops={ open:Keypad_open, read:Keypad_read, poll:Keypad_poll, fasync:Keypad_fasync, release:Keypad_release,};該接口定義完之后一些便是對這幾個具體函數(shù)的實現(xiàn)了!現(xiàn)在我們一起進(jìn)入下一步吧,是不是覺得其實沒什么難度的呢?別那么早開心著呢?這幾個函數(shù)的實現(xiàn)時候,涉及到很多技術(shù),包括內(nèi)核定時器,等待隊列的具體實現(xiàn)(阻塞方式),異步方式的具體實現(xiàn)技巧,循環(huán)隊列??吹竭@么多技術(shù)你是否感到很興奮呢?以下本人將以通俗的方式為你講解,希望你能理解。三.設(shè)備的打開操作接口函數(shù)具體實現(xiàn)(Keypad_open)設(shè)備打開一般包括兩大操作,一是完成設(shè)備的初始化,二是設(shè)備引用計數(shù)器加1static int Keypad_open(struct inode *inode,struct file *filp){ read_xy(); try_module_get(THIS_MODULE);//此函數(shù)為linux 2.6內(nèi)核增加的,不同于2.4內(nèi)核,功能是計數(shù)器的值加1 return 0;}static void read_xy(void){ new_data();//獲取鍵值函數(shù) keypad_starttimer();//開啟內(nèi)核定時器,在固定周期時間內(nèi)獲取鍵盤新的變化}以下實現(xiàn)鍵盤鍵值獲取函數(shù)read_xy()主要是從KEY_CS(對應(yīng)的讀入地址,之前可以根據(jù)具體的硬件設(shè)備定義,比如#define kEY_CS(*
(volatile unsigned short *)(0xf820000))此處應(yīng)該根據(jù)具體的不同而不同!將讀入的鍵值存入buf[]緩存中,環(huán)形緩沖的寫指針是head,讀指針是tail,前面已經(jīng)定義過了////////////////////////////////鍵盤事件的數(shù)據(jù)結(jié)構(gòu)定義/////////////////////////////////typedef struct{ ulong status;//按鍵的值 ulong click;//是否有按鍵按下,1表示有,0表示沒有}KEY_EVENT static KEY_EVENT cur_data,buf[BUFSIZE];//BUFSIZE為宏定義,用于定義環(huán)形緩沖的大小static void new_data(void){ if((KEY_CS & 0xff)!=0xff) //從KEY_CS地址讀入數(shù)據(jù),若有一個為0則表示有一個按鍵被按下了(此處硬件電路為低電平有效) { switch(KEY_CS & 0xff){ case ~KEY0 & 0xff: cur_data.status=1;///////1被按下 break; case ~KEY1 & 0xff: cur_data.status=2;//2被按下 break; /////////其他一樣添加,懂嗎?? } cur_data.click=1; } else if(KEY_CS & 0xff==0xff){ cur_data.click=0; cur_data.status=0; } if(head!=tail){////////循環(huán)隊列緩沖區(qū)的應(yīng)用在此開始了^_^ int last=head--; if(last<0)////////若已經(jīng)到了對首之前,則跳到隊尾,以實現(xiàn)循環(huán)隊列 last=BUFSIZE-1; } //////按鍵信息存入循環(huán)隊列緩沖區(qū)中 buf[head]=cur_data; if(++head==BUFSIZE) head=0; if(head==tail && tail++=BUFSIZE) tail=0; if(fasync) kill_fasync(&fasyc,SIGIO,POLL_IN); wake_up_interruptible(&queue);}
接下來我們介紹其他幾個文件接口函數(shù)的實現(xiàn)四.先介紹關(guān)閉函數(shù)keypad_release(),為什么先介紹它呢?道理很簡單,應(yīng)該它比較簡單,先讓大家做下熱身運動,在介紹完這個之后,繼續(xù)會介紹一個比較復(fù)雜的函數(shù). 關(guān)閉操作主要實現(xiàn)的是:關(guān)閉設(shè)備異步通知,設(shè)備計數(shù)器減1,刪除定時器信號中斷static int Keypad_release(struct inode *inode,struct){ Keypad_fasync(-1,filp,0); module_put(THIS_MODULE); del_timer(&timer); return 0;}五.設(shè)備讀取操作接口函數(shù)實現(xiàn)Keypad_read() 主要作用是從緩沖區(qū)讀取鍵值,通過調(diào)用get_data()實現(xiàn),通過copy_to_user()函數(shù)將鍵值復(fù)制到用戶的數(shù)據(jù)區(qū)中static ssize_t Keypad_read(struct file *filp,char *buf,ssize_t count,loff_t *l){ DECLEARE_WAITQUEUE(wait,current);//聲明等待隊列,將當(dāng)前進(jìn)程加入到等待隊列中 KEY_EVENT t; ulong out_buf[2]; if(head==tail)//當(dāng)前循環(huán)隊列中沒有數(shù)據(jù)可以讀取 { if(filp->f_flags & O_NONBLOCK)//假如用戶采用的是非堵塞方式讀取 return _EAGAIN; add_wait_queue(&queue,&wait);//將當(dāng)前進(jìn)程加入等待隊列 current->state=TASK_INTERRUPTIBLE;//設(shè)置當(dāng)前進(jìn)程的狀態(tài) while((head==tail)&&!signal_pending(current))//假若還沒有數(shù)據(jù)到循環(huán)隊列并且當(dāng)前進(jìn)程沒有受到信號 { shedule();//進(jìn)程調(diào)度 current->state=TASK_INTERRUPTIBLE; } current->state=TASK_RUNNING; remove_wait_queue(&queue,&wait); if(head==tail) return count; t=get_data();//調(diào)用get_data()函數(shù),得到緩沖區(qū)中的數(shù)據(jù),下面將給予詳細(xì)的 介紹 out_buf[0]=t.status; out_buf[1]=t.click; copy_to_user(buf,&out_buf,sizeof(out_buf));//將得到的鍵值拷貝到用戶數(shù)據(jù)區(qū) return count; }}很自然我們就應(yīng)該要介紹get_data()函數(shù)的實現(xiàn)了,該函數(shù)的功能就是從我們定義的循環(huán)隊列緩沖區(qū)中讀出我們要的鍵值,所以其實很簡單的如果理解循環(huán)隊列的原理,在此不多加解釋,大家應(yīng)該具備一般的數(shù)據(jù)結(jié)構(gòu)相關(guān)的知識吧static KEY_EVENT get_data(void){ int last=tail if(++tail==BUFSIZE) tail=0; return buf[last];}上面如果你看得懂得話,那么可以進(jìn)入下面的學(xué)習(xí)了,主要介紹的是內(nèi)核定時器的使用,利用等待隊列實現(xiàn)阻塞型I/O,poll系統(tǒng)調(diào)用,異步通知方式,介紹完之后,我將給出一個應(yīng)用實例,對于有使用過文件操作系統(tǒng)調(diào)用的來說,對我們所寫的鍵盤驅(qū)動來說,他們基本上是一樣的。廢話少說,我們馬上開始我們精彩的驅(qū)動開發(fā)!六.內(nèi)核定時器的使用 在該驅(qū)動中,我們假設(shè)對鍵盤的獲取是以0.2s為周期執(zhí)行。源代碼如下static struct timer_list timer;///////我們定義的定時器,也許你會問timer_list是什么來的,其實一看名稱就應(yīng)該就知道了,而為什么要用到list那么多定時器呢?其實在linux中還有很多相同的定義,比如說信號,我們定義的也是信號集,你可以定義該list是一個元素的,也可以是多個的。所以對于timer_list就可以這樣描述:在未來某一個特定時刻執(zhí)行某一系列特定任務(wù)的功能。下面我們還會給出內(nèi)核中timer_list的具體描述, static int Keypad_starttimer(void){ init_timer(&timer);//初始化定時器結(jié)構(gòu) timer.function=Keypad_timer;//超時服務(wù)程序 timer.expires=jiffies+20;//當(dāng)前時刻加0.2s add_timer(&timer); return 0;}///超時服務(wù)程序static void Keypad_timer(unsigned long data){ read_xy();}/////////接下來說下timer-list這個數(shù)據(jù)結(jié)構(gòu),如果你不感興趣的話可以跳過,該結(jié)構(gòu)在
include\linux\timer.h中定義struct timer_list{ struct list_head entry; unsigned long expries; spinlock_t lock; unsigned long magic; void (*function)(unsigned long); unsigner long data; struct tvec_t_base_s *base;}七.利用等待隊列實現(xiàn)阻塞型I\O 在用戶程序執(zhí)行讀操作的時候有可能尚且沒有數(shù)據(jù)可以讀取,為此需要讓read操作等待,直到有數(shù)據(jù)可以讀取,這就是阻塞型i\o,阻塞型io可以通過使用進(jìn)程休眠方法實現(xiàn)。在無數(shù)據(jù)可以讀取的時候,采用等待隊列讓進(jìn)程休眠,直到有數(shù)據(jù)到達(dá)的時候才喚醒進(jìn)程完成數(shù)據(jù)的讀操作?! ≡诒掘?qū)動中的read,若循環(huán)隊列緩沖區(qū)中沒有數(shù)據(jù),則進(jìn)程進(jìn)入休眠態(tài),定時器函數(shù)每隔0.2s讀取鍵值一次,將按鍵狀態(tài)放入緩沖并且適時喚醒進(jìn)程讀取數(shù)據(jù)?!?等待隊列的使用流程如下: 1.聲明一個等待隊列 2.把當(dāng)前進(jìn)程加入到等待隊列中 3.把進(jìn)程的狀態(tài)設(shè)置為TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE; 4.調(diào)用schedule,以讓出cpu 5.檢測所需要的資源是否可用,若是,把當(dāng)前進(jìn)程從等待隊列中刪除,否則轉(zhuǎn)3循環(huán)接下來我們在對read中有關(guān)等待隊列阻塞實現(xiàn)做具體的解釋static ssize_t Keypad_read(struct file *filp,char *buf,ssize_t count,loff_t *l){ DECLEARE_WAITQUEUE(wait,current);//聲明等待隊列,將當(dāng)前進(jìn)程加入到等待隊列中 KEY_EVENT t; ulong out_buf[2]; if(head==tail)//當(dāng)前循環(huán)隊列中沒有數(shù)據(jù)可以讀取 { if(filp->f_flags & O_NONBLOCK)//假如用戶采用的是非堵塞方式讀取 return _EAGAIN; add_wait_queue(&queue,&wait);//將當(dāng)前進(jìn)程加入等待隊列 current->state=TASK_INTERRUPTIBLE;//設(shè)置當(dāng)前進(jìn)程的狀態(tài) while((head==tail)&&!signal_pending(current))//假若還沒有數(shù)據(jù)到循環(huán)隊列并且當(dāng)前進(jìn)程沒有受到信號(該類信號具體來說是未決的休眠) { shedule();//進(jìn)程調(diào)度 current->state=TASK_INTERRUPTIBLE; } current->state=TASK_RUNNING;//該進(jìn)程恢復(fù)執(zhí)行 remove_wait_queue(&queue,&wait);//移出等待隊列 if(head==tail) return count; t=get_data();//調(diào)用get_data()函數(shù),得到緩沖區(qū)中的數(shù)據(jù),下面將給予詳細(xì)的 介紹 out_buf[0]=t.status; out_buf[1]=t.click; copy_to_user(buf,&out_buf,sizeof(out_buf));//將得到的鍵值拷貝到用戶數(shù)據(jù)區(qū) return count; }}八.poll系統(tǒng)調(diào)用操作接口函數(shù) 當(dāng)程序需要進(jìn)行對多個文件讀寫時,如果某個文件沒有準(zhǔn)備好,則系統(tǒng)就會處于讀寫阻塞的狀態(tài),這影響了其他文件的讀寫,為了避免讀寫阻塞,一般可以在應(yīng)用程序中使用poll或者select函數(shù)。當(dāng)poll函數(shù)返回時,會給出一個文件是否可讀寫的標(biāo)志,應(yīng)用程序根據(jù)不同的標(biāo)志讀寫相應(yīng)的文件,實現(xiàn)非阻塞的讀寫,poll()函數(shù)通過poll系統(tǒng)調(diào)用,調(diào)用對應(yīng)設(shè)備驅(qū)動的poll()接口函數(shù),poll返回不同的標(biāo)志,告訴主進(jìn)程文件是否可以讀寫,這些返回標(biāo)志存放在include\asm\poll.h中 標(biāo)志 含義 POLLIN 如果設(shè)備無阻塞的讀,就返回該值 POLLRDNORM 通常的數(shù)據(jù)已經(jīng)準(zhǔn)備好,可以讀了,就返回該值。通常的做法是會返回(POLLLIN|POLLRDNORA) POLLRDBAND 如果可以從設(shè)備讀出帶外數(shù)據(jù),就返回該值,它只可在linux內(nèi)核的某些網(wǎng)絡(luò)代碼中使用,通常不用在設(shè)備驅(qū)動程序中 POLLPRI 如果可以無阻塞的讀取高優(yōu)先級(帶外)數(shù)據(jù),就返回該值,返回該值會導(dǎo)致select
報告文件發(fā)生異常,以為select八帶外數(shù)據(jù)當(dāng)作異常處理POLLHUP 當(dāng)讀設(shè)備的進(jìn)程到達(dá)文件尾時,驅(qū)動程序必須返回該值,依照select的功能描述,調(diào)用select的進(jìn)程被告知進(jìn)程時可讀的。 POLLERR 如果設(shè)備發(fā)生錯誤,就返回該值。 POLLOUT 如果設(shè)備可以無阻塞地些,就返回該值 POLLWRNORM 設(shè)備已經(jīng)準(zhǔn)備好,可以寫了,就返回該值。通常地做法是(POLLOUT|POLLNORM) POLLWRBAND 于POLLRDBAND類似在本章地驅(qū)動程序中,Keypad_poll()函數(shù)在緩沖區(qū)有新數(shù)據(jù)時(當(dāng)head?。絫ail),返回一個
POLLIN|POLLRDNORM,告訴主進(jìn)程有新的
九.在設(shè)備驅(qū)動中實現(xiàn)異步通知 雖然大多數(shù)時候阻塞型和非阻塞型操作的組合及poll方法可以有效查詢設(shè)備是否可以讀寫,但是如果驅(qū)動程序能避免主動的查詢,改主動為被動的信號通知觸發(fā),則可以提高程序的效率,這也就是異步通知的目的。異步通知向進(jìn)程發(fā)送SIGIO信號,通知訪問設(shè)備的進(jìn)程,表示該設(shè)備已經(jīng)準(zhǔn)備好IO讀寫了?!≈缶褪侨绾螌崿F(xiàn)異步通知的問題了,要啟動異步通知,必須執(zhí)行兩個步驟:首先,須要制定某個作為文件的“屬主”。文件屬主的進(jìn)程ID保存在filp->f_owner中,這可以通過fcntl()系統(tǒng)調(diào)用執(zhí)行F_SETOWN命令設(shè)置。此外,用戶程序還必須曙色之設(shè)備的FASYNC標(biāo)志,以真正啟動異步通知機(jī)制。這里的FASYNC標(biāo)志也使用fcntl()設(shè)置。 在完成這兩個步驟之后,當(dāng)新數(shù)據(jù)到達(dá)時就會產(chǎn)生一個SIGNO信號,此信號發(fā)送到存放在filp->owner中的進(jìn)程?!尿?qū)動的角度看,則主要時通過調(diào)用兩個內(nèi)核提供的函數(shù)來實現(xiàn)就是了。他們分別是:int
fasync_helper()和void kill_fasync();這兩個函數(shù)定義在:include\linux\fs\fcntl.h 要實現(xiàn)異步,驅(qū)動中只要如下編寫即可static struct fasync_struct *fasync;//首先是定義一個結(jié)構(gòu)體static int Keypad_release(struct inode *inode,struct file *filp){ Keypad_fasync(-1,filp,0);//這是一個異步通知 。。。。。。。}static int Keypad_fasync(int fd,struct file *filp,int on){ int retval; retval=fasync_helper(fd,filp,on,&fasync); if(retval<0) return retval; return 0;}到此為止,鍵盤驅(qū)動已經(jīng)介紹完了,接下來就介紹下一個利用使用驅(qū)動的應(yīng)用實例了。以下程序的主體是一個條件循環(huán),每次循環(huán)執(zhí)行一次,就讀取一次鍵值。1。打開Keypad設(shè)備 #define DEV_NAME "/dev/Keypad"int fb=0;fb=open(DEV_NAME,O_RNONLY);if(!fb){ printf("Error:cannot open Keypad device.\n"); exit(1);}printf("The Keypad device was opened successfully.\n");}2.讀取鍵值unsigned long keydata[2];int input=1;while(input!=0){ if(read(fd,(char*)keydata,sizeof(keydata))==-1){ printf("Error reading the keypad data"); close(fb); exit(2); } if(keydata[0]){ switch(keydata[1]){ case 1:printf("KEYPUSED 1");//1鍵被按下 input=0;////下此循環(huán)退出 break; 。。。。。。。。。。。。。。。。。?! }}3。關(guān)閉Keypad設(shè)備close(fb);printf("Good bye Keypad"); 鍵盤驅(qū)動到此介紹完畢!!
關(guān)鍵詞: 驅(qū)動
網(wǎng)站首頁 |網(wǎng)站簡介 | 關(guān)于我們 | 廣告業(yè)務(wù) | 投稿信箱
Copyright © 2000-2020 www.sgycos.com All Rights Reserved.
中國網(wǎng)絡(luò)消費網(wǎng) 版權(quán)所有 未經(jīng)書面授權(quán) 不得復(fù)制或建立鏡像
聯(lián)系郵箱:920 891 263@qq.com