作者:彭东林

邮箱:pengdonglin137@163.com

开发板:tiny4412ADK+S700 4GB Flash

主机:Wind7 64位

虚拟机:Vmware+Ubuntu12_04

u-boot:U-Boot 2010.12

Linux内核版本:linux-3.0.31

Android版本:android-4.1.2

下面要分析的是内核Log打印的几个阶段

  1. 自解压阶段
  2. 内核启动阶段
  3. 内核启动完全以后
  4. shell终端下

在这个阶段内核log打印可以调用printk和printascii,同时printk又分为两个阶段,从刚才的分析中知道,printk最终调用的是有register_console注册的console_drivers的write函数,在tiny4412平台上调用register_console的地方有两处,第一处是在arch/arm/kernel/early_printk.c中,另一处就是在串口驱动注册中,具体是在driver/tty/serial/samsung.c中,下面我们开始分析。

printascii的实现:

首先printascii需要配置内核才能使用:

make LOCALVERSION="" ARCH=arm CROSS_COMPILE=arm-linux- menuconfig

Kernel hacking

--- Kernel low-level debugging functions

这样就可以使用printascii了:

在printk中会调用printascii,

#ifdef        CONFIG_DEBUG_LL

         printascii(printk_buf);

#endif

print_asciii使用汇编实现的,在文件arch/arm/kernel/debug.S中:

  1. .macro addruart_current, rx, tmp1, tmp2
  2. addruart \tmp1, \tmp2
  3. mrc p15, , \rx, c1, c0
  4. tst \rx, #
  5. moveq \rx, \tmp1
  6. movne \rx, \tmp2
  7. .endm
  8.  
  9. ENTRY(printascii)
  10. addruart_current r3, r1, r2
  11. b 2f
  12. : waituart r2, r3
  13. senduart r1, r3
  14. busyuart r2, r3
  15. teq r1, #'\n'
  16. moveq r1, #'\r'
  17. beq 1b
  18.  
  19. : teq r0, #
  20. ldrneb r1, [r0], #
  21. teqne r1, #
  22. bne 1b
  23.  
  24. mov pc, lr
  25. ENDPROC(printascii)

其中 addruart 是在文件arch/arm/mach-exynos/include/mach/debug-macro.S中实现的,waituart、senduart以及busyuart是在arch/arm/plat-samsung/include/plat/debug-macro.S中实现的,大家可以参考这两个文件理解具体实现过程。

early_printk中调用register_console

tiny4412使用的内核默认是没有开启early_printk的,即log_buf中内容只有等driver/tty/serial下的串口驱动注册完成后才能输出到串口终端,在此之前调用printk的内容都缓存到log_buf中了,如果想提前使用的话,需要使能early_printk,下面说明一下如何使能early_printk。

make LOCALVERSION="" ARCH=arm CROSS_COMPILE=arm-linux- menuconfig

Kernel hacking

-- Kernel low-level debugging functions

--Early printk

要使能early_printk首先必须使能Kernel low-level debugging functions,因为early_printk最终也是使用printascii实现的,这样arch/arm/kernel/early_printk.c就会参加编译

在early_printk.c中:

  1. static int __init setup_early_printk(char *buf)
  2. {
  3. printk("%s enter\n", __func__);
  4. register_console(&early_console);
  5. return ;
  6. }
  7. early_param("earlyprintk", setup_early_printk);

虽然内核配置了,但是要让内核调用setup_early_printk还必须在u-boot给内核传参的时候给bootargs在加上一个参数”earlyprintk”,如下:

  1. set bootargs console=ttySAC0,115200n8 androidboot.console=ttySAC0 uhost0=n ctp= skipcali=y vmalloc=384m lcd=S70 earlyprintk

那么内核是如何处理的呢?

在文件include/linux/init.h中:

  1. #define __setup_param(str, unique_id, fn, early) \
  2. static const char __setup_str_##unique_id[] __initconst \
  3. __aligned() = str; \
  4. static struct obs_kernel_param __setup_##unique_id \
  5. __used __section(.init.setup) \
  6. __attribute__((aligned((sizeof(long))))) \
  7. = { __setup_str_##unique_id, fn, early }
  8.  
  9. #define early_param(str, fn) \
  10. __setup_param(str, fn, fn, )

将early_param("earlyprintk", setup_early_printk);展开后:

  1. static const char __setup_str_setup_early_printk[] __initconst __aligned() = "earlyprintk";
  2. static struct obs_kernel_param __setup_setup_early_printk __used __section(.init.setup) \
  3. __attribute__((aligned((sizeof(long))))) = \
  4. { __setup_str_setup_early_printk, setup_early_printk, }

即上面的这个结构体被链接到了”.init.setup”段,在arch/arm/kernel/vmlinux.lds中:

  1. . = ALIGN(); __setup_start = .; *(.init.setup) __setup_end = .;

将所有的.init.setup都链接到了__setup_start和__setup_end之间

在内核启动的时候:

  1. start_kernel
  2. --- setup_arch(&command_line);
  3. //这个函数的目的是获得u-boot传给内核的参数(bootargs),并将参数存放在command_line中,然后解析command_line,并调用相关的函数处理--- parse_early_param() (arch/arm/kernel/setup.c)
  4. --- strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
  5. --- parse_early_options(tmp_cmdline);

parse_early_options用于解析tmp_cmdline

  1. void __init parse_early_options(char *cmdline)
  2. {
  3. parse_args("early options", cmdline, NULL, , do_early_param);
  4. }

在文件kernel/params.c中:

  1. int parse_args(const char *name,
  2. char *args,
  3. const struct kernel_param *params,
  4. unsigned num,
  5. int (*unknown)(char *param, char *val))
  6. {
  7. char *param, *val;
  8.  
  9. /* Chew leading spaces */
  10. args = skip_spaces(args); // 跳过args开头的空格
  11.  
  12. while (*args) {
  13. int ret;
  14. int irq_was_disabled;
  15. /*
  16. 如:cmdline是“console=ttySAC0,115200n8 androidboot.console=ttySAC0”
  17. 执行next_arg后:
  18. params=”console”, val=” ttySAC0,115200n8” args=” androidboot.console=ttySAC0”
  19. */
  20. args = next_arg(args, &param, &val); // 获得下一个参数的位置,
  21. irq_was_disabled = irqs_disabled();
  22. /*
  23. parse_one所做的主要就是将params和val传递给unknown处理,这里unknown就是do_early_param,所以下面分析do_early_param
  24. */
  25. ret = parse_one(param, val, params, num, unknown);

  26. }
  27. }
  28.  
  29. /* All parsed OK. */
  30. return ;
  31. }
  1. static int __init do_early_param(char *param, char *val)
  2. {
  3. const struct obs_kernel_param *p;
  4. /*
  5. 还记得early_param("earlyprintk", setup_early_printk)展开后的结果吗?
  6. 其中early为1,str是 “earlyprintk”,setup_func就是setup_early_printk
  7. */
  8. for (p = __setup_start; p < __setup_end; p++) {
  9. if ((p->early && strcmp(param, p->str) == ) ||
  10. (strcmp(param, "console") == &&
  11. strcmp(p->str, "earlycon") == )
  12. ) {
  13. if (p->setup_func(val) != )

  14. }
  15. }
  16. return ;
  17. }

下面我们就分析setup_early_printk:

  1. static int __init setup_early_printk(char *buf)
  2. {
  3. register_console(&early_console);
  4. return ;
  5. }

结构体early_console 的定义如下:

  1. static struct console early_console = {
  2. .name = "earlycon",
  3. .write = early_console_write,
  4. .flags = CON_PRINTBUFFER | CON_BOOT,
  5. .index = -,
  6. };

看一下early_console_write干了什么:

  1. static void early_console_write(struct console *con, const char *s, unsigned n)
  2. {
  3. early_write(s, n);
  4. }
  1. static void early_write(const char *s, unsigned n)
  2. {
  3. while (n-- > ) {
  4. if (*s == '\n')
  5. printch('\r');
  6. printch(*s);
  7. s++;
  8. }
  9. }

可以看到,它调用的是printch,它在文件arch/arm/kernel/debug.S中实现:

  1. ENTRY(printascii)
  2. addruart_current r3, r1, r2
  3. b 2f
  4. : waituart r2, r3
  5. senduart r1, r3
  6. busyuart r2, r3
  7. teq r1, #'\n'
  8. moveq r1, #'\r'
  9. beq 1b
  10. : teq r0, #
  11. ldrneb r1, [r0], #
  12. teqne r1, #
  13. bne 1b
  14. mov pc, lr
  15. ENDPROC(printascii)
  16.  
  17. ENTRY(printch)
  18. addruart_current r3, r1, r2
  19. mov r1, r0
  20. mov r0, #
  21. b 1b
  22. ENDPROC(printch)

这样当driver/tty/serial下的驱动尚未注册时,printk就已经可以使用了,它最终调用的是early_console_write输出到串口终端的

下面我们分析一个函数register_console

  1. /*
  2. 这段话最好看一下
  3. * The console driver calls this routine during kernel initialization
  4. * to register the console printing procedure with printk() and to
  5. * print any messages that were printed by the kernel before the
  6. * console driver was initialized.
  7. *
  8. * This can happen pretty early during the boot process (because of
  9. * early_printk) - sometimes before setup_arch() completes - be careful
  10. * of what kernel features are used - they may not be initialised yet.
  11. *
  12. * There are two types of consoles - bootconsoles (early_printk) and
  13. * "real" consoles (everything which is not a bootconsole) which are
  14. * handled differently.
  15. * - Any number of bootconsoles can be registered at any time.
  16. * - As soon as a "real" console is registered, all bootconsoles
  17. * will be unregistered automatically.
  18. * - Once a "real" console is registered, any attempt to register a
  19. * bootconsoles will be rejected
  20. */
  21. void register_console(struct console *newcon)
  22. {
  23. int i;
  24. unsigned long flags;
  25. struct console *bcon = NULL;
  26.  
  27. /*
  28. * before we register a new CON_BOOT console, make sure we don't
  29. * already have a valid console
  30. 这个判断的目的是:看当前系统是否有”real”类型的console注册,如果有的话,直接返回,即”real”类型和”bootconsoles”类型的console不能共存,
    如果注册了”real”类型的console的话,则会对”bootconsoles”类型的console进行unregister
  31. 如果已经有”real”类型的console,则注册”bootconsoles”类型的console时会失败,”bootconsoles”类型的console的flags设置CON_BOOT位。
  32. 这里在内核刚启动,还没有任何console注册,console_drivers是NULL
  33. */
  34. if (console_drivers && newcon->flags & CON_BOOT) {
  35. /* find the last or real console */
  36. for_each_console(bcon) {
  37. if (!(bcon->flags & CON_BOOT)) {
  38. printk(KERN_INFO "Too late to register bootconsole %s%d\n",
  39. newcon->name, newcon->index);
  40. return;
  41. }
  42. }
  43. }
  44.  
  45. if (console_drivers && console_drivers->flags & CON_BOOT)
  46. bcon = console_drivers;
  47. // perferred_console和selected_console的初始值都是-1,但是如果在bootargs中设置了类似“console=ttySAC0,115200n8”时,在解析console参数时会设置selected_console,这个一会分析
  48. if (preferred_console < || bcon || !console_drivers)
  49. preferred_console = selected_console;
  50.  
  51. if (newcon->early_setup) // 如果有的话,则调用
  52. newcon->early_setup();
  53.  
  54. /*
  55. * See if we want to use this console driver. If we
  56. * didn't select a console we take the first one
  57. * that registers here.
  58. */
  59. if (preferred_console < ) {
  60. if (newcon->index < )
  61. newcon->index = ;
  62. if (newcon->setup == NULL ||
  63. newcon->setup(newcon, NULL) == ) {
  64. newcon->flags |= CON_ENABLED;
  65. if (newcon->device) {
  66. newcon->flags |= CON_CONSDEV;
  67. preferred_console = ;
  68. }
  69. }
  70. }
  71.  
  72. /*
  73. * See if this console matches one we selected on
  74. * the command line.
  75. // console_cmdline会在解析bootargs的console参数时设置
  76. */
  77. for (i = ; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[];
  78. i++) {
  79. if (strcmp(console_cmdline[i].name, newcon->name) != )
  80. continue;
  81. if (newcon->index >= &&
  82. newcon->index != console_cmdline[i].index)
  83. continue;
  84. if (newcon->index < )
  85. newcon->index = console_cmdline[i].index;
  86. if (newcon->setup &&
  87. newcon->setup(newcon, console_cmdline[i].options) != )
  88. break;
  89. newcon->flags |= CON_ENABLED;
  90. newcon->index = console_cmdline[i].index;
  91. if (i == selected_console) { // selected_console是从bootargs中解析出来的
  92. newcon->flags |= CON_CONSDEV;
  93. preferred_console = selected_console;
  94. }
  95. break;
  96. }
  97.  
  98. if (!(newcon->flags & CON_ENABLED))
  99. return;
  100.  
  101. /*
  102. * If we have a bootconsole, and are switching to a real console,
  103. * don't print everything out again, since when the boot console, and
  104. * the real console are the same physical device, it's annoying to
  105. * see the beginning boot messages twice
  106. */
  107. if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))
  108. newcon->flags &= ~CON_PRINTBUFFER;
  109.  
  110. /*
  111. * Put this console in the list - keep the
  112. * preferred driver at the head of the list.
  113. */
  114. console_lock();
  115. // 这里会把跟selected_console一样的ttySAC尽量往前放
  116. if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {
  117. newcon->next = console_drivers;
  118. console_drivers = newcon;
  119. if (newcon->next)
  120. newcon->next->flags &= ~CON_CONSDEV;
  121. } else {
  122. newcon->next = console_drivers->next;
  123. console_drivers->next = newcon;
  124. }
  125.  
  126. if (newcon->flags & CON_PRINTBUFFER) {
  127. spin_lock_irqsave(&logbuf_lock, flags);
  128. con_start = log_start;
  129. spin_unlock_irqrestore(&logbuf_lock, flags);
  130. exclusive_console = newcon;
  131. }
  132. console_unlock();
  133. console_sysfs_notify();
  134.  
  135. /*
  136. * By unregistering the bootconsoles after we enable the real console
  137. * we get the "console xxx enabled" message on all the consoles -
  138. * boot consoles, real consoles, etc - this is to ensure that end
  139. * users know there might be something in the kernel's log buffer that
  140. * went to the bootconsole (that they do not see on the real console)
  141. */
  142. if (bcon &&
  143. ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&
  144. !keep_bootcon) {
  145. // 在命令行中可以设置参数,将keep_bootcon置1,就不会将bootconsole注销了
  146. /* we need to iterate through twice, to make sure we print
  147. * everything out, before we unregister the console(s)
  148. 如果使能了early_printk的话,下面的这条log会打印两次,因为一次是从real console,另一次是从boot consoles。原因是 当real已经注册时(上面更新了console_drivers),
    但是此时boot consoles还尚未unregister。
  149. */
  150. printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",
  151. newcon->name, newcon->index);
  152. for_each_console(bcon)
  153. if (bcon->flags & CON_BOOT)
  154. unregister_console(bcon); // unregister boot consoles
  155. } else {
  156. printk(KERN_INFO "%sconsole [%s%d] enabled\n",
  157. (newcon->flags & CON_BOOT) ? "boot" : "" ,
  158. newcon->name, newcon->index);
  159. }
  160. }

上面我们分析了early_printk,下面我们分析当内核启动之后的printk打印的实现。

当Linux内核启动之后,或者更确切的说是串口驱动注册后,使用的是real console,会把boot consoles都disable。

内核起来后,使用串口终端可以登录,在用户空间(对于android)是/system/bin/sh跟用户交互,它接收用户通过串口输入的命令,然后执行,它虽然在最底层都会操作串口硬件寄存器,但是跟内核的printk还不一样,前者走的是tty驱动架构,他们在底层是通过不同的机制来操作uart控制器的。下面一块分析。

在此之前,我们还要看一下u-boot给内核传参:

  1. console=ttySAC0,115200n8 androidboot.console=ttySAC0 uhost0=n ctp= skipcali=y vmalloc=384m lcd=S70

由于tiny4412一共有4个串口,我们使用了com0和com3,在参数中的console参数console=ttySAC0,115200n8告诉Linux,将来sh要运行在ttySAC0上,它的波特率是115200,每帧8位。也就是我们要通过com0与Linux交互。

在此可以做一个实验,如果我们执行

“hello world”会通过串口ttySAC0打印到终端

但是执行

什么反应也没有,因为我们接在com0上而不是com3上。后面我们会尝试修改默认的串口,从com0改成com3.

那么系统是如何解析console=ttySAC0,115200n8的呢?

在kernel/printk.c中:

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

在include/linux/init.h中:

  1. #define __setup(str, fn) \
  2. __setup_param(str, fn, fn, )

而__setup_param我们在上面已经分析了,最终__setup("console=", console_setup);

的展开结果是:

  1. static const char __setup_str_console_setup[] __initconst \
  2. __aligned() = "console=";
  3.  
  4. static struct obs_kernel_param __setup_console_setup \
  5. __used __section(.init.setup) \
  6. __attribute__((aligned((sizeof(long))))) \
  7. = { __setup_str_console_setup, console_setup, }

可以看到它也链接到了”.init.setup”段。在内核启动的时候:

  1. asmlinkage void __init start_kernel(void)
  2. {
  3. char * command_line;
  4. extern const struct kernel_param __start___param[], __stop___param[];
  5. ...
  6. parse_args("Booting kernel", static_command_line, __start___param,
  7. __stop___param - __start___param,
  8. &unknown_bootoption);
  9. ......
  10. }
  1. int parse_args(const char *name,
  2. char *args,
  3. const struct kernel_param *params,
  4. unsigned num,
  5. int (*unknown)(char *param, char *val))
  6. {
  7. char *param, *val;
  8.  
  9. DEBUGP("Parsing ARGS: %s\n", args);
  10.  
  11. /* Chew leading spaces */
  12. args = skip_spaces(args);
  13.  
  14. while (*args) {
  15. int ret;
  16. int irq_was_disabled;
  17.  
  18. args = next_arg(args, &param, &val);

  19. ret = parse_one(param, val, params, num, unknown);
  20. // 在parse_one中会调用unknown函数,并将param和val传给它

  21. }
  1. static int __init unknown_bootoption(char *param, char *val)
  2. {
  3. /* Change NUL term back to "=", to make "param" the whole string. */
  4. if (val) {
  5. /* param=val or param="val"? */
  6. if (val == param+strlen(param)+)
  7. val[-] = '=';
  8. else if (val == param+strlen(param)+) {
  9. val[-] = '=';
  10. memmove(val-, val, strlen(val)+);
  11. val--;
  12. } else
  13. BUG();
  14. }
  15.  
  16. /* Handle obsolete-style parameters */
  17. if (obsolete_checksetup(param))
  18. return ;
  19.  
  20. /* Unused module parameter. */
  21. if (strchr(param, '.') && (!val || strchr(param, '.') < val))
  22. return ;
  23.  
  24. if (panic_later)
  25. return ;
  26.  
  27. if (val) {
  28. /* Environment option */
  29. unsigned int i;
  30. for (i = ; envp_init[i]; i++) {
  31. if (i == MAX_INIT_ENVS) {
  32. panic_later = "Too many boot env vars at `%s'";
  33. panic_param = param;
  34. }
  35. if (!strncmp(param, envp_init[i], val - param))
  36. break;
  37. }
  38. envp_init[i] = param;
  39. } else {
  40. /* Command line option */
  41. unsigned int i;
  42. for (i = ; argv_init[i]; i++) {
  43. if (i == MAX_INIT_ARGS) {
  44. panic_later = "Too many boot init vars at `%s'";
  45. panic_param = param;
  46. }
  47. }
  48. argv_init[i] = param;
  49. }
  50. return ;
  51. }

对于“console=ttySAC0,115200n8”符合这个条件

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

这样就会调用console_setup,并把参数:ttySAC0,115200n8 传给str变量

  1. static int __init console_setup(char *str)
  2. {
  3. char buf[sizeof(console_cmdline[].name) + ]; /* 4 for index */
  4. char *s, *options, *brl_options = NULL;
  5. int idx;
  6.  
  7. /*
  8. * Decode str into name, index, options.
  9. */
  10. if (str[] >= '' && str[] <= '') {
  11. strcpy(buf, "ttyS");
  12. strncpy(buf + , str, sizeof(buf) - );
  13. } else {
  14. strncpy(buf, str, sizeof(buf) - );
  15. }
  16.  
  17. //这里将ttySAC0,115200n8分成了两个字符串” ttySAC0”和”115200n8”
  18. buf[sizeof(buf) - ] = ;
  19. if ((options = strchr(str, ',')) != NULL)
  20. *(options++) = ; // 此时options指向字符串”115200n8”
  21.  
  22. //这里会从ttySAC0中将0解析出来,这个循环执行完成后,*s就是’0’
  23. for (s = buf; *s; s++)
  24. if ((*s >= '' && *s <= '') || *s == ',')
  25. break;
  26. idx = simple_strtoul(s, NULL, ); // 将字符’0’转化成10进制类型的0,然后赋值给idx
  27. *s = ; // 将’0’所在的位置为0,那么就将”ttySAC0”变成了”ttySAC”

  28. __add_preferred_console(buf, idx, options, brl_options);
  29. console_set_on_cmdline = ;
  30. return ;
  31. }

 

name=”ttySAC”, idx=0, options=”115200n8”, brl_options=NULL

  1. static int __add_preferred_console(char *name, int idx, char *options,
  2. char *brl_options)
  3. {
  4. struct console_cmdline *c;
  5. int i;
  6.  
  7. // 此时console_cmdline还没有赋值,所以name[0]是空,循环不成立
  8. // 如果在bootargs中传了两个console参数,那么在解析第二个console参数的时候name[0]就不是空的了
  9. for (i = ; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[]; i++)
  10. if (strcmp(console_cmdline[i].name, name) == &&
  11. console_cmdline[i].index == idx) {
  12. if (!brl_options)
  13. selected_console = i;
  14. return ;
  15. }
  16. if (i == MAX_CMDLINECONSOLES)
  17. return -E2BIG;
  18. // 从这里可以看出,如果bootargs中传递了两个console参数,如console=ttySAC0,console=ttySAC3,那么最终selected_console就是3
  19. if (!brl_options)
  20. selected_console = i;
  21. c = &console_cmdline[i];
  22. strlcpy(c->name, name, sizeof(c->name)); // “ttySAC”
  23. c->options = options; // “115200n8”
  24. c->index = idx; //
  25. return ;
  26. }

这里可以想一想,如果在bootargs中传递了两个console,如”console=ttySAC0,115200n8 console=ttySAC3,115200n8”,那么:

console_cmdline[0].name = “ttySAC”

console_cmdline[0].index = 0

console_cmdline[0].options = "115200n8"

console_cmdline[1].name = “ttySAC”

console_cmdline[1].index = 3

console_cmdline[1].options = "115200n8"

selected_console = 1

对于tiny4412,有四个串口,所以会调用四次register_console,但是只有一次能成功,那一次呢?如果bootargs中console参数是ttySAC0,那么只有名为ttySAC0的串口才能成功调用register_console。具体过程,我们下面分析。是在register_console中调用newcon->setup时,因为port的mapbase没有设置而返回错误。因为这四个串口在注册时,会调用probe四次,probe的时候才会为这个port的mapbase赋值,然后才调用register_console的。

tiny4412 串口驱动分析七 --- log打印的几个阶段之内核启动阶段(earlyprintk)的更多相关文章

  1. tiny4412 串口驱动分析八 --- log打印的几个阶段之内核启动阶段(printk tiny4412串口驱动的注册)

    作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...

  2. tiny4412 串口驱动分析三 --- log打印的几个阶段之内核自解压

    作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...

  3. tiny4412 串口驱动分析四 --- 修改默认的串口输出

    作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...

  4. tiny4412 串口驱动分析一 --- u-boot中的串口驱动

    作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...

  5. tiny4412 串口驱动分析二 --- printk的实现

    作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...

  6. tiny4412 串口驱动分析六 --- TTY驱动架构

    转载: http://www.linuxidc.com/Linux/2013-11/92639.htm 参考: http://blog.csdn.net/lamdoc/article/details/ ...

  7. tiny4412 串口驱动分析五 --- LDD3上TTY驱动程序源码

    关于tty这部分请参考: <Linux设备驱动开发详解 第二版>第14章 Linux终端设备驱动 <精通Linux设备驱动程序开发>第6章 串行设备驱动程序 <Linux ...

  8. tiny4412 串口驱动分析九 --- shell终端

    作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...

  9. linux的串口驱动分析

    1.串口驱动中的数据结构 • UART驱动程序结构:struct uart_driver  驱动 • UART端口结构: struct uart_port  串口 • UART相关操作函数结构: st ...

随机推荐

  1. HDU 2295 Radar (二分 + Dancing Links 重复覆盖模型 )

    以下转自 这里 : 最小支配集问题:二分枚举最小距离,判断可行性.可行性即重复覆盖模型,DLX解之. A*的启发函数: 对当前矩阵来说,选择一个未被控制的列,很明显该列最少需要1个行来控制,所以ans ...

  2. 第九章 广播和本地组播(IGMP和MLD)

    距离项目开启已经过去了一段时间,这段时间内自己学习的内容也算挺多的,但是也较容易遗忘,之后应该在空余的时间内多翻翻博客,更加清楚传统计算机网络的运作. 由于51要出去玩,更要好好利用好最近的时间.完成 ...

  3. Servlet 返回Json数据格式

    其实就是把数据库中的数据查询出来拼接成一个Json数据 import dao.UserDao; import endy.User; import javax.servlet.ServletExcept ...

  4. hadoop2.5.2学习及实践笔记(三)—— HDFS概念及体系结构

    注:文中涉及的文件路径或配置文件中属性名称是针对hadoop2.X系列,相对于之前版本,可能有改动. 附: HDFS用户指南官方介绍: http://hadoop.apache.org/docs/r2 ...

  5. v-if与v-show区别

    在v-show中,元素是一直存在的,当v-show为false时,元素display:none只是隐藏了而已. v-if 作用:判断是否加载固定的内容,如果为真,则加载:为假时,则不加载. 用处:用在 ...

  6. node.js开发hello world

    在你的 D 盘下面创建一个 server.js,写入以下内容 ---------------------------------------------------- var http = requi ...

  7. 【bzoj1179】[Apio2009]Atm Tarjan缩点+Spfa最长路

    题目描述 输入 第一行包含两个整数N.M.N表示路口的个数,M表示道路条数.接下来M行,每行两个整数,这两个整数都在1到N之间,第i+1行的两个整数表示第i条道路的起点和终点的路口编号.接下来N行,每 ...

  8. POJ 3243 Clever Y | BSGS算法完全版

    题目: 给你A,B,K 求最小的x满足Ax=B (mod K) 题解: 如果A,C互质请参考上一篇博客 将 Ax≡B(mod C) 看作是Ax+Cy=B方便叙述与处理. 我们将方程一直除去A,C的最大 ...

  9. 2-sat 学习笔记

    一.问题描述 以你咕的模板题为例 题目描述 有\(n\)个布尔变量\(x_1\)~\(x_n\),另有\(m\)个需要满足的条件,每个条件的形式都是"\(x_i\)为true/false或\ ...

  10. bzoj 2618 半平面交模板+学习笔记

    题目大意 给你n个凸多边形,求多边形的交的面积 分析 题意\(=\)给你一堆边,让你求半平面交的面积 做法 半平面交模板 1.定义半平面为向量的左侧 2.将所有向量的起点放到一个中心,以中心参照进行逆 ...