Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1132894
  • 博文数量: 55
  • 博客积分: 400
  • 博客等级: 一等列兵
  • 技术积分: 2789
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-30 13:08
个人简介

一个人的差异在于业余时间

文章分类

全部博文(55)

文章存档

2019年(1)

2018年(1)

2017年(1)

2016年(2)

2015年(20)

2014年(10)

2013年(7)

2012年(12)

2011年(1)

分类: LINUX

2014-04-29 16:46:56

   我们在编写驱动的时,可能需要变成成模块(*.ko)来加载,比如常用的wifi驱动,在模块加载的时候,我们还希望能够动态的传递一些参数,这时候就需要Module_param系列宏,参考内核2.6.32.60  至于例子搜一下代码里很多尤其drivers目录下^^. 
 我们先看一个shell脚本,安装模块:

#########

   insmod net_dev.ko param_queue_id=${QUEUE_ID} param_board=$BOARD \

                    param_tcont_id=$TCONT_ID param_us_gem_flow_id=$US_GEMFLOW_ID
真正代码层:

static int param_queue_id =3;

module_param(param_queue_id, int, 0);

 

int map_ds_traffic_to_cpu = 0;

module_param(map_ds_traffic_to_cpu, int, 0);
static char param_board[20]="";
module_param_string(param_board, param_board, sizeof(param_board), 0);

//

参数用 module_param 宏定义来声明, 它定义在 moduleparam.h.

module_param(name,type,perm);

module_param 使用了 3 个参数: 变量名, 它的类型, 以及一个权限掩码用来做一个辅助的 sysfs 入口(查看/sys/module/xXX/会发现里面有我们对应的参数,当然必须有至少读的权限,默认0,为任何人都没有权限,甚至不可见). 这个宏定义应当放在任何函数之外, 典型地是出现在源文件的前面.

数组参数, 用逗号间隔的列表提供的值, 模块加载者也支持. 声明一个数组参数, 使用:

  module_param_array(name,type,num,perm);

  这里 name 是你的数组的名称(也是参数名),

  type 是数组元素的类型,

  num 是一个整型变量,

  perm 是通常的权限值.

如果数组参数在加载时设置, num 被设置成提供的数的个数. 模块加载者拒绝比数组能放下的多的值. module_param_array() 宏的第三个参数用来记录用户insmod 时提供的给这个数组的元素个数,NULL 表示不关心用户提供的个数

如果模块参数是一个字符串时,通常使用charp类型定义这个模块参数。内核复制用户提供的字符串到内存,并且相对应的变量指向这个字符串。

例如:
static char *name;
module_param(name, charp, 0);

另一种方法是通过宏module_param_string()让内核把字符串直接复制到程序中的字符数组内。
module_param_string(name, string, len, perm);

这里,name是外部的参数名,string是内部的变量名,len是以string命名的buffer大小(可以小于buffer的大小,但是没有意义),perm表示sysfs的访问权限(或者perm是零,表示完全关闭相对应的sysfs项)。

例如:
static char species[BUF_LEN];
module_param_string(specifies, species, BUF_LEN, 0);

如果需要传递多个参数可以通过宏module_param_array()实现。 
module_param_array(name, type, nump, perm);
其中,name既是外部模块的参数名又是程序内部的变量名,type是数据类型,perm是sysfs的访问权限。指针nump指向一个整数,其值表示有多少个参数存放在数组name中。值得注意是name数组必须静态分配.
   说了这么多,来让我们看看它的本质吧.
    include/linux/moduleparam.h
   

点击(此处)折叠或打开

  1. /* This is the fundamental function for registering boot/module
  2.    parameters. perm sets the visibility in sysfs: 000 means it's
  3.    not there, read bits mean it's readable, write bits mean it's
  4.    writable. */
  5. #define __module_param_call(prefix, name, set, get, arg, isbool, perm)    \
  6.     /* Default value instead of permissions? */            \
  7.     static int __param_perm_check_##name __attribute__((unused)) =    \
  8.     BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2))    \
  9.     + BUILD_BUG_ON_ZERO(sizeof(""prefix) > MAX_PARAM_PREFIX_LEN);    \
  10.     static const char __param_str_##name[] = prefix #name;        \
  11.     static struct kernel_param __moduleparam_const __param_##name    \
  12.     __used                                \
  13.     __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
  14.     = { __param_str_##name, perm, isbool ? KPARAM_ISBOOL : 0,    \
  15.      set, get, { arg } }

  16. #define module_param_call(name, set, get, arg, perm)             \
  17.     __module_param_call(MODULE_PARAM_PREFIX,             \
  18.              name, set, get, arg,             \
  19.              __same_type(*(arg), bool), perm)

  20. /* Helper functions: type is byte, short, ushort, int, uint, long,
  21.    ulong, charp, bool or invbool, or XXX if you define param_get_XXX,
  22.    param_set_XXX and param_check_XXX. */
  23. #define module_param_named(name, value, type, perm)             \
  24.     param_check_##type(name, &(value));                 \
  25.     module_param_call(name, param_set_##type, param_get_##type, &value, perm); \
  26.     __MODULE_PARM_TYPE(name, #type)

  27. #define module_param(name, type, perm)                \
  28.     module_param_named(name, name, type, perm)
仔细上上面代码里层层剥离,其实开始就是权限检测,定义static const char __param_str_##name[] = prefix #name; 然后初始化struct kernel_param(其实就是把要保存的参数信息放这里),并放到内存节区 :__param中.
那么我们看下 struct kernel_param


点击(此处)折叠或打开

  1. struct kernel_param {
  2.     const char *name;
  3.     u16 perm;
  4.     u16 flags;
  5.     param_set_fn set;
  6.     param_get_fn get;
  7.     union {
  8.         void *arg;
  9.         const struct kparam_string *str;
  10.         const struct kparam_array *arr;
  11.     };
  12. };

  13. /* Special one for strings we want to copy into */
  14. struct kparam_string {
  15.     unsigned int maxlen;
  16.     char *string;
  17. };

  18. /* Special one for arrays */
  19. struct kparam_array
  20. {
  21.     unsigned int max;
  22.     unsigned int *num;
  23.     param_set_fn set;
  24.     param_get_fn get;
  25.     unsigned int elemsize;
  26.     void *elem;
  27. };
那么这里也简单的说一下模块加载的过程. 用户空间只是直接调用了系统调用sys_init_Module.对模块或者参数并没有处理. 这个接口在kernel/module.c 中

点击(此处)折叠或打开

  1. /* This is where the real work happens */
  2. SYSCALL_DEFINE3(init_module, void __user *, umod,
  3.         unsigned long, len, const char __user *, uargs)
  4. {
  5.     struct module *mod;
  6.     int ret = 0;

  7.     /* Must have permission */
  8.     if (!capable(CAP_SYS_MODULE) || modules_disabled)
  9.         return -EPERM;

  10.     /* Only one module load at a time, please */
  11.     if (mutex_lock_interruptible(&module_mutex) != 0)
  12.         return -EINTR;

  13.     /* Do all the hard work */
  14.     mod = load_module(umod, len, uargs);
  15.     if (IS_ERR(mod)) {
  16.         mutex_unlock(&module_mutex);
  17.         return PTR_ERR(mod);
  18.     }

  19.     /* Drop lock so they can recurse */
  20.     mutex_unlock(&module_mutex);

  21.     blocking_notifier_call_chain(&module_notify_list,
  22.             MODULE_STATE_COMING, mod);

  23.     do_mod_ctors(mod);
  24.     /* Start the module */
  25.     if (mod->init != NULL)
  26.         ret = do_one_initcall(mod->init);
  27.     if (ret < 0) {
  28.         /* Init routine failed: abort. Try to protect us from
  29.                    buggy refcounters. */
  30.         mod->state = MODULE_STATE_GOING;
  31.         synchronize_sched();
  32.         module_put(mod);
  33.         blocking_notifier_call_chain(&module_notify_list,
  34.                      MODULE_STATE_GOING, mod);
  35.         mutex_lock(&module_mutex);
  36.         free_module(mod);
  37.         mutex_unlock(&module_mutex);
  38.         wake_up(&module_wq);
  39.         return ret;
  40.     }
  41.     if (ret > 0) {
  42.         printk(KERN_WARNING
  43. "%s: '%s'->init suspiciously returned %d, it should follow 0/-E convention\n"
  44. "%s: loading module anyway...\n",
  45.          __func__, mod->name, ret,
  46.          __func__);
  47.         dump_stack();
  48.     }

  49.     /* Now it's a first class Wake up anyone waiting for it. */
  50.     mod->state = MODULE_STATE_LIVE;
  51.     wake_up(&module_wq);
  52.     blocking_notifier_call_chain(&module_notify_list,
  53.                  MODULE_STATE_LIVE, mod);

  54.     /* We need to finish all async code before the module init sequence is done */
  55.     async_synchronize_full();

  56.     mutex_lock(&module_mutex);
  57.     /* Drop initial reference. */
  58.     module_put(mod);
  59.     trim_init_extable(mod);
  60. #ifdef CONFIG_KALLSYMS
  61.     mod->num_symtab = mod->core_num_syms;
  62.     mod->symtab = mod->core_symtab;
  63.     mod->strtab = mod->core_strtab;
  64. #endif
  65.     module_free(mod, mod->module_init);
  66.     mod->module_init = NULL;
  67.     mod->init_size = 0;
  68.     mod->init_text_size = 0;
  69.     mutex_unlock(&module_mutex);

  70.     return 0;
  71. }
里面核心函数load_module,把模块代码和数据从用户空间copy到内核空间,等。因为我们知道ko文件其实也是elf格式文件,至于如何解读和执行elf文件,大家可以阅读elf文件格式和文档,这里不过多解释.相当于用户空间执行解析elf,放到了内核来做,当然也是有区别的^^.
查看模块信息:

modinfo [-adhpv][模块文件]

 功能:显示kernel模块的信息。

 参数:
       -a或--author  显示模块开发人员。 

  -d或--description  显示模块的说明。 
     -h或--help  显示modinfo的参数使用方法。 
     -p或--parameters  显示模块所支持的参数。 
     -V或--version  显示版本信息。


既然上面说到了模块参数,这里顺便也说下

 引导期间内核选项 __setup
用__setup宏注册关键字 
net/core/dev.c 比较常见的netdev关键字 ,在系统启动时候的参数

点击(此处)折叠或打开

  1. /*
  2.  * Saves at boot time configured settings for any netdevice.
  3.  */
  4. int __init netdev_boot_setup(char *str)
  5. {
  6.     int ints[5];
  7.     struct ifmap map;

  8.     str = get_options(str, ARRAY_SIZE(ints), ints);
  9.     if (!str || !*str)
  10.         return 0;

  11.     /* Save settings */
  12.     memset(&map, 0, sizeof(map));
  13.     if (ints[0] > 0)
  14.         map.irq = ints[1];
  15.     if (ints[0] > 1)
  16.         map.base_addr = ints[2];
  17.     if (ints[0] > 2)
  18.         map.mem_start = ints[3];
  19.     if (ints[0] > 3)
  20.         map.mem_end = ints[4];

  21.     /* Add new entry to the list */
  22.     return netdev_boot_setup_add(str, &map);
  23. }

  24. __setup("netdev=", netdev_boot_setup);
还有在启动时候,必须挂载根文件系统,指定root目录(如果谁pc机安装过linux系统,在启动的时候,有个选择启动的界面,可以打断,可以看到后面的参数;一般bootloader用grub ) init/do_mounts.c

点击(此处)折叠或打开

  1. static int __init root_dev_setup(char *line)
  2. {
  3.     strlcpy(saved_root_name, line, sizeof(saved_root_name));
  4.     return 1;
  5. }

  6. __setup("root=", root_dev_setup);

点击(此处)折叠或打开

  1. static int __init readonly(char *str)
  2. {
  3.     if (*str)
  4.         return 0;
  5.     root_mountflags |= MS_RDONLY;
  6.     return 1;
  7. }

  8. static int __init readwrite(char *str)
  9. {
  10.     if (*str)
  11.         return 0;
  12.     root_mountflags &= ~MS_RDONLY;
  13.     return 1;
  14. }

  15. __setup("ro", readonly);
  16. __setup("rw", readwrite);

我们看看它是如何调用的. 
在init/main.c 中 
asmlinkage void __init start_kernel(void)
{

...
printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
  __stop___param - __start___param,
  &unknown_bootoption);

...

}

这里我们看到启动的时候传递的参数会在这里解析。我们继续回到init.h

点击(此处)折叠或打开

  1. struct obs_kernel_param {
  2.     const char *str;
  3.     int (*setup_func)(char *);
  4.     int early;
  5. };

  6. /*
  7.  * Only for really core code. See moduleparam.h for the normal way.
  8.  *
  9.  * Force the alignment so the compiler doesn't space elements of the
  10.  * obs_kernel_param "array" too far apart in .init.setup.
  11.  */
  12. #define __setup_param(str, unique_id, fn, early)            \
  13.     static const char __setup_str_##unique_id[] __initconst    \
  14.         __aligned(1) = str; \
  15.     static struct obs_kernel_param __setup_##unique_id    \
  16.         __used __section(.init.setup)            \
  17.         __attribute__((aligned((sizeof(long)))))    \
  18.         = { __setup_str_##unique_id, fn, early }

  19. #define __setup(str, fn)                    \
  20.     __setup_param(str, fn, fn, 0)

  21. /* NOTE: fn is as per module_param, not Emits warning if fn
  22.  * returns non-zero. */
  23. #define early_param(str, fn)                    \
  24.     __setup_param(str, fn, fn, 1)

注册的时候有两个宏,__setup, early_param  当然既然带个early自然会比__setup注册的处理的早. 也就是我们看到start_kernel里两个函数的先后顺序.
我们就把宏展开:
本质是注册了struct obs_kernel_param 并把它放到了内存节区 .init.setup ,关于内存节区前面我们讲过。当然如果注册的关键字被编译成模块,那么这个宏扩展为空. 
我们先看early解析:

点击(此处)折叠或打开

  1. /* Check for early params. */
  2. static int __init do_early_param(char *param, char *val)
  3. {
  4.     struct obs_kernel_param *p;

  5.     for (p = __setup_start; p < __setup_end; p++) {
  6.         if ((p->early && strcmp(param, p->str) == 0) ||
  7.          (strcmp(param, "console") == 0 &&
  8.          strcmp(p->str, "earlycon") == 0)
  9.         ) {
  10.             if (p->setup_func(val) != 0)
  11.                 printk(KERN_WARNING
  12.                  "Malformed early option '%s'\n", param);
  13.         }
  14.     }
  15.     /* We accept everything at this stage. */
  16.     return 0;
  17. }
include/asm-generic/vmlinux-lds.h

点击(此处)折叠或打开

  1. #define INIT_SETUP(initsetup_align)                    \
  2.         . = ALIGN(initsetup_align);                \
  3.         VMLINUX_SYMBOL(__setup_start) = .;            \
  4.         *(.init.setup)                        \
  5.         VMLINUX_SYMBOL(__setup_end) = .;
就是查询节区.init.setup ,如果查到就调用p->setup_func(val)
那么正常注册的关键字呢?

点击(此处)折叠或打开

  1. parse_args("Booting kernel", static_command_line, __start___param,
  2.          __stop___param - __start___param,
  3.          &unknown_bootoption);
我们注意看第三个参数.那么__start_param在哪里呢?没见过吧^^ 
include/asm-generic/vmlinux-lds.h :

点击(此处)折叠或打开

  1. /* Built-in module parameters. */                \
  2.     __param : AT(ADDR(__param) - LOAD_OFFSET) {            \
  3.         VMLINUX_SYMBOL(__start___param) = .;            \
  4.         *(__param)                        \
  5.         VMLINUX_SYMBOL(__stop___param) = .;            \
  6.         . = ALIGN((align));                    \
  7.         VMLINUX_SYMBOL(__end_rodata) = .;

这里查询__param ,如果找到赋值并返回.

点击(此处)折叠或打开

  1. /* Args looks like "foo=bar,bar2 baz=fuz wiz". */
  2. int parse_args(const char *name,
  3.      char *args,
  4.      struct kernel_param *params,
  5.      unsigned num,
  6.      int (*unknown)(char *param, char *val))
  7. {
  8.     char *param, *val;

  9.     DEBUGP("Parsing ARGS: %s\n", args);

  10.     /* Chew leading spaces */
  11.     while (isspace(*args))
  12.         args++;

  13.     while (*args) {
  14.         int ret;
  15.         int irq_was_disabled;

  16.         args = next_arg(args, &param, &val);
  17.         irq_was_disabled = irqs_disabled();
  18.         ret = parse_one(param, val, params, num, unknown);
  19.         if (irq_was_disabled && !irqs_disabled()) {
  20.             printk(KERN_WARNING "parse_args(): option '%s' enabled "
  21.                     "irq's!\n", param);
  22.         }
  23.         switch (ret) {
  24.         case -ENOENT:
  25.             printk(KERN_ERR "%s: Unknown parameter `%s'\n",
  26.              name, param);
  27.             return ret;
  28.         case -ENOSPC:
  29.             printk(KERN_ERR
  30.              "%s: `%s' too large for parameter `%s'\n",
  31.              name, val ?: "", param);
  32.             return ret;
  33.         case 0:
  34.             break;
  35.         default:
  36.             printk(KERN_ERR
  37.              "%s: `%s' invalid for parameter `%s'\n",
  38.              name, val ?: "", param);
  39.             return ret;
  40.         }
  41.     }

  42.     /* All parsed OK. */
  43.     return 0;
  44. }











阅读(2783) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~