系統呼叫處理函式的登記方式
想讓 user process 能對驅動程式發出 open() 與 read() 之類系統呼叫的話,驅動程式必須先向 kernel 登記相應的 handler 才行。 驅程程式的 handler 是透過 file_operations 結構指定的,其宣告在 include/linux/fs.h 內。 C 語言在初始化 struct 的時候需要記得成員的定義位置,但如果用上 gcc 擴充語法的話,就不必記得這些位置,比如說,可以寫成下面這個樣子:
struct file_operations devone_fops = {
.open = devone_open,
.release = devone_close, }
這樣就會將 open 及 release 成員指定為驅動程式提供的函式指標,並自動把其它成員設為 NULL。
open handler
User process 在操作 device file 時,第一個動作一定是「開啟」 ,結束操作之後則會將它「關閉」。 因此,驅動程式至少要提供 open 與 close 這兩個 handler。
int (*open) (struct inode *inode, struct file *file);
inode 引數是內含「inode」資料的結構指標,開發驅動程式時會用到的成員如下: cdev_t i_rdev: Major/minor number void i_private: 驅動程式私有指標 驅動程式可以透過 i_rdev 成員判別驅動程式內部使用的 minor number,想改變控制硬體對象時,可以使用。 取出 minor number 的方法有兩種,如下: ret = MINOR(inode->i_rdev); ret = iminor(inode) 考慮到可攜性,還是使用 iminor() 比較好,想得到 major number ,亦是使用同樣的方法: unsigned iminor(const struct inode inode); unsigned imajor(const struct inode *inode); i_private 成員是驅動程式可以自由使用的指標。
close Handler
close handler 的 prototype 如下,這邊要注意它的成員名稱是「release」:
int (*release) (struct inode *inode, struct file *file);
引數與 open handler 一樣,被開啟時如果有配置資源的話,一定要在 close handler 之內釋放掉,否則會導致 memory leak 的問題,只要 OS 沒有重開,就無法釋放資源。 就算 user process 忘記明確呼叫 close(),OS 也會在 user process 結束的時候呼叫 close()。 一個或多個 user process 可能同時重複開啟同一個 device file,想在 close handler 被呼叫時,得知目前是對應哪個 open() 開啟的話,可以透過 inode 或 file 結構的驅動程式私有指標來判斷。
read Handler
這邊要介紹 read 與 write handler 的實作方式:
ssize_t *read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
只要這樣定義 handler,驅動程式就能把資料傳給 user process。 filp 引數指向開啟裝置檔時 kernel 建立的 file 結構,跟 open 收到的指標是同一個,因此,open handler 設給「filp->private_data」成員的指標,在 read handler 裡也可以拿來用。 buf 引數是 user process 呼叫 read() 時指定的緩衝區指標,驅動程式無法直接取用 buf 指標,必須透過 copy_to_user() 這個 kernel 提供的函式把資料複製過去,原因是之前所提有關「kernel space」與「user space」的不同。 count 引數是 user process 呼叫 read() 時提供的緩衝區空間。 f_pos 引數是 offset,驅動程式可以視需要將之更新。 範例:
static ssize_t dev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos){
printk(KERN_INFO "read\n");
printk(KERN_INFO "Offset is %lu.\n",*f_pos);
if(data[*f_pos]=='\0'){
printk(KERN_INFO "End of string, returning zero.\n");
return 0;
}
copy_to_user(buf,&data[*f_pos],1);
*f_pos += 1;
return 1;
}
這邊dev_read去讀取一個名為data的字串,當字串中出現'\0'表示字串結束,不再讀取,
這邊dev_read去讀取一個名為data的字串,當字串中出現'\0'表示字串結束,不再讀取,利用copy_to_user
這個函數將字串內容傳到user space,命令列輸入cat /dev/device便可以看到字串內容,f_pos的意義在標示目前檔案讀取到的位置,每當讀完一次便+1移到下個位置,沒有設置的話,cat的值會一直不斷的跑值下去,因為每一次讀取都會從頭開始,要用crtl+c來中斷。
write Handler
ssize_t *write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
write handler 的 prototype 與 read handler 大致相同。 write handler 是在 user process 要傳資料給驅動程式時使用的,驅動程式需透過 kernel 提供的 copy_from_user() 函式從 buf 引數讀入資料,緩衝區的大小是 count bytes。
static ssize_t dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos){
int ret = 0;
printk("<1>EXAMPLE: write (size=%zu)\n", size);
if(copy_from_user(&data,buf,count)){
ret = -EFAULT;
printk("copy_from_user failed!\n");
return ret;
}
printk("kernel-data:%s\n",data);
return size;
}
dev_write利用copy_from_user
從user space取值進來,儲存到data這個空陣列,如果回傳值不等於0就顯示失敗的訊息。