Chinaunix首页 | 论坛 | 博客
  • 博客访问: 240884
  • 博文数量: 54
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 431
  • 用 户 组: 普通用户
  • 注册时间: 2014-07-26 09:36
文章分类

全部博文(54)

分类: LINUX

2016-01-28 10:21:29

1.平台简介:

    硬件平台: QCA9531
    软件平台: Openwrt-trunk    Kernel-4.1.15

2.软件调试:

 2.1 __setup()宏

 在研究uboot传递给内核的cmdline解析的时候,我们经常会看到一个宏__setup(), 如console变量:
--/kernel/printk/printk.c

......
__setup("console=", console_setup);
......

      这到底是什么意思呢? 我们先来看看__setup()宏的定义就知道了:
--/include/linux/init.h

#define __setup_param(str, unique_id, fn, early)                          \
        static const char __setup_str_##unique_id[] __initconst         \
                __aligned(1) = str;                                                   \
        static struct obs_kernel_param __setup_##unique_id             \
                __used __section(.init.setup)                                      \
                __attribute__((aligned((sizeof(long)))))                      \
                = { __setup_str_##unique_id, fn, early }

#define __setup(str, fn)                                                \
        __setup_param(str, fn, fn, 0)


struct obs_kernel_param {                                          #NOTE:该变量定义在/include/linux/init.h中
        const char *str;
        int (*setup_func)(char *);
        int early;                                                           #NOTE:此字段用于控制命令行参数的初始化等级
};
  
      宏变量的作用主要是用来替换,这里使用console变量为例进行替换,看看最终的替换结果:
__setup("console=", console_setup)  -->
(NOTE:此次变换,直接替换参数即可)

_setup_param("console=", console_setup, console_setup, 0) --> 
(NOTE:此次变换,替换后定义了两个变量:

 一个字符数组__setup_str_console_setup,并初始化其为"console=";
 一个struct obs_kernel_param对象__setup_console_setup,并使用__setup_str_console_setup, console_setup, 0分别对其成员进行初始化。
 
static const char __setup_str_console_setup[] __initconst         \        
                __aligned(1) = "console=";                                    \
static struct obs_kernel_param __setup_console_setup             \
                __used __section(.init.setup)                                   \
                __attribute__((aligned((sizeof(long)))))                  \
                = { __setup_str_console_setup, console_setup, 0 }   (#NOTE:此处引用了上面定义的字符数组变量)

(#NOTE:上述替换过程中出现了一些编译器选项,下面一一进行解答:
__initconst: 编译器处理选项,主要用于控制链接器链接时,程序的代码段布局,其定义在include/linux/init.h,其内容如下:
                #define __init           __section(.init.text) __cold notrace
                #define __initdata      __section(.init.data)
                #define __initconst     __constsection(.init.rodata)        #NOTE:该选项表明作用对象链接到程序的.init.rodata段中
                #define __exitdata      __section(.exit.data)
                #define __exit_call     __used __section(.exitcall.exit)
__aligned(1): 存储类修饰符,主要用于控制字节对其,这里使用1字节对其,与char数组常规对其一致,因此此处没有什么实际作用,只是为了安全考虑。
__used:         变量或函数属性,当未被使用时编辑器将提供警告信息,作用与__unused相反。
__section(.init.setup) :指定代码段为.init.setup代码段,此处即为该参数处理的入口。这里我们看看链接脚本arch/mips/kernel/vmlinux.lds,此段是如何链接的:
                                    __setup_start = .; KEEP(*(.init.setup)) __setup_end = .;

__attribute__((aligned((sizeof(long))))) :指定对其长度为sizeof(long)





 2.1 Kernel对 bootargs的接收

 替换结果产生了一个字符数组和一个结构体变量,在探究参数引用之前,我们先探究以下这些参数的来源,即内核对bootargs的接收?
   booargs在openwrt的框架中的内核中有3种方式进行配置和修改:
      (1) 采用uboot动态传递参数的形式
      (2) 采用menuconfig中的Default kernel command string配置选项进行配置。
      (3) 采用openwrt中patch-cmdline工具进行替换,make kernel_menuconifg 时使用[*] OpenWrt specific image command line hack定义CONFIG_IMAGE_CMDLINE_HACK选项。

--/init/main.c

char __initdata boot_command_line[COMMAND_LINE_SIZE];

asmlinkage __visible void __init start_kernel(void)
{
   char *command_line;
      
     ......
        setup_arch(&command_line);
        ......
        mangle_bootargs(command_line);
        setup_command_line(command_line);
        ......
        pr_notice("Kernel command line: %s\n", boot_command_line);       #NOTE:启动时命令行参数的打印信息出自此处
        ......
}

--/arch/mips/kernel/setup.c

static char __initdata command_line[COMMAND_LINE_SIZE];
char __initdata arcs_cmdline[COMMAND_LINE_SIZE];
#ifdef CONFIG_CMDLINE_BOOL
static char __initdata builtin_cmdline[COMMAND_LINE_SIZE] = CONFIG_CMDLINE;
#endif



void __init setup_arch(char **cmdline_p)
{
       ......
       prom_init();                                            #NOTE:此处初始化prom,对9531而言,调用arch/mips/ath79/prom.c中的prom_init()函数
       ......
       arch_mem_init(cmdline_p);
       ......
}

static void __init arch_mem_init(char **cmdline_p)
{
      ......                                                                                                                            
(#NOTE:下述宏定义来自于内核的.config,位于kernel_menuconfig的如下位置:
            Kernel hacking-->
                 [*]Built-in kernel command line -->         (CONFIG_CMDLINE_BOOL)
                 (rootfstype=squashfs,jffs2 noinitrd) Default kernel command string         (CONFIG_CMDLINE)
                 [  ] Built-in command line overrides firmware arguments       (CONFIG_CMDLINE_OVERRIDE)
)

#ifdef CONFIG_CMDLINE_BOOL                                                     
#ifdef CONFIG_CMDLINE_OVERRIDE
#NOTE:采用静态CMDLINE, 若定义了OVERRIDE配置时,直接用builtin_cmdline(CONFIG_CMDLINE)替换boot_command_line
        strlcpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);                   
#else                                                                                                                                              
        if (builtin_cmdline[0]) {                                                          
#NOTE:采用静态CMDLINE,若未定义OVERRIDE配置时,用builtin_cmdline(CONFIG_CMDLINE)追加在arcs_cmdline之后,
           然后替换boot_command_line,arcs_cmdline的首次初始化在setup_arch()函数中调用的prom_init()函数中进行,
           详情参见"2.3 Openwrt patch-cmdline机制"
                                                   
                strlcat(arcs_cmdline, " ", COMMAND_LINE_SIZE);                          
                strlcat(arcs_cmdline, builtin_cmdline, COMMAND_LINE_SIZE);    
        }                                                                                                                 
        strlcpy(boot_command_line, arcs_cmdline, COMMAND_LINE_SIZE);                                      
#endif       
#else      
#NOTE:不采用静态CMDLINE,则直接用arcs_cmdline替换boot_command_line中的内容  
        strlcpy(boot_command_line, arcs_cmdline, COMMAND_LINE_SIZE);   
#endif       
        strlcpy(command_line, boot_command_line, COMMAND_LINE_SIZE);                                      

        *cmdline_p = command_line;                                                                        

        parse_early_param();                        #NOTE:此处在平台中对解析了struct obs_kernel_param中early字段为1的对象
       ......
}




 2.2 bootargs的引用

  替换结果产生了一个字符数组和一个结构体变量,这些变量又是什么时候被内核引用的呢?
--/init/main.c
static int __init obsolete_checksetup(char *line)
{
        const struct obs_kernel_param *p;
        int had_early_param = 0;

        p = __setup_start;                                           #NOTE:__setup_start定义在链接文件中,其为.init.setup段的首部。
        do {
                int n = strlen(p->str);           #NOTE: 取struct obs_kernel_param中的str的长度,如"console="
                if (parameqn(line, p->str, n)) {      #NOTE: 匹配对应的str字段
                        if (p->early) {            #NOTE: 标记此标记的参数,已经在函数parse_early_param()中处理。
                                /* Already done in parse_early_param?
                                 * (Needs exact match on param part).
                                 * Keep iterating, as we can have early
                                 * params and __setups of same names 8( */
                                if (line[n] == '\0' || line[n] == '=')
                                        had_early_param = 1;
                        } else if (!p->setup_func) {      #NOTE: 处理函数为空则忽略该参数
                                pr_warn("Parameter %s is obsolete, ignored\n",
                                        p->str);
                                return 1;
                        } else if (p->setup_func(line + n))  #NOTE:调用处理函数,并将参数的值传入,line+n指示的字符串即为命令行参数中对应参数=后面的值。
                                return 1;
                }
                p++;
        } while (p < __setup_end);          #NOTE:__setup_end定义在链接文件中,其为.init.setup段的尾部。

        return had_early_param;
}

       命令行参数分两次解析,首先解析early为1的特权参数,然后解析early为0的unknown参数,下面我们再来看看命令行参数解析的入口:
--/init/main.c
asmlinkage __visible void __init start_kernel(void)
{
     ......
        pr_notice("Kernel command line: %s\n", boot_command_line);       #NOTE:启动时命令行参数的打印信息出自此处
        parse_early_param(); #NOTE:第一次解析参数,解析struct obs_kernel_param中early字段为1的参数,该动作可能在setup_arch()函数中已经完成。
        after_dashes = parse_args("Booting kernel",      #NOTE:第二次解析参数,解析函数为unknown_bootoption
                                  static_command_line, __start___param,
                                  __stop___param - __start___param,
                                  -1, -1, &unknown_bootoption);
        ......
}


第一次参数解析:
void __init parse_early_param(void)
{
        static int done __initdata;
        static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;

        if (done)
                return;

        /* All fall through to do_early_param. */
        strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE); #NOTE:拷贝boot_command_line,为解析early参数做准备
        parse_early_options(tmp_cmdline);                   #NOTE:解析命令行参数
        done = 1;


void __init parse_early_options(char *cmdline)
{
#NOTE:这里可以看出,第一次和第二次参数解析调用函数一致,只是携带的处理函数不一样,这里携带的是do_early_param()回调函数。
        parse_args("early options", cmdline, NULL, 0, 0, 0, do_early_param);       
}

/* Check for early params. */
static int __init do_early_param(char *param, char *val, const char *unused)
{       
        const struct obs_kernel_param *p;

        for (p = __setup_start; p < __setup_end; p++) {  #NOTE:这里也是以.init.setup段中的__setup_start和__setup_end为搜索边界
                if ((p->early && parameq(param, p->str)) ||
                    (strcmp(param, "console") == 0 &&
                     strcmp(p->str, "earlycon") == 0)
                ) {
                        if (p->setup_func(val) != 0)
                                pr_warn("Malformed early option '%s'\n", param);
                }
        }
        /* We accept everything at this stage. */
        return 0;
}


    命令行解析函数:
--/kernel/params.c

/* Args looks like "foo=bar,bar2 baz=fuz wiz". */
char *parse_args(const char *doing, char *args, const struct kernel_param *params, unsigned num, s16 min_level, s16 max_level, int (*unknown)(char *param, char *val, const char *doing))
{
        char *param, *val;

        /* Chew leading spaces */
        args = skip_spaces(args);

        if (*args)
                pr_debug("doing %s, parsing ARGS: '%s'\n", doing, args);

        while (*args) {
                int ret;
                int irq_was_disabled;

                args = next_arg(args, ?m, &val);         #NOTE: 此处进行参数分割
                /* Stop at -- */
                if (!val && strcmp(param, "--") == 0)
                        return args;
                irq_was_disabled = irqs_disabled();
                ret = parse_one(param, val, doing, params, num,
                                min_level, max_level, unknown);
                if (irq_was_disabled && !irqs_disabled())
                        pr_warn("%s: option '%s' enabled irq's!\n",
                                doing, param);

                switch (ret) {
                case -ENOENT:
                        pr_err("%s: Unknown parameter `%s'\n", doing, param);
                        return ERR_PTR(ret);
                case -ENOSPC:
                        pr_err("%s: `%s' too large for parameter `%s'\n",
                               doing, val ?: "", param);
                        return ERR_PTR(ret);
                case 0:
                        break;
                default:
                        pr_err("%s: `%s' invalid for parameter `%s'\n",
                               doing, val ?: "", param);
                        return ERR_PTR(ret);
                }
        }

        /* All parsed OK. */
        return NULL;
}


(NOTE:此处以args 为"board=PIONEER-9531  console=ttyS0,115200 rootfstype=squashfs,jffs2 noinitrd"进行分析)
static char *next_arg(char *args, char **param, char **val)
{
        unsigned int i, equals = 0;
        int in_quote = 0, quoted = 0;
        char *next;

        if (*args == '"') {   #NOTE:第一次调用此函数时,此条件不成立,*args='b'
                args++;         
                in_quote = 1;
                quoted = 1;
        }

        for (i = 0; args[i]; i++) {
                if (isspace(args[i]) && !in_quote)    #NOTE:第一次调用时in_quote为0,直接跳出
                        break;
                if (equals == 0) {
                        if (args[i] == '=')
                                equals = i;
                }
                if (args[i] == '"')
                        in_quote = !in_quote;
        }

        *param = args;
        if (!equals)         #NOTE:第一次调用时,变量equals为0,满足条件
                *val = NULL;
        else {
                args[equals] = '\0';
                *val = args + equals + 1;

                /* Don't include quotes in value. */
                if (**val == '"') {
                        (*val)++;
                        if (args[i-1] == '"')
                                args[i-1] = '\0';
                }
        }
        if (quoted && args[i-1] == '"') #NOTE:第一次调用时,变量quoted为0,不满足条件
                args[i-1] = '\0';

        if (args[i]) {
                args[i] = '\0';
                next = args + i + 1;
        } else
                next = args + i;

        /* Chew up trailing spaces. */
        return skip_spaces(next);
}




2.3 Openwrt patch-cmdline机制

      Openwrt在原有内核的的bootargs配置的基础上,添加了CMDLINE_HACK的方式,若需要使用此方式需要选中[*] OpenWrt specific image command line hack选项。内核在编译固件的时候会使用patch-cmdline工具添加bootargs.
其中patch-cmdline工具的作用方式如下,(编译输出信息)
staging_dir/host/bin/patch-cmdline build_dir/target-mips_34kc_musl-1.1.11/linux-ar71xx_generic/pioneer-qca9531-64m-16m-kernel.bin 'board=PIONEER-9531  console=ttyATH0,115200'

其中,patch-cmdline工具源码如下:
tools/patch-image/src/patch-cmdline.c

int main()
{
        ......
        if (((fd = open(argv[1], O_RDWR)) < 0) ||
                (ptr = (char *) mmap(0, SEARCH_SPACE + CMDLINE_MAX, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == (void *) (-1)) {          #NOTE:此处利用mmap将固件内容映射出来
                fprintf(stderr, "Could not open kernel image");
                goto err2;
        }
    
        for (p = ptr; p < (ptr + SEARCH_SPACE); p += 4) {
                if (memcmp(p, "CMDLINE:", 8) == 0) {                  #NOTE:此处匹配head.s中设置的标记字符CMDLINE
                        found = 1;
                        p += 8;
                        break;
                }
        }
        if (!found) {
                fprintf(stderr, "Command line marker not found!\n");
                goto err3;
        }

        memset(p, 0, CMDLINE_MAX - 8);                                      #NOTE:下述利用调用工具时传入的参数替换固件中的bootargs
        strcpy(p, argv[2]);                                                        
        msync(p, CMDLINE_MAX, MS_SYNC|MS_INVALIDATE);
        ret = 0;
      
        ......
}



   Openwrt的CMDLINE_HACK机制是在head.S中添加标记字符CMDLINE:和预留其空间的方式进行实现,具体实现方式如下:
--arch/mips/kernel/head.S

#ifdef CONFIG_IMAGE_CMDLINE_HACK
        .ascii  "CMDLINE:"                                   #NOTE: 定义一个"CMDLINE:" ascii标记字符串,注意没有使用asciiz,末尾没有\0
EXPORT(__image_cmdline)                              #NOTE: 到处一个符号标识,这里可以看成是后面1024字节数组的数组名称
        .fill   0x400                                            #NOTE: 在字符串标记后,开辟1024B的空间,用于存放bootargs
#endif /* CONFIG_IMAGE_CMDLINE_HACK */

--arch/mips/ath79/prom.c

void __init prom_init(void)
{
     const char *env;

     if (ath79_prom_init_myloader())
                return;
     if (!ath79_use_image_cmdline())
                fw_init_cmdline();
      ......
}

#ifdef CONFIG_IMAGE_CMDLINE_HACK               #NOTE:这里是CMDLINE_HACK的配置开关
extern char __image_cmdline[];

static int __init ath79_use_image_cmdline(void)
{       
        char *p = __image_cmdline;                                    #NOTE:指向head.s中的1024字节的bootargs区域.
        int replace = 0;
        
        if (*p == '-') {                                                        #NOTE: 用patch-cmdline工具打入的bootargs,若以-开头,表示强制覆盖,否则按追加处理.
                replace = 1;
                p++;
        }

        if (*p == '\0')                                                         #NOTE: 若bootargs中的字符串开头即为\0,则立即返回
                return 0;

        if (replace) {                                                            #NOTE: 根据上述bootargs是否前缀-来控制是替换还是追加到arcs_cmdline
                strlcpy(arcs_cmdline, p, sizeof(arcs_cmdline)); 
        } else {
                strlcat(arcs_cmdline, " ", sizeof(arcs_cmdline));
                strlcat(arcs_cmdline, p, sizeof(arcs_cmdline));
        }
    
        /* Validate and setup environment pointer */
        if (fw_arg2 < CKSEG0)                                            #NOTE:重新校正环境变量指针
                _fw_envp = NULL;
        else
                _fw_envp = (int *)fw_arg2;
        
        return 1;
}
#else
static inline int ath79_use_image_cmdline(void) { return 0; }
#endif


                                                                                                        ------未完待续


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