2.6内核中动态模块加载时参数设置

一、问题的引出

在执行modprobe 的时候,发现oprofile这个动态模块可以在加载时指定参数,强制使用timer=1来让内核使用定时器方式来进行系统的监控。从常识上来说,这个timer参数肯定不是在用户态解析出来的,因为模块加载毕竟是一个模块特有的框架性东西,不可能在用户态做专门出来,所以就看一下内核是怎么和用户态的程序进行交互的。

二、模块特有参数的声明

以引入该问题的oprofile为例 \linux-2.6.21\drivers\oprofile\oprof.c其中就有一个定时器模式的强制选择方法:

有相关的声明

/* timer
   0 - use performance monitoring hardware if available
   1 - use the timer int mechanism regardless
 */
static int timer = 0;

module_param_named(timer, timer, int, 0644);
MODULE_PARM_DESC(timer, "force use of timer interrupt");

可以看到,这个地方有一个C语言通常的静态数据类型声明,还有一个动态的模块声明。对于模块内直接变量的声明,我们是比较容易理解的,但是问题就是这个模块命令行参数是如何处理的。

linux-2.6.21\include\linux\moduleparam.h

/* This is the fundamental function for registering boot/module
   parameters.  perm sets the visibility in sysfs: 000 means it's
   not there, read bits mean it's readable, write bits mean it's
   writable. */
#define __module_param_call(prefix, name, set, get, arg, perm)  \
 /* Default value instead of permissions? */   \
 static int __param_perm_check_##name __attribute__((unused)) = \
 BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2)); \
 static char __param_str_##name[] = prefix #name;  \这里定义一个字符串名称,也就是elf节中的mod中给变量起得特殊字符串名称,相当于字符串形式的变量ID,因为在C语言处理后,代码中的static int timer已经从目标文件中消失
 static struct kernel_param const __param_##name   \
 __attribute_used__      \
    __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
 = { __param_str_##name, perm, set, get, arg }然后这个结构统一放入一个内核预定时的数据结构kernel_param结构中,这是一个结构体。我们要注意的是,这个结构中所有的符号都是要被重新定义的,而最后的arg则是一个变量的位置,也就是最后将字符串形式的变量转换为数值之后需要保存的位置,并且这里它们在内核加载之后都需要重定向,这个歌结构放入模块的一个特定的__param节

 

#define module_param_call(name, set, get, arg, perm)         \
 __module_param_call(MODULE_PARAM_PREFIX, name, set, get, arg, perm)

 

/* Helper functions: type is byte, short, ushort, int, uint, long,
   ulong, charp, bool or invbool, or XXX if you define param_get_XXX,
   param_set_XXX and param_check_XXX. */
#define module_param_named(name, value, type, perm)      \
 param_check_##type(name, &(value));       \
 module_param_call(nameparam_set_##typeparam_get_##type, &value, perm); \这里的name就是专门为模块的某个参数提供的专门的命名,这个可以和模块中定义的变量名不同;param_set_type将会被替换为param_set_int,同样接下来被替换为param_get_int,这两个都是对字符串形式的数值进行向数值转换的接口实现;接下来的value则表示这个C语言中定义的(或者说模块中真正使用的变量的值,这里就是那个static int timer),最后的权限我们就先不管了
 __MODULE_PARM_TYPE(name, #type)

三、模块的加载

linux-2.6.21\kernel\module.c

sys_init_module-->>load_module-->>>>simplify_symbols


/* Change all symbols so that sh_value encodes the pointer directly. */
static int simplify_symbols(Elf_Shdr *sechdrs,
       unsigned int symindex,
       const char *strtab,
       unsigned int versindex,
       unsigned int pcpuindex,
       struct module *mod)
{
 Elf_Sym *sym = (void *)sechdrs[symindex].sh_addr;
 unsigned long secbase;
 unsigned int i, n = sechdrs[symindex].sh_size / sizeof(Elf_Sym);
 int ret = 0;

 for (i = 1; i < n; i++) {
  switch (sym[i].st_shndx) {
  case SHN_COMMON:
   /* We compiled with -fno-common.  These are not
      supposed to happen.  */
   DEBUGP("Common symbol: %s\n", strtab + sym[i].st_name);
   printk("%s: please compile with -fno-common\n",
          mod->name);
   ret = -ENOEXEC;
   break;

  case SHN_ABS:
   /* Don't need to do anything */
   DEBUGP("Absolute symbol: 0x%08lx\n",
          (long)sym[i].st_value);
   break;

  case SHN_UNDEF:
   sym[i].st_value
     = resolve_symbol(sechdrs, versindex,
        strtab + sym[i].st_name, mod);这个resolve_symbol函数是一个重要函数,从这个函数我们应该知道,内核中为什么要导出一些static符号,不然动态加载模块无法找到这些它引用的变量,将会导致这些变量模块动态加载失败。同时也说明我们在内核中dump_stack可以看到很多的内核符号,它们并不是为了dump_stack而设置的,更重要的就是为了执行这个动态加载该函数之后,ko文件中的所有符号都会被重定位到,也就是说,这个模块中的所有符号都有了当前运行内核的绝对地址。这个其实也是一个地址无关代码,所以可以在内核中实现不同的ko版本的尽量兼容这个重定位之后,我们原来在编译的时候生成的param_get_int和param_set_int的函数也已经被重定位好,而参数int在内核的位置也是确定的,所以就可以开始接受并解析用户态传入的字符串信息了

   /* Ok if resolved.  */
   if (sym[i].st_value != 0)
    break;
   /* Ok if weak.  */
   if (ELF_ST_BIND(sym[i].st_info) == STB_WEAK)
    break;

   printk(KERN_WARNING "%s: Unknown symbol %s\n",
          mod->name, strtab + sym[i].st_name);
   ret = -ENOENT;
   break;

  default:这个是大部分static变量的重定位方式,因为static引用的符号大部分都是在该ko文件中定义的,所以它的重定位一般是代码段或者是数据段的基地址加上一个编译时确定的地址,所以在ko文件加载入内核之后,这个基地址就确定了,所以通过这个加载时基地址加上这个编译时地址就得到了模块内的绝对地址
   /* Divert to percpu allocation if a percpu var. */
   if (sym[i].st_shndx == pcpuindex)
    secbase = (unsigned long)mod->percpu;
   else
    secbase = sechdrs[sym[i].st_shndx].sh_addr;
   sym[i].st_value += secbase;
   break;
  }
 }

 return ret;
}
四、参数的解析

parse_args---》》》parse_one其中还有对next_arg的调用,这些代码都是相约虻サ腃语言字符串操作,所以不再注释

 /* Find parameter */
 for (i = 0; i < num_params; i++) {
  if (parameq(param, params[i].name)) {
   DEBUGP("They are equal!  Calling %p\n",
          params[i].set);
   return params[i].set(val, &params[i]);
  }
 }

从这里可以看到,模块比较实用的名字是

#define module_param_named(name, value, type, perm)

中的第一变量,而和真正的内核使用的C语言代码没有关系。所以,也就是说,内核可以用

module_param_named(HEHE, HAHA, type, perm)

stati int HAHA;

但是用户态要通过

insmod xxx HEHE=xx

的形式,也就是第一个参数而不是内核中的C语言代码,当然两者也可以一样,就是使用

#define module_param(name, type, perm)    \
 module_param_named(name, name, type, perm)

posted on 2019-03-06 20:29  tsecer  阅读(637)  评论(0编辑  收藏  举报

导航