一、 模块使用计数的背景知识
模块是一种可以在内核运行过程中动态加载、卸载的内核功能组件。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时帮助我们实现同样功能的。
l 打开字符设备的大体流程如下:
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))
l 关闭设备的大体流程
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的使用计数
……
}
分享到:
相关推荐
总的来说,Linux-2.6内核模块程序设计需要理解和利用新的特性,如".ko"文件格式、模块兼容性检查、引用计数管理以及Makefile的使用。这些变化提高了模块的安全性、稳定性和管理效率,为系统开发提供了更强大的基础。...
2.6内核中模块的命名方式为*.ko。模块在被使用时,是不允许被卸载的。编程时需要用“使用计数”来描述模块是否在被使用。 二、2.4内核使用计数的实现方法 2.4内核中,模块自身通过 MOD_INC_USE_COUNT, MOD_DEC...
《2.4和2.6内核模块和驱动详解》 在Linux系统中,内核扮演着至关重要的角色,而内核模块和驱动程序是其不可或缺的部分。随着内核版本的迭代,从2.4到2.6,内核在可装载模块机制、设备模型以及API等方面进行了诸多...
Linux2.6内核驱动移植需要遵守新的编程规范和接口标准,需要注意入口函数、许可证声明、模块参数、模块别名、模块计数、符号导出、内核版本检查、设备号、内存分配头文件变更、结构体的初始化、用户模式帮助器和...
《Linux2.6内核驱动移植参考》文档主要介绍了Linux 2.6内核与旧版本相比在驱动程序开发方面的一些重大变化,这对于Linux驱动开发者来说是非常重要的参考资料。以下是其中的关键知识点: 1. **入口函数改变**: - ...
在了解Linux 2.6内核移植之前,首先需要明确Linux内核移植的概念,它是指将Linux操作系统内核源代码通过适当的修改,使其能在特定的硬件平台上运行。内核移植是嵌入式系统开发的一个重要环节,涉及到对内核的裁剪、...
Linux内核API是Linux操作系统中最核心的部分,它为硬件设备驱动程序、文件系统以及系统管理程序提供了编程接口。Linux内核API手册详细介绍了各个API的...这些内容是构建、维护和优化Linux系统和内核模块的关键资源。
【Linux2.6内核驱动移植】随着Linux2.6内核的发布,开发者们面临着对设备驱动程序的大量改写工作,以适应新内核的变化。本文档旨在提供一个移植指南,概述2.6内核与之前版本的主要差异,但不包含所有函数的详细用法...
在Linux 2.6内核中,链表作为一种基础数据结构,广泛应用于内核模块中,用于管理和组织数据。其核心结构是list_head结构体,该结构体包含两个指针,分别指向前一个和后一个list_head元素。链表头在初始化时,其next...
Linux 2.6内核是一个重要的里程碑,它引入了许多改进和新特性,对驱动程序的编写方式产生了显著影响。以下是针对Linux 2.6内核驱动移植的一些关键知识点: 1. **入口函数改变**: 在旧版本中,驱动的初始化和退出...
《Linux内核模块编程指南》是一本非常经典的教程,涵盖了从2.2到2.6版本的Linux内核模块开发知识。这本书对于那些想要深入理解Linux内核并编写内核模块的开发者来说,是不可或缺的参考资料。以下是该书涉及的一些...
Linux内核可装载模块对设备驱动的影响主要体现在2.4到2.6内核的升级过程中,这涉及到多个方面的变化,包括模块编译、版本检查、初始化与退出、模块使用计数、内核符号输出、命令行参数处理以及许可证声明等。...
在Linux 2.6内核模型中,有几个关键的概念和数据结构,包括Kobject、Kset、Subsystem、Sysfs、Bus、Device Class和Device_driver。 1. **Kobject**: Kobject是设备模型的基础构建块,它代表了一个抽象的对象,...
### Linux 2.6.26.6 内核下的 LED 驱动程序分析 在深入探讨这个特定的 LED 驱动程序之前,我们首先来了解一下 Linux 内核和驱动开发的基本概念。 #### Linux 内核简介 Linux 是一个开源的操作系统内核,由 Linus ...
5. **模块计数**:2.6内核使用`try_module_get`和`module_put`来管理模块使用计数,取代了2.4内核中的`MOD_INC_USE_COUNT`和`MOD_DEC_USE_COUNT`。 6. **符号导出**:2.6内核默认不导出所有符号,需要显式使用`...
《Linux内核模块编程指南2.6版》是一本针对Linux系统开发人员的重要参考资料,它详细介绍了如何编写和管理Linux内核模块。内核模块是Linux操作系统中的可加载组件,它们允许用户根据需要动态地扩展内核功能。下面将...