亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

在 Linux 下用戶空間與內(nèi)核空間數(shù)據(jù)交換的方式

系統(tǒng) 1845 0

發(fā)布日期: 2006 年 2 月 16 日

燚 楊 (yang.y.yi@gmail.com), 計算機科學(xué)碩士

簡介: 本系列文章包括兩篇,它們文詳細地地介紹了Linux系統(tǒng)下用戶空間與內(nèi)核空間數(shù)據(jù)交換的九種方式,包括內(nèi)核啟動參數(shù)、模塊參數(shù)與sysfs、sysctl、系統(tǒng)調(diào)用、netlink、procfs、seq_file、debugfs和relayfs,并給出具體的例子幫助讀者掌握這些技術(shù)的使用。
本文是該系列文章的第二篇,它介紹了procfs、seq_file、debugfs和relayfs,并結(jié)合給出的例子程序詳細地說明了它們?nèi)绾问褂谩?

一、procfs

procfs是比較老的一種用戶態(tài)與內(nèi)核態(tài)的數(shù)據(jù)交換方式,內(nèi)核的很多數(shù)據(jù)都是通過這種方式出口給用戶的,內(nèi)核的很多參數(shù)也是通過這種方式來讓用戶方便設(shè)置的。除了sysctl出口到/proc下的參數(shù),procfs提供的大部分內(nèi)核參數(shù)是只讀的。實際上,很多應(yīng)用嚴重地依賴于procfs,因此它幾乎是必不可少的組件。前面部分的幾個例子實際上已經(jīng)使用它來出口內(nèi)核數(shù)據(jù),但是并沒有講解如何使用,本節(jié)將講解如何使用procfs。

Procfs提供了如下API:
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
????????????????????????????????????????? struct proc_dir_entry *parent)

該函數(shù)用于創(chuàng)建一個正常的proc條目,參數(shù)name給出要建立的proc條目的名稱,參數(shù)mode給出了建立的該proc條目的訪問權(quán)限,參數(shù)parent指定建立的proc條目所在的目錄。如果要在/proc下建立proc條目,parent應(yīng)當為NULL。否則它應(yīng)當為proc_mkdir返回的struct proc_dir_entry結(jié)構(gòu)的指針。
extern void remove_proc_entry(const char *name, struct proc_dir_entry *parent)

該函數(shù)用于刪除上面函數(shù)創(chuàng)建的proc條目,參數(shù)name給出要刪除的proc條目的名稱,參數(shù)parent指定建立的proc條目所在的目錄。
struct proc_dir_entry *proc_mkdir(const char * name, struct proc_dir_entry *parent)

該函數(shù)用于創(chuàng)建一個proc目錄,參數(shù)name指定要創(chuàng)建的proc目錄的名稱,參數(shù)parent為該proc目錄所在的目錄。
extern struct proc_dir_entry *proc_mkdir_mode(const char *name, mode_t mode,
??????????????????????? struct proc_dir_entry *parent);
struct proc_dir_entry *proc_symlink(const char * name,
??????????????? struct proc_dir_entry * parent, const char * dest)

該函數(shù)用于建立一個proc條目的符號鏈接,參數(shù)name給出要建立的符號鏈接proc條目的名稱,參數(shù)parent指定符號連接所在的目錄,參數(shù)dest指定鏈接到的proc條目名稱。
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)

該函數(shù)用于建立一個規(guī)則的只讀proc條目,參數(shù)name給出要建立的proc條目的名稱,參數(shù)mode給出了建立的該proc條目的訪問權(quán)限,參數(shù)base指定建立的proc條目所在的目錄,參數(shù)read_proc給出讀去該proc條目的操作函數(shù),參數(shù)data為該proc條目的專用數(shù)據(jù),它將保存在該proc條目對應(yīng)的struct file結(jié)構(gòu)的private_data字段中。
struct proc_dir_entry *create_proc_info_entry(const char *name,
??????? mode_t mode, struct proc_dir_entry *base, get_info_t *get_info)

該函數(shù)用于創(chuàng)建一個info型的proc條目,參數(shù)name給出要建立的proc條目的名稱,參數(shù)mode給出了建立的該proc條目的訪問權(quán)限,參數(shù)base指定建立的proc條目所在的目錄,參數(shù)get_info指定該proc條目的get_info操作函數(shù)。實際上get_info等同于read_proc,如果proc條目沒有定義個read_proc,對該proc條目的read操作將使用get_info取代,因此它在功能上非常類似于函數(shù)create_proc_read_entry。
struct proc_dir_entry *proc_net_create(const char *name,
??????? mode_t mode, get_info_t *get_info)

該函數(shù)用于在/proc/net目錄下創(chuàng)建一個proc條目,參數(shù)name給出要建立的proc條目的名稱,參數(shù)mode給出了建立的該proc條目的訪問權(quán)限,參數(shù)get_info指定該proc條目的get_info操作函數(shù)。
struct proc_dir_entry *proc_net_fops_create(const char *name,
??????? mode_t mode, struct file_operations *fops)

該函數(shù)也用于在/proc/net下創(chuàng)建proc條目,但是它也同時指定了對該proc條目的文件操作函數(shù)。
void proc_net_remove(const char *name)

該函數(shù)用于刪除前面兩個函數(shù)在/proc/net目錄下創(chuàng)建的proc條目。參數(shù)name指定要刪除的proc名稱。

除了這些函數(shù),值得一提的是結(jié)構(gòu)struct proc_dir_entry,為了創(chuàng)建一了可寫的proc條目并指定該proc條目的寫操作函數(shù),必須設(shè)置上面的這些創(chuàng)建proc條目的函數(shù)返回的指針指向的struct proc_dir_entry結(jié)構(gòu)的write_proc字段,并指定該proc條目的訪問權(quán)限有寫權(quán)限。

為了使用這些接口函數(shù)以及結(jié)構(gòu)struct proc_dir_entry,用戶必須在模塊中包含頭文件linux/proc_fs.h。

在源代碼包中給出了procfs示例程序procfs_exam.c,它定義了三個proc文件條目和一個proc目錄條目,讀者在插入該模塊后應(yīng)當看到如下結(jié)構(gòu):
$ ls /proc/myproctest
aint??????? astring??????? bigprocfile
$

讀者可以通過cat和echo等文件操作函數(shù)來查看和設(shè)置這些proc文件。特別需要指出,bigprocfile是一個大文件(超過一個內(nèi)存頁),對于這種大文件,procfs有一些限制,因為它提供的緩存,只有一個頁,因此必須特別小心,并對超過頁的部分做特別的考慮,處理起來比較復(fù)雜并且很容易出錯,所有procfs并不適合于大數(shù)據(jù)量的輸入輸出,后面一節(jié)seq_file就是因為這一缺陷而設(shè)計的,當然seq_file依賴于procfs的一些基礎(chǔ)功能。

回頁首

二、seq_file

一般地,內(nèi)核通過在procfs文件系統(tǒng)下建立文件來向用戶空間提供輸出信息,用戶空間可以通過任何文本閱讀應(yīng)用查看該文件信息,但是procfs有一個缺陷,如果輸出內(nèi)容大于1個內(nèi)存頁,需要多次讀,因此處理起來很難,另外,如果輸出太大,速度比較慢,有時會出現(xiàn)一些意想不到的情況,Alexander Viro實現(xiàn)了一套新的功能,使得內(nèi)核輸出大文件信息更容易,該功能出現(xiàn)在2.4.15(包括2.4.15)以后的所有2.4內(nèi)核以及2.6內(nèi)核中,尤其是在2.6內(nèi)核中,已經(jīng)大量地使用了該功能。

要想使用seq_file功能,開發(fā)者需要包含頭文件linux/seq_file.h,并定義與設(shè)置一個seq_operations結(jié)構(gòu)(類似于file_operations結(jié)構(gòu)):
struct seq_operations {
??????? void * (*start) (struct seq_file *m, loff_t *pos);
??????? void (*stop) (struct seq_file *m, void *v);
??????? void * (*next) (struct seq_file *m, void *v, loff_t *pos);
??????? int (*show) (struct seq_file *m, void *v);
};

start函數(shù)用于指定seq_file文件的讀開始位置,返回實際讀開始位置,如果指定的位置超過文件末尾,應(yīng)當返回NULL,start函數(shù)可以有一個特殊的返回SEQ_START_TOKEN,它用于讓show函數(shù)輸出文件頭,但這只能在pos為0時使用,next函數(shù)用于把seq_file文件的當前讀位置移動到下一個讀位置,返回實際的下一個讀位置,如果已經(jīng)到達文件末尾,返回NULL,stop函數(shù)用于在讀完seq_file文件后調(diào)用,它類似于文件操作close,用于做一些必要的清理,如釋放內(nèi)存等,show函數(shù)用于格式化輸出,如果成功返回0,否則返回出錯碼。

Seq_file也定義了一些輔助函數(shù)用于格式化輸出:
int seq_putc(struct seq_file *m, char c);

函數(shù)seq_putc用于把一個字符輸出到seq_file文件。
int seq_puts(struct seq_file *m, const char *s);

函數(shù)seq_puts則用于把一個字符串輸出到seq_file文件。
int seq_escape(struct seq_file *, const char *, const char *);

函數(shù)seq_escape類似于seq_puts,只是,它將把第一個字符串參數(shù)中出現(xiàn)的包含在第二個字符串參數(shù)中的字符按照八進制形式輸出,也即對這些字符進行轉(zhuǎn)義處理。
int seq_printf(struct seq_file *, const char *, ...)
??????? __attribute__ ((format (printf,2,3)));

函數(shù)seq_printf是最常用的輸出函數(shù),它用于把給定參數(shù)按照給定的格式輸出到seq_file文件。
int seq_path(struct seq_file *, struct vfsmount *, struct dentry *, char *);

函數(shù)seq_path則用于輸出文件名,字符串參數(shù)提供需要轉(zhuǎn)義的文件名字符,它主要供文件系統(tǒng)使用。

在定義了結(jié)構(gòu)struct seq_operations之后,用戶還需要把打開seq_file文件的open函數(shù),以便該結(jié)構(gòu)與對應(yīng)于seq_file文件的struct file結(jié)構(gòu)關(guān)聯(lián)起來,例如,struct seq_operations定義為:
struct seq_operations exam_seq_ops = {
??? .start = exam_seq_start,
?? .stop = exam_seq_stop,
?? .next = exam_seq_next,
?? .show = exam_seq_show
};

那么,open函數(shù)應(yīng)該如下定義:
static int exam_seq_open(struct inode *inode, struct file *file)
{
??????? return seq_open(file, &exam_seq_ops);
};

注意,函數(shù)seq_open是seq_file提供的函數(shù),它用于把struct seq_operations結(jié)構(gòu)與seq_file文件關(guān)聯(lián)起來。 最后,用戶需要如下設(shè)置struct file_operations結(jié)構(gòu):
struct file_operations exam_seq_file_ops = {
??????? .owner?? = THIS_MODULE,
??????? .open??? = exm_seq_open,
??????? .read??? = seq_read,
??????? .llseek? = seq_lseek,
??????? .release = seq_release
};

注意,用戶僅需要設(shè)置open函數(shù),其它的都是seq_file提供的函數(shù)。

然后,用戶創(chuàng)建一個/proc文件并把它的文件操作設(shè)置為exam_seq_file_ops即可:
struct proc_dir_entry *entry;
entry = create_proc_entry("exam_seq_file", 0, NULL);
if (entry)
entry->proc_fops = &exam_seq_file_ops;

對于簡單的輸出,seq_file用戶并不需要定義和設(shè)置這么多函數(shù)與結(jié)構(gòu),它僅需定義一個show函數(shù),然后使用single_open來定義open函數(shù)就可以,以下是使用這種簡單形式的一般步驟:

1.定義一個show函數(shù)
int exam_show(struct seq_file *p, void *v)
{

}

2. 定義open函數(shù)
int exam_single_open(struct inode *inode, struct file *file)
{
??????? return(single_open(file, exam_show, NULL));
}

注意要使用single_open而不是seq_open。

3. 定義struct file_operations結(jié)構(gòu)
struct file_operations exam_single_seq_file_operations = {
??????? .open?????????? = exam_single_open,
??????? .read?????????? = seq_read,
??????? .llseek???????? = seq_lseek,
??????? .release??????? = single_release,
};

注意,如果open函數(shù)使用了single_open,release函數(shù)必須為single_release,而不是seq_release。 在源代碼包中給出了一個使用seq_file的具體例子seqfile_exam.c,它使用seq_file提供了一個查看當前系統(tǒng)運行的所有進程的/proc接口,在編譯并插入該模塊后,用戶通過命令"cat /proc/ exam_esq_file"可以查看系統(tǒng)的所有進程。

回頁首

三、debugfs

內(nèi)核開發(fā)者經(jīng)常需要向用戶空間應(yīng)用輸出一些調(diào)試信息,在穩(wěn)定的系統(tǒng)中可能根本不需要這些調(diào)試信息,但是在開發(fā)過程中,為了搞清楚內(nèi)核的行為,調(diào)試信息非常必要,printk可能是用的最多的,但它并不是最好的,調(diào)試信息只是在開發(fā)中用于調(diào)試,而printk將一直輸出,因此開發(fā)完畢后需要清除不必要的printk語句,另外如果開發(fā)者希望用戶空間應(yīng)用能夠改變內(nèi)核行為時,printk就無法實現(xiàn)。因此,需要一種新的機制,那只有在需要的時候使用,它在需要時通過在一個虛擬文件系統(tǒng)中創(chuàng)建一個或多個文件來向用戶空間應(yīng)用提供調(diào)試信息。

有幾種方式可以實現(xiàn)上述要求:

使用procfs,在/proc創(chuàng)建文件輸出調(diào)試信息,但是procfs對于大于一個內(nèi)存頁(對于x86是4K)的輸出比較麻煩,而且速度慢,有時回出現(xiàn)一些意想不到的問題。

使用sysfs(2.6內(nèi)核引入的新的虛擬文件系統(tǒng)),在很多情況下,調(diào)試信息可以存放在那里,但是sysfs主要用于系統(tǒng)管理,它希望每一個文件對應(yīng)內(nèi)核的一個變量,如果使用它輸出復(fù)雜的數(shù)據(jù)結(jié)構(gòu)或調(diào)試信息是非常困難的。

使用libfs創(chuàng)建一個新的文件系統(tǒng),該方法極其靈活,開發(fā)者可以為新文件系統(tǒng)設(shè)置一些規(guī)則,使用libfs使得創(chuàng)建新文件系統(tǒng)更加簡單,但是仍然超出了一個開發(fā)者的想象。

為了使得開發(fā)者更加容易使用這樣的機制,Greg Kroah-Hartman開發(fā)了debugfs(在2.6.11中第一次引入),它是一個虛擬文件系統(tǒng),專門用于輸出調(diào)試信息,該文件系統(tǒng)非常小,很容易使用,可以在配置內(nèi)核時選擇是否構(gòu)件到內(nèi)核中,在不選擇它的情況下,使用它提供的API的內(nèi)核部分不需要做任何改動。

使用debugfs的開發(fā)者首先需要在文件系統(tǒng)中創(chuàng)建一個目錄,下面函數(shù)用于在debugfs文件系統(tǒng)下創(chuàng)建一個目錄:
??????? struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);

參數(shù)name是要創(chuàng)建的目錄名,參數(shù)parent指定創(chuàng)建目錄的父目錄的dentry,如果為NULL,目錄將創(chuàng)建在debugfs文件系統(tǒng)的根目錄下。如果返回為-ENODEV,表示內(nèi)核沒有把debugfs編譯到其中,如果返回為NULL,表示其他類型的創(chuàng)建失敗,如果創(chuàng)建目錄成功,返回指向該目錄對應(yīng)的dentry條目的指針。

下面函數(shù)用于在debugfs文件系統(tǒng)中創(chuàng)建一個文件:
??????? struct dentry *debugfs_create_file(const char *name, mode_t mode,
?????????????????????????????????????? struct dentry *parent, void *data,
?????????????????????????????????????? struct file_operations *fops);

參數(shù)name指定要創(chuàng)建的文件名,參數(shù)mode指定該文件的訪問許可,參數(shù)parent指向該文件所在目錄,參數(shù)data為該文件特定的一些數(shù)據(jù),參數(shù)fops為實現(xiàn)在該文件上進行文件操作的fiel_operations結(jié)構(gòu)指針,在很多情況下,由seq_file(前面章節(jié)已經(jīng)講過)提供的文件操作實現(xiàn)就足夠了,因此使用debugfs很容易,當然,在一些情況下,開發(fā)者可能僅需要使用用戶應(yīng)用可以控制的變量來調(diào)試,debugfs也提供了4個這樣的API方便開發(fā)者使用:
??? struct dentry *debugfs_create_u8(const char *name, mode_t mode,
???????????????????????????????????? struct dentry *parent, u8 *value);
??? struct dentry *debugfs_create_u16(const char *name, mode_t mode,
????????????????????????????????????? struct dentry *parent, u16 *value);
??? struct dentry *debugfs_create_u32(const char *name, mode_t mode,
????????????????????????????????????? struct dentry *parent, u32 *value);
??? struct dentry *debugfs_create_bool(const char *name, mode_t mode,
struct dentry *parent, u32 *value);

參數(shù)name和mode指定文件名和訪問許可,參數(shù)value為需要讓用戶應(yīng)用控制的內(nèi)核變量指針。

當內(nèi)核模塊卸載時,Debugfs并不會自動清除該模塊創(chuàng)建的目錄或文件,因此對于創(chuàng)建的每一個文件或目錄,開發(fā)者必須調(diào)用下面函數(shù)清除:
??????? void debugfs_remove(struct dentry *dentry);

參數(shù)dentry為上面創(chuàng)建文件和目錄的函數(shù)返回的dentry指針。

在源代碼包中給出了一個使用debufs的示例模塊debugfs_exam.c,為了保證該模塊正確運行,必須讓內(nèi)核支持debugfs,debugfs是一個調(diào)試功能,因此它位于主菜單Kernel hacking,并且必須選擇Kernel debugging選項才能選擇,它的選項名稱為Debug Filesystem。為了在用戶態(tài)使用debugfs,用戶必須mount它,下面是在作者系統(tǒng)上的使用輸出:
$ mkdir -p /debugfs
$ mount -t debugfs debugfs /debugfs
$ insmod ./debugfs_exam.ko
$ ls /debugfs
debugfs-exam
$ ls /debugfs/debugfs-exam
u8_var??????? u16_var??????? u32_var??????? bool_var
$ cd /debugfs/debugfs-exam
$ cat u8_var
0
$ echo 200 > u8_var
$ cat u8_var
200
$ cat bool_var
N
$ echo 1 > bool_var
$ cat bool_var
Y

回頁首

四、relayfs

relayfs是一個快速的轉(zhuǎn)發(fā)(relay)數(shù)據(jù)的文件系統(tǒng),它以其功能而得名。它為那些需要從內(nèi)核空間轉(zhuǎn)發(fā)大量數(shù)據(jù)到用戶空間的工具和應(yīng)用提供了快速有效的轉(zhuǎn)發(fā)機制。

Channel是relayfs文件系統(tǒng)定義的一個主要概念,每一個channel由一組內(nèi)核緩存組成,每一個CPU有一個對應(yīng)于該channel的內(nèi)核緩存,每一個內(nèi)核緩存用一個在relayfs文件系統(tǒng)中的文件文件表示,內(nèi)核使用relayfs提供的寫函數(shù)把需要轉(zhuǎn)發(fā)給用戶空間的數(shù)據(jù)快速地寫入當前CPU上的channel內(nèi)核緩存,用戶空間應(yīng)用通過標準的文件I/O函數(shù)在對應(yīng)的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上的緩存對應(yīng)的文件。
mmap(),把打開的channel緩存映射到調(diào)用者進程的內(nèi)存空間。
read(),讀取channel緩存,隨后的讀操作將看不到被該函數(shù)消耗的字節(jié),如果channel的操作模式為非覆蓋寫,那么用戶空間應(yīng)用在有內(nèi)核模塊寫時仍可以讀取,但是如果channel的操作模式為覆蓋式,那么在讀操作期間如果有內(nèi)核模塊進行寫,結(jié)果將無法預(yù)知,因此對于覆蓋式寫的channel,用戶應(yīng)當在確認在channel的寫完全結(jié)束后再進行讀。
poll(),用于通知用戶空間應(yīng)用轉(zhuǎn)發(fā)數(shù)據(jù)跨越了子緩存的邊界,支持的輪詢標志有POLLIN、POLLRDNORM和POLLERR。
close(),關(guān)閉open函數(shù)返回的文件描述符,如果沒有進程或內(nèi)核模塊打開該channel緩存,close函數(shù)將釋放該channel緩存。

注意:用戶態(tài)應(yīng)用在使用上述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對應(yīng)一個該channel的緩存,每一個緩存又由一個或多個子緩存組成,每一個緩存是子緩存組成的一個環(huán)型緩存。

函數(shù)relay_open用于創(chuàng)建一個channel并分配對應(yīng)于每一個CPU的緩存,用戶空間應(yīng)用通過在relayfs文件系統(tǒng)中對應(yīng)的文件可以訪問channel緩存,參數(shù)base_filename用于指定channel的文件名,relay_open函數(shù)將在relayfs文件系統(tǒng)中創(chuàng)建base_filename0..base_filenameN-1,即每一個CPU對應(yīng)一個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)被用戶應(yīng)用讀取,因此寫操作決不失敗。在非覆蓋寫模式下,如果緩存滿了,寫將失敗,但內(nèi)核將在用戶空間應(yīng)用讀取緩存數(shù)據(jù)時通過函數(shù)relay_subbufs_consumed()通知relayfs。如果用戶空間應(yīng)用沒來得及消耗緩存中的數(shù)據(jù)或緩存已滿,兩種模式都將導(dǎo)致數(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()應(yīng)該如下實現(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;
??? 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()在子緩存中預(yù)留頭空間,預(yù)留空間可以保存任何需要的信息,如上面例子中,預(yù)留空間用于保存子緩存填充字節(jié)數(shù),在subbuf_start()實現(xiàn)中,前一個子緩存的填充值被設(shè)置。前一個子緩存的填充值和指向前一個子緩存的指針一道作為subbuf_start()的參數(shù)傳遞給subbuf_start(),只有在子緩存完成后,才能知道填充值。subbuf_start()也被在channel創(chuàng)建時分配每一個channel緩存的第一個子緩存時調(diào)用,以便預(yù)留頭空間,但在這種情況下,前一個子緩存指針為NULL。

內(nèi)核模塊使用函數(shù)relay_write()或__relay_write()往channel緩存中寫需要轉(zhuǎn)發(fā)的數(shù)據(jù),它們的區(qū)別是前者失效了本地中斷,而后者只搶占失效,因此前者可以在任何內(nèi)核上下文安全使用,而后者應(yīng)當在沒有任何中斷上下文將寫channel緩存的情況下使用。這兩個函數(shù)沒有返回值,因此用戶不能直接確定寫操作是否失敗,在緩存滿且寫模式為非覆蓋模式時,relayfs將通過回調(diào)函數(shù)buf_full來通知內(nèi)核模塊。

函數(shù)relay_reserve()用于在channel緩存中預(yù)留一段空間以便以后寫入,在那些沒有臨時緩存而直接寫入channel緩存的內(nèi)核模塊可能需要該函數(shù),使用該函數(shù)的內(nèi)核模塊在實際寫這段預(yù)留的空間時可以通過調(diào)用relay_commit()來通知relayfs。當所有預(yù)留的空間全部寫完并通過relay_commit通知relayfs后,relayfs將調(diào)用回調(diào)函數(shù)deliver()通知內(nèi)核模塊一個完整的子緩存已經(jīng)填滿。由于預(yù)留空間的操作并不在寫channel的內(nèi)核模塊完全控制之下,因此relay_reserve()不能很好地保護緩存,因此當內(nèi)核模塊調(diào)用relay_reserve()時必須采取恰當?shù)耐綑C制。

當內(nèi)核模塊結(jié)束對channel的使用后需要調(diào)用relay_close() 來關(guān)閉channel,如果沒有任何用戶在引用該channel,它將和對應(yīng)的緩存全部被釋放。

函數(shù)relay_flush()強制在所有的channel緩存上做一個子緩存切換,它在channel被關(guān)閉前使用來終止和處理最后的子緩存。

函數(shù)relay_reset()用于將一個channel恢復(fù)到初始狀態(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)核模塊,對于復(fù)雜的使用,需要應(yīng)用程序配合。該模塊實現(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是一種比較復(fù)雜的內(nèi)核態(tài)與用戶態(tài)的數(shù)據(jù)交換方式,本例子程序只提供了一個較簡單的使用方式,對于復(fù)雜的使用,請參考relayfs用例頁面http://relayfs.sourceforge.net/examples.html。

回頁首

小結(jié)

本文是該系列文章最后一篇,它詳細地講解了其余四種用戶空間與內(nèi)核空間的數(shù)據(jù)交換方式,并通過實際例子程序向讀者講解了如何在內(nèi)核開發(fā)中使用這些技術(shù),其中seq_file是單向的,即只能向內(nèi)核傳遞,而不能從內(nèi)核獲取,而另外三種方式均可以進行雙向數(shù)據(jù)交換,即既可以從用戶應(yīng)用傳遞給內(nèi)核,又可以從內(nèi)核傳遞給應(yīng)用態(tài)應(yīng)用。procfs一般用于向用戶出口少量的數(shù)據(jù)信息,或用戶通過它設(shè)置內(nèi)核變量從而控制內(nèi)核行為。seq_file實際上依賴于procfs,因此為了使用seq_file,必須使內(nèi)核支持procfs。debugfs用于內(nèi)核開發(fā)者調(diào)試使用,它比其他集中方式都方便,但是僅用于簡單類型的變量處理。relayfs是一種非常復(fù)雜的數(shù)據(jù)交換方式,要想準確使用并不容易,但是如果使用得當,它遠比procfs和seq_file功能強大。

參考資料
Linux 2.6.13的內(nèi)核源碼, http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.13.tar.bz2。
Linux 2.6.14的內(nèi)核源碼, http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.14.tar.bz2。
內(nèi)核文檔,Documentation/filesystems/proc.txt。
LWN, Driver porting: The seq_file interface, http://lwn.net/Articles/22355/。
LWN, debugfs, http://lwn.net/Articles/115405/。
Linux kernel procfs guide, http://kernelnewbies.org/documents/kdoc/procfs-guide/lkprocfsguide.html。
內(nèi)核文檔,Documentation/filesystems/relayfs.txt。
Relayfs project homepage, http://relayfs.sourceforge.net/。
relayfs usage examples, http://relayfs.sourceforge.net/examples.html。
relayfs source, http://prdownloads.sourceforge.net/relayfs/old-relayfs-051205-kernel-2.6.11.patch.bz2?download。
relayfs example applications, http://prdownloads.sourceforge.net/relayfs/relay-apps-0.91.tar.gz?download。

關(guān)于作者

楊燚,計算機科學(xué)碩士,畢業(yè)于中科院計算技術(shù)研究所,有4年的Linux內(nèi)核編程經(jīng)驗,目前從事嵌入式實時Linux的開發(fā)與性能測試。您可以通過 yang.y.yi@gmail.com 與作者聯(lián)系。

在 Linux 下用戶空間與內(nèi)核空間數(shù)據(jù)交換的方式,第 2 部分: procfs、seq_file、debugfs和relayfs


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 午夜精品久久久久久久99热 | 欧美黄色网页 | 国产精品一级香蕉一区 | 国产 色 | 狠狠色噜噜狠狠狠狠98 | 香蕉久久综合 | 精品麻豆国产 | 香蕉久久网站 | 中文字幕专区在线亚洲 | 亚洲一区在线视频观看 | 日韩三级一区二区 | 日本精品在线视频 | 成人国产精品一级毛片天堂 | 日韩 三级| 777奇米影视久久激情日韩欧美 | 狠狠香蕉 | 黄色一及毛片 | 天天爱天天操 | 99re66热这里只有精品17 | 亚洲国产一区二区三区a毛片 | 免费视频爱爱太爽了 | 成人毛片国产a | 成年女人aaaaa毛片 | 秘密影院久久综合亚洲综合 | 91最新免费地址入口 | 成人二区 | 欧美日韩一区二区在线观看 | 国产成人高清视频免费播放 | 激情五月婷婷综合 | 中文字幕第5页 | 一级毛片成人免费看a | 日韩一区精品视频在线看 | 国产成人综合洲欧美在线 | 9191精品国产免费不久久 | 成人影院免费在线观看 | 国产99久9在线 | 香蕉一区 | 一区二区三区www | 波多野结衣高清在线播放 | 成人免费观看视频久爱网 | 波多野结衣中文字幕一区二区三区 |