relayfs是一個快速的轉(zhuǎn)發(fā)(relay)數(shù)據(jù)的文件系統(tǒng),它以其功能而得名。它為那些需要從內(nèi)核空間轉(zhuǎn)發(fā)大量數(shù)據(jù)到用戶空間的工具和應用提供了快速有效的轉(zhuǎn)發(fā)機制。
Channel是relayfs文件系統(tǒng)定義的一個主要概念,每一個channel由一組內(nèi)核緩存組成,每一個CPU有一個對應于該channel 的內(nèi)核緩存,每一個內(nèi)核緩存用一個在relayfs文件系統(tǒng)中的文件文件表示,內(nèi)核使用relayfs提供的寫函數(shù)把需要轉(zhuǎn)發(fā)給用戶空間的數(shù)據(jù)快速地寫入當前CPU上的channel內(nèi)核緩存,用戶空間應用通過標準的文件I/O函數(shù)在對應的channel文件中可以快速地取得這些被轉(zhuǎn)發(fā)出的數(shù)據(jù)mmap 來。寫入到channel中的數(shù)據(jù)的格式完全取決于內(nèi)核中創(chuàng)建channel的模塊或子系統(tǒng)。
relayfs的用戶空間API:
relayfs實現(xiàn)了四個標準的文件I/O函數(shù),open、mmap、poll和close.
open(),打開一個channel在某一個CPU上的緩存對應的文件。
mmap(),把打開的channel緩存映射到調(diào)用者進程的內(nèi)存空間。
read (),讀取channel緩存,隨后的讀操作將看不到被該函數(shù)消耗的字節(jié),如果channel的操作模式為非覆蓋寫,那么用戶空間應用在有內(nèi)核模塊寫時仍 可以讀取,但是如果channel的操作模式為覆蓋式,那么在讀操作期間如果有內(nèi)核模塊進行寫,結(jié)果將無法預知,因此對于覆蓋式寫的channel,用戶 應當在確認在channel的寫完全結(jié)束后再進行讀。
poll(),用于通知用戶空間應用轉(zhuǎn)發(fā)數(shù)據(jù)跨越了子緩存的邊界,支持的輪詢標志有POLLIN、POLLRDNORM和POLLERR。
close(),關閉open函數(shù)返回的文件描述符,如果沒有進程或內(nèi)核模塊打開該channel緩存,close函數(shù)將釋放該channel緩存。
注意:用戶態(tài)應用在使用上述API時必須保證已經(jīng)掛載了relayfs文件系統(tǒng),但內(nèi)核在創(chuàng)建和使用channel時不需要relayfs已經(jīng)掛載。下面命令將把relayfs文件系統(tǒng)掛載到/mnt/relay。
mount -t relayfs relayfs /mnt/relay
relayfs內(nèi)核API:
relayfs提供給內(nèi)核的API包括四類:channel管理、寫函數(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)
寫函數(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)講過,每一個channel由一組channel緩存組成,每個CPU對應一個該channel的緩存,每一個緩存又由一個或多個子緩存組成,每一個緩存是子緩存組成的一個環(huán)型緩存。
函數(shù)relay_open用于創(chuàng)建一個channel并分配對應于每一個CPU的緩存,用戶空間應用通過在relayfs文件系統(tǒng)中對應的文件可以 訪問channel緩存,參數(shù)base_filename用于指定channel的文件名,relay_open函數(shù)將在relayfs文件系統(tǒng)中創(chuàng)建 base_filename0..base_filenameN-1,即每一個CPU對應一個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)建的目錄,誰創(chuàng)建的目錄,誰就負責在不用時負責刪除。參數(shù)subbuf_size用于指定channel緩存中每一個子緩存的大小,參數(shù)n_subbufs用于指定 channel緩存包含的子緩存數(shù),因此實際的channel緩存大小為(subbuf_size x n_subbufs),參數(shù)overwrite用于指定該channel的操作模式,relayfs提供了兩種寫模式,一種是覆蓋式寫,另一種是非覆蓋式 寫。使用哪一種模式完全取決于函數(shù)subbuf_start的實現(xiàn),覆蓋寫將在緩存已滿的情況下無條件地繼續(xù)從緩存的開始寫數(shù)據(jù),而不管這些數(shù)據(jù)是否已經(jīng) 被用戶應用讀取,因此寫操作決不失敗。在非覆蓋寫模式下,如果緩存滿了,寫將失敗,但內(nèi)核將在用戶空間應用讀取緩存數(shù)據(jù)時通過函數(shù) relay_subbufs_consumed()通知relayfs。如果用戶空間應用沒來得及消耗緩存中的數(shù)據(jù)或緩存已滿,兩種模式都將導致數(shù)據(jù)丟失,唯一的區(qū)別是,前者丟失數(shù)據(jù)在緩存開頭,而后者丟失數(shù)據(jù)在緩存末尾。一旦內(nèi)核再次調(diào)用函數(shù)relay_subbufs_consumed(),已滿的緩存將不再滿,因而可以繼續(xù)寫該緩存。當緩存滿了以后,relayfs將調(diào)用回調(diào)函數(shù)buf_full()來通知內(nèi)核模塊或子系統(tǒng)。當新的數(shù)據(jù)太大無法寫 入當前子緩存剩余的空間時,relayfs將調(diào)用回調(diào)函數(shù)subbuf_start()來通知內(nèi)核模塊或子系統(tǒng)將需要使用新的子緩存。內(nèi)核模塊需要在該回調(diào)函數(shù)中實現(xiàn)下述功能:
初始化新的子緩存;
如果1正確,完成當前子緩存;
如果2正確,返回是否正確完成子緩存切換;
在非覆蓋寫模式下,回調(diào)函數(shù)subbuf_start()應該如下實現(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;
}
如果當前緩存滿,即所有的子緩存都沒讀取,該函數(shù)返回0,指示子緩存切換沒有成功。當子緩存通過函數(shù)relay_subbufs_consumed ()被讀取后,讀取者將負責通知relayfs,函數(shù)relay_buf_full()在已經(jīng)有讀者讀取子緩存數(shù)據(jù)后返回0,在這種情況下,子緩存切換成 功進行。
在覆蓋寫模式下, subbuf_start()的實現(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()檢查,因為此模式下,緩存是環(huán)行的,可以無條件地寫。因此在此模式下,子緩存切換必定成功,函數(shù) relay_subbufs_consumed() 也無須調(diào)用。如果channel寫者沒有定義subbuf_start(),缺省的實現(xiàn)將被使用。 可以通過在回調(diào)函數(shù)subbuf_start()中調(diào)用輔助函數(shù)subbuf_start_reserve()在子緩存中預留頭空間,預留空間可以保存任 何需要的信息,如上面例子中,預留空間用于保存子緩存填充字節(jié)數(shù),在subbuf_start()實現(xiàn)中,前一個子緩存的填充值被設置。前一個子緩存的填 充值和指向前一個子緩存的指針一道作為subbuf_start()的參數(shù)傳遞給subbuf_start(),只有在子緩存完成后,才能知道填充值。 subbuf_start()也被在channel創(chuàng)建時分配每一個channel緩存的第一個子緩存時調(diào)用,以便預留頭空間,但在這種情況下,前一個子 緩存指針為NULL。
內(nèi)核模塊使用函數(shù)relay_write()或__relay_write()往channel緩存中寫需要轉(zhuǎn)發(fā)的數(shù)據(jù),它們的區(qū)別是前者失效了本 地中斷,而后者只搶占失效,因此前者可以在任何內(nèi)核上下文安全使用,而后者應當在沒有任何中斷上下文將寫channel緩存的情況下使用。這兩個函數(shù)沒有 返回值,因此用戶不能直接確定寫操作是否失敗,在緩存滿且寫模式為非覆蓋模式時,relayfs將通過回調(diào)函數(shù)buf_full來通知內(nèi)核模塊。
函數(shù)relay_reserve()用于在channel緩存中預留一段空間以便以后寫入,在那些沒有臨時緩存而直接寫入channel緩存的內(nèi)核 模塊可能需要該函數(shù),使用該函數(shù)的內(nèi)核模塊在實際寫這段預留的空間時可以通過調(diào)用relay_commit()來通知relayfs。當所有預留的空間全 部寫完并通過relay_commit通知relayfs后,relayfs將調(diào)用回調(diào)函數(shù)deliver()通知內(nèi)核模塊一個完整的子緩存已經(jīng)填滿。由于預留空間的操作并不在寫channel的內(nèi)核模塊完全控制之下,因此relay_reserve()不能很好地保護緩存,因此當內(nèi)核模塊調(diào)用 relay_reserve()時必須采取恰當?shù)耐綑C制。
當內(nèi)核模塊結(jié)束對channel的使用后需要調(diào)用relay_close() 來關閉channel,如果沒有任何用戶在引用該channel,它將和對應的緩存全部被釋放。
函數(shù)relay_flush()強制在所有的channel緩存上做一個子緩存切換,它在channel被關閉前使用來終止和處理最后的子緩存。
函數(shù)relay_reset()用于將一個channel恢復到初始狀態(tài),因而不必釋放現(xiàn)存的內(nèi)存映射并重新分配新的channel緩存就可以使用channel,但是該調(diào)用只有在該channel沒有任何用戶在寫的情況下才可以安全使用。
回調(diào)函數(shù)buf_mapped() 在channel緩存被映射到用戶空間時被調(diào)用。
回調(diào)函數(shù)buf_unmapped()在釋放該映射時被調(diào)用。內(nèi)核模塊可以通過它們觸發(fā)一些內(nèi)核操作,如開始或結(jié)束channel寫操作。
在源代碼包中給出了一個使用relayfs的示例程序relayfs_exam.c,它只包含一個內(nèi)核模塊,對于復雜的使用,需要應用程序配合。該模塊實現(xiàn)了類似于文章中seq_file示例實現(xiàn)的功能。
當然為了使用relayfs,用戶必須讓內(nèi)核支持relayfs,并且要mount它,下面是作者系統(tǒng)上的使用該模塊的輸出信息:
$ mkdir -p /relayfs
$ insmod 。/relayfs-exam.ko
$ mount -t relayfs relayfs /relayfs
$ cat /relayfs/example0
…
$
relayfs是一種比較復雜的內(nèi)核態(tài)與用戶態(tài)的數(shù)據(jù)交換方式,本例子程序只提供了一個較簡單的使用方式,對于復雜的使用,請參考relayfs用例頁面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”);
評論
查看更多