裝置並不是通過其檔案名稱來標識,而是通過檔案的主、次裝置號標識(檔案名稱和檔案的主次裝置號在裝置檔案的父目錄的inode的資料區中表現出來的,這是fs層的東東)。
裝置檔案和普通檔案的區別:
查看裝置檔案的命令為:ls -l /dev/
1:存取權限前面的字母b/c,分別表示塊裝置和字元裝置。
2:裝置檔案沒有檔案長度,而增加了另外兩個值,分別為主裝置號和次裝置號。二者共同形成一個唯一的號碼,核心由此可以尋找到對應的裝置驅動程式。
由於引入了udev機制,/dev不再放置到基於磁碟的檔案系統中,而是使用tmpfs,這是RAM磁碟檔案系統ramfs的一種輕型變體。這意味著裝置結點不是持久性的,系統關機/重啟後就會消失。
IOCTL:輸入輸出控制介面,是用於配置和修改特定裝置屬性的通用介面。
核心如果能瞭解到系統中有哪些字元裝置和塊裝置可用,那自然是很有利的,因而需要維護一個資料庫,此外必須提供一個介面,以便驅動程式開發人員能夠將新項添加到資料庫中。
資料結構:
1、裝置資料庫
儘管塊裝置和字元裝置彼此的行為有很大的不同,但用於跟蹤所有可用裝置的資料庫是相同的。因為字元裝置和塊裝置都是通過唯一的裝置號標識。但是,資料庫會根據塊裝置/字元裝置來跟蹤不同的對象。
。每個字元裝置都表示為一個struct cdev的執行個體
。struct genhd用於管理塊裝置的分區,作用類似於字元裝置的cdev.
有兩個全域數組(bdev_map用於塊裝置,cdev_map用於字元裝置)用來實現散列表,使用主裝置號作為散列鍵。cdev_map和bdev_map都是同一資料結構struct kobj_map的執行個體。散列的方法很簡單:major%255.
struct kobj_map {
struct probe {
struct probe *next;
dev_t dev;
unsigned long range;
struct module *owner;
kobj_probe_t *get;
int (*lock)(dev_t, void *);
void *data;
} *probes[255];
struct mutex *lock;
};
互斥量lock實現了對散列表訪問的序列化.struct probe的成員如下:
next:將所有散列錶鏈接在一個單鏈表中。(沒有搞明白這個單鏈表中的裝置的主裝置號都是相同的否?)
dev: 表示裝置號,包括主次裝置號
rang:從裝置號的連續範圍儲存在range中。那麼與裝置關聯的各個從裝置號的範圍是[MINORS(dev), MINORS(dev)+range-1].
owner:指向提供裝置驅動程式的模組。
get:指向一個函數,可以返回與裝置關聯的kobject執行個體。
data:字元裝置和塊裝置的區別就在於data.對於字元裝置,他指向struct cdev的一個執行個體,而對於塊裝置,則指向struct genhd的執行個體。
2、字元裝置範圍資料庫
第二類資料庫只是用於字元裝置。他是用於管理為驅動程式分配的裝置號範圍。驅動程式可以請求一個動態裝置號(用register_chrdev_region()函數註冊裝置號),或者指定一個範圍,從中擷取(用alloc_chrdev_region()函數分配裝置號)。
再次使用散列表來跟蹤已經分配的裝置號範圍,並同樣使用主裝置號作為散列鍵。資料結構如下:
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev; /* will die 未來版本將刪除*/
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
next:連結同一散列行中的所有散列元素
major:主裝置號
baseminor是包含minorct個從裝置號的連續範圍的最小的從裝置
name為裝置提供了一個標識符
與檔案系統關聯
inode中裝置檔案成員
只列出與驅動有關的成員
struct inode{
...
dev_t i_rdev;
...
umode_t i_mode;
...
struct file_operations *i_fop;
...
union {
struct block_device *i_bdev;
struct cdev *i_cdev;
};
....
};
i_mode:為唯一的標識與一個裝置檔案關聯,核心在i_mode中儲存量檔案類型(面向塊/面向字元)
i_rdev中儲存了主次裝置號
i_fop是一組函數指標的集合,包括許多檔案操作(open,read,write等),這些有虛擬檔案系統使用來出來塊裝置
核心會根據inode表示塊裝置還是字元裝置,來使用i_bdev/i_cdev指向更多具體資訊。
標準檔案操作
在開啟一個裝置檔案時,各種檔案系統的實現都會調用init_special_inode函數,為字元裝置或者塊裝置檔案建立一個Inode(在檔案系統層).
在此還是有疑問的,在開啟裝置的時候才建立Inode,難道在裝置檔案建立的時候fs系統沒有建立Inode嗎,fs只是把裝置檔案的ID和檔案名稱存於/dev的inode的dentry中嗎?
針對上面的問題需要說明的是:在裝置檔案開啟的時候和建立裝置檔案的時候都建立了inode,但這兩個inode不是同一個,是兩個不同類型的資料。裝置檔案建立的時候產生的Inode是儲存在硬碟中的,他的結構比較簡單,隨裝置檔案的刪除會消失的。而裝置檔案開啟的時候建立的inode是struct inode結構體,他是儲存在ram中的,隨著檔案的關閉會消失的。
對下面的問題“fs只是把裝置檔案的id 和檔案名稱存於/dev的inode的dentry中嗎?”這樣的猜想是錯誤的,有這樣的說法,/dev目錄是一個臨時的檔案夾,他並不在inode塊中存在inode結點,但他目錄下的檔案在inode塊中是存在inode結點的。那麼我們就該想了,那fs是如何找到/dev目錄下的檔案呢?裝置檔案是一類比較特殊的檔案,他的尋找方式跟普通的檔案的尋找方式是不同的。其實裝置檔案根本就不需要尋找他在磁碟上對應的inode結點,因為裝置檔案根本就沒有資料區,我們需要的裝置檔案的裝置操作指標(f_ops).如何找到裝置操作指標的呢?看下面的黑體部分就可以了。(上面的說法對字元裝置檔案是可行的,但沒有看塊裝置,不知道塊裝置是否也是這樣的。其實也同樣存在一個問題,按照上面的解釋,我們完全可以不用為裝置檔案在磁碟上建立一個inode結點,但系統還是建了,可以肯定是這樣的inode結點是有作用的,系統不會乾沒意義的事的)
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
} else if (S_ISBLK(mode)) {
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
} else if (S_ISFIFO(mode))
inode->i_fop = &def_fifo_fops;
else if (S_ISSOCK(mode))
inode->i_fop = &bad_sock_fops;
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)/n", mode);
}
用於字元裝置的標準操作
fs/device.c
const struct file_operations def_chr_fops = {
.open = chrdev_open,
};
chrdev_open()函數的主要任務是向該結構(其實不明白"該結構"指的是什麼結構,似乎有點像struct file)填入已開啟裝置的函數指標(file_operation),使得能夠在裝置檔案上執行有意義的操作,並最終能夠操作裝置本身。
chrdev_open()函數的框圖:
假定表示裝置檔案的inode此前沒有開啟過,根據裝置號,kobject_lookup查詢字元裝置的資料庫(cdev_map),並返回與該驅動程式向關聯的kobject執行個體。該傳回值可用於擷取cdev執行個體。
擷取了對應於裝置的cdev執行個體,核心通過cdev->ops還可以訪問特定於裝置的file_operations.接下來設定各種資料結構之間的關聯
inode->i_cdev指向所選擇的cdev執行個體。在下一次開啟該inode時,就不必再查詢字元裝置的資料庫,因為可以使用緩衝的值。
該inode將添加到cdev->list
file->f_ops是用於struct file新的file_operations,設定為指向struct cdev給出的file_operations執行個體。
接下來調用struct file新的file_operations中的open函數(現在是用於特定的裝置,我們針對特定的裝置在驅動層實現的open函數),在裝置上執行所需的初始化任務。
用於塊裝置的標準操作
fs/block_dev.c
const struct file_operations def_blk_fops = {
.open = blkdev_open,
.release = blkdev_close,
.llseek = block_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = generic_file_aio_read,
.aio_write = generic_file_aio_write_nolock,
.mmap = generic_file_mmap,
.fsync = block_fsync,
.unlocked_ioctl = block_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_blkdev_ioctl,
#endif
.splice_read = generic_file_splice_read,
.splice_write = generic_file_splice_write,
};
儘管file_operations與block_device_operations的結構類似,但兩者不能混淆。file_operations由VFS層用來與使用者空間通訊的,他裡面的函數是有使用者層來調用的。其中的函數會調用block_device_operations中的函數,以實現與塊裝置的通訊。block_device_operations必須針對各種具體的塊裝置分別實現,對裝置的屬性加以抽象,而在此基礎上建立file_operations,是使用者可以使用相同的操作(file_operations提供的函數)即可處理所有的塊裝置。