4.3. 用查詢來調(diào)試

2018-02-24 15:49 更新

4.3.?用查詢來調(diào)試

前面一節(jié)描述了 printk 是任何工作的以及怎樣使用它. 沒有談到的是它的缺點(diǎn).

大量使用 printk 能夠顯著地拖慢系統(tǒng), 即便你降低 cosole_loglevel 來避免加載控制臺設(shè)備, 因?yàn)?syslogd 會不停地同步它的輸出文件; 因此, 要打印的每一行都引起一次磁盤操作. 從 syslogd 的角度這是正確的實(shí)現(xiàn). 它試圖將所有東西寫到磁盤上, 防止系統(tǒng)剛好在打印消息后崩潰; 然而, 你不想只是為了調(diào)試信息的原因而拖慢你的系統(tǒng). 可以在出現(xiàn)于 /etc/syslogd.conf 中的你的日志文件名前加一個連字號來解決這個問題[14]. 改變配置文件帶來的問題是, 這個改變可能在你結(jié)束調(diào)試后保留在那里, 即便在正常系統(tǒng)操作中你確實(shí)想盡快刷新消息到磁盤. 這樣永久改變的另外的選擇是運(yùn)行一個非 klogd 程序( 例如 cat /proc/kmsg, 如之前建議的), 但是這可能不會提供一個合適的環(huán)境給正常的系統(tǒng)操作.

經(jīng)常地, 最好的獲得相關(guān)信息的方法是查詢系統(tǒng), 在你需要消息時(shí), 不是連續(xù)地產(chǎn)生數(shù)據(jù). 實(shí)際上, 每個 Unix 系統(tǒng)提供許多工具來獲取系統(tǒng)消息: ps, netstat, vmstat, 等等.

有幾個技術(shù)給驅(qū)動開發(fā)者來查詢系統(tǒng): 創(chuàng)建一個文件在 /proc 文件系統(tǒng)下, 使用 ioctl 驅(qū)動方法, 借助 sysfs 輸出屬性. 使用 sysfs 需要不少關(guān)于驅(qū)動模型的背景知識. 在 14 章討論.

4.3.1.?使用 /proc 文件系統(tǒng)

/proc文件系統(tǒng)是一個特殊的軟件創(chuàng)建的文件系統(tǒng), 內(nèi)核用來輸出消息到外界. /proc 下的每個文件都綁到一個內(nèi)核函數(shù)上, 當(dāng)文件被讀的時(shí)候即時(shí)產(chǎn)生文件內(nèi)容. 我們已經(jīng)見到一些這樣的文件起作用; 例如, /proc/modules, 常常返回當(dāng)前已加載的模塊列表.

/proc 在 Linux 系統(tǒng)中非常多地應(yīng)用. 很多現(xiàn)代 Linux 發(fā)布中的工具, 例如 ps, top, 以及 uptime, 從 /proc 中獲取它們的信息. 一些設(shè)備驅(qū)動也通過 /proc 輸出信息, 你的也可以這樣做. /proc 文件系統(tǒng)是動態(tài)的, 因此你的模塊可以在任何時(shí)候添加或去除條目.

完全特性的 /proc 條目可能是復(fù)雜的野獸; 另外, 它們可寫也可讀, 但是, 大部分時(shí)間, /proc 條目是只讀的文件. 本節(jié)只涉及簡單的只讀情況. 那些感興趣于實(shí)現(xiàn)更復(fù)雜的東西的人可以從這里獲取基本知識; 接下來可參考內(nèi)核源碼來獲知完整的信息.

在我們繼續(xù)之前, 我們應(yīng)當(dāng)提及在 /proc 下添加文件是不鼓勵的. /proc 文件系統(tǒng)在內(nèi)核開發(fā)者看作是有點(diǎn)無法控制的混亂, 它已經(jīng)遠(yuǎn)離它的本來目的了(是提供關(guān)于系統(tǒng)中運(yùn)行的進(jìn)程的信息). 建議新代碼中使信息可獲取的方法是利用 sysfs. 如同建議的, 使用 sysfs 需要對 Linux 設(shè)備模型的理解, 然而, 我們直到 14 章才接觸它. 同時(shí), /proc 下的文件稍稍容易創(chuàng)建, 并且它們完全適合調(diào)試目的, 所以我們在這里包含它們.

4.3.1.1.?在 /proc 里實(shí)現(xiàn)文件

所有使用 /proc 的模塊應(yīng)當(dāng)包含 <linux/proc_fs.h> 來定義正確的函數(shù).

要創(chuàng)建一個只讀 /proc 文件, 你的驅(qū)動必須實(shí)現(xiàn)一個函數(shù)來在文件被讀時(shí)產(chǎn)生數(shù)據(jù). 當(dāng)某個進(jìn)程讀文件時(shí)(使用 read 系統(tǒng)調(diào)用), 這個請求通過這個函數(shù)到達(dá)你的模塊. 我們先看看這個函數(shù)并在本章后面討論注冊接口.

當(dāng)一個進(jìn)程讀你的 /proc 文件, 內(nèi)核分配了一頁內(nèi)存(就是說, PAGE_SIZE 字節(jié)), 驅(qū)動可以寫入數(shù)據(jù)來返回給用戶空間. 那個緩存區(qū)傳遞給你的函數(shù), 是一個稱為 read_proc 的方法:


                int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);

page 指針是你寫你的數(shù)據(jù)的緩存區(qū); start 是這個函數(shù)用來說有關(guān)的數(shù)據(jù)寫在頁中哪里(下面更多關(guān)于這個); offset 和 count 對于 read 方法有同樣的含義. eof 參數(shù)指向一個整數(shù), 必須由驅(qū)動設(shè)置來指示它不再有數(shù)據(jù)返回, data 是驅(qū)動特定的數(shù)據(jù)指針, 你可以用做內(nèi)部用途.

這個函數(shù)應(yīng)當(dāng)返回實(shí)際擺放于 page 緩存區(qū)的數(shù)據(jù)的字節(jié)數(shù), 就象 read 方法對別的文件所作一樣. 別的輸出值是 eof 和 start. eof 是一個簡單的標(biāo)志, 但是 start 值的使用有些復(fù)雜; 它的目的是幫助實(shí)現(xiàn)大的(超過一頁) /proc 文件.

start 參數(shù)有些非傳統(tǒng)的用法. 它的目的是指示哪里(哪一頁)找到返回給用戶的數(shù)據(jù). 當(dāng)調(diào)用你的 proc_read 方法, start 將會是 NULL. 如果你保持它為 NULL, 內(nèi)核假定數(shù)據(jù)已放進(jìn) page 偏移是 0; 換句話說, 它假定一個頭腦簡單的 proc_read 版本, 它安放虛擬文件的整個內(nèi)容到 page, 沒有注意 offset 參數(shù). 如果, 相反, 你設(shè)置 start 為一個 非NULL 值, 內(nèi)核認(rèn)為由 start 指向的數(shù)據(jù)考慮了 offset, 并且準(zhǔn)備好直接返回給用戶. 通常, 返回少量數(shù)據(jù)的簡單 proc_read 方法只是忽略 start. 更復(fù)雜的方法設(shè)置 start 為 page 并且只從請求的 offset 那里開始安放數(shù)據(jù).

還有一段距離到 /proc 文件的另一個主要問題, 它也打算解答 start. 有時(shí)內(nèi)核數(shù)據(jù)結(jié)構(gòu)的 ASCII 表示在連續(xù)的 read 調(diào)用中改變, 因此讀進(jìn)程可能發(fā)現(xiàn)從一個調(diào)用到下一個有不一致的數(shù)據(jù). 如果 start 設(shè)成一個小的整數(shù)值, 調(diào)用者用它來遞增 filp-<f_pos 不依賴你返回的數(shù)據(jù)量, 因此使 f_pos 成為你的 read_proc 過程的一個內(nèi)部記錄數(shù). 如果, 例如, 如果你的 read_proc 函數(shù)從一個大結(jié)構(gòu)數(shù)組返回信息并且第一次調(diào)用返回了 5 個結(jié)構(gòu), start可設(shè)成5. 下一個調(diào)用提供同一個數(shù)作為 offset; 驅(qū)動就知道從數(shù)組中第 6 個結(jié)構(gòu)返回?cái)?shù)據(jù). 這是被它的作者承認(rèn)的一個" hack ", 可以在 fs/proc/generic.c 見到.

注意, 有更好的方法實(shí)現(xiàn)大的 /proc 文件; 它稱為 seq_file, 我們很快會討論它. 首先, 然而, 是時(shí)間舉個例子了. 下面是一個簡單的(有點(diǎn)丑陋) read_proc 實(shí)現(xiàn), 為 scull 設(shè)備:


int scull_read_procmem(char *buf, char **start, off_t offset, int count, int *eof, void *data)
{
    int i, j, len = 0;
    int limit = count - 80; /* Don't print more than this */

    for (i = 0; i < scull_nr_devs && len <= limit; i++) {
        struct scull_dev *d = &scull_devices[i];
        struct scull_qset *qs = d->data;
        if (down_interruptible(&d->sem))
            return -ERESTARTSYS;
        len += sprintf(buf+len,"\nDevice %i: qset %i, q %i, sz %li\n", i, d->qset, d->quantum, d->size);
        for (; qs && len <= limit; qs = qs->next) { /* scan the list */
            len += sprintf(buf + len, " item at %p, qset at %p\n", qs, qs->data);
            if (qs->data && !qs->next) /* dump only the last item */
                for (j = 0; j < d->qset; j++) {
                    if (qs->data[j])
                        len += sprintf(buf + len, " % 4i: %8p\n", j, qs->data[j]);
                }
        }
        up(&scull_devices[i].sem);

    }
    *eof = 1;
    return len;

}

這是一個相當(dāng)?shù)湫偷?read_proc 實(shí)現(xiàn). 它假定不會有必要產(chǎn)生超過一頁數(shù)據(jù)并且因此忽略了 start 和 offset 值. 它是, 但是, 小心地不覆蓋它的緩存, 只是以防萬一.

4.3.1.2.?老接口

如果你閱覽內(nèi)核源碼, 你會遇到使用老接口實(shí)現(xiàn) /proc 的代碼:


int (*get_info)(char *page, char **start, off_t offset, int count); 

所有的參數(shù)的含義同 read_proc 的相同, 但是沒有 eof 和 data 參數(shù). 這個接口仍然支持, 但是將來會消失; 新代碼應(yīng)當(dāng)使用 read_proc 接口來代替.

4.3.1.3.?創(chuàng)建你的 /proc 文件

一旦你有一個定義好的 read_proc 函數(shù), 你應(yīng)當(dāng)連接它到 /proc 層次中的一個入口項(xiàng). 使用一個 creat_proc_read_entry 調(diào)用:


struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode, struct proc_dir_entry *base, read_proc_t *read_proc, void *data); 

這里, name 是要創(chuàng)建的文件名子, mod 是文件的保護(hù)掩碼(缺省系統(tǒng)范圍時(shí)可以作為 0 傳遞), base 指出要創(chuàng)建的文件的目錄( 如果 base 是 NULL, 文件在 /proc 根下創(chuàng)建 ), read_proc 是實(shí)現(xiàn)文件的 read_proc 函數(shù), data 被內(nèi)核忽略( 但是傳遞給 read_proc). 這就是 scull 使用的調(diào)用, 來使它的 /proc 函數(shù)可用做 /proc/scullmem:


create_proc_read_entry("scullmem", 0 /* default mode */,
                       NULL /* parent dir */, scull_read_procmem,
                       NULL /* client data */);

這里, 我們創(chuàng)建了一個名為 scullmem 的文件, 直接在 /proc 下, 帶有缺省的, 全局可讀的保護(hù).

目錄入口指針可用來在 /proc 下創(chuàng)建整個目錄層次. 但是, 注意, 一個入口放在 /proc 的子目錄下會更容易, 通過簡單地給出目錄名子作為這個入口名子的一部分 -- 只要這個目錄自身已經(jīng)存在. 例如, 一個(常常被忽略)傳統(tǒng)的是 /proc 中與設(shè)備驅(qū)動相連的入口應(yīng)當(dāng)在 driver/ 子目錄下; scull 能夠安放它的入口在那里, 簡單地通過指定它為名子 driver/scullmem.

/proc 中的入口, 當(dāng)然, 應(yīng)當(dāng)在模塊卸載后去除. remove_proc_entry 是恢復(fù) create_proc_read_entry 所做的事情的函數(shù):


remove_proc_entry("scullmem", NULL /* parent dir */); 

去除入口失敗會導(dǎo)致在不希望的時(shí)間調(diào)用, 或者, 如果你的模塊已被卸載, 內(nèi)核崩掉.

當(dāng)如展示的使用 /proc 文件, 你必須記住幾個實(shí)現(xiàn)的麻煩事 -- 不要奇怪現(xiàn)在不鼓勵使用它.

最重要的問題是關(guān)于去除 /proc 入口. 這樣的去除很可能在文件使用時(shí)發(fā)生, 因?yàn)闆]有所有者關(guān)聯(lián)到 /proc 入口, 因此使用它們不會作用到模塊的引用計(jì)數(shù). 這個問題可以簡單的觸發(fā), 例如通過運(yùn)行 sleep 100 < /proc/myfile, 剛好在去除模塊之前.

另外一個問題時(shí)關(guān)于用同樣的名子注冊兩個入口. 內(nèi)核信任驅(qū)動, 不會檢查名子是否已經(jīng)注冊了, 因此如果你不小心, 你可能會使用同樣的名子注冊兩個或多個入口. 這是一個已知發(fā)生在教室中的問題, 這樣的入口是不能區(qū)分的, 不但在你存取它們時(shí), 而且在你調(diào)用 remove_proc_entry 時(shí).

4.3.1.4.?seq_file 接口

如我們上面提到的, 在 /proc 下的大文件的實(shí)現(xiàn)有點(diǎn)麻煩. 一直以來, /proc 方法因?yàn)楫?dāng)輸出數(shù)量變大時(shí)的錯誤實(shí)現(xiàn)變得聲名狼藉. 作為一種清理 /proc 代碼以及使內(nèi)核開發(fā)者活得輕松些的方法, 添加了 seq_file 接口. 這個接口提供了簡單的一套函數(shù)來實(shí)現(xiàn)大內(nèi)核虛擬文件.

set_file 接口假定你在創(chuàng)建一個虛擬文件, 它涉及一系列的必須返回給用戶空間的項(xiàng). 為使用 seq_file, 你必須創(chuàng)建一個簡單的 "iterator" 對象, 它能在序列里建立一個位置, 向前進(jìn), 并且輸出序列里的一個項(xiàng). 它可能聽起來復(fù)雜, 但是, 實(shí)際上, 過程非常簡單. 我們一步步來創(chuàng)建 /proc 文件在 scull 驅(qū)動里, 來展示它是如何做的.

第一步, 不可避免地, 是包含 <linux/seq_file.h>. 接著你必須創(chuàng)建 4 個 iterator 方法, 稱為 start, next, stop, 和 show.

start 方法一直是首先調(diào)用. 這個函數(shù)的原型是:


void *start(struct seq_file *sfile, loff_t *pos);

sfile 參數(shù)可以幾乎是一直被忽略. pos 是一個整型位置值, 指示應(yīng)當(dāng)從哪里讀. 位置的解釋完全取決于實(shí)現(xiàn); 在結(jié)果文件里不需要是一個字節(jié)位置. 因?yàn)?seq_file 實(shí)現(xiàn)典型地步進(jìn)一系列感興趣的項(xiàng), position 常常被解釋為指向序列中下一個項(xiàng)的指針. scull 驅(qū)動解釋每個設(shè)備作為系列中的一項(xiàng), 因此進(jìn)入的 pos 簡單地是一個 scull_device 數(shù)組的索引. 因此, scull 使用的 start 方法是:


static void *scull_seq_start(struct seq_file *s, loff_t *pos)
{
    if (*pos >= scull_nr_devs)
        return NULL;  /* No more to read */
    return scull_devices + *pos;
}

返回值, 如果非NULL, 是一個可以被 iterator 實(shí)現(xiàn)使用的私有值.

next 函數(shù)應(yīng)當(dāng)移動 iterator 到下一個位置, 如果序列里什么都沒有剩下就返回 NULL. 這個方法的原型是:


void *next(struct seq_file *sfile, void *v, loff_t *pos); 

這里, v 是從前一個對 start 或者 next 的調(diào)用返回的 iterator, pos 是文件的當(dāng)前位置. next 應(yīng)當(dāng)遞增有 pos 指向的值; 根據(jù)你的 iterator 是如何工作的, 你可能(盡管可能不會)需要遞增 pos 不止是 1. 這是 scull 所做的:


static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
    (*pos)++;
    if (*pos >= scull_nr_devs)
        return NULL;
    return scull_devices + *pos;
}

當(dāng)內(nèi)核處理完 iterator, 它調(diào)用 stop 來清理:


void stop(struct seq_file *sfile, void *v); 

scull 實(shí)現(xiàn)沒有清理工作要做, 所以它的 stop 方法是空的.

設(shè)計(jì)上, 值得注意 seq_file 代碼在調(diào)用 start 和 stop 之間不睡眠或者進(jìn)行其他非原子性任務(wù). 你也肯定會看到在調(diào)用 start 后馬上有一個 stop 調(diào)用. 因此, 對你的 start 方法來說請求信號量或自旋鎖是安全的. 只要你的其他 seq_file 方法是原子的, 調(diào)用的整個序列是原子的. (如果這一段對你沒有意義, 在你讀了下一章后再回到這.)

在這些調(diào)用中, 內(nèi)核調(diào)用 show 方法來真正輸出有用的東西給用戶空間. 這個方法的原型是:


int show(struct seq_file *sfile, void *v); 

這個方法應(yīng)當(dāng)創(chuàng)建序列中由 iterator v 指示的項(xiàng)的輸出. 不應(yīng)當(dāng)使用 printk, 但是; 有一套特殊的用作 seq_file 輸出的函數(shù):

int seq_printf(struct seq_file sfile, const char fmt, ...);
這是給 seq_file 實(shí)現(xiàn)的 printf 對等體; 它采用常用的格式串和附加值參數(shù). 你必須也將給 show 函數(shù)的 set_file 結(jié)構(gòu)傳遞給它, 然而. 如果seq_printf 返回非零值, 意思是緩存區(qū)已填充, 輸出被丟棄. 大部分實(shí)現(xiàn)忽略了返回值, 但是.

int seq_putc(struct seq_file sfile, char c);int seq_puts(struct seq_file sfile, const char *s);
它們是用戶空間 putc 和 puts 函數(shù)的對等體.

int seq_escape(struct seq_file m, const char s, const char *esc);
這個函數(shù)是 seq_puts 的對等體, 除了 s 中的任何也在 esc 中出現(xiàn)的字符以八進(jìn)制格式打印. esc 的一個通用值是"\t\n\", 它使內(nèi)嵌的空格不會搞亂輸出和可能搞亂 shell 腳本.

int seq_path(struct seq_file sfile, struct vfsmount m, struct dentry dentry, char esc);
這個函數(shù)能夠用來輸出和給定命令項(xiàng)關(guān)聯(lián)的文件名子. 它在設(shè)備驅(qū)動中不可能有用; 我們是為了完整在此包含它.

回到我們的例子; 在 scull 使用的 show 方法是:


static int scull_seq_show(struct seq_file *s, void *v)
{
    struct scull_dev *dev = (struct scull_dev *) v;
    struct scull_qset *d;
    int i;

    if (down_interruptible (&dev->sem))
        return -ERESTARTSYS;

    seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n",
               (int) (dev - scull_devices), dev->qset,
               dev->quantum, dev->size);

    for (d = dev->data; d; d = d->next) { /* scan the list */
        seq_printf(s, " item at %p, qset at %p\n", d, d->data);
        if (d->data && !d->next) /* dump only the last item */

            for (i = 0; i < dev->qset; i++) {
                if (d->data[i])
                    seq_printf(s, " % 4i: %8p\n",
                               i, d->data[i]);
            }
    }
    up(&dev->sem);
    return 0;
}

這里, 我們最終解釋我們的" iterator" 值, 簡單地是一個 scull_dev 結(jié)構(gòu)指針.

現(xiàn)在已有了一個完整的 iterator 操作的集合, scull 必須包裝起它們, 并且連接它們到 /proc 中的一個文件. 第一步是填充一個 seq_operations 結(jié)構(gòu):


static struct seq_operations scull_seq_ops = {
 .start = scull_seq_start,
 .next = scull_seq_next,
 .stop = scull_seq_stop,
 .show = scull_seq_show
}; 

有那個結(jié)構(gòu)在, 我們必須創(chuàng)建一個內(nèi)核理解的文件實(shí)現(xiàn). 我們不使用前面描述過的 read_proc 方法; 在使用 seq_file 時(shí), 最好在一個稍低的級別上連接到 /proc. 那意味著創(chuàng)建一個 file_operations 結(jié)構(gòu)(是的, 和字符驅(qū)動使用的同樣結(jié)構(gòu)) 來實(shí)現(xiàn)所有內(nèi)核需要的操作, 來處理文件上的讀和移動. 幸運(yùn)的是, 這個任務(wù)是簡單的. 第一步是創(chuàng)建一個 open 方法連接文件到 seq_file 操作:


static int scull_proc_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &scull_seq_ops);
}

調(diào)用 seq_open 連接文件結(jié)構(gòu)和我們上面定義的序列操作. 事實(shí)證明, open 是我們必須自己實(shí)現(xiàn)的唯一文件操作, 因此我們現(xiàn)在可以建立我們的 file_operations 結(jié)構(gòu):


static struct file_operations scull_proc_ops = {
 .owner = THIS_MODULE,
 .open = scull_proc_open,
 .read = seq_read,
 .llseek = seq_lseek,
 .release = seq_release 
}; 

這里我們指定我們自己的 open 方法, 但是使用預(yù)裝好的方法 seq_read, seq_lseek, 和 seq_release 給其他.

最后的步驟是創(chuàng)建 /proc 中的實(shí)際文件:


entry = create_proc_entry("scullseq", 0, NULL);
if (entry)
    entry->proc_fops = &scull_proc_ops;

不是使用 create_proc_read_entry, 我們調(diào)用低層的 create_proc_entry, 我們有這個原型:


struct proc_dir_entry *create_proc_entry(const char *name,mode_t mode,struct proc_dir_entry *parent); 

參數(shù)和它們的在 create_proc_read_entry 中的對等體相同: 文件名子, 它的位置, 以及父目錄.

有了上面代碼, scull 有一個新的 /proc 入口, 看來很象前面的一個. 但是, 它是高級的, 因?yàn)樗还芩妮敵鲇卸嗝创? 它正確處理移動, 并且通常它是易讀和易維護(hù)的. 我們建議使用 seq_file , 來實(shí)現(xiàn)包含多個非常小數(shù)目的輸出行數(shù)的文件.

4.3.2.?ioctl 方法

ioctl, 我們在第 1 章展示給你如何使用, 是一個系統(tǒng)調(diào)用, 作用于一個文件描述符; 它接收一個確定要進(jìn)行的命令的數(shù)字和(可選地)另一個參數(shù), 常常是一個指針. 作為一個使用 /proc 文件系統(tǒng)的替代, 你可以實(shí)現(xiàn)幾個用來調(diào)試用的 ioctl 命令. 這些命令可以從驅(qū)動拷貝相關(guān)的數(shù)據(jù)結(jié)構(gòu)到用戶空間, 這里你可以檢查它們.

這種方式使用 ioctl 來獲取信息有些比使用 /proc 困難, 因?yàn)槟阈枰硪粋€程序來發(fā)出 ioctl 并且顯示結(jié)果. 必須編寫這個程序, 編譯, 并且與你在測試的模塊保持同步. 另一方面, 驅(qū)動側(cè)代碼可能容易過需要實(shí)現(xiàn)一個 /proc 文件的代碼.

有時(shí)候 ioctl 是獲取信息最好的方法, 因?yàn)樗\(yùn)行比讀取 /proc 快. 如果在數(shù)據(jù)寫到屏幕之前必須做一些事情, 獲取二進(jìn)制形式的數(shù)據(jù)比讀取一個文本文件要更有效. 另外, ioctl 不要求劃分?jǐn)?shù)據(jù)為小于一頁的片段.

ioctl 方法的另一個有趣的優(yōu)點(diǎn)是信息獲取命令可留在驅(qū)動中, 當(dāng)調(diào)試被禁止時(shí). 不象對任何查看目錄的人(并且太多人可能奇怪"這個怪文件是什么")都可見的 /proc 文件, 不記入文檔的 ioctl 命令可能保持不為人知. 另外, 如果驅(qū)動發(fā)生了怪異的事情, 它們?nèi)詫⒃谀抢? 唯一的缺點(diǎn)是模塊可能會稍微大些.

[14] 連字號, 或者減號, 是一個"魔術(shù)"標(biāo)識以阻止 syslogd 刷新文件到磁盤在每個新消息, 有關(guān)文檔在 syslog.conf(5), 一個值得一讀的 manpage.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號