最近因為想學習撰寫一隻 Linux device driver,所以先藉由 Linux Device Driver Programming 驅動程式設計,這本書裡的select & poll 的範例程式開始學習。但是因為這本書當時再寫的時候,kernel版本大致分為 2.4 和 2.6,版本比較舊,有些函式已有變動,所以有做一些修改。(我的Kernel版本為4.2版)
驅動程式內部能夠使用到 printf(),因為kernel space沒有直接對應的console (鍵盤,畫面)。但是還是有可代用的function printk()。它輸出的資料會跑到kernel buffer內。kernel buffer 可以用 dmesg 指令查閱,不過空間才只有 128KB(default),而且是環狀的形式(所以心資料會蓋過最舊的資料)。因此不能一直把資料保留在裏面,可以用syslogd 或是 klogd 之類的程式把資料寫到 syslog 裡(var/log/message),不過這種方法還是可能會漏掉訊息。
下面是訊息等級,在 kernel 4.2 版本,預設等級是 log level。但是在 kernel 2.6 版本預設等級是 4。
驅動程式的進入點並不是 main() ,因為驅動程式與一般應用程式是不同的,而且必須準備多個,其中至少要兩個進入點
其他進入點還包含
如果 User process要跟驅動程式交換資料,必須要透過device file(裝置檔),這個檔案可以透過mknod的函式呼叫產生,如 line 153。也可以用指令 mknod 來產生。產生完後會在/dev的資料夾下出現裝置檔。
mknod 裝置檔名 種類 主編號 副編號
刪除的話,只需直接 rm 即可。
mknod 指令可以指定的範圍
Major number 和 Minor number 的功能是當 User process 打開裝置檔的時候,能找到對應的驅動程式來處理。
Kernel 的 open() 系統呼叫介面是 sys_open(),但是要怎麼知道是藥用哪個驅動程式來處理,就要用到 Major number。所以Major number必須是要獨一無二的。Major number與指向驅動程式的指標會存放在 kobj_map對應表中。所以當kernel 發起 sys_open()之後,就會去查詢kobj_map,透過Major number找出對應的驅動程式連結起來。
而Minor number是驅動程式自己管理,kernel不管的,如果有多個裝置,就必須要用Minor number來區分。
User Process 與驅動程式的溝通管道
Major number 登記方法
為了要讓裝置檔和驅動程式連起來,驅動程式要先跟kernel登記 major number。
登記方法可以分為兩種
c語言的struct 初始化可以使用gcc擴充語法 :
struct file_operations devone_fops = {
.open = devone_open,
.release = devone_close,
};
書上介紹了 open, close, read, write的 handler。
int (*open) (struct inode *inode, struct file *file);
return 0 : 成功,否則 錯誤
inode 結構成員
i_private 是驅動程式可以自由使用的指標。常見的用途
int (*release) (struct inode *inode, struct file *file);
如果開啟時候有配置資源的,就必須要在close handler內釋放。否則會倒置記憶體洩漏。
一個 user process或是多個user process 可能同時重複開啟同一個裝置檔。可以透過inode或是file結構內的私有指標來判斷是誰開啟的。
inode結構是在mknod指令建立裝置檔的時候由kernel建立的,因此
ssize_t *read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
buf 是 user process 呼叫 read()時指定的緩衝區指標。但是因為kernel space與user space的虛擬記憶體空間不同,所以驅動程式不能直接取用buf指標,必須要透過 copy_to_user() 這個function把資料複製過去。
ssize_t *write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
user想傳資料給驅動程式使用的話,要用copy_from_user(),從buf讀資料。
在OS 偵測到新硬體的時候,自動建立裝置檔。
在移除裝置的時候,自動刪除裝置檔 (hotplug)機制
下圖是 udev 機制 :
open() 系統呼叫,預設會以blocking mode開啟,意思是process等待事情發生,而進入sleep。而read/write也是一樣,如果沒有資料可以供讀寫就會block住。
Linux 應用程式大多是以blocking mode開發,Windows主流是 non-blocking mode。 但是對於應用程式來說,有時read會被block住的話,會造成設計上的問題,因此Linux多了一些功能 :
同時執行多個同步IO工作(synchronous IO multiplexing),使用select系統呼叫的作法。透過select可以監視多個file handle在三種狀態之間變化(可讀,可寫,發生錯誤)。但是select本身會block住,可以設定timeout,最多5秒。 因此可以在呼叫read()之前先呼叫select()判斷資料抵達了沒。
另外還有一個跟select()很像的是poll()。不同的是指定file handle的方式不同,而且在指定多個file handle的時候,poll()走訪所有file handle的速度比較快。
Reference :
驅動程式筆記與程式碼講解
函式加上 static ,讓命名空間限制在檔案內。不過在這裡就算不加static也不會影響kernel整體的符號表。這是只緊限於kernel module的時候,也就是動態載入的驅動程式來說。如果是希望其他module能夠呼叫的話,就必須要使用EXPORT_SYMBOL來明確匯出函式。驅動程式內部能夠使用到 printf(),因為kernel space沒有直接對應的console (鍵盤,畫面)。但是還是有可代用的function printk()。它輸出的資料會跑到kernel buffer內。kernel buffer 可以用 dmesg 指令查閱,不過空間才只有 128KB(default),而且是環狀的形式(所以心資料會蓋過最舊的資料)。因此不能一直把資料保留在裏面,可以用syslogd 或是 klogd 之類的程式把資料寫到 syslog 裡(var/log/message),不過這種方法還是可能會漏掉訊息。
下面是訊息等級,在 kernel 4.2 版本,預設等級是 log level。但是在 kernel 2.6 版本預設等級是 4。
驅動程式的進入點並不是 main() ,因為驅動程式與一般應用程式是不同的,而且必須準備多個,其中至少要兩個進入點
- insmod 與 modprobe 呼叫的初始化函式
- rmmod 呼叫的結束函式
其他進入點還包含
- 系統呼叫
- 中斷服務程序
- 計時器程序
驅動程式碼
如果 User process要跟驅動程式交換資料,必須要透過device file(裝置檔),這個檔案可以透過mknod的函式呼叫產生,如 line 153。也可以用指令 mknod 來產生。產生完後會在/dev的資料夾下出現裝置檔。
mknod 裝置檔名 種類 主編號 副編號
刪除的話,只需直接 rm 即可。
mknod 指令可以指定的範圍
種類 | 範圍 |
---|---|
Major number | 0~4095 |
Minor number | 0~1048575 |
Major number 和 Minor number 的功能是當 User process 打開裝置檔的時候,能找到對應的驅動程式來處理。
Kernel 的 open() 系統呼叫介面是 sys_open(),但是要怎麼知道是藥用哪個驅動程式來處理,就要用到 Major number。所以Major number必須是要獨一無二的。Major number與指向驅動程式的指標會存放在 kobj_map對應表中。所以當kernel 發起 sys_open()之後,就會去查詢kobj_map,透過Major number找出對應的驅動程式連結起來。
而Minor number是驅動程式自己管理,kernel不管的,如果有多個裝置,就必須要用Minor number來區分。
User Process 與驅動程式的溝通管道
- 通常是以 open()的系統呼叫來打開裝置檔
- 透過write() 將資料傳給驅動程式
- 透過read() 從驅動程式取資料
- 最後要以close() 關閉裝置檔
Major number 登記方法
為了要讓裝置檔和驅動程式連起來,驅動程式要先跟kernel登記 major number。
登記方法可以分為兩種
- 靜態(傳統)
這是kernel 2.4的作法,之後都是採動態登記,但是也是可以使用。
int register_chrdev(unsigned int major, const char *name, const struct file_operation *fops);
它是在 fs/char_dev.c的檔案內實作的。登錄成功之後,可以在 /proc/devices 顯示驅動程式名稱。
major也可以傳入 0,如此kernel會自動分配一個沒用到的major number。
卸載的時候,用
void unregister_chrdev(unsigned int major, const char *name); - 動態
流程:
以 alloc_chrdev_region() 動態取得major number- int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
- void cdev_init(struct cdev *cdev, const struct file_operation *fops);
初始化cdev結構,並登記系統呼叫handler(fops)。
- int cdev_add(struct cdev *p, dev_t dev, unsigned count);
dev 是 alloc_chrdev_region()配置的major number和minor number 交給 MKDEV()造出來的數值。
cdev_del() 向kernel釋放驅動程式- void dcdev_del(struct cdev *p);
- void unregister_chrdev_region(dev_t from, unsigned count);
驅動裝置登記過程在 devone_init() function。
系統呼叫處理函式的登記方式
user process對驅動程式下達了系統呼叫,驅動程式也必須要有對應的handler才可以。
驅動程式的handler是透過 file_operations 結構指定的,如下。
其中必須注意的是 不同 kernel版本file_operations的結構也會稍有不同,所以必須留意!
c語言的struct 初始化可以使用gcc擴充語法 :
struct file_operations devone_fops = {
.open = devone_open,
.release = devone_close,
};
書上介紹了 open, close, read, write的 handler。
open handler
prototype:int (*open) (struct inode *inode, struct file *file);
return 0 : 成功,否則 錯誤
inode 結構成員
inode 結構成員 | 意義 |
---|---|
bdev_t i_rdev | Major/minor number |
void *i_private | 驅動程式私有指標 |
i_private 是驅動程式可以自由使用的指標。常見的用途
- 在open handler 配置記憶體,設給這個指標
- 在write與read handler運用這塊記憶體
- 在close handler釋放記憶體
file結構成員
file 結構成員 | 意義 |
---|---|
struct file_operations *f_op | 系統呼叫handlers |
unsigned int f_flags | open 函式第2引數傳入的flag |
void *private_data | 驅動程式私有資料指標 |
close handler
prototype:int (*release) (struct inode *inode, struct file *file);
如果開啟時候有配置資源的,就必須要在close handler內釋放。否則會倒置記憶體洩漏。
一個 user process或是多個user process 可能同時重複開啟同一個裝置檔。可以透過inode或是file結構內的私有指標來判斷是誰開啟的。
inode結構是在mknod指令建立裝置檔的時候由kernel建立的,因此
- inode 結構的個數 : 1個
- file 結構的個數 : 開檔次數
read handler
prototype:ssize_t *read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
buf 是 user process 呼叫 read()時指定的緩衝區指標。但是因為kernel space與user space的虛擬記憶體空間不同,所以驅動程式不能直接取用buf指標,必須要透過 copy_to_user() 這個function把資料複製過去。
write handler
prototype:ssize_t *write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
user想傳資料給驅動程式使用的話,要用copy_from_user(),從buf讀資料。
udev
為了方便管理裝置檔的機制。在OS 偵測到新硬體的時候,自動建立裝置檔。
在移除裝置的時候,自動刪除裝置檔 (hotplug)機制
下圖是 udev 機制 :
驅動程式的支援
- 登記 class
class *class_create(struct module *owner, const char *name); - 卸載驅動程式
void class_destroy(struct class *cls); - 接著要建立 /sys/class/class名稱/裝置名稱
struct device *device_create(struct class *cls, struct device *parent, dev_t dev, void *drvdata, const char *fmt, ...); - 卸載時要先刪除登記的裝置檔
void device_destroy(struct class *cls, dev_t devt);
Linux 應用程式大多是以blocking mode開發,Windows主流是 non-blocking mode。 但是對於應用程式來說,有時read會被block住的話,會造成設計上的問題,因此Linux多了一些功能 :
- Non-blocking mode
- 同時執行多個同步IO工作
同時執行多個同步IO工作(synchronous IO multiplexing),使用select系統呼叫的作法。透過select可以監視多個file handle在三種狀態之間變化(可讀,可寫,發生錯誤)。但是select本身會block住,可以設定timeout,最多5秒。 因此可以在呼叫read()之前先呼叫select()判斷資料抵達了沒。
另外還有一個跟select()很像的是poll()。不同的是指定file handle的方式不同,而且在指定多個file handle的時候,poll()走訪所有file handle的速度比較快。
Reference :
留言
張貼留言