console驱动:一、基本概念终端是一种字符型设备,通常使用tty简称各种类型的终端。linux的终端类型:/dev/ttySn,串行口终端/dev/pty,伪终端/dev/tty,当前进程的控制终端,可以是介绍的其它任何一种终端/dev/ttyn,tty1~tty6是虚拟终端,tty0当前虚拟终端的别名。/dev/console,控制台终端(显示器)二、uboot传参数的处理linux启动时uboot传递进console=ttyS2,115200n8的参数内核中用__setup()宏声明参数处理的方法:__setup("console=", console_setup);1.console_cmdline结构体struct console_cmdline{ char name[8]; //驱动名 int index; //次设备号 char *options; //选项#ifdef CONFIG_A11Y_BRAILLE_CONSOLE char *brl_options;#endif};2.内核调用console_setup()函数处理uboot传进的console参数static int __init console_setup(char *str){ char buf[sizeof(console_cmdline[0].name) + 4]; //分配驱动名+index的缓冲区,分配12个字节 char *s, *options, *brl_options = NULL; int idx;#ifdef CONFIG_A11Y_BRAILLE_CONSOLE if (!memcmp(str, "brl,", 4)) { brl_options = ""; str += 4; } else if (!memcmp(str, "brl=", 4)) { brl_options = str + 4; str = strchr(brl_options, ','); if (!str) { printk(KERN_ERR "need port name after brl=\n"); return 1; } *(str++) = 0; }#endif if (str[0] >= '0' && str[0] = '0' && *s name, name, sizeof(c->name)); //填充全局console_cmdline的驱动名“ttyS2” c->options = options; //填充配置选项115200n8#ifdef CONFIG_A11Y_BRAILLE_CONSOLE c->brl_options = brl_options;#endif c->index = idx; //填充索引号2,即次设备号 return 0;}三、在console初始化之前能使用printk,使用内核提供的early printk支持。//在调用console_init之前调用printk也能打印出信息,这是為什麼呢?在start_kernel函数中很早就调用了 parse_early_param函数,//该函数会调用到链接脚本中.init.setup段的函数。其中就有 setup_early_serial8250_console函数。//该函数通过 register_console(&early_serial8250_console);//注册了一个比较简单的串口设备。可以用来打印内核启 动早期的信息。//对于early printk的console注册往往通过内核的early_param完成。early_param(“earlycon”,setup_early_serial8250_console);//定义一个earlycon的内核参数,内核解析这个参数时调用setup_early_serial8250_console()函数1.setup_early_serial8250_console()函数//earlycon = uart8250,mmio,0xff5e0000,115200n8int __init setup_early_serial8250_console(char *cmdline){char *options;int err;options = strstr(cmdline, "uart8250,");//找到“uart8250,”字符串,返回此字符串的起始位置if (!options) {options = strstr(cmdline, "uart,");if (!options)return 0;}options = strchr(cmdline, ',') + 1;//options指针指向第一个逗号后边的字符串地址err = early_serial8250_setup(options);//进行配置if (err port.membase || device->port.iobase)//early_device设备的端口地址若配置过则返回return 0;err = parse_options(device, options);//解析参数并配置early_device设备对应的uart_port结构if (err port;//找到early_device设备对应的uart_port结构int mmio, mmio32, length;if (!options)return -ENODEV;port->uartclk = BASE_BAUD * 16;//串口时钟mmio = !strncmp(options, "mmio,", 5);//查找"mmio,"字符串,找到mmio=1mmio32 = !strncmp(options, "mmio32,", 7);//mmio32=0if (mmio || mmio32) {port->iotype = (mmio ? UPIO_MEM : UPIO_MEM32);//串口类型设为UPIO_MEM=2port->mapbase = simple_strtoul(options + (mmio ? 5 : 7),&options, 0);//获得串口的配置寄存器基础地址(物理地址),这里是得到0xff5e0000if (mmio32)port->regshift = 2;#ifdef CONFIG_FIX_EARLYCON_MEMset_fixmap_nocache(FIX_EARLYCON_MEM_BASE,port->mapbase & PAGE_MASK);port->membase =(void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);port->membase += port->mapbase & ~PAGE_MASK;#elseport->membase = ioremap_nocache(port->mapbase, 64);//映射到内存的配置寄存器基础地址if (!port->membase) {printk(KERN_ERR "%s: Couldn't ioremap 0x%llx\n",__func__,(unsigned long long) port->mapbase);return -ENOMEM;}#endif} else if (!strncmp(options, "io,", 3)) {port->iotype = UPIO_PORT;port->iobase = simple_strtoul(options + 3, &options, 0);mmio = 0;} elsereturn -EINVAL;options = strchr(options, ',');//指针移到“115200n8”字符串处if (options) {//存在options++;device->baud = simple_strtoul(options, NULL, 0);//取得波特率115200length = min(strcspn(options, " "), sizeof(device->options));strncpy(device->options, options, length);//将字符串115200n8拷贝到设备的device->options字段中} else {device->baud = probe_baud(port);snprintf(device->options, sizeof(device->options), "%u",device->baud);}if (mmio || mmio32)printk(KERN_INFO "Early serial console at MMIO%s 0x%llx (options '%s')\n",mmio32 ? "32" : "",(unsigned long long)port->mapbase,device->options);elseprintk(KERN_INFO "Early serial console at I/O port 0x%lx (options '%s')\n",port->iobase,device->options);return 0;}static void __init init_port(struct early_serial8250_device *device){struct uart_port *port = &device->port;unsigned int divisor;unsigned char c;serial_out(port, UART_LCR, 0x3);/* 8n1 */serial_out(port, UART_IER, 0);/* no interrupt */serial_out(port, UART_FCR, 0);/* no fifo */serial_out(port, UART_MCR, 0x3);/* DTR + RTS */divisor = port->uartclk / (16 * device->baud);//根据波特率设置分频c = serial_in(port, UART_LCR);serial_out(port, UART_LCR, c | UART_LCR_DLAB);serial_out(port, UART_DLL, divisor & 0xff);serial_out(port, UART_DLM, (divisor >> 8) & 0xff);serial_out(port, UART_LCR, c & ~UART_LCR_DLAB);}void register_console(struct console *newcon){int i;unsigned long flags;struct console *bcon = NULL;/*现在是注册一个early console,即static struct console early_serial8250_console __initdata = {.name= "uart",.write= early_serial8250_write,.flags= CON_PRINTBUFFER | CON_BOOT,//所用具有CON_BOOT属性的console都会在内核初始化到late initcall阶段被注销,相互消他们的函数是.index= -1,};*/if (console_drivers && newcon->flags & CON_BOOT) {//注册的是否是引导控制台。early console的CON_BOOT置位,表示只是一个引导控制台,以后会被注销for_each_console(bcon) {////遍历全局console_drivers数组if (!(bcon->flags & CON_BOOT)) {//判断是否已经有引导控制台了,有了的话就直接退出printk(KERN_INFO "Too late to register bootconsole %s%d\n",newcon->name, newcon->index);return;}}}if (console_drivers && console_drivers->flags & CON_BOOT)//如果注册的是引导控制台bcon = console_drivers;//让bcon指向全局console_driversif (preferred_console early_setup)//early console没有初始化early_setup字段,以下这个函数不执行newcon->early_setup();//调用serial8250_console_early_setup()if (preferred_console index index = 0;if (newcon->setup == NULL ||newcon->setup(newcon, NULL) == 0) {newcon->flags |= CON_ENABLED;if (newcon->device) {newcon->flags |= CON_CONSDEV;preferred_console = 0;}}} //传给内核参数: //Kernel command line: console=ttyS2,115200n8 rw root=/dev/ram0 initrd=0xc2000000,20M mem=128M ip=192.168.1.220::192.168.1.1:255.255.255.0::eth0:off //所以这里将根据传参console=ttyS2,115200来配置作为console的ttyS2串口for (i = 0; i name) != 0)//比较终端名称“ttyS”continue;if (newcon->index >= 0 &&newcon->index != console_cmdline[i].index)//console_cmdline[i].index=2。//比较次设备号continue;if (newcon->index index = console_cmdline[i].index;//将终端号赋值给serial8250_console->index#ifdef CONFIG_A11Y_BRAILLE_CONSOLE//没有定义,下边不执行if (console_cmdline[i].brl_options) {newcon->flags |= CON_BRL;braille_register_console(newcon,console_cmdline[i].index,console_cmdline[i].options,console_cmdline[i].brl_options);return;}#endif//console_cmdline[i].options = "115200n8",对于early console而言setup字段未被初始化,故下边的函数不执行if (newcon->setup &&newcon->setup(newcon, console_cmdline[i].options) != 0)//调用serial8250_console_setup()对终端进行配置break;newcon->flags |= CON_ENABLED; //设置标志为CON_ENABLE(这个在printk调用中使用到)newcon->index = console_cmdline[i].index;//设置索引号if (i == selected_console) { //索引号和uboot指定的console的一样newcon->flags |= CON_CONSDEV;//设置标志CON_CONSDEV(全局console_drivers链表中靠前)preferred_console = selected_console;}break;}//for循环作用大致是查看注册的console是否是uboot知道的引导console,是则设置相关标志和preferred_consoleif (!(newcon->flags & CON_ENABLED))return;if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))//防止重复打印newcon->flags &= ~CON_PRINTBUFFER;acquire_console_sem();if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {//如果是preferred控制台newcon->next = console_drivers;console_drivers = newcon;//添加进全局console_drivers链表前面位置(printk中会遍历该表调用合适的console的write方法打印信息)if (newcon->next)newcon->next->flags &= ~CON_CONSDEV;} else {//如果不是preferred控制台newcon->next = console_drivers->next;console_drivers->next = newcon; //添加进全局console_drivers链表后面位置}//主册console主要是刷选preferred_console放置在全局console_drivers链表前面,剩下的console放置链表靠后的位置,并设置相应的flags,//console_drivers最终会在printk函数的层层调用中遍历到,并调用console的write方法将信息打印出来if (newcon->flags & CON_PRINTBUFFER) {spin_lock_irqsave(&logbuf_lock, flags);con_start = log_start;spin_unlock_irqrestore(&logbuf_lock, flags);}release_console_sem();if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) {printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",newcon->name, newcon->index);for_each_console(bcon)if (bcon->flags & CON_BOOT)unregister_console(bcon);} else {//调用这里printk(KERN_INFO "%sconsole [%s%d] enabled\n",(newcon->flags & CON_BOOT) ? "boot" : "" ,newcon->name, newcon->index);}}四、在未对console进行初始化之前,内核使用early console进行打印。之后内核进行真正的console初始化//console_init()在start_kernel()中调用,用来对控制台初始化,这个函数执行完成后,串口可以看到内核用printk()函数打印的信息void __init console_init(void){ initcall_t *call; /* Setup the default TTY line discipline. */ //此函数调用tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY) //#define N_TTY 0 /*struct tty_ldisc_ops tty_ldisc_N_TTY = { .magic = TTY_LDISC_MAGIC, .name = "n_tty", .open = n_tty_open, .close = n_tty_close, .flush_buffer = n_tty_flush_buffer, .chars_in_buffer = n_tty_chars_in_buffer, .read = n_tty_read, .write = n_tty_write, .ioctl = n_tty_ioctl, .set_termios = n_tty_set_termios, .poll = n_tty_poll, .receive_buf = n_tty_receive_buf, .write_wakeup = n_tty_write_wakeup };内核定义一个tty_ldiscs数组,然后根据数组下标来存放对应的线路规程的操作集,而这里的数组下标表示的就是具体的协议,在头文件中已经通过宏定义好了。例如N_TTY 0。所以可以发现:ldisc[0] 存放的是N_TTY对应的线路规程操作集ldisc[1]存放的是N_SLIP对应的线路规程操作集ldisc[2]存放的就是N_MOUSE对应的线路规程操作集依次类推。此处就是ldisc[N_TTY] = tty_ldisc_N_TTY。 int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc){ unsigned long flags; int ret = 0; if (disc = NR_LDISCS) return -EINVAL; spin_lock_irqsave(&tty_ldisc_lock, flags); tty_ldiscs[disc] = new_ldisc;//tty_ldiscs[0]存放的是N_TTY对应的线路规程操作集 new_ldisc->num = disc;//0 new_ldisc->refcount = 0; spin_unlock_irqrestore(&tty_ldisc_lock, flags); return ret; }*/ tty_ldisc_begin();//这段代码前面是注册了第0个(逻辑上1)线路规程 //依次调用从__con_initcall_start到__con_initcall_end之间的函数指针 //会调用两个函数就是con_init()和serial8250_console_init() call = __con_initcall_start; while (call UART_NR)//串口数量不能大于3个nr_uarts = UART_NR;serial8250_isa_init_ports();//对三个串口的uart_8250_port结构静态常量serial8250_ports结构进行初始化,主要是将up->port.ops = &serial8250_pops/*static struct console serial8250_console = {.name= "ttyS",.write= serial8250_console_write,//写方法.device= uart_console_device,//tty驱动.setup= serial8250_console_setup,//设置串口波特率,也就是设置串口。很重要,里面涉及到平台特性,波特率相关。.early_setup= serial8250_console_early_setup,.flags= CON_PRINTBUFFER | CON_ANYTIME,.index= -1,.data= &serial8250_reg,};*/register_console(&serial8250_console);//在这里注册serial8250_console真正的console终端return 0;}console_initcall(serial8250_console_init);/*serial8250_console_init()函数会比serial8250_probe()先调用,所以调用register_console的时候,port还没有初始化,所以当register_console调用serial8250_console_setup()设置buad,parity bits的时候,serial8250_console_setup()会检测port->iobase和port->membase是否是有效值,如果不是就返回,放弃初始化console,所以实际上,console不是在serial8250_console_init()里边初始化,如果要在serial8250_console_init初始化,需要将port静态初始化.当serial8250_probe()调用uart_add_one_port->uart_configure_port:if (port->cons && !(port->cons->flags & CON_ENABLED)){printk("%s retister console\n", __FUNCTION__);register_console(port->cons);}该函数会检查console有没有初始化,如果没有初始化,则调用register_console来初始化.所以console放在这里初始化也是比较好一些,可以将console_initcall(serial8250_console_init) comment.*///对三个串口的uart_8250_port结构静态常量serial8250_ports结构进行初始化,主要是将up->port.ops = &serial8250_popsstatic void __init serial8250_isa_init_ports(void){struct uart_8250_port *up;static int first = 1;int i, irqflag = 0;if (!first)//静态变量,serial8250_console_init()第一次进入这个函数,之后serial8250_init()再进入这个函数就会直接返回return;first = 0;//对三个串口的uart_8250_port结构serial8250_ports结构体进行初始化for (i = 0; i port.line = i;//0代表串口0,1代表串口1spin_lock_init(&up->port.lock);init_timer(&up->timer);//初始化定时器up->timer.function = serial8250_timeout;//初始化定时器的超时函数//ALPHA_KLUDGE_MCR needs to be killed.up->mcr_mask = ~ALPHA_KLUDGE_MCR;up->mcr_force = ALPHA_KLUDGE_MCR;//初始化uart_8250_port指向的uart_port字段port的操作up->port.ops = &serial8250_pops;/*static struct uart_ops serial8250_pops = {.tx_empty= serial8250_tx_empty,.set_mctrl= serial8250_set_mctrl,.get_mctrl= serial8250_get_mctrl,.stop_tx= serial8250_stop_tx,.start_tx= serial8250_start_tx,.stop_rx= serial8250_stop_rx,.enable_ms= serial8250_enable_ms,.break_ctl= serial8250_break_ctl,.startup= serial8250_startup,.shutdown= serial8250_shutdown,.set_termios= serial8250_set_termios,.set_ldisc= serial8250_set_ldisc,.pm= serial8250_pm,.type= serial8250_type,.release_port= serial8250_release_port,.request_port= serial8250_request_port,.config_port= serial8250_config_port,.verify_port= serial8250_verify_port,#ifdef CONFIG_CONSOLE_POLL.poll_get_char = serial8250_get_poll_char,.poll_put_char = serial8250_put_poll_char,#endif};*/}if (share_irqs)//中断是否共享(这里设置成不共享)irqflag = IRQF_SHARED;//条件不满足,不会进来初始化for (i = 0, up = serial8250_ports;i port.iobase = old_serial_port[i].port;up->port.irq = irq_canonicalize(old_serial_port[i].irq);up->port.irqflags = old_serial_port[i].irqflags;up->port.uartclk = old_serial_port[i].baud_base * 16;up->port.flags = old_serial_port[i].flags;up->port.hub6 = old_serial_port[i].hub6;up->port.membase = old_serial_port[i].iomem_base;up->port.iotype = old_serial_port[i].io_type;up->port.regshift = old_serial_port[i].iomem_reg_shift;set_io_from_upio(&up->port);up->port.irqflags |= irqflag;if (serial8250_isa_config != NULL)serial8250_isa_config(i, &up->port, &up->capabilities);*/}}//下边再次调用register_console()注册serial8250_console真正的console终端void register_console(struct console *newcon){int i;unsigned long flags;struct console *bcon = NULL;/*现在是注册一个serial8250_console,即static struct console serial8250_console = {.name= "ttyS",.write= serial8250_console_write,//写方法.device= uart_console_device,//tty驱动.setup= serial8250_console_setup,//设置串口波特率,也就是设置串口。很重要,里面涉及到平台特性,波特率相关。.early_setup= serial8250_console_early_setup,.flags= CON_PRINTBUFFER | CON_ANYTIME,.index= -1,.data= &serial8250_reg,};*/if (console_drivers && newcon->flags & CON_BOOT) {//注册的是serial8250_console,CON_BOOT没有置位,不是引导控制台。下边不会进去遍历for_each_console(bcon) {////遍历全局console_drivers数组if (!(bcon->flags & CON_BOOT)) {//判断是否已经有引导控制台了,有了的话就直接退出printk(KERN_INFO "Too late to register bootconsole %s%d\n",newcon->name, newcon->index);return;}}}if (console_drivers && console_drivers->flags & CON_BOOT)//如果注册的是引导控制台,serial8250_console不是引导控制台bcon = console_drivers;//这里不执行if (preferred_console early_setup)//serial8250_console初始化early_setup字段newcon->early_setup();//调用serial8250_console_early_setup()if (preferred_console index index = 0;if (newcon->setup == NULL ||newcon->setup(newcon, NULL) == 0) {newcon->flags |= CON_ENABLED;if (newcon->device) {newcon->flags |= CON_CONSDEV;preferred_console = 0;}}} //传给内核参数: //Kernel command line: console=ttyS2,115200n8 rw root=/dev/ram0 initrd=0xc2000000,20M mem=128M ip=192.168.1.220::192.168.1.1:255.255.255.0::eth0:off //所以这里将根据传参console=ttyS2,115200来配置作为console的ttyS2串口for (i = 0; i name) != 0)//比较终端名称“ttyS”continue;if (newcon->index >= 0 &&newcon->index != console_cmdline[i].index)//console_cmdline[i].index=2。//比较次设备号continue;if (newcon->index index = console_cmdline[i].index;//将终端号赋值给serial8250_console->index,这里是2//console_cmdline[i].options = "115200n8",对于serial8250_console而言setup字段已初始化if (newcon->setup && newcon->setup(newcon, console_cmdline[i].options) != 0)//调用serial8250_console_setup()对终端进行配置,调用不成功break;//在这里注册serial8250_console时,调用serial8250_console_setup()由于port->iobase和port->membase不是有效值,//故返回错误,这样下边的操作不会执行,直接break跳出,从flag1出跳出函数。即在这里serial8250_console没有注册成功//由于内核在下边的操作队串口进行初始化时,还会调用register_console()来注册serial8250_console,在那时注册就会成功newcon->flags |= CON_ENABLED; //设置标志为CON_ENABLE,表示console使能(这个在printk调用中使用到)newcon->index = console_cmdline[i].index;//设置索引号if (i == selected_console) { //索引号和uboot指定的console的一样newcon->flags |= CON_CONSDEV;//设置标志CON_CONSDEV(全局console_drivers链表中靠前)preferred_console = selected_console;}break;}//for循环作用大致是查看注册的console是否是uboot知道的引导console,是则设置相关标志和preferred_console //flag1:if (!(newcon->flags & CON_ENABLED))//若前边没有设置CON_ENABLED标志,就退出return;if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))//防止重复打印newcon->flags &= ~CON_PRINTBUFFER;acquire_console_sem();if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {//如果是preferred控制台newcon->next = console_drivers;console_drivers = newcon;//添加进全局console_drivers链表前面位置(printk中会遍历该表调用合适的console的write方法打印信息)if (newcon->next)newcon->next->flags &= ~CON_CONSDEV;} else {//如果不是preferred控制台newcon->next = console_drivers->next;console_drivers->next = newcon; //添加进全局console_drivers链表后面位置}//主册console主要是刷选preferred_console放置在全局console_drivers链表前面,剩下的console放置链表靠后的位置,并设置相应的flags,//console_drivers最终会在printk函数的层层调用中遍历到,并调用console的write方法将信息打印出来if (newcon->flags & CON_PRINTBUFFER) {spin_lock_irqsave(&logbuf_lock, flags);con_start = log_start;spin_unlock_irqrestore(&logbuf_lock, flags);}release_console_sem();if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) {printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",newcon->name, newcon->index);for_each_console(bcon)if (bcon->flags & CON_BOOT)unregister_console(bcon);} else {//调用这里printk(KERN_INFO "%sconsole [%s%d] enabled\n",(newcon->flags & CON_BOOT) ? "boot" : "" ,newcon->name, newcon->index);}}//serial8250_console_early_setup()-->serial8250_find_port_for_earlycon()int serial8250_find_port_for_earlycon(void){struct early_serial8250_device *device = &early_device;//early console初始化时对early_device结构的初始化struct uart_port *port = &device->port;int line;int ret;if (!device->port.membase && !device->port.iobase)//early_device结构初始化时已经配置好return -ENODEV;//early console注册时不会调用此函数。//当真正的console初始化时,会调用此函数。//真正的console初始化时,会查找early console注册时用的是哪一个串口号,从serial8250_ports[]中根据uart_port->mapbase地址来比对line = serial8250_find_port(port);//根据uart_port结构找到串口号,比对没有找到串口号,line返回负值if (line options);if (ret options);return ret;}static int __init serial8250_console_setup(struct console *co, char *options){struct uart_port *port;int baud = 9600;int bits = 8;int parity = 'n';int flow = 'n';if (co->index >= nr_uarts)//console的索引,这里是2,即ttyS2co->index = 0;port = &serial8250_ports[co->index].port;//找到对应的ttyS2的uart_port结构//由于console_init在注册serial8250_console时调用的register_console()函数调用serial8250_console_setup()//进入这个函数时,由于ttyS2的uart_port结构没有初始化,port->iobase 和port->membase值都未设置,所以直接从下边返回//当进行串口初始化时,还会回来注册serial8250_console,再调用到这里,由于设置了ttyS2的uart_port结构,所以下边的配置就会成功if (!port->iobase && !port->membase)//第一次注册时,由于未设置,从这里直接返回return -ENODEV;if (options)//如果options不为空,就将options里的数值写给baud, &parity, &bits, &flowuart_parse_options(options, &baud, &parity, &bits, &flow);//没有配置options,则使用缺省值,否则使用传下来的的参数options里的串口配置return uart_set_options(port, co, baud, parity, bits, flow);}五、通过四知道,在对console注册时,没有成功,由于串口还没有配置。当对串口配置时再对console注册就能成功。serial8250_console就能注册到内核全局变量console_drivers中。这样终端打印时就通过注册的serial8250_console就能将信息打印到终端上。//内核的打印函数asmlinkage int printk(const char *fmt, ...){va_list args;//可变参数链表int r;#ifdef CONFIG_KGDB_KDBif (unlikely(kdb_trap_printk)) {va_start(args, fmt);r = vkdb_printf(fmt, args);va_end(args);return r;}#endifva_start(args, fmt);//获取第一个可变参数r = vprintk(fmt, args);//调用vprintk函数va_end(args);//释放可变参数链表指针return r;}//vprintk函数asmlinkage int vprintk(const char *fmt, va_list args){int printed_len = 0;int current_log_level = default_message_loglevel;unsigned long flags;int this_cpu;char *p;boot_delay_msec();printk_delay();preempt_disable();raw_local_irq_save(flags);this_cpu = smp_processor_id();if (unlikely(printk_cpu == this_cpu)) {if (!oops_in_progress) {recursion_bug = 1;goto out_restore_irqs;}zap_locks();}lockdep_off();spin_lock(&logbuf_lock);printk_cpu = this_cpu;if (recursion_bug) {recursion_bug = 0;strcpy(printk_buf, recursion_bug_msg);printed_len = strlen(recursion_bug_msg);}printed_len += vscnprintf(printk_buf + printed_len,sizeof(printk_buf) - printed_len, fmt, args);p = printk_buf;if (p[0] == '') {switch (c) {case '0' ... '7': /* loglevel */current_log_level = c - '0';case 'd': /* KERN_DEFAULT */if (!new_text_line) {emit_log_char('\n');new_text_line = 1;}case 'c': /* KERN_CONT */p += 3;break;}}}for ( ; *p; p++) {if (new_text_line) {/* Always output the token */emit_log_char('');printed_len += 3;new_text_line = 0;if (printk_time) {//打印时间信息/* Follow the token with the time */char tbuf[50], *tp;unsigned tlen;unsigned long long t;unsigned long nanosec_rem;t = cpu_clock(printk_cpu);nanosec_rem = do_div(t, 1000000000);tlen = sprintf(tbuf, "[%5lu.%06lu] ",(unsigned long) t,nanosec_rem / 1000);for (tp = tbuf; tp 0);cur_index = start;start_print = start;while (cur_index != end) {if (msg_level 2) &&LOG_BUF(cur_index + 0) == '= '0' &&LOG_BUF(cur_index + 1) ') {msg_level = LOG_BUF(cur_index + 1) - '0';cur_index += 3;start_print = cur_index;}while (cur_index != end) {char c = LOG_BUF(cur_index);cur_index++;if (c == '\n') {if (msg_level next) if ((con->flags & CON_ENABLED) && con->write &&(cpu_online(smp_processor_id()) ||(con->flags & CON_ANYTIME))) con->write(con, &LOG_BUF(start), end - start); //调用console的写方法 }}//由于已经注册的终端是serial8250_console,这个终端的写方法是调用serial8250_console_write()函数--->uart_console_write()--->serial8250_console_putchar()//--->serial_out()最终打印在串口2终端上/*static struct console serial8250_console = {.name= "ttyS",.write= serial8250_console_write,//写方法.device= uart_console_device,//tty驱动.setup= serial8250_console_setup,//设置串口波特率,也就是设置串口。很重要,里面涉及到平台特性,波特率相关。.early_setup= serial8250_console_early_setup,.flags= CON_PRINTBUFFER | CON_ANYTIME,.index= -1,.data= &serial8250_reg,};*/console_drivers链表在register_console中会设置 12-10 21:52