37
Chap 3- 字字字字字字字字

Chap 3- 字元裝置驅動程式

  • Upload
    adolfo

  • View
    54

  • Download
    0

Embed Size (px)

DESCRIPTION

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

Citation preview

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

Chap 3- 字元裝置驅動程式

Page 2: Chap 3- 字元裝置驅動程式

Outline

• Introduction

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

Page 3: Chap 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 之間軟體介面

Page 4: Chap 3- 字元裝置驅動程式

3.1--scull 的設計藍圖• 定義驅動程式要提供哪些功能給 user-space 的程式

– 可循序存取 ( 字元裝置 ) or 可隨機存取 ( 區塊裝置 )

– 模擬單一裝置 (ex: 一機多體 or 多個同類裝置 )

• Scull 所模擬出的每一種裝置,分別由不同類型的模組予以實現 ( 相同的 machine 差別在於 policy 的不同 )– scull0~scull3 四個由記憶區所構成的裝置,兼具“共通”

“持續”– scullpipe0~scullpipe3 四個 FIFO 裝置 ( blocking 與

nonblocking )

– Scullsingle 一次只容許一個被行程存取– Scullpriv 允許每各終端機都有權開啟一次,分屬不同行程– Sculluid 允許開始多次,限同一使用者。 ( 回傳錯誤碼 )

– Scullwuid 允許開始多次,限同一使用者。 ( 推延 , 等待 )

Page 5: Chap 3- 字元裝置驅動程式

3.2-- 主編號與次編號• 主編號 (major number)(0~255)

– 代表裝置所配合的驅動程式– 當核心收到 open() 系統呼叫時,就是依據“主編號”來選擇驅

動程式• 次編號 (minor number)(0~255)

– 驅動程式以次編號來辨認同類裝置的個體– 核心本身用不到,只有驅動程式自己才知道次編號的意義

• 當使用者要存取字元裝置時,必須透過檔案系統裡的“代表名稱”特殊檔 (special file) 、裝置檔 (device file) 、 檔案系統樹的節點 (node) ,集中在 /dev/ 目錄下。

• 裝置類型:– “c” 代表 char driver 的特殊檔– “b” 代表 block driver 的裝置檔

Page 6: Chap 3- 字元裝置驅動程式

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

Page 7: Chap 3- 字元裝置驅動程式

• 在呼叫 register_chrdev_region() 時:– major 引數給‘ 0’ :

=> 回傳值為‘ >0 & <255’ 核心分配的主編號– major 引數給‘ >0 & <255’ :

=> 回傳值為‘ 0’ 表示核心同意你的要求– 發生錯誤時:

=> 回傳值為‘負數’• 如果你的驅動程式會被用於廣大群眾,或者有可能被

內入正式核心,則須設法申請專用的主編號。

3.2.1— 裝置編號的配置與釋放

Page 8: Chap 3- 字元裝置驅動程式

3.2.2— 動態配置主編號• alloc_chrdev_region(dev_t *dev, unsigned int firstminor,

unsigned int count, char *name)

• 動態分配主編號的缺點是無法事先建立 /dev/ 裝置節點,因為每次分配到的主編號不一定相同

Page 9: Chap 3- 字元裝置驅動程式

cat /proc/devices

Character devices:

1 mem

2 pty

226 drm

253 scull

254 pcmcia

Block devices:

2 fd

3 ide0

3.2.2— 動態配置主編號

Page 10: Chap 3- 字元裝置驅動程式

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( )

Page 11: Chap 3- 字元裝置驅動程式

Read()

Scull_read()

Page 12: Chap 3- 字元裝置驅動程式

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 *);– 用於擷取出裝置上的資料。

Page 13: Chap 3- 字元裝置驅動程式

• 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

Page 14: Chap 3- 字元裝置驅動程式

• 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)

Page 15: Chap 3- 字元裝置驅動程式

• 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)” 。

Page 16: Chap 3- 字元裝置驅動程式

3.3.2—file 結構• fops變數名稱用來表示 file_operation 結構 ( 或是指向此結構的指標 )

• 在 file_operations 結構中的每一個欄位,都必須指向驅動程式中負責特定作業方法。對於驅動程式不支援的作業項目 (method) ,其對應欄位就必須指向 NULL ,當核心遇到 NULL 時會採取適當的預設行為。

• 當再重新編譯新核心之後, file_operations 會指向NULL ,會採取預設行為。

• 每一種作業方法,其回傳值都有同樣的意義: 0 代表成功,負值為錯誤碼。

Page 17: Chap 3- 字元裝置驅動程式

• 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

3.3.2—file 結構

Page 18: Chap 3- 字元裝置驅動程式

• 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.

3.3.2—file 結構

Page 19: Chap 3- 字元裝置驅動程式

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

Page 20: Chap 3- 字元裝置驅動程式

• struct dentry *f_dentry;– 檔案所屬的“目錄項”結構 (directory entry ,通常不理會

dentry 結構,頂多也只是透過 file->f_dentry->d_inode 來存取inode 結構而已。

• file 結構實際的欄位超過以上所述。事實上驅動程式決不會自己填寫 file 結構,而只會存其他地方產生的結構。

3.3.2—file 結構

Page 21: Chap 3- 字元裝置驅動程式

3.3.3 - inode 結構• 核心內部使用 inode結構代表檔案,不同於 file結構, inode結構是代表已開啟的檔案的 FD。

• dev_t i_rdev;– 對於代表檔案裝置檔的 inodes,此欄位含有實際的裝置編號 (主次編號的組合 )。

• struct cdev *i_cdec– struct cdev 是核心內部用來表示字元裝置的結構。對於代表字元裝置的 inode,此欄位含有一個指向該結構的指標。

Page 22: Chap 3- 字元裝置驅動程式

3.5— open & release

• Open 作業方法提供驅動程式一個機會,為後續的作業進行任何必要的初始化準備工作。此外, open 通常還會遞增目標裝置的“用量計次,以免模組檔案關閉之前被謝載。

• Release 作業方法負責遞減用量計次 (usage count) 。 < 定義在 linux/module.h>

– MOD_INC_USE_COUNT :將目前模組使用次數加一– MOD_DEC_USE_COUNT:將目前模組使用次數減一

Page 23: Chap 3- 字元裝置驅動程式

3.5.1— open 作業方法• 大部份驅動程式的 open 作業方法,應該執行下列動作:

– 遞增用量計次– 檢查裝置特有的錯誤 (ex: 數據機佔線 , 沒放光碟片… )

– 如果目標裝置是第一次被開啟,則應該進行初始化程序– 辨認次編號,並更新 f_op指標– 配置並裝填任何要放在 filp->private_data 的資料結構– 開啟的第一步是是要看目標裝置的次編號為何? < 在 scull 是檢查 inode->i_rdev>

less main.c

Page 24: Chap 3- 字元裝置驅動程式

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;}

Page 25: Chap 3- 字元裝置驅動程式

3.5.2— release 作業方法• 如果再 open 有用遞增用量計次,則 release 就必須遞減

之。因為只要用量計次沒歸零,核心就會拒絕卸載模組。

• 有時候,一個檔案被開啟與被關閉的次數不一定相同,如何能夠確定用量計次一定正確?– dup( ) 和 fork( ) 系統呼叫會直接建立以開檔案的副本,而不會

啟動驅動程式的 open ,但這些副本在程式結束時卻會觸發close( ) 系統呼叫…

– ANS :並非每次 close( ) 系統呼叫都會觸發 release 作業方法。close( ) 只有再真正需要釋放裝置上的資料時,才會去呼叫release 這就是為何取名 release而不是 close 的原因

• 其它類型的裝置有各自的關閉函式,因為 scull_open( )已經依據裝置類型,讓 filp->f_op指向該類裝置的作業方法。

Page 26: Chap 3- 字元裝置驅動程式

3.6— Scull 的記憶體用法規劃• 本節只專注 scull 的記憶體配置策略,而不涉及真實驅

動程式所需的硬體管理計技巧• scull 裝置是一個長度可隨資料量而變的記憶區

– 寫入的資料越多,長度自然增加– 如果用較短的資料去蓋寫它,長度也會跟著縮減– 不限制“裝置”區的大小

• 在結構上,每個 scull 裝置都是一個“指標鏈結串列” (a linked list of pointers) ,每個指標分別指向一個Scull_Dev 結構 (最多可代表四百萬位元組 ) 。– 這個陣列含有 1000 個指標 配額集 (quantum set)

– 每個指標指向一個 4000bytes 大小的區域 配額 (quantum)

Page 27: Chap 3- 字元裝置驅動程式

3.6 — Scull 的記憶體用法規劃• 配額與配額集應該佔用多少記憶量才合適?

– 是操作策略 (policy)上的問題,與機制 (machanism) 無關。– “擬定合理的預設值,同時提供使用者修改的彈性”

• Scull 容許使用者以多種方式來修改配額量:– 編譯期:修改 scull.h 內的 SCULL_QUANTUM 與

SCULL_QSET 常數值。– 模組載入期: scull_quantum 與 scull_qset變數值。– 執行期:使用 ioctl( )修改目前設定值與預設值。

• 如何決定預設值?– “半滿配額與配額集所浪費掉的記憶量”與“如果配額量太小,以致於需要釋放原有記憶體 , 重新配置 , 重建串鏈所造成的效率損耗”兩者之間找出平衡點。

• kmalloc( ) 的內部設計也應該列入考量…

Page 28: Chap 3- 字元裝置驅動程式

Scull_Dev

nextdata

Scull_Dev

nextdata

Scull_Dev

nextdata

配額集 個別配額

scull scull 裝置裝置的記憶佈局的記憶佈局

Page 29: Chap 3- 字元裝置驅動程式

3.7—read & write

• filp 檔案指標• buff 引數指向 user-space 的緩衝區• count 要被傳輸的資料量• offp 是一個指向“ long offset type” 的物件指標,代

表 使用者正在存取的檔案位置

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

Page 30: Chap 3- 字元裝置驅動程式

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

Page 31: Chap 3- 字元裝置驅動程式

ssize_t dev_read( struct file *filestruct file *file, char *bufchar *buf, size_t countsize_t count, loff_t loff_t *ppos*ppos);

struct file

f_countf_flagsf_mode

f_pos

….….

Buffer(in the

Kernel-space)

Buffer( in the

user-spaceor

Libc )

Copy_to_user( )

Read Read 作業方法作業方法的引數之應用的引數之應用

核心空間( 不可置換 )

使用者空間(可被置換 )

Page 32: Chap 3- 字元裝置驅動程式

3.7.1—read 作業方法• 若傳回值等於當初傳給 read( ) 系統呼叫的 count 引數,那表示當初要求傳輸的資料已經全數傳輸成功了。

• 若傳回值大於 0 ,但小於 count ,則表示只順利傳輸了部份資料,原因多與目標裝置有關,通常應用程式會再嘗試重新讀取。

• 若傳回值為 0 。代表已經遇到檔案尾端 (EOF) 。• 若傳回值為 -1( 不可能有其他負數 ) ,則代表發生了某

種錯誤。

Page 33: Chap 3- 字元裝置驅動程式

3.7.2—write 作業方法• 若傳回值等於 count ,表示要求的資料量已經全數被寫

入裝置。• 若傳回值為正數值,但小於 count ,表示只有部份資料

被寫入裝置。呼叫者應該再試幾次,將其餘資料也寫入裝置。

• 若傳回值為 0 ,表示沒寫入任何資料。不代表發生錯誤,所以不會傳回代碼。應該是著重新發出 write( ) 呼叫。

• 若傳回負值,表示發生了某種錯誤。如同 read 的狀況,錯誤代碼的意義定義在 <linux/errno.h>

Page 34: Chap 3- 字元裝置驅動程式

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

裝置類型 主編號 次編號 代表名稱

Page 35: Chap 3- 字元裝置驅動程式

DEMO - 2

• 檔案系統製作裝置節點的命令是 mknod ,必須有特權身分 (root) 才能使用此工具。至少需要四個引數…– < 代表名稱 > < 裝置類型 > < 主編號 > < 次編號 >

mknod /dev/ant c 252 0

• 像任何儲存在磁碟上的普通檔案一樣, mknod 所產生的裝置節點會被保存下來,除非刻意刪除它們。用一般的 rm命令即可辦到… < 不使用時未刪除及佔用空間>

rm /dev/ant

Page 36: Chap 3- 字元裝置驅動程式

DEMO - 3

• ./scull_load

• cat /proc/devices

• ./scull_unload

Page 37: Chap 3- 字元裝置驅動程式

DEMO - 4

• make

• insmod ./scull.ko

• 可以在記憶區製造出一個字元裝置,並且對它做寫入資料或讀取