Chinaunix首页 | 论坛 | 博客
  • 博客访问: 264835
  • 博文数量: 69
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 900
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-07 16:35
文章分类

全部博文(69)

文章存档

2011年(1)

2010年(68)

我的朋友

分类: LINUX

2010-07-31 13:47:46

一 printk 函数
printk函数首先把要打印的信息放到buffer里面,然后调用release_console_sem最后调用到相关驱动的write函数,如果你设定了 CONFIG_CMDLINE="console=ttySL0,19200,那么printk信息就会调用ttySL这个驱动的write函数,也就是从串口输出数据了.在__call_console_drivers里面有一个很重要的变量console_drivers,它决定了调用哪支driver输出printk信息.

printk()
{
  ..............
 
 //把待输出文字放入buffer
 va_start(args, fmt);
 printed_len = vsnprintf(printk_buf, sizeof(printk_buf), fmt, args);
 va_end(args);
 
   ..............
 
 release_console_sem();
       |
       ----------void release_console_sem(void)
         {
           .............
           _call_console_drivers
                 |
                 ----------static void __call_console_drivers(unsigned long start, unsigned long end)
                   {
                    struct console *con;
                   
                    for (con = console_drivers; con; con = con->next) {
                     if ((con->flags & CON_ENABLED) && con->write)
                      con->write(con, &LOG_BUF(start), end - start);
                    }
                   }
}

二 选择console driver

下面就是printk如果确定调用哪个driver的write函数输出信息过程,或者说一个console driver选择的过程.
首先看一下linux内核启动代码:

[Main.c]
asmlinkage void __init start_kernel(void)
{
  ..........

 setup_arch(&command_line);
 printk("Kernel command line: %s\n", saved_command_line);
 parse_options(command_line);
 
  ..........
}

parse_options
{
 //关键调用
 checksetup(line)
      |
      --------int __init checksetup(char *line)
       {
        struct kernel_param *p;
       
        if (line == NULL)
         return 0;
       
        p = &__setup_start;
        do {
         int n = strlen(p->str);
         if (!strncmp(line,p->str,n)) {
          if (p->setup_func(line+n))
           return 1;
         }
         p++;
        } while (p < &__setup_end);
        return 0;
       }
}

setup_arch根据CONFIG_CMDLINE指定的内容设定command_line指针. parse_options会遍历一个kernel_param结构数组,起始于__setup_start, 终止于__setup_end,

struct kernel_param {
 const char *str;
 int (*setup_func)(char *);
};

此数组里面的数据均来自于__setup(str, fn)这个宏.

[Init.h]
#define __setup(str, fn)        \
 static char __setup_str_##fn[] __initdata = str;    \
 static struct kernel_param __setup_##fn __attribute__((unused)) __initsetup = { __setup_str_##fn, fn }

在[printk.c]里面有一个很重要的语句 __setup("console=", console_setup);这句话也就是说当CONFIG_CMDLINE含有"console="字符的话,就调用console_setup函数, 所以在parse_options调用的时候,
就会调用到console_setup函数, console_setup就会记录下来console驱动的name,以及一些选项参数到console_cmdline数组中(如波特率),设置preferred_console参数,这样console driver已经选择好一半了.

console_setup()
{
  ..............
 
 for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)
  if (strcmp(console_cmdline[i].name, name) == 0 &&
     console_cmdline[i].index == idx) {
    preferred_console = i;
    return 1;
  }
 if (i == MAX_CMDLINECONSOLES)
  return 1;
 preferred_console = i;
 c = &console_cmdline[i];
 memcpy(c->name, name, sizeof(c->name));
 c->options = options;
 c->index = idx;
 
  ..............
}

选择console driver的另一半来自于register_console(linux启动后也会调用此函数),register_console
最重要的一句话是console->next = console_drivers;这样就完成了选择console driver的全过程.

void register_console(struct console * console)
{
   ..............
  
   //找到console_driver的过程
  
 for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++) {
  if (strcmp(console_cmdline[i].name, console->name) != 0)
   continue;
  if (console->index >= 0 &&
      console->index != console_cmdline[i].index)
   continue;
  if (console->index < 0)
   console->index = console_cmdline[i].index;
  if (console->setup &&
      console->setup(console, console_cmdline[i].options) != 0)  //此时做了setup的动作.
   break;
  console->flags |= CON_ENABLED;
  console->index = console_cmdline[i].index;
  if (i == preferred_console)
   console->flags |= CON_CONSDEV;
  break;
 }

 if (!(console->flags & CON_ENABLED))
  return; //屏蔽掉其他非console的driver 
  
  if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
  console->next = console_drivers;
  console_drivers = console; //设定console_driver
 } else {
  console->next = console_drivers->next;
  console_drivers->next = console;
 }
   ..............
}

[后记]
bootloader也可以传递参数给Kernel, 原理就是bootloader向一块内存中写入具有特定结构的数据,然后Kernel在调用时分析此内存数据,最后也会放到标准command_line缓存中,像上面一样处理.

[Setup.c]
__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
static int __init parse_tag_cmdline(const struct tag *tag)
{
 strncpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
 default_command_line[COMMAND_LINE_SIZE - 1] = '\0';  //放到default_command_line中
 return 0;
}

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