Linux内核源代码情景分析-insmod

看本文前,先看着篇文章,Linux字符设备驱动

insmod,大体上所做的事,有这么一些:

1、打开待安装模块并将其读入到用户空间。所谓“模块”就是经过编译但未经连接的.o文件。

2、模块中必定有一些在模块内部无法落实的符号(函数名或变量名),对这些符号的引用必须连接到内核中的相应符号。为此目的,需要通过系统调用query_module向内核询问这些符号在内核中的地址。如果内核允许"移出"这些符号的地址,就会返回有关的"符号表“。有些符号可能并不属于内核本身,而属于已经安装的其他模块。

至此在用户空间中完成了单向连接的模块镜像。后面init_module中参数mod_user,前半部分是module结构(里面的指针指向了后半部分的数据),后半部分是name大小+模块镜像(里面包含init_module和cleanup_module)+其他参数(例如deps)的集合体。

3、然后,通过系统调用create_module在内核中创建一个module数据结构,并且"预订"所需的系统(内核)空间。

4、最后,通过系统调用init_module,把用户空间中完成了单向连接的模块映像装入内核空间,再调用模块中一个名为init_module的函数(在Linux字符设备驱动一文中就是freg_init)。

接下来,我们解释query_module,代码如下:

asmlinkage long
sys_query_module(const char *name_user, int which, char *buf, size_t bufsize,//name_user为查询对象所在模块的模块名,查询的结果通过缓冲区buf返回
		 size_t *ret)
{
	struct module *mod;
	int err;

	lock_kernel();
	if (name_user == NULL)
		mod = &kernel_module;
	else {
		long namelen;
		char *name;

		if ((namelen = get_mod_name(name_user, &name)) < 0) {//把name从用户空间拷贝到系统空间
			err = namelen;
			goto out;
		}
		err = -ENOENT;
		if (namelen == 0)//如果为0,则指向内核本身
			mod = &kernel_module;
		else if ((mod = find_module(name)) == NULL) {//在module_list中寻找相应的module结构
			put_mod_name(name);
			goto out;
		}
		put_mod_name(name);
	}

	switch (which)
	{
	case 0:
		err = 0;
		break;
	case QM_MODULES:
		err = qm_modules(buf, bufsize, ret);
		break;
	case QM_DEPS:
		err = qm_deps(mod, buf, bufsize, ret);//所依赖模块的个数,buf为这些模块的模块名
		break;
	case QM_REFS:
		err = qm_refs(mod, buf, bufsize, ret);
		break;
	case QM_SYMBOLS:
		err = qm_symbols(mod, buf, bufsize, ret);
		break;
	case QM_INFO:
		err = qm_info(mod, buf, bufsize, ret);
		break;
	default:
		err = -EINVAL;
		break;
	}
out:
	unlock_kernel();
	return err;
}
struct module *
find_module(const char *name)
{
	struct module *mod;

	for (mod = module_list; mod ; mod = mod->next) {
		if (mod->flags & MOD_DELETED)
			continue;
		if (!strcmp(mod->name, name))
			break;
	}

	return mod;
}
static int
qm_deps(struct module *mod, char *buf, size_t bufsize, size_t *ret)
{
	size_t i, space, len;

	if (mod == &kernel_module)
		return -EINVAL;
	if (!MOD_CAN_QUERY(mod))
		if (put_user(0, ret))
			return -EFAULT;
		else
			return 0;

	space = 0;
	for (i = 0; i < mod->ndeps; ++i) {
		const char *dep_name = mod->deps[i].dep->name;//所依赖的module的名字;mod->deps[i]指向了module_ref

		len = strlen(dep_name)+1;
		if (len > bufsize)
			goto calc_space_needed;
		if (copy_to_user(buf, dep_name, len))
			return -EFAULT;
		buf += len;
		bufsize -= len;
		space += len;
	}

	if (put_user(i, ret))
		return -EFAULT;
	else
		return 0;

calc_space_needed:
	space += len;
	while (++i < mod->ndeps)
		space += strlen(mod->deps[i].dep->name)+1;

	if (put_user(space, ret))
		return -EFAULT;
	else
		return -ENOSPC;
}

各种数据结构如下:

struct module_symbol
{
	unsigned long value;
	const char *name;
};

struct module_ref
{
	struct module *dep;	/* "parent" pointer */
	struct module *ref;	/* "child" pointer */
	struct module_ref *next_ref;
};

/* TBD */
struct module_persist;

struct module
{
	unsigned long size_of_struct;	/* == sizeof(module) */
	struct module *next;
	const char *name;//模块名
	unsigned long size;//模块的大小

	union
	{
		atomic_t usecount;
		long pad;
	} uc;				/* Needs to keep its size - so says rth */

	unsigned long flags;		/* AUTOCLEAN et al */

	unsigned nsyms;//符号数
	unsigned ndeps;//这个module所依赖module的个数

	struct module_symbol *syms;//描述一个符号,包括符号名及其所在的地址
	struct module_ref *deps;//这个module所依赖的module,因为数量是固定的,所以是个数组
	struct module_ref *refs;//谁引用了这个module
	int (*init)(void);//init_module
	void (*cleanup)(void);//cleanup_module
	const struct exception_table_entry *ex_table_start;
	const struct exception_table_entry *ex_table_end;
#ifdef __alpha__
	unsigned long gp;
#endif
	/* Members past this point are extensions to the basic
	   module support and are optional.  Use mod_member_present()
	   to examine them.  */
	const struct module_persist *persist_start;
	const struct module_persist *persist_end;
	int (*can_unload)(void);
	int runsize;			/* In modutils, not currently used */
	const char *kallsyms_start;	/* All symbols for kernel debugging */
	const char *kallsyms_end;
	const char *archdata_start;	/* arch specific data for module */
	const char *archdata_end;
	const char *kernel_data;	/* Reserved for kernel internal use */
}

再来看,create_module在内核中创建一个module数据结构,并且"预订"所需的系统(内核)空间,代码如下:

asmlinkage unsigned long
sys_create_module(const char *name_user, size_t size)//name_user为module的名字,size为module结构大小加上完成了单向连接的模块镜像大小
{
	char *name;
	long namelen, error;
	struct module *mod;

	if (!capable(CAP_SYS_MODULE))
		return -EPERM;
	lock_kernel();
	if ((namelen = get_mod_name(name_user, &name)) < 0) {//把name从用户空间拷贝到系统空间
		error = namelen;
		goto err0;
	}
	if (size < sizeof(struct module)+namelen) {
		error = -EINVAL;
		goto err1;
	}
	if (find_module(name) != NULL) {//在module_list中寻找相应的module结构
		error = -EEXIST;
		goto err1;
	}
	if ((mod = (struct module *)module_map(size)) == NULL) {//申请了module结构
		error = -ENOMEM;
		goto err1;
	}

	memset(mod, 0, sizeof(*mod));
	mod->size_of_struct = sizeof(*mod);//module结构的大小
	mod->next = module_list;
	mod->name = (char *)(mod + 1);//name存放在紧跟module结构后
	mod->size = size;//module结构大小+name大小+完成了单向连接的模块镜像大小+其他参数,如上面所示的mod_user
	memcpy((char*)(mod+1), name, namelen+1);//name复制到指定的位置(紧跟module后)

	put_mod_name(name);

	module_list = mod;	//链入到module_list中

	error = (long) mod;
	goto err0;
err1:
	put_mod_name(name);
err0:
	unlock_kernel();
	return error;
}

最后,我们看init_module,把用户空间中完成了单向连接的模块映像装入内核空间,再调用模块中一个名为init_module的函数,代码如下:

asmlinkage long
sys_init_module(const char *name_user, struct module *mod_user)//mod_user在上面我们已经介绍过了,是用户空间中module结构
{
	struct module mod_tmp, *mod;
	char *name, *n_name, *name_tmp = NULL;
	long namelen, n_namelen, i, error;
	unsigned long mod_user_size;
	struct module_ref *dep;

	if (!capable(CAP_SYS_MODULE))
		return -EPERM;
	lock_kernel();
	if ((namelen = get_mod_name(name_user, &name)) < 0) {//把name从用户空间拷贝到系统空间
		error = namelen;
		goto err0;
	}
	if ((mod = find_module(name)) == NULL) {//在module_list中寻找相应的module结构,我们刚刚创建了
		error = -ENOENT;
		goto err1;
	}

	/* Check module header size.  We allow a bit of slop over the
	   size we are familiar with to cope with a version of insmod
	   for a newer kernel.  But don‘t over do it. */
	if ((error = get_user(mod_user_size, &mod_user->size_of_struct)) != 0)//从用户空间拷贝module结构的大小到mod_user_size
		goto err1;
	if (mod_user_size < (unsigned long)&((struct module *)0L)->persist_start
	    || mod_user_size > sizeof(struct module) + 16*sizeof(void*)) {//对大小做了个检查
		printk(KERN_ERR "init_module: Invalid module header size.\n"
		       KERN_ERR "A new version of the modutils is likely "
				"needed.\n");
		error = -EINVAL;
		goto err1;
	}

	/* Hold the current contents while we play with the user‘s idea
	   of righteousness.  */
	mod_tmp = *mod;//把刚找到的内核空间module结构mod赋值给mod_tmp
	name_tmp = kmalloc(strlen(mod->name) + 1, GFP_KERNEL);	/* Where‘s kstrdup()? */
	if (name_tmp == NULL) {
		error = -ENOMEM;
		goto err1;
	}
	strcpy(name_tmp, mod->name);//把原来的名字保存在name_tmp中

	error = copy_from_user(mod, mod_user, mod_user_size);//把module结构mod_user从用户空间拷贝到内核空间module结构mod
	if (error) {
		error = -EFAULT;
		goto err2;
	}

	/* Sanity check the size of the module.  */
	error = -EINVAL;

	if (mod->size > mod_tmp.size) {
		printk(KERN_ERR "init_module: Size of initialized module "
				"exceeds size of created module.\n");
		goto err2;
	}

	//mod_bound用来检查由用户提供的指针所指的对象是否落在模块的边界(size以内)内

	if (!mod_bound(mod->name, namelen, mod)) {
		printk(KERN_ERR "init_module: mod->name out of bounds.\n");
		goto err2;
	}
	if (mod->nsyms && !mod_bound(mod->syms, mod->nsyms, mod)) {
		printk(KERN_ERR "init_module: mod->syms out of bounds.\n");
		goto err2;
	}
	if (mod->ndeps && !mod_bound(mod->deps, mod->ndeps, mod)) {
		printk(KERN_ERR "init_module: mod->deps out of bounds.\n");
		goto err2;
	}
	if (mod->init && !mod_bound(mod->init, 0, mod)) {
		printk(KERN_ERR "init_module: mod->init out of bounds.\n");
		goto err2;
	}
	if (mod->cleanup && !mod_bound(mod->cleanup, 0, mod)) {
		printk(KERN_ERR "init_module: mod->cleanup out of bounds.\n");
		goto err2;
	}
	if (mod->ex_table_start > mod->ex_table_end
	    || (mod->ex_table_start &&
		!((unsigned long)mod->ex_table_start >= ((unsigned long)mod + mod->size_of_struct)
		  && ((unsigned long)mod->ex_table_end
		      < (unsigned long)mod + mod->size)))
	    || (((unsigned long)mod->ex_table_start
		 - (unsigned long)mod->ex_table_end)
		% sizeof(struct exception_table_entry))) {
		printk(KERN_ERR "init_module: mod->ex_table_* invalid.\n");
		goto err2;
	}
	if (mod->flags & ~MOD_AUTOCLEAN) {
		printk(KERN_ERR "init_module: mod->flags invalid.\n");
		goto err2;
	}
#ifdef __alpha__
	if (!mod_bound(mod->gp - 0x8000, 0, mod)) {
		printk(KERN_ERR "init_module: mod->gp out of bounds.\n");
		goto err2;
	}
#endif
	if (mod_member_present(mod, can_unload)
	    && mod->can_unload && !mod_bound(mod->can_unload, 0, mod)) {
		printk(KERN_ERR "init_module: mod->can_unload out of bounds.\n");
		goto err2;
	}
	if (mod_member_present(mod, kallsyms_end)) {
	    if (mod->kallsyms_end &&
		(!mod_bound(mod->kallsyms_start, 0, mod) ||
		 !mod_bound(mod->kallsyms_end, 0, mod))) {
		printk(KERN_ERR "init_module: mod->kallsyms out of bounds.\n");
		goto err2;
	    }
	    if (mod->kallsyms_start > mod->kallsyms_end) {
		printk(KERN_ERR "init_module: mod->kallsyms invalid.\n");
		goto err2;
	    }
	}
	if (mod_member_present(mod, archdata_end)) {
	    if (mod->archdata_end &&
		(!mod_bound(mod->archdata_start, 0, mod) ||
		 !mod_bound(mod->archdata_end, 0, mod))) {
		printk(KERN_ERR "init_module: mod->archdata out of bounds.\n");
		goto err2;
	    }
	    if (mod->archdata_start > mod->archdata_end) {
		printk(KERN_ERR "init_module: mod->archdata invalid.\n");
		goto err2;
	    }
	}
	if (mod_member_present(mod, kernel_data) && mod->kernel_data) {
	    printk(KERN_ERR "init_module: mod->kernel_data must be zero.\n");
	    goto err2;
	}

	/* Check that the user isn‘t doing something silly with the name.  */

	if ((n_namelen = get_mod_name(mod->name - (unsigned long)mod
				      + (unsigned long)mod_user,
				      &n_name)) < 0) {//把用户空间的module结构mod_user的name拷贝到n_name
		printk(KERN_ERR "init_module: get_mod_name failure.\n");
		error = n_namelen;
		goto err2;
	}
	if (namelen != n_namelen || strcmp(n_name, mod_tmp.name) != 0) {//n_name和原来的mod_tmp.name比较,是否一致
		printk(KERN_ERR "init_module: changed module name to "
				"`%s‘ from `%s‘\n",
		       n_name, mod_tmp.name);
		goto err3;
	}

	//通过了检查

	if (copy_from_user((char *)mod+mod_user_size,
			   (char *)mod_user+mod_user_size,
			   mod->size-mod_user_size)) {//把模块镜像(里面包含init_module和cleanup_module)和其他参数(例如deps)的集合体从用户空间拷贝到内核空间module结构mod的后面
		error = -EFAULT;
		goto err3;
	}

	if (module_arch_init(mod))
		goto err3;

	/* On some machines it is necessary to do something here
	   to make the I and D caches consistent.  */
	flush_icache_range((unsigned long)mod, (unsigned long)mod + mod->size);

	mod->next = mod_tmp.next;
	mod->refs = NULL;

	/* Sanity check the module‘s dependents */
	for (i = 0, dep = mod->deps; i < mod->ndeps; ++i, ++dep) {
		struct module *o, *d = dep->dep;

		/* Make sure the indicated dependencies are really modules.  */
		if (d == mod) {//检查所依赖的module不是自身的module
			printk(KERN_ERR "init_module: self-referential "
					"dependency in mod->deps.\n");
			goto err3;
		}

		/* Scan the current modules for this dependency */
		for (o = module_list; o != &kernel_module && o != d; o = o->next)
			;

		if (o != d) {//检查所依赖的module必须在module_list中
			printk(KERN_ERR "init_module: found dependency that is "
				"(no longer?) a module.\n");
			goto err3;
		}
	}

	/* Update module references.  */
	for (i = 0, dep = mod->deps; i < mod->ndeps; ++i, ++dep) {
		struct module *d = dep->dep;//根据"依赖"关系,确定"引用"关系

		dep->ref = mod;
		dep->next_ref = d->refs;
		d->refs = dep;
		/* Being referenced by a dependent module counts as a
		   use as far as kmod is concerned.  */
		d->flags |= MOD_USED_ONCE;
	}

	/* Free our temporary memory.  */
	put_mod_name(n_name);
	put_mod_name(name);

	/* Initialize the module.  */
	mod->flags |= MOD_INITIALIZING;
	atomic_set(&mod->uc.usecount,1);
	if (mod->init && (error = mod->init()) != 0) {//调用init函数,也就是init_module
		atomic_set(&mod->uc.usecount,0);
		mod->flags &= ~MOD_INITIALIZING;
		if (error > 0)	/* Buggy module */
			error = -EBUSY;
		goto err0;
	}
	atomic_dec(&mod->uc.usecount);

	/* And set it running.  */
	mod->flags = (mod->flags | MOD_RUNNING) & ~MOD_INITIALIZING;
	error = 0;
	goto err0;

err3:
	put_mod_name(n_name);
err2:
	*mod = mod_tmp;
	strcpy((char *)mod->name, name_tmp);	/* We know there is room for this */
err1:
	put_mod_name(name);
err0:
	unlock_kernel();
	kfree(name_tmp);
	return error;
}

mod_bound,用来检查由用户提供的指针所指的对象是否落在模块的边界(size以内)内,代码如下:

#define mod_bound(p, n, m) ((unsigned long)(p) >= ((unsigned long)(m) + ((m)->size_of_struct)) && 	         (unsigned long)((p)+(n)) <= (unsigned long)(m) + (m)->size)

对于,mod_bound(mod->name, namelen, mod)来说:

((unsigned long)(mod->name) >= ((unsigned long)(mod) + ((mod)->size_of_struct)) && 	         (unsigned long)((mod->name)+(namelen)) <= (unsigned long)(mod) + (mod)->size)

最后sys_init_module,调用了mod->init方法,也就是init_module,对于Linux字符设备驱动,也就是freg_init。

时间: 2024-08-11 05:04:05

Linux内核源代码情景分析-insmod的相关文章

Linux内核源代码情景分析-内存管理之slab-回收

在上一篇文章Linux内核源代码情景分析-内存管理之slab-分配与释放,最后形成了如下图的结构: 图 1 我们看到空闲slab块占用的若干页面,不会自己释放:我们是通过kmem_cache_reap和kmem_cache_shrink来回收的.他们的区别是: 1.我们先看kmem_cache_shrink,代码如下: int kmem_cache_shrink(kmem_cache_t *cachep) { if (!cachep || in_interrupt() || !is_chaine

Linux内核源代码情景分析-系统调用mknod

普通文件可以用open或者create创建,FIFO文件可以用pipe创建,mknod主要用于设备文件的创建. 在内核中,mknod是由sys_mknod实现的,代码如下: asmlinkage long sys_mknod(const char * filename, int mode, dev_t dev) //比如filename为/tmp/server_socket,dev是设备号 { int error = 0; char * tmp; struct dentry * dentry;

Linux内核源代码情景分析-文件系统的安装

执行sudo mount -t ext2 /dev/sdb1 /mnt/sdb,将文件系统挂在到/mnt/sdb上.系统调用mount,映射到内核层执行的是sys_mount.假设/dev/sdb1和/mnt/sdb都位于ext2文件系统中. asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type, unsigned long flags, void * data)//dev_name指向了"/dev/sdb

Linux内核源代码情景分析-访问权限与文件安全性

在Linux内核源代码情景分析-从路径名到目标节点,一文中path_walk代码中,err = permission(inode, MAY_EXEC)当前进程是否可以访问这个节点,代码如下: int permission(struct inode * inode,int mask) { if (inode->i_op && inode->i_op->permission) { int retval; lock_kernel(); retval = inode->i_

Linux内核源代码情景分析-文件系统安装后的访问

在Linux内核源代码情景分析-文件系统的安装,一文中,已经调用sudo mount -t ext2 /dev/sdb1 /mnt/sdb,在/mnt/sdb节点上挂载了文件系统,那么我们接下来访问/mnt/sdb/hello.c节点.我们来看一下path_walk的执行有什么不同? int path_walk(const char * name, struct nameidata *nd) { struct dentry *dentry; struct inode *inode; int er

Linux内核源代码情景分析-内存管理

用户空间的页面有下面几种: 1.普通的用户空间页面,包括进程的代码段.数据段.堆栈段.以及动态分配的"存储堆". 2.通过系统调用mmap()映射到用户空间的已打开文件的内容. 3.进程间的共享内存区. 这些页面的的周转有两方面的意思. 1.页面的分配,使用,回收.如进程压栈时新申请的页面,这类页面不进行盘区交换,不使用时释放得以回收. 这部分通过一个场景来解释: Linux内核源代码情景分析-内存管理之用户堆栈的扩展. 2.盘区交换.如要执行硬盘上的对应代码段.把硬盘上的代码段换入内

Linux内核源代码情景分析-共享内存

一.库函数shmget()--共享内存区的创建与寻找 asmlinkage long sys_shmget (key_t key, size_t size, int shmflg) { struct shmid_kernel *shp; int err, id = 0; down(&shm_ids.sem); if (key == IPC_PRIVATE) { err = newseg(key, shmflg, size);//分配一个共享内存区供本进程专用,最后返回的是一体化的标示号 } el

Linux内核源代码情景分析-mmap后,文件与虚拟区间建立映射

一.文件映射的页面换入 在mmap后,mmap参考Linux内核源代码情景分析-系统调用mmap(),当这个区间的一个页面首次受到访问时,会由于见面无映射而发生缺页异常,相应的异常处理程序do_no_page(). static inline int handle_pte_fault(struct mm_struct *mm, struct vm_area_struct * vma, unsigned long address, int write_access, pte_t * pte) {

Linux内核源代码情景分析-交换分区

在Linux内核源代码情景分析-共享内存中,共享内存,当内存紧张时是换出到交换分区. 在Linux内核源代码情景分析-mmap后,文件与虚拟区间建立映射中,文件映射的页面,当内存紧张时是换出到硬盘上的文件中. 这里的交换分区,就是是swap分区,记得给电脑安装ubuntu时,就有一项是swap分区. 交换分区和文件的区别是: 文件是在一个具体的文件系统之下的,交换分区没有这个必要,它可能是一个裸分区,不需要文件系统. 需要说明一点,并不是所有从物理内存中交换出来的数据都会被放到Swap中(如果这