1 / 37

Chap 3- 字元裝置驅動程式

Chap 3- 字元裝置驅動程式. Outline. Introduction 3.1 scull 的設計藍圖 3.2 主編號與次編號 3.3 重要的資料結構 3.4 註冊字元裝置 3.5 open 與 release 3.6 Scull 的記憶體用法規劃 3.7 read 與 write. 3--Introduction. 範例: scull--( Simple Character Utility for Loading Localities )

adolfo
Télécharger la présentation

Chap 3- 字元裝置驅動程式

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Chap 3-字元裝置驅動程式

  2. Outline • Introduction • 3.1 scull的設計藍圖 3.2 主編號與次編號 • 3.3 重要的資料結構 3.4 註冊字元裝置 • 3.5 open 與 release 3.6 Scull 的記憶體用法規劃 • 3.7 read 與 write

  3. 3--Introduction • 範例:scull--( Simple Character Utility for Loading Localities ) • Makefile, main.c, access.c, empty.c, pipe.c, scull.h, scull.init, scull_load, scull_unload, alpha.checkthem • scull的作用是“讓使用者可把一塊記憶區當成字元裝置來使用”scull所驅動的目標裝置是一塊記憶區 • 不需依賴任何“特殊”硬體 • 只要有linux平台就可以編譯與執行 • 未提供任何實用功能,只展示核心與char driver之間軟體介面

  4. 3.1--scull的設計藍圖 • 定義驅動程式要提供哪些功能給user-space的程式 • 可循序存取(字元裝置) or 可隨機存取(區塊裝置) • 模擬單一裝置(ex:一機多體 or 多個同類裝置) • Scull所模擬出的每一種裝置,分別由不同類型的模組予以實現(相同的machine差別在於policy的不同) • scull0~scull3四個由記憶區所構成的裝置,兼具“共通” “持續” • scullpipe0~scullpipe3四個FIFO裝置( blocking與nonblocking ) • Scullsingle一次只容許一個被行程存取 • Scullpriv允許每各終端機都有權開啟一次,分屬不同行程 • Sculluid允許開始多次,限同一使用者。(回傳錯誤碼) • Scullwuid允許開始多次,限同一使用者。(推延,等待)

  5. 3.2--主編號與次編號 • 主編號(major number)(0~255) • 代表裝置所配合的驅動程式 • 當核心收到open()系統呼叫時,就是依據“主編號”來選擇驅動程式 • 次編號(minor number)(0~255) • 驅動程式以次編號來辨認同類裝置的個體 • 核心本身用不到,只有驅動程式自己才知道次編號的意義 • 當使用者要存取字元裝置時,必須透過檔案系統裡的“代表名稱”特殊檔(special file) 、裝置檔(device file) 、 檔案系統樹的節點(node),集中在/dev/目錄下。 • 裝置類型: • “c”代表char driver的特殊檔 • “b”代表block driver的裝置檔

  6. 3.2.1—裝置編號的配置與釋放 • 大部份常見的裝置幾乎都有固定的主編號,可在核心源碼樹的Documentation/devices.txt檔案內找到一份“裝置-主編號”對照表。<挑選可用主編號>  less /usr/src/kernel/linux-3.0.8/Documentation/devices.txt • “實驗性或自家使用”的主編號: • 60~63 、 120~127、240.254<真正公開給大眾使用的驅動程式不該使用這些範圍內的主編號> extern int register_chrdev_region(dev_t first, unsigned int count, const char *name); extern int unregister_chrdev_region (dev_t first , unsigned int count);

  7. 3.2.1—裝置編號的配置與釋放 • 在呼叫register_chrdev_region()時: • major引數給‘0’: =>回傳值為‘>0 & <255’核心分配的主編號 • major引數給‘>0 & <255’: =>回傳值為‘0’表示核心同意你的要求 • 發生錯誤時: => 回傳值為‘負數’ • 如果你的驅動程式會被用於廣大群眾,或者有可能被內入正式核心,則須設法申請專用的主編號。

  8. 3.2.2—動態配置主編號 • alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name) • 動態分配主編號的缺點是無法事先建立/dev/ 裝置節點,因為每次分配到的主編號不一定相同

  9. 3.2.2—動態配置主編號  cat /proc/devices Character devices: 1 mem 2 pty 226 drm 253 scull 254 pcmcia Block devices: 2 fd 3 ide0

  10. 3.3.1—檔案作業 • 驅動程式內部以一個file結構來代表一個已開啟的裝置 • 核心透過一個file_operations結構來存取驅動程式內部的作業函式(method)<都定義在linux/fs.h>  less /usr/src/kernel/linux-3.0.8/include/linux/fs.h • file結構包含一個f_op欄位,他是一個指向file_operations結構的指標 • file_operation結構本身是由一系列“函式指標”所構成,這些指標分別指向驅動程式的各項作業函式。 Ex:核心收到操作檔案的系統戶叫(ex:read( )),依據系統呼叫指定的fd找出對應驅動程式的file結構,然後再循線從file_operations結構中找出對應於該系統呼叫的作業函式scull_read( )

  11. Read() Scull_read()

  12. 3.3.1—檔案作業 • struct module *owner; • file_operations的第一個欄位不是函式指標,而是一個指向結構所屬模組的指標。 • loff_t (*llseek) (struct file *, loff_t, int); • seek作業方法的作用,是改變檔案目前的讀寫點位置。 • ssize_t (*read) (struct file *,char _ _user*,size_t,loff_t *); • 用於擷取出裝置上的資料。

  13. ssize_t (*write) (struct file *, const char *, size_t, loff_t *); • 將資料寫入裝置。 • int (*readdir) (struct file *,void *,filldir_t); • 對於裝置檔而言,此欄位必須是NULL,因為他是用來讀取目錄,而且只對檔案系統有意義。 • unsigned int (*poll) (struct file *, struct poll_table_struct *); • 查詢裝置的I/O狀態。 • int (*mmap) (struct file *, struct vm_area_struct *); • mmap用來將I/O memory對應到行程的位址空間。若驅動程式沒提供此方法,則mmap()系統呼叫將傳回-ENODEV

  14. int (*ioctl) (struct inode *, struct file *, unsigned int,unsigned long); • 每個裝置或多或少都有其特殊功能,標準的作業方法不見得能提供應用程式所需要的一切功能(ex:格式化),對於隨裝置而定的功能,應用程式可透過ioctl()系統呼叫來執行一系列裝置特有命令,而ioctl作業發法的任務,就是實現在類特殊命令。 • int (*open) (struct inode *, struct file *); • 開啟,這必定是裝置檔操作程序的第一步,但是驅動程式並非一定要宣告一個對應方法不可。如果將此欄位指向NULL,開啟裝置的動作一定會成功,只不過驅動程式不會收到通知而已。 • int (*flush) (struct file *); • 這是行程在關閉其裝置檔之前的必要動作。flush應該執行任何還沒解決的作業程序。請不要將此方法與fsnc()系統呼叫聯想在一起。目前,只有NFS需要用到flush。如果讓flush指向NULL,不會有動作發生。 • int (*release) (struct inode *, struct file *); • 在file結構被釋放之前,此方法會被呼叫。(並非,每次呼叫close( )都會觸發release)

  15. int (*fsync) (struct inode *, struct dentry *, int); • 此為fsync( )系統呼叫的實際後台,其作用是出清(flush)任何延滯的資料。如果驅動程式不提供此作業方法,系統呼叫會傳回-EINVAL • int (*fasync) (struct inode *, struct flie *, int); • 當裝置的FASYNC旗標出現變化時,核心就會呼叫驅動程式的fasync的作業方法。非同步通知(asynchronous notification)。 • int (*lock) (struct file *, int, struct file_lock *); • 檔案鎖定。Lock的作業方法用來實現檔案所訂的效果。對一般檔案而言,這項更能絕不可避免,但是幾乎沒有任何驅動程式時作此作業方法。 • ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); • ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); • “分散讀取(scatter read)”以及“累積寫出(gather write)”。

  16. 3.3.2—file 結構 • fops變數名稱用來表示file_operation結構(或是指向此結構的指標) • 在file_operations結構中的每一個欄位,都必須指向驅動程式中負責特定作業方法。對於驅動程式不支援的作業項目(method),其對應欄位就必須指向NULL,當核心遇到NULL時會採取適當的預設行為。 • 當再重新編譯新核心之後,file_operations會指向NULL,會採取預設行為。 • 每一種作業方法,其回傳值都有同樣的意義:0代表成功,負值為錯誤碼。

  17. 3.3.2—file 結構 • File與一般應用程式常用的FILE毫無關係。File定義在C函式庫內,不可能出現在核心程式碼理,而file結構也絕不會出現在user-space應用程式理。 • struct file代表一個已開啟的檔案(open file),對於系統上每一個已開啟的檔案,在kernel-space裡都有一個對應的struct file。每一個file結構都是在核心收到open()系統呼叫時自動建立的,任何作用在檔案的作業方法與核心函式,都會收到該檔案的file結構。close()系統… • struct file不同於磁碟檔案<磁碟檔案是由struct inode來表示> • 在這裡file是結構,而filp是指向file結構的指標 • file => struct ; filp => pointer

  18. 3.3.2—file 結構 • file結構的重要欄位: • mode_t f_mode; • 代表檔案的“存取模式”,可能的存取方式包括“可讀”(FMODE_READ),”可寫”(FMODE_WRITE)或“可讀可寫”(FMODE_READ | FMODE_WRITE)。ioctl作業方法或許會想要檢查此欄位來確定讀寫權限,但是read,write則不需要執行權限檢查,因為核心在收到read(),write()系統呼叫時,就已經幫你檢查過了。 • loff_t f_pos; • 檔案目前的讀寫位置。loff_t是一個64bit值。若驅動程式想知道檔案目前的讀寫點位置,可以讀取此值,但是絕對不應該直接修改此值。read, write作業方法應該透過它們從自己第三引數收到的指標(loff_t *)來更新讀寫點位置,而不是直接修改filp->f_pos.

  19. unsigned short f_flags; • 各項“檔案旗標”的組合,像是O_RDONLY, O_NONBLOCK與O_SYNC等等。驅動程式在進行非推延作業(nonblocking operation)時,會需要檢查這些旗標。(所有的旗標都定義在<linux/fcntl.h>) • struct file_operations *f_op; • f_op為指向file_operation結構的指標。每當需要提調(dispatch)任何檔案作業時,核心會讀取此指標,找出對應的作業方法。核心絕不會快取filp->f_op的值<,ex:主編號為1的檔案(/dev/null, /dev/zero …)其搭配的open作業方法會依據開啟對象的次編號,將flip->f_op指向不同的作業方法,此技巧使得主編號相同的一系列檔案,可以表現出多種不同的行為模式,核心容許這種替換檔案作業方法的能力,相當於物件導向的“method overrideing”技術> • void *private_data; • Private_data是相當有用的資源,可供我們保存生命其跨越多次系統呼叫的狀態資訊。驅動程式可以自由決定要如何運用此指標,甚至完全忽略,但必須記得在release method裡釋放掉此指標所占用的記憶體。

  20. 3.3.2—file 結構 • struct dentry *f_dentry; • 檔案所屬的“目錄項”結構(directory entry,通常不理會dentry結構,頂多也只是透過file->f_dentry->d_inode來存取inode結構而已。 • file結構實際的欄位超過以上所述。事實上驅動程式決不會自己填寫file結構,而只會存其他地方產生的結構。

  21. 3.3.3 - inode 結構 • 核心內部使用inode結構代表檔案,不同於file結構, inode結構是代表已開啟的檔案的FD。 • dev_t i_rdev; • 對於代表檔案裝置檔的inodes,此欄位含有實際的裝置編號(主次編號的組合)。 • struct cdev *i_cdec • struct cdev 是核心內部用來表示字元裝置的結構。對於代表字元裝置的inode,此欄位含有一個指向該結構的指標。

  22. 3.5— open & release • Open作業方法提供驅動程式一個機會,為後續的作業進行任何必要的初始化準備工作。此外,open通常還會遞增目標裝置的“用量計次,以免模組檔案關閉之前被謝載。 • Release作業方法負責遞減用量計次(usage count)。 <定義在 linux/module.h> • MOD_INC_USE_COUNT :將目前模組使用次數加一 • MOD_DEC_USE_COUNT:將目前模組使用次數減一

  23. 3.5.1— open作業方法 • 大部份驅動程式的open作業方法,應該執行下列動作: • 遞增用量計次 • 檢查裝置特有的錯誤(ex:數據機佔線, 沒放光碟片…) • 如果目標裝置是第一次被開啟,則應該進行初始化程序 • 辨認次編號,並更新f_op指標 • 配置並裝填任何要放在filp->private_data的資料結構 • 開啟的第一步是是要看目標裝置的次編號為何?<在scull是檢查inode->i_rdev> less main.c

  24. 3.5.2— release作業方法 • Release作業方法扮演的腳色正好與open相反。有時函式名稱會是device_close(),而非device_release()。不管名稱如何,主要工作流程都一樣: • 釋放open配置給filp->private_data的任何東西 • 在最後一次關閉時,將目標裝置關機 • 遞減用量計次 • <基本上scull沒有硬體可以關機,所以程式碼相當簡潔> Int scull_release (struct inode *inode, struct file *filp) { return 0; }

  25. 3.5.2— release作業方法 • 如果再open有用遞增用量計次,則release就必須遞減之。因為只要用量計次沒歸零,核心就會拒絕卸載模組。 • 有時候,一個檔案被開啟與被關閉的次數不一定相同,如何能夠確定用量計次一定正確? • dup( )和fork( )系統呼叫會直接建立以開檔案的副本,而不會啟動驅動程式的open,但這些副本在程式結束時卻會觸發close( )系統呼叫… • ANS:並非每次close( )系統呼叫都會觸發release作業方法。close( )只有再真正需要釋放裝置上的資料時,才會去呼叫release這就是為何取名release而不是close的原因 • 其它類型的裝置有各自的關閉函式,因為scull_open( )已經依據裝置類型,讓filp->f_op指向該類裝置的作業方法。

  26. 3.6— Scull的記憶體用法規劃 • 本節只專注scull的記憶體配置策略,而不涉及真實驅動程式所需的硬體管理計技巧 • scull裝置是一個長度可隨資料量而變的記憶區 • 寫入的資料越多,長度自然增加 • 如果用較短的資料去蓋寫它,長度也會跟著縮減 • 不限制“裝置”區的大小 • 在結構上,每個scull裝置都是一個“指標鏈結串列”(a linked list of pointers),每個指標分別指向一個Scull_Dev結構(最多可代表四百萬位元組)。 • 這個陣列含有1000個指標  配額集(quantum set) • 每個指標指向一個4000bytes大小的區域  配額(quantum)

  27. 3.6 — Scull 的記憶體用法規劃 • 配額與配額集應該佔用多少記憶量才合適? • 是操作策略(policy)上的問題,與機制(machanism)無關。 • “擬定合理的預設值,同時提供使用者修改的彈性” • Scull容許使用者以多種方式來修改配額量: • 編譯期:修改scull.h內的SCULL_QUANTUM與SCULL_QSET 常數值。 • 模組載入期:scull_quantum與scull_qset變數值。 • 執行期:使用ioctl( )修改目前設定值與預設值。 • 如何決定預設值? • “半滿配額與配額集所浪費掉的記憶量”與“如果配額量太小,以致於需要釋放原有記憶體,重新配置,重建串鏈所造成的效率損耗”兩者之間找出平衡點。 • kmalloc( )的內部設計也應該列入考量…

  28. Scull_Dev Scull_Dev Scull_Dev next next next data data data scull 裝置的記憶佈局 配額集 個別配額

  29. 3.7—read & write ssize_t read(struct file *filp, char *buff, size_t count, loff_t *offp); ssize_t write(struct file *filp, const char *buff, size_t count, loff_t *offp); • filp檔案指標 • buff引數指向user-space的緩衝區 • count要被傳輸的資料量 • offp是一個指向“long offset type”的物件指標,代表 使用者正在存取的檔案位置

  30. 3.8—read & write • “跨空間”資料傳輸(在kernel-space與user-space之間傳遞資料) • scull的read作業方法,需有可將整段資料寫到user-sapce的能力。write作業方法,需要能從kernel-space讀取一整段資料的能力。 • 由以下兩個核心函式(具有檢查功能)提供: • 預防動作必須注意(user-space有換出功能-swapped out) unsigned long copy_to_user(void *to, const void *from, unsigned long count ); unsigned long copy_from_user(void *to, const void *from, unsigned long count );

  31. ssize_t dev_read( struct file *file, char *buf, size_t count, loff_t *ppos); struct file f_count f_flags f_mode Buffer (in the Kernel-space ) Buffer ( in the user-space or Libc ) Copy_to_user( ) f_pos …. …. 核心空間(不可置換) 使用者空間 (可被置換) Read 作業方法的引數之應用

  32. 3.7.1—read 作業方法 • 若傳回值等於當初傳給read( )系統呼叫的count引數,那表示當初要求傳輸的資料已經全數傳輸成功了。 • 若傳回值大於0,但小於count,則表示只順利傳輸了部份資料,原因多與目標裝置有關,通常應用程式會再嘗試重新讀取。 • 若傳回值為0。代表已經遇到檔案尾端(EOF)。 • 若傳回值為-1(不可能有其他負數),則代表發生了某種錯誤。

  33. 3.7.2—write 作業方法 • 若傳回值等於count,表示要求的資料量已經全數被寫入裝置。 • 若傳回值為正數值,但小於count,表示只有部份資料被寫入裝置。呼叫者應該再試幾次,將其餘資料也寫入裝置。 • 若傳回值為0,表示沒寫入任何資料。不代表發生錯誤,所以不會傳回代碼。應該是著重新發出write( )呼叫。 • 若傳回負值,表示發生了某種錯誤。如同read的狀況,錯誤代碼的意義定義在<linux/errno.h>

  34. 裝置類型 主編號 次編號 代表名稱 DEMO-1  ls –al /dev/ |less brw-rw---- 1 root disk 66, 72 Apr 11 2002 sdak8 brw-rw---- 1 root disk 66, 73 Apr 11 2002 sdak9 crw-r--r-- 1 root root 253, 1 Mar 1 22:58 scull1 crw-r--r-- 1 root root 253, 2 Mar 1 22:58 scull2 crw-r--r-- 1 root root 253, 3 Mar 1 22:58 scull3 brw-rw---- 1 root disk 8, 0 Apr 11 2002 sda crw-rw---- 1 root uucp 154, 18 Apr 11 2002 ttySR18

  35. DEMO-2 • 檔案系統製作裝置節點的命令是mknod,必須有特權身分(root)才能使用此工具。至少需要四個引數… • <代表名稱> <裝置類型> <主編號> <次編號>  mknod /dev/ant c 252 0 • 像任何儲存在磁碟上的普通檔案一樣,mknod所產生的裝置節點會被保存下來,除非刻意刪除它們。用一般的rm命令即可辦到…<不使用時未刪除及佔用空間>  rm /dev/ant

  36. DEMO-3 • ./scull_load • cat /proc/devices • ./scull_unload

  37. DEMO-4 • make • insmod ./scull.ko • 可以在記憶區製造出一個字元裝置,並且對它做寫入資料或讀取

More Related