前面一節(jié)描述了 printk 是任何工作的以及怎樣使用它. 沒(méi)有談到的是它的缺點(diǎn).
大量使用 printk 能夠顯著地拖慢系統(tǒng), 即便你降低 cosole_loglevel 來(lái)避免加載控制臺(tái)設(shè)備, 因?yàn)?syslogd 會(huì)不停地同步它的輸出文件; 因此, 要打印的每一行都引起一次磁盤(pán)操作. 從 syslogd 的角度這是正確的實(shí)現(xiàn). 它試圖將所有東西寫(xiě)到磁盤(pán)上, 防止系統(tǒng)剛好在打印消息后崩潰; 然而, 你不想只是為了調(diào)試信息的原因而拖慢你的系統(tǒng). 可以在出現(xiàn)于 /etc/syslogd.conf 中的你的日志文件名前加一個(gè)連字號(hào)來(lái)解決這個(gè)問(wèn)題[14]. 改變配置文件帶來(lái)的問(wèn)題是, 這個(gè)改變可能在你結(jié)束調(diào)試后保留在那里, 即便在正常系統(tǒng)操作中你確實(shí)想盡快刷新消息到磁盤(pán). 這樣永久改變的另外的選擇是運(yùn)行一個(gè)非 klogd 程序( 例如 cat /proc/kmsg, 如之前建議的), 但是這可能不會(huì)提供一個(gè)合適的環(huán)境給正常的系統(tǒng)操作.
經(jīng)常地, 最好的獲得相關(guān)信息的方法是查詢系統(tǒng), 在你需要消息時(shí), 不是連續(xù)地產(chǎn)生數(shù)據(jù). 實(shí)際上, 每個(gè) Unix 系統(tǒng)提供許多工具來(lái)獲取系統(tǒng)消息: ps, netstat, vmstat, 等等.
有幾個(gè)技術(shù)給驅(qū)動(dòng)開(kāi)發(fā)者來(lái)查詢系統(tǒng): 創(chuàng)建一個(gè)文件在 /proc 文件系統(tǒng)下, 使用 ioctl 驅(qū)動(dòng)方法, 借助 sysfs 輸出屬性. 使用 sysfs 需要不少關(guān)于驅(qū)動(dòng)模型的背景知識(shí). 在 14 章討論.
/proc文件系統(tǒng)是一個(gè)特殊的軟件創(chuàng)建的文件系統(tǒng), 內(nèi)核用來(lái)輸出消息到外界. /proc 下的每個(gè)文件都綁到一個(gè)內(nèi)核函數(shù)上, 當(dāng)文件被讀的時(shí)候即時(shí)產(chǎn)生文件內(nèi)容. 我們已經(jīng)見(jiàn)到一些這樣的文件起作用; 例如, /proc/modules, 常常返回當(dāng)前已加載的模塊列表.
/proc 在 Linux 系統(tǒng)中非常多地應(yīng)用. 很多現(xiàn)代 Linux 發(fā)布中的工具, 例如 ps, top, 以及 uptime, 從 /proc 中獲取它們的信息. 一些設(shè)備驅(qū)動(dòng)也通過(guò) /proc 輸出信息, 你的也可以這樣做. /proc 文件系統(tǒng)是動(dòng)態(tài)的, 因此你的模塊可以在任何時(shí)候添加或去除條目.
完全特性的 /proc 條目可能是復(fù)雜的野獸; 另外, 它們可寫(xiě)也可讀, 但是, 大部分時(shí)間, /proc 條目是只讀的文件. 本節(jié)只涉及簡(jiǎn)單的只讀情況. 那些感興趣于實(shí)現(xiàn)更復(fù)雜的東西的人可以從這里獲取基本知識(shí); 接下來(lái)可參考內(nèi)核源碼來(lái)獲知完整的信息.
在我們繼續(xù)之前, 我們應(yīng)當(dāng)提及在 /proc 下添加文件是不鼓勵(lì)的. /proc 文件系統(tǒng)在內(nèi)核開(kāi)發(fā)者看作是有點(diǎn)無(wú)法控制的混亂, 它已經(jīng)遠(yuǎn)離它的本來(lái)目的了(是提供關(guān)于系統(tǒng)中運(yùn)行的進(jìn)程的信息). 建議新代碼中使信息可獲取的方法是利用 sysfs. 如同建議的, 使用 sysfs 需要對(duì) Linux 設(shè)備模型的理解, 然而, 我們直到 14 章才接觸它. 同時(shí), /proc 下的文件稍稍容易創(chuàng)建, 并且它們完全適合調(diào)試目的, 所以我們?cè)谶@里包含它們.
所有使用 /proc 的模塊應(yīng)當(dāng)包含 <linux/proc_fs.h> 來(lái)定義正確的函數(shù).
要?jiǎng)?chuàng)建一個(gè)只讀 /proc 文件, 你的驅(qū)動(dòng)必須實(shí)現(xiàn)一個(gè)函數(shù)來(lái)在文件被讀時(shí)產(chǎn)生數(shù)據(jù). 當(dāng)某個(gè)進(jìn)程讀文件時(shí)(使用 read 系統(tǒng)調(diào)用), 這個(gè)請(qǐng)求通過(guò)這個(gè)函數(shù)到達(dá)你的模塊. 我們先看看這個(gè)函數(shù)并在本章后面討論注冊(cè)接口.
當(dāng)一個(gè)進(jìn)程讀你的 /proc 文件, 內(nèi)核分配了一頁(yè)內(nèi)存(就是說(shuō), PAGE_SIZE 字節(jié)), 驅(qū)動(dòng)可以寫(xiě)入數(shù)據(jù)來(lái)返回給用戶空間. 那個(gè)緩存區(qū)傳遞給你的函數(shù), 是一個(gè)稱為 read_proc 的方法:
int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);
page 指針是你寫(xiě)你的數(shù)據(jù)的緩存區(qū); start 是這個(gè)函數(shù)用來(lái)說(shuō)有關(guān)的數(shù)據(jù)寫(xiě)在頁(yè)中哪里(下面更多關(guān)于這個(gè)); offset 和 count 對(duì)于 read 方法有同樣的含義. eof 參數(shù)指向一個(gè)整數(shù), 必須由驅(qū)動(dòng)設(shè)置來(lái)指示它不再有數(shù)據(jù)返回, data 是驅(qū)動(dòng)特定的數(shù)據(jù)指針, 你可以用做內(nèi)部用途.
這個(gè)函數(shù)應(yīng)當(dāng)返回實(shí)際擺放于 page 緩存區(qū)的數(shù)據(jù)的字節(jié)數(shù), 就象 read 方法對(duì)別的文件所作一樣. 別的輸出值是 eof 和 start. eof 是一個(gè)簡(jiǎn)單的標(biāo)志, 但是 start 值的使用有些復(fù)雜; 它的目的是幫助實(shí)現(xiàn)大的(超過(guò)一頁(yè)) /proc 文件.
start 參數(shù)有些非傳統(tǒng)的用法. 它的目的是指示哪里(哪一頁(yè))找到返回給用戶的數(shù)據(jù). 當(dāng)調(diào)用你的 proc_read 方法, start 將會(huì)是 NULL. 如果你保持它為 NULL, 內(nèi)核假定數(shù)據(jù)已放進(jìn) page 偏移是 0; 換句話說(shuō), 它假定一個(gè)頭腦簡(jiǎn)單的 proc_read 版本, 它安放虛擬文件的整個(gè)內(nèi)容到 page, 沒(méi)有注意 offset 參數(shù). 如果, 相反, 你設(shè)置 start 為一個(gè) 非NULL 值, 內(nèi)核認(rèn)為由 start 指向的數(shù)據(jù)考慮了 offset, 并且準(zhǔn)備好直接返回給用戶. 通常, 返回少量數(shù)據(jù)的簡(jiǎn)單 proc_read 方法只是忽略 start. 更復(fù)雜的方法設(shè)置 start 為 page 并且只從請(qǐng)求的 offset 那里開(kāi)始安放數(shù)據(jù).
還有一段距離到 /proc 文件的另一個(gè)主要問(wèn)題, 它也打算解答 start. 有時(shí)內(nèi)核數(shù)據(jù)結(jié)構(gòu)的 ASCII 表示在連續(xù)的 read 調(diào)用中改變, 因此讀進(jìn)程可能發(fā)現(xiàn)從一個(gè)調(diào)用到下一個(gè)有不一致的數(shù)據(jù). 如果 start 設(shè)成一個(gè)小的整數(shù)值, 調(diào)用者用它來(lái)遞增 filp-<f_pos 不依賴你返回的數(shù)據(jù)量, 因此使 f_pos 成為你的 read_proc 過(guò)程的一個(gè)內(nèi)部記錄數(shù). 如果, 例如, 如果你的 read_proc 函數(shù)從一個(gè)大結(jié)構(gòu)數(shù)組返回信息并且第一次調(diào)用返回了 5 個(gè)結(jié)構(gòu), start可設(shè)成5. 下一個(gè)調(diào)用提供同一個(gè)數(shù)作為 offset; 驅(qū)動(dòng)就知道從數(shù)組中第 6 個(gè)結(jié)構(gòu)返回?cái)?shù)據(jù). 這是被它的作者承認(rèn)的一個(gè)" hack ", 可以在 fs/proc/generic.c 見(jiàn)到.
注意, 有更好的方法實(shí)現(xiàn)大的 /proc 文件; 它稱為 seq_file, 我們很快會(huì)討論它. 首先, 然而, 是時(shí)間舉個(gè)例子了. 下面是一個(gè)簡(jiǎn)單的(有點(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;
}
這是一個(gè)相當(dāng)?shù)湫偷?read_proc 實(shí)現(xiàn). 它假定不會(huì)有必要產(chǎn)生超過(guò)一頁(yè)數(shù)據(jù)并且因此忽略了 start 和 offset 值. 它是, 但是, 小心地不覆蓋它的緩存, 只是以防萬(wàn)一.
如果你閱覽內(nèi)核源碼, 你會(huì)遇到使用老接口實(shí)現(xiàn) /proc 的代碼:
int (*get_info)(char *page, char **start, off_t offset, int count);
所有的參數(shù)的含義同 read_proc 的相同, 但是沒(méi)有 eof 和 data 參數(shù). 這個(gè)接口仍然支持, 但是將來(lái)會(huì)消失; 新代碼應(yīng)當(dāng)使用 read_proc 接口來(lái)代替.
一旦你有一個(gè)定義好的 read_proc 函數(shù), 你應(yīng)當(dāng)連接它到 /proc 層次中的一個(gè)入口項(xiàng). 使用一個(gè) 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 是要?jiǎng)?chuàng)建的文件名子, mod 是文件的保護(hù)掩碼(缺省系統(tǒng)范圍時(shí)可以作為 0 傳遞), base 指出要?jiǎng)?chuàng)建的文件的目錄( 如果 base 是 NULL, 文件在 /proc 根下創(chuàng)建 ), read_proc 是實(shí)現(xiàn)文件的 read_proc 函數(shù), data 被內(nèi)核忽略( 但是傳遞給 read_proc). 這就是 scull 使用的調(diào)用, 來(lái)使它的 /proc 函數(shù)可用做 /proc/scullmem:
create_proc_read_entry("scullmem", 0 /* default mode */,
NULL /* parent dir */, scull_read_procmem,
NULL /* client data */);
這里, 我們創(chuàng)建了一個(gè)名為 scullmem 的文件, 直接在 /proc 下, 帶有缺省的, 全局可讀的保護(hù).
目錄入口指針可用來(lái)在 /proc 下創(chuàng)建整個(gè)目錄層次. 但是, 注意, 一個(gè)入口放在 /proc 的子目錄下會(huì)更容易, 通過(guò)簡(jiǎn)單地給出目錄名子作為這個(gè)入口名子的一部分 -- 只要這個(gè)目錄自身已經(jīng)存在. 例如, 一個(gè)(常常被忽略)傳統(tǒng)的是 /proc 中與設(shè)備驅(qū)動(dòng)相連的入口應(yīng)當(dāng)在 driver/ 子目錄下; scull 能夠安放它的入口在那里, 簡(jiǎn)單地通過(guò)指定它為名子 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 */);
去除入口失敗會(huì)導(dǎo)致在不希望的時(shí)間調(diào)用, 或者, 如果你的模塊已被卸載, 內(nèi)核崩掉.
當(dāng)如展示的使用 /proc 文件, 你必須記住幾個(gè)實(shí)現(xiàn)的麻煩事 -- 不要奇怪現(xiàn)在不鼓勵(lì)使用它.
最重要的問(wèn)題是關(guān)于去除 /proc 入口. 這樣的去除很可能在文件使用時(shí)發(fā)生, 因?yàn)闆](méi)有所有者關(guān)聯(lián)到 /proc 入口, 因此使用它們不會(huì)作用到模塊的引用計(jì)數(shù). 這個(gè)問(wèn)題可以簡(jiǎn)單的觸發(fā), 例如通過(guò)運(yùn)行 sleep 100 < /proc/myfile, 剛好在去除模塊之前.
另外一個(gè)問(wèn)題時(shí)關(guān)于用同樣的名子注冊(cè)兩個(gè)入口. 內(nèi)核信任驅(qū)動(dòng), 不會(huì)檢查名子是否已經(jīng)注冊(cè)了, 因此如果你不小心, 你可能會(huì)使用同樣的名子注冊(cè)兩個(gè)或多個(gè)入口. 這是一個(gè)已知發(fā)生在教室中的問(wèn)題, 這樣的入口是不能區(qū)分的, 不但在你存取它們時(shí), 而且在你調(diào)用 remove_proc_entry 時(shí).
如我們上面提到的, 在 /proc 下的大文件的實(shí)現(xiàn)有點(diǎn)麻煩. 一直以來(lái), /proc 方法因?yàn)楫?dāng)輸出數(shù)量變大時(shí)的錯(cuò)誤實(shí)現(xiàn)變得聲名狼藉. 作為一種清理 /proc 代碼以及使內(nèi)核開(kāi)發(fā)者活得輕松些的方法, 添加了 seq_file 接口. 這個(gè)接口提供了簡(jiǎn)單的一套函數(shù)來(lái)實(shí)現(xiàn)大內(nèi)核虛擬文件.
set_file 接口假定你在創(chuàng)建一個(gè)虛擬文件, 它涉及一系列的必須返回給用戶空間的項(xiàng). 為使用 seq_file, 你必須創(chuàng)建一個(gè)簡(jiǎn)單的 "iterator" 對(duì)象, 它能在序列里建立一個(gè)位置, 向前進(jìn), 并且輸出序列里的一個(gè)項(xiàng). 它可能聽(tīng)起來(lái)復(fù)雜, 但是, 實(shí)際上, 過(guò)程非常簡(jiǎn)單. 我們一步步來(lái)創(chuàng)建 /proc 文件在 scull 驅(qū)動(dòng)里, 來(lái)展示它是如何做的.
第一步, 不可避免地, 是包含 <linux/seq_file.h>. 接著你必須創(chuàng)建 4 個(gè) iterator 方法, 稱為 start, next, stop, 和 show.
start 方法一直是首先調(diào)用. 這個(gè)函數(shù)的原型是:
void *start(struct seq_file *sfile, loff_t *pos);
sfile 參數(shù)可以幾乎是一直被忽略. pos 是一個(gè)整型位置值, 指示應(yīng)當(dāng)從哪里讀. 位置的解釋完全取決于實(shí)現(xiàn); 在結(jié)果文件里不需要是一個(gè)字節(jié)位置. 因?yàn)?seq_file 實(shí)現(xiàn)典型地步進(jìn)一系列感興趣的項(xiàng), position 常常被解釋為指向序列中下一個(gè)項(xiàng)的指針. scull 驅(qū)動(dòng)解釋每個(gè)設(shè)備作為系列中的一項(xiàng), 因此進(jìn)入的 pos 簡(jiǎn)單地是一個(gè) 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, 是一個(gè)可以被 iterator 實(shí)現(xiàn)使用的私有值.
next 函數(shù)應(yīng)當(dāng)移動(dòng) iterator 到下一個(gè)位置, 如果序列里什么都沒(méi)有剩下就返回 NULL. 這個(gè)方法的原型是:
void *next(struct seq_file *sfile, void *v, loff_t *pos);
這里, v 是從前一個(gè)對(duì) start 或者 next 的調(diào)用返回的 iterator, pos 是文件的當(dāng)前位置. next 應(yīng)當(dāng)遞增有 pos 指向的值; 根據(jù)你的 iterator 是如何工作的, 你可能(盡管可能不會(huì))需要遞增 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 來(lái)清理:
void stop(struct seq_file *sfile, void *v);
scull 實(shí)現(xiàn)沒(méi)有清理工作要做, 所以它的 stop 方法是空的.
設(shè)計(jì)上, 值得注意 seq_file 代碼在調(diào)用 start 和 stop 之間不睡眠或者進(jìn)行其他非原子性任務(wù). 你也肯定會(huì)看到在調(diào)用 start 后馬上有一個(gè) stop 調(diào)用. 因此, 對(duì)你的 start 方法來(lái)說(shuō)請(qǐng)求信號(hào)量或自旋鎖是安全的. 只要你的其他 seq_file 方法是原子的, 調(diào)用的整個(gè)序列是原子的. (如果這一段對(duì)你沒(méi)有意義, 在你讀了下一章后再回到這.)
在這些調(diào)用中, 內(nèi)核調(diào)用 show 方法來(lái)真正輸出有用的東西給用戶空間. 這個(gè)方法的原型是:
int show(struct seq_file *sfile, void *v);
這個(gè)方法應(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 對(duì)等體; 它采用常用的格式串和附加值參數(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ù)的對(duì)等體.
int seq_escape(struct seq_file m, const char s, const char *esc);
這個(gè)函數(shù)是 seq_puts 的對(duì)等體, 除了 s 中的任何也在 esc 中出現(xiàn)的字符以八進(jìn)制格式打印. esc 的一個(gè)通用值是"\t\n\", 它使內(nèi)嵌的空格不會(huì)搞亂輸出和可能搞亂 shell 腳本.
int seq_path(struct seq_file sfile, struct vfsmount m, struct dentry dentry, char esc);
這個(gè)函數(shù)能夠用來(lái)輸出和給定命令項(xiàng)關(guān)聯(lián)的文件名子. 它在設(shè)備驅(qū)動(dòng)中不可能有用; 我們是為了完整在此包含它.
回到我們的例子; 在 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" 值, 簡(jiǎn)單地是一個(gè) scull_dev 結(jié)構(gòu)指針.
現(xiàn)在已有了一個(gè)完整的 iterator 操作的集合, scull 必須包裝起它們, 并且連接它們到 /proc 中的一個(gè)文件. 第一步是填充一個(gè) 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
};
有那個(gè)結(jié)構(gòu)在, 我們必須創(chuàng)建一個(gè)內(nèi)核理解的文件實(shí)現(xiàn). 我們不使用前面描述過(guò)的 read_proc 方法; 在使用 seq_file 時(shí), 最好在一個(gè)稍低的級(jí)別上連接到 /proc. 那意味著創(chuàng)建一個(gè) file_operations 結(jié)構(gòu)(是的, 和字符驅(qū)動(dòng)使用的同樣結(jié)構(gòu)) 來(lái)實(shí)現(xiàn)所有內(nèi)核需要的操作, 來(lái)處理文件上的讀和移動(dòng). 幸運(yùn)的是, 這個(gè)任務(wù)是簡(jiǎn)單的. 第一步是創(chuàng)建一個(gè) 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, 我們有這個(gè)原型:
struct proc_dir_entry *create_proc_entry(const char *name,mode_t mode,struct proc_dir_entry *parent);
參數(shù)和它們的在 create_proc_read_entry 中的對(duì)等體相同: 文件名子, 它的位置, 以及父目錄.
有了上面代碼, scull 有一個(gè)新的 /proc 入口, 看來(lái)很象前面的一個(gè). 但是, 它是高級(jí)的, 因?yàn)樗还芩妮敵鲇卸嗝创? 它正確處理移動(dòng), 并且通常它是易讀和易維護(hù)的. 我們建議使用 seq_file , 來(lái)實(shí)現(xiàn)包含多個(gè)非常小數(shù)目的輸出行數(shù)的文件.
ioctl, 我們?cè)诘?1 章展示給你如何使用, 是一個(gè)系統(tǒng)調(diào)用, 作用于一個(gè)文件描述符; 它接收一個(gè)確定要進(jìn)行的命令的數(shù)字和(可選地)另一個(gè)參數(shù), 常常是一個(gè)指針. 作為一個(gè)使用 /proc 文件系統(tǒng)的替代, 你可以實(shí)現(xiàn)幾個(gè)用來(lái)調(diào)試用的 ioctl 命令. 這些命令可以從驅(qū)動(dòng)拷貝相關(guān)的數(shù)據(jù)結(jié)構(gòu)到用戶空間, 這里你可以檢查它們.
這種方式使用 ioctl 來(lái)獲取信息有些比使用 /proc 困難, 因?yàn)槟阈枰硪粋€(gè)程序來(lái)發(fā)出 ioctl 并且顯示結(jié)果. 必須編寫(xiě)這個(gè)程序, 編譯, 并且與你在測(cè)試的模塊保持同步. 另一方面, 驅(qū)動(dòng)側(cè)代碼可能容易過(guò)需要實(shí)現(xiàn)一個(gè) /proc 文件的代碼.
有時(shí)候 ioctl 是獲取信息最好的方法, 因?yàn)樗\(yùn)行比讀取 /proc 快. 如果在數(shù)據(jù)寫(xiě)到屏幕之前必須做一些事情, 獲取二進(jìn)制形式的數(shù)據(jù)比讀取一個(gè)文本文件要更有效. 另外, ioctl 不要求劃分?jǐn)?shù)據(jù)為小于一頁(yè)的片段.
ioctl 方法的另一個(gè)有趣的優(yōu)點(diǎn)是信息獲取命令可留在驅(qū)動(dòng)中, 當(dāng)調(diào)試被禁止時(shí). 不象對(duì)任何查看目錄的人(并且太多人可能奇怪"這個(gè)怪文件是什么")都可見(jiàn)的 /proc 文件, 不記入文檔的 ioctl 命令可能保持不為人知. 另外, 如果驅(qū)動(dòng)發(fā)生了怪異的事情, 它們?nèi)詫⒃谀抢? 唯一的缺點(diǎn)是模塊可能會(huì)稍微大些.
[14] 連字號(hào), 或者減號(hào), 是一個(gè)"魔術(shù)"標(biāo)識(shí)以阻止 syslogd 刷新文件到磁盤(pán)在每個(gè)新消息, 有關(guān)文檔在 syslog.conf(5), 一個(gè)值得一讀的 manpage.
更多建議: