国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
linux kernel下輸入輸出console如何實現(xiàn)

http://blog.csdn.net/skyflying2012/article/details/41078349

2012

最近工作在調(diào)試usb虛擬串口,讓其作為kernel啟動的調(diào)試串口,以及user空間的輸入輸出控制臺。

利用這個機會,學(xué)習(xí)下printk如何選擇往哪個console輸出以及user空間下控制臺如何選擇,記錄與此,與大家共享,也方便自己以后翻閱。

Kernel版本號:3.4.55

依照我的思路(還是時間順序)分了4部分,指定kernel調(diào)試console ,  kernel下printk console的選擇 ,kernel下console的注冊,user空間console的選擇。


一 指定kernel調(diào)試console

首先看kernel啟動時如何獲取和處理指定的console參數(shù)。

kernel的啟動參數(shù)cmdline可以指定調(diào)試console,如指定‘console=ttyS0,115200’,

kernel如何解析cmdline,我之前寫了一篇博文如下:

http://blog.csdn.net/skyflying2012/article/details/41142801

根據(jù)之前的分析,cmdline中有console=xxx,start_kernel中parse_args遍歷.init.setup段所有obs_kernel_param。

kernel/printk.c中注冊了‘console=’的解析函數(shù)console_setup(注冊了obs_kernel_param),所以匹配成功,會調(diào)用console_setup來解析,如下:

  1. static int __init console_setup(char *str)  
  2. {  
  3.     char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for index */  
  4.     char *s, *options, *brl_options = NULL;  
  5.     int idx;   
  6.   
  7. #ifdef CONFIG_A11Y_BRAILLE_CONSOLE  
  8.     if (!memcmp(str, "brl,", 4)) {  
  9.         brl_options = "";  
  10.         str += 4;  
  11.     } else if (!memcmp(str, "brl=", 4)) {  
  12.         brl_options = str + 4;   
  13.         str = strchr(brl_options, ',');  
  14.         if (!str) {  
  15.             printk(KERN_ERR "need port name after brl=\n");  
  16.             return 1;  
  17.         }      
  18.         *(str++) = 0;   
  19.     }      
  20. #endif  
  21.   
  22.     /* 
  23.      * Decode str into name, index, options. 
  24.      */  
  25.     if (str[0] >= '0' && str[0] <= '9') {  
  26.         strcpy(buf, "ttyS");  
  27.         strncpy(buf + 4, str, sizeof(buf) - 5);  
  28.     } else {  
  29.         strncpy(buf, str, sizeof(buf) - 1);  
  30.     }  
  31.     buf[sizeof(buf) - 1] = 0;  
  32.     if ((options = strchr(str, ',')) != NULL)  
  33.         *(options++) = 0;  
  34. #ifdef __sparc__  
  35.     if (!strcmp(str, "ttya"))  
  36.         strcpy(buf, "ttyS0");  
  37.     if (!strcmp(str, "ttyb"))  
  38.         strcpy(buf, "ttyS1");  
  39. #endif  
  40.     for (s = buf; *s; s++)  
  41.         if ((*s >= '0' && *s <= '9') || *s == ',')  
  42.             break;  
  43.     idx = simple_strtoul(s, NULL, 10);  
  44.     *s = 0;  
  45.   
  46.     __add_preferred_console(buf, idx, options, brl_options);  
  47.     console_set_on_cmdline = 1;  
  48.     return 1;  
  49. }  
  50. __setup("console=", console_setup);  

參數(shù)是console=的值字符串,如“ttyS0,115200”,console_setup對console=參數(shù)值做解析,以ttyS0,115200為例,最后buf=“ttyS”,idx=0,options="115200",brl_options=NULL。調(diào)用__add_preferred_console如下:

  1. /* 
  2.  * If exclusive_console is non-NULL then only this console is to be printed to. 
  3.  */  
  4. static struct console *exclusive_console;  
  5.   
  6. /* 
  7.  *  Array of consoles built from command line options (console=) 
  8.  */  
  9. struct console_cmdline  
  10. {                    
  11.     char    name[8];            /* Name of the driver       */  
  12.     int index;              /* Minor dev. to use        */  
  13.     char    *options;           /* Options for the driver   */  
  14. #ifdef CONFIG_A11Y_BRAILLE_CONSOLE  
  15.     char    *brl_options;           /* Options for braille driver */  
  16. #endif  
  17. };  
  18.   
  19. #define MAX_CMDLINECONSOLES 8  
  20.           
  21. static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];  
  22. static int selected_console = -1;  
  23. static int preferred_console = -1;  
  24. int console_set_on_cmdline;  
  25. EXPORT_SYMBOL(console_set_on_cmdline);  
  26. static int __add_preferred_console(char *name, int idx, char *options,  
  27.                    char *brl_options)  
  28. {  
  29.     struct console_cmdline *c;  
  30.     int i;  
  31.   
  32.     /* 
  33.      *  See if this tty is not yet registered, and 
  34.      *  if we have a slot free. 
  35.      */  
  36.     for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)  
  37.         if (strcmp(console_cmdline[i].name, name) == 0 &&  
  38.               console_cmdline[i].index == idx) {  
  39.                 if (!brl_options)  
  40.                     selected_console = i;  
  41.                 return 0;  
  42.         }  
  43.     if (i == MAX_CMDLINECONSOLES)  
  44.         return -E2BIG;  
  45.     if (!brl_options)  
  46.         selected_console = i;  
  47.     c = &console_cmdline[i];  
  48.     strlcpy(c->name, name, sizeof(c->name));  
  49.     c->options = options;  
  50. #ifdef CONFIG_A11Y_BRAILLE_CONSOLE  
  51.     c->brl_options = brl_options;  
  52. #endif  
  53.     c->index = idx;  
  54.     return 0;  
  55. }  

kernel利用結(jié)構(gòu)體數(shù)組console_cmdline[8],最多可支持8個cmdline傳入的console參數(shù)。

__add_preferred_console將name idx options保存到數(shù)組下一個成員console_cmdline結(jié)構(gòu)體中,如果數(shù)組中已有重名,則不添加,并置selected_console為最新添加的console_cmdline的下標(biāo)號。

比如cmdline中有“console=ttyS0,115200 console=ttyS1,9600”

則在console_cmdline[8]數(shù)組中console_cmdline[0]代表ttyS0,console_cmdline[1]代表ttyS1,而selected_console=1.

二 kernel下printk console的選擇

kernel下調(diào)試信息是通過printk輸出,如果要kernel正常打印,則需要搞明白printk怎么選擇輸出的設(shè)備。

關(guān)于printk的實現(xiàn)原理,我在剛工作的時候?qū)戇^一篇博文,kernel版本是2.6.21的,但是原理還是一致的,可供參考:

http://blog.csdn.net/skyflying2012/article/details/7970341

printk首先將輸出內(nèi)容添加到一個kernel緩沖區(qū)中,叫l(wèi)og_buf,log_buf相關(guān)代碼如下:

  1. #define MAX_CMDLINECONSOLES 8  
  2.   
  3. static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];  
  4. static int selected_console = -1;  
  5. static int preferred_console = -1;  
  6. int console_set_on_cmdline;  
  7. EXPORT_SYMBOL(console_set_on_cmdline);  
  8.   
  9. /* Flag: console code may call schedule() */  
  10. static int console_may_schedule;  
  11.   
  12. #ifdef CONFIG_PRINTK  
  13.           
  14. static char __log_buf[__LOG_BUF_LEN];  
  15. static char *log_buf = __log_buf;  
  16. static int log_buf_len = __LOG_BUF_LEN;  
  17. static unsigned logged_chars; /* Number of chars produced since last read+clear operation */  
  18. static int saved_console_loglevel = -1;  
log_buf的大小由kernel menuconfig配置,我配置的CONFIG_LOG_BUF_SHIFT為17,則log_buf為128k。

printk內(nèi)容會一直存在log_buf中,log_buf滿了之后則會從頭在開始存,覆蓋掉原來的數(shù)據(jù)。

根據(jù)printk的實現(xiàn)原理,printk最后調(diào)用console_unlock實現(xiàn)log_buf數(shù)據(jù)刷出到指定設(shè)備。

這里先不關(guān)心printk如何處理log buf數(shù)據(jù)(比如添加內(nèi)容級別),只關(guān)心printk如何一步步找到指定的輸出設(shè)備,根據(jù)printk.c代碼,可以找到如下線索。

printk->vprintk->console_unlock->call_console_drivers->_call_console_drivers->_call_console_drivers->__call_console_drivers

看線索最底層__call_console_drivers代碼。如下:

  1. /* 
  2.  * Call the console drivers on a range of log_buf 
  3.  */  
  4. static void __call_console_drivers(unsigned start, unsigned end)  
  5. {  
  6.     struct console *con;  
  7.   
  8.     for_each_console(con) {  
  9.         if (exclusive_console && con != exclusive_console)  
  10.             continue;  
  11.         if ((con->flags & CON_ENABLED) && con->write &&  
  12.                 (cpu_online(smp_processor_id()) ||  
  13.                 (con->flags & CON_ANYTIME)))  
  14.             con->write(con, &LOG_BUF(start), end - start);  
  15.     }  
  16. }  
for_each_console定義如下:

  1. /*           
  2.  * for_each_console() allows you to iterate on each console 
  3.  */               
  4. #define for_each_console(con) \  
  5.     for (con = console_drivers; con != NULL; con = con->next)  

遍歷console_drivers鏈表所有console struct,如果有exclusive_console,則調(diào)用與exclusive_console一致console的write,

如果exclusive_console為NULL,則調(diào)用所有ENABLE的console的write方法將log buf中start到end的內(nèi)容發(fā)出。

可以看出,execlusive_console來指定printk輸出唯一console,如果未指定,則向所有enable的console寫。

默認(rèn)情況下execlusive_console=NULL,所以printk默認(rèn)是向所有enable的console寫!

只有一種情況是指定execlusive_console,就是在console注冊時,下面會講到。

到這里就很明了了,kernel下每次printk打印,首先存log_buf,然后遍歷console_drivers,找到合適console(execlusive_console或所有enable的),刷出log。

console_drivers鏈表的成員是哪里來的,誰會指定execulsive_console?接著來看下一部分,kernel下console的注冊


三 kernel下console的注冊

上面分析可以看出,作為kernel移植最基本的一步,kernel下printk正常輸出,最重要的一點是在console_drivers鏈表中添加console struct。那誰來完成這個工作?

答案是register_console函數(shù),在printk.c中,下面來分析下該函數(shù)。

  1. void register_console(struct console *newcon)  
  2. {  
  3.     int i;  
  4.     unsigned long flags;  
  5.     struct console *bcon = NULL;  
  6.   
  7.     //如果注冊的是bootconsole(kernel早期啟動打印),需要檢查console_drivers中  
  8.     //沒有“real console”也就是說bootconsole必須是第一個注冊的console。  
  9.     if (console_drivers && newcon->flags & CON_BOOT) {  
  10.         /* find the last or real console */  
  11.         for_each_console(bcon) {  
  12.             if (!(bcon->flags & CON_BOOT)) {  
  13.                 printk(KERN_INFO "Too late to register bootconsole %s%d\n",  
  14.                     newcon->name, newcon->index);  
  15.                 return;  
  16.             }  
  17.         }  
  18.     }  
  19.   
  20.     if (console_drivers && console_drivers->flags & CON_BOOT)  
  21.         bcon = console_drivers;  
  22.   
  23.     //preferred console為console_cmdline中最后一個console  
  24.     if (preferred_console < 0 || bcon || !console_drivers)  
  25.         preferred_console = selected_console;  
  26.   
  27.     if (newcon->early_setup)  
  28.         newcon->early_setup();  
  29.   
  30.     if (preferred_console < 0) {  
  31.         if (newcon->index < 0)  
  32.             newcon->index = 0;  
  33.         if (newcon->setup == NULL ||  
  34.             newcon->setup(newcon, NULL) == 0) {  
  35.             newcon->flags |= CON_ENABLED;  
  36.             if (newcon->device) {  
  37.                 newcon->flags |= CON_CONSDEV;  
  38.                 preferred_console = 0;  
  39.             }  
  40.         }  
  41.     }  
  42.   
  43.     //檢查newcon是否是cmdline指定的console,如果是,則使能(CON_ENABLE)并初始化該console  
  44.     for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];  
  45.             i++) {  
  46.         if (strcmp(console_cmdline[i].name, newcon->name) != 0)  
  47.             continue;  
  48.         if (newcon->index >= 0 &&  
  49.             newcon->index != console_cmdline[i].index)  
  50.             continue;  
  51.         if (newcon->index < 0)  
  52.             newcon->index = console_cmdline[i].index;  
  53. #ifdef CONFIG_A11Y_BRAILLE_CONSOLE  
  54.         if (console_cmdline[i].brl_options) {  
  55.             newcon->flags |= CON_BRL;  
  56.             braille_register_console(newcon,  
  57.                     console_cmdline[i].index,  
  58.                     console_cmdline[i].options,  
  59.                     console_cmdline[i].brl_options);  
  60.             return;  
  61.         }  
  62. #endif  
  63.         if (newcon->setup &&  
  64.             newcon->setup(newcon, console_cmdline[i].options) != 0)  
  65.             break;  
  66.         newcon->flags |= CON_ENABLED;  
  67.         newcon->index = console_cmdline[i].index;  
  68.         if (i == selected_console) {  
  69.             //如果newcon是cmdline指定的最新的console,則置位CONSDEV  
  70.             newcon->flags |= CON_CONSDEV;  
  71.             preferred_console = selected_console;  
  72.         }  
  73.         break;  
  74.     }  
  75.   
  76.     //該console沒有使能,退出  
  77.     if (!(newcon->flags & CON_ENABLED))  
  78.         return;  
  79.   
  80.     //如果有bootconsole,則newcon不需要輸出register之前的log,因為如果bootconsole和newcon是同一個設(shè)備  
  81.     //則之前的log就輸出2次  
  82.     if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))  
  83.         newcon->flags &= ~CON_PRINTBUFFER;  
  84.   
  85.     //把newcon加入console_drivers鏈表,對于置位CON_CONSDEV的con,放在鏈表首  
  86.     console_lock();  
  87.     if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {  
  88.         newcon->next = console_drivers;  
  89.         console_drivers = newcon;  
  90.         if (newcon->next)  
  91.             newcon->next->flags &= ~CON_CONSDEV;  
  92.     } else {  
  93.         newcon->next = console_drivers->next;  
  94.         console_drivers->next = newcon;  
  95.     }  
  96.     if (newcon->flags & CON_PRINTBUFFER) {  
  97.         //如果newcon置位PRINTBUFFER,則將log全部刷出  
  98.         raw_spin_lock_irqsave(&logbuf_lock, flags);  
  99.         con_start = log_start;  
  100.         raw_spin_unlock_irqrestore(&logbuf_lock, flags);  
  101.         //修改printk輸出的指定唯一exclusive_console為newcon  
  102.         //保證將之前的log只輸出到newcon  
  103.         exclusive_console = newcon;  
  104.     }  
  105.     //解鎖console,刷出log到newcon  
  106.     console_unlock();  
  107.     console_sysfs_notify();  
  108.   
  109.     //如果有bootconsole,則unregister bootconsole(從console_drivers中刪掉)  
  110.     //并告訴使用者現(xiàn)在console切換  
  111.     if (bcon &&  
  112.         ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&  
  113.         !keep_bootcon) {  
  114.         /* we need to iterate through twice, to make sure we print 
  115.          * everything out, before we unregister the console(s) 
  116.          */  
  117.         printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",  
  118.             newcon->name, newcon->index);  
  119.         for_each_console(bcon)  
  120.             if (bcon->flags & CON_BOOT)  
  121.                 unregister_console(bcon);  
  122.     } else {  
  123.         printk(KERN_INFO "%sconsole [%s%d] enabled\n",  
  124.             (newcon->flags & CON_BOOT) ? "boot" : "" ,  
  125.             newcon->name, newcon->index);  
  126.     }  
  127. }  

如果之前注冊了bootconsole,則不會將該次register之前的log刷出,防止bootconsole和該次注冊的newcon是同一個物理設(shè)備時,log打印2次。

如果沒有bootconsole,則會指定exclusive_console=newcon,console_unlock時,刷新全部log到該指定exclusive console。

console_unlock結(jié)束時會將exclusive_console置NULL,所以exclusive console默認(rèn)情況下就是NULL。

最后會unregister bootconsole,是將bootconsole從console_drivers中刪除,這樣之后的printk就不會想bootconsole輸出了。

有意思的一個地方是,在unregister bootconsole之前的printk:

  1. printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",  
  2.             newcon->name, newcon->index);  
因為此時bootconsole還沒刪掉,而newconsole已經(jīng)加入console_drivers,如果bootconsole和newconsole是同一個物理設(shè)備,我們會看到這句printk會出現(xiàn)2次哦!

如果在cmdline指定2個I/O設(shè)備,如“console==ttyS0,115200 console=ttyS1,115200”,因ttyS設(shè)備都是serial driver中注冊的real console,所以會看到kernel的打印分別出現(xiàn)在2個串口上!

boot console和real console差別在于bootconsole注冊于kernel啟動早期,方便對于kernel早期啟動進行調(diào)試打印。

那這些console是在哪里調(diào)用register_console進行注冊的?

bootconsole的注冊,如arch/arm/kernel/early_printk.c,是在parse_args參數(shù)解析階段注冊bootconsole。

在start_kernel中console_init函數(shù)也會遍歷.con_initcall.init段中所有注冊函數(shù),而這些注冊函數(shù)也可以來注冊bootconsole。

.con_initcall.init段中函數(shù)的注冊可以使用宏定義console_initcall。這些函數(shù)中調(diào)用register_console,方便在kernel初期實現(xiàn)printk打印。

realconsole的注冊,是在各個driver,如serial加載時完成。

經(jīng)過上面分析,對于一個新實現(xiàn)的輸入輸出設(shè)備,如果要將其作為kernel下的printk調(diào)試輸出設(shè)備,需要2步:

(1)register console,console struct如下:

  1. struct console {  
  2.     char    name[16];  
  3.     void    (*write)(struct console *, const char *, unsigned);  
  4.     int (*read)(struct console *, char *, unsigned);  
  5.     struct tty_driver *(*device)(struct console *, int *);   
  6.     void    (*unblank)(void);  
  7.     int (*setup)(struct console *, char *);   
  8.     int (*early_setup)(void);  
  9.     short   flags;  
  10.     short   index;  
  11.     int cflag;  
  12.     void    *data;  
  13.     struct   console *next;  
  14. };  
定義一個console,因為kernel調(diào)試信息是單向的,沒有交互,所以只需要實現(xiàn)write即可,還需要實現(xiàn)setup函數(shù),進行設(shè)備初始化(如設(shè)置波特率等),以及標(biāo)志位flags(將所有l(wèi)og刷出),舉個例子,如下:

  1. static struct console u_console =  
  2. {  
  3.     .name       = "ttyS",  
  4.     .write      = u_console_write,  
  5.     .setup      = u_console_setup,  
  6.     .flags      = CON_PRINTBUFFER,  
  7.     .index      = 0,  
  8.     .data       = &u_reg,  
  9. };static int __init  
  10. u_console_init(void)  
  11. {  
  12.     register_console(&u_console);  
  13.     return 0;  
  14. }  

為了調(diào)試方便,可以在console_init調(diào)用該函數(shù)進行注冊,則需要

  1. console_initcall(u_console_init);  
也可以在kernel加載driver時調(diào)用,則需要在driver的probe時調(diào)用u_console_init,但是這樣只能等driver調(diào)register_console之后,console_unlock才將所有l(wèi)og刷出,之前的log都會存在log buf中。

(2)cmdline指定調(diào)試console,在kernel的cmdline添加參數(shù)console=ttyS0,115200



四 user空間console的選擇

用戶空間的輸入輸出依賴于其控制臺使用的哪個,這里有很多名詞,如控制臺,tty,console等,這些名字我也很暈,不用管他們的真正含義,搞嵌入式,直接找到它的實現(xiàn),搞明白從最上層軟件,到最底層硬件,如何操作,還有什么會不清楚呢。

在start_kernel中最后起內(nèi)核init進程時,如下:

  1. /* Open the /dev/console on the rootfs, this should never fail */  
  2.     if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)  
  3.         printk(KERN_WARNING "Warning: unable to open an initial console.\n");  
  4.   
  5.     (void) sys_dup(0);  
  6.     (void) sys_dup(0);  
去打開console設(shè)備,console設(shè)備做了控制臺。

console設(shè)備文件的創(chuàng)建在driver/tty/tty_io.c中,如下:

  1. static const struct file_operations console_fops = {  
  2.     .llseek     = no_llseek,  
  3.     .read       = tty_read,  
  4.     .write      = redirected_tty_write,  
  5.     .poll       = tty_poll,  
  6.     .unlocked_ioctl = tty_ioctl,  
  7.     .compat_ioctl   = tty_compat_ioctl,  
  8.     .open       = tty_open,  
  9.     .release    = tty_release,  
  10.     .fasync     = tty_fasync,  
  11. };  
  12. int __init tty_init(void)  
  13. {  
  14.     cdev_init(&tty_cdev, &tty_fops);  
  15.     if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||  
  16.         register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)  
  17.         panic("Couldn't register /dev/tty driver\n");  
  18.     device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");  
  19.   
  20.     cdev_init(&console_cdev, &console_fops);  
  21.     if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||  
  22.         register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)  
  23.         panic("Couldn't register /dev/console driver\n");  
  24.     consdev = device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL,  
  25.                   "console");  
  26.     if (IS_ERR(consdev))  
  27.         consdev = NULL;  
  28.     else  
  29.         WARN_ON(device_create_file(consdev, &dev_attr_active) < 0);  
  30.       
  31. #ifdef CONFIG_VT  
  32.     vty_init(&console_fops);  
  33. #endif  
  34.     return 0;  
  35. }  

console的操作函數(shù)都是使用的tty的操作函數(shù),看open的實現(xiàn),如何找到具體的操作設(shè)備:

  1. static int tty_open(struct inode *inode, struct file *filp)  
  2. {  
  3.     struct tty_struct *tty;  
  4.     int noctty, retval;  
  5.     struct tty_driver *driver = NULL;  
  6.     int index;  
  7.     dev_t device = inode->i_rdev;  
  8.     unsigned saved_flags = filp->f_flags;  
  9.       
  10.     nonseekable_open(inode, filp);  
  11.   
  12. retry_open:  
  13.     retval = tty_alloc_file(filp);  
  14.     if (retval)   
  15.         return -ENOMEM;  
  16.       
  17.     noctty = filp->f_flags & O_NOCTTY;  
  18.     index  = -1;  
  19.     retval = 0;  
  20.   
  21.     mutex_lock(&tty_mutex);  
  22.     tty_lock();  
  23.   
  24.     tty = tty_open_current_tty(device, filp);  
  25.     if (IS_ERR(tty)) {  
  26.         retval = PTR_ERR(tty);  
  27.         goto err_unlock;  
  28.     } else if (!tty) {  
  29.         driver = tty_lookup_driver(device, filp, &noctty, &index);  
  30.         if (IS_ERR(driver)) {  
  31.             retval = PTR_ERR(driver);  
  32.             goto err_unlock;  
  33.         }  /* check whether we're reopening an existing tty */  
  34.         tty = tty_driver_lookup_tty(driver, inode, index);  
  35.         if (IS_ERR(tty)) {  
  36.             retval = PTR_ERR(tty);  
  37.             goto err_unlock;  
  38.         }  
  39.     }  
}

首先tty_open_current_tty找該進程所對應(yīng)的tty,因為init進程我們并沒有制定tty,所以該函數(shù)返回NULL。

接下來調(diào)用tty_lookup_driver,如下:

  1. static struct tty_driver *tty_lookup_driver(dev_t device, struct file *filp,  
  2.         int *noctty, int *index)  
  3. {  
  4.     struct tty_driver *driver;  
  5.   
  6.     switch (device) {  
  7. #ifdef CONFIG_VT  
  8.     case MKDEV(TTY_MAJOR, 0): {  
  9.         extern struct tty_driver *console_driver;  
  10.         driver = tty_driver_kref_get(console_driver);  
  11.         *index = fg_console;  
  12.         *noctty = 1;  
  13.         break;  
  14.     }  
  15. #endif  
  16.     case MKDEV(TTYAUX_MAJOR, 1): {  
  17.         struct tty_driver *console_driver = console_device(index);  
  18.         if (console_driver) {  
  19.             driver = tty_driver_kref_get(console_driver);  
  20.             if (driver) {  
  21.                 /* Don't let /dev/console block */  
  22.                 filp->f_flags |= O_NONBLOCK;  
  23.                 *noctty = 1;  
  24.                 break;  
  25.             }  
  26.         }  
  27.         return ERR_PTR(-ENODEV);  
  28.     }  
  29.     default:  
  30.         driver = get_tty_driver(device, index);  
  31.         if (!driver)  
  32.             return ERR_PTR(-ENODEV);  
  33.         break;  
  34.     }  
  35.     return driver;  
  36. }  
console設(shè)備文件,次設(shè)備號是1,根據(jù)代碼,會調(diào)用console_device來獲取對應(yīng)的tty_driver,如下:

  1. struct tty_driver *console_device(int *index)  
  2. {         
  3.     struct console *c;  
  4.     struct tty_driver *driver = NULL;  
  5.   
  6.     console_lock();   
  7.     for_each_console(c) {  
  8.         if (!c->device)  
  9.             continue;   
  10.         driver = c->device(c, index);  
  11.         if (driver)  
  12.             break;  
  13.     }     
  14.     console_unlock();  
  15.     return driver;  
  16. }  
又遇到了熟悉的for_each_console,遍歷console_drivers鏈表,對于存在device成員的console,調(diào)用device方法,獲取tty_driver,退出遍歷。

之后對于該console設(shè)備的讀寫操作都是基于該tty_driver。

所有的輸入輸出設(shè)備都會注冊tty_driver。

所以,對于一個新實現(xiàn)的輸入輸出設(shè)備,如果想讓其即作為kernel的printk輸出設(shè)備,也作為user空間的控制臺,則需要在上面u_console基礎(chǔ)上再實現(xiàn)device方法成員,來返回該設(shè)備的tty_driver。


那么還有一個問題:

如果cmdline指定2個I/O設(shè)備,“console=ttyS0,115200 console=ttyS1,115200”,user空間選擇哪個作為console?

用戶空間console open時,console_device遍歷console_drivers,找到有device成員的console,獲取tty_driver,就會退出遍歷。

所以哪個console放在console_drivers前面,就會被選擇為user空間的console。

在分析register_console時,如果要注冊的newcon是cmdline指定的最新的console(i = selected_console),則置位CON_CONSDEV,

而在后面newcon加入console_drivers時,判斷該置位,置位CON_CONSDEV,則將newcon加入到console_drivers的鏈表頭,否則插入到后面。

所以這里user空間會選擇ttyS1作為用戶控件的console!


總結(jié)下,kernel和user空間下都有一個console,關(guān)系到kernel下printk的方向和user下printf的方向,實現(xiàn)差別還是很大的。

kernel下的console是輸入輸出設(shè)備driver中實現(xiàn)的簡單的輸出console,只實現(xiàn)write函數(shù),并且是直接輸出到設(shè)備。


user空間下的console,實際就是tty的一個例子,所有操作函數(shù)都繼承與tty,全功能,可以打開 讀寫 關(guān)閉,所以對于console的讀寫,都是由kernel的tty層來最終發(fā)送到設(shè)備。

kernel的tty層之下還有l(wèi)disc線路規(guī)程層,線路規(guī)程層之下才是具體設(shè)備的driver。

ldisc層處理一些對于控制臺來說有意義的輸入輸出字符,比如輸入的crtl+C,輸出的‘\n‘進過線路規(guī)程會變?yōu)椤痋n\r‘。


所以對于kernel下console的write方法,不要忘記,對于log buf中'\n'的處理,實現(xiàn)一個簡單的線路規(guī)程!









本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
linux console驅(qū)動詳解
linux tty driver
LINUX下的tty,console與串口
linux下/dev/tty, /dev/tty0, /dev/console區(qū)別
17. 內(nèi)核參數(shù)解析
軟件調(diào)試
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服