作者:劉洪濤,華清遠見嵌入式學院金牌講師。
一、模組使用計數的背景知識
模組是一種可以在核心運行過程中動態載入、卸載的核心功能組件。2.6核心中模組的命名方式為*.ko。模組在被使用時,是不允許被卸載的。編程時需要用“使用計數”來描述模組是否在被使用。
二、2.4核心使用計數的實現方法
2.4核心中,模組自身通過 MOD_INC_USE_COUNT, MOD_DEC_USE_COUNT宏來管理自己被使用的計數。通常我們在寫模組時,會在open方法中加入MOD_INC_USE_COUNT,在close方法中加入MOD_DEC_USE_COUNT來實現使用計數。
三、2.6核心使用計數的實現方法
2.6核心提供了更健壯、靈活的模組計數管理介面 try_module_get(&module), module_put(&module)取代2.4中的模組使用計數管理宏。模組的使用計數不必由自身管理,而且在管理模組使用計數時考慮到SMP與PREEMPT機制的影響(參考module.h中try_module_get和module.c中module_put的實現)。
int try_module_get(struct module *module); 用於增加模組使用計數;若返回為0,表示調用失敗,希望使用的模組沒有被載入或正在被卸載中。
void module_put(struct module *module); 減少模組使用計數。
try_module_get與module_put 的引入與使用與2.6核心下的裝置模型密切相關。2.6核心為不同類型的裝置定義了struct module *owner 域,用來指向管理此裝置的模組。如字元裝置的定義:
struct cdev
{
struct kobject kobj;
struct module *owner;
struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
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 *);
……
};
從裝置使用的角度出發,當需要開啟、開始使用某個裝置時,使用 try_module_get(dev->owner)去增加管理此裝置的 owner模組的使用計數;當關閉、不再使用此裝置時,使用module_put(dev->owner)減少對管理此裝置的owner模組的使用計數。這樣,當裝置在使用時,管理此裝置的模組就不能被卸載;只有裝置不再使用時模組才能被卸載。
2.6核心下,對於為具體裝置寫驅動的開發人員而言,基本無需使用 try_module_get與module_put,因為此時開發人員所寫的驅動通常為支援某具體裝置的owner模組,對此裝置owner模組的計數管理由核心裡更底層的代碼如匯流排驅動或是此類裝置共用的核心模組來實現,從而簡化了裝置驅動開發。
四、舉例說明2.6核心模組使用計數的實現過程
舉一個2.6核心下字元裝置驅動編寫的例子來說明問題。在2.6核心下編寫一個裝置驅動時,初始化過程中大家都會看到如下的模板:
static struct file_operations simple_remap_ops = {
.owner = THIS_MODULE,
.open = simple_open,
……
};
static void simple_setup_cdev(struct cdev *dev, int minor,
struct file_operations *fops)
{
int err, devno = MKDEV(simple_major, minor);
cdev_init(dev, fops);
dev->owner = THIS_MODULE;
err = cdev_add (dev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk (KERN_NOTICE "Error %d adding simple%d", err, minor);
}
無論是cdev還是file_operations都將自己的struct module *owner成員指向了THIS_MODULE。那麼這個THIS_MODULE 是什麼呢?
核心源碼目錄下include/linux/module.h
#ifdef MODULE
#define MODULE_GENERIC_TABLE(gtype,name) \
extern const struct gtype##_id __mod_##gtype##_table \
__attribute__ ((unused, alias(__stringify(name))))
extern struct module __this_module;
#define THIS_MODULE (&__this_module)
#else /* !MODULE */
#define MODULE_GENERIC_TABLE(gtype,name)
#define THIS_MODULE ((struct module *)0)
#endif
__this_module這個符號是在載入到核心後才產生的。insmod命令執行後,會調用kernel/module.c裡的一個系統調用 sys_init_module,它會調用load_module函數,將使用者空間傳入的整個核心模組檔案建立成一個核心模組,並返回一個struct module結構體,從此,核心中便以這個結構體代表這個核心模組。THIS_MODULE類似進程的CURRENT。
struct module
{……
struct module_ref ref[NR_CPUS];
}
struct module_ref
{
local_t count;//記錄模組使用計數
} ____cacheline_aligned;
現在咱們就看看核心是如何協助我們完成使用計數的。
在2.4核心中,我們是通過在open方法中增加引用計數,在close方法中減少引用計數。在2.6核心中,核心肯定也是要在open、close時協助我們實現同樣功能的。
開啟字元裝置的大體流程如下:
sys_open()->do_sys_open()->do_filp_open()->nameidata_to_filp() ->__dentry_open()->chrdev_open()->open()
2.6核心中並不要求模組在open中顯示的實現使用計數,真正使用模組使用計數是在chrdev_open()中完成的。
核心源碼fs/char_dev.c
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
……
cdev_get(p); //增加cdev中owner指向的module的使用計數
……
filp->f_op = fops_get(p->ops);}// 增加file_operations中owner指向的module的使用計數
if (filp->f_op->open) {
lock_kernel();
ret = filp->f_op->open(inode,filp);//調用到裝置驅動中的open
unlock_kernel();
}
}
static struct kobject *cdev_get(struct cdev *p)
{
struct module *owner = p->owner;
……
if (owner && !try_module_get(owner))
return NULL;
……
}
核心源碼Include/linux/fs.h
#define fops_get(fops) \
(((fops) && try_module_get((fops)->owner) ? (fops) : NULL))
關閉裝置的大體流程
sys_close()->filp_close()->fput()->__fput()->release()
2.6核心中並不要求模組在release中顯示的實現使用計數,真正使用模組使用計數是在__fput()中完成的。
void __fput(struct file *file)
{
struct dentry *dentry = file->f_path.dentry;
struct vfsmount *mnt = file->f_path.mnt;
struct inode *inode = dentry->d_inode;
if (file->f_op && file->f_op->release)
file->f_op->release(inode, file);//調用到裝置驅動中的release
……
cdev_put(inode->i_cdev); //減少cdev中owner指向的module的使用計數
……
fops_put(file->f_op);// 減少file_operations中owner指向的module的使用計數
……
}