Linux內核中的proc文件系統
簡介
procfs文件系統是內核中的一個特殊文件系統。它是一個虛擬文件系統: 它不是實際的存儲設備中的文件,而是存在于內存中。procfs中的文件是用來允許用戶空間的程序訪問內核中的某些信息(比如進程信息在 /proc/[0-9]+/中),或者用來做調試用途(/proc/ksyms,這個文件列出了已經登記的內核符號,這些符號給出了變量或函數的地址。每行給出一個符號的地址,符號名稱以及登記這個符號的模塊。程序ksyms、insmod和kmod使用這個文件。它還列出了正在運行的任務數,總任務數和***分配的PID。)
這個文檔描述了內核中procfs文件系統的使用。它以介紹所有和管理文件系統相關的函數開始。在函數介紹后,它還展示了怎么和用戶空間通信,和一些小技巧。在文檔的***,還給出了一個完整的例子。
注意/proc/sys中的文件屬于sysctl文件,它們不屬于procfs文件系統,被另外一套完全不同的api管理。
seq_file
procfs在處理大文件時有點笨拙。為了清理procfs文件系統并且使內核編程簡單些,引入了seq_file機制。seq_file機制提供了大量簡單的接口去實現大內核虛擬文件。
seq_file機制適用于你利用結構序列去創建一個返回給用戶空間的虛擬文件。要使用seq_file機制,你必須創建一個”iterator”對象,這個對象指向這個序列,并且能逐個指向這個序列中的對象,此外還要能輸出這個序列中的任一個對象。它聽起來復雜,實際上,操作過程相當簡單。接下來將用實際的例子展示到底怎么做。
首先,你必須包含頭文件<Linux/seq_file.h>。接下來,你必須創建迭代器方法:start, next, stop, and show。
start方法通常被首先調用。這個方法的函數原型是:
- void *start(struct seq_file *sfile, loff_t *pos);
sfile沒什么作用,通常被忽略。pos參數是一個整型,表示從哪個位置開始讀。關于位置的定義完全取決于函數實現;它不一定要是結果文件中的一個字節位置。 由于seq_file機制通常是利用一個特定的結構序列實現的,所以位置通常是一個指向序列中下一個結構體的指針。在wing驅動中,每一個設備表示序列中的一個結構,所以,入參pos代表g_pstWingDevices數組的索引。因此,在wing驅動中start方法的實現為:
- static void *wing_seq_start(struct seq_file *s, loff_t *pos)
- {
- if (*pos >= g_iWingDevicesNum)
- return NULL; /* No more to read */
- return g_pstWingDevices + *pos;
- }
返回值如果不為NULL,代表一個可以被迭代器使用的私有數據。
next函數應該移動迭代器到下一個位置,如果序列中沒有數據,返回NULL。這個方法的函數原型為:
- void *next(struct seq_file *sfile, void *v, loff_t *pos);
這里,參數v代表上一個函數調用(可能是start函數,或者是next函數)返回的迭代器,, 參數pos是文件中的當前位置。next函數應該改變pos的指向,具體是逐步改變還是跳躍改變取決于迭代器的工作機制。next函數在wing驅動中的實現為:
- static void* wing_seq_next(struct seq_file *s, void *v, loff_t *pos)
- {
- (*pos)++;
- if (*pos >= g_iWingDevicesNum)
- return NULL;
- return g_pstWingDevices + *pos;
- }
當內核停止了迭代器的工作,它調用stop函數清理現場:
- void stop(struct seq_file *sfile, void *v);
wing驅動沒有清理工作要做,所以stop函數為空。
- void wing_seq_stop(struct seq_file *sfile, void *v)
- {
- }
要是seq_file代碼在調用start和stop時不執行睡眠或是非原子的操作,那么這種機制將毫無意義。你要保證從start函數調用到stop函數調用是很短暫的。因此,在開始函數中獲得一個信號量或者自旋鎖是比較安全的做法。要是seq_file其他方法是原子的,整個調用鏈必須是原子的。
在這些函數調用中,內核調用call函數向內核空間輸出特性的信息。這個函數的函數原型是:
- int show(struct seq_file *sfile, void *v);
這個方法應該創建序列中由指示器v指定項的輸出。不能使用printk,而是使用以下這些特定函數:
- int seq_printf(struct seq_file *sfile, const char *fmt, ...)
這個函數是seq_file機制中類似于printf的實現;它使用通常的格式字符串和參數組成輸出字符串。你必須把show函數中的seq_file結構體傳給這個函數。如果它返回一個非零的值,表示buffer已經填充好,輸出被丟出去了。在大多數實現中,都選擇忽略返回值。
- int seq_putc(struct seq_file *sfile, char c);
- int seq_puts(struct seq_file *sfile, const char *s);
這兩個函數相當于用戶層的putc和puts。
- int seq_escape(struct seq_file *m, const char *s, const char *esc);
這個函數是 seq_puts 的對等體, 除了 s 中的任何也在 esc 中出現的字符以八進制格式打印. esc 的一個通用值是”\t\n\”, 它使內嵌的空格不會搞亂輸出和可能搞亂 shell 腳本.
- int seq_path(struct seq_file *sfile, struct vfsmount *m, struct dentry *dentry, char *esc);
這個函數能夠用來輸出和給定命令項關聯的文件名子. 它在設備驅動中不可能有用;我們是為了完整在此包含它.
wing設備中的show函數例子:
- static int wing_seq_show(struct seq_file *s, void *v)
- {
- ST_Wing_Dev_Type* pDev = (ST_Wing_Dev_Type* ) v;
- seq_printf(s, "\nThis Device is %i\n", pDev->iData);
- return 0;
- }
在我的例子中,我將一個ST_Wing_Dev_Type結構體表示為迭代器。
上面就是完整的迭代器操作,wing驅動必須將它們打包到一起好連接到procfs文件系統。首先要做的就是利用它們組成一個seq_operations結構體:
- static struct seq_operations s_stWingSeqOps = {
- .start = wing_seq_start,
- .next = wing_seq_next,
- .stop = wing_seq_stop,
- .show = wing_seq_show
- };
有了這個結構,我們必須創建一個內核能理解的文件實現。我們不使用前面說過的read_proc方法;在使用seq_file時, ***在一個稍低的級別上連接到procfs。這意味著創建一個file_operations(和字符設備一樣的結構),這個結構實現了內核對文件的reads和seeks操作。幸運的是,這個操做很簡單。首先創建一個把文件和seq_file方法聯接起來的open方法:
- static int wing_proc_open(struct inode *inode, struct file *file)
- {
- return seq_open(file, &s_stWingSeqOps);
- }
調用seq_open函數的時候將文件和上面定義的序列操作關聯到一起。open是唯一要我們實現的函數接口,所以我們的file_operations結構體是:
- static struct file_operations s_stWingProcFops = {
- .owner = THIS_MODULE,
- .open = wing_proc_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = seq_release
- };
***我們要在procfs文件系統中創建文件:
- proc_create("wingdevices", 0644, NULL, &s_stWingProcFops);
關鍵結構體
struct proc_dir_entry代表的是/proc目錄下的一個目錄或者文件,他是procfs文件系統的主要結構體,它的定義在/fs/internal.h中:
- /*
- * This is not completely implemented yet. The idea is to
- * create an in-memory tree (like the actual /proc filesystem
- * tree) of these proc_dir_entries, so that we can dynamically
- * add new files to /proc.
- *
- * The "next" pointer creates a linked list of one /proc directory,
- * while parent/subdir create the directory structure (every
- * /proc file has a parent, but "subdir" is NULL for all
- * non-directory entries).
- */
- struct proc_dir_entry {
- unsigned int low_ino;
- umode_t mode;
- nlink_t nlink;
- kuid_t uid;
- kgid_t gid;
- loff_t size;
- const struct inode_operations *proc_iops;
- const struct file_operations *proc_fops;
- struct proc_dir_entry *next, *parent, *subdir;
- void *data;
- atomic_t count; /* use count */
- atomic_t in_use; /* number of callers into module in progress; */
- /* negative -> it's going away RSN */
- struct completion *pde_unload_completion;
- struct list_head pde_openers; /* who did ->open, but not ->release */
- spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
- u8 namelen;
- char name[];
- };
主要接口
procfs應該包含的頭文件<linux/proc_fs.h>。
在3.x內核中procfs主要接口有:
- proc_symlink
- proc_mkdir
- proc_mkdir_data
- proc_mkdir_mode
- proc_create_data
- proc_create
- proc_set_size
- proc_set_user
- PDE_DATA
- proc_get_parent_data
- proc_remove
- remove_proc_entry
- remove_proc_subtree
proc_mkdir
說明:在/proc下創建目錄
函數原型:
- struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent)
參數:
- name
要創建的目錄名稱
- parent
父目錄,如果為NULL,表示直接在/proc下面創建目錄。
proc_mkdir_data
說明:在/proc下創建目錄
函數原型:
- struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode, struct proc_dir_entry *parent, void *data)
參數:
- name
要創建的目錄名稱
- mode
指定要創建目錄的權限
- parent
父目錄,如果為NULL,表示直接在/proc下面創建目錄。
- data
proc_create_data
說明:創建proc虛擬文件系統文件
函數原型:
- struct proc_dir_entry *proc_create_data(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops, void *data)
參數:
- name
你要創建的文件名。
- mode
為創建的文件指定權限
- parent
為你要在哪個文件夾下建立名字為name的文件,如:init_net.proc_net是要在/proc/net/下建立文件。
- proc_fops
為struct file_operations
- data
保存私有數據的指針,如不要為NULL。
例子:
- ////////////////////////test.c////////////////////////////////////////
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/types.h>
- #include <linux/slab.h>
- #include <linux/fs.h>
- #include <linux/proc_fs.h>
- #include <linux/seq_file.h>
- #include <net/net_namespace.h>
- #include <linux/mm.h>
- MODULE_LICENSE("GPL");
- typedef struct {
- int data1;
- int data2;
- }ST_Data_Info_Type;
- static ST_Data_Info_Type g_astDataInfo[2];
- static int test_proc_show(struct seq_file *m, void *v)
- {
- ST_Data_Info_Type* pInfo = (ST_Data_Info_Type*)m->private;
- if(pInfo != NULL)
- {
- seq_printf(m, "%d----%d\n", pInfo->data1, pInfo->data2);
- }
- return 0;
- }
- static int test_proc_open(struct inode *inode, struct file *file)
- {
- return single_open(file, test_proc_show, PDE_DATA(inode));
- }
- static const struct file_operations dl_file_ops = {
- .owner = THIS_MODULE,
- .open = test_proc_open,
- .read = seq_read,
- .llseek = seq_lseek,
- .release = single_release,
- };
- static struct proc_dir_entry *s_pstRootTestDir;
- void init_mem(void)
- {
- /* create /proc/test */
- s_pstRootTestDir = proc_mkdir("test", NULL);
- if (!s_pstRootTestDir)
- return;
- g_astDataInfo[0].data1=1;
- g_astDataInfo[0].data2=2;
- proc_create_data("proc_test1", 0644, s_pstRootTestDir, &dl_file_ops, &g_astDataInfo[0]);
- g_astDataInfo[1].data1=3;
- g_astDataInfo[1].data2=4;
- proc_create_data("proc_test2", 0644, s_pstRootTestDir, &dl_file_ops, &g_astDataInfo[1]);
- }
- static int __init test_module_init(void)
- {
- printk("[test]: module init\n");
- init_mem();
- return 0;
- }
- static void __exit test_module_exit(void)
- {
- printk("[test]: module exit\n");
- remove_proc_entry("proc_test1", s_pstRootTestDir);
- remove_proc_entry("proc_test2", s_pstRootTestDir);
- remove_proc_entry("test", NULL);
- }
- module_init(test_module_init);
- module_exit(test_module_exit);
proc_create
說明:創建proc虛擬文件系統文件
函數原型:
- struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops)
參數:
- name
你要創建的文件名。
- mode
為創建的文件指定權限
- parent
為你要在哪個文件夾下建立名字為name的文件,如:init_net.proc_net是要在/proc/net/下建立文件。
- proc_fops
為struct file_operations
注意:這個接口和proc_create_data的區別在于他不能保存私有數據指針。
PDE_DATA
獲取proc_create_data傳入的私有數據。
proc_symlink
說明:這個函數在procfs目錄下創建一個從name指向dest的符號鏈接. 它在用戶空間等效為ln -s dest name。
函數原型:
- struct proc_dir_entry *proc_symlink(const char *name, struct proc_dir_entry *parent, const char *dest)
參數:
- name
原始符號。
- parent
符號所在的目錄。
- dest
所要創建的符號鏈接名字。
remove_proc_entry
說明:刪除procfs文件系統中的文件或者目錄。
函數原型:
- void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
參數:
- name
要刪除的文件或者目錄名。
- parent
符號所在的目錄,如果為NULL,表示在/proc目錄下。






















