1. 程式人生 > >Linux核心通訊之---proc檔案系統(詳解)

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 也依然會存在。還有一個 

debugfs 檔案系統,不過(顧名思義)它提供的更多是除錯介面。debugfs 的一個優點是它將一個值匯出給使用者空間非常簡單(實際上這不過是一個呼叫而已)。

這些檔案的解釋和意義如下:

cmdline:系統啟動時輸入給核心命令列引數 
cpuinfo:CPU的硬體資訊 (型號, 家族, 快取大小等)  
devices:主裝置號及裝置組的列表,當前載入的各種裝置(塊裝置/字元裝置) 
dma:使用的DMA通道 
filesystems:當前核心支援的檔案系統,當沒有給 mount(1) 指明哪個檔案系統的時候, mount(1) 就依靠該檔案遍歷不同的檔案系統
interrupts :中斷的使用及觸發次數,除錯中斷時很有用 
ioports I/O:當前在用的已註冊 I/O 埠範圍 
kcore:該偽檔案以 core 檔案格式給出了系統的實體記憶體映象(比較有用),可以用 GDB 查探當前核心的任意資料結構。該檔案的總長度是實體記憶體 (RAM) 的大小再加上 4KB
kmsg:可以用該檔案取代系統呼叫 syslog(2) 來記錄核心日誌資訊,對應dmesg命令
kallsym:核心符號表,該檔案儲存了核心輸出的符號定義, modules(X)使用該檔案動態地連線和捆綁可裝載的模組
loadavg:負載均衡,平均負載數給出了在過去的 1、 5,、15 分鐘裡在執行佇列裡的任務數、總作業數以及正在執行的作業總數。
locks:核心鎖 。
meminfo實體記憶體、交換空間等的資訊,系統記憶體佔用情況,對應df命令。
misc:雜項 。
modules:已經載入的模組列表,對應lsmod命令 。
mounts:已載入的檔案系統的列表,對應mount命令,無引數。
partitions:系統識別的分割槽表 。
slabinfo:sla池資訊。
stat:全面統計狀態表,CPU記憶體的利用率等都是從這裡提取資料。對應ps命令。
swaps:對換空間的利用情況。 
version:指明瞭當前正在執行的核心版本。

可載入核心模組(LKM)是用來展示 /proc 檔案系統的一種簡單方法,這是因為這是一種用來動態地向 Linux 核心新增或刪除程式碼的新方法。LKM 也是 Linux 核心中為裝置驅動程式和檔案系統使用的一種流行機制。如果你曾經重新編譯過 Linux 核心,就可能會發現在核心的配置過程中,有很多裝置驅動程式和其他核心元素都被編譯成了模組。如果一個驅動程式被直接編譯到了核心中,那麼即使這個驅動程式沒有執行,它的程式碼和靜態資料也會佔據一部分空間。但是如果這個驅動程式被編譯成一個模組,就只有在需要記憶體並將其載入到核心時才會真正佔用記憶體空間。

整合到 /proc 檔案系統中

核心程式設計師可以使用的標準 API,LKM 程式設計師也可以使用。

方法一:(create_proc_entry建立proc檔案)

1.1 .建立目錄:

  1. struct proc_dir_entry *proc_mkdir(constchar *name,  
  2.                 struct proc_dir_entry *parent);  

1.2 .建立proc檔案:

  1. struct proc_dir_entry *create_proc_entry( constchar *name,  mode_t mode,  
  2.                 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 時發生了錯誤)。然後就可以使用這個返回的指標來配置這個虛擬檔案的其他引數,例如在對該檔案執行讀操作時應該呼叫的函式。

  1. struct proc_dir_entry {  
  2.     ......  
  3.     const struct file_operations *proc_fops;    <==檔案操作結構體  
  4.     struct proc_dir_entry *next, *parent, *subdir;  
  5.     void *data;  
  6.     read_proc_t *read_proc;                    <==讀回撥  
  7.     write_proc_t *write_proc;                  <==寫回調  
  8.     ......  
  9. }; 

1.3 .刪除proc檔案/目錄:

  1. 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