天天看點

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 的一個優點是它将一個值導出給使用者空間非常簡單(實際上這不過是一個調用而已)。

Linux核心通信之---proc檔案系統(詳解)
Linux核心通信之---proc檔案系統(詳解)
Linux核心通信之---proc檔案系統(詳解)

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

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 .建立目錄:

[c]  view plain  copy

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

1.2 .建立proc檔案:

[c]  view plain  copy

  1. struct proc_dir_entry *create_proc_entry( const char *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

 時發生了錯誤)。然後就可以使用這個傳回的指針來配置這個虛拟檔案的其他參數,例如在對該檔案執行讀操作時應該調用的函數。

[html]  view plain  copy

  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檔案/目錄:

[c] view plain copy

  1. void remove_dir_entry(const char *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

執行個體代碼:

[cpp]  view plain  copy

  1. #include <linux/module.h>    
  2. #include <linux/kernel.h>    
  3. #include <linux/init.h>    
  4. #include <linux/proc_fs.h>    
  5. #include <linux/jiffies.h>    
  6. #include <asm/uaccess.h>    
  7. #define MODULE_VERS "1.0"    
  8. #define MODULE_NAME "procfs_example"    
  9. #define FOOBAR_LEN 8    
  10. struct fb_data_t {    
  11.     char name[FOOBAR_LEN + 1];    
  12.     char value[FOOBAR_LEN + 1];    
  13. };    
  14. static struct proc_dir_entry *example_dir, *foo_file;      
  15. struct fb_data_t foo_data;    
  16. static int proc_read_foobar(char *page, char **start,    
  17.                 off_t off, int count,     
  18.                 int *eof, void *data)    
  19. {    
  20.     int len;    
  21.     struct fb_data_t *fb_data = (struct fb_data_t *)data;    
  22.     len = sprintf(page, "%s = '%s'\n",     
  23.               fb_data->name, fb_data->value);    
  24.     return len;    
  25. }    
  26. static int proc_write_foobar(struct file *file,    
  27.                  const char *buffer,    
  28.                  unsigned long count,     
  29.                  void *data)    
  30. {    
  31.     int len;    
  32.     struct fb_data_t *fb_data = (struct fb_data_t *)data;    
  33.     if(count > FOOBAR_LEN)    
  34.         len = FOOBAR_LEN;    
  35.     else    
  36.         len = count;    
  37.     if(copy_from_user(fb_data->name, buffer, len))    
  38.         return -EFAULT;    
  39.     fb_data->value[len] = '\0';    
  40.     return len;    
  41. }    
  42. static int __init init_procfs_example(void)    
  43. {    
  44.     int rv = 0;    
  45.     example_dir = proc_mkdir(MODULE_NAME, NULL);    
  46.     if(example_dir == NULL) {    
  47.         rv = -ENOMEM;    
  48.         goto out;    
  49.     }    
  50.     foo_file = create_proc_entry("foo", 0644, example_dir);    
  51.     if(foo_file == NULL) {    
  52.         rv = -ENOMEM;    
  53.         goto no_foo;    
  54.     }    
  55.     strcpy(foo_data.name, "foo");    
  56.     strcpy(foo_data.value, "foo");    
  57.     foo_file->data = &foo_data;    
  58.     foo_file->read_proc = proc_read_foobar;    
  59.     foo_file->write_proc = proc_write_foobar;    
  60.     printk(KERN_INFO "%s %s initialised\n",    
  61.            MODULE_NAME, MODULE_VERS);    
  62.     return 0;    
  63. no_foo:    
  64.     remove_proc_entry("jiffies", example_dir);    
  65. out:    
  66.     return rv;    
  67. }    
  68. static void __exit cleanup_procfs_example(void)    
  69. {    
  70.     remove_proc_entry("foo", example_dir);      
  71.     remove_proc_entry(MODULE_NAME, NULL);    
  72.     printk(KERN_INFO "%s %s removed\n",    
  73.            MODULE_NAME, MODULE_VERS);    
  74. }    
  75. module_init(init_procfs_example);    
  76. module_exit(cleanup_procfs_example);    
  77. MODULE_AUTHOR("Erik Mouw");    
  78. MODULE_DESCRIPTION("procfs examples");    
  79. MODULE_LICENSE("GPL");    

linux裝置驅動學習筆記--核心調試方法之proc:http://blog.csdn.NET/itsenlin/article/details/43374921

添加一個新的自定義的系統調用:http://blog.csdn.net/daydring/article/details/23913525

繼續閱讀