Skip to content

Char Driver 實作

char driver相較其他兩者較為簡單
本章將以scull (Simple Character Utility for Loading Locality)為例介紹char driver

sucll

所謂scull,是以一塊記憶體為裝置的char driver
scull 無關硬體,只與被kernel分配的記憶體互動

設計

scull可以作為使用computer memory 的driver的基礎
同時scull有多種type,可以參考這裡

  • Note: 可以看到各種scull非常有記憶體的影子

Major and Minor Number

在/dev ls -l 可以看到每行檔案(or 資料夾)最開頭有英文字或-,代表其檔案類型

- 代表一般檔案
d 代表目錄
b block driver
c character driver
l linking file (超連結)
p pipeline file
s socket file

而c就表示char driver,也就是我們本章的目標

其中另一區,修改時間前面的兩個數字,就是Major 與 minor number
Majar number 表達了該device (Review : device在linux中皆為檔案)
被哪一個driver管理,舉例而言:
vcs1 and vcsa1 devices 皆被 driver 7 管理

minor number 則是用來區分具體裝置的,就像是旅館名-房號決定一個房間
major number + minor number 也決定一個裝置

在2.6版本,通常會用dev_t這個資料結構紀錄device number , 也會有對應的函式取用
但有可能未來版本會更動

寫char driver第一步就是取得使用的裝置number

利用

int register_chrdev_region(dev_t first, unsigned int count,
 char *name);

實現,其中first是該裝置第一個device number , 而count會往後要多個device(依device number連續往下) name則是為這一段device 取名 ,最後會出現於 /proc/devices 和 sysfs

  • Note: 如我count太大,可能會spill到其他major number (旅館房間裝不下,借其他旅館)
    此時只要有足夠的可用device,就沒有問題(其他旅館與原先旅館是相同的,就沒有問題)
    但大多數情況下,因為不曉得major number,而會使用
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,
 unsigned int count, char *name);

來分配,其中dev是輸出型parameter,最後值會被改成allocated range的第一個數字
firstminor 則是第一個minor number,通常為0
剩下與register無異

動態的用alloc分配也可以避免很多衝突與麻煩

當然,使用完成也需要call unregister_chrdev_region(dev_t first, unsigned int count);
來釋放資源 (通常放在exit function)

三大常見資料結構

* File Operations

通常會把file 視為物件,file operation視為其method,這種OOP概念在linux kernel 中很常見
file_operations 通常用 fops 簡稱
這個結構裡大多數欄位都是一個指向一個file operation function的指標 (若沒有支援會留下NULL,而每個欄位對NULL的應對方式又有所不同)
以下是一些重點欄位介紹

  • struct module *owner
    指向"擁有"這個結構的module,防止module被卸載後,結構裡的操作函式還在被使用
    通常直接給THIS_MODULE即可

  • loff_t (*llseek) (struct file *, loff_t, int);
    用於移動檔案當下讀/寫位置,而後會回傳一個新位置(正數,負數代表錯誤) loff_t代表long offset,至少是64bits 長
    如果是NULL會有無法預測的行為發生

  • ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    取用device的data
    如果是NULL會造成fail
    會回傳成功讀取多少byte

  • ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    送資料到device
    如果是NULL會造成fail
    會回傳成功寫入多少byte

  • int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); ioctl (input/output control)
    是一種system call 提供針對特定裝置的操作(device-specific)指令
    比如格式化某個機械式硬碟的track
    如果裝置為提供這種操作,會回傳error

  • int (*open) (struct inode *, struct file *); 雖然這是device file的第一個操作 (一定要先開檔才有後續操作)
    但是是可以給NULL的,此時總是會成功開啟device,然而這種情況下driver不會收到通知

  • int (*release) (struct inode *, struct file *); 與open相對的關檔
    也是可以給NULL

其他還有很多函式,可以參考本書章節
這裡列出的就是基本的scull會用到的操作

struct file_operations scull_fops = {
 .owner = THIS_MODULE,
 .llseek = scull_llseek,
 .read = scull_read,
 .write = scull_write,
 .ioctl = scull_ioctl,
 .open = scull_open,
 .release = scull_release,
};

* File

file 結構代表一個正開啟的檔案
(不一定是device driver ,每一個開啟的file 在kernel space 都有一個對應的file 結構)

一個指向file的指標被稱為filp或file

介紹幾個常見欄位,剩下一樣參考原書

  • mode_t f_mode
    決定可讀、可寫權限,通常會用macro FMODE_READ and FMODE_WRITE 賦值

  • loff_t f_pos;
    當下的讀寫位址

  • void *private_data;
    通常在open時被設定
    可以記錄一些module的狀態,或是自由地給使用者運用
    在release時記得free就好

* inode

是kernel內部表示一個檔案,與file 不同的是,一個檔案可以有多種file表達(比如read、write模式), 但一定只有一個inode

只有兩個與driver 相關feild

  • dev_t i_rdev
    也就是inode代表的device file,這裡會填上device major number
  • struc cdev * i_cdev

在2.5版以後也可以藉由傳inode pointer 來完成device的解除註冊

char device註冊

引入linux/cdev.h

void cdev_init(struct cdev *cdev, struct file_operations *fops);
輸入一個cdev與其對應的操作

int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
告訴kernel cdev的存在

void cdev_del(struct cdev *dev);
當要移除的時候使用

開關檔函式練習

open

open其實不只有開檔,還需要

  • 檢查是否發生裝置特定的錯誤
  • 初始化裝置
  • 如果需要的話,更新f_op pointer
  • 分配給需要filp->private_data的資料結構
int scull_open(struct inode *inode, struct file *filp)
{
 struct scull_dev *dev; /* device information */
 dev = container_of(inode->i_cdev, struct scull_dev, cdev);
 filp->private_data = dev; /* for other methods */
 /* now trim to 0 the length of the device if open was write-only */
 if ( (filp->f_flags & O_ACCMODE) = = O_WRONLY) {
 scull_trim(dev); /* ignore errors */
 }
 return 0; /* success */
}

其中container_of(inode->i_cdev, struct scull_dev, cdev);
是一個改良後得巨集
代替原本的開檔(open函式指標),回傳sucll_dev的結構

release

release需要負責

  • 回收分配給filp->private的東西
  • 關閉使用的硬體裝置
int scull_release(struct inode *inode, struct file *filp)
{
 return 0;
}

release則是因為沒有需要關閉的硬體,所以簡單return 0就好