linux注册函数机制,Linux可信计算机制模块详细分析之函数实现机制(1)字符设备驱动...
原標題:Linux可信計算機制模塊詳細分析之函數實現機制(1)字符設備驅動
2.3 函數實現機制
2.3.1 Linux 字符設備驅動
在linux 3.5.4中,用結構體cdev描述字符設備,cdev結構體在文件include/linux/cdev.h中定義,如下所示:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
類型
字段
說明
struct kobject
kobj
設備在底層的統一接口,是設備模型的核心結構,每個cdev結構體都有一個kobject,每個在內核中注冊的kobject對象都對應于sysfs文件中的一個目錄
struct module *
owner
指向實現驅動的模塊
struct file_operations *
ops
操作設備文件的方法,實現與具體硬件通信的操作,也是字符設備驅動程序的主體部分
struct list_head
list
實現一個雙向鏈表,其中包含表示該設備的設備文件的inode
dev_t
dev
表示設備號,一個設備號由主設備號和次設備號組成
unsigned int
count
表示與該設備關聯的次設備號的數目
表21結構體cdev 各字段說明
flle_operations結構體中定義了操作設備文件的方法,都是一些函數指針,其在/include/linux/fs.h文件中定義如下:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
. . . . . . .
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
int (*check_flags)(int);
. . . . . . . .
};
字段名
說明
owner
指向擁有該結構體的驅動模塊指針
llseek
用于修改文件讀寫位置
read
從字符設備中讀取數據
write
向字符設備發送數據
aio_read
初始化一個異步的讀取操作
aio_write
初始化一個異步的寫入操作
readdir
用于讀取目錄,對于字符設備文件,該字段為NULL
unlocked_ioctl
執行設備I/O控制命令
mmap
用于將設備內存映射到進程地址空間
open
打開操作
release
關閉操作
synch
刷新待處理的數據
lock
文件鎖操作
check_flags
標志位檢查
表2-2 file_operations結構體字段說明
內核中所有已分配的字符設備編號都記錄在一個名為chrdevs散列表里。該散列表中的每一個元素是一個char_device_struct結構,內核并不是為每一個字符設備編號定義一個char_device_struct結構,而是為一組對應同一個字符設備驅動的設備編號范圍定義一個char_device_struct結構。chrdevs散列表的大小是255。char_device_struct結構在文件/fs/char_dev.c中定義,如下所示:
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];
類型
字段
說明
struct char_device_struct*
next
指向散列鏈表中的下一個元素的指針
unsigned int
major
主設備號
usigned int
baseminor
次設備號范圍中的起始設備號
int
minorct
次設備號范圍
char
name[64]
與設備編號關聯的設備驅動名稱
struct cdev*
cdev
指向字符設備描述結構體的指針,在未來內核版本中會刪除此字段
表2-3 char_device_struct結構個字段說明
用于操作cdev結構體的函數的具體實現在文件/fs/char_dev.c中,cdev_init()函數用于初始化cdev成員,并建立cdev和file_operations之間的連接實現,代碼如下:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
cdev_init()函數執行流程如下:
(1)調用memset()函數初始化cdev結構體實例。
(2)初始化cdev結構體實例鏈表。
(3)初始化kobject結構體。
(4)將cdev結構體實例與對應file_operations結構體對應。
cdev_alloc()函數用于動態申請一個cdev內存,具體代碼在文件fs/char_dev.c中,代碼如下:
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
cdev_alloc()函數執行流程如下:
(1)調用kzalloc()給cdev結構體分配空間。
(2)如果分配空間成功,初始化cdev結構體實例鏈表并初始化kobject結構體。
(3)返回cdev結構體指針
cdev_add()函數用于向系統添加一個cdev對象,完成字符設備的注冊,對cdev_add()的調用通常發生在字符設備驅動模塊加載函數中。在調用cdev_add()函數前,要首先調用register_chrdev_region()或者alloc_chrdev_region()函數向系統申請設備號。具體代碼在文件fs/char_dev.c中,代碼如下:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
cdev_add執行流程如下:
(1)初始化cdev結構體中的dev、count字段,dev是設備號,count是設備號范圍,及與該設備驅動相關的次設備號范圍。
(2)調用kobj_map()函數,返回結果。kobj_map()用于用來把字符設備編號和cdev 結構變量一起保存到cdev_map這個散列表里。當后續要打開一個字符設備文件時,通過調用kobj_lookup()函數,根據設備編號就可以找到cdev結構變量,從而取出其中的ops字段
cdev_del()函數用于刪除一個cdev對象,完成字符設備的銷毀,通常在字符設備驅動模塊卸載函數中調用,在調用cdev_del()函數前,要調用unregister_chrdev_region()釋放原先申請的設備號。具體代碼在文件fs/char_dev.c中,代碼如下。
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
cdev_del()執行流程如下:
(1)調用cdev_unmap()函數從cdev_map散列表中刪除cdev設備實例和相應設備編號。
(2)將kobject結構體實例數量減一。
register_chrdev_region()函數用于向系統申請設備號,函數有三個參數:第一個為初始的設備號,第二個是請求的設備號的大小,即次設備號的大小,第三個參數是這個范圍(次設備號大小)內的設備號對應的設備驅動程序。該函數先執行個for循環檢查請求的設備號范圍是否超過了次設備號范圍,然后在每個設備號范圍上調用__register_chrdev_region()函數,函數執行成功返回0,錯誤返回錯誤代碼。具體代碼在文件fs/char_dev.c中,代碼如下:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
register_chrdev_region()函數執行流程如下:
(1)通過MKDEV()函數得到一個設備號存儲于next中。
(2)判斷在from的基礎上再追加count個設備是否已經溢出到下一個主設備號,。如果溢出(next大于to),那么整個for語句就只執行個一次__register_chrdev_region函數;否則當設備號沒溢出時,會循環調用__register_chrdev_region函數,直到next大于to。
(3)如果在某個小范圍調用__register_chrdev_region時出現了失敗,那么會將此前分配的設備號都釋放。
alloc_chrdev_region()函數也是向系統申請設備號,但相比register_chrdev_region()函數,其可以動態分配一個主設備號。函數有四個參數:第一個為初始設備號,第二個是請求分配的次設備號的初始值,第三個是次設備號的范圍,第四個是次設備號對應的驅動程序的名稱,此函數最終調用__register_chrdev_region()函數實現。具體代碼在文件fs/char_dev.c中,如下所示:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
register_chrdev()函數是一個老式分配設備編號范圍的函數,其是__register_chrdev()函數的封裝,__register_chrdev()函數內部調用__register_chrdev_region()函數實現。其分配一個單獨主設備號和0 ~ 255的次設備號范圍。如果申請的主設備號為0則動態分配一個。該函數還需傳入一個file_operations結構的指針,函數內部自動分配了一個新的cdev結構。
resgister_chrdev_region()、alloc_chrdev_region()、register_chrdev()三個字符設備注冊函數最終都調用了__register_chrdev_region()函數。__register_chrdev_region()函數用于向系統注冊一個設備號,有四個參數,第一個是設備的主設備號,第二個是初始此設備號,第三個是次設備號范圍,第四個是與設備號對應的設備驅動名稱。__register_chrdev_region()函數代碼在文件/fs/char_dev.c中。__register_chrdev_region()函數執行流程圖如下所示:
圖2-4 __register_chrdev_region()函數執行流程圖
chrdev_open()函數用于打開字符設備文件,其有兩個參數,第一個是文件索引節點結構體指針,第二個參數是文件對象結構體指針。chrdev_open()函數執行流程圖如下:
圖2-5 chrdev_open()函數執行流程圖
unregister_chrdev_region()函數用于注銷設備號,其調用__unregister_chrdev_region()函數具體,代碼在文件fs/char_dev.c中,如下所示:
void unregister_chrdev_region(dev_t from, unsigned count)
{
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
}
unregister_chrdev()函數也是用于注銷一個設備號,與設備號注冊函數register_chrdev()對應,其是對__unregister_chrdev()函數的封裝,__unregister_chrdev()函數中調用了__unregister_chrdev_region()。__unregister_chrdev()函數代碼在文件fs/char_dev.c中,如下所示:
void __unregister_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name)
{
struct char_device_struct *cd;
cd = __unregister_chrdev_region(major, baseminor, count);
if (cd && cd->cdev)
cdev_del(cd->cdev);
kfree(cd);
}
__unregister_chrdev_region()是注銷設備號的核心函數,代碼在文件fs/char_dev.c中。其執行流程比較簡單,先從chrdevs散列表里中找到需要釋放的char_device_struct結構實例,再將char_device_struct結構實例從chrdevs散列表中刪除。返回搜狐,查看更多
責任編輯:
總結
以上是生活随笔為你收集整理的linux注册函数机制,Linux可信计算机制模块详细分析之函数实现机制(1)字符设备驱动...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 西游笔绘西行牛魔王技能强度解析
- 下一篇: linux 其他常用命令