1. 程式人生 > >Linux檔案系統與裝置檔案

Linux檔案系統與裝置檔案

在裝置驅動程式設計中,一般會關注file 和 inode 這兩個結構體。

 1.file 結構體

file 結構體代表一個開啟的檔案,Linux系統中每一個開啟的檔案都有一個與之關聯的  struct file  結構體;

mode_t f_mode;     檔案模式

確定檔案是可讀的或者是可寫的(或者都是), 通過位 FMODE_READ 和FMODE_WRITE.

loff_t f_pos;     檔案當前的讀寫位置. loff_t 在所有平臺都是 64 位( 在 gcc 術語裡是 long long ).

unsigned int f_flags;     檔案標誌, 例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 

 

struct file_operations *f_op;     和檔案關聯的操作函式結構體指標. 

void *private_data;     private_data是一個有用的資源, 在系統呼叫間保留狀態資訊, 我們大部分例子模組都使用它.

struct dentry *f_dentry;     關聯到檔案的目錄入口( dentry )結構. 

裝置驅動編寫者正常地不需要關心 dentry結構, 除了作為 filp->f_dentry->d_inode 存取 inode 結構.

2.file_operation 結構

是一個字元驅動如何建立與裝置編號的連線。這個結構是一個函式指標的集合,每個開啟檔案(內部用一個file結構來表示)與它自身的函式集合相關連(通過包含一個f_op的成員,它指向一個file_operations結構)。

struct module *owner

    第一個 file_operations 成員根本不是一個操作; 它是一個指向擁有這個結構的模組的指標.

 這個成員用來在它的操作還在被使用時阻止模組被解除安裝. 

幾乎所有時間中, 它被簡單初始化為 THIS_MODULE, 一個在 <linux/module.h> 中定義的巨集.

loff_t (*llseek) (struct file *, loff_t, int);

llseek 方法用作改變檔案中的當前讀/寫位置, 並且新位置作為(正的)返回值.

loff_t 引數是一個"long offset", 並且就算在 32 位平臺上也至少 64 位寬. 錯誤由一個負返回值指示. 

如果這個函式指標是 NULL, seek 呼叫會以潛在地無法預知的方式修改 file 結構中的位置計數器( 在"file 結構" 一節中描述).

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

用來從裝置中獲取資料. 在這個位置的一個空指標導致 read 系統呼叫以 -EINVAL("Invalid argument") 失敗.

 一個非負返回值代表了成功讀取的位元組數( 返回值是一個 "signed size" 型別, 常常是目標平臺本地的整數型別).

ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);

初始化一個非同步讀 -- 可能在函式返回前不結束的讀操作. 如果這個方法是 NULL,所有的操作會由 read 代替進行(同步地).

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

傳送資料給裝置. 如果 NULL, -EINVAL 返回給呼叫 write 系統呼叫的程式. 如果非負, 返回值代表成功寫的位元組數.

ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);

初始化裝置上的一個非同步寫.

int (*readdir) (struct file *, void *, filldir_t);

對於裝置檔案這個成員應當為 NULL; 它用來讀取目錄, 並且僅對檔案系統有用.

unsigned int (*poll) (struct file *, struct poll_table_struct *);

poll 方法是 3 個系統呼叫的後端: poll, epoll, 和 select, 都用作查詢對一個或多個檔案描述符的讀或寫是否會阻塞. poll 方法應當返回一個位掩碼指示是否非阻塞的讀或寫是可能的, 並且, 可能地, 提供給核心資訊用來使呼叫程序睡眠直到I/O 變為可能. 如果一個驅動的 poll 方法為 NULL, 裝置假定為不阻塞地可讀可寫.

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

ioctl 系統呼叫提供了發出裝置特定命令的方法(例如格式化軟盤的一個磁軌, 這不是讀也不是寫). 另外, 幾個 ioctl 命令被核心識別而不必引用 fops 表. 如果裝置不提供 ioctl 方法, 對於任何未事先定義的請求(-ENOTTY, "裝置無這樣的ioctl"), 系統呼叫返回一個錯誤.

int (*mmap) (struct file *, struct vm_area_struct *);

mmap 用來請求將裝置記憶體對映到程序的地址空間. 如果這個方法是 NULL, mmap 系統呼叫返回 -ENODEV.

int (*open)(struct inode *inode, struct file *filp);

儘管這常常是對裝置檔案進行的第一個操作, 不要求驅動宣告一個對應的方法. 如果這個項是 NULL, 裝置開啟一直成功, 但是你的驅動不會得到通知.在大部分驅動中, open 應當進行下面的工作:
檢查裝置特定的錯誤(例如裝置沒準備好, 或者類似的硬體錯誤
如果它第一次開啟, 初始化裝置

如果需要, 更新 f_op 指標.

分配並填充要放進 filp->private_data 的任何資料結構

inode 引數有我們需要的資訊,以它的 i_cdev 成員的形式, 裡面包含我們之前建立的cdev 結構. 

唯一的問題是通常我們不想要 cdev 結構本身, 我們需要的是包含 cdev 結構的 xxx_dev 結構體指標. 

為了實現目的,核心以 container_of 巨集的形式, 在 <linux/kernel.h> 中定義:

container_of(pointer, container_type, container_field);

eg:

struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */

int (*flush) (struct file *);

flush 操作在程序關閉它的裝置檔案描述符的拷貝時呼叫; 它應當執行(並且等待)裝置的任何未完成的操作. 這個必須不要和使用者查詢請求的 fsync 操作混淆了. 當前, flush 在很少驅動中使用; SCSI 磁帶驅動使用它, 例如, 為確保所有寫的資料在裝置關閉前寫到磁帶上. 如果 flush 為 NULL, 核心簡單地忽略使用者應用程式的請求.

int (*release) (struct inode *, struct file *);

在檔案結構被釋放時引用這個操作. 如同 open, release 可以為 NULL.

release 方法的角色是 open 的反面. 有時你會發現方法的實現稱為 device_close, 而不是 device_release.

任一方式, 裝置方法應當進行下面的任務:

釋放 open 分配在 filp->private_data 中的任何東西
在最後的 close 關閉裝置
eg    :
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,
};

3. inode 結構體

VFS inode 結構由核心在內部用來表示檔案 ,是Linux管理檔案系統的最基本單位。

對於表示裝置檔案的inode結構體,

dev_t   i_rdev;    對於代表裝置檔案的節點, 這個成員包含實際的裝置編號.

從一個inode中獲得主裝置號和次裝置號:

unsigned int iminor(struct inode *inode)
unsigned int imajor(struct inode *inode)

struct     cdev     *i_cdev;    struct cdev 是核心的內部結構, 代表字元裝置;

 這個成員包含一個指標, 指向這個結構, 當節點指的是一個字元裝置檔案時.

3. udev 使用者空間裝置管理

將裝置管理轉移到使用者空間,將具體的裝置操作交由使用者,實現了機制與策略的分離。

udev 完全在使用者態工作,利用裝置加入或移除時核心所傳送的熱插拔事件來工作。在熱插拔時,裝置的詳細資訊會由核心通過

netlink 套接字傳送出來(uevent)  。udev的裝置命名策略、許可權控制和事件處理都是在使用者態下完成的。它利用從核心收到的資訊來進行建立裝置檔案節點等工作。

4. sysfs 檔案系統

該檔案系統是一個虛擬的檔案系統,它可以產生一個包括所有系統硬體的層級檢視,與提供程序和狀態資訊的proc檔案系統類似。它的一個目的就是展示裝置驅動模型中各元件的層次關係。

sysfs 各頂級目錄:

block 目錄包含所有的塊裝置

devices 目錄包含系統所有的裝置,並根據裝置掛接的匯流排型別組織成層次結構

bus 目錄包含系統中所有的匯流排型別

class 目錄包含系統中的裝置型別

在  /sys 目錄下執行 tree 會看到一個相當長的樹形結構。