日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

kernel 下串口serial输入输出控制,屏蔽log的输出

發布時間:2025/3/21 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 kernel 下串口serial输入输出控制,屏蔽log的输出 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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

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

Kernel版本號:3.4.55

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


一 指定kernel調試console

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

kernel的啟動參數cmdline可以指定調試console,如指定‘console=ttyS0,115200’,

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

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

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

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

[cpp]?view plaincopy
  • static?int?__init?console_setup(char?*str)??
  • {??
  • ????char?buf[sizeof(console_cmdline[0].name)?+?4];?/*?4?for?index?*/??
  • ????char?*s,?*options,?*brl_options?=?NULL;??
  • ????int?idx;???
  • ??
  • #ifdef?CONFIG_A11Y_BRAILLE_CONSOLE??
  • ????if?(!memcmp(str,?"brl,",?4))?{??
  • ????????brl_options?=?"";??
  • ????????str?+=?4;??
  • ????}?else?if?(!memcmp(str,?"brl=",?4))?{??
  • ????????brl_options?=?str?+?4;???
  • ????????str?=?strchr(brl_options,?',');??
  • ????????if?(!str)?{??
  • ????????????printk(KERN_ERR?"need?port?name?after?brl=\n");??
  • ????????????return?1;??
  • ????????}??????
  • ????????*(str++)?=?0;???
  • ????}??????
  • #endif??
  • ??
  • ????/*?
  • ?????*?Decode?str?into?name,?index,?options.?
  • ?????*/??
  • ????if?(str[0]?>=?'0'?&&?str[0]?<=?'9')?{??
  • ????????strcpy(buf,?"ttyS");??
  • ????????strncpy(buf?+?4,?str,?sizeof(buf)?-?5);??
  • ????}?else?{??
  • ????????strncpy(buf,?str,?sizeof(buf)?-?1);??
  • ????}??
  • ????buf[sizeof(buf)?-?1]?=?0;??
  • ????if?((options?=?strchr(str,?','))?!=?NULL)??
  • ????????*(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?==?',')??
  • ????????????break;??
  • ????idx?=?simple_strtoul(s,?NULL,?10);??
  • ????*s?=?0;??
  • ??
  • ????__add_preferred_console(buf,?idx,?options,?brl_options);??
  • ????console_set_on_cmdline?=?1;??
  • ????return?1;??
  • }??
  • __setup("console=",?console_setup);??
  • 參數是console=的值字符串,如“ttyS0,115200”,console_setup對console=參數值做解析,以ttyS0,115200為例,最后buf=“ttyS”,idx=0,options="115200",brl_options=NULL。調用__add_preferred_console如下:

    [cpp]?view plaincopy
  • /*?
  • ?*?If?exclusive_console?is?non-NULL?then?only?this?console?is?to?be?printed?to.?
  • ?*/??
  • static?struct?console?*exclusive_console;??
  • ??
  • /*?
  • ?*??Array?of?consoles?built?from?command?line?options?(console=)?
  • ?*/??
  • struct?console_cmdline??
  • {????????????????????
  • ????char????name[8];????????????/*?Name?of?the?driver???????*/??
  • ????int?index;??????????????/*?Minor?dev.?to?use????????*/??
  • ????char????*options;???????????/*?Options?for?the?driver???*/??
  • #ifdef?CONFIG_A11Y_BRAILLE_CONSOLE??
  • ????char????*brl_options;???????????/*?Options?for?braille?driver?*/??
  • #endif??
  • };??
  • ??
  • #define?MAX_CMDLINECONSOLES?8??
  • ??????????
  • static?struct?console_cmdline?console_cmdline[MAX_CMDLINECONSOLES];??
  • static?int?selected_console?=?-1;??
  • static?int?preferred_console?=?-1;??
  • int?console_set_on_cmdline;??
  • EXPORT_SYMBOL(console_set_on_cmdline);??
  • static?int?__add_preferred_console(char?*name,?int?idx,?char?*options,??
  • ???????????????????char?*brl_options)??
  • {??
  • ????struct?console_cmdline?*c;??
  • ????int?i;??
  • ??
  • ????/*?
  • ?????*??See?if?this?tty?is?not?yet?registered,?and?
  • ?????*??if?we?have?a?slot?free.?
  • ?????*/??
  • ????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)?{??
  • ????????????????if?(!brl_options)??
  • ????????????????????selected_console?=?i;??
  • ????????????????return?0;??
  • ????????}??
  • ????if?(i?==?MAX_CMDLINECONSOLES)??
  • ????????return?-E2BIG;??
  • ????if?(!brl_options)??
  • ????????selected_console?=?i;??
  • ????c?=?&console_cmdline[i];??
  • ????strlcpy(c->name,?name,?sizeof(c->name));??
  • ????c->options?=?options;??
  • #ifdef?CONFIG_A11Y_BRAILLE_CONSOLE??
  • ????c->brl_options?=?brl_options;??
  • #endif??
  • ????c->index?=?idx;??
  • ????return?0;??
  • }??

  • kernel利用結構體數組console_cmdline[8],最多可支持8個cmdline傳入的console參數。

    __add_preferred_console將name idx options保存到數組下一個成員console_cmdline結構體中,如果數組中已有重名,則不添加,并置selected_console為最新添加的console_cmdline的下標號。

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

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

    二 kernel下printk console的選擇

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

    關于printk的實現原理,我在剛工作的時候寫過一篇博文,kernel版本是2.6.21的,但是原理還是一致的,可供參考:

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

    printk首先將輸出內容添加到一個kernel緩沖區中,叫log_buf,log_buf相關代碼如下:

    [cpp]?view plaincopy
  • #define?MAX_CMDLINECONSOLES?8??
  • ??
  • static?struct?console_cmdline?console_cmdline[MAX_CMDLINECONSOLES];??
  • static?int?selected_console?=?-1;??
  • static?int?preferred_console?=?-1;??
  • int?console_set_on_cmdline;??
  • EXPORT_SYMBOL(console_set_on_cmdline);??
  • ??
  • /*?Flag:?console?code?may?call?schedule()?*/??
  • static?int?console_may_schedule;??
  • ??
  • #ifdef?CONFIG_PRINTK??
  • ??????????
  • static?char?__log_buf[__LOG_BUF_LEN];??
  • static?char?*log_buf?=?__log_buf;??
  • static?int?log_buf_len?=?__LOG_BUF_LEN;??
  • static?unsigned?logged_chars;?/*?Number?of?chars?produced?since?last?read+clear?operation?*/??
  • static?int?saved_console_loglevel?=?-1;??
  • log_buf的大小由kernel menuconfig配置,我配置的CONFIG_LOG_BUF_SHIFT為17,則log_buf為128k。

    printk內容會一直存在log_buf中,log_buf滿了之后則會從頭在開始存,覆蓋掉原來的數據。

    根據printk的實現原理,printk最后調用console_unlock實現log_buf數據刷出到指定設備。

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

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

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

    [cpp]?view plaincopy
  • /*?
  • ?*?Call?the?console?drivers?on?a?range?of?log_buf?
  • ?*/??
  • static?void?__call_console_drivers(unsigned?start,?unsigned?end)??
  • {??
  • ????struct?console?*con;??
  • ??
  • ????for_each_console(con)?{??
  • ????????if?(exclusive_console?&&?con?!=?exclusive_console)??
  • ????????????continue;??
  • ????????if?((con->flags?&?CON_ENABLED)?&&?con->write?&&??
  • ????????????????(cpu_online(smp_processor_id())?||??
  • ????????????????(con->flags?&?CON_ANYTIME)))??
  • ????????????con->write(con,?&LOG_BUF(start),?end?-?start);??
  • ????}??
  • }??
  • for_each_console定義如下:

    [cpp]?view plaincopy
  • /*???????????
  • ?*?for_each_console()?allows?you?to?iterate?on?each?console?
  • ?*/???????????????
  • #define?for_each_console(con)?\??
  • ????for?(con?=?console_drivers;?con?!=?NULL;?con?=?con->next)??
  • 遍歷console_drivers鏈表所有console struct,如果有exclusive_console,則調用與exclusive_console一致console的write,

    如果exclusive_console為NULL,則調用所有ENABLE的console的write方法將log buf中start到end的內容發出。

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

    默認情況下execlusive_console=NULL,所以printk默認是向所有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函數,在printk.c中,下面來分析下該函數。

    [cpp]?view plaincopy
  • void?register_console(struct?console?*newcon)??
  • {??
  • ????int?i;??
  • ????unsigned?long?flags;??
  • ????struct?console?*bcon?=?NULL;??
  • ??
  • ????//如果注冊的是bootconsole(kernel早期啟動打印),需要檢查console_drivers中??
  • ????//沒有“real?console”也就是說bootconsole必須是第一個注冊的console。??
  • ????if?(console_drivers?&&?newcon->flags?&?CON_BOOT)?{??
  • ????????/*?find?the?last?or?real?console?*/??
  • ????????for_each_console(bcon)?{??
  • ????????????if?(!(bcon->flags?&?CON_BOOT))?{??
  • ????????????????printk(KERN_INFO?"Too?late?to?register?bootconsole?%s%d\n",??
  • ????????????????????newcon->name,?newcon->index);??
  • ????????????????return;??
  • ????????????}??
  • ????????}??
  • ????}??
  • ??
  • ????if?(console_drivers?&&?console_drivers->flags?&?CON_BOOT)??
  • ????????bcon?=?console_drivers;??
  • ??
  • ????//preferred?console為console_cmdline中最后一個console??
  • ????if?(preferred_console?<?0?||?bcon?||?!console_drivers)??
  • ????????preferred_console?=?selected_console;??
  • ??
  • ????if?(newcon->early_setup)??
  • ????????newcon->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;??
  • ????????????}??
  • ????????}??
  • ????}??
  • ??
  • ????//檢查newcon是否是cmdline指定的console,如果是,則使能(CON_ENABLE)并初始化該console??
  • ????for?(i?=?0;?i?<?MAX_CMDLINECONSOLES?&&?console_cmdline[i].name[0];??
  • ????????????i++)?{??
  • ????????if?(strcmp(console_cmdline[i].name,?newcon->name)?!=?0)??
  • ????????????continue;??
  • ????????if?(newcon->index?>=?0?&&??
  • ????????????newcon->index?!=?console_cmdline[i].index)??
  • ????????????continue;??
  • ????????if?(newcon->index?<?0)??
  • ????????????newcon->index?=?console_cmdline[i].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??
  • ????????if?(newcon->setup?&&??
  • ????????????newcon->setup(newcon,?console_cmdline[i].options)?!=?0)??
  • ????????????break;??
  • ????????newcon->flags?|=?CON_ENABLED;??
  • ????????newcon->index?=?console_cmdline[i].index;??
  • ????????if?(i?==?selected_console)?{??
  • ????????????//如果newcon是cmdline指定的最新的console,則置位CONSDEV??
  • ????????????newcon->flags?|=?CON_CONSDEV;??
  • ????????????preferred_console?=?selected_console;??
  • ????????}??
  • ????????break;??
  • ????}??
  • ??
  • ????//該console沒有使能,退出??
  • ????if?(!(newcon->flags?&?CON_ENABLED))??
  • ????????return;??
  • ??
  • ????//如果有bootconsole,則newcon不需要輸出register之前的log,因為如果bootconsole和newcon是同一個設備??
  • ????//則之前的log就輸出2次??
  • ????if?(bcon?&&?((newcon->flags?&?(CON_CONSDEV?|?CON_BOOT))?==?CON_CONSDEV))??
  • ????????newcon->flags?&=?~CON_PRINTBUFFER;??
  • ??
  • ????//把newcon加入console_drivers鏈表,對于置位CON_CONSDEV的con,放在鏈表首??
  • ????console_lock();??
  • ????if?((newcon->flags?&?CON_CONSDEV)?||?console_drivers?==?NULL)?{??
  • ????????newcon->next?=?console_drivers;??
  • ????????console_drivers?=?newcon;??
  • ????????if?(newcon->next)??
  • ????????????newcon->next->flags?&=?~CON_CONSDEV;??
  • ????}?else?{??
  • ????????newcon->next?=?console_drivers->next;??
  • ????????console_drivers->next?=?newcon;??
  • ????}??
  • ????if?(newcon->flags?&?CON_PRINTBUFFER)?{??
  • ????????//如果newcon置位PRINTBUFFER,則將log全部刷出??
  • ????????raw_spin_lock_irqsave(&logbuf_lock,?flags);??
  • ????????con_start?=?log_start;??
  • ????????raw_spin_unlock_irqrestore(&logbuf_lock,?flags);??
  • ????????//修改printk輸出的指定唯一exclusive_console為newcon??
  • ????????//保證將之前的log只輸出到newcon??
  • ????????exclusive_console?=?newcon;??
  • ????}??
  • ????//解鎖console,刷出log到newcon??
  • ????console_unlock();??
  • ????console_sysfs_notify();??
  • ??
  • ????//如果有bootconsole,則unregister?bootconsole(從console_drivers中刪掉)??
  • ????//并告訴使用者現在console切換??
  • ????if?(bcon?&&??
  • ????????((newcon->flags?&?(CON_CONSDEV?|?CON_BOOT))?==?CON_CONSDEV)?&&??
  • ????????!keep_bootcon)?{??
  • ????????/*?we?need?to?iterate?through?twice,?to?make?sure?we?print?
  • ?????????*?everything?out,?before?we?unregister?the?console(s)?
  • ?????????*/??
  • ????????printk(KERN_INFO?"console?[%s%d]?enabled,?bootconsole?disabled\n",??
  • ????????????newcon->name,?newcon->index);??
  • ????????for_each_console(bcon)??
  • ????????????if?(bcon->flags?&?CON_BOOT)??
  • ????????????????unregister_console(bcon);??
  • ????}?else?{??
  • ????????printk(KERN_INFO?"%sconsole?[%s%d]?enabled\n",??
  • ????????????(newcon->flags?&?CON_BOOT)???"boot"?:?""?,??
  • ????????????newcon->name,?newcon->index);??
  • ????}??
  • }??
  • 如果之前注冊了bootconsole,則不會將該次register之前的log刷出,防止bootconsole和該次注冊的newcon是同一個物理設備時,log打印2次。

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

    console_unlock結束時會將exclusive_console置NULL,所以exclusive console默認情況下就是NULL。

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

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

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

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

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

    那這些console是在哪里調用register_console進行注冊的?

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

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

    .con_initcall.init段中函數的注冊可以使用宏定義console_initcall。這些函數中調用register_console,方便在kernel初期實現printk打印。

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

    經過上面分析,對于一個新實現的輸入輸出設備,如果要將其作為kernel下的printk調試輸出設備,需要2步:

    (1)register console,console struct如下:

    [cpp]?view plaincopy
  • struct?console?{??
  • ????char????name[16];??
  • ????void????(*write)(struct?console?*,?const?char?*,?unsigned);??
  • ????int?(*read)(struct?console?*,?char?*,?unsigned);??
  • ????struct?tty_driver?*(*device)(struct?console?*,?int?*);???
  • ????void????(*unblank)(void);??
  • ????int?(*setup)(struct?console?*,?char?*);???
  • ????int?(*early_setup)(void);??
  • ????short???flags;??
  • ????short???index;??
  • ????int?cflag;??
  • ????void????*data;??
  • ????struct???console?*next;??
  • };??
  • 定義一個console,因為kernel調試信息是單向的,沒有交互,所以只需要實現write即可,還需要實現setup函數,進行設備初始化(如設置波特率等),以及標志位flags(將所有log刷出),舉個例子,如下:

    [cpp]?view plaincopy
  • static?struct?console?u_console?=??
  • {??
  • ????.name???????=?"ttyS",??
  • ????.write??????=?u_console_write,??
  • ????.setup??????=?u_console_setup,??
  • ????.flags??????=?CON_PRINTBUFFER,??
  • ????.index??????=?0,??
  • ????.data???????=?&u_reg,??
  • };static?int?__init??
  • u_console_init(void)??
  • {??
  • ????register_console(&u_console);??
  • ????return?0;??
  • }??
  • 為了調試方便,可以在console_init調用該函數進行注冊,則需要

    [cpp]?view plaincopy
  • console_initcall(u_console_init);??
  • 也可以在kernel加載driver時調用,則需要在driver的probe時調用u_console_init,但是這樣只能等driver調register_console之后,console_unlock才將所有log刷出,之前的log都會存在log buf中。

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



    四 user空間console的選擇

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

    在start_kernel中最后起內核init進程時,如下:

    [cpp]?view plaincopy
  • /*?Open?the?/dev/console?on?the?rootfs,?this?should?never?fail?*/??
  • ????if?(sys_open((const?char?__user?*)?"/dev/console",?O_RDWR,?0)?<?0)??
  • ????????printk(KERN_WARNING?"Warning:?unable?to?open?an?initial?console.\n");??
  • ??
  • ????(void)?sys_dup(0);??
  • ????(void)?sys_dup(0);??
  • 去打開console設備,console設備做了控制臺。

    console設備文件的創建在driver/tty/tty_io.c中,如下:

    [cpp]?view plaincopy
  • static?const?struct?file_operations?console_fops?=?{??
  • ????.llseek?????=?no_llseek,??
  • ????.read???????=?tty_read,??
  • ????.write??????=?redirected_tty_write,??
  • ????.poll???????=?tty_poll,??
  • ????.unlocked_ioctl?=?tty_ioctl,??
  • ????.compat_ioctl???=?tty_compat_ioctl,??
  • ????.open???????=?tty_open,??
  • ????.release????=?tty_release,??
  • ????.fasync?????=?tty_fasync,??
  • };??
  • int?__init?tty_init(void)??
  • {??
  • ????cdev_init(&tty_cdev,?&tty_fops);??
  • ????if?(cdev_add(&tty_cdev,?MKDEV(TTYAUX_MAJOR,?0),?1)?||??
  • ????????register_chrdev_region(MKDEV(TTYAUX_MAJOR,?0),?1,?"/dev/tty")?<?0)??
  • ????????panic("Couldn't?register?/dev/tty?driver\n");??
  • ????device_create(tty_class,?NULL,?MKDEV(TTYAUX_MAJOR,?0),?NULL,?"tty");??
  • ??
  • ????cdev_init(&console_cdev,?&console_fops);??
  • ????if?(cdev_add(&console_cdev,?MKDEV(TTYAUX_MAJOR,?1),?1)?||??
  • ????????register_chrdev_region(MKDEV(TTYAUX_MAJOR,?1),?1,?"/dev/console")?<?0)??
  • ????????panic("Couldn't?register?/dev/console?driver\n");??
  • ????consdev?=?device_create(tty_class,?NULL,?MKDEV(TTYAUX_MAJOR,?1),?NULL,??
  • ??????????????????"console");??
  • ????if?(IS_ERR(consdev))??
  • ????????consdev?=?NULL;??
  • ????else??
  • ????????WARN_ON(device_create_file(consdev,?&dev_attr_active)?<?0);??
  • ??????
  • #ifdef?CONFIG_VT??
  • ????vty_init(&console_fops);??
  • #endif??
  • ????return?0;??
  • }??

  • console的操作函數都是使用的tty的操作函數,看open的實現,如何找到具體的操作設備:

    [cpp]?view plaincopy
  • static?int?tty_open(struct?inode?*inode,?struct?file?*filp)??
  • {??
  • ????struct?tty_struct?*tty;??
  • ????int?noctty,?retval;??
  • ????struct?tty_driver?*driver?=?NULL;??
  • ????int?index;??
  • ????dev_t?device?=?inode->i_rdev;??
  • ????unsigned?saved_flags?=?filp->f_flags;??
  • ??????
  • ????nonseekable_open(inode,?filp);??
  • ??
  • retry_open:??
  • ????retval?=?tty_alloc_file(filp);??
  • ????if?(retval)???
  • ????????return?-ENOMEM;??
  • ??????
  • ????noctty?=?filp->f_flags?&?O_NOCTTY;??
  • ????index??=?-1;??
  • ????retval?=?0;??
  • ??
  • ????mutex_lock(&tty_mutex);??
  • ????tty_lock();??
  • ??
  • ????tty?=?tty_open_current_tty(device,?filp);??
  • ????if?(IS_ERR(tty))?{??
  • ????????retval?=?PTR_ERR(tty);??
  • ????????goto?err_unlock;??
  • ????}?else?if?(!tty)?{??
  • ????????driver?=?tty_lookup_driver(device,?filp,?&noctty,?&index);??
  • ????????if?(IS_ERR(driver))?{??
  • ????????????retval?=?PTR_ERR(driver);??
  • ????????????goto?err_unlock;??
  • ????????}??/*?check?whether?we're?reopening?an?existing?tty?*/??
  • ????????tty?=?tty_driver_lookup_tty(driver,?inode,?index);??
  • ????????if?(IS_ERR(tty))?{??
  • ????????????retval?=?PTR_ERR(tty);??
  • ????????????goto?err_unlock;??
  • ????????}??
  • ????}??
  • }

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

    接下來調用tty_lookup_driver,如下:

    [cpp]?view plaincopy
  • static?struct?tty_driver?*tty_lookup_driver(dev_t?device,?struct?file?*filp,??
  • ????????int?*noctty,?int?*index)??
  • {??
  • ????struct?tty_driver?*driver;??
  • ??
  • ????switch?(device)?{??
  • #ifdef?CONFIG_VT??
  • ????case?MKDEV(TTY_MAJOR,?0):?{??
  • ????????extern?struct?tty_driver?*console_driver;??
  • ????????driver?=?tty_driver_kref_get(console_driver);??
  • ????????*index?=?fg_console;??
  • ????????*noctty?=?1;??
  • ????????break;??
  • ????}??
  • #endif??
  • ????case?MKDEV(TTYAUX_MAJOR,?1):?{??
  • ????????struct?tty_driver?*console_driver?=?console_device(index);??
  • ????????if?(console_driver)?{??
  • ????????????driver?=?tty_driver_kref_get(console_driver);??
  • ????????????if?(driver)?{??
  • ????????????????/*?Don't?let?/dev/console?block?*/??
  • ????????????????filp->f_flags?|=?O_NONBLOCK;??
  • ????????????????*noctty?=?1;??
  • ????????????????break;??
  • ????????????}??
  • ????????}??
  • ????????return?ERR_PTR(-ENODEV);??
  • ????}??
  • ????default:??
  • ????????driver?=?get_tty_driver(device,?index);??
  • ????????if?(!driver)??
  • ????????????return?ERR_PTR(-ENODEV);??
  • ????????break;??
  • ????}??
  • ????return?driver;??
  • }??
  • console設備文件,次設備號是1,根據代碼,會調用console_device來獲取對應的tty_driver,如下:

    [cpp]?view plaincopy
  • struct?tty_driver?*console_device(int?*index)??
  • {?????????
  • ????struct?console?*c;??
  • ????struct?tty_driver?*driver?=?NULL;??
  • ??
  • ????console_lock();???
  • ????for_each_console(c)?{??
  • ????????if?(!c->device)??
  • ????????????continue;???
  • ????????driver?=?c->device(c,?index);??
  • ????????if?(driver)??
  • ????????????break;??
  • ????}?????
  • ????console_unlock();??
  • ????return?driver;??
  • }??
  • 又遇到了熟悉的for_each_console,遍歷console_drivers鏈表,對于存在device成員的console,調用device方法,獲取tty_driver,退出遍歷。

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

    所有的輸入輸出設備都會注冊tty_driver。

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


    那么還有一個問題:

    如果cmdline指定2個I/O設備,“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!


    總結下,kernel和user空間下都有一個console,關系到kernel下printk的方向和user下printf的方向,實現差別還是很大的。

    kernel下的console是輸入輸出設備driver中實現的簡單的輸出console,只實現write函數,并且是直接輸出到設備。


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

    kernel的tty層之下還有ldisc線路規程層,線路規程層之下才是具體設備的driver。

    ldisc層處理一些對于控制臺來說有意義的輸入輸出字符,比如輸入的crtl+C,輸出的‘\n‘進過線路規程會變為’\n\r‘。


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

    總結

    以上是生活随笔為你收集整理的kernel 下串口serial输入输出控制,屏蔽log的输出的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。