版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、Linux設備驅動程序-簡單字符模塊這一章主要通過介紹字符設備的驅動程序編寫,來學習Linux設備驅動的基本知識。Globalmem可以為真正的設備驅動程序提供樣板。一、主設備號和次設備號主設備號表示設備對應的驅動程序;次設備號由內核使用,用于正確確定設備文件所指的設備。主設備號相同的設備使用相同的驅動程序,次設備號用于區(qū)分具體設備的實例。比如PC機中的IDE設備,一般主設備號使用3,WINDOWS進行的分區(qū),一般將主分區(qū)的次設備號為1,擴展分區(qū)的次設備號為2、3、4,邏輯分區(qū)使用5、6.。內核用dev_t類型(<linux/types.h>)來保存設備編號,dev_t是一個32位
2、的數,12位表示主設備號,20位表示次設備號。在實際使用中,是通過<linux/kdev_t.h>中定義的宏來轉換格式。(dev_t)->主設備號、次設備號MAJOR(dev_tdev)MINOR(dev_tdev)主設備號、次設備號->(dev_t)MKDEV(intmajor,intminor)對于查看/dev目錄下的設備的主次設備號可以使用如下命令:/mnt/yaffsls/dev-lcrw1rootroot5,1Jan100:00consolecrw1rootroot5,64Jan100:00cua0crw1rootroot5,65Jan100:00cua1cr
3、w-rw-rw-1rootroot1,7Jan100:00fulldrwxr-xr-x1rootroot0Jan100:00keyboardcrw-r1rootroot1,2Jan100:00kmemcrw-r1rootroot1,1Jan100:00memdrwxr-xr-x1rootroot0Jan100:00mtddrwxr-xr-x1rootroot0Jan100:00mtdblockcrw-rw-rw-1rootroot1,3Jan100:00nullcrw-r1rootroot1,4Jan100:00portcrw1rootroot108,0Jan100:00pppcrw-rw-r
4、w-1rootroot5,2Jan100:00ptmxcrw-r-r-1rootroot1,8Jan100:00randomlr-xr-xr-x1rootroot4Jan100:00root->rd/0crw-rw-rw-1rootroot5,0Jan100:00ttycrw1rootroot4,64Jan100:11ttyS0crw1rootroot4,65Jan100:00ttyS1crw-r-r-1rootroot1,9Jan100:00urandomcrw-rw-rw-1 rootroot1,5 Jan 1 00:00 zero建立一個字符設備之前,驅動程序首先要做的事情就是獲得
5、設備編號。其這主要函數在<linux/fs.h>中聲明:intregister_chrdev_region(dev_tfirst,unsignedintcount,char*name);/靜態(tài)指定設備編號intalloc_chrdev_region(dev_t*dev,unsignedintfirstminor,unsignedintcount,char*name);/動態(tài)生成設備編號voidunregister_chrdev_region(dev_tfirst,unsignedintcount);/釋放設備編號分配之設備號的最佳方式是:默認采用動態(tài)分配,同時保留在加載甚至是編譯時
6、指定主設備號的余地。以下是在globalmeml.c中用來獲取主設備好的代碼:dev_tdevno=MKDEV(globalmem_major,0);/*申請設備號*/if(globalmem_major)result=register_chrdev_region(devno,1,"globalmem");else/*動態(tài)申請設備號*/result=alloc_chrdev_region(&devno,0,1,"globalmem");globalmem_major=MAJOR(devno);if(result<0)returnresult
7、;name是和該編號范圍file_operations在這部分中,比較重要的是在用函數獲取設備編號后,其中的參數中。關聯的設備名稱,它將出現在/proc/devices二、一些重要的數據結構大部分基本的驅動程序操作涉及及到三個重要的內核數據結構,分別是file和inode,它們的定義都在<linux/fs.h>。三、字符設備的注冊內核內部使用structcdev結構來表示字符設備。在內核調用設備的操作之前,必須分配并注冊一個或多個structcdev。代碼應包含<linux/cdev.h>,它定義了structcdev以及與其相關的一些輔助函數。我們一般將cdev結構嵌
8、入到自己的設備特定結構中去如:structglobalmem_devstructcdevcdev;/*cdev結構體*/unsignedcharmemGLOBALMEM_SIZE;/*全局內存*/;注冊一個獨立的cdev設備的基本過程如下:1、為自己的設備結構體分配空間structglobalmem_dev*globalmem_devp;/*設備結構體指針*/globalmem_devp=kmalloc(sizeof(structglobalmem_dev),GFP_KERNEL);if(!globalmem_devp)/*申請失敗*/result =-ENOMEM;gotofail_mall
9、oc;memset(globalmem_devp,0,sizeof(structglobalmem_dev);2、初始化structcdevvoidcdev_init(structcdev*cdev,conststructfile_operations*fops)cdev.owner=THIS_MODULE;4、cdev設置完成,通知內核structcdev的信息(在執(zhí)行這步之前必'須確定你對structcdev的以上設置已經完成!)intcdev_add(structcdev*p,dev_tdev,unsignedcount)5、從系統(tǒng)中移除一個字符設備:voidcdev_del(s
10、tructcdev*p)以下globalmem的初始化代碼/*初始化并注冊cdev*/staticvoidglobalmem_setup_cdev(structglobalmem_dev*dev,intindex)interr,devno=MKDEV(globalmem_major,index);cdev_init(&dev->cdev,&globalmem_fops);dev->cdev.owner=THIS_MODULE;dev->cdev.ops=&globalmem_fops;err=cdev_add(&dev->cdev,dev
11、no,1);if(err)printk(KERN_NOTICE"Error%daddingglobalmem%d",err,index);/*設備驅動模塊加載函數*/intglobalmem_init(void)intresult;dev_tdevno=MKDEV(globalmem_major,0);/*申請設備號*/if(globalmem_major)result=register_chrdev_region(devno,1,"globalmem");else/*動態(tài)申請設備號*/result=alloc_chrdev_region(&de
12、vno,0,1,"globalmem");globalmem_major=MAJOR(devno);if(result<0)returnresult;/*動態(tài)申請設備結構體的內存*/globalmem_devp=kmalloc(sizeof(structglobalmem_dev),GFP_KERNEL);if(!globalmem_devp)/*申請失敗*/result=-ENOMEM;gotofail_malloc;memset(globalmem_devp,0,sizeof(structglobalmem_dev);globalmem_setup_cdev(gl
13、obalmem_devp,0);return0;fail_malloc:unregister_chrdev_region(devno,1);returnresult;四、內存申請函數Globalmem驅動程序引入了兩個Linux內核中用于內存管理的核心函數,它們的定義都在<linux/slab.h>:void*kmalloc(size_tsize,intflags);voidkfree(void*ptr);例子:#include<linux/slab.h>char*buff;buff=kmalloc(1024,GFP_KERNEL);if(buff!=NULL)kfre
14、e(buff);elseprintk("kmallocerrorn");flags的參數GFP_KERNEL請求動態(tài)內存總是分配成功,如無則等待。故不能用在中斷中。GFP_ATOMIC無條件分配內存,沒有立即釋放,進程不睡眠。GFP_DMA用于分配連續(xù)的物理內存五、open和release、open方法提供給驅動程序以初始化的能力,為以后的操作作準備。應完成的工作如下:(1)檢查設備特定的錯誤(如設備未就緒或硬件問題);(2)如果設備是首次打開,則對其進行初始化;(3)如有必要,更新f_op文件指針;(4)分配并填寫置于filp->private_data里的數據結構
15、。而根據globalmem的實際情況,他的open函數只要完成第四步(將初始化過的structglobalmem_devdev的指針傳遞到filp->private_data里,以備后用)就好了,所以open函數很簡單。但是其中用到了定義在<linux/kernel.h>中的container_of宏,源碼如下:#definecontainer_of(ptr,type,member)(consttypeof(type*)0)->member)*._mptr=(ptr);(type*)(char*)_mptr-offsetof(type,member);)其實從源碼可以看
16、出,其作用就是:通過指針ptr,獲得包含ptr所指向數據(是member結構體)的type結構體的指針。即是用指針得到另外一個指針。intglobalmem_open(structinode*inode,structfile*filp)/*將設備結構體指針賦值給文件私有數據指針*/structglobalmem_dev*dev;dev=container_of(inode->i_cdev,structglobalmem_dev,cdev);filp->private_data=dev;return0;第一個參數是結構體成員的指針,第二個參數為整個結構體的類型,第三個參數為傳入的第一
17、個參數即結構體成員的類型,返回值為整個結構體的指針。、release方法提供釋放內存,關閉設備的功能。應完成的工作如下:(1)釋放由open分配的、保存在file->private_data中的所有內容;(2)在最后一次關閉操作時關閉設備。有時執(zhí)行的內容和exit里面的內容重合,所以本代碼有的時候什么也不做。六、read和writeread和write方法的主要作用就是實現內核與用戶空間之間的數據拷貝。因為Linux的內核空間和用戶空間隔離的,所以要實現數據拷貝就必須使用在<asm/uaccess.h>中定義的:read方法完成將數據從內核拷貝到應用程序空間,write方法相
18、反,將數據從應用程序空間拷貝到內核。對于這兩個方法,參數filp是文件指針,count是請求傳輸數據的長度,buffer是用戶空間的數據緩沖區(qū),ppos(有時候寫成fpos都是一個概念)是文件中進行操作的偏移量,類型為64位數。由于用戶空間和內核空間的內存映射方式完全不同,所以不能使用象memcpy之類的函數,必須使用如下函數:unsignedlongcopy_to_user(void_user*to,constvoid*from,unsignedlongcount);unsignedlongcopy_from_user(void*to,constvoid_user*from,unsigned
19、longcount);如果要復制的為簡單類型,如char,long,int等,則可以使用簡單的put_user()和get_user().如下所示:intval;/內核空間整型變量get_user(val,(int*)arg);/用戶空間到內核空間,arg是用戶空間的地址put_user(val,(int*)arg);/內核空間到用戶空間,arg是用戶空間的地址至于read和write的具體函數比較簡單,就在實驗中驗證好了。?read的返回值1 .返回值等于傳遞給read系統(tǒng)調用的count參數,表明請求的數據傳輸成功。2 .返回值大于0,但小于傳遞給read系統(tǒng)調用的count參數,表明部分
20、數據傳輸成功,根據設備的不同,導致這個問題的原因也不同,一般采取再次讀取的方法。3 .返回值=0,表示到達文件的末尾。4 .返回值為負數,表示出現錯誤,并且指明是何種錯誤。5 .在阻塞型io中,read調用會出現阻塞。?Write的返回值1 .返回值等于傳遞給write系統(tǒng)調用的count參數,表明請求的數據傳輸成功。2 .返回值大于0,但小于傳遞給write系統(tǒng)調用的count參數,表明部分數據傳輸成功,根據設備的不同,導致這個問題的原因也不同,一般采取再次讀取的方法。3 .返回值=0,表示沒有寫入任何數據。標準庫在調用write時,出現這種情況會重復調用write。4 .返回值為負數,表示
21、出現錯誤,并且指明是何種錯誤。錯誤號的定義參見<linux/errno.h>5 .在阻塞型io中,write調用會出現阻塞。七、模塊實驗測試結果:rootNEU/#insmod/lib/modules/rootNEU/#Ismodglobalmem29200-Live0xbf000000rootNEU/#cat/proc/devicesCharacterdevices:1 mem2 pty3 ttyp4 /dev/vc/05 tty6 /dev/tty7 /dev/console8 /dev/ptmx9 vcs10 misc13 input14 sound21sg29fb81vid
22、eo4linux90mtd128ptm136pts180usb189usb_device204s3c2410_serial253globalmem254devfsBlockdevices:1ramdisk31mtdblock180ubrootNEU/#rootNEU/#mknod/dev/globalmemc2530rootNEU/#ls/dev/globalmem/dev/globalmemrootNEU/#echohello>/dev/globalmemwritten6bytes(s)from0rootNEU/#cat/dev/globalmemread4096bytes(s)fro
23、m0hellocat:readerror:NosuchdeviceoraddressrootNEU/#八、附錄:file_operations結構 :structmodule*owner第一個file_operations成員根本不是一個操作;它是一個指向擁有這個結構的模塊的指針.這個成員用來在它的操作還在被使用時阻止模塊被卸載.幾乎所有時間中,它被簡單初始化為THIS_MODULE,一個在<linux/module.h>中定義的宏.loff_t(*llseek)(structfile*,loff_t,int);llseek方法用作改變文件中的當前讀/寫位置,并且新位置作為(正的)
24、返回值.loff_t參數是一個"longoffset",并且就算在32位平臺上也至少64位寬.錯誤由一個負返回值指示.如果這個函數指針是NULL,seek調用會以潛在地無法預知的方式修改file結構中的位置計數器(在"file結構"一節(jié)中描述).ssize_t(*read)(structfile*,char_user*,size_t,loff_t*);用來從設備中獲取數據.在這個位置的一個空指針導致read系統(tǒng)調用以-EINVAL("Invalidargument")失敗.一個非負返回值代表了成功讀取的字節(jié)數(返回值是一個"
25、signedsize"類型,常常是目標平臺本地的整數類型).ssize_t(*aio_read)(structkiocb*,char_user*,size_t,loff_t);初始化一個異步讀-可能在函數返回前不結束的讀操作.如果這個方法是NULL,所有的操作會由read代替進行(同步地).ssize_t(*write)(structfile*,constchar_user*,size_t,loff_t*);發(fā)送數據給設備.如果NULL,-EINVAL返回給調用write系統(tǒng)調用的程序.如果非負,返回值代表成功寫的字節(jié)數.ssize_t(*aio_write)(structkiocb
26、*,constchar_user*,size_t,loff_t*);初始化設備上的一個異步寫.int(*readdir)(structfile*,void*,filldir_t);對于設備文件這個成員應當為NULL;它用來讀取目錄,并且僅對文件系統(tǒng)有用.unsignedint(*poll)(structfile*,structpoll_table_struct*);poll方法是3個系統(tǒng)調用的后端:poll,epoll,和select,都用作查詢對一個或多個文件描述符的讀或寫是否會阻塞.poll方法應當返回一個位掩碼指示是否非阻塞的讀或寫是可能的,并且,可能地,提供給內核信息用來使調用進程睡眠
27、直到I/O變?yōu)榭赡?如果一個驅動的poll方法為NULL,設備假定為不阻塞地可讀可寫.int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);ioctl系統(tǒng)調用提供了發(fā)出設備特定命令的方法(例如格式化軟盤的一個磁道,這不是讀也不是寫).另外,幾個ioctl命令被內核識別而不必引用fops表.如果設備不提供ioctl方法,對于任何未事先定義的請求(-ENOTTY,"設備無這樣的ioctl"),系統(tǒng)調用返回一個錯誤.int(*mmap)(structfile*,structvm_area_struct*);m
28、map用來請求將設備內存映射到進程的地址空間.如果這個方法是NULL,mmap系統(tǒng)調用返回-ENODEV.int(*open)(structinode*,structfile*);盡管這常常是對設備文件進行的第一個操作,不要求驅動聲明一個對應的方法.如果這個項是NULL,設備打開一直成功,但是你的驅動不會得到通知.int(*flush)(structfile*);flush操作在進程關閉它的設備文件描述符的拷貝時調用;它應當執(zhí)行(并且等待)設備的任何未完成的操作.這個必須不要和用戶查詢請求的fsync操作混淆了.當前,flush在很少驅動中使用;SCSI磁帶驅動使用它,例如,為確保所有寫的數據
29、在設備關閉前寫到磁帶上.如果flush為NULL,內核簡單地忽略用戶應用程序的請求.int(*release)(structinode*,structfile*);在文件結構被釋放時引用這個操作.如同open,release可以為NULL.int(*fsync)(structfile*,structdentry*,int);這個方法是fsync系統(tǒng)調用的后端,用戶調用來刷新任何掛著的數據.如果這個指針是NULL,系統(tǒng)調用返回-EINVAL.int(*aio_fsync)(structkiocb*,int);這是fsync方法的異步版本.int(*fasync)(int,structfile*,
30、int);這個操作用來通知設備它的FASYNC標志的改變.異步通知是一個高級的主題,在第6章中描述.這個成員可以是NULL如果驅動不支持異步通知.int(*lock)(structfile*,int,structfile_lock*);lock方法用來實現文件加鎖;加鎖對常規(guī)文件是必不可少的特性,但是設備驅動幾乎從不實現它.ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);
31、這些方法實現發(fā)散/匯聚讀和寫操作.應用程序偶爾需要做一個包含多個內存區(qū)的單個讀或寫操作;這些系統(tǒng)調用允許它們這樣做而不必對數據進行額外拷貝.如果這些函數指針為NULL,read和write方法被調用(可能多于一次).ssize_t(*sendfile)(structfile*,loff_t*,size_t,read_actor_t,void*);這個方法實現sendfile系統(tǒng)調用的讀,使用最少的拷貝從一個文件描述符搬移數據到另一個.例如,它被一個需要發(fā)送文件內容到一個網絡連接的web服務器使用.設備驅動常常使sendfile為NULL.ssize_t(*sendpage)(structfil
32、e*,structpage*,int,size_t,loff_t*,int);sendpage是sendfile的另一半;它由內核調用來發(fā)送數據,一次一頁,到對應的文件.設備驅動實際上不實現sendpage.unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);這個方法的目的是在進程的地址空間找一個合適的位置來映射在底層設備上的內存段中.這個任務通常由內存管理代碼進行;這個方法存在為了使驅動能強制特殊設備可能有的任何的對齊請求.大部分驅動可以置這個方法為
33、NULL.10int(*check_flags)(int)這個方法允許模塊檢查傳遞給fnctl(F_SETFL)調用的標志.int(*dir_notify)(structfile*,unsignedlong);這個方法在應用程序使用fcntl來請求目錄改變通知時調用.只對文件系統(tǒng)有用;驅動不需要實現dir_notifyfile_operations結構是整個Linux內核的重要數據結構,它也是file、inode結構的重要成員,表中分別說明結構中主要的成員:表file_operations結構Ownermodule的擁有者。Llseek重新定位讀寫位置。Read從設備中讀取數據。Write向字
34、符設備中寫入數據。Readdir只用于文件系統(tǒng),對設備無用。Ioctl控制設備,除讀寫操作外的其他控制命令。Mmap將設備內存映射到進程地址空間,通常只用于塊設備。Open打開設備并初始化設備。Flush清除內容,一般只用于網絡文件系統(tǒng)中。Release關閉設備并釋放資源。Fsync實現內存與設備的同步,如將內存數據寫入硬盤。Fasync實現內存與設備之間的異步通訊。Lock文件鎖定,用于文件共享時的互斥訪問。Readv在進行讀操作前要驗證地址是否可讀。Writev在進行寫操作前要驗證地址是否可寫。在嵌入式系統(tǒng)的開發(fā)中,我們一般僅僅實現其中幾個接口函數:read、write、ioctl、ope
35、n、release,就可以完成應用系統(tǒng)需要的功能。structfile_operationsglobalmem_fops=.owner=THIS_MODULE,.llseek=scull_llseek,.read=scull_read,.write=scull_write,.ioctl=scull_ioctl,.open=scull_open,.release=scull_release,;staticstructfile_operationsglobalmem_fops=完成了將驅動函數映射為標準接口,上面的這種特殊表示方法不是標準C的語法,這是GNU譯器的一種特殊擴展,它使用名字對進行結構
36、字段的初始化,它的好處體現在結構清晰,易于理解,并且避免了結構發(fā)生變化帶來的許多問題。structfile結構定義于<linux/fs.h>,是設備驅動中第二個最重要的數據結構.系統(tǒng)中每個打開的文件有一個關聯的structfile在內核空間).它由內核在open時創(chuàng)建,并傳遞給在文件上操作的任何函數,直到最后的關閉.在文件的所有實例都關閉后,內核釋放這個數據結構.在內核源碼中,structfile的指針常常稱為file或者filp("filepointer").我們將一直稱這個指針為filp以避免和結構自身混淆.因此,file指的是結構,而filp是結構指針.m
37、ode_tf_mode;文件模式確定文件是可讀的或者是可寫的(或者都是),通過位FMODE_READ和FMODE_WRITE.你可能想在你的open或者ioctl函數中檢查這個成員的讀寫許可,但是你不需要檢查讀寫許可,因為內核在調用你的方法之前檢查.當文件還沒有為那種存取而打開時讀或寫的企圖被拒絕,驅動甚至不知道這個情況.loff_tf_pos;當前讀寫位置.loff_t在所有平臺都是64位(在gcc術語里是longlong).驅動可以讀這個值,如果它需要知道文件中的當前位置,但是正常地不應該改變它讀和寫應當使用它們作為最后參數而收到的指針來更新一個位置,代替直接作用于filp->f_p
38、os.這個規(guī)則的一個例外是在llseek方法中,它的目的就是改變文件位置.另一種解釋:此變量顯示出當前讀寫位置,而由read,write,llseek等可修改讀寫位置的函數改變,用在管理文件指針的設備驅動程序上。unsignedintf_flags;這些是文件標志,例如O_RDONLY,O_NONBLOCK,和O_SYNC.驅動應當檢查O_NONBLOCK標志來看是否是請求非阻塞操作(我們在第一章的"阻塞和非阻塞操作"一節(jié)中討論非阻塞I/O);其他標志很少使用.特別地,應當檢查讀/寫許可,使用f_mode而不是f_flags.所有的標志在頭文件<linux/fcntl.h>中定義.structfile_operations*f_op;和文件關聯的操作.內核安排指針作為它的open實現的一部分,接著讀取它當它需要分派任何的操作時.filp->
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 【KS5U原創(chuàng)】新課標2021年高二暑假化學作業(yè)(八)
- 【創(chuàng)新設計】(人教)2020-2021高中化學選修五【分層訓練】4-3-蛋白質和核酸
- 【創(chuàng)新設計】2020-2021學年高中物理人教版選修3-1練習:1.10-電容器的電容
- 【名師一號】2020-2021學年高中地理人教版必修三-雙基限時練7
- 【2021春走向高考】2022屆高三歷史(岳麓版)一輪復習:階段性測試題10
- 保定市2022高考英語閱讀理解選練(1)答案
- 2021廣東韶關市高考英語自選練習(3)及答案
- 《結直腸癌教學》課件
- 【學練考】2021-2022學年高一歷史岳麓版必修1練習冊:單元測評一-
- 【名師一號】2020-2021學年高中數學人教B版必修2雙基限時練9(第一章)
- 2025年中國煙草總公司湖北省公司校園招聘227人高頻重點提升(共500題)附帶答案詳解
- 部隊行車安全課件
- 2024版帶貨主播電商平臺合作服務合同范本3篇
- 2025公司資產劃轉合同
- 2024-2030年中國鋁汽車緊固件行業(yè)銷售規(guī)模與盈利前景預測報告
- 2025康復科年度工作計劃
- 廣東省清遠市2023-2024學年高一上學期期末質量檢測物理試題(解析版)
- 拼圖行業(yè)未來五年前景展望
- 2024-2025學年人教版數學五年級上冊期末檢測試卷(含答案)
- 廣西玉林市(2024年-2025年小學六年級語文)統(tǒng)編版質量測試(上學期)試卷及答案
- 《外盤期貨常識》課件
評論
0/150
提交評論