字元裝置驅動資料結構
前言
資料結構是字元裝置驅動的核心,其它很多程式碼都是圍繞資料結構展開的,所以有必要對字元裝置驅動資料結構做一個梳理,先看三張圖,便於從巨集觀上了解字元裝置驅動程式.
一.管理系統裝置號分配
定義:
static struct char_device_struct { struct char_device_struct *next; // 指向下一個char_device_struct 結構體 unsigned int major; // 主裝置號 unsigned int baseminor; // 從裝置號起始值 int minorct;// 從裝置號數量 char name[64];// 裝置或驅動的名字 struct cdev *cdev; /* will die */ } *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; // CHRDEV_MAJOR_HASH_SIZE是255
說明:
1.新分配的裝置號
從254開始搜尋到0,如果chrdevs[i]為空,則讓chrdevs[i]指向新分配的主裝置號char_device_struct 結構體,i就是分配的裝置號
2.cat /proc/devices 可以檢視系統的字元裝置和塊裝置,這些資訊就取自chrdevs
核心程式碼如下:
void chrdev_show(struct seq_file *f, off_t offset) { struct char_device_struct *cd; if (offset < CHRDEV_MAJOR_HASH_SIZE) { mutex_lock(&chrdevs_lock); for (cd = chrdevs[offset]; cd; cd = cd->next) seq_printf(f, "%3d %s\n", cd->major, cd->name); mutex_unlock(&chrdevs_lock); } }
查詢結果:
二.字元裝置結構體
定義:
struct cdev { struct kobject kobj; // sysfs 核心物件 struct module *owner;//該字元裝置所在的核心模組(所有者)的物件指標,一般為THIS_MODULE主要用於模組計數 const struct file_operations *ops;//該結構描述了字元裝置所能實現的操作集(開啟、關閉、讀/寫、...),是極為關鍵的一個結構 struct list_head list;//用來將已經向核心註冊的所有字元裝置形成連結串列 dev_t dev; //字元裝置的裝置號,由主裝置號和次裝置號構成(如果是一次申請多個裝置號,此裝置號為第一 unsigned int count;//隸屬於同一主裝置號的次裝置號的個數 }; static struct cdev gpio_cdev;
說明:
1.cdev表示一個字元裝置結構體,在字元設備註冊時作為kobj_map 結構體的data資料成員
三.管理字元設備註冊進系統
定義:
struct kobj_map {
struct probe { // probe(探測)
struct probe *next;
dev_t dev; // 裝置號
unsigned long range;// 從裝置號個數
struct module *owner;
kobj_probe_t *get;
int (*lock)(dev_t, void *);
void *data; // 存放cdev結構體指標
} *probes[255]; // 對應每個探測到的字元裝置
struct mutex *lock;
};
static struct kobj_map *cdev_map;
說明:
1.cdev_map指標初始化:start_kernel()->vfs_caches_init()->chrdev_init()->kobj_map_init();
2.cdev_map指向每個註冊到系統的字元裝置
三.inode結構體
定義:
/*
* Keep mostly read-only and often accessed (especially for
* the RCU path lookup and 'stat' data) fields at the beginning
* of the 'struct inode'
*/
struct inode {
umode_t i_mode;
unsigned short i_opflags;
uid_t i_uid;
gid_t i_gid;
unsigned int i_flags;
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
const struct inode_operations *i_op;
struct super_block *i_sb;
struct address_space *i_mapping;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
/* Stat data, not accessed from path walking */
unsigned long i_ino;
/*
* Filesystems may only read i_nlink directly. They shall use the
* following functions for modification:
*
* (set|clear|inc|drop)_nlink
* inode_(inc|dec)_link_count
*/
union {
const unsigned int i_nlink;
unsigned int __i_nlink;
};
dev_t i_rdev;
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes;
blkcnt_t i_blocks;
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
/* Misc */
unsigned long i_state;
struct mutex i_mutex;
unsigned long dirtied_when; /* jiffies of first dirtying */
struct hlist_node i_hash;
struct list_head i_wb_list; /* backing dev IO list */
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
union {
struct list_head i_dentry;
struct rcu_head i_rcu;
};
atomic_t i_count;
unsigned int i_blkbits;
u64 i_version;
atomic_t i_dio_count;
atomic_t i_writecount;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct file_lock *i_flock;
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;// 字元裝置驅動cdev結構體
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
void *i_private; /* fs or device private pointer */
};
說明:
inode結構體的詳細解析參考深入淺出理解linux inode結構
VFS inode 包含檔案訪問許可權、屬主、組、大小、生成時間、訪問時間、最後修改時間等資訊。它是Linux 管理檔案系統的最基本單位,也是檔案系統連線任何子目錄、檔案的橋樑。
核心使用inode結構體在核心內部表示一個檔案。因此,它與表示一個已經開啟的檔案描述符的結構體(即file 檔案結構)是不同的,我們可以使用多個file 檔案結構表示同一個檔案的多個檔案描述符,但此時,所有的這些file檔案結構全部都必須只能指向一個inode結構體。
inode結構體包含了一大堆檔案相關的資訊,但是就針對驅動程式碼來說,我們只要關心其中的兩個域即可:
(1) dev_t i_rdev;
表示裝置檔案的結點,這個域實際上包含了裝置號。
(2) struct cdev *i_cdev;
struct cdev是核心的一個內部結構,它是用來表示字元裝置的,當inode結點指向一個字元裝置檔案時,此域為一個指向inode結構的指標。
下面看一下上層應用open() 呼叫系統呼叫函式的過程
對於一個字元裝置檔案, 其inode->i_cdev 指向字元驅動物件cdev, 如果i_cdev為 NULL ,則說明該裝置檔案沒有被開啟.
由於多個裝置可以共用同一個驅動程式.所以,通過字元裝置的inode 中的i_devices 和 cdev中的list組成一個連結串列
首先,系統呼叫open開啟一個字元裝置的時候, 通過一系列呼叫,最終會執行到 chrdev_open
(最終是通過呼叫到def_chr_fops中的.open, 而def_chr_fops.open = chrdev_open. 這一系列的呼叫過程,本文暫不討論)
int chrdev_open(struct inode * inode, struct file * filp)
chrdev_open()所做的事情可以概括如下:
1. 根據裝置號(inode->i_rdev), 在字元裝置驅動模型中查詢對應的驅動程式, 這通過kobj_lookup() 來實現, kobj_lookup()會返回對應驅動程式cdev的kobject.
2. 設定inode->i_cdev , 指向找到的cdev.
3. 將inode新增到cdev->list 的連結串列中.
4. 使用cdev的ops 設定file物件的f_op
5. 如果ops中定義了open方法,則呼叫該open方法
6. 返回
執行完 chrdev_open()之後,file物件的f_op指向cdev的ops,因而之後對裝置進行的read, write等操作,就會執行cdev的相應操作。