1. 程式人生 > >printk 工作原理

printk 工作原理

========================================================
v0.1 3.4.2009 by arethe   Email: [email protected]
========================================================
    printk()的引數個數是可變的,linux核心中提供了va_arg機制。該機制主要通過3個巨集來實現:
    va_arg(ap, T):獲取ap中的一個引數,該引數的型別是T,然後ap自加sizeof(T),跳過剛獲取的引數。
    va_end(ap):該巨集定義為空。
    va_start(ap, A):通過A獲取引數列表的地址,A是printk的第一個引數(fmt)。一個函式的引數,是按從右到左的順序逐個入棧的,因此通過A的地址加上A的大小(通常為4Bytes),就可以獲得從第二個引數開始的引數列表的首地址。
    printk()函式首先使用va_start(args, fmt),將第二個引數的地址存入args變數。然後呼叫vprintk(fmt, args),vprintk完成具體的輸出任務,返回值為輸出的字元的個數。
    下面我們詳細討論vprintk的實現。
    printk機制中有兩個主要的快取:一個是printk_buf,一個是log_buf。前者用來儲存將要輸出的字串,fmt+報錯資訊(如果有的話)。後者用來儲存最終要輸出的字串,是整個printk機制的核心。log_buf是一個環形陣列,預設大小為128K。配合這個緩衝使用的有3個變數:
    static unsigned log_start;      /* Index into log_buf: next char to be read by syslog() */
    static unsigned con_start;      /* Index into log_buf: next char to be sent to consoles */
    static unsigned log_end;        /* Index into log_buf: most-recently-written-char + 1 */
    在用到這個緩衝時,我們再詳細的介紹。
    vprintk首先會呼叫函式boot_delay_msec(),進行忙等待一段時間。這段時間的大小是由核心啟動引數boot_delay指定的。boot_delay的單位是毫秒。不知道這裡為什麼要等待。
    然後呼叫preempt_disable()禁止搶佔機制。接下來是關中斷,獲取當前CPU的編號。
    如果在某個CPU上正在執行printk時,突然崩潰掉,........
    vscnprintf()函式,將輸出的字串按fmt中的格式編排好,放入printk_buf中,並返回應該輸出的字元的個數。為了保證完整性,我們還是來談談vscnprintf()函式的實現吧。
    vscnprintf()呼叫的是vsnprintf(buf,size,fmt,args)。該函式進行主要的格式化操作。其首先判斷size是否小於0,若小於0,則給出一個警告,並返回0。然後對格式化字串fmt進行遍歷,如果fmt當前的字元不是"%",直接將其考入buf中,若是"%",則後面的處理要複雜一點。相信讀者對printf和printk的使用方法都很熟悉,"%"後面一般會跟一個標誌符,這種標誌符共有5個,'-','+','#','SPACE','0'。標誌符'-'表示後面的字元靠左輸出,比如printk("%-10c",'a'),會先輸出'a',再輸出9個空格。'#'標誌的作用是當後面輸出16進位制的資料時,會自動在資料前加上"0x",比如printk("%#x",10),會輸出"0xa"。'+'標誌的作用是在輸出的數字前自動加上一個"+",比如printk("%+d/n",10),輸出結果為:"+10"。這裡會根據不同的標誌符對一個標誌變數"flags"進行置位。各標誌符對應的bit如下:
    #define ZEROPAD 1               /* pad with zero -- '0'*/
    #define SIGN    2               /* unsigned/signed long --  */
    #define PLUS    4               /* show plus -- '+' */
    #define SPACE   8               /* space if plus -- ' ' */
    #define LEFT    16              /* left justified -- '-' */
    #define SMALL   32              /* Must be 32 == 0x20 */
    #define SPECIAL 64              /* 0x -- '#' */
    在標誌符的後面通常是輸出寬度,這是一個數字,vsnprintf()定義了一個變數來獲取這個值,首先,我們需要判斷緊接著標誌符後面的是不是數字,如果是則通過skip_atoi()將該數字字串轉化成數字。skip_atoi()的實現很簡潔:
    static int skip_atoi(const char **s)
    {//change the string "s" to digit
            int i=0;
 
            while (isdigit(**s))
                    i = i*10 + *((*s)++) - '0';
            return i;
    }
    對於核心中的這類函式最好能記熟一點,在用的時候就不用再費時間自己實現了。在這裡有一點需要強調的是"%*",至少我以前並不知道"%*"的含義,"*"對應後面的一個引數,這個引數指定輸出的寬度。比如:printk("%*c",10,'a');會先輸出9個空格再輸出字元'a'。你甚至可以給一個負值,表示靠左輸出。獲取的輸出寬度儲存在變數field_width中。
    接下來處理的是輸出精度,在格式化輸出浮點數時,我們經常採用".num"的形式來指定小數點後輸出幾位有效數字。在處理的時候,首先判度當前字元是不是'.',如果是,那麼讀入後面緊跟著的數字,儲存在變數precision中。這裡也可以使用".*"的形式,將精度放到後面的引數中指定。
    接著是讀取限定符,如果有的話。什麼是限定符呢? 如果我們想輸出一個長整數,會用到"%ld",這裡的"l"便是限定符,用於輔助說明輸出資料的具體型別。printf或printk中用的限定符有:"h,l,L,Z,z,t"。另外"ll"相當於"L"。大家應該能猜到下一步應該做什麼了,沒錯,判斷輸出資料的型別。可能的型別有"c,s,p,n,%,o,X,x,d,i,u",這裡的實現都很簡單,如果大家有興趣,可以自己去看原始碼。
    有一個問題到現在一直沒有說明,就是如何從引數列表中獲得想要的引數。其實在文章剛開始的時候提到了一點,就是va_arg()巨集,該巨集每次根據指定的型別讀取一個引數,然後將引數列表的開始位置自動向前移一個。這樣,我們在分析"fmt"的格式的同時也就把對應的引數放到了輸出字串(printk_buf)中合適的位置上。
    分析完"fmt"後,函式vsnprintf()返回應該輸出的字元個數。
    執行流有返回到了函式vprintk()中,我們接著來看。下面是一個for迴圈,用於將printk_buf中的輸出字串拷貝到日誌快取log_buf中。首先需要判度輸出的級別,我們知道在printk中,可以指定8個輸出級別,通過printk("<x>...")來指定。這裡的輸出級別被儲存到變數current_log_level中。
    下面用到了一個函式emit_log_char():
    static void emit_log_char(char c)
    {// write c into log. the log buf is ring queue, the defaut is 128K.
            LOG_BUF(log_end) = c;
            log_end++;
            if (log_end - log_start > log_buf_len)
                    log_start = log_end - log_buf_len;
            if (log_end - con_start > log_buf_len)
                    con_start = log_end - log_buf_len;
              if (logged_chars < log_buf_len)
                logged_chars++;
    }
    這個函式的作用是把字元c寫道日誌快取log_buf中,並更新log_start,log_end,con_start的值。
    接下來有個需要解釋一下的問題,就是printk_time。我們在啟動核心的時候可以通過指定一個核心啟動引數"time",來使所有printk出來的資料前加入當前時間。這個時間是從系統啟動到這個printk時所逝去的時間。
                        if (printk_time) {
                                /* Follow the token with the time */
                                char tbuf[50], *tp;
                                unsigned tlen;
                                unsigned long long t;
                                unsigned long nanosec_rem;
 
                                t = cpu_clock(printk_cpu);//the unit of t is
nanosecond
                                nanosec_rem = do_div(t, 1000000000);//Now, the
unit of t is second, the unit of nanosec_rem is nanosecond.
                                tlen = sprintf(tbuf, "[%5lu.%06lu] ",
                                                (unsigned long) t,
                                                nanosec_rem / 1000);
 
                                for (tp = tbuf; tp < tbuf + tlen; tp++)
                                        emit_log_char(*tp);
                                printed_len += tlen;
                        }
    函式cpu_clock()返回從系統啟動到當前的納秒值。do_div(a,b)是一個巨集,它計算a/b,將商放在a中,返回餘數。那麼tbuf中的資料便是"[second.nanosecond]"形式的。有興趣的話,大家可以在核心啟動時加入time引數試一試,看看會有什麼效果。
    在for迴圈結束後,printk_buf中的資料便按照新的輸出格式(比如在每一行前面加入print_time)copy到了log_buf中。
    下面便是具體的輸出工作了,在輸出之前,需要先獲取訊號量console_sem,這裡獲取的方式採用的是down_trylock(&console_sem)。這個函式在沒有獲取訊號量時不會睡眠,而是立即返回一個非0值。另外,我們還需要釋放鎖console_locked和logbuf_lock。這些工作在函式acquire_console_semaphore_for_printk(this_cpu)中完成,當該函式成功返回時,會呼叫函式release_console_sem()進行顯示工作。這個函式會再呼叫call_console_drivers(_con_start, _log_end)將_con_start,_log_end之間的log_buf中的內容。函式call_console_drivers()首先判斷輸出級別,然後根據輸出內容,按行呼叫函式_call_console_drivers(start_print, cur_index, msg_level),該函式的作用是處理環形佇列,將正確的內容作為引數呼叫函式__call_console_drivers(start, end),該將輸出內容傳送給console的驅動。在核心中,所有的console都被鏈入了一個連結串列--console_drivers,在這裡,我們要遍歷這個連結串列,如果某個console允許輸出的話,就呼叫它的write()方法。
/*
 * Call the console drivers on a range of log_buf
 */
static void __call_console_drivers(unsigned start, unsigned end)
{
        struct console *con;
 
        for (con = console_drivers; con; con = con->next) {//All consoles belongs to a list -- console_drivers.
                if ((con->flags & CON_ENABLED) && con->write &&
                                (cpu_online(smp_processor_id()) ||
                                (con->flags & CON_ANYTIME)))
                        con->write(con, &LOG_BUF(start), end - start);
        }
}
    到此為止,整個printk的過程就結束了。哦,不!還有一個工作沒有做,就是將輸出內容同時寫入日誌。這個工作是由核心中的守護程序klogd來完成的。在函式release_console_sem(void)的最後,會判斷是否需要喚醒klogd,然後呼叫wake_up_klogd()來喚醒守護程序。那麼什麼時候需要喚醒klogd呢?只要log_buf中有未輸出的資訊,便需喚醒klogd將其寫入日誌檔案。
    OK!現在是真的結束了。如果有什麼問題,可以跟我聯絡,共同討論。

相關推薦

printk 工作原理

======================================================== v0.1 3.4.2009 by arethe   Email: [email protected] =========================

linux printk工作原理

記得在編譯linux核心make menuconfig的時候設定輸出資訊到console,要修改CONFIG_CMDLINE的內容,但是自始至終也沒搞懂為何這樣設定就可以把列印資訊從串列埠輸出呢? 帶著這個疑問,我查看了linux的printk函式,最後找到了答案. 一 printk 函式printk函式首

VMware快照的工作原理

所有 整合 100g 性能 不變 小時 此外 建立 console VMware中的快照是對VMDK在某個時間點的“拷貝”,這個“拷貝”並不是對VMDK文件的復制,而是保持磁盤文件和系統內存在該時間點的狀態,以便在出現故障後虛擬機能夠恢復到該時間點。如果對某個虛擬機創建了多

Vue工作原理小結

key 如何實現 reference 讀寫 owa 方法 cli scrip 枚舉 本文能幫你做什麽?1、了解vue的雙向數據綁定原理以及核心代碼模塊2、緩解好奇心的同時了解如何實現雙向綁定為了便於說明原理與實現,本文相關代碼主要摘自vue源碼, 並進行了簡化改造,相對較

angularjs工作原理解析

body oot 分隔 復制 抖動 修改 重新 接收 裏的 個人覺得,要很好的理解AngularJS的運行機制,才能盡可能避免掉到坑裏面去。在這篇文章中,我將根據網上的資料和自己的理解對AngularJS的在啟動後,每一步都做了些什麽,做一個比較清楚詳細的解析。 首

shell編程培訓之shell的工作原理

shell編程培訓Shell是用戶和Linux操作系統之間的接口。Linux中有多種shell,其間缺省運用的是Bash。本章敘述了shell的作業原理,shell的品種,shell的一般操作及Bash的特性。什麽是shellLinux系統的shell作為操作系統的外殼,為用戶提供使用操作系統的接口。它是命令

session rsyns 的工作原理

session rsyns 的工作原理session的工作原理 1.session實現與工作原理瀏覽器和服務器采用http無狀態的通訊,為了保持客戶端的狀態,使用session來達到這個目的。然而服務端是怎麽樣標示不同的客戶端或用戶呢?這裏我們可以使用生活中的一個例子,假如你參加一個晚會,認識了很多人,你會采

CGI的工作原理

設置 mark 之間 com 環境變量 沒有 mar 輸出 表單 CGI是Webserver和外部程序之間的一個接口。利用CGI程序能夠處理從Web上client發送出來的表單和數據。並對此做出相關操作。這樣的反應能夠是HTML文件、圖片、聲音、視頻等能夠在瀏覽器窗口

struts2工作原理

struts archive logs tro images src str hive www 摘取於:http://www.cnblogs.com/jy02444453/archive/2011/08/27/2155427.html struts2工作原理

USB Type-C工作原理解析

說明 是否 forms dfp 其他 耗時 def 左右 del 自從蘋果發布了新MacBook,USB Type-C接口就成為了熱議對象。我來從硬件角度解析下這個USB Type-C,以便大家更好的了解USB Type-C的工作原理。特色尺寸小,支持正反插,速度快(10G

session 的工作原理

銷毀 這樣的 機制 過期 登陸 處理 art 客戶端瀏覽器 生成 一直在使用session存儲數據,一直沒有好好總結一下session的使用方式以及其工作原理,今天在這裏做一下梳理。這裏的介紹主要是基於php語言,其他的語言操作可能會有差別,但基本的原理不變。 1.在p

SpringMVC的工作原理

free 視圖渲染 jstl resp mapping div 更多 con lib 一、spring簡介     springMVC是spring框架的一個模塊,springMVC和spring無需通過中間整合層進行開發。 springMVC是一個基於mvc的

Nginx 模塊的工作原理

nginx 模塊的工作原理Nginx 模塊的工作原理 Handlers :(處理器模塊)此模塊直接處理請求 並且進行內容傳輸以及修改headres信息等操作。Handlers模塊只能處理一個。Filters(處理器模塊):此類模塊只需要對其他服務器模塊輸出的內容進行修改操做,最後又Nginx操做proxies

路由器工作原理

htm 一個 aid 提高 通信 最好 原理 有一種 clas 主機A和主機B所在的網段被許多路由器隔開,這是主機A與主機B的通信就要進過這些中間路由器,這就要面臨一個很重要的問題,如何選擇到達目的地的路徑。包從A到達B有很多條路徑可供選擇,但是很顯然,在這些路徑中在某一

linux設備驅動之platform平臺總線工作原理(三)

linux設備和驅動設備為數據,驅動為加工著1、以led-s3c24xx.c為例來分析platform設備和驅動的註冊過程其中關於led的驅動數據結構為:static struct platform_driver s3c24xx_led_driver = { .probe = s3c24xx_led_pr

zabbix簡介與工作原理

zabbix簡介與工作原理註;如有雷同純屬巧合。1.zabbix簡介zabbix(音同 zbix)是一個基於WEB界面的提供分布式系統監視以及網絡監視功能的企業級的開源解決方案zabbix能監視各種網絡參數,保證服務器系統的安全運營;並提供靈活的通知機制以讓系統管理員快速定位/解決存在的各種問題。zabbix

深度解析線程工作原理

路徑 cep dead test deadlock end priority interrupt prior   1, 線程的概念     一個程序中的方法有幾條執行路徑, 就有幾個線程   2, 線程的創建     兩種方式:       1, 繼承Thread

Android 基於Netty的消息推送方案之概念和工作原理(二)

img b2c 決定 watermark net nios 通道 感覺 art 上一篇文章中我講述了關於消息推送的方案以及一個基於Netty實現的一個簡單的Hello World。為了更好的理解Hello World中的代碼,今天我來解說一下關於Netty中一些概念和工

spring工作原理

模塊 反射機制 spa oca .class 關聯 sso 三種方式 += 對spring原理以前寫過類似的博客,地址:點擊打開鏈接。 但是經過一些時間後,盡管天天用著spring,但一提到原理方面,就遺忘了呢?就記得AOP和IOC,

希捷操作系統SeaOS工作原理

希捷操作系統seaos工作原理以希捷操作系統SeaOS為例:我們拆開硬盤的電路板,能看到CPU ,Flash ROM , RAM ,這三大跟電腦和手機相似的結構Flash ROM 和 固件區(硬盤碟片上的固件使用區域) Flash ROM 和固件區都是SeaOS系統用來存放的空間,就像我們電腦的C盤