Syslog
1 內核日誌基本框架
內核日誌是通過printk函數實現的,它與用戶空間對應的函數printf具有同樣的作用。
內核創建一個名爲_log_buf的Ring Buffer(環型緩衝區)來保存內核中打印的內核日誌信息。
而用戶態可以通過Syslog相關的系統調用或者/proc文件以及/dev/kmsg設備節點來查看_log_buf的信息,這些操作都是通過do_syslog的系統調用接口來實現的。
內核日誌框架
2 Printk
在內核中,printk可以使用和prinf一樣的格式將格式化消息輸入到緩衝區。
Printk的格式如下:
int printk( const char * fmt, ... );
其中,fmt是一個定義文本和格式的字符串,其後面的參數列表是輸出格式中需要輸入的可變個數參數。
通過 printk 實現的日誌是通過內核配置選項CONFIG_PRINTK激活的。雖然 CONFIG_PRINTK一般都是激活的,但是不包含這個選項的系統對內核的調用會返回一個ENOSYS 錯誤返回值。
2.1 日誌級別
內核允許每一個消息根據日誌級別(定義不同消息重要必的八種級別之一)來分類。這些級別可以用來判斷系統是否不可用(緊急消息)、是否發現嚴重狀況(嚴重消息)或者是否爲簡單報告消息。這個內核代碼直接將日誌級別定義消息的第一個參數。
例如下面的代碼:
printk( KERN_CRIT "Error code %08x.\n", val );
KERN_CRIT只是一個普通的字符串,作爲預處理程序的一部分,C會自動地使用一個名爲 字符串串聯的功能將這兩個字符串組合在一起。組合的結果是將日誌級別和用戶指定的格式字符串包含在一個字符串中。如果調用者未將日誌級別提供給printk,那麼系統就會使用默認值KERN_WARNING(表示只有KERN_WARNING 級別以上的日誌消息會被記錄。)
內核的日誌級別包括:
標識符 | 字符串 | 使用方法 |
KERN_EMERG | <0> | 緊急消息(導致系統崩潰) |
KERN_ALERT | <1> | 必須立即處理的錯誤 |
KERN_CRIT | <2> | 嚴重錯誤(硬件或軟件) |
KERN_ERR | <3> | 錯誤狀況(一般出現在驅動程序上) |
KERN_WARNING | <4> | 警告狀況(可能導致錯誤) |
KERN_NOTICE | <5> | 不是錯誤,但是一個重要狀況 |
KERN_INFO | <6> | 報告消息 |
KERN_DEBUG | <7> | 僅用於調試的消息 |
KERN_DEFAULT | <d> | 默認內核日誌級別 |
KERN_CONT | <c> | 日誌行繼續(避免增加新的時間截) |
2.2 Printk的實現
2.2.1 日誌緩衝區_log_buf
內核中,printk將日誌信息打印到環型緩衝區(Ring Buffer)_log_buf中。
環形緩衝區__log_buf在使用之前就是已定義好的全局變量,緩衝區的長度爲1 <<CONFIG_LOG_ BUF_SHIFT。變量CONFIG_LOG_BUF_SHIFT在內核編譯時由配置文件定義,一般爲18。在內核編譯時,編譯器根據配置文件的設置,產生如下的宏定義:
#define CONFIG_LOG_BUF_SHIFT 18
環型緩衝區的定義如下:
2.2.2 主要數據結構
1、Cont:
Cont結構用來保存多行需要打印在一起的信息的,使這些信息不會因爲外部的進程或者其他因素致使這些信息不能連續打印。
這裏的len字段表示當前cont中的已有信息的長度,如果len==0表示沒有要連續打印的多行數據。
Owner字段表示cont是屬於哪個進程的,如果進程不同,那麼多行打印的數據不能同時保存在cont中。
Ts_nsec是第一次加入的日誌信息的時間戳,level是級別,flushed表示已經疏導log_buf中。
2、log
保存當前這條日誌的頭信息,這些頭信息是內核的管理數據,並不會通過print打印到用戶態的終端上,而是會通過/dev/kmsg輸出。
3、log_flags
這四個flag用來控制日誌打印的方式。
4、buffer控制字段
分別記錄log_buf的頭尾的計數和索引。
2.2.3 Printk的處理流程
主要操作步驟爲:
1) va_start獲取參數。以下的操作通過vprintk_emit函數實現。
2) 調用函數boot_delay_msec()忙等待一段時間,這段時間的大小時由內核啓動參數boot_delay指定的;
3) 獲取當前CPU的編號;
4) vscnprintf()函數,將輸出的字符串按fmt中的格式編排好,放入text中,並返回應該輸出的字符的個數;
5) 如果日誌信息的最後一個字符是\n,表示該日誌之後要新起一行,將lflags加上LOG_NEWLINE標識;
6) 從text中取出輸入的日誌的級別level;
7) 如果lflags沒有LOG_NEWLINE標誌,如果cont中有信息(前一次),並且新的信息有前綴或者新的信息是另外的進程打印的,這是需要將cont中的信息寫到log_buf中。否則,將需要打印的新信息加入到cont中,如果添加失敗(主要由於cont的可用大小不夠),直接調用log_store將text信息添加到log_buf中;
8) Lflags有LOG_NEWLINE標誌,如果cont中有前一次的信息,這個信息也是屬於當前進程的日誌,如果新加入的信息沒有前綴,則直接將信息加到cont中,然後再將cont刷到緩衝區。如果信息沒有被加入到cont中,那麼直接調用log_store將text信息添加到log_buf中
9) 獲取終端信號量,打印終端。
2.3 Printk與終端console
Printk首先將要打印的信息放到buffer中,然後調用release_console_sem最後調用到相關驅動的write函數,如果你設定了CONFIG_CMDLINE="console=ttySL0,19200,那麼printk信息就會調用ttySL這個驅動的write函數,也就是從串口輸出數據了。在__call_console_drivers裏面有一個很重要的變量console_drivers,它決定調用哪支driver輸出printk信息。
爲了更好的控制不同級別的日誌顯示在終端上,內核設置了控制檯的日誌級別,console_loglevel。printk日誌級別的作用是打印一定級別的消息,與之類似,控制檯只顯示一定級別的消息。
當日志級別小於console_loglevel時,消息才能顯示出來。
3 Syslog
3.1 Syslog系統調用
Syslog系統調用(在內核中調用 ./linux/kernel/printk.c 的 do_syslog)是一個相對較小的函數,它能夠讀取和控制內核環緩衝區。注意在 glibc 2.0 中,由於詞彙 syslog 使用過於廣泛,這個函數的名稱被修改成 klogctl,它指的是各種調用和應用程序。syslog 和 klogctl(在用戶空間中)的原型函數定義爲:
int syslog( int type, char *bufp,int len );
int klogctl( int type, char *bufp, int len );
type參數用來傳遞所要執行的命令,其主要的參數值包括:
命令/操作代碼 | 作用 |
SYSLOG_ACTION_CLOSE (0) | 關閉日誌(未實現) |
SYSLOG_ACTION_OPEN (1) | 打開日誌(未實現) |
SYSLOG_ACTION_READ (2) | 從日誌讀取 |
SYSLOG_ACTION_READ_ALL (3) | 從日誌讀取所有消息(非破壞地) |
SYSLOG_ACTION_READ_CLEAR (4) | 從日誌讀取並清除所有消息 |
SYSLOG_ACTION_CLEAR (5) | 清除環緩衝區 |
SYSLOG_ACTION_CONSOLE_OFF (6) | Disable printks to the console |
SYSLOG_ACTION_CONSOLE_ON (7) | 激活控制檯printk |
SYSLOG_ACTION_CONSOLE_LEVEL (8) | 將消息級別設置爲控制接受 |
SYSLOG_ACTION_SIZE_UNREAD (9) | 返回日誌中未讀取的字符數 |
SYSLOG_ACTION_SIZE_BUFFER (10) | 返回內核環緩衝區大小 |
3.2 do_syslog
3.2.1 syslog操作
SYSLOG_ACTION_READ
1、 如果log_buf中的數據都已經讀完,則阻塞進程,將進程放入log_wait等待隊列中;
2、 調用syslog_print打印局部信息。
SYSLOG_ACTION_READ_ALL
調用syslog_print_all打印全部信息。
SYSLOG_ACTION_CLEAR
調用syslog_print_all函數清空數據。
3.2.2 syslog_print
根據參數,向相應的buf中寫入要輸出的局部日誌信息。
3.2.3 syslog_print_all
1、 根據參數,向相應的buf中寫入要輸出全部日誌信息。
2、 參數clear爲ture,需要將clear的索引和計數指向log_buf的尾部。
4 /proc/kmsg
1、/proc/kmsg初始化proc_kmsg_init中通過proc文件系統文件創建的接口proc_create創建proc文件類型,並註冊對應的file_operations的proc_kmsg_operations。
2、proc_kmsg_operations的定義如下:
kmsg_open、kmsg_release、kmsg_read、kmsg_poll的實現都是通過給do_syslog的接口傳遞不同的參數來實現的。如下:
5 Syslog隔離方案
5.1 架構圖
- 在容器環境下實現syslog的容器隔離,從而在一個容器中只能看到當前容器中的進程執行所產生的系統日誌信息。
- 容器中的系統日誌用戶態接口和內核態的系統調用API等使用接口使用內核原有的實現。
- 進程上下文的nsproxy信息中添加syslog的命名空間信息,每一個syslog命名空間包含僅屬於自己的日誌緩衝區。
- 當內核通過printk、do_syslog以及/dev處理系統日誌時,通過當前進程上下文的nsproxy信息獲取該容器中進程所屬的syslog命名空間,從而對屬於這個命名空間的系統日誌緩衝區讀寫等操作。
5.2 隔離數據
1、Log_buf
內核實現syslog的主要數據結構就是log_buf這個Ring Buffer,因此要實現syslog的隔離,主要要實現包含一個log_buf的命名空間。這個命名空間包含log_buf的控制參數。
對於log_buf的控制參數包括:
2、console
向終端寫入數據時通過console_seq、console_idx控制在緩衝區log_buf的索引和計數,針對隔離的log_buf需要有對應的控制標識符。
3、syslog/kmsg
通過syslog(/proc/kmsg)讀log_buf時,同樣由syslog_idx、syslog_seq、syslog_prev、syslog_partial這樣的參數來控制,因此對於隔離的log_buf,這些參數也要隔離。
4、/dev/kmsg
對設備節點的操作,通過直接操作log_buf來獲取日誌信息,實現kmsg_fops中的devkmsg_open、devkmsg_read、 devkmsg_writev、devkmsg_llseek、
devkmsg_poll、devkmsg_release不需要隔離相關控制字段。
5、log_wait
通過/dev/kmsg、syslog或者/proc/kmsg從log_buf中獲取日誌時,內核通過建立一個等待隊列來喚醒因爲暫時沒有需要讀出日誌信息而阻塞的進程。這個結構同log_buf強相關,針對每一個命名空間中的log_buf,對應要有一個這個隊列。
以上是syslog隔離過程中需要隔離的主要數據和字段。在操作這些數據和字段值時,都要從對應的命名空間中獲取相應數據。