1 / 52

Chap 3- 字元裝置驅動程式

Chap 3- 字元裝置驅動程式. Outline. Introduction 3.1 scull 的設計藍圖 3.2 主編號與次編號 3.3 檔案架構 3.4 file 結構 3.5 open 與 release 3.6 Scull 的記憶體用法規劃 3.7 相競狀況 3.8 read 與 write 3.9 try scull 3.10 devfs 檔案系統 3.11 回溯相容性 3.12 速查參考. 3--Introduction.

lilianna
Download Presentation

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 file 結構 • 3.5 open 與 release 3.6 Scull 的記憶體用法規劃 • 3.7 相競狀況 3.8 read 與 write • 3.9 try scull 3.10 devfs 檔案系統 • 3.11 回溯相容性 3.12 速查參考

  3. 3--Introduction • 本章目標:寫出一個完整的字元裝置驅動程式(char device driver)--簡稱char driver • 終極目標:寫出一個模組化的char driver • 範例: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-sapce的程式 • 可循序存取(字元裝置) or 可隨機存取(區塊裝置) • 模擬單一裝置(ex:一機多體 or 多個同類裝置) • Scull所模擬出的每一種裝置,分別由不同類型的模組予以實現(相同的machine差別在於policy的不同) • scull0~scull3四個由記憶區所構成的裝置,兼具“共通” “持續” • scullpipe0~scullpipe3四個FIFO裝置( blocking與nonblocking ) • Scullsingle一次只容許一個被行程存取 • Scullpriv允許每各終端機都有權開啟依次,分屬不同行程 • Sculluid允許開始多次,限同一使用者。(回傳錯誤碼) • Scullwuid允許開始多次,限同一使用者。(推延,等待) Ch_5.2.5 Ch_5.6

  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--主編號與次編號  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

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

  8. 3.2.1--隨機取得主編號 • 大部份常見的裝置幾乎都有固定的主編號,可在核心源碼樹的Documentation/devices.txt檔案內找到一份“裝置-主編號”對照表。<挑選可用主編號不易>  less /usr/src/linux-2.4.20/Documentation/devices.txt • “實驗性或自家使用”的主編號: • 60~63 、 120~127、240.254<真正公開給大眾使用的驅動程式不該使用這些範圍內的主編號> • “隨機索取主編號”呼叫register_chrdev() • 定義在<linux/fs.h>  less /usr/src/linux-2.4.20/include/linux/fs.h extern int register_chrdev(unsigned int, const char *, struct file_operations *); extern int unregister_chrdev(unsigned int, const char *);

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

  10. 3.2.1--隨機取得主編號  cat /proc/devices Character devices: 1 mem 2 pty 226 drm 253 scull 254 pcmcia Block devices: 2 fd 3 ide0

  11. 3.2.1--隨機取得主編號 • 缺點: • 因為模組分配到的主編號不一定每次都一樣,所以無法是先建立裝置節點。<不能作出必要時才載入“load-on-demand”的驅動程式> • 因此,光靠insmod是不夠的,還必須查閱/proc/devices的內容後,找出其隨機取得的‘主編號’,再製作成適當的裝置節點(甚至要刪除上次留下的無用節點) • 可寫成一個shell script來一次解決<利用awk之類工具> • 範例:scull_load ==> 每次開機時透過/etc/rc.local來執行 scull_unload==> 卸載模組及清理/dev/下的相關節點 scull.init ==> 由系統開機/關機程序自動載入/卸載 模組。接受傳統命令start,stop,restart Ch_11

  12. #!/bin/sh module="scull“ <scull_load> device="scull" mode="664" # Group: since distributions do it differently, look for wheel or use staff if grep '^staff:' /etc/group > /dev/null; then group="staff" else group="wheel" fi # invoke insmod with all arguments we got # and use a pathname, as newer modutils don't look in . by default /sbin/insmod -f ./$module.o $* || exit 1 major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"` # Remove stale nodes and replace them, then give gid and perms # Usually the script is shorter, it's scull that has several devices in it. rm -f /dev/${device}[0-3] mknod /dev/${device}0 c $major 0 mknod /dev/${device}1 c $major 1 mknod /dev/${device}2 c $major 2 mknod /dev/${device}3 c $major 3 ln -sf ${device}0 /dev/${device} chgrp $group /dev/${device}[0-3] chmod $mode /dev/${device}[0-3]

  13. #!/bin/sh module="scull“ <scull_unload> device="scull" # invoke rmmod with all arguments we got /sbin/rmmod $module $* || exit 1 # Remove stale nodes rm -f /dev/${device} /dev/${device}[0-3] rm -f /dev/${device}priv rm -f /dev/${device}pipe /dev/${device}pipe[0-3] rm -f /dev/${device}single rm -f /dev/${device}uid rm -f /dev/${device}wuid • scull_load & scull_unload也適用其他其他驅動程式,只需重新定義變數,並稍微調整mknod那幾行即可。 • 因為指令搞需要特權身分才能執行,而新建出來的特殊檔自然會屬於root。但裝置節點的存取權限通常不是這樣,所以要修改權限的擁有權。

  14. 3.2.2—移除驅動程式 • 在模組被卸載之前,他必須先釋放主編號,而這動作可由unregister_chrdev()完成,應該在模組的清理函式理呼叫他: • major表示要被釋放的主編號;name是當初註冊的名稱 <必須與當初呼叫register_chrdev()所用的引述一致> • 對於動態取得主編號的驅動程式,如果在卸載模組之後沒有順便除掉相關的/dev結點,就可能產生意料外的錯誤。譬如,已經沒有作用的/dev/framegrabber可能在一個月後指向火災警報器<如果兩個驅動程式都使用隨機取得的主編號> int unregister_chrdev(unsigned int major, const char *name):

  15. 3.2.3--副編號(dev_t & kdev_t) • 每當核心要呼叫驅動程式,都必須讓驅動程式知道,他應該作用在哪一個裝置上;驅動程式以裝置編號來變任期作用對象。(裝置編號=主編號+次編號) • dev-t • Unix以dev_t(代表“device type”)作為裝置編號的資料型別 • 定義在<sys/types.h> • 更改不易,因太多應用程式“知道”其內部結構… • kdev-t • Linux核心內部採用另一種不同的資料型別 • 定義在<linux/kdv_t.h> • ‘黑盒子’每個核心函式都不必知道其內部結構,而應用程式可以不知道kdev_t的存在。=>核心改版可以放手修正,不會影響任何驅動程式

  16. 3.2.3--副編號(dev_t & kdev_t) • 可操作的kdev_t巨集與函式: • MAJOR(kdev_t dev) =>從kdev結構取得主編號 • MINOR(kdev_t dev) =>從kdev結構取得次編號 • MKDEV(int major, int minor) =>以指定的主編號與次編號建立一個kdev_t結構 • kdev_t_to_nr(kdev_t dev) =>將一個kdev_t結構轉換成一個數值(dev_t) • to_kdev_t(int dev) =>將一個數值轉換成kdev_t結構<注意:由於核心模式並未定義dev_t,所以這裡用一個int來代替>

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

  18. Read() Scull_read()

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

  20. 3.3—檔案作業 • struct module *owner; • 不同於file_operations結構裡的其它欄位,此欄位不是函式指標,而是一個指向結構所屬模組的指標。驅動程式本身用不到此欄位,因為他是供核心用來維護模組的用量計次(usage count)。 • loff_t (*llseek) (struct file *, loff_t, int); • //seek作業方法的作用,是改變檔案目前的讀寫點位置;如果成功,則傳回新位置(正值),傳回值loff_t是一個至少64-bits “long offset”,即使是在32-bits平台上也是如此。 • ssize_t (*read) (struct file *,char *,size_t,loff_t *); • 用於擷取出裝置上的資料。若將此欄位設定為NULL,則read()系統呼叫發生-EINVAL失敗。(Invalid argument,引數值無效) ,如果成功,則傳回一個非負數值,代表成功讀取的位元數。<ssize_t是一個64-bits的int>

  21. ssize_t (*write) (struct file *, const char *, size_t, loff_t *); • 將資料寫入裝置。若發生錯誤,則觸發write()系統呼叫的行程會收到-EINVAL。如果成功,write將傳回一個非負值,代表成功寫出的位元數。 • int (*readdir) (struct file *,void *,filldir_t); • 對於裝置檔而言,此欄位必須是NULL,因為他是用來讀取目錄,而且只對檔案系統有意義。 • unsigned int (*poll) (struct file *, struct poll_table_struct *); • poll作業方法是兩種系統呼叫的後台:poll()與select()-兩者都是用來探詢裝置在某特殊狀態下是法可讀或可寫,而且兩者都會等到目標裝置呈為可寫或可讀的狀態,才會返回。如果驅動程式沒定義自己poll作業方法,核心會預設目標裝置的性質為同時兼具可讀可寫,沒有特殊狀態。傳回值是一個代表裝置狀態的位元遮罩(bit mask)。 • int (*mmap) (struct file *, struct vm_area_struct *); • mmap用來將I/O memory對應到行程的位址空間。若驅動程式沒提供此方法,則mmap()系統呼叫將傳回-ENODEV

  22. 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)

  23. 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 *); • 以上兩者都是2.3版研發過程中才提出來的新作業發訪,分別用於實現“分散讀取(scatter read)”以及“累積寫出(gather write)” Ch_5

  24. 3.3—檔案作業 • scull 驅動程式只實作最重要的幾項作業方法,而起GNU的標記語法來宣告初始話他自己的file_operations結構: • GNU C編譯器的優點: • 驅動程式的移植性較高 • 程式碼精簡,容易理解 • 在某些情況下,能得到相當程度的效能提升 struct file_operations scull_fops = { llseek: scull_llseek, read: scull_read, write: scull_write, ioctl: scull_ioctl, open: scull_open, release: scull_release,};

  25. 3.4—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

  26. 3.4—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.

  27. 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裡釋放掉此指標所占用的記憶體。

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

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

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

  31. 3.5.1— open作業方法 • 核心不需要裝置的次編號,所以驅動程式可對次編號自由賦予任何意義。 • 實務上,次編號常被用來區別“同類裝置的不同個體”或“同一個裝置的不同操作模式”。 • Ex:/dev/st0(次編號0)與/dev/st1(次編號1)分別代表不同的SCSI磁碟機,但是/dev/nst0(次編號)128其實與/dev/st0是同一台機器,只不過作模式不同而已(/dev/nst0被關閉之後不自動回帶) • 使用者可用自己慣用的名稱來代表常用裝置,也就是將常用裝置連結到另依各比較人性化的檔名。 • Ex:用/dev/cdrom代表/dev/hdc,用/dev/modem代表/dev/ttyS1

  32. 3.5.1— open作業方法 • scull驅動程式對於次編號的運用方式: • 高半位元組(bit7~bit4)代表裝置的類型(個性) • 低半位元組(bit3~bit0)用於辨別同類裝置的不同個體 • 以scull為例: • scull0與scullpipe0有不同的高半位元組->不同類型 • scull0與scull1則有不同的低半位元組->同類裝置 • 再scull裡定義了TYPE與NUM巨集,分別用於從次編號取得裝置類型與個體編號。 #define TYPE(dev)(MINOR(dev)) >> 4 ) /*高四位元*/ #define NUM(dev) (MINOR(dev)) & 0xf) /*低四位元*/

  33. struct file_operations *scull_fop_array[]={ &scull_fops, /* type 0 */ &scull_priv_fops, /* type 1 */ &scull_pipe_fops, /* type 2 */ &scull_sngl_fops, /* type 3 */ &scull_user_fops, /* type 4 */ &scull_wusr_fops /* type 5 */ }; #define SCULL_MAX_TYPE 5 /*scull_open依據TYPE(dev)來選擇 /*要使用fop_array裡的那一組fops int scull_open(struct inode *inode, struct file *filp) { int type = TYPE(inode->i_rdev); …略… if (type > SCULL_MAX_TYPE) return -ENODEV; filp->f_op = scull_fop_array[type]; …略… • 對於每種裝置類型,scull分別定義一個專屬的file_operations結構,並使open將filp->f_op指向這些結構。以下示範scull驅動程式如何製作多個fops:

  34. 一般而言,驅動程式不必呼叫自己的作業方法,因為fops主要是讓核心挑選適當的作業方法。但是,如果驅動程式必須在自己的open作業方法裡辨認裝置類型,或許會想要在修改了fops指標之後,刻意呼叫fops->open( )一次。 • 以下是scull_open( )的實際作法: int scull_open(struct inode *inode, struct file *filp) { Scull_Dev *dev; /* 裝置資訊 */ int num = NUM(inode->i_rdev); int type = TYPE(inode->i_rdev); /*如果private_data無效,那表示我們不是在使用devfs /*所以可依據type(次編號的高半位元)挑出新的f_op if (!filp->private_data && type) { if (type > SCULL_MAX_TYPE) return -ENODEV; filp->f_op = scull_fop_array[type]; return filp->f_op->open(inode, filp); /* dispatch to specific open */ }

  35. /*type 0,檢查裝置編號(除非priate_data有效)*/ dev = (Scull_Dev *)filp->private_data; if (!dev) { if (num >= scull_nr_devs) return -ENODEV; dev = &scull_devices[num]; filp->private_data = dev; /*供其他方法使用*/ } /*在放心休息之前的必要動作*/ MOD_INC_USE_COUNT; /* Before we maybe sleep */ /*如果開啟模式是write-only,就將目標裝置的長度減到0*/ if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { if (down_interruptible(&dev->sem)) { MOD_DEC_USE_COUNT; return -ERESTARTSYS; } scull_trim(dev); /*忽略錯誤 */ up(&dev->sem); } return 0; /*成功*/ } • Scull_Dev是此驅動程式用來持有記憶區的資料結構 • scull_nr_devs 是可用裝置的數量 • scull_devices[] 實際指向Scull_Dev結構的指標陣列

  36. 3.5.1— open作業方法 • 這段程式看起來似乎有點空洞? • 未實作任何處理硬體的動作, • 並未計算它目前被開啟了多少次? • scull的本質被設計成通用,持續性的裝置 • 核心能透過file_operations結構owner欄位來維護模組的用量計次,為何要再作一次同樣動作? • 為了與舊版核心相容 • 為何要再write_only模式時將記憶區長度歸零? • open是唯一會影響目標裝置的動作,除了write only之外的其他存取模式,open不必作任何事。 • 根據設計理念,當scull存有大量資料時,若被較少量的資料蓋寫,則最後會只剩較短的記憶區,讓多的資料消失。

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

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

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

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

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

  42. 3.6— 的記憶體用法規劃 • Scull用來保存裝置資訊的資料結構: <scull.h> • Scull_trim( )函式負責釋放整各資料區:write-only模式scull_open( )時,它就是以scull_trim( )來清空舊資料。<main.c> typedef struct Scull_Dev { void **data; struct Scull_Dev *next; /* next listitem */ int quantum; /* the current quantum size */ int qset; /* the current array size */ unsigned long size; devfs_handle_t handle; /* only used if devfs is there */ unsigned int access_key; /* used by sculluid and scullpriv */ struct semaphore sem; /* mutual exclusion semaphore */ } Scull_Dev;

  43. int scull_trim(Scull_Dev *dev) /*遊走一遍整各串列*/ { /*並釋放所遇到的任何配額與配額集*/ Scull_Dev *next, *dptr; int qset = dev->qset; /* "dev" is not-null */ int i; for (dptr = dev; dptr; dptr = next) { /* all the list items */ if (dptr->data) { for (i = 0; i < qset; i++) if (dptr->data[i]) kfree(dptr->data[i]); kfree(dptr->data); dptr->data=NULL; } next=dptr->next; if (dptr != dev) kfree(dptr); /* all of them but the first */ } dev->size = 0; dev->quantum = scull_quantum; dev->qset = scull_qset; dev->next = NULL; return 0; }

  44. 3.7—相競狀況 • A與B都開啟了同一個scull裝置來寫入資料,並同時將自己的資料寫到scull記憶區的末端…… • 相競狀況(race condition),同時競爭相同資源。 • 單一CPU & SMP系統上 • Linux 核心提供了多項機制來迴避…<Ch9> • 權狀機制(struct semaphore) <asm/semaohore.h> • 驅動程式必須依照此程式介面來使用… • 在scull,各裝置獨自配置一個權狀並儲存在Scull_Dev結構裡 • 設定權狀初值的函式是sema_init( ) • 權狀初值必須為1表示目前在開放狀態 • 取得權狀的功能稱為P => down_interruptible( ) & down( ) • 釋放權狀的功能稱為V => up(&sem)

  45. 3.7—相競狀況 • 權狀的使用: • 受權狀保護的資料,必須被明確定義 • 不能同時使用兩個試圖取得權狀的函式(ex:down_interruptible() & down( )),否則會造成死結(deadlock)

  46. 3.8—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”的物件指標,代表 使用者正在存取的檔案位置

  47. 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 );

  48. 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 作業方法的引數之應用

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

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

More Related