深入淺出Linux設備驅(qū)動編程_第1頁
深入淺出Linux設備驅(qū)動編程_第2頁
深入淺出Linux設備驅(qū)動編程_第3頁
深入淺出Linux設備驅(qū)動編程_第4頁
深入淺出Linux設備驅(qū)動編程_第5頁
已閱讀5頁,還剩53頁未讀 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領

文檔簡介

1、 五設備的阻塞與非阻塞操作阻塞操作是指,在執(zhí)行設備操作時,若不能獲得資源,則進程掛起直到滿足可操作的條件再進行操作。非阻塞操作的進程在不能進行設備操作時,并不掛起。被掛起的進程進入sleep狀態(tài),被從調(diào)度器的運行隊列移走,直到等待的條件被滿足。在Linux驅(qū)動程序中,我們可以使用等待隊列(waitqueue)來實現(xiàn)阻塞操作。waitqueue很早就作為一個基本的功能單位出現(xiàn)在Linux內(nèi)核里了,它以隊列為基礎數(shù)據(jù)結(jié)構,與進程調(diào)度機制緊密結(jié)合,能夠用于實現(xiàn)核心的異步事件通知機制。等待隊列可以用來同步對系統(tǒng)資源的訪問,上節(jié)中所講述Linux信號量在內(nèi)核中也是由等待隊列來實現(xiàn)的。下面我們重新定義設備

2、globalvar”,它可以被多個進程打開,但是每次只有當一個進程寫入了一個數(shù)據(jù)之后本進程或其它進程才可以讀取該數(shù)據(jù),否則一直阻塞。7#include#include#include#include#include#includeMODULE_LICENSE(GPL);#defineMAJOR_NUM254staticssize_tglobalvar_read(structfile*,char*,size_t,loff_t*);staticssize_tglobalvar_write(structfile*,constchar*,size_t,loff_t*);structfile_opera

3、tionsglobalvar_fops=read:globalvar_read,write:globalvar_write,;staticintglobal_var=0;staticstructsemaphoresem;staticwait_queue_head_toutq;staticintflag=0;staticint_initglobalvar_init(void)intret;ret=register_chrdev(MAJOR_NUM,globalvar”,&globalvar_fops);if(ret)printk(globalvarregisterfailure);elsepri

4、ntk(globalvarregistersuccess);init_MUTEX(&sem);init_waitqueue_head(&outq);returnret;staticvoid_exitglobalvar_exit(void)intret;ret=unregister_chrdev(MAJOR_NUM,globalvar);if(ret)printk(globalvarunregisterfailure);elseprintk(globalvarunregistersuccess);staticssize_tglobalvar_read(structfile*filp,char*b

5、uf,size_tlen,loff_t*off)if(wait_event_interruptible(outq,flag!=0)return-ERESTARTSYS;if(down_interruptible(&sem)return-ERESTARTSYS;flag=0;if(copy_to_user(buf,&global_var,sizeof(int)up(&sem);return-EFAULT;up(&sem);returnsizeof(int);staticssize_tglobalvar_write(structfile*filp,constchar*buf,size_tlen,l

6、off_t*off)if(down_interruptible(&sem)return-ERESTARTSYS;if(copy_from_user(&global_var,buf,sizeof(int)up(&sem);return-EFAULT;up(&sem);flag=1;通知數(shù)據(jù)可獲得wake_up_interruptible(&outq);returnsizeof(int);module_init(globalvar_init);module_exit(globalvar_exit);編寫兩個用戶態(tài)的程序來測試,第一個用于阻塞地讀/dev/globalvar,另一個用于寫/dev/g

7、lobalvar。只有當后一個對/dev/globalvar進行了輸入之后,前者的read才能返回。讀的程序為:#include#include#include#includemain()intfd,num;fd=open(/dev/globalvar,O_RDWR,S_IRUSR|S_IWUSR);if(fd!=-1)while(1)read(fd,&num,sizeof(int);/程序?qū)⒆枞诖苏Z句,除非有針對globalvar的輸入printf(Theglobalvaris%dn,num);寫的程序為:#include#include#include#includemain()intf

8、d,num;fd=open(/dev/globalvar,O_RDWR,S_IRUSRIS_IWUSR);if(fd!=-1)while(1)printf(Pleaseinputtheglobalvar:n);scanf(%d,&num);write(fd,&num,sizeof(int);如果輸入0,退出if(num=0)close(fd);break;else打開兩個終端,分別運行上述兩個應用程序,發(fā)現(xiàn)當在第二個終端中沒有輸入數(shù)據(jù)時,第一個終端沒有輸出(阻塞),每當我們在第二個終端中給globalvar輸入一個值,第一個終端就會輸出這個值,如下圖:關于上述例程,我們補充說一點,如果將驅(qū)動程

9、序中的read函數(shù)改為:staticssize_tglobalvar_read(structfile*filp,char*buf,size_tlen,loff_t*off)獲取信號量:可能阻塞if(down_interruptible(&sem)return-ERESTARTSYS;等待數(shù)據(jù)可獲得:可能阻塞if(wait_event_interruptible(outq,flag!=0)return-ERESTARTSYS;flag=0;臨界資源訪問即交換wait_event_interruptible(outq,flag!=0)和down_interruptible(&sem)的順序,這個驅(qū)

10、動程序?qū)⒆兊貌豢蛇\行。實際上,當兩個可能要阻塞的事件同時出現(xiàn)時,即兩個wait_event或down擺在一起的時候,將變得非常危險,死鎖的可能性很大,這個時候我們要特別留意它們的出現(xiàn)順序。當然,我們應該盡可能地避免這種情況的發(fā)生還有一個與設備阻塞與非阻塞訪問息息相關的論題,即select和poll,select和poll的本質(zhì)一樣,前者在BSDUnix中引入,后者在SystemV中引入。poll和select用于查詢設備的狀態(tài),以便用戶程序獲知是否能對設備進行非阻塞的訪問,它們都需要設備驅(qū)動程序中的poll函數(shù)支持。驅(qū)動程序中poll函數(shù)中最主要用到的一個API是poll_wait,其原型如下

11、:voidpoll_wait(structfile*filp,wait_queue_heat_t*queue,poll_table*wait);poll_wait函數(shù)所做的工作是把當前進程添加到wait參數(shù)指定的等待列表(poll_table)中。下面我們給globalvar的驅(qū)動添加一個poll函數(shù):需要說明的是,poll_wait函數(shù)并不阻塞,程序中poll_wait(filp,&outq,wait)這句話的意思并不是說一直等待outq信號量可獲得,真正的阻塞動作是上層的select/poll函數(shù)中完成的。select/poll會在一個循環(huán)中對每個需要監(jiān)聽的設備調(diào)用它們自己的poll支持函

12、數(shù)以使得當前進程被加入各個設備的等待列表。若當前沒有任何被監(jiān)聽的設備就緒,則內(nèi)核進行調(diào)度(調(diào)用schedule)讓出cpu進入阻塞狀態(tài),schedule返回時將再次循環(huán)檢測是否有操作可以進行,如此反復;否則,若有任意一個設備就緒,select/poll都立即返回。我們編寫一個用戶態(tài)應用程序來測試改寫后的驅(qū)動。程序中要用到BSDUnix中引入的select函數(shù),其原型為:intselect(intnumfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);其中readfds、writefds、exce

13、ptfds分別是被select()監(jiān)視的讀、寫和異常處理的文件描述符集合,numfds的值是需要檢查的號碼最高的文件描述符加Ltimeout參數(shù)是一個指向structtimeval類型的指針,它可以使select()在等待timeout時間后若沒有文件描述符準備好則返回。structtimeval數(shù)據(jù)結(jié)構為:structtimevalinttv_sec;/*seconds*/inttv_usec;/*microseconds*/;除此之外,我們還將使用下列API:FD_ZERO(fd_set*set)清除一個文件描述符集;FD_SE(intfd,fd_set*set)將一個文件描述符加入文件描

14、述符集中;FD_R(intfd,fd_set*set)將一個文件描述符從文件描述符集中清除;FD_ISSE(intfd,fd_set*set)判斷文件描述符是否被置位。下面的用戶態(tài)測試程序等待/dev/globalvar可讀,但是設置了5秒的等待超時,若超過5秒仍然沒有數(shù)據(jù)可讀,則輸出Nodatawithin5seconds:#include#include#include#include#include#include#includemain()intfd,num;fd_setrfds;structtimevaltv;fd=open(/dev/globalvar,O_RDWR,S_IRUSR

15、|S_IWUSR);if(fd!=-1)while(1)/查看globalvar是否有輸入FD_ZERO(&rfds);FD_SET(fd,&rfds);設置超時時間為5stv.tv_sec=5;tv.tv_usec=0;select(fd+1,&rfds,NULL,NULL,&tv);數(shù)據(jù)是否可獲得?if(FD_ISSET(fd,&rfds)read(fd,&num,sizeof(int);printf(Theglobalvaris%dn,num);輸入為0,退出if(num=0)close(fd);break;elseprintf(Nodatawithin5seconds.n);elsep

16、rintf(deviceopenfailuren);開兩個終端,分別運行程序:一個對globalvar進行寫,一個用上述程序?qū)lobalvar進行讀。當我們在寫終端給globalvar輸入一個值后,讀終端立即就能輸出該值,當我們連續(xù)5秒沒有輸入時,Nodatawithin5seconds在讀終端被輸出,如下圖:六設備驅(qū)動中的異步通知結(jié)合阻塞與非阻塞訪問、poll函數(shù)可以較好地解決設備的讀寫,但是如果有了異步通知就更方便了。異步通知的意思是:一旦設備就緒,則主動通知應用程序,這樣應用程序根本就不需要查詢設備狀態(tài),這一點非常類似于硬件上中斷地概念,比較準確的稱謂是信號驅(qū)動(SIGIO)的異步I/

17、O”。我們先來看一個使用信號驅(qū)動的例子,它通過signal(SIGIO,input_handler)對STDIN_FILENO啟動信號機制,輸入可獲得時input_handler被調(diào)用,其源代碼如下:I#include#include#include#include#include#include#defineMAX_LEN100voidinput_handler(intnum)chardataMAX_LEN;intlen;讀取并輸出STDIN_FILENO上的輸入len=read(STDIN_FILENO,&data,MAX_LEN);datalen=0;printf(inputavaila

18、ble:%sn,data);main()intoflags;啟動信號驅(qū)動機制signal(SIGIO,input_handler);fcntl(STDIN_FILENO,F_SETOWN,getpid();oflags=fcntl(STDIN_FILENO,F_GETFL);fcntl(STDIN_FILENO,F_SETFL,oflagsIFASYNC);最后進入一個死循環(huán),程序什么都不干了,只有信號能激發(fā)input_handler的運行如果程序中沒有這個死循環(huán),會立即執(zhí)行完畢while(1);程序的運行效果如下圖:為了使設備支持該機制,我們需要在驅(qū)動程序中實現(xiàn)fasync()函數(shù),并在wr

19、ite()函數(shù)中當數(shù)據(jù)被寫入時,調(diào)用kill_fasync()函數(shù)激發(fā)一個信號,此部分工作留給讀者來完成。七設備驅(qū)動中的中斷處理與Linux設備驅(qū)動中中斷處理相關的首先是申請與釋放IRQ的APIrequest_irq()和free_irq(),request_irq()的原型為:一intrequest_irq(unsignedintirq,void(*handler)(intirq,void*dev_id,structpt_regs*regs),unsignedlongirqflags,constchar*devname,void*dev_id);irq是要申請的硬件中斷號;handler是向

20、系統(tǒng)登記的中斷處理函數(shù),是一個回調(diào)函數(shù),中斷發(fā)生時,系統(tǒng)調(diào)用這個函數(shù),dev_id參數(shù)將被傳遞;irqflags是中斷處理的屬性,若設置SA_INTERRUPT,標明中斷處理程序是快速處理程序,快速處理程序被調(diào)用時屏蔽所有中斷,慢速處理程序不屏蔽;若設置SA_SHIRQ,則多個設備共享中斷,dev_id在中斷共享時會用到,一般設置為這個設備的device結(jié)構本身或者NULL。free_irq()的原型為:voidfree_irq(unsignedintirq,void*dev_id);另外,與Linux中斷息息相關的一個重要概念是Linux中斷分為兩個半部:上半部(tophalf)和下半部(b

21、ottomhalf)。上半部的功能是登記中斷,當一個中斷發(fā)生時,它進行相應地硬件讀寫后就把中斷例程的下半部掛到該設備的下半部執(zhí)行隊列中去。因此,上半部執(zhí)行的速度就會很快,可以服務更多的中斷請求。但是,僅有登記中斷是遠遠不夠的,因為中斷的事件可能很復雜。因此,Linux引入了一個下半部,來完成中斷事件的絕大多數(shù)使命。下半部和上半部最大的不同是下半部是可中斷的,而上半部是不可中斷的,下半部幾乎做了中斷處理程序所有的事情,而且可以被新的中斷打斷!下半部則相對來說并不是非常緊急的,通常還是比較耗時的,因此由系統(tǒng)自行安排運行時機,不在中斷服務上下文中執(zhí)行。Linux實現(xiàn)下半部的機制主要有tasklet和

22、工作隊列。tasklet基于Linuxsoftirq,其使用相當簡單,我們只需要定義tasklet及其處理函數(shù)并將二者關聯(lián):voidmy_tasklet_func(unsignedlong);定義一個處理函數(shù):DECLARE_TASKLET(my_tasklet,my_tasklet_func,data);/定義一個tasklet結(jié)構my_tasklet,與my_tasklet_func(data)函數(shù)相關聯(lián)然后,在需要調(diào)度tasklet的時候引用一個簡單的API就能使系統(tǒng)在適當?shù)臅r候進行調(diào)度運行:tasklet_schedule(&my_tasklet);此外,Linux還提供了另外一些其它

23、的控制tasklet調(diào)度與運行的API:DECLARE_TASKLET_DISABLED(name,function,data);與DECLARE_TASKLET類似,但等待tasklet被使能tasklet_enable(structtasklet_struct*);使能tasklettasklet_disble(structtasklet_struct*);禁用tasklettasklet_init(structtasklet_struct*,void(*func)(unsignedlong),unsignedlong);類似DECLARE_TASKLET()tasklet_kill(st

24、ructtasklet_struct*);/清除指定tasklet的可調(diào)度位,即不允許調(diào)度該tasklet我們先來看一個tasklet的運行實例,這個實例沒有任何實際意義,僅僅為了演示。它的功能是:在globalvar被寫入一次后,就調(diào)度一個tasklet,函數(shù)中輸出taskletisexecuting:J#include定義與綁定tasklet函數(shù)voidtest_tasklet_action(unsignedlongt);DECLARE_TASKLET(test_tasklet,test_tasklet_action,0);voidtest_tasklet_action(unsignedl

25、ongt)printk(taskletisexecuting);ssize_tglobalvar_write(structfile*filp,constchar*buf,size_tlen,loff_t*off)if(copy_from_user(&global_var,buf,sizeof(int)return-EFAULT;調(diào)度tasklet執(zhí)行tasklet_schedule(&test_tasklet);returnsizeof(int);由于中斷與真實的硬件息息相關,脫離硬件而空談中斷是毫無意義的,我們還是來舉一個簡單的例子。這個例子來源于SAMSUNGS3c2410嵌入式系統(tǒng)實例,

26、看看其中實時鐘的驅(qū)動中與中斷相關的部分:staticstructfasync_struct*rtc_async_queue;staticint_initrtc_init(void)misc_register(&rtc_dev);create_proc_read_entry(driver/rtc,0,0,rtc_read_proc,NULL);#ifRTC_IRQif(rtc_has_irq=0)gotono_irq2;init_timer(&rtc_irq_timer);rtc_irq_timer.function=rtc_dropped_irq;spin_lock_irq(&rtc_lock

27、);/*Initializeperiodicfreq.toCMOSresetdefault,whichis1024Hz*/0 x06),CMOS_WRITE(CMOS_READ(RTC_FREQ_SELECT)&0 xF0)|RTC_FREQ_SELECT);0 x06),spin_unlock_irq(&rtc_lock);rtc_freq=1024;no_irq2:#endifprintk(KERN_INFORealTimeClockDrivervRTC_VERSIONn);return0;staticvoid_exitrtc_exit(void)remove_proc_entry(dri

28、ver/rtc,NULL);misc_deregister(&rtc_dev);release_region(RTC_PORT(0),RTC_IO_EXTENT);if(rtc_has_irq)free_irq(RTC_IRQ,NULL);staticvoidrtc_interrupt(intirq,void*dev_id,structpt_regs*regs)/*Canbeanalarminterrupt,updatecompleteinterrupt,oraperiodicinterrupt.Westorethestatusinthelowbyteandthenumberofinterru

29、ptsreceivedsincethelastreadintheremainderofrtc_irq_data./spin_lock(&rtc_lock);rtc_irq_data+=0 x100;rtc_irq_data&=0 xff;rtc_irq_data1=(CMOS_READ(RTC_INTR_FLAGS)&0 xF0);if(rtc_status&RTC_TIMER_ON)mod_timer(&rtc_irq_timer,jiffies+HZ/rtc_freq+2*HZ/100);spin_unlock(&rtc_lock);/*Nowdotherestoftheactions*/

30、wake_up_interruptible(&rtc_wait);kill_fasync(&rtc_async_queue,SIGIO,POLL_IN);staticintrtc_fasync(intfd,structfile*filp,inton)returnfasync_helper(fd,filp,on,&rtc_async_queue);staticvoidrtc_dropped_irq(unsignedlongdata)unsignedlongfreq;spin_lock_irq(&rtc_lock);/*Justincasesomeonedisabledthetimerfrombe

31、hindourback.*/if(rtc_status&RTC_TIMER_ON)mod_timer(&rtc_irq_timer,jiffies+HZ/rtc_freq+2*HZ/100);rtc_irq_data+=(rtc_freq/HZ)data、function賦值;(3)調(diào)用add_timer將timer加入列表;(4)在定時器到期時,function被執(zhí)行;(5)在程序中涉及timer控制的地方適當?shù)卣{(diào)用del_timer、mod_timer刪除timer或修改timer的expires。我們可以參考driverscharkeyboard.c中鍵盤的驅(qū)動中關于timer的部分:s

32、taticvoidkbd_processkeycode(unsignedcharkeycode,charup_flag,intautorepeat)charraw_mode=(kbd-kbdmode=VC_RAW);if(up_flag)rep=0;if(!test_and_clear_bit(keycode,key_down)up_flag=kbd_unexpected_up(keycode);elserep=test_and_set_bit(keycode,key_down);/*Ifthekeyboardautorepeatedforus,ignoreit.*Wedoourownauto

33、repeatprocessing.*/if(rep&!autorepeat)return;if(kbd_repeatkeycode=keycode|!up_flag|raw_mode)kbd_repeatkeycode=-1;del_timer(&key_autorepeat_timer);/*Calculatethenexttimewhenwehavetodosomeautorepeatprocessing.Notethatwedonotdoautorepeatprocessingwhileinrawmodebutwedodoautorepeatprocessinginmediumrawmo

34、de.*/if(!up_flag&!raw_mode)kbd_repeatkeycode=keycode;if(vc_kbd_mode(kbd,VC_REPEAT)if(rep)key_autorepeat_timer.expires=jiffies+kbd_repeatinterval;elsekey_autorepeat_timer.expires=jiffies+kbd_repeattimeout;add_timer(&key_autorepeat_timer);九.內(nèi)存與I/O操作對于提供了MMU(存儲管理器,輔助操作系統(tǒng)進行內(nèi)存管理,提供虛實地址轉(zhuǎn)換等硬件支持)的處理器而言,Linu

35、x提供了復雜的存儲管理系統(tǒng),使得進程所能訪問的內(nèi)存達到4GB。進程的4GB內(nèi)存空間被人為的分為兩個部分-用戶空間與內(nèi)核空間。用戶空間地址分布從0到3GB任人6_0尸尸5工在0 x86中它等于0 xC0000000),3GB到4GB為內(nèi)核空間,如下圖:內(nèi)核空間中,從3G到vmalloc_start這段地址是物理內(nèi)存映射區(qū)域(該區(qū)域中包含了內(nèi)核鏡像、物理頁框表mem_map等等),比如我們使用的VMware虛擬系統(tǒng)內(nèi)存是160M,那么3G3G+160M這片內(nèi)存就應該映射物理內(nèi)存。在物理內(nèi)存映射區(qū)之后,就是vmalloc區(qū)域。對于160M的系統(tǒng)而言,vmalloc_start位置應在3G+160M附

36、近(在物理內(nèi)存映射區(qū)與vmalloc_start期間還存在一個8M的gap來防止躍界),vmalloc_end的位置接近4G(最后位置系統(tǒng)會保留一片128k大小的區(qū)域用于專用頁面映射),如下圖:其他內(nèi)核數(shù)據(jù)內(nèi)核Tasks實際的其他內(nèi)核數(shù)據(jù)內(nèi)核TasksRe:dlTiskzSpace實際內(nèi)移空間遇輯地址kmalloc和get_free_page申請的內(nèi)存位于物理內(nèi)存映射區(qū)域,而且在物理上也是連續(xù)的,它們與真實的物理地址只有一個固定的偏移,因此存在較簡單的轉(zhuǎn)換關系,virt_to_phys()可以實現(xiàn)內(nèi)核虛擬地址轉(zhuǎn)化為物理地址:#define_pa(x)(unsignedlong)(x)_OFFS

37、ET)externinlineunsignedlongvirt_to_phys(volatilevoid*address)return_pa(address);上面轉(zhuǎn)換過程是將虛擬地址減去3G(PAGE_OFFSET=0XC000000)。與之對應的函數(shù)為phys_to_virt(),將內(nèi)核物理地址轉(zhuǎn)化為虛擬地址:#defineva(x)(void*)(unsignedlong)(x)+PAGE_OFFSET)externinlinevoid*phys_to_virt(unsignedlongaddress)return_va(address);virt_to_phys(Dphys_to_vi

38、rt()都定義在includeasm-i386io.h中。而vmalloc申請的內(nèi)存則位于vmalloc_startvmalloc_end之間,與物理地址沒有簡單的轉(zhuǎn)換關系,雖然在邏輯上它們也是連續(xù)的,但是在物理上它們不要求連續(xù)。我們用下面的程序來演示kmalloc、get_free_page和vmalloc的區(qū)別:#include#include#includeMODULE_LICENSE(GPL);unsignedchar*pagemem;unsignedchar*kmallocmem;unsignedchar*vmallocmem;int_initmem_module_init(void

39、)最好每次內(nèi)存申請都檢查申請是否成功下面這段僅僅作為演示的代碼沒有檢查pagemem=(unsignedchar*)get_free_page(0);printk(pagememaddr=%x,pagemem);kmallocmem=(unsignedchar*)kmalloc(100,0);printk(kma110cmemaddr=%x,kmallocmem);vmallocmem=(unsignedchar*)vmalloc(1000000);printk(vma110cmemaddr=%x,vmallocmem);return0;void_exitmem_module_exit(voi

40、d)free_page(pagemem);kfree(kmallocmem);vfree(vmallocmem);module_init(mem_module_init);module_exit(mem_module_exit);我們的系統(tǒng)上有160MB的內(nèi)存空間,運行一次上述程序,發(fā)現(xiàn)pagemem的地址在0 xc7997000(約3G+121M)、kmallocmem地址在0 xc9bc1380(約3G+155M)、vmallocmem的地址在0 xcabeb000(約3G+171M)處,符合前文所述的內(nèi)存布局。接下來,我們討論Linux設備驅(qū)動究竟怎樣訪問外設的I/O端口(寄存器)。幾乎

41、每一種外設都是通過讀寫設備上的寄存器來進行的,通常包括控制寄存器、狀態(tài)寄存器和數(shù)據(jù)寄存器三大類,外設的寄存器通常被連續(xù)地編址。根據(jù)CPU體系結(jié)構的不同,CPU對IO端口的編址方式有兩種:(1)I/O映射方式(I/O-mapped)典型地,如X86處理器為外設專門實現(xiàn)了一個單獨的地址空間,稱為I/O地址空間或者I/O端口空間,CPU通過專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元。2)內(nèi)存映射方式(Memory-mapped)RISC指令系統(tǒng)的CPU(如ARM、PowerPC等)通常只實現(xiàn)一個物理地址空間,外設I/O端口成為內(nèi)存的一部分。此時,CPU可以象訪問一個內(nèi)存

42、單元那樣訪問外設I/O端口,而不需要設立專門的外設I/O指令。但是,這兩者在硬件實現(xiàn)上的差異對于軟件來說是完全透明的,驅(qū)動程序開發(fā)人員可以將內(nèi)存映射方式的I/O端口和外設內(nèi)存統(tǒng)一看作是I/O內(nèi)存資源。一般來說,在系統(tǒng)運行時,外設的I/O內(nèi)存資源的物理地址是已知的,由硬件的設計決定。但是CPU通常并沒有為這些已知的外設I/O內(nèi)存資源的物理地址預定義虛擬地址范圍,驅(qū)動程序并不能直接通過物理地址訪問I/O內(nèi)存資源,而必須將它們映射到核心虛地址空間內(nèi)(通過頁表),然后才能根據(jù)映射所得到的核心虛地址范圍,通過訪內(nèi)指令訪問這些I/O內(nèi)存資源。Linux在io.h頭文件中聲明了函數(shù)ioremap(),用來將

43、I/O內(nèi)存資源的物理地址映射到核心虛地址空間(3GB4GB)中,原型如下:void*ioremap(unsignedlongphys_addr,unsignedlongsize,unsignedlongflags);iounmap函數(shù)用于取消ioremap()所做的映射,原型如下:voidiounmap(void*addr);這兩個函數(shù)都是實現(xiàn)在mm/ioremap.c文件中。在將I/O內(nèi)存資源的物理地址映射成核心虛地址后,理論上講我們就可以象讀寫RAM那樣直接讀寫I/O內(nèi)存資源了。為了保證驅(qū)動程序的跨平臺的可移植性,我們應該使用Linux中特定的函數(shù)來訪問I/O內(nèi)存資源,而不應該通過指向核心

44、虛地址的指針來訪問。如在x86平臺上,讀寫I/O的函數(shù)如下所示:#definereadb(addr)(*(volatileunsignedchar*)_io_virt(addr)#definereadw(addr)(*(volatileunsignedshort*)_io_virt(addr)#definereadl(addr)(*(volatileunsignedint*)_io_virt(addr)#definewriteb(b,addr)(*(volatileunsignedchar*)_io_virt(addr)=(b)#definewritew(b,addr)(*(volatileu

45、nsignedshort*)_io_virt(addr)=(b)#definewritel(b,addr)(*(volatileunsignedint*)_io_virt(addr)=(b)#definememset_io(a,b,c)memset(_io_virt(a),(b),(c)#definememcpy_fromio(a,b,c)memcpy(a),_io_virt(b),(c)#definememcpy_toio(a,b,c)memcpy(_io_virt(a),(b),(c)最后,我們要特別強調(diào)驅(qū)動程序中mmap函數(shù)的實現(xiàn)方法。用mmap映射一個設備,意味著使用戶空間的一段地址關

46、聯(lián)到設備內(nèi)存上,這使得只要程序在分配的地址范圍內(nèi)進行讀取或者寫入,實際上就是對設備的訪問。筆者在Linux源代碼中進行包含ioremap文本的搜索,發(fā)現(xiàn)真正出現(xiàn)的ioremap的地方相當少。所以筆者追根索源地尋找I/O操作的物理地址轉(zhuǎn)換到虛擬地址的真實所在,發(fā)現(xiàn)Linux有替代ioremap的語句,但是這個轉(zhuǎn)換過程卻是不可或缺的。譬如我們再次摘取S3c2410這個ARM芯片RTC(實時鐘)驅(qū)動中的一小段:staticvoidget_rtc_time(intalm,structrtc_time*rtc_tm)spin_lock_irq(&rtc_lock);if(alm=1)rtc_tm-tm_

47、year=(unsignedchar)ALMYEAR&Msk_RTCYEAR;rtc_tm-tm_mon=(unsignedchar)ALMMON&Msk_RTCMON;rtc_tm-tm_mday=(unsignedchar)ALMDAY&Msk_RTCDAY;rtc_tm-tm_hour=(unsignedchar)ALMHOUR&Msk_RTCHOUR;rtc_tm-tm_min=(unsignedchar)ALMMIN&Msk_RTCMIN;rtc_tm-tm_sec=(unsignedchar)ALMSEC&Msk_RTCSEC;elseread_rtc_bcd_time:rtc_t

48、m-tm_year=(unsignedchar)BCDYEAR&Msk_RTCYEAR;rtc_tm-tm_mon=(unsignedchar)BCDMON&Msk_RTCMON;rtc_tm-tm_mday=(unsignedchar)BCDDAY&Msk_RTCDAY;rtc_tm-tm_hour=(unsignedchar)BCDHOUR&Msk_RTCHOUR;rtc_tm-tm_min=(unsignedchar)BCDMIN&Msk_RTCMIN;rtc_tm-tm_sec=(unsignedchar)BCDSEC&Msk_RTCSEC;if(rtc_tm-tm_sec=0)/*R

49、e-readallBCDregistersincaseofBCDSECis0.SeeRTCsectionatthemanualformoreinfo.*/gotoread_rtc_bcd_time;spin_unlock_irq(&rtc_lock);BCD_TO_BIN(rtc_tm-tm_year);BCD_TO_BIN(rtc_tm-tm_mon);BCD_TO_BIN(rtc_tm-tm_mday);BCD_TO_BIN(rtc_tm-tm_hour);BCD_TO_BIN(rtc_tm-tm_min);BCD_TO_BIN(rtc_tm-tm_sec);/*Theepochoftm_

50、yearis1900*/rtc_tm-tm_year+=RTC_LEAP_YEAR-1900;/*tm_monstartsat0,butrtcmonthstartsat1*/rtc_tm-tm_mon-;I/O操作似乎就是對ALMYEAR、ALMMON、ALMDAY定義的寄存器進行操作,那這些宏究竟定義為什么呢?#defineALMDAYbRTC(0 x60)#defineALMMONbRTC(0 x64)#defineALMYEARbRTC(0 x68)其中借助了宏bRTC,這個宏定義為:#definebRTC(Nb)_REG(0 x57000000+(Nb)其中又借助了宏REG,而REG又

51、定義為:#define_REG(x)io_p2v(x)最后的io_p2V才是真正玩虛擬地址和物理地址轉(zhuǎn)換的地方:#defineio_p2V(x)(x)I0 xa0000000)與_REG對應的有個_PREG:#define_PREG(x)io_v2p(x)與io_p2v對應的有個io_v2p:#defineio_v2P(x)(x)&0 xa0000000)可見有沒有出現(xiàn)ioremap是次要的,關鍵問題是有無虛擬地址和物理地址的轉(zhuǎn)換!下面的程序在啟動的時候保留一段內(nèi)存,然后使用ioremap將它映射到內(nèi)核虛擬空間,同時又用remap_page_range映射到用戶虛擬空間,這樣一來,內(nèi)核和用戶都

52、能訪問。如果在內(nèi)核虛擬地址將這段內(nèi)存初始化串a(chǎn)bcd,那么在用戶虛擬地址能夠讀出來:/*mmapioremap.c*/#include#include#include#include#include/*formem_map_(un)reserve*/#include/*forvirt_to_phys*/#include/*forkmallocandkfree*/MODULE_PARM(mem_start,i);MODULE_PARM(mem_size,i);staticintmem_start=101,mem_size=10;staticchar*reserve_virt_addr;stati

53、cintmajor;intmmapdrv_open(structinode*inode,structfile*file);intmmapdrv_release(structinode*inode,structfile*file);intmmapdrv_mmap(structfile*file,structvm_area_struct*vma);staticstructfile_operationsmmapdrv_fops=owner:THIS_MODULE,mmap:mmapdrv_mmap,open:mmapdrv_open,release:mmapdrv_release,;intinit_

54、module(void)if(major=register_chrdev(0,mmapdrv,&mmapdrv_fops)0)printk(mmapdrv:unabletoregistercharacterdevicen);return(-EIO);printk(mmapdevicemajor=%dn,major);printk(highmemoryphysicaladdress0 x%ldMn,virt_to_phys(high_memory)/1024/1024);reserve_virt_addr=ioremap(mem_start*1024*1024,mem_size*1024*102

55、4);printk(reserve_virt_addr=0 x%lxn,(unsignedlong)reserve_virt_addr);if(reserve_virt_addr)inti;for(i=0;ivm_pgoffvm_end-vma-vm_start;if(sizemem_size*1024*1024)printk(sizetoobign);return(-ENXIO);offset=offset+mem_start*1024*1024;/*wedonotwanttohavethisareaswappedout,lockit*/vma-vm_flags1=VM_LOCKED;if(

56、remap_page_range(vma,vma-vm_start,offset,size,PAGE_SHARED)printk(remappagerangefailedn);return-ENXIO;return(0);remap_page_range函數(shù)的功能是構造用于映射一段物理地址的新頁表,實現(xiàn)了內(nèi)核空間與用戶空間的映射,其原型如下:intremap_page_range(vma_area_struct*vma,unsignedlongfrom,unsignedlongto,unsignedlongsize,pgprot_tprot);使用mmap最典型的例子是顯示卡的驅(qū)動,將顯存空間

57、直接從內(nèi)核映射到用戶空間將可提供顯存的讀寫效率。十結(jié)構化設備驅(qū)動程序在19節(jié)關于設備驅(qū)動的例子中,我們沒有考慮設備驅(qū)動程序的結(jié)構組織問題。實際上,Linux設備驅(qū)動的開發(fā)者習慣于一套約定俗成的數(shù)據(jù)結(jié)構組織方法和程序框架。設備結(jié)構體Linux設備驅(qū)動程序的編寫者喜歡把與某設備相關的所有內(nèi)容定義為一個設備結(jié)構體,其中包括設備驅(qū)動涉及的硬件資源、全局軟件資源、控制(自旋鎖、互斥鎖、等待隊列、定時器等),在涉及設備的操作時,僅僅操作這個結(jié)構體就可以了。對于globalvar設備,這個結(jié)構體就是:structglobalvar_devintglobal_var=0;structsemaphoresem;

58、wait_queue_head_toutq;intflag=0;open(release。一般來說,較規(guī)范的open()通常需要完成下列工作:檢查設備相關錯誤,如設備尚未準備好等;如果是第一次打開,則初始化硬件設備;識別次設備號,如果有必要則更新讀寫操作的當前位置指針f_ops;分配和填寫要放在file-private_data里的數(shù)據(jù)結(jié)構;使用計數(shù)增1。release()的作用正好與open()相反,通常要完成下列工作:使用計數(shù)減1;釋放在file-private_data中分配的內(nèi)存;如果使用計算為0,則關閉設備。我們使用LDD2中scull_u的例子:intscull_u_open(st

59、ructinode*inode,structfile*filp)Scull_Dev*dev=&scull_u_device;/*deviceinformation*/intnum=NUM(inode-i_rdev);if(!filp-private_data&num0)return-ENODEV;/*notdevfs:allow1deviceonly*/spin_lock(&scull_u_lock);if(scull_u_count&(scull_u_owner!=current-uid)&/*allowuser*/(scull_u_owner!=current-euid)&/*alloww

60、hoeverdidsu*/!capable(CAP_DAC_OVERRIDE)/*stillallowroot*/spin_unlock(&scull_u_lock);return-EBUSY;/*-EPERMwouldconfusetheuser*/if(scull_u_count=0)scull_u_owner=current-uid;/*grabit*/scull_u_count+;spin_unlock(&scull_u_lock);/*then,everythingelseiscopiedfromthebaresculldevice*/if(filp-f_flags&O_ACCMOD

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論