52
Chap 3- 字字字字字字字字

Chap 3- 字元裝置驅動程式

  • Upload
    anson

  • View
    54

  • Download
    0

Embed Size (px)

DESCRIPTION

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

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

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

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

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

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

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

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

ng )

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

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

Ch_5.2.5

Ch_5.6

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-- 主編號與次編號 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 7: Chap 3- 字元裝置驅動程式

3.2-- 主編號與次編號• 檔案系統製作裝置節點的命令是 mknod ,必須有特權

身分 (root) 才能使用此工具。至少需要四個引數…– < 代表名稱 > < 裝置類型 > < 主編號 > < 次編號 >

mknod /dev/ant c 252 0

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

rm /dev/ant

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

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

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

3.2.1-- 隨機取得主編號• 在呼叫 register_chrdev() 時:

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

– major 引數給‘ >0 & <255’ :=> 回傳值為‘ 0’ 表示核心同意你的要求

– 發生錯誤時:=> 回傳值為‘負數’

• 如果你的驅動程式會被用於廣大群眾,或者有可能被內入正式核心,則須設法申請專用的主編號。

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

3.2.1-- 隨機取得主編號 cat /proc/devices

Character devices:

1 mem

2 pty

226 drm

253 scull

254 pcmcia

Block devices:

2 fd

3 ide0

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

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

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

#!/bin/shmodule="scull“ <scull_load>device="scull"mode="664"# Group: since distributions do it differently, look for wheel or use staffif 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 1major=`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 0mknod /dev/${device}1 c $major 1mknod /dev/${device}2 c $major 2mknod /dev/${device}3 c $major 3ln -sf ${device}0 /dev/${device}chgrp $group /dev/${device}[0-3]chmod $mode /dev/${device}[0-3]

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

#!/bin/shmodule="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}privrm -f /dev/${device}pipe /dev/${device}pipe[0-3]rm -f /dev/${device}singlerm -f /dev/${device}uidrm -f /dev/${device}wuid

• scull_load & scull_unload也適用其他其他驅動程式,只需重新定義變數,並稍微調整 mknod那幾行即可。

• 因為指令搞需要特權身分才能執行,而新建出來的特殊檔自然會屬於 root。但裝置節點的存取權限通常不是這樣,所以要修改權限的擁有權。

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

3.2.2—移除驅動程式• 在模組被卸載之前,他必須先釋放主編號,而這動作

可由 unregister_chrdev() 完成,應該在模組的清理函式理呼叫他:

– major 表示要被釋放的主編號; name 是當初註冊的名稱< 必須與當初呼叫 register_chrdev() 所用的引述一致 >

• 對於動態取得主編號的驅動程式,如果在卸載模組之後沒有順便除掉相關的 /dev 結點,就可能產生意料外的錯誤。譬如,已經沒有作用的 /dev/framegrabber 可能在一個月後指向火災警報器 <如果兩個驅動程式都使用隨機取得的主編號 >

int unregister_chrdev(unsigned int major, const char *name) :

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

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 的存在。 => 核心改版可以放手修正,不會影響任何驅動程式

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

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 來代替 >

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

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

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

Read()

Scull_read()

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

3.3— 檔案作業• fops變數名稱用來表示 file_operation 結構 (或是指向

此結構的指標 )

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

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

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

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

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 ,則 rea

d() 系統呼叫發生 -EINVAL失敗。 (Invalid argument ,引數值無效 ) ,如果成功,則傳回一個非負數值,代表成功讀取的位元數。 <ssize_t 是一個 64-bits 的 int>

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

• 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

Page 22: 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 結構被釋放之前,此方法會被呼叫。 (並非,每次呼叫 cl

ose( ) 都會觸發 release)

Page 23: 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 *);– 以上兩者都是 2.3版研發過程中才提出來的新作業發訪,分別

用於實現“分散讀取 (scatter read)” 以及“累積寫出 (gather write)”

Ch_5

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

3.3— 檔案作業• scull 驅動程式只實作最重要的幾項作業方法,而起 G

NU 的標記語法來宣告初始話他自己的 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,};

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

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

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

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.

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

• unsigned short f_flags;– 各項“檔案旗標”的組合,像是 O_RDONLY, O_NONBLOC

K 與 O_SYNC 等等。驅動程式在進行非推延作業 (nonblocking operation) 時,會需要檢查這些旗標。 ( 所有的旗標都定義在 <linux/fcntl.h>)

• struct file_operations *f_op;– f_op為指向 file_operation 結構的指標。每當需要提調 (dispatc

h) 任何檔案作業時,核心會讀取此指標,找出對應的作業方法。核心決不會快取 filp->f_op 的值 <,ex: 主編號為 1 的檔案(/dev/null, /dev/zero …)其搭配的 open 作業方法會依據開啟對象的次編號,將 flip->f_op指向不同的作業方法,此技巧使得主編號相同的一系列檔案,可以表現出多種不同的行為模式,核心容許這種替換檔案作業方法的能力,相當於物件導向的“ method overrideing”技術 >

• void *private_data;– Private_data 是相當有用的資源,可供我們保存生命其跨越多

次系統呼叫的狀態資訊。驅動程式可以自由決定要如何運用此指標,甚至完全忽略,但必須記得在 release method 裡釋放掉此指標所占用的記憶體。

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

3.4—file 結構• struct dentry *f_dentry;

– 檔案所屬的“目錄項”結構 (directory entry ,通常簡稱為 dentry) 。目錄項的處理城係已經被最佳化了,所以通常不理會 dentry 結構,頂多也只是透過 file->f_dentry->d_inode 來存取 inode 結構而已。

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

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

3.5— open & release

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

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

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

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

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

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

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

less main.c

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

3.5.1— open 作業方法• 核心不需要裝置的次編號,所以驅動程式可對次編號

自由賦予任何意義。• 實務上,次編號常被用來區別“同類裝置的不同個體

”或“同一個裝置的不同操作模式”。– Ex:/dev/st0( 次編號 0) 與 /dev/st1( 次編號 1) 分別代表不同的 S

CSI 磁碟機,但是 /dev/nst0( 次編號 )128其實與 /dev/st0 是同一台機器,只不過作模式不同而已 (/dev/nst0 被關閉之後不自動回帶 )

• 使用者可用自己慣用的名稱來代表常用裝置,也就是將常用裝置連結到另依各比較人性化的檔名。– Ex: 用 /dev/cdrom 代表 /dev/hdc ,用 /dev/modem 代表 /dev/tty

S1

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

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) /* 低四位元 */

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

• 對於每種裝置類型, scull 分別定義一個專屬的 file_operations 結構,並使 open將 filp->f_op指向這些結構。以下示範 scull 驅動程式如何製作多個 fops :

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 裡的那一組 fopsint 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];…略…

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

• 一般而言,驅動程式不必呼叫自己的作業方法,因為 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 */ }

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

• Scull_Dev 是此驅動程式用來持有記憶區的資料結構• scull_nr_devs 是可用裝置的數量• scull_devices[] 實際指向 Scull_Dev 結構的指標陣列

/*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; /* 成功 */}

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

3.5.1— open 作業方法• 這段程式看起來似乎有點空洞?

– 未實作任何處理硬體的動作,• 並未計算它目前被開啟了多少次?

– scull 的本質被設計成通用 , 持續性的裝置• 核心能透過 file_operations 結構 owner欄位來維護模組

的用量計次,為何要再作一次同樣動作?– 為了與舊版核心相容

• 為何要再 write_only 模式時將記憶區長度歸零?– open 是唯一會影響目標裝置的動作,除了 write only 之外的其他存取模式, open 不必作任何事。

– 根據設計理念,當 scull 存有大量資料時,若被較少量的資料蓋寫,則最後會只剩較短的記憶區,讓多的資料消失。

Page 37: 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){

MOD_DEC_USE_COUNT;return 0;

}

Page 38: 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 39: Chap 3- 字元裝置驅動程式

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

動程式所需的硬體管理計技巧 (Ch8&9)– scull 裝置是一個長度可隨資料量而變的記憶區– 寫入的資料越多,長度資然增加– 如果用較短的資料去蓋寫它,長度也會真著縮減– 不限制“裝置”區的大小

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

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

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

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

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

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

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

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

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

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

Scull_Dev

nextdata

Scull_Dev

nextdata

Scull_Dev

nextdata

配額集 個別配額

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

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

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;

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

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

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

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)

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

3.7— 相競狀況• 權狀的使用:

– 受權狀保護的資料,必須被明確定義– 不能同時使用兩個試圖取得權狀的函式 (ex:down_interruptible

() & down( )) ,否則會造成死結 (deadlock)

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

3.8—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 47: 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 48: Chap 3- 字元裝置驅動程式

ssize_t dev_read( struct file *filestruct file *file, char *bufchar *buf, size_t countsize_t count, loff_t *pposloff_t *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 49: Chap 3- 字元裝置驅動程式

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

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

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

種錯誤。

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

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

• 若傳回值為正數值,但小於 count ,表示只有部份資料被寫入裝置。呼叫者應該再試幾次,將其餘資料也寫入裝置。

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

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

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

3.9—try scull

• 一但驅動程式具備了 open, release, read, write ,就可以開始編譯並測試它了。

• 製作出來的 scull” 裝置”,就像一個可在 kernel-space自由伸展的資料緩衝區, RAM 有多大, scull 就能裝多少資料 .

• 使用 cp, dd或 I/O轉向命令來測試 scull 驅動程式cp xxx /dev/scull0 或 ls –l > /dev/scull0

• 利用 strace 工具來監視程式發出的系統呼叫• 使用 free 來測試 scull 的記憶體用量

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

3.10—devfs 檔案系統• 新版 linux 核心特地為裝置節點提供的新的特殊檔案系

統。• 到 2.3.46版本才正式納入核心。• 使用 devfs 的好處:

– 在 /dev 目錄下的裝置節點,由裝置初始化程序產生,再移除裝置時移除。

– 驅動程式可以指定裝置名稱 , 擁有權 , 權限位元。不過, user-space 的程式仍然可以修改擁有權與權限位元。

– 不需要為目標裝置擇定一個主編號與次編號。• 相容性問題:

– 為了維持 devfs 的相容性,有些程式片段必須以 CONFIG_DEVFS_FS為是否要編譯的片段條件。