2-Linux print system

題圖:nipic

Linux print system

linux中的調(diào)試方法有很多種涩澡,但我們最常用的也是最關(guān)鍵的調(diào)試工具應(yīng)該就是使用print函數(shù)與console進(jìn)行信息交互隐砸。在內(nèi)核驅(qū)動(dòng)的調(diào)試過(guò)程我們一般會(huì)使用內(nèi)核提供的printk函數(shù)來(lái)進(jìn)行調(diào)試寂嘉,在應(yīng)用層時(shí)我們會(huì)使用標(biāo)準(zhǔn)的c庫(kù)函數(shù)printf,在shell腳本中我們會(huì)使用echo/logger等工具,接下來(lái)就讓我們逐步了解這些工具吧虏肾。

1.內(nèi)核printk函數(shù)工作原理


printk函數(shù)是內(nèi)核提供的格式化打印函數(shù)冠蒋,它幾乎可以在內(nèi)核的任意時(shí)刻使用(中斷上下文羽圃、進(jìn)程上下文、持有鎖時(shí)抖剿、多處理器處理時(shí))朽寞。但也有一些地方不能使用printk函數(shù)進(jìn)行打印,如系統(tǒng)啟動(dòng)過(guò)程中和console_init之前斩郎,就不能直接調(diào)用printk脑融,如果確實(shí)需要調(diào)試這段代碼,內(nèi)核提供了一個(gè)新的接口函數(shù)early_printk缩宜,需要我們?cè)?config中將其打開(kāi)肘迎,具體實(shí)現(xiàn)方式自行分析下,這邊對(duì)printk函數(shù)進(jìn)行具體分析锻煌。

printk的實(shí)現(xiàn)函數(shù)位于 kernel/printk.c中(較新版本的內(nèi)核位于kernel/printk/printk.c中妓布,原理一樣),如下:

asmlinkage int printk(const char *fmt, ...)
{
    va_list args;
    int r;
    va_start(args, fmt);
    r = vprintk(fmt, args);
    va_end(args);
    return r;
}

va_startva_end是在讀取可變參數(shù)中常用的函數(shù)宋梧,主要就是將*fmt中的數(shù)據(jù)保存到args中匣沼,接著看vprintk(fmt, args)函數(shù),如下:

asmlinkage int vprintk(const char *fmt, va_list args)
{
    int printed_len = 0;
    int current_log_level = default_message_loglevel;
    unsigned long flags;
    int this_cpu;
    char *p;

    boot_delay_msec();
    printk_delay();

    /*關(guān)閉內(nèi)核搶占*/
    preempt_disable();
    /*保存中斷信息捂龄,獲取cpu id*/
    raw_local_irq_save(flags);
    this_cpu = smp_processor_id();

    /*
     * Ouch, printk recursed into itself!
     */
     /*CPU crash時(shí)的保護(hù)處理機(jī)制*/
    if (unlikely(printk_cpu == this_cpu)) {
        /*
         * If a crash is occurring during printk() on this CPU,
         * then try to get the crash message out but make sure
         * we can't deadlock. Otherwise just return to avoid the
         * recursion and return - but flag the recursion so that
         * it can be printed at the next appropriate moment:
         */
        if (!oops_in_progress) {
            recursion_bug = 1;
            goto out_restore_irqs;
        }
        zap_locks();
    }

    lockdep_off();
    spin_lock(&logbuf_lock);
    printk_cpu = this_cpu;

    if (recursion_bug) {
        recursion_bug = 0;
        strcpy(printk_buf, recursion_bug_msg);
        printed_len = strlen(recursion_bug_msg);
    }
    /* Emit the output into the temporary buffer */
    /*將要輸出的字符串放入printk_buf中释涛,并返回字符長(zhǎng)度*/
    printed_len += vscnprintf(printk_buf + printed_len,
                  sizeof(printk_buf) - printed_len, fmt, args);


    p = printk_buf;

    /* Do we have a loglevel in the string? */
    /*如果調(diào)用者沒(méi)有提供合適的日志級(jí)別加叁,則插入默認(rèn)級(jí)別*/
    if (p[0] == '<') {
        unsigned char c = p[1];
        if (c && p[2] == '>') {
            switch (c) {
            case '0' ... '7': /* loglevel */
                current_log_level = c - '0';
            /* Fallthrough - make sure we're on a new line */
            case 'd': /* KERN_DEFAULT */
                if (!new_text_line) {
                    emit_log_char('\n');
                    new_text_line = 1;
                }
            /* Fallthrough - skip the loglevel */
            case 'c': /* KERN_CONT */
                p += 3;
                break;
            }
        }
    }

    /*
     * Copy the output into log_buf.  If the caller didn't provide
     * appropriate log level tags, we insert them here
     */
     
    /*拷貝printk_buf數(shù)據(jù)到環(huán)形緩沖區(qū)中,拷貝過(guò)程由函數(shù)emit_log_char實(shí)現(xiàn)唇撬,每次拷貝一個(gè)字節(jié)*/
    for ( ; *p; p++) {
        if (new_text_line) {
            /* Always output the token */
            emit_log_char('<');
            emit_log_char(current_log_level + '0');
            emit_log_char('>');
            printed_len += 3;
            new_text_line = 0;

            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);
                nanosec_rem = do_div(t, 1000000000);
                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;
            }

            if (!*p)
                break;
        }

        emit_log_char(*p);
        if (*p == '\n')
            new_text_line = 1;
    }

    /*
     * Try to acquire and then immediately release the
     * console semaphore. The release will do all the
     * actual magic (print out buffers, wake up klogd,
     * etc). 
     *
     * The acquire_console_semaphore_for_printk() function
     * will release 'logbuf_lock' regardless of whether it
     * actually gets the semaphore or not.
     */
    /*獲取console結(jié)構(gòu)的信號(hào)量*/
    if (acquire_console_semaphore_for_printk(this_cpu))
        /*將信息傳給console驅(qū)動(dòng)*/
        release_console_sem();

    lockdep_on();
out_restore_irqs:
    /*恢復(fù)中斷信息*/
    raw_local_irq_restore(flags);
    /*開(kāi)啟內(nèi)核搶占*/
    preempt_enable();
    return printed_len;
}

vprintk函數(shù)比較長(zhǎng)一點(diǎn)殉农,但其執(zhí)行的流程其實(shí)比較直觀:

  • 關(guān)閉內(nèi)核搶占,保存中斷信息局荚;
  • 將數(shù)據(jù)保存到臨時(shí)緩沖printk_buf中超凳,并將日志級(jí)別信息添加進(jìn)buf,如果沒(méi)定義則以默認(rèn)的級(jí)別添加進(jìn)去耀态,注意printk_buf只有1KB轮傍;
  • 通過(guò)emit_log_char()函數(shù)將printk_buf中的數(shù)據(jù)以一個(gè)一個(gè)char的形式傳給log_buf;
  • 獲取console信號(hào)量首装,然后調(diào)用release_console_sem() 函數(shù)创夜,該函數(shù)主要就是調(diào)用console驅(qū)動(dòng),將buf的內(nèi)容通過(guò)console的write接口傳輸出去仙逻;
  • 最后就是恢復(fù)中斷信息驰吓,打開(kāi)內(nèi)核搶占。

通過(guò)上面的流程我們應(yīng)該已經(jīng)了解printk函數(shù)的實(shí)現(xiàn)系奉,不過(guò)還是有幾個(gè)細(xì)節(jié)下面再提及:

1.環(huán)形緩沖區(qū)

環(huán)形緩沖區(qū)的定義#define LOG_BUF(idx) (log_buf[(idx) & LOG_BUF_MASK])檬贰,接著追蹤下去,可以發(fā)現(xiàn)#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) 缺亮,其中CONFIG_LOG_BUF_SHIFT 用來(lái)限制環(huán)形緩沖區(qū)的大小翁涤,默認(rèn)是17,即128KB萌踱,可以在.config中進(jìn)行配置調(diào)整葵礼。

這邊的log_buf(128KB)和上面的printk_buf(1KB)是不一樣的,printk_buf更像是臨時(shí)緩沖區(qū)并鸵,每次調(diào)用printk函數(shù)最大量是1KB鸳粉,當(dāng)重新調(diào)用printk函數(shù)時(shí)會(huì)將上次的清空,寫(xiě)入新的數(shù)據(jù)到printk_buf园担,log_buf是一個(gè)環(huán)形緩沖區(qū)届谈,它的數(shù)據(jù)是printk_buf給它的,但是當(dāng)新的數(shù)據(jù)寫(xiě)入時(shí)粉铐,原本的數(shù)據(jù)不會(huì)被清空疼约,而是一直往后寫(xiě),當(dāng)log_buf寫(xiě)滿128KB時(shí)就環(huán)形寫(xiě)入蝙泼,從頭部開(kāi)始覆蓋寫(xiě)入程剥。

2.emit_log_char()函數(shù)

該函數(shù)實(shí)現(xiàn)的是每次寫(xiě)入一個(gè)字符,即對(duì)于每一個(gè)字符的賦值log_end則只管++,在加一之后進(jìn)行判斷织鲸,如果log_end的值大于log_start舔腾,則表示緩沖區(qū)的長(zhǎng)度已經(jīng)達(dá)到最大,下一次的寫(xiě)入就將覆蓋之前最舊的位置搂擦,因此log_start = log_end - log_buf_len,將log_start的位置向后移一位(因?yàn)槊看沃粚?xiě)入一個(gè)字符稳诚,不可能超過(guò)一位)實(shí)現(xiàn)環(huán)形緩存區(qū),代碼如下:

static void emit_log_char(char c)
{
    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++;
}

3.release_console_sem()函數(shù)

該函數(shù)完成console相關(guān)的操作瀑踢,主要過(guò)程就是將con_startlog_end間的數(shù)據(jù)通過(guò)call_console_drivers()函數(shù)來(lái)完成數(shù)據(jù)往console的傳遞扳还,并且在最后環(huán)形klogd進(jìn)程。

void release_console_sem(void)
{
    unsigned long flags;
    unsigned _con_start, _log_end;
    unsigned wake_klogd = 0;

    if (console_suspended) {
        up(&console_sem);
        return;
    }

    console_may_schedule = 0;

    for ( ; ; ) {
        spin_lock_irqsave(&logbuf_lock, flags);
        wake_klogd |= log_start - log_end;
        if (con_start == log_end)
            break;          /* Nothing to print */
        _con_start = con_start;
        _log_end = log_end;
        con_start = log_end;        /* Flush */
        spin_unlock(&logbuf_lock);
        stop_critical_timings();    /* don't trace print latency */
        call_console_drivers(_con_start, _log_end);
        start_critical_timings();
        local_irq_restore(flags);
    }
    console_locked = 0;
    up(&console_sem);
    spin_unlock_irqrestore(&logbuf_lock, flags);
    if (wake_klogd)
        wake_up_klogd();
}

call_console_drivers()函數(shù)最終會(huì)調(diào)用__call_console_drivers 函數(shù)橱夭,該函數(shù)指向struct console *con氨距; 設(shè)備,最終通過(guò)設(shè)備寫(xiě)函數(shù)棘劣,進(jìn)行寫(xiě)如數(shù)據(jù)俏让,如下:

static void __call_console_drivers(unsigned start, unsigned end)
{
    struct console *con;

    for_each_console(con) {
        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函數(shù)的實(shí)現(xiàn)流程已經(jīng)完成,當(dāng)然中間有很多細(xì)節(jié)之處還不是很了解茬暇,需要進(jìn)一步深入首昔。

2.內(nèi)核printk函數(shù)打印優(yōu)先級(jí)


上面我們提到了printk函數(shù)日志的優(yōu)先級(jí),下面進(jìn)行分析糙俗,在console上我們通過(guò)cat /proc/sys/kernel/printk文件來(lái)查看內(nèi)核模塊打印的優(yōu)先級(jí)

# cat /proc/sys/kernel/printk
7       4       1      7

該文件里面有四個(gè)數(shù)值勒奇,這四個(gè)值是在kernel/printk.c 中被定義的,如下:

int console_printk[4] = {
      DEFAULT_CONSOLE_LOGLEVEL,   /* console_loglevel */
      DEFAULT_MESSAGE_LOGLEVEL,   /* default_message_loglevel */
      MINIMUM_CONSOLE_LOGLEVEL,   /* minimum_console_loglevel */
      DEFAULT_CONSOLE_LOGLEVEL,   /* default_console_loglevel */
};

分別對(duì)應(yīng):

  • 控制臺(tái)日志級(jí)別:優(yōu)先級(jí)高于該值的消息將被打印至控制臺(tái)
  • 默認(rèn)的消息日志級(jí)別:將用該優(yōu)先級(jí)來(lái)打印沒(méi)有優(yōu)先級(jí)的消息
  • 最低的控制臺(tái)日志級(jí)別:控制臺(tái)日志級(jí)別可被設(shè)置的最小值(最高優(yōu)先級(jí))
  • 默認(rèn)的控制臺(tái)日志級(jí)別:控制臺(tái)日志級(jí)別的缺省值

include/linux/printk.h中內(nèi)核一共定義了8個(gè)printk級(jí)別臼节,數(shù)值越大優(yōu)先級(jí)越低撬陵,如下:

#define KERN_EMERG      "<0>"   /* system is unusable                   */
#define KERN_ALERT      "<1>"   /* action must be taken immediately     */
#define KERN_CRIT       "<2>"   /* critical conditions                  */
#define KERN_ERR        "<3>"   /* error conditions                     */
#define KERN_WARNING    "<4>"   /* warning conditions                   */
#define KERN_NOTICE     "<5>"   /* normal but significant condition     */
#define KERN_INFO       "<6>"   /* informational                        */
#define KERN_DEBUG      "<7>"   /* debug-level messages                 */

實(shí)際應(yīng)用時(shí):

  • 我們會(huì)先把DEFAULT_CONSOLE_LOGLEVEL設(shè)置為8,這樣優(yōu)先級(jí)高于8的都會(huì)被打印到console(數(shù)值小于8)网缝;因?yàn)閜rintk的最低級(jí)為7,則代表所有的log都會(huì)print到console上進(jìn)行調(diào)試蟋定,待我們調(diào)試完成后粉臊,將DEFAULT_CONSOLE_LOGLEVEL的值設(shè)置為低于我們要打印的級(jí)別,這樣就不會(huì)有那么多沒(méi)必要的log了驶兜。
  • 可以通過(guò)echo的方式來(lái)直接修改log的優(yōu)先級(jí)扼仲,如echo 4 4 1 7 > /proc/sys/kernel/printk
  • 當(dāng)輸入printk("Hello, world!\n") 沒(méi)有帶任何優(yōu)先級(jí),則會(huì)采用默認(rèn)級(jí)別抄淑,默認(rèn)級(jí)別為4屠凶,和輸入printk(KERN_WARNING"Hello, world!\n")的效果是一致的。
  • Debug信息設(shè)置為KERN_DEBUG肆资,即 printk(KERN_DEBUG"Hello, world!\n") 矗愧,但我們觀察到內(nèi)核中都不會(huì)直接調(diào)用這個(gè)接口,而是重新提供了一個(gè)debug的接口函數(shù)郑原,pr_debug(fmt, ...)唉韭,這個(gè)將在下一部分進(jìn)行講解夜涕。

cat /proc/sys/kernel/printk文件的時(shí)候,發(fā)現(xiàn)該文件下面還有幾個(gè)與printk相關(guān)的文件属愤,如下:

cat /proc/sys/kernel/printk
7       4       1       7
cat /proc/sys/kernel/printk_delay 
0
cat /proc/sys/kernel/printk_ratelimit
5
cat /proc/sys/kernel/printk_ratelimit_burst 
10

這些文件分別表示:

  • printk_delay 中的值表示printk消息之間延遲的毫秒數(shù)女器,用于提高某些場(chǎng)景的可讀寫(xiě),如打印的太快了住诸,我們也不可能從這種狂刷屏幕上讀取到什么有效信息驾胆,所以減慢打印速度。
  • printk_ratelimitprintk_ratelimit_burst 也有點(diǎn)這個(gè)調(diào)慢速度的作用贱呐,如上面的printk_ratelimit = 5,printk_ratelimit_burst = 10俏拱,則表示當(dāng)內(nèi)核中由printk_ratelimit() 函數(shù)限制的打印信息每打印10次就會(huì)停止5s不再向console輸出。

下面是一個(gè)測(cè)試的例子:

for (i = 0 ; i < 1000; i ++){ 
   if(printk_ratelimit()){
       printk(KERN_DEBUG "Test for ratelimte i = %d j = %d\n",i ,++j);
   }
}
printk(KERN_NOTICE "After Test i = %d j = %d\n",i , j);

printk_ratelimit()根據(jù)打印的頻繁程度返回的一個(gè)值決定是否將debug信息打印出來(lái)吼句。整個(gè)處理機(jī)制是锅必,系統(tǒng)根據(jù)printk_ratelimit_burst的值設(shè)置一個(gè)隊(duì)列長(zhǎng)度,如果這個(gè)隊(duì)列滿惕艳,則值printk_ratelimit()為0搞隐,禁止新的消息加入隊(duì)列,等待printk_ratelimit秒設(shè)定的時(shí)間远搪,將 printk_ratelimit()設(shè)為1劣纲,即允許新的消息加入隊(duì)列,進(jìn)行再次打印谁鳍。所以在上面的例子癞季,我們看到輸出為10次。

3.內(nèi)核動(dòng)態(tài)debug機(jī)制pr_debug函數(shù)的使用


pr_debug(fmt, ...)函數(shù)的定義位于include/linux/printk.h中倘潜,如下:

/* If you are writing a driver, please use dev_dbg instead */
#if defined(DEBUG)
#define pr_debug(fmt, ...) \
        printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#elif defined(CONFIG_DYNAMIC_DEBUG)
/* dynamic_pr_debug() uses pr_fmt() internally so we don't need it here */
#define pr_debug(fmt, ...) \
        dynamic_pr_debug(fmt, ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...) \
        no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif

從上面可以看到pr_debug(fmt, ...) 函數(shù)一共會(huì)有三種方式:

  • 如果定義了DEBUG绷柒,則使用printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__),該函數(shù)即上面所說(shuō)的方式涮因。
  • 如果沒(méi)定義DEBUG而定義了CONFIG_DYNAMIC_DEBUG废睦,則使用dynamic_pr_debug(fmt, ##__VA_ARGS__),該函數(shù)為內(nèi)核動(dòng)態(tài)debug機(jī)制养泡。
  • 如果都沒(méi)定義嗜湃,則使用no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__),該函數(shù)會(huì)直接返回0澜掩。

在實(shí)際應(yīng)用中我們都使用第二種方法购披,內(nèi)核動(dòng)態(tài)debug機(jī)制,這種方法可以按模塊來(lái)確認(rèn)是否打印該模塊的調(diào)試信息肩榕,而第一種方式會(huì)將所有模塊的debug信息都打印出來(lái)刚陡,我們可以做一個(gè)實(shí)驗(yàn),在include/linux/printk.h的最上面定義#define DEBUG,并把DEFAULT_CONSOLE_LOGLEVEL設(shè)置為8橘荠,啟動(dòng)后我們會(huì)看到console上有成千上萬(wàn)的log屿附,想找到有用的信息都難,這就是因?yàn)槎x了DEBUG后內(nèi)核中所有的模塊debug都被開(kāi)放了哥童。
下面就說(shuō)說(shuō)怎樣添加需要調(diào)試的模塊添加動(dòng)態(tài)debug機(jī)制挺份,以打開(kāi)char驅(qū)動(dòng)的debug機(jī)制為例:

  • 配置Kconfig,添加要調(diào)試驅(qū)動(dòng)模塊的DEBUG選項(xiàng)贮懈,在driver/char目錄下的Kconfig中添加如下代碼:
config OPEN_CHAR_DEBUG
. bool "Support OPEN CHAR DEBUG"
. depends on ARM
. help
. this is DEBUG function
  • 配置Makefile匀泊,添加編譯支持驅(qū)動(dòng)模塊 EXTRA_CFLAGS += -DDEBUG
    在driver/char目錄下的Makefile中添加如下代碼:
. ifeq($(CONFIG_OPEN_CHAR_DEBUG), y)
. EXTRA_CFLAGS += ‐DDEBUG
. endif
  • 在內(nèi)核的.config文件中添加該模塊的支持CONFIG_LINYE_CHAR_DEBUG=y,或者通過(guò)make configure來(lái)選中新增加的這個(gè)模塊朵你。
    通過(guò)這三步設(shè)置我們就可以在driver/char下面使用pr_debug(fmt, ...) 函數(shù)了各聘。

4.printf與printk函數(shù)的區(qū)別


在文章的開(kāi)頭已經(jīng)有說(shuō)道“內(nèi)核驅(qū)動(dòng)的調(diào)試過(guò)程我們一般會(huì)使用內(nèi)核提供的printk函數(shù)來(lái)進(jìn)行調(diào)試,在應(yīng)用層時(shí)我們會(huì)使用標(biāo)準(zhǔn)的c庫(kù)函數(shù)printf“所以它們之間最大的區(qū)別就是抡医,在Linux內(nèi)核是不支持printf函數(shù)躲因,在應(yīng)用層也是無(wú)法使用printk函數(shù)。

1.下面列舉出一些區(qū)別

  • printk()函數(shù)是直接使用了向終端設(shè)備寫(xiě)函數(shù)tty_write()忌傻。而printf()函數(shù)是調(diào)用write()系統(tǒng)調(diào)用函數(shù)向標(biāo)準(zhǔn)輸出設(shè)備寫(xiě)大脉。所以在用戶態(tài)(如進(jìn)程0)不能夠直接使用printk()函數(shù),而在內(nèi)核態(tài)由于他已是特權(quán)級(jí)水孩,所以無(wú)需系統(tǒng)調(diào)用來(lái)改變特權(quán)級(jí)镰矿,因而能夠直接使用 printk()函數(shù)。
  • printk可以以日志等級(jí)的形式進(jìn)行打印信息俘种。
  • printk無(wú)法打印浮點(diǎn)型數(shù)據(jù)秤标,printf可以打印浮點(diǎn)型數(shù)據(jù)。
  • printk是“行驅(qū)動(dòng)”的宙刘,說(shuō)只有收到一個(gè)換行符數(shù)據(jù)才會(huì)真正輸出到終端苍姜,否則就不會(huì)有任何信息輸出。

2.幾個(gè)常用的字符格式化函數(shù)

  • sprintf
    把格式化的字符串輸出到指定字符串中(char buffer[50])荐类,例:
sprintf( buffer,"String:%s", "abc");`則`buffer = "String:abc"
  • vsprintf
    把參數(shù)可變的字符串進(jìn)行格式化后輸出到指定字符串中怖现,例:
void log(const char* format, ... )
{
    char buf[50]
    va_list args;
    va_start (args, format);
    vfprintf (buf, format, args);      
    va_end (args);
}
  • fprintf
    把格式化的字符串輸出到指定文件設(shè)備中(FILE* stream),例:
void log(void)
{
    FILE* stream;
    stream = fopen("fprintf.out", "w");
    fprintf(stream, "%s%d", "aa", 1);
    fclose(stream);
}
  • vfprintf
    把參數(shù)可變的字符串進(jìn)行格式化后輸出到指定文件設(shè)備中玉罐,例:
void log(FILE *file, const char* format, ... )
{
    va_list args;
    va_start (args, format);
    vfprintf (file, format, args);      
    va_end (args);
}

5.shell 中打印調(diào)試信息的方法


在寫(xiě)shell腳本的時(shí)候,有時(shí)也需要打印log到console上進(jìn)行觀察潘拨,下面列出幾種方法:

1.echo

最直接的方式就是這節(jié)在需要打印的位置使用echo函數(shù)寫(xiě)數(shù)據(jù)到/dev/console進(jìn)行顯示吊输,例:

echo "this is debug meg!" > /dev/console
num=1
echo $num > /dev/console 

2.logger

logger其實(shí)使用的是系統(tǒng)日志模塊syslog,這里面感覺(jué)內(nèi)容比較多铁追,后面單獨(dú)做一個(gè)總結(jié)季蚂,這邊先不過(guò)多介紹。

3.shell調(diào)試

shell也有一個(gè)真實(shí)的調(diào)試模式:

1、如果在腳本"test.sh" 中有錯(cuò)誤扭屁,可以這樣來(lái)進(jìn)行調(diào)試:
sh -n test.sh算谈,這將返回所有語(yǔ)法錯(cuò)誤。

2料滥、如果想看腳本"test.sh" 中所有變量的值然眼,可以這樣來(lái)進(jìn)行調(diào)試:
sh -x test.sh,這將執(zhí)行該腳本并顯示所有變量的值葵腹,每5秒刷新一次高每。

3、sh -v test.sh践宴,一邊執(zhí)行腳本鲸匿,一邊將執(zhí)行過(guò)的腳本命令打印到標(biāo)準(zhǔn)錯(cuò)誤輸出,如果哪邊出錯(cuò)則停止阻肩。

6.tips


1带欢、如何在每條打印信息前添加時(shí)間參數(shù)
在.comfig里面添加CONFIG_PRINTK_TIME = y

2、通過(guò)dmesg可以查看log_buf內(nèi)核環(huán)形緩存區(qū)的數(shù)據(jù)

Linux print system的分析就到這邊烤惊,有感悟時(shí)會(huì)持續(xù)會(huì)更新乔煞。

注:以上內(nèi)容都是本人在學(xué)習(xí)過(guò)程積累的一些心得,難免會(huì)有參考到其他文章的一些知識(shí)撕氧,如有侵權(quán)瘤缩,請(qǐng)及時(shí)通知我,我將及時(shí)刪除或標(biāo)注內(nèi)容出處伦泥,如有錯(cuò)誤之處也請(qǐng)指出剥啤,進(jìn)行探討學(xué)習(xí)。文章只是起一個(gè)引導(dǎo)作用不脯,詳細(xì)的數(shù)據(jù)解析內(nèi)容還請(qǐng)查看Linux相關(guān)教程府怯,感謝您的查閱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末防楷,一起剝皮案震驚了整個(gè)濱河市牺丙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌复局,老刑警劉巖冲簿,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異亿昏,居然都是意外死亡峦剔,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)角钩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吝沫,“玉大人呻澜,你說(shuō)我怎么就攤上這事〔蚁眨” “怎么了羹幸?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)辫愉。 經(jīng)常有香客問(wèn)我栅受,道長(zhǎng),這世上最難降的妖魔是什么一屋? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任窘疮,我火速辦了婚禮,結(jié)果婚禮上冀墨,老公的妹妹穿的比我還像新娘闸衫。我一直安慰自己,他們只是感情好诽嘉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布蔚出。 她就那樣靜靜地躺著,像睡著了一般虫腋。 火紅的嫁衣襯著肌膚如雪骄酗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天悦冀,我揣著相機(jī)與錄音趋翻,去河邊找鬼。 笑死盒蟆,一個(gè)胖子當(dāng)著我的面吹牛踏烙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播历等,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼讨惩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了寒屯?” 一聲冷哼從身側(cè)響起荐捻,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寡夹,沒(méi)想到半個(gè)月后处面,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡菩掏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年鸳君,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片患蹂。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡或颊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出传于,到底是詐尸還是另有隱情囱挑,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布沼溜,位于F島的核電站平挑,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏系草。R本人自食惡果不足惜通熄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望找都。 院中可真熱鬧唇辨,春花似錦、人聲如沸能耻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)晓猛。三九已至饿幅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間戒职,已是汗流浹背栗恩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洪燥,地道東北人磕秤。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蚓曼,于是被迫代替她去往敵國(guó)和親亲澡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容