zoukankan      html  css  js  c++  java
  • 驱动调试(一)-printk


    title: 驱动调试(一)-printk
    date: 2019/1/9 19:35:14
    toc: true

    驱动调试(一)-printk

    引入

    uboot的启动参数中定义了我们内核启动时的信息输出

    bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0
    

    如果去除console=ttySAC0,则内核复制后没有信息输出,可以看下lcd,已经有显示了

    #OpenJTAG> set bootargs noinitrd root=/dev/mtdblock3 init=/linuxrc
    
    Starting kernel ...
    Uncompressing Linux.............................................. done, booting the kernel.
    

    也可以设置为tty1,直接在LCD上输出,这个需要有lcd驱动程序了(废话 哈哈),这里我试了tty0和tty1 和tty2,tty3都是在lcd显示

    set  bootargs noinitrd root=/dev/mtdblock3 init=/linuxrc console=tty1
    

    其实也可以使用多个终端输出,比如这样

    set  bootargs noinitrd root=/dev/mtdblock3 init=/linuxrc console=tty1 console=ttySAC0
    

    注意 这里设置参数后不要使用save 保存到flash,直接退回到menu,输入b启动即可

    那么内核的printk是怎么根据console=xxx找到输出的硬件设备的?

    框架

    入口console_setup

    搜索console=,在有以下代码__setup("console=", console_setup);,这个宏是用来处理启动参数的

    文件在kernelprintk.c

    /*
     * Set up a list of consoles.  Called from init/main.c
     */
    static int __init console_setup(char *str)
    {  
    	char name[sizeof(console_cmdline[0].name)];
    	char *s, *options;
    	int idx;
    
        
    	/*
    	 * Decode str into name, index, options.
    	 */
        
        // 先复制8字节到name
    	if (str[0] >= '0' && str[0] <= '9') {
    		strcpy(name, "ttyS");
    		strncpy(name + 4, str, sizeof(name) - 5);
    	} else {
    		strncpy(name, str, sizeof(name) - 1);
    	}
    	name[sizeof(name) - 1] = 0;
        
        
        // 判断是否有"," 也就是是不是有选项字节
    	if ((options = strchr(str, ',')) != NULL)
    		*(options++) = 0;
    #ifdef __sparc__
    	if (!strcmp(str, "ttya"))
    		strcpy(name, "ttyS0");
    	if (!strcmp(str, "ttyb"))
    		strcpy(name, "ttyS1");
    #endif
        
        //从name中 找数字
    	for (s = name; *s; s++)
    		if ((*s >= '0' && *s <= '9') || *s == ',')
    			break;
    	idx = simple_strtoul(s, NULL, 10);
    	*s = 0;
    
        // 这里就会添加控制台了,也就是记录下来,还没有找到硬件
    	add_preferred_console(name, idx, options);
    	return 1;
    }
    __setup("console=", console_setup);
    

    add_preferred_console

    这里是将命令行参数解析后存入全绝的结构体变量console_cmdline,这里只是存起来,并没有去解析

    add_preferred_console
    {
    	struct console_cmdline *c;
    	
    	// 这里有个全局变量 console_cmdline,保存所有的终端,这里支持8个
    	//#define MAX_CMDLINECONSOLES 8
    	//static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];
    	
    	//step1 判断是否存在了已经
    	for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)
    		...
    	
        //指向最后一个命令行的参数console_cmdline
    	selected_console = i;	
    
    	//step2 存入这个全局的数组 包括name,序号,选项
    	c = &console_cmdline[i];
    	memcpy(c->name, name, sizeof(c->name));
    	c->name[sizeof(c->name) - 1] = 0;
    	c->options = options;
    	c->index = idx;
    	
    }
    

    register_console

    继续搜索这个全局变量,可以看到注册函数,匹配命令行的name和注册的驱动后加入到链表中

    selected_console 在add_preferred_console 处理命令参数的时候 指向最后一个命令行的参数console_cmdline
    
    register_console   
    	// 如果没有注册过console,preferred_console 指向selected_console 也就是最后一个命令行参数的console
    	if (preferred_console < 0 || bootconsole || !console_drivers)
    		preferred_console = selected_console;
       // 如果没有注册过console,会先来一个初始化这第一个来注册的console
        if (preferred_console < 0)
            console->setup(console, NULL)
            console->index = 0; //没有注册时,强制赋值0
        for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];i++)
            ...
            // 1. 比较命令行的名字与注册的驱动的名字,如果name匹配
            // 2. 执行带有option的 console->setup(console, console_cmdline[i].options)
            // 3. 如果成功,设置标志
                console->flags |= CON_ENABLED;
                console->index = console_cmdline[i].index;
            //4. 选择一个作为preferred_console,如果匹配到最后一个命令行,preferred_console就等于这个selected_console=最后一个命令行
                if (i == selected_console) {
                console->flags |= CON_CONSDEV;
                preferred_console = selected_console;
           // 5.加入到链表 console_drivers ,注册的console本身也包含了一个链表指向
               	if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
        		console->next = console_drivers;
        		console_drivers = console;
        		if (console->next)
        			console->next->flags &= ~CON_CONSDEV;
            	} else {
            		console->next = console_drivers->next;
            		console_drivers->next = console;
            	}    
    

    这里的链表结构应该是如下这样的:

    mark

    s3c24xx_serial_initconsole

    搜索这个注册函数的调用,发现s3c24xx_serial_initconsole使用了这个注册函数,可以看到s3c24xx_serial_console的name正是"ttySAC"

    s3c24xx_serial_initconsole
    	// 驱动相关,先看看是不是有硬件驱动
    	struct platform_device *dev = s3c24xx_uart_devs[0];
    	//注册console
    	register_console(&s3c24xx_serial_console);
    
    static struct console s3c24xx_serial_console =
    {
    	.name		= S3C24XX_SERIAL_NAME,
    	.device		= uart_console_device,
    	.flags		= CON_PRINTBUFFER,
    	.index		= -1,
    	.write		= s3c24xx_serial_console_write,
    	.setup		= s3c24xx_serial_console_setup
    };
    
    #define S3C24XX_SERIAL_NAME	"ttySAC"
    #define S3C24XX_SERIAL_MAJOR	204
    #define S3C24XX_SERIAL_MINOR	64
    

    write

    可以在这个结构体里面发现write函数操作了实际的硬件,也就是writeprintk是实际的写硬件函数

    s3c24xx_serial_console_write
    	>uart_console_write(cons_uart, s, count, s3c24xx_serial_console_putchar);
    		>wr_regb(cons_uart, S3C2410_UTXH, ch);
    

    printk

    asmlinkage int printk(const char *fmt, ...)
    {
    	va_start(args, fmt);
    	r = vprintk(fmt, args);
    	va_end(args);
    }
    

    vprintk

    这个函数最后会查找console_drivers这个链表来进行打印处理,通过msg_level判断是否输出到硬件

    vprintk(const char *fmt, va_list args)
    {
    	// 解析数据到一个buf
    	/* Emit the output into the temporary buffer */
    	printed_len = vscnprintf(printk_buf, sizeof(printk_buf), fmt, args);
    
    	//对buf 进行特殊处理printk_buf,填充到 log_buf
    	for (p = printk_buf; *p; p++)
    	{
    		//如果没有 形如 <数字> 的开头,自动补上 <DEFAULT_CONSOLE_LOGLEVEL>也就是<4>,提取这个lev
    		//如果有,同样提取这个lev
    		emit_log_char(c)
    	}
    
    	if (cpu_online(smp_processor_id()) || have_callable_console()) {
                                                //have_callable_console 遍历 console_drivers
                                                // > for (con = console_drivers; con; con = con->next)
                                                // 这个链表就是register_console 中注册的了
    		console_may_schedule = 0;
    		// 打印输出
    		release_console_sem();
    		{
    			....
            }
    	}
    }
    

    release_console_sem

    先将数据输出到LOG_BUF,实际的输出到硬件会去判断一个打印级别,不论是否到达打印级别,都可以使用dmesg显示这个log_buf[]

    release_console_sem()
    {
    	// 静态全局变量
    	_con_start = con_start;
    	_log_end = log_end;
    	call_console_drivers(_con_start, _log_end);
    	{
    		// 提取打印等级
    		msg_level = LOG_BUF(cur_index + 1) - '0';
    		_call_console_drivers(start_print, cur_index, msg_level);
    		{
    			// lev < 设置的log lev,则打印
    			if ((msg_log_level < console_loglevel || ignore_loglevel) && console_drivers && start != end)
    			{
    				//遍历console驱动链表,判断是否有write函数,如果有,执行write函数
    				__call_console_drivers(start, end);
    				{
    					for (con = console_drivers; con; con = con->next)
    					{
    						if ((con->flags & CON_ENABLED) && con->write...)
    							con->write(con, &LOG_BUF(start), end - start);
    					}
    				}
    			}
    						
    		}
    	}
    }
    

    打印级别

    我们在里面使用的是_call_console_drivers中判断if msg_log_level < console_loglevel,也就是说默认的级别就是console_loglevel,也就是默认小于<7>才打印

    #define console_loglevel (console_printk[0]) ==7
    

    可以使用cat /proc/sys/kernel/printk查看是不是这个,这个值就是数组console_printk[4]

    # cat /proc/sys/kernel/printk
    7       4       1       7
    
    • 第一个参数 7表示小于7优先级消息才会被输出到控制台
    • 第二个参数4 表示默认的printk消息优先级别,即printk(“hell world”);优先级为4, 由于4<7,故可以被打印到控制台。
    • 第三个参数1 表示可接收的最高优先级,当printk disable控制台输出时,设置第一个参数为1,但是,从内核等级来看,还有优先级0,这个是printk最高级优先级,一般用于内核严重消息打印。比如内存错误或者 watchdog reset.也可以设置第一个和第三个参数为0
    • 第四个参数7 默认控制台优先级,即第一个参数的默认优先级。

    具体相关的定义在这里,可以使用includelinuxkernel.h查看

    int console_printk[4] = {
    	//=7
    	DEFAULT_CONSOLE_LOGLEVEL,	/* console_loglevel */
    	//=4
    	DEFAULT_MESSAGE_LOGLEVEL,	/* default_message_loglevel */
    	//=1
    	MINIMUM_CONSOLE_LOGLEVEL,	/* minimum_console_loglevel */
    	//=7
    	DEFAULT_CONSOLE_LOGLEVEL,	/* default_console_loglevel */
    };
    
    /* printk's without a loglevel use this.. */
    #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */
    /* We show everything that is MORE important than this.. */
    #define MINIMUM_CONSOLE_LOGLEVEL 1 /* Minimum loglevel we let people use */
    #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */
    
    
    #define    KERN_EMERG     "<0>"        // 系统崩溃
    #define    KERN_ALERT     "<1>"      //必须紧急处理
    #define    KERN_CRIT     "<2>"       // 临界条件,严重的硬软件错误
    #define    KERN_ERR       "<3>"       // 报告错误
    #define    KERN_WARNING   "<4>"       //警告
    #define    KERN_NOTICE    "<5>"      //普通但还是须注意
    #define    KERN_INFO      "<6>"      // 信息
    #define    KERN_DEBUG     "<7>"     // 调试信息
    

    使用printk

    格式可以加上打印级别,形式如下:

    printk(KERN_EMERG "abc")  ===  printk( "<0>abc");
    

    修改打印级别

    1. 临时修改/proc/sys/kernel/printk,重启后失效,下述命令关闭打印,也就是设置小于DEFAULT_CONSOLE_LOGLEVEL=1才打印

      echo "1 4 1 7" > /proc/sys/kernel/printk
      
    2. 修改初始化的数组或者是那个判断的函数

      int console_printk[4] = {
      	1,//DEFAULT_CONSOLE_LOGLEVEL,	/* console_loglevel */
      	DEFAULT_MESSAGE_LOGLEVEL,	/* default_message_loglevel */
      	MINIMUM_CONSOLE_LOGLEVEL,	/* minimum_console_loglevel */
      	DEFAULT_CONSOLE_LOGLEVEL,	/* default_console_loglevel */
      };
      

      或者修改 内核源代码

      static void _call_console_drivers(unsigned long start,
      				unsigned long end, int msg_log_level)
      {
      	//if ((msg_log_level < console_loglevel || ignore_loglevel) &&
          if ((msg_log_level < 1 || ignore_loglevel) &&
      			console_drivers && start != end) {
      		if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) {
      			/* wrapped write */
      			__call_console_drivers(start & LOG_BUF_MASK,
      						log_buf_len);
      			__call_console_drivers(0, end & LOG_BUF_MASK);
      		} else {
      			__call_console_drivers(start, end);
      		}
      	}
      }
      
    3. 设置uboot传递的参数,我们可以看到级别定义如下,搜索文本console_loglevel,可以找到一些函数

      更多的参考文档可以查看Documentationkernel-parameters.txt,搜索console

      #define console_loglevel (console_printk[0])
      
      //initmain.c
      static int __init debug_kernel(char *str)
      {
      	if (*str)
      		return 0;
      	console_loglevel = 10;
      	return 1;
      }
      static int __init quiet_kernel(char *str)
      {
      	if (*str)
      		return 0;
      	console_loglevel = 4;
      	return 1;
      }
      __setup("debug", debug_kernel);
      __setup("quiet", quiet_kernel);
      static int __init loglevel(char *str)
      {
      	get_option(&str, &console_loglevel);
      	return 1;
      }
      __setup("loglevel=", loglevel);
      

      也就是说可以用这些参数传递打印级别

      loglevel=0 console=ttySA0,115200
      debug # 使用级别10
      quiet # 使用级别4
      
      
      set bootargs noinitrd root=/dev/mtdblock3 init=/linuxrc loglevel=0 console=ttySAC0
      boot
      # cat /proc/sys/kernel/printk
      0       4       1       7
      
      set bootargs noinitrd root=/dev/mtdblock3 init=/linuxrc debug console=ttySAC0
      boot
      # cat /proc/sys/kernel/printk
      10      4       1       7
      
      set bootargs noinitrd root=/dev/mtdblock3 init=/linuxrc quiet console=ttySAC0
      boot
      # cat /proc/sys/kernel/printk
      4       4       1       7
      

    使用dmesg打印所有日志

    使用这个命令可以打印那些被屏蔽的缓冲,可以保存到文本里面去看

    # dmesg 
    ....?...................................... done, booting the kernel.
    Linux version 2.6.22.6 (book@100ask) (gcc version 3.4.5) #3 Wed Jan 9 15:33:52 CST 2019
    CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177
    

    测试

    修改级别为0,都可以实现如下效果,不打印内核启动信息

    Starting kernel ...
    
    Uncompressing Linux...................................................................................................................... done, booting the kernel.
    init started: BusyBox v1.7.0 (2018-11-13 23:35:45 CST)
    starting pid 766, tty '': '/etc/init.d/rcS'
    
    Please press Enter to activate this console.
    starting pid 771, tty '/dev/console': 'bin/sh'
    
    

    也可以# cat /proc/sys/kernel/printk 来查看,除了那个修改函数中的if判断的,都可以打印出来如下效果

    # cat /proc/sys/kernel/printk
    1       4       1       7
    

    修改if判断的依然是7 4 1 7,因为这个文件本质上就是这个数组显示 哈哈

    小结

    1. 内核解析uboot传递的命令行参数,来寻找实际的硬件来输出信息

      console_setup
      	>add_preferred_console
      
    2. 注册实际的硬件驱动,加入到console_drivers链表

      s3c24xx_serial_initconsole
      	>register_console
      		>比较命令行的name 与 硬件驱动的name ,如果匹配,加入到 console_drivers链表中
      
    3. 使用printk( lev "...")来输出信息,如果没有指定lev,以默认的lev=4输出,具体是在console_drivers中寻找驱动找到他的write函数输出

      printk
      	>vprintk
      		>release_console_sem
      			>判断lev 打印
      
    4. 使用dmesg可以打印所有日志,包括被屏蔽的

    参考文档

    1. Documentationkernel-parameters.txt ,搜索console查看命令行参数传递
    2. 3.4内核代码分析 http://blog.chinaunix.net/uid-27717694-id-3495612.html
    3. cnblog https://www.cnblogs.com/lifexy/p/7993136.html

    附录(3.4内核的分析)

    这个代码讲的比较具体,先不去仔细分析了

    http://blog.chinaunix.net/uid-27717694-id-3495612.html

    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=
    ");  
                return 1;  
            }  
            *(str++) = 0;  
        }  
    #endif   
     
        if (str[0] >= '0' && str[0] <= '9') { //第一个参数属于[0,9]   
            strcpy(buf, "ttyS");    //则将其驱动名设为ttyS   
            strncpy(buf + 4, str, sizeof(buf) - 5);//将次设备号放其后面   
        } else {  
            strncpy(buf, str, sizeof(buf) - 1); //否则直接将驱动名+设备号拷贝到buf中
        }  
        buf[sizeof(buf) - 1] = 0;  
        if ((options = strchr(str, ',')) != NULL) //获取options,即“115200n8”   
            *(options++) = 0;  
             
    #ifdef __sparc__   
        if (!strcmp(str, "ttya"))  
            strcpy(buf, "ttyS0");  
        if (!strcmp(str, "ttyb"))  
            strcpy(buf, "ttyS1");  
    #endif  
      
        for (s = buf; *s; s++)  
            if ((*s >= '0' && *s <= '9') || *s == ',')//移动指针s到次设备号处  
                break;  
        idx = simple_strtoul(s, NULL, 10); //获取次设备号,字符串转换成unsigend long long型数据,s表示字符串的开始,NULL表示字符串的结束,10表示进制 
                                                                            //这里返回的是次设备号=2
        *s = 0;  
       
        __add_preferred_console(buf, idx, options, brl_options);  
        console_set_on_cmdline = 1;  
        return 1;  
    }  
     
    3.__add_preferred_console()函数
    //整体的作用是根据uboot传递的参数设置全局console_cmdline数组
    //该数组及全局selected_console,在register_console中会使用到
    static int __add_preferred_console(char *name, int idx, char *options,char *brl_options)  
    {  
        struct console_cmdline *c;  
        int i;  
        for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)//可以最多8个console   
            if (strcmp(console_cmdline[i].name, name) == 0 && console_cmdline[i].index == idx) {  
                //比较已注册的console_cmdline数组中的项的名字及次设备号,若console_cmdline已经存在   
                    if (!brl_options)  
                        selected_console = i;//设置全局selected_console索引号   
                    return 0;//则返回   
            }  
          
        if (i == MAX_CMDLINECONSOLES)//判断console_cmdline数组是否满了   
            return -E2BIG;  
        if (!brl_options)  
            selected_console = i; //设置全局selected_console索引号   
         
        c = &console_cmdline[i];//获取全局console_cmdline数组的第i项地址   
        strlcpy(c->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,115200n8
    int __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 < 0)
            return err;
         
        /*
        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,
        };
        */
        //注册一个早期的console,到真正的console_init时,此console会被注销,因为设置了CON_BOOT标志
        register_console(&early_serial8250_console);
     
        return 0;
    }
     
    static int __init early_serial8250_setup(char *options)
    {
        struct early_serial8250_device *device = &early_device;
        int err;
     
        if (device->port.membase || device->port.iobase)//early_device设备的端口地址若配置过则返回
            return 0;
     
        err = parse_options(device, options);//解析参数并配置early_device设备对应的uart_port结构
        if (err < 0)
            return err;
     
        init_port(device);//early_device设备对应的初始化uart_port结构
        return 0;
    }
     
    static int __init parse_options(struct early_serial8250_device *device,char *options)
    {
        struct uart_port *port = &device->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=1
        mmio32 = !strncmp(options, "mmio32,", 7);//mmio32=0
        if (mmio || mmio32) {
            port->iotype = (mmio ? UPIO_MEM : UPIO_MEM32);//串口类型设为UPIO_MEM=2
            port->mapbase = simple_strtoul(options + (mmio ? 5 : 7),&options, 0);//获得串口的配置寄存器基础地址(物理地址),这里是得到0xff5e0000
            if (mmio32)
                port->regshift = 2;
    #ifdef CONFIG_FIX_EARLYCON_MEM
            set_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;
    #else
            port->membase = ioremap_nocache(port->mapbase, 64);//映射到内存的配置寄存器基础地址
            if (!port->membase) {
                printk(KERN_ERR "%s: Couldn't ioremap 0x%llx
    ",    __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;
        } else
            return -EINVAL;
     
        options = strchr(options, ',');//指针移到“115200n8”字符串处
        if (options) {//存在
            options++;
            device->baud = simple_strtoul(options, NULL, 0);//取得波特率115200
            length = 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')
    ",mmio32 ? "32" : "",(unsigned long long)port->mapbase,device->options);
        else
            printk(KERN_INFO
                  "Early serial console at I/O port 0x%lx (options '%s')
    ",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
    ",newcon->name, newcon->index);
                    return;
                }
            }
        }
         
        if (console_drivers && console_drivers->flags & CON_BOOT)//如果注册的是引导控制台  
            bcon = console_drivers;//让bcon指向全局console_drivers   
     
        if (preferred_console < 0 || bcon || !console_drivers)
            preferred_console = selected_console;//设置preferred_console为uboot命令选择的selected_console(即索引)   
     
        if (newcon->early_setup)//early console没有初始化early_setup字段,以下这个函数不执行
            newcon->early_setup();//调用serial8250_console_early_setup()
     
     
        if (preferred_console < 0) {
            if (newcon->index < 0)
                newcon->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 < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];i++) {//遍历全局console_cmdline找到匹配的 
            if (strcmp(console_cmdline[i].name, newcon->name) != 0)//比较终端名称“ttyS”
                continue;
            if (newcon->index >= 0 &&newcon->index != console_cmdline[i].index)//console_cmdline[i].index=2。//比较次设备号  
                continue;
            if (newcon->index < 0)
                newcon->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_console
     
        if (!(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
    ",newcon->name, newcon->index);
            for_each_console(bcon)
                if (bcon->flags & CON_BOOT)
                    unregister_console(bcon);
        } else {//调用这里
            printk(KERN_INFO "%sconsole [%s%d] enabled
    ",(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 < N_TTY || 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 < __con_initcall_end) {
      (*call)();
      call++;
     }
    }
     
    static int __init serial8250_console_init(void)
    {
        if (nr_uarts > 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
    ", __FUNCTION__);
        register_console(port->cons);
    }
    该函数会检查console有没有初始化,如果没有初始化,则调用register_console来初始化.
    所以console放在这里初始化也是比较好一些,可以将console_initcall(serial8250_console_init) comment.
    */
     
    //对三个串口的uart_8250_port结构静态常量serial8250_ports结构进行初始化,主要是将up->port.ops = &serial8250_pops
    static 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 < nr_uarts; i++) {
            struct uart_8250_port *up = &serial8250_ports[i];
     
            up->port.line = i;//0代表串口0,1代表串口1
            spin_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 < ARRAY_SIZE(old_serial_port) && i < nr_uarts;i++, up++) {
    /*  up->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
    ",newcon->name, newcon->index);
                    return;
                }
            }
        }
         
        if (console_drivers && console_drivers->flags & CON_BOOT)//如果注册的是引导控制台,serial8250_console不是引导控制台
            bcon = console_drivers;//这里不执行 
     
        if (preferred_console < 0 || bcon || !console_drivers)
            preferred_console = selected_console;//设置preferred_console为uboot命令选择的selected_console(即在Uboot传入的参数“console=ttyS2,115200n8”在console_cmdline[]数组中的索引)   
                                                                                     //这里preferred_console =0
        if (newcon->early_setup)//serial8250_console初始化early_setup字段
            newcon->early_setup();//调用serial8250_console_early_setup()
     
     
        if (preferred_console < 0) {//由于preferred_console =0,不会进入下边
            if (newcon->index < 0)
                newcon->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 < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];i++) {//遍历全局console_cmdline找到匹配的,i=0就是匹配的“ttyS2”
            if (strcmp(console_cmdline[i].name, newcon->name) != 0)//比较终端名称“ttyS”
                continue;
            if (newcon->index >= 0 &&newcon->index != console_cmdline[i].index)//console_cmdline[i].index=2。//比较次设备号  
                continue;
            if (newcon->index < 0)
                newcon->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
    ",newcon->name, newcon->index);
            for_each_console(bcon)
                if (bcon->flags & CON_BOOT)
                    unregister_console(bcon);
        } else {//调用这里
            printk(KERN_INFO "%sconsole [%s%d] enabled
    ",(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 < 0)
            return -ENODEV;//从这里返回,下边的不再执行
         
        //若找到early console用的串口号,更新当初传入内核参数使用的console_cmdline[i],名称改成ttyS。。。。
        ret = update_console_cmdline("uart", 8250, "ttyS", line, device->options);
        if (ret < 0)
            ret = update_console_cmdline("uart", 0,"ttyS", line, device->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,即ttyS2
            co->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, &flow
            uart_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_KDB
        if (unlikely(kdb_trap_printk)) {
            va_start(args, fmt);
            r = vkdb_printf(fmt, args);
            va_end(args);
            return r;
        }
    #endif
        va_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] == '<') {//处理打印级别字段
            unsigned char c = p[1];
            if (c && p[2] == '>') {
                switch (c) {
                case '0' ... '7': /* loglevel */
                    current_log_level = c - '0';
                case 'd': /* KERN_DEFAULT */
                    if (!new_text_line) {
                        emit_log_char('
    ');
                        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('<');
                emit_log_char(current_log_level + '0');
                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 < tbuf + tlen; tp++)
                        emit_log_char(*tp);
                    printed_len += tlen;
                }
     
                if (!*p)
                    break;
            }
     
            emit_log_char(*p);
            if (*p == '
    ')
                new_text_line = 1;
        }
        if (acquire_console_semaphore_for_printk(this_cpu))
            release_console_sem();
     
        lockdep_on();
    out_restore_irqs:
        raw_local_irq_restore(flags);
     
        preempt_enable();
        return printed_len;
    }
     
    //接着调用release_console_sem函数 
    void release_console_sem(void)
    {
        unsigned long flags;
        unsigned _con_start, _log_end;
        unsigned wake_klogd = 0;
     
        if (console_suspended) {
            up(&console_sem);
            return;
        }
     
        console_may_schedule = 0;
     
        for ( ; ; ) {
            spin_lock_irqsave(&logbuf_lock, flags);
            wake_klogd |= log_start - log_end;
            if (con_start == log_end)
                break;          /* Nothing to print */
            _con_start = con_start;
            _log_end = log_end;
            con_start = log_end;        /* Flush */
            spin_unlock(&logbuf_lock);
            stop_critical_timings();    /* don't trace print latency */
            call_console_drivers(_con_start, _log_end);
            start_critical_timings();
            local_irq_restore(flags);
        }
        console_locked = 0;
        up(&console_sem);
        spin_unlock_irqrestore(&logbuf_lock, flags);
        if (wake_klogd)
            wake_up_klogd();
    }
    EXPORT_SYMBOL(release_console_sem);
     
    //调用call_console_drivers函数
    static void call_console_drivers(unsigned start, unsigned end)
    {
        unsigned cur_index, start_print;
        static int msg_level = -1;
     
        BUG_ON(((int)(start - end)) > 0);
     
        cur_index = start;
        start_print = start;
        while (cur_index != end) {
            if (msg_level < 0 && ((end - cur_index) > 2) &&LOG_BUF(cur_index + 0) == '<' &&LOG_BUF(cur_index + 1) >= '0' &&LOG_BUF(cur_index + 1) <= '7' &&LOG_BUF(cur_index + 2) == '>') {
                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 == '
    ') {
                    if (msg_level < 0) {
                        msg_level = default_message_loglevel;
                    }
                    _call_console_drivers(start_print, cur_index, msg_level);
                    msg_level = -1;
                    start_print = cur_index;
                    break;
                }
            }
        }
        _call_console_drivers(start_print, end, msg_level);
    }_call_console_drivers函数
     
    //调用console的写方法
    static void __call_console_drivers(unsigned start, unsigned end)  
    {  
        struct console *con;  
       
        for_each_console(con) {//遍历console_drivers数组 #define for_each_console(con) for (con = console_drivers; con != NULL; con = con->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中会设置
    
  • 相关阅读:
    poj 2676 Suduku (dfs)
    poj 1562 Oil Deposits (dfs)
    poj 2907 Collecting Beepers (dfs)
    poj 1655 Balancing Act (树形dfs)
    poj 3411 Paid Roads (dfs)
    hdu 2896 病毒侵袭 (AC)
    hdu 3065 病毒侵袭持续中 (AC)
    poj 2251 Dungeon Master (bfs)
    java中debug使用
    Swing入门级小项目总结
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10251040.html
Copyright © 2011-2022 走看看