1. In driver debugging , Use printk(), It's the simplest , The most convenient way

When uboot From the command line “console=tty1” when , Express printk() The output is on the development board LCD On the screen

When uboot From the command line “console=ttySA0,115200” when , Express printk() The output is in the serial port UART0 On , Baud rate =115200

When uboot From the command line “console=tty1 console=ttySA0,115200” when , Express printk() At the same time, the output is on the serial port , And the development board LCD On the screen

obviously printk(), Or call the hardware processing functions of different console according to the command line parameters

How is the kernel determined according to the above command line parameters printk() The output device of ?

2. We use “console=ttySA0,115200” For example , Get into linux-2.6.22.6\kernel\printk.c

Find the following paragraph :

__setup("console=", console_setup); among __setup() Is that : if uboot The command line string passed in contains “console=”, Then call console_setup() function , Also on “console=” The following string "ttySA0,115200" Analyze 

3. We use *str= "ttySA0,115200" For example ,console_setup() The function is shown below

static int __init console_setup(char *str)                    //*str="ttySA0,115200"{   char name[sizeof(console_cmdline[0].name)];     // char name[8]   char *s, *options;   int idx; 
       /** Decode str into name, index, options.*/   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="ttySA0, "   }   name[sizeof(name) - 1] = 0;            //*name="ttySA0"   if ((options = strchr(str, ',')) != NULL)   // find ',', Return to options, therefore options=",115200"  *(options++) = 0;                //*options="115200", *str="ttySA0"#ifdef __sparc__   if (!strcmp(str, "ttya"))  strcpy(name, "ttyS0");   if (!strcmp(str, "ttyb"))  strcpy(name, "ttyS1");#endif   for (s = name; *s; s++)                                     //*s="0"  if ((*s >= '0' && *s <= '9') || *s == ',') break;   idx = simple_strtoul(s, NULL, 10);   // and strtoul() equally , take s Medium "0" Bring up , therefore idx=0   *s = 0;                                         // take "ttySA0" Medium "0" Set to 0, therefore *name="ttySA"   add_preferred_console(name, idx, options);      
      //*name="ttySA"  // idx=0  //*options="115200"   return 1;}

From the code and comments above , The final call add_preferred_console(“ttySA”, 0, “115200”) Function to add the console

4. Get into console_setup()>add_preferred_console()

int __init add_preferred_console(char *name, int idx, char *options){   struct console_cmdline *c;   int i;   /* MAX_CMDLINECONSOLES=8, Indicates that at most 8 A console */   for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)  if (strcmp(console_cmdline[i].name, name) == 0 &&console_cmdline[i].index == idx) // console_cmdline[] It's a global array , Used to match whether the console to be added is duplicate   {selected_console = i;    return 0;  // stay console_cmdline[] in , There is already a console to add , therefore return  }   if (i == MAX_CMDLINECONSOLES)             //i==8, Indicates that the array is full   return -E2BIG;   selected_console = i;   
 /* The console information of the command line exists console_cmdline[i] in */   c = &console_cmdline[i];             
       memcpy(c->name, name, sizeof(c->name));   c->name[sizeof(c->name) - 1] = 0;   c->options = options;   c->index = idx;  return 0;}

The function above , Finally, the information from the console was put into console_cmdline[] In the global array , Now let's search the array , have a look printk() How to call the hardware processing function of the console .

We found that linux-2.6.22.6\kernel\Printk.c Inside register_console(struct console *console) function , Useful to console_cmdline[]

obviously ,register_console() Function is used to register the console , Continue to search register_console

As shown in the figure below , Find a lot CPU Console driver initialization for :
 Insert picture description here

5. We use 2410 For example (linux2.6.22.6\drivers\serial\S3c2410.c):

static int s3c24xx_serial_initconsole(void){
  ... ...
  register_console(&s3c24xx_serial_console);
  return 0;}console_initcall(s3c24xx_serial_initconsole);    // Declare the console initialization function 

Through the top register_console() To register s3c24xx_serial_console Structure , The members of the structure are as follows :

static struct console s3c24xx_serial_console ={   .name            = S3C24XX_SERIAL_NAME,              // Console name    .device           = uart_console_device,              //tty drive    .flags             = CON_PRINTBUFFER,                 // sign    .index             = -1,                              / Index value        .write             = s3c24xx_serial_console_write,    // Hardware processing function for printing serial data    .setup            = s3c24xx_serial_console_setup      // Used to set UART Baud rate , send out , Receiving and other functions };

The name of the structure is shown in the figure below :
 Insert picture description here
stay register_console() in , Will pass “ttySAC” To match console_cmdline[i] The name of , When the match is successful ,printk() Called console The structure is s3c24xx_serial_console 了

6. Next , analysis printk() How to call s3c24xx_serial_console Structure of the write(), To print information

printk() The function is shown below

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

among args and fmt The value of is we printk Substituting parameters

7. Then enter printk()->vprintk():

asmlinkage int vprintk(const char *fmt, va_list args){unsigned long flags;int printed_len;char *p;static char printk_buf[1024];                  // Temporary buffer static int log_level_unknown = 1;preempt_disable(); // Turn off kernel preemption ... .../* Send the output information to the temporary buffer printk_buf[] */printed_len = vscnprintf(printk_buf, sizeof(printk_buf), fmt, args);/* Copy printk_buf Data to circular buffer log_buf[], If the caller does not provide the appropriate print level , Insert default */
   for (p = printk_buf; *p; p++) {... ... /* Judge printk The print level of the print , That's the prefix value "<0>" to  "<7>"*/  if (p[0] == '<' && p[1] >='0' && p[1] <= '7' && p[2] == '>')  {loglev_char = p[1];  // Get print level characters , Put the level in  loglev_char in p += 3;                 
                    printed_len -= 3;  }   else  {                   // If there is no print level , Then insert the default value , such as printk("abc"), Will turn into printk("<4>abc")  loglev_char = default_message_loglevel+ '0';
     }   ... ...    // Start copying to circular buffer log_buf[]}  /* cpu_online(): testing CPU Whether online   
       have_callable_console(): Check if there is a registered console */if (cpu_online(smp_processor_id()) || have_callable_console()) {console_may_schedule = 0;release_console_sem();    // call release_console_sem() Print information to the console } else {/* Release the lock to avoid flushing the buffer */console_locked = 0;up(&console_sem);}lockdep_on();local_irq_restore(flags); // Recover local interrupt identifier }... ....}

From the code and comments above , obviously vprintk() Is that :

1) Put the print information in the temporary buffer printk_buf[]
2) From the temporary buffer printk_buf[] Copy to circular buffer log_buf[]
->2.1) Check the print level before each copy , If there is no print level , Then insert the default value default_message_loglevel
3) Finally check if there is a registered console , If you have any , Then call release_console_sem()

7.1 So print level "<0>" to "<7>" What is it ?

Find out printk Print level of stay include/linux/kernel.h Find :

#define    KERN_EMERG     "<0>"       //  System crash #define    KERN_ALERT     "<1>"   // It has to be dealt with urgently #define    KERN_CRIT    "<2>"       //  critical condition , Serious hardware and software errors #define    KERN_ERR       "<3>"       //  Report error #define    KERN_WARNING   "<4>"       // Warning #define    KERN_NOTICE    "<5>"      // Ordinary, but still need to pay attention to #define    KERN_INFO      "<6>"      //  Information #define    KERN_DEBUG     "<7>"     //  Debugging information 

7.2 that ,printk() How to add these prefix values ?

such as : printk Print level 0 , You can enter printk(KERN_EMERG “abc”); perhaps printk( “<0>abc”);

When printk() There is no print level prefix in , such as printk("abc "), The default value will be added default_message_loglevel

7.3 So the default value default_message_loglevel Which level is defined ?

find :

#define MINIMUM_CONSOLE_LOGLEVEL    1  // Print level "<1>"#define DEFAULT_CONSOLE_LOGLEVEL    7  // Print level "<7>"#define DEFAULT_MESSAGE_LOGLEVEL    4  // Print level "<4>"    int console_printk[4] = {   DEFAULT_CONSOLE_LOGLEVEL,        //= Print level "<7>"  DEFAULT_MESSAGE_LOGLEVEL,        // = Print level "<4>"    MINIMUM_CONSOLE_LOGLEVEL,       // = Print level "<1>"    DEFAULT_CONSOLE_LOGLEVEL,      };#define console_loglevel (console_printk[0])            // Information printing maximum , console_printk[1]=7 #define default_message_loglevel (console_printk[1])   // Information printing default , console_printk[1]=4#define minimum_console_loglevel (console_printk[2])  // Information print minimum , console_printk[2]=1#define default_console_loglevel (console_printk[3])

Obviously the default value default_message_loglevel For print level "<4>":

When the default value default_message_loglevel Greater than console_loglevel when , Indicates that the console will not print information

And the minimum minimum_console_loglevel, It is used to judge whether it is greater than console_loglevel

8. Next we move on to release_console_sem(), Let's see where it judges the print level and console_loglevel It's worth it

8.1 printk()->vprintk()->release_console_sem():

void release_console_sem(void){  
   ... ...
  call_console_drivers(_con_start, _log_end);             
  // Will just be saved in the circular buffer log_buf[] The data in , Send it to the command line console
  //_con_start: It's equal to the starting address , _log_end: It's equal to the end address }

8.2 printk()->vprintk()->release_console_sem()->call_console_drivers():

static void call_console_drivers(unsigned long start, unsigned long end){unsigned long cur_index, start_print;   ... ...cur_index = start;start_print = start;while (cur_index != end) // When printing the address of the data , It's equal to the end address , Then quit while{   /* Judge printk Print level of , That's the prefix value "<0>" to "<7>"*/   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) == '>')  {       /* LOG_BUF (addr): obtain addr Data on the address  */ msg_level = LOG_BUF(cur_index + 1) - '0';   //msg_level Equal to print level ,0~7    cur_index += 3;                             // Skip the former 3 A prefix value , such as : "<0>abc", Turn into "abc"    start_print = cur_index;                    // start_print Indicates the starting address of the data to be printed   }   while (cur_index != end)     // Enter the print data link   { char c = LOG_BUF(cur_index);     // Get the cur_index Data on the address  cur_index++; if (c == '\n')                // Determine whether the printed data ends {   if (msg_level < 0) { // If there is no print level , Then insert the default value , The general default level is 4 msg_level = default_message_loglevel;}  _call_console_drivers(start_print, cur_index, msg_level); // call _call_console_drivers()         }   }}

8.3 Get into printk()->vprintk()->release_console_sem()->call_console_drivers()->_call_console_drivers():

static void _call_console_drivers(unsigned long start,unsigned long end, int msg_log_level){     
     /* Determine the print level of the data to be printed msg_log_level , If less than console_loglevel  Value is printed */
   if ((msg_log_level < console_loglevel || ignore_loglevel) &&console_drivers && start != end) {  ... ...  __call_console_drivers(start, end);       }}

It turns out that , When printk(“abc”) When you can't print , May be default_message_loglevel The default value is >=console_loglevel value

9. So how do we modify console_loglevel value ?

There are the following 3 Methods

9.1 By modifying the /proc/sys/kernel/printk To change the printk Print level

As shown in the figure below , You can see default_message_loglevel The default value is less than console_loglevel value , Meet the printing conditions
 Insert picture description here
And then through # echo “1 4 1 7” > /proc/sys/kernel/printk to console_loglevel Set to 1, You can block printing

The drawback is that after the kernel restarts , /proc/sys/kernel/printk The content of will return to the initial value , be equal to "7 4 1 7", You can refer to the method 2 and 3 To make up for that

9.2 Modify the kernel file directly

Directly modifying _call_console_drivers () function ( be located kernel\printk.c)

Put... In the above function console_loglevel Value changed to 0:

if ((msg_log_level < 0 || ignore_loglevel) &&console_drivers && start != end)

You can block printing

9.3 Set command line parameters

take uboot In the command line “console=ttySA0,115200” Change it to “loglevel=0 console=ttySA0,115200”, Represents the setting of the kernel console_loglevel value =0, As shown in the figure below :
 Insert picture description here
As shown in the figure above , You can also add... To the command line debug、quiet Field

debug: It means that you will console_loglevel value =10, Represents all the information in the print kernel , It's usually used for trial use ( I'll talk about debugging later )

quiet: It means that you will console_loglevel value =4

(*PS: Although the screen printed , But there's a buffer for printing log_buf[] in , Can pass dmesg Order to see log_buf[])

10. Next, keep tracking :

printk()->vprintk()->release_console_sem()->call_console_drivers()->_call_console_drivers()->__call_console_drivers():

static void __call_console_drivers(unsigned long start, unsigned long end){   struct console *con;             // console Structure    /*for Loop search console */   for (con = console_drivers; con; 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); // Call the console's write Function print log_buf The data of   }}

Final ,__call_console_drivers() Would call s3c24xx_serial_console Structure of the write function , To print information

11.printk() summary :

1) First , The kernel uses command line arguments , take console Information put in console_cmdline[] In the global array

such as : “console=ttySA0,115200”

2) then , adopt console_initcall() To find the console initialization function

such as : console_initcall(s3c24xx_serial_initconsole); // To find the s3c24xx_serial_initconsole() function

3) In the console initialization function , adopt register_console() To register console Structure

such as : register_console(&s3c24xx_serial_console); // register s3c24xx_serial_console

4) stay register_console() in , matching console_cmdline[] and console Structure , Find hardware processing related through command line parameters console Structure

5) Use printk(), Save the print information in the circular buffer first log_buf[], Then judge the printing level , Whether to call console->write

( PS: Can pass dmesg Command to print the circular buffer log_buf[] )

12.printk() After the analysis , Next, let's talk about how to use printk() To debug the driver

Just one piece of code ok:

printk(KERN_DEBUG"%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);//__FILE__:    Represents the file path //__FUNCTION__:  Represents the function name //__LINE__:    Indicates which line the code is on //KERN_DEBUG:    be equal to 7, Indicates that the print level is 7

And then in the drive , You can use the above code to insert into each line where you need to debug ,

Then refer to the above 9 Section , Set up console_loglevel Greater than

7(KERN_DEBUG).

( When debugging is complete , then console_loglevel Set to 7, The debug information will not be displayed )

FILE, FUNCTION, LINE It can also be used in the application layer printf() in