Linux核心通訊之---proc檔案系統(詳解)
使用 /proc 檔案系統來訪問 Linux 核心的內容,這個虛擬檔案系統在核心空間和用戶空間之間打開了一個通訊視窗:
/proc 檔案系統是一個虛擬檔案系統,通過它可以使用一種新的方法在 Linux核心空間和使用者間之間進行通訊。在 /proc 檔案系統中,我們可以將對虛擬檔案的讀寫作為與核心中實體進行通訊的一種手段,但是與普通檔案不同的是,這些虛擬檔案的內容都是動態建立的。本文對 /proc 虛擬檔案系統進行了介紹,並展示了它的用法。
最初開發
/proc 檔案系統是為了提供有關係統中程序的資訊。但是由於這個檔案系統非常有用,因此核心中的很多元素也開始使用它來報告資訊,或啟用動態執行時配置。清單 1 是對 /proc 中部分元素進行一次互動查詢的結果。它顯示的是 /proc 檔案系統的根目錄中的內容。注意,在左邊是一系列數字編號的檔案。每個實際上都是一個目錄,表示系統中的一個程序。由於在 GNU/Linux 中建立的第一個程序是 init
程序,因此它的 process-id
為 1。然後對這個目錄執行一個 ls
命令,這會顯示很多檔案。每個檔案都提供了有關這個特殊程序的詳細資訊。/proc
中另外一些有趣的檔案有:cpuinfo
,它標識了處理器的型別和速度;pci
,顯示在
PCI 總線上找到的裝置;modules
,標識了當前載入到核心中的模組。
另外,我們還可以使用 sysctl
來配置這些核心條目。/proc
檔案系統並不是 GNU/Linux 系統中的惟一一個虛擬檔案系統。在這種系統上,sysfs 是一個與 /proc
類似的檔案系統,但是它的組織更好(從 /proc 中學習了很多教訓)。不過 /proc 已經確立了自己的地位,因此即使 sysfs 與 /proc 相比有一些優點,/proc 也依然會存在。還有一個
這些檔案的解釋和意義如下:
cmdline:系統啟動時輸入給核心命令列引數 |
可載入核心模組(LKM)是用來展示 /proc 檔案系統的一種簡單方法,這是因為這是一種用來動態地向 Linux 核心新增或刪除程式碼的新方法。LKM 也是 Linux 核心中為裝置驅動程式和檔案系統使用的一種流行機制。如果你曾經重新編譯過 Linux 核心,就可能會發現在核心的配置過程中,有很多裝置驅動程式和其他核心元素都被編譯成了模組。如果一個驅動程式被直接編譯到了核心中,那麼即使這個驅動程式沒有執行,它的程式碼和靜態資料也會佔據一部分空間。但是如果這個驅動程式被編譯成一個模組,就只有在需要記憶體並將其載入到核心時才會真正佔用記憶體空間。
整合到 /proc 檔案系統中
核心程式設計師可以使用的標準 API,LKM 程式設計師也可以使用。
方法一:(create_proc_entry建立proc檔案)
1.1 .建立目錄:
- struct proc_dir_entry *proc_mkdir(constchar *name,
- struct proc_dir_entry *parent);
1.2 .建立proc檔案:
- struct proc_dir_entry *create_proc_entry( constchar *name, mode_t mode,
- struct proc_dir_entry *parent );
create_proc_entry函式用於建立一個一般的proc檔案,其中name是檔名,比如“hello”,mode是檔案模式,parent是要建立的proc檔案的父目錄(若parent
= NULL則建立在/proc目錄下)。create_proc_entry
的返回值是一個proc_dir_entry
指標(或者為 NULL,說明在 create
時發生了錯誤)。然後就可以使用這個返回的指標來配置這個虛擬檔案的其他引數,例如在對該檔案執行讀操作時應該呼叫的函式。
- struct proc_dir_entry {
- ......
- const struct file_operations *proc_fops; <==檔案操作結構體
- struct proc_dir_entry *next, *parent, *subdir;
- void *data;
- read_proc_t *read_proc; <==讀回撥
- write_proc_t *write_proc; <==寫回調
- ......
- };
1.3 .刪除proc檔案/目錄:
- void remove_dir_entry(constchar *name, struct proc_dir_entry *parent);
要從 /proc 中刪除一個檔案,可以使用 remove_proc_entry
函式。要使用這個函式,我們需要提供檔名字串,以及這個檔案在
/proc 檔案系統中的位置(parent)。
3、proc檔案讀回撥函式
static int (*proc_read)(char *page, char **start, off_t off,
int count, int *eof, void *data);
4、proc檔案寫回調函式
static int proc_write_foobar(struct file *file, const char *buffer, unsigned long count, void *data);
proc檔案實際上是一個叫做proc_dir_entry的struct(定義在proc_fs.h),該struct中有int read_proc和int write_proc兩個元素,要實現proc的檔案的讀寫就要給這兩個元素賦值。但這裡不是簡單地將一個整數賦值過去就行了,需要實現兩個回撥函式。在使用者或應用程式訪問該proc檔案時,就會呼叫這個函式,實現這個函式時只需將想要讓使用者看到的內容放入page即可。在使用者或應用程式試圖寫入該proc檔案時,就會呼叫這個函式,實現這個函式時需要接收使用者寫入的資料(buff引數)。
寫回調函式
我們可以使用 write_proc
函式向
/proc 中寫入一項。這個函式的原型如下:
int mod_write( struct file *filp, const char __user *buff, unsigned long len, void *data );
filp
引數實際上是一個開啟檔案結構(我們可以忽略這個引數)。buff
引數是傳遞給您的字串資料。緩衝區地址實際上是一個使用者空間的緩衝區,因此我們不能直接讀取它。len
引數定義了在 buff
中有多少資料要被寫入。data
引數是一個指向私有資料的指標。在這個模組中,我們聲明瞭一個這種型別的函式來處理到達的資料。
Linux 提供了一組 API 來在使用者空間和核心空間之間移動資料。對於 write_proc
的情況來說,我們使用了 copy_from_user
函式來維護使用者空間的資料。
讀回撥函式
我們可以使用 read_proc
函式從一個
/proc 項中讀取資料(從核心空間到使用者空間)。這個函式的原型如下:
int mod_read( char *page, char **start, off_t off, int count, int *eof, void *data );
page
引數是這些資料寫入到的位置,其中 count
定義了可以寫入的最大字元數。在返回多頁資料(通常一頁是
4KB)時,我們需要使用 start
和 off
引數。當所有資料全部寫入之後,就需要設定 eof
(檔案結束引數)。與 write
類似,data
表示的也是私有資料。此處提供的 page
緩衝區在核心空間中。因此,我們可以直接寫入,而不用呼叫 copy_to_user
。
例項程式碼:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/jiffies.h>
#include <asm/uaccess.h>
#define MODULE_VERS "1.0"
#define MODULE_NAME "procfs_example"
#define FOOBAR_LEN 8
struct fb_data_t {
char name[FOOBAR_LEN + 1];
char value[FOOBAR_LEN + 1];
};
static struct proc_dir_entry *example_dir, *foo_file;
struct fb_data_t foo_data;
static int proc_read_foobar(char *page, char **start,
off_t off, int count,
int *eof, void *data)
{
int len;
struct fb_data_t *fb_data = (struct fb_data_t *)data;
/* DON'T DO THAT - buffer overruns are bad */
len = sprintf(page, "%s = '%s'\n",
fb_data->name, fb_data->value);
return len;
}
static int proc_write_foobar(struct file *file,
const char *buffer,
unsigned long count,
void *data)
{
int len;
struct fb_data_t *fb_data = (struct fb_data_t *)data;
if(count > FOOBAR_LEN)
len = FOOBAR_LEN;
else
len = count;
if(copy_from_user(fb_data->name, buffer, len))
return -EFAULT;
fb_data->value[len] = '\0';
return len;
}
static int __init init_procfs_example(void)
{
int rv = 0;
/* create directory */
example_dir = proc_mkdir(MODULE_NAME, NULL);
if(example_dir == NULL) {
rv = -ENOMEM;
goto out;
}
/* create foo and bar files using same callback
* functions
*/
foo_file = create_proc_entry("foo", 0644, example_dir);
if(foo_file == NULL) {
rv = -ENOMEM;
goto no_foo;
}
strcpy(foo_data.name, "foo");
strcpy(foo_data.value, "foo");
foo_file->data = &foo_data;
foo_file->read_proc = proc_read_foobar;
foo_file->write_proc = proc_write_foobar;
/* everything OK */
printk(KERN_INFO "%s %s initialised\n",
MODULE_NAME, MODULE_VERS);
return 0;
no_foo:
remove_proc_entry("jiffies", example_dir);
out:
return rv;
}
static void __exit cleanup_procfs_example(void)
{
remove_proc_entry("foo", example_dir);
remove_proc_entry(MODULE_NAME, NULL);
printk(KERN_INFO "%s %s removed\n",
MODULE_NAME, MODULE_VERS);
}
module_init(init_procfs_example);
module_exit(cleanup_procfs_example);
MODULE_AUTHOR("Erik Mouw");
MODULE_DESCRIPTION("procfs examples");
MODULE_LICENSE("GPL");
linux裝置驅動學習筆記--核心除錯方法之proc:http://blog.csdn.net/itsenlin/article/details/43374921
新增一個新的自定義的系統呼叫:http://blog.csdn.net/daydring/article/details/23913525