close

原文網址 http://blog.xuite.net/tzeng015/twblog/113272242

 

在 Linux 下用戶空間與內核空間資料交換的九種方式,包括
http://svn.openfoundry.org/vwebsdk/Reference/switch.txt

 

在 Linux 下用戶空間與內核空間資料交換的九種方式,包括
1. 內核啟動參數 (__setup)
2. 模組參數與 sysfs (module_param)
3. sysctl (register_sysctl_table)
4. 系統調用
5. netlink 是一種雙向的資料交換方式,它使用起來非常簡單高效,特別是它的廣播特性在一些應用中非常方便。
6. procfs 一般用于向用戶保留少量的數據訊息,或用戶透過它設置內核變量從而控制內核行為。
7. seq_file 是單向的,即只能向內核傳遞,而不能從內核獲取,其餘的均可以進行雙向資料交換,即既可以從用戶應用傳遞給內核,有可以從內核傳遞給應用態應用。
實際上倚賴于 procfs,因此為了使用 seq_file,必須使內核支持 procfs。
8. debugfs 用于內核開發者調試使用,它比其他集中模式都方便,但是僅用于簡單類型的變量處理。
9. relayfs 是一種非常複雜的數據交換模式,要想準確使用並不容易,但是如果使用得當,它遠比 procfs 和 seq_file 功能強大。

9. relayfs
relayfs 是一個快速的轉發(relay)數據的文件系統,它以其功能而得名。
它為那些需要從內核空間轉發大量數據到用戶空間的工具和應用提供了快速有效的轉發機製。
Channel 是 relayfs 文件系統定義的一個主要概念,
每一個 channel 由一組內核緩存組成,每一個CPU有一個對應于該 channel 的內核緩存,每一個內核緩存用一個在 relayfs 文件系統中的文件文件表示,
內核使用 relayfs 提供的寫函數把需要轉發給用戶空間的數據快速地寫入當前CPU上的 channel 內核緩存,
用戶空間應用透過標準的文件I/O函數在對應的 channel 文件中可以快速地取得這些被轉發出的數據 mmap 來。
寫入到 channel 中的數據的格式完全取決于內核中創建 channel 的模塊或子系統。

relayfs 的用戶空間API︰relayfs實現了五個標準的文件I/O函數, open, mmap, read, 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(base_filename, parent, subbuf_size, n_subbufs, overwrite, callbacks)
用于創建一個 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() 來通知內核模塊或子系統將需要使用新的子緩存。
內核模塊需要在該回調函數 callbacks 中實現下述功能︰
初始化新的子緩存;
如果1正確,完成當前子緩存;
如果2正確,返回是否正確完成子緩存切換;
在非覆蓋寫模式下,回調函數 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;

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(chan, length)
用于在 channel 緩存中預留一段空間以便以後寫入,在那些沒有臨時緩存而直接寫入 channel 緩存的內核模塊可能需要該函數,
使用該函數的內核模塊在實際寫這段預留的空間時可以透過調用 relay_commit() 來通知 relayfs。
當所有預留的空間全部寫完並透過 relay_commit 通知 relayfs 后,relayfs 將調用回調函數 deliver() 通知內核模塊一個完整的子緩存已經填滿。
由于預留空間的操作並不在寫 channel 的內核模塊完全控制之下,因此 relay_reserve() 不能很好地保護緩存,因此當內核模塊調用 relay_reserve() 時必須採取恰當的同步機製。
當內核模塊結束對 channel 的使用后需要調用 relay_close() 來關閉 channel,如果沒有任何用戶在引用該 channel,它將和對應的緩存全部被釋放。

relay_flush(chan)
強製在所有的 channel 緩存上做一個子緩存切換,它在 channel 被關閉前使用來終止和處理最後的子緩存。

relay_reset(chan)
用于將一個 channel 恢復到初始狀態,因而不必釋放現存的內存映射並重新分發新的 channel 緩存就可以使用 channel,但是該調用只有在該 channel 沒有任何用戶在寫的情況下才可以安全使用。

buf_mapped(buf, filp)
在channel緩存被映射到用戶空間時被調用。

buf_unmapped(buf, filp)
在釋放該映射時被調用。
內核模塊可以透過它們觸發一些內核操作,如開始或結束channel寫操作。
在 program-examples 中給出了一個使用 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

參考資料
1. 內核源碼
2. Linux Device Drivers, Third Edition, http://lwn.net/Kernel/LDD3。
3. 內核文檔,Documentation/filesystems/sysfs.txt。
4. 內核文檔,Documentation/sysctl/*。
5. 內核文檔,Documentation/filesystems/proc.txt。
6. 內核文檔,Documentation/filesystems/relayfs.txt。
5. Linux Kernel Module Programming Guide,http://ldp.icenet.is/LDP/lkmpg/2.6/html/lkmpg.html。
7. http://www.wlug.org.nz/LinuxNetlinkSockets。
9. netlink手冊, netlink(7)。 Extending netlink, http://lwn.net/Articles/131802/。
5. LWN, Driver porting: The seq_file interface, http://lwn.net/Articles/22355/。
6. LWN, debugfs, http://lwn.net/Articles/115405/。
7. Linux kernel procfs guide, http://kernelnewbies.org/documents/kdoc/procfs-guide/lkprocfsguide.html。
9. Relayfs project homepage, http://relayfs.sourceforge.net/。
10. relayfs usage examples, http://relayfs.sourceforge.net/examples.html。
11. relayfs source, http://prdownloads.sourceforge.net/relayfs/old-relayfs-051205-kernel-2.6.11.patch.bz2
relayfs example applications, http://prdownloads.sourceforge.net/relayfs/relay-apps-0.91.tar.gz

arrow
arrow

    橘 發表在 痞客邦 留言(0) 人氣()