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中会设置
    
  • 相关阅读:
    错误 1 类,结构或接口成员声明中的标记"="无效
    转asp.net中的App_GlobalResources和App_LocalResources使用
    input type=file 上传文件样式美化(转载)
    Postman Post请求上传文件
    vuex 、store、state (转载)
    ES5、ES2015、ECMAScript6(转载)
    axios 用法简介(转载)
    js中const,var,let区别(转载)
    C#开发微信公众平台-就这么简单(附Demo)转载
    什么是 Native、Web App、Hybrid、React Native 和 Weex?(转载)
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10251040.html
Copyright © 2011-2022 走看看