分享

5.3 内核命令行处理(2)

 lifei_szdz 2013-06-22

5.3 内核命令行处理(2)

代码清单5-5是语法乏味的定义。回想代码清单5-4,我们最初所调用的__setup宏的形式如下:

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

经过稍稍简化,编译器在宏扩展后,其预处理器产生如下结果:

  1. static char __setup_str_console_setup[] __initdata = "console=";  
  2. static struct obs_kernel_param __setup_console_setup  \  
  3. __attribute__((__section__(".init.setup")))=  
  4.    {__setup_str_console_setup, console_setup, 0}; 

为了增加可读性,将上述结果的第2行和第3行采用UNIX的行续符"\"分隔开来。

我们故意略去了两个和本次讨论内容无关的编译器属性。简要地说,__attribute_ used__(本身就是一个隐藏了很多语法细节的宏)会告诉编译器发出一个函数或变量,即使在编译过程中并没有用到任何优化参数 。__attribute__(aligned)会告诉编译器按照特定的边界来对齐结构,在本例中是sizeof(long)。

简化处理后剩下的就是这种机制的核心部分。首先,编译器会产生名为__setup_str_ console_setup[]的初始化后字符数组,该数组包含console=字符串信息;其次,编译器会产生一个包含三个成员的结构:指向内核命令行字符串(在字符数组中声明)的指针、指向配置函数本身的指针和一个简单的标识。这里的关键在于依附于结构的段属性,该属性会通知编译器将该结构送到ELF目标模块内名为.init.setup的特殊段中。在这个链接阶段,所有由__setup宏定义的结构一起被放置到这个.init.setup段中,实际结果就是创建了一个包含这些结构的数组。代码清单5-6是.../init/main.c中的一部分内容,它们说明了这个数据是如何获取和使用的。

代码清单5-6 内核命令行处理

  1. 1 extern struct obs_kernel_param __setup_start[], __setup_end[];  
  2. 2  
  3. 3 static int __init obsolete_checksetup(char *line)  
  4. 4 {  
  5. 5         struct obs_kernel_param *p;  
  6. 6  
  7. 7         p = __setup_start;  
  8. 8         do {  
  9. 9                 int n = strlen(p->str);  
  10. 10                 if (!strncmp(line, p->str, n)) {  
  11. 11                         if (p->early) {  
  12. 12                                  /* Already done in parse_early_param? (Needs  
  13. 13                                   * exact match on param part) */  
  14. 14                                  if (line[n] == '\0' || line[n] == '=')  
  15. 15                                           return 1;  
  16. 16                         } else if (!p->setup_func) {  
  17. 17                             printk(KERN_WARNING "Parameter %s is obsolete,"  
  18. 18                                     " ignored\n", p->str);  
  19. 19                                  return 1;  
  20. 20                         } else if (p->setup_func(line + n))  
  21. 21                                  return 1;  
  22. 22                }  
  23. 23                p++;  
  24. 24        } while (p < __setup_end);  
  25. 25        return 0;  
  26. 26 } 

对该段代码解释还算简单。函数由一个在main.c文件中其他地方解析的单命令行参数调用。在这个例子中,我们要讨论的指针line指向字符串console=ttyS0,115200,它是内核命令行的一个组成部分。两个外部结构指针__setup_start和__setup_end是在一个链接脚本文本文件中定义的,而不是定义在C文件或头文件中。对于obs_kernel_param结构数组用来标记该数组起始和结束的标签则存在于目标文件的.init.setup段中。

在代码清单5-6中,通过指针p对这个特殊的内核命令行参数寻找匹配信息的过程,对整个结构都进行了扫描。具体在本例中,代码要为字符串信息console=寻找匹配信息,在这个相关的结构中,函数返回一个指向console_setup()函数的指针,它会以该参数(字符串ttyS0,115200)作为其唯一的函数参数,这一处理过程会在内核命令行处理完毕之前不停地重复。

采用所描述的这种机制将目标对象存放到ELF段的列表中,这种机制在内核中的许多地方都用到了。另一个采用这种机制的例子是,使用__init宏系列将初始化程序放到目标文件中一个普通的段中。与其很相近的__initdata被__setup宏用来标记为只在初始化过程中用到的数据。使用这些宏标记的初始化函数和数据被集中放到ELF段中,接下来,当使用了这些用来初始化的函数和数据之后,内核会释放之前它们所占用的内存空间。你也许在引导过程的最后阶段看到过类似的内核信息:"Freeing init memory: 296K."。不同用户对这些函数和数据的使用可能不尽相同,但是如果有三分之一兆,就值得使用__init宏系列,这也恰恰就是使用前面声明的__setup_str_console_setup[]数组里的__initdata宏的目的所在。

你也许会对代码清单5-6中的obsolete_符号感到迷惑,这是因为内核开发者正在用一种更通用的机制来代替内核命令行处理机制,以实现对引导时间和可加载模块参数的注册。在当前情况下,__setup宏声明了几百个参数,然而在新的开发中希望使用内核头文件.../include/ linux/moduleparam.h中定义的一系列函数来实现,更值得注意的是使用module_param*宏系列。这些内容将在第8章中介绍设备驱动程序的时候详细介绍。

上面所说的这种新机制通过在解析程序中包含一个未知的函数指针参数进而保持了向后兼容性,因此,对于module_param*结构来说,是未知的参数就会被视为未知参数,并且对命令行的处理过程就在开发者的控制下重新回到了原有的机制。在仔细研究../kernel/params.c中的代码和.../init/main.c中的parse_args()调用后就可以对这一过程有很好的理解。

对于由__setup宏所创建的结构obs_kernel_param,其中标志(flag)成员的用途是最后要注意的内容。仔细研究代码清单5-6就会明白。该结构中称为early的标志用来指示这个特定的内核命令行参数是否会在引导过程中预先使用,一些命令行参数就是特意要在引导过程中提前用到,那么在这种情况下的标志就会为提前解析命令行参数提供一种实现机制。你会在main.c代码中看到一个名为do_early_param()的函数,该函数会遍历数组,该数组是__setup宏结构由目标链接器产生的,同时该函数会处理每一个被标记为预先使用的内核命令行参数,在引导过程执行这一处理操作时给开发者一些控制权。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多