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