天天看點

SerialConsoles and Consoles in Generalhttp://ar.linux.it/docs/sercons/sercons.html SerialConsoles and Consoles in General

http://ar.linux.it/docs/sercons/sercons.html

SerialConsoles and Consoles in General

       在讨論了序列槽驅動的架構以及如何通過串行裝置和PPP以及SLIP協定互動之後,http://ar.linux.it/docs/serial/serial.html這個月我們要看一下,如何使用序列槽作為控制台.幸運的是,控制台管理是獨立于底層的具體的串行端口的,并且它自身也比較有趣.是以,這篇文章不會過多的讨論串行端口,而是主要講解控制台管理本身的内容,同時最後會講解一個UDP控制台的實作,來說明2.4中的控制台管理是如何實作的.代碼通過了2.4.4核心測試.

1,控制台的思想(由來)

曾幾何時,電腦是一個大盒子加上很多的終端,這些終端中的一個擔負着一個特殊的角色,那就是”系統控制台”,這個終端是唯一的一個特殊終端-它可以單使用者模式登入來進行系統恢複,也是唯一一個可以接受系統的錯誤消息.

現代的電腦的硬體配置大部分是顯示器和鍵盤,并不使用很多的的字元終端,是以現在的”系統控制台”在硬體上已經演變為一個本地的鍵盤和顯示器.

當然,另外一種情況,主機沒有自己的顯示硬體的時候,”系統控制台”的角色往往就由串行端口來擔負了:在電腦插一條序列槽線,就可以遠端登入,可以擷取錯誤診斷資訊.

雖說這樣使得看起來“系統控制控制台”類似于”控制終端”,他們都能輸入輸出.

但是,啟動一個shell和傳輸核心消息使用的完全不同的機制.前者,是通過init程序來啟動的,這部分的讨論超出了本文的範圍.我們這部分核心相關的章節,主要關心後者:核心消息是如何傳遞給目前活動的系統管理源.注意我不會讨論使用者程式如何去手機核心消息(比如klogd和syslogd),我會把重心放在核心消息發送到”控制台裝置”,這個控制台裝置可能是串行端口或者其他裝置.

2,預設的控制台

當你運作一個經典的字元模式的GNU/Linux系統的時候,控制台預設往往是目前的虛拟終端(真正的終端是用序列槽線連出去的,這裡用顯示器來模拟,當然是虛拟的終端了).是以,核心消息就落到了你的shell區域,這種行為可能讓人很惱火,比如你在通路一塊壞掉的軟碟,結果每隔幾秒,它就發出幾行抱怨資訊(就是核心消息不時的打斷你,輸出到你的shell中,的确,我曾經alt+f1的時候,進去後,核心自動發出I/O錯誤的資訊什麼的),下面我們會講怎麼解決這個問題.讓你使用X的時候,目前的虛拟終端是圖形模式,傳輸核心消息的功能被禁用了,即使核心傳輸了核心消息,我們也看不到.

傳輸核心消息機制到控制台的具體實作是函數printk,在kernel/printk.c中定義.這個函數使用vsprintf(在lib/vsprintf.c中定義)建立一個消息字元串,把字元串放在一個存放核心消息的循環緩沖區中,然後把它發送給所有級别小于console_loglevel的控制台裝置(也就是說,控制台裝置可以由多個),循環緩沖區的讨論超出了本文的範圍,因為它僅僅是臨時緩存進而讓使用者程式可以接受到核心消息.

變量console_loglevel,用于選擇什麼消息足夠重要,值得發送到系統控制台,預設的”重要程度級别”是DEFAULT_CONSOLE_LOGLEVEL,系統管理者可以通過向/proc/sys/kernel/printk這個檔案寫值來修改預設的級别.舉例來說,使用8這個值可以把所有低于debug級别的消息都發送到控制台上,使用指令"echo8 > /proc/sys/kernel/printk"就可以達到這個效果.其他優先級的值(0-7)使用宏KERN_ERROR和KERN_DEBUG等定義在linux/kernel.h

如上所述,如果目前的消息優先級低于console_loglevel,那麼所有的消息會發送的所有的控制台裝置.發送消息的具體代碼會掃面一個控制台裝置的連結清單,縮減版的示意代碼如下(list 1)

if(msg_level < console_loglevel && console_drivers) {

struct console *c = console_drivers;                  while(c) {                  if ((c->flags & CON_ENABLED) && c->write)                  c->write(c, msg, p - msg + line_feed);                  c = c->next;                  }              }           

在通常的linux配置中,隻包含一個控制台裝置,并且對應一個虛拟終端.相關代碼在drivers/char/console.c,對應的,使用vt_onsole_print函數來列印消息(console裝置的列印函數),檢視這個函數的實作我們可以看到,核心消息并不是一定發送到前台的活動的虛拟終端(tty0(表示目前活動的虛拟終端)),如果變量kmsg_redirect不是0(而是i),那麼核心消息就會發送到第i個虛拟終端上.

這個kmsg_redirect變量可以通過在使用者空間使用ioctl(TIOCLINUX)操作一個關聯到對應的虛拟控制台的檔案描述符來改變.

這裡想規範幾個概念:

控制台:核心和外界互動的唯一出口,是一個接口,隻有一個控制台

控制台裝置:邏輯裝置,可以有多個,用序列槽作為控制台的實體映射(具體實作),那麼序列槽就是控制台裝置,需要相關的注冊函數,把本身注冊為控制台裝置

虛拟終端:LCD相關ALT+Fn 1-6 是字元虛拟終端7是圖形界面虛拟終端,都是虛拟的,因為終端隻有一種,序列槽叫做終端!!!!

因為很多電腦不用序列槽連出終端來,而是使用LCD+鍵盤,是以叫做虛拟(用LCD虛拟)的終端.

還有個shell,還沒弄明白

序列槽,終端:實體裝置,用線連出來.

為了達到這個目的,我使用了一個名為setconsole簡單的工具,源碼在這篇文章的例子程式中,通過使用”setconsole1”指令,你可以強制使所有的核心消息列印到1号虛拟終端上,而不是列印到你所使用的那個虛拟終端上(預設情況下,是列印目前使用的虛拟終端???tty0??)

如果你在使用一台配備了序列槽的”标準”PC,那麼你可以通過傳遞”console=”指令行選項到核心的方式,選擇一個序列槽作為控制台(裝置).核心源碼的一部分—檔案Documentation/serial-console.txt非常清晰的描述了總體的設計,以及你可能需要了解的細節,是以我這裡不再重複描述.需要注意的一點是,同一時刻可能有多個控制台裝置同時存在(比如說,一個序列槽控制台(裝置),和一個虛拟終端控制台(裝置)),從上面的代碼清單list1可以看出這點.

3,聲明和選擇一個控制台(裝置)

為了聲明(注冊)一個新的控制台裝置,你的核心代碼需要包含頭檔案<linux/console.h>,頭檔案定義了structconsole和這個結構體裡面使用的一些标志.

一旦注冊了一個structconsole(就是說一旦你配置好了了一個console結構體),你的代碼就可以簡單的調用register_console把本裝置添加到已有的控制台裝置清單中,被注冊的結構體(也就是console)包含下面幾個成員:

.name :控制台裝置的名稱,用于分析指令行中的”console=”參數

.write() :用于列印核心消息的函數

.wait_key() :這個函數可能在系統啟動啟動期間被調用,強制等待直到使用者按下一個鍵

.device() :這個函數傳回目前正在用做控制台裝置的tty裝置的裝置号(如果表示tty社别呢??)

.unblank() :這個函數(如果被定義了的話),用于開啟螢幕(麻意思?)

.setup() :這個函數,是當指令行參數”console =” 和本控制台裝置比對的時候,調用

.flags :各種各樣的console标志

.index :在控制台裝置數組(清單)中的索引值

代碼list2展示了pc的序列槽的裝置驅動(drivers/char/serial.c)注冊并申請了序列槽控制台(控制台裝置):

staticstruct console sercons = {

name:           "ttyS",                  write:          serial_console_write,                  device:         serial_console_device,                  wait_key:       serial_console_wait_key,                  setup:          serial_console_setup,                  flags:          CON_PRINTBUFFER,                  index:          -1,              };              void __init serial_console_init(void)              {                  register_console(&sercons);              }           

代碼list2a展示了一個pc的并行口(drivers/char/lp.c)聲明和注冊并行控制台裝置.我們可以看到,注冊一個并行控制台裝置有一點點複雜,因為核心會首先檢查一下連接配接到電腦上的行式列印機是否是第一個(0号),如果是,就拒絕使用他作為控制台裝置.

staticstruct console lpcons = {

name:           "lp",                  write:          lp_console_write,                  device:         lp_console_device,                  flags:          CON_PRINTBUFFER,              };              static int lp_register(int nr, struct parport *port)              {              [...]              #ifdef CONFIG_LP_CONSOLE                  if (!nr) {                  if (port->modes & PARPORT_MODE_SAFEININT) {                  register_console (&lpcons);                  console_registered = port;                  printk (KERN_INFO "lp%d: console ready\n", CONSOLE_LP);                  } else                  printk (KERN_ERR "lp%d: cannot run console on %s\n",                  CONSOLE_LP, port->name);                  }              #endif                  return 0;              }           

比較有意思的一點是console.h頭檔案和frame-buffer-consoles有關聯,這部分功能(基于structconsw 結構體)超出了本章節的範圍,它處理的是把虛拟字元終端映射到不同的顯示硬體上去.

4,寫一個控制台裝置驅動

為了更好的了解linux下組成系統控制台的機制,我們看一下一個控制台裝置驅動的實作:使用UDP發送核心消息到網絡上.

在我看來,把對串行控制台的讨論徹底離開序列槽通信的具體過程,能幫助更好的了解”控制台”這個抽象概念.

序列槽裝置作為控制台裝置的相關代碼都包含在drivers/char/serial.c

本章節并沒有涵蓋UDP控制台的所有細節,因為這将讓這個研究太龐大而讓人失去興趣.IngoMolnar的網絡控制台講解了一個完整的UDP控制台.http://people.redhat.com/mingo/netconsole-patches/

後面的就不翻譯了...Myskeletal UDP console, which could be a good starting point to graspthe whole mechanism and will be discussed throu