relayfs是一個(gè)快速的轉(zhuǎn)發(fā)(relay)數(shù)據(jù)的文件系統(tǒng),它以其功能而得名。它為那些需要從內(nèi)核空間轉(zhuǎn)發(fā)大量數(shù)據(jù)到用戶空間的工具和應(yīng)用提供了快速有效的轉(zhuǎn)發(fā)機(jī)制。
Channel是relayfs文件系統(tǒng)定義的一個(gè)主要概念,每一個(gè)channel由一組內(nèi)核緩存組成,每一個(gè)CPU有一個(gè)對(duì)應(yīng)于該channel 的內(nèi)核緩存,每一個(gè)內(nèi)核緩存用一個(gè)在relayfs文件系統(tǒng)中的文件文件表示,內(nèi)核使用relayfs提供的寫(xiě)函數(shù)把需要轉(zhuǎn)發(fā)給用戶空間的數(shù)據(jù)快速地寫(xiě)入當(dāng)前CPU上的channel內(nèi)核緩存,用戶空間應(yīng)用通過(guò)標(biāo)準(zhǔn)的文件I/O函數(shù)在對(duì)應(yīng)的channel文件中可以快速地取得這些被轉(zhuǎn)發(fā)出的數(shù)據(jù)mmap 來(lái)。寫(xiě)入到channel中的數(shù)據(jù)的格式完全取決于內(nèi)核中創(chuàng)建channel的模塊或子系統(tǒng)。
relayfs的用戶空間API:
relayfs實(shí)現(xiàn)了四個(gè)標(biāo)準(zhǔn)的文件I/O函數(shù),open、mmap、poll和close.
open(),打開(kāi)一個(gè)channel在某一個(gè)CPU上的緩存對(duì)應(yīng)的文件。
mmap(),把打開(kāi)的channel緩存映射到調(diào)用者進(jìn)程的內(nèi)存空間。
read (),讀取channel緩存,隨后的讀操作將看不到被該函數(shù)消耗的字節(jié),如果channel的操作模式為非覆蓋寫(xiě),那么用戶空間應(yīng)用在有內(nèi)核模塊寫(xiě)時(shí)仍 可以讀取,但是如果channel的操作模式為覆蓋式,那么在讀操作期間如果有內(nèi)核模塊進(jìn)行寫(xiě),結(jié)果將無(wú)法預(yù)知,因此對(duì)于覆蓋式寫(xiě)的channel,用戶 應(yīng)當(dāng)在確認(rèn)在channel的寫(xiě)完全結(jié)束后再進(jìn)行讀。
poll(),用于通知用戶空間應(yīng)用轉(zhuǎn)發(fā)數(shù)據(jù)跨越了子緩存的邊界,支持的輪詢標(biāo)志有POLLIN、POLLRDNORM和POLLERR。
close(),關(guān)閉open函數(shù)返回的文件描述符,如果沒(méi)有進(jìn)程或內(nèi)核模塊打開(kāi)該channel緩存,close函數(shù)將釋放該channel緩存。
注意:用戶態(tài)應(yīng)用在使用上述API時(shí)必須保證已經(jīng)掛載了relayfs文件系統(tǒng),但內(nèi)核在創(chuàng)建和使用channel時(shí)不需要relayfs已經(jīng)掛載。下面命令將把relayfs文件系統(tǒng)掛載到/mnt/relay。
mount -t relayfs relayfs /mnt/relay
relayfs內(nèi)核API:
relayfs提供給內(nèi)核的API包括四類:channel管理、寫(xiě)函數(shù)、回調(diào)函數(shù)和輔助函數(shù)。
Channel管理函數(shù)包括:
relay_open(base_filename, parent, subbuf_size, n_subbufs, overwrite, callbacks)
relay_close(chan)
relay_flush(chan)
relay_reset(chan)
relayfs_create_dir(name, parent)
relayfs_remove_dir(dentry)
relay_commit(buf, reserved, count)
relay_subbufs_consumed(chan, cpu, subbufs_consumed)
寫(xiě)函數(shù)包括:
relay_write(chan, data, length)
__relay_write(chan, data, length)
relay_reserve(chan, length)
回調(diào)函數(shù)包括:
subbuf_start(buf, subbuf, prev_subbuf_idx, prev_subbuf)
buf_mapped(buf, filp)
buf_unmapped(buf, filp)
輔助函數(shù)包括:
relay_buf_full(buf)
subbuf_start_reserve(buf, length)
前面已經(jīng)講過(guò),每一個(gè)channel由一組channel緩存組成,每個(gè)CPU對(duì)應(yīng)一個(gè)該channel的緩存,每一個(gè)緩存又由一個(gè)或多個(gè)子緩存組成,每一個(gè)緩存是子緩存組成的一個(gè)環(huán)型緩存。
函數(shù)relay_open用于創(chuàng)建一個(gè)channel并分配對(duì)應(yīng)于每一個(gè)CPU的緩存,用戶空間應(yīng)用通過(guò)在relayfs文件系統(tǒng)中對(duì)應(yīng)的文件可以 訪問(wèn)channel緩存,參數(shù)base_filename用于指定channel的文件名,relay_open函數(shù)將在relayfs文件系統(tǒng)中創(chuàng)建 base_filename0..base_filenameN-1,即每一個(gè)CPU對(duì)應(yīng)一個(gè)channel文件,其中N為CPU數(shù),缺省情況下,這些文件將建立在relayfs文件系統(tǒng)的根目錄下,但如果參數(shù)parent非空,該函數(shù)將把channel文件創(chuàng)建于parent目錄下,parent目錄使 用函數(shù)relay_create_dir創(chuàng)建,函數(shù)relay_remove_dir用于刪除由函數(shù)relay_create_dir創(chuàng)建的目錄,誰(shuí)創(chuàng)建的目錄,誰(shuí)就負(fù)責(zé)在不用時(shí)負(fù)責(zé)刪除。參數(shù)subbuf_size用于指定channel緩存中每一個(gè)子緩存的大小,參數(shù)n_subbufs用于指定 channel緩存包含的子緩存數(shù),因此實(shí)際的channel緩存大小為(subbuf_size x n_subbufs),參數(shù)overwrite用于指定該channel的操作模式,relayfs提供了兩種寫(xiě)模式,一種是覆蓋式寫(xiě),另一種是非覆蓋式 寫(xiě)。使用哪一種模式完全取決于函數(shù)subbuf_start的實(shí)現(xiàn),覆蓋寫(xiě)將在緩存已滿的情況下無(wú)條件地繼續(xù)從緩存的開(kāi)始寫(xiě)數(shù)據(jù),而不管這些數(shù)據(jù)是否已經(jīng) 被用戶應(yīng)用讀取,因此寫(xiě)操作決不失敗。在非覆蓋寫(xiě)模式下,如果緩存滿了,寫(xiě)將失敗,但內(nèi)核將在用戶空間應(yīng)用讀取緩存數(shù)據(jù)時(shí)通過(guò)函數(shù) relay_subbufs_consumed()通知relayfs。如果用戶空間應(yīng)用沒(méi)來(lái)得及消耗緩存中的數(shù)據(jù)或緩存已滿,兩種模式都將導(dǎo)致數(shù)據(jù)丟失,唯一的區(qū)別是,前者丟失數(shù)據(jù)在緩存開(kāi)頭,而后者丟失數(shù)據(jù)在緩存末尾。一旦內(nèi)核再次調(diào)用函數(shù)relay_subbufs_consumed(),已滿的緩存將不再滿,因而可以繼續(xù)寫(xiě)該緩存。當(dāng)緩存滿了以后,relayfs將調(diào)用回調(diào)函數(shù)buf_full()來(lái)通知內(nèi)核模塊或子系統(tǒng)。當(dāng)新的數(shù)據(jù)太大無(wú)法寫(xiě) 入當(dāng)前子緩存剩余的空間時(shí),relayfs將調(diào)用回調(diào)函數(shù)subbuf_start()來(lái)通知內(nèi)核模塊或子系統(tǒng)將需要使用新的子緩存。內(nèi)核模塊需要在該回調(diào)函數(shù)中實(shí)現(xiàn)下述功能:
初始化新的子緩存;
如果1正確,完成當(dāng)前子緩存;
如果2正確,返回是否正確完成子緩存切換;
在非覆蓋寫(xiě)模式下,回調(diào)函數(shù)subbuf_start()應(yīng)該如下實(shí)現(xiàn):
static int subbuf_start(struct rchan_buf *buf, void *subbuf, void *prev_subbuf, unsigned intprev_padding)
{
if (prev_subbuf)
*((unsigned *)prev_subbuf) = prev_padding;
if (relay_buf_full(buf))
return 0;
subbuf_start_reserve(buf, sizeof(unsigned int));
return 1;
}
如果當(dāng)前緩存滿,即所有的子緩存都沒(méi)讀取,該函數(shù)返回0,指示子緩存切換沒(méi)有成功。當(dāng)子緩存通過(guò)函數(shù)relay_subbufs_consumed ()被讀取后,讀取者將負(fù)責(zé)通知relayfs,函數(shù)relay_buf_full()在已經(jīng)有讀者讀取子緩存數(shù)據(jù)后返回0,在這種情況下,子緩存切換成 功進(jìn)行。
在覆蓋寫(xiě)模式下, subbuf_start()的實(shí)現(xiàn)與非覆蓋模式類似:
static int subbuf_start(struct rchan_buf *buf, void *subbuf, void *prev_subbuf, unsigned int prev_padding)
{
if (prev_subbuf)
*((unsigned *)prev_subbuf) = prev_padding;
subbuf_start_reserve(buf, sizeof(unsigned int));
return 1;
}
只是不做relay_buf_full()檢查,因?yàn)榇四J较拢彺媸黔h(huán)行的,可以無(wú)條件地寫(xiě)。因此在此模式下,子緩存切換必定成功,函數(shù) relay_subbufs_consumed() 也無(wú)須調(diào)用。如果channel寫(xiě)者沒(méi)有定義subbuf_start(),缺省的實(shí)現(xiàn)將被使用。 可以通過(guò)在回調(diào)函數(shù)subbuf_start()中調(diào)用輔助函數(shù)subbuf_start_reserve()在子緩存中預(yù)留頭空間,預(yù)留空間可以保存任 何需要的信息,如上面例子中,預(yù)留空間用于保存子緩存填充字節(jié)數(shù),在subbuf_start()實(shí)現(xiàn)中,前一個(gè)子緩存的填充值被設(shè)置。前一個(gè)子緩存的填 充值和指向前一個(gè)子緩存的指針一道作為subbuf_start()的參數(shù)傳遞給subbuf_start(),只有在子緩存完成后,才能知道填充值。 subbuf_start()也被在channel創(chuàng)建時(shí)分配每一個(gè)channel緩存的第一個(gè)子緩存時(shí)調(diào)用,以便預(yù)留頭空間,但在這種情況下,前一個(gè)子 緩存指針為NULL。
內(nèi)核模塊使用函數(shù)relay_write()或__relay_write()往channel緩存中寫(xiě)需要轉(zhuǎn)發(fā)的數(shù)據(jù),它們的區(qū)別是前者失效了本 地中斷,而后者只搶占失效,因此前者可以在任何內(nèi)核上下文安全使用,而后者應(yīng)當(dāng)在沒(méi)有任何中斷上下文將寫(xiě)channel緩存的情況下使用。這兩個(gè)函數(shù)沒(méi)有 返回值,因此用戶不能直接確定寫(xiě)操作是否失敗,在緩存滿且寫(xiě)模式為非覆蓋模式時(shí),relayfs將通過(guò)回調(diào)函數(shù)buf_full來(lái)通知內(nèi)核模塊。
函數(shù)relay_reserve()用于在channel緩存中預(yù)留一段空間以便以后寫(xiě)入,在那些沒(méi)有臨時(shí)緩存而直接寫(xiě)入channel緩存的內(nèi)核 模塊可能需要該函數(shù),使用該函數(shù)的內(nèi)核模塊在實(shí)際寫(xiě)這段預(yù)留的空間時(shí)可以通過(guò)調(diào)用relay_commit()來(lái)通知relayfs。當(dāng)所有預(yù)留的空間全 部寫(xiě)完并通過(guò)relay_commit通知relayfs后,relayfs將調(diào)用回調(diào)函數(shù)deliver()通知內(nèi)核模塊一個(gè)完整的子緩存已經(jīng)填滿。由于預(yù)留空間的操作并不在寫(xiě)channel的內(nèi)核模塊完全控制之下,因此relay_reserve()不能很好地保護(hù)緩存,因此當(dāng)內(nèi)核模塊調(diào)用 relay_reserve()時(shí)必須采取恰當(dāng)?shù)耐綑C(jī)制。
當(dāng)內(nèi)核模塊結(jié)束對(duì)channel的使用后需要調(diào)用relay_close() 來(lái)關(guān)閉channel,如果沒(méi)有任何用戶在引用該channel,它將和對(duì)應(yīng)的緩存全部被釋放。
函數(shù)relay_flush()強(qiáng)制在所有的channel緩存上做一個(gè)子緩存切換,它在channel被關(guān)閉前使用來(lái)終止和處理最后的子緩存。
函數(shù)relay_reset()用于將一個(gè)channel恢復(fù)到初始狀態(tài),因而不必釋放現(xiàn)存的內(nèi)存映射并重新分配新的channel緩存就可以使用channel,但是該調(diào)用只有在該channel沒(méi)有任何用戶在寫(xiě)的情況下才可以安全使用。
回調(diào)函數(shù)buf_mapped() 在channel緩存被映射到用戶空間時(shí)被調(diào)用。
回調(diào)函數(shù)buf_unmapped()在釋放該映射時(shí)被調(diào)用。內(nèi)核模塊可以通過(guò)它們觸發(fā)一些內(nèi)核操作,如開(kāi)始或結(jié)束channel寫(xiě)操作。
在源代碼包中給出了一個(gè)使用relayfs的示例程序relayfs_exam.c,它只包含一個(gè)內(nèi)核模塊,對(duì)于復(fù)雜的使用,需要應(yīng)用程序配合。該模塊實(shí)現(xiàn)了類似于文章中seq_file示例實(shí)現(xiàn)的功能。
當(dāng)然為了使用relayfs,用戶必須讓內(nèi)核支持relayfs,并且要mount它,下面是作者系統(tǒng)上的使用該模塊的輸出信息:
$ mkdir -p /relayfs
$ insmod 。/relayfs-exam.ko
$ mount -t relayfs relayfs /relayfs
$ cat /relayfs/example0
…
$
relayfs是一種比較復(fù)雜的內(nèi)核態(tài)與用戶態(tài)的數(shù)據(jù)交換方式,本例子程序只提供了一個(gè)較簡(jiǎn)單的使用方式,對(duì)于復(fù)雜的使用,請(qǐng)參考relayfs用例頁(yè)面http://relayfs.sourceforge.net/examples.html。
//kernel module: relayfs-exam.c
#include 《linux/module.h》
#include 《linux/relayfs_fs.h》
#include 《linux/string.h》
#include 《linux/sched.h》
#define WRITE_PERIOD (HZ * 60)
static struct rchan * chan;
static size_t subbuf_size = 65536;
static size_t n_subbufs = 4;
static char buffer[256];
void relayfs_exam_write(unsigned long data);
static DEFINE_TIMER(relayfs_exam_timer, relayfs_exam_write, 0, 0);
void relayfs_exam_write(unsigned long data)
{
int len;
task_t * p = NULL;
len = sprintf(buffer, “Current all the processes:\n”);
len += sprintf(buffer + len, “process name\t\tpid\n”);
relay_write(chan, buffer, len);
for_each_process(p) {
len = sprintf(buffer, “%s\t\t%d\n”, p-》comm, p-》pid);
relay_write(chan, buffer, len);
}
len = sprintf(buffer, “\n\n”);
relay_write(chan, buffer, len);
relayfs_exam_timer.expires = jiffies + WRITE_PERIOD;
add_timer(&relayfs_exam_timer);
}
/*
* subbuf_start() relayfs callback.
*
* Defined so that we can 1) reserve padding counts in the sub-buffers, and
* 2) keep a count of events dropped due to the buffer-full condition.
*/
static int subbuf_start(struct rchan_buf *buf,
void *subbuf,
void *prev_subbuf,
unsigned int prev_padding)
{
if (prev_subbuf)
*((unsigned *)prev_subbuf) = prev_padding;
if (relay_buf_full(buf))
return 0;
subbuf_start_reserve(buf, sizeof(unsigned int));
return 1;
}
/*
* relayfs callbacks
*/
static struct rchan_callbacks relayfs_callbacks =
{
.subbuf_start = subbuf_start,
};
/**
* module init - creates channel management control files
*
* Returns 0 on success, negative otherwise.
*/
static int init(void)
{
chan = relay_open(“example”, NULL, subbuf_size,
n_subbufs, &relayfs_callbacks);
if (!chan) {
printk(“relay channel creation failed.\n”);
return 1;
}
relayfs_exam_timer.expires = jiffies + WRITE_PERIOD;
add_timer(&relayfs_exam_timer);
return 0;
}
static void cleanup(void)
{
del_timer_sync(&relayfs_exam_timer);
if (chan) {
relay_close(chan);
chan = NULL;
}
}
module_init(init);
module_exit(cleanup);
MODULE_LICENSE(“GPL”);
評(píng)論