使用 /proc 文件系統來訪問 Linux 內核的內容

在這裏插入圖片描述
原文鏈接:https://www.ibm.com/developerworks/cn/linux/l-proc.html#icomments

最初開發 /proc 文件系統是爲了提供有關係統中進程的信息。但是由於這個文件系統非常有用,因此內核中的很多元素也開始使用它來報告信息,或啓用動態運行時配置。

/proc 文件系統包含了一些目錄(用作組織信息的方式)和虛擬文件。虛擬文件可以向用戶呈現內核中的一些信息,也可以用作一種從用戶空間向內核發送信息的手段。實際上我們並不會同時需要實現這兩點,但是本文將向您展示如何配置這個文件系統進行輸入和輸出。

要在 /proc 文件系統中創建一個虛擬文件,請使用 create_proc_entry 函數。這個函數可以接收一個文件名、一組權限和這個文件在 /proc 文件系統中出現的位置。create_proc_entry 的返回值是一個 proc_dir_entry 指針(或者爲 NULL,說明在 create 時發生了錯誤)。然後就可以使用這個返回的指針來配置這個虛擬文件的其他參數,例如在對該文件執行讀操作時應該調用的函數。create_proc_entry 的原型和 proc_dir_entry 結構中的一部分如清單 1 所示。

清單1. 用來管理 /proc 文件系統項的元素

struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode,
                                             struct proc_dir_entry *parent );
struct proc_dir_entry {
    const char *name;           // virtual file name
    mode_t mode;                // mode permissions
    uid_t uid;              // File's user id
    gid_t gid;              // File's group id
    struct inode_operations *proc_iops; // Inode operations functions
    struct file_operations *proc_fops;  // File operations functions
    struct proc_dir_entry *parent;      // Parent directory
    ...
    read_proc_t *read_proc;         // /proc read function
    write_proc_t *write_proc;       // /proc write function
    void *data;             // Pointer to private data
    atomic_t count;             // use count
    ...
};
void remove_proc_entry( const char *name, struct proc_dir_entry *parent );

稍後我們就可以看到如何使用 read_proc 和 write_proc 命令來插入對這個虛擬文件進行讀寫的函數。

要從 /proc 中刪除一個文件,可以使用 remove_proc_entry 函數。要使用這個函數,我們需要提供文件名字符串,以及這個文件在 /proc 文件系統中的位置(parent)。這個函數原型如清單 7 所示。

parent 參數可以爲 NULL(表示 /proc 根目錄),也可以是很多其他值,這取決於我們希望將這個文件放到什麼地方。表 1 列出了可以使用的其他一些父 proc_dir_entry,以及它們在這個文件系統中的位置。

表 1. proc_dir_entry 快捷變量
在這裏插入圖片描述
回調函數
我們可以使用 write_proc 函數向 /proc 中寫入一項。這個函數的原型如下:

int mod_write( struct file *filp, const char __user *buff,
               unsigned long len, void *data );

filp 參數實際上是一個打開文件結構(我們可以忽略這個參數)。buff 參數是傳遞給您的字符串數據。緩衝區地址實際上是一個用戶空間的緩衝區,因此我們不能直接讀取它。len 參數定義了在 buff 中有多少數據要被寫入。data 參數是一個指向私有數據的指針(參見 清單 1)。在這個模塊中,我們聲明瞭一個這種類型的函數來處理到達的數據。

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。

其他有用的函數
我們還可以使用 proc_mkdir、symlinks 以及 proc_symlink 在 /proc 文件系統中創建目錄。對於只需要一個 read 函數的簡單 /proc 項來說,可以使用 create_proc_read_entry,這會創建一個 /proc 項,並在一個調用中對 read_proc 函數進行初始化。這些函數的原型如清單 2 所示。

清單 2. 其他有用的 /proc 函數

/* Create a directory in the proc filesystem */
struct proc_dir_entry *proc_mkdir( const char *name,
                                     struct proc_dir_entry *parent );
/* Create a symlink in the proc filesystem */
struct proc_dir_entry *proc_symlink( const char *name,
                                       struct proc_dir_entry *parent,
                                       const char *dest );
/* Create a proc_dir_entry with a read_proc_t in one call */
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 );
/* Copy buffer to user-space from kernel-space */
unsigned long copy_to_user( void __user *to,
                              const void *from,
                              unsigned long n );
/* Copy buffer to kernel-space from user-space */
unsigned long copy_from_user( void *to,
                                const void __user *from,
                                unsigned long n );
/* Allocate a 'virtually' contiguous block of memory */
void *vmalloc( unsigned long size );
/* Free a vmalloc'd block of memory */
void vfree( void *addr );
/* Export a symbol to the kernel (make it visible to the kernel) */
EXPORT_SYMBOL( symbol );
/* Export all symbols in a file to the kernel (declare before module.h) */
EXPORT_SYMTAB

通過 /proc 文件系統實現財富分發
下面是一個可以支持讀寫的 LKM。這個簡單的程序提供了一個財富甜點分發。在加載這個模塊之後,用戶就可以使用 echo 命令向其中導入文本財富,然後再使用 cat 命令逐一讀出。

清單 3 給出了基本的模塊函數和變量。init 函數(init_fortune_module)負責使用 vmalloc 來爲這個點心罐分配空間,然後使用 memset 將其全部清零。使用所分配並已經清空的 cookie_pot 內存,我們在 /proc 中創建了一個 proc_dir_entry 項,並將其稱爲 fortune。當 proc_entry 成功創建之後,對自己的本地變量和 proc_entry 結構進行了初始化。我們加載了 /proc read 和 write 函數(如清單 9 和清單 10 所示),並確定這個模塊的所有者。cleanup 函數簡單地從 /proc 文件系統中刪除這一項,然後釋放 cookie_pot 所佔據的內存。

cookie_pot 是一個固定大小(4KB)的頁,它使用兩個索引進行管理。第一個是 cookie_index,標識了要將下一個 cookie 寫到哪裏去。變量 next_fortune 標識了下一個 cookie 應該從哪裏讀取以便進行輸出。在所有的 fortune 項都讀取之後,我們簡單地回到了 next_fortune。

清單 3. 模塊的 init/cleanup 和變量

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Fortune Cookie Kernel Module");
MODULE_AUTHOR("M. Tim Jones");
#define MAX_COOKIE_LENGTH       PAGE_SIZE
static struct proc_dir_entry *proc_entry;
static char *cookie_pot;  // Space for fortune strings
static int cookie_index;  // Index to write next fortune
static int next_fortune;  // Index to read next fortune
int init_fortune_module( void )
{
  int ret = 0;
  cookie_pot = (char *)vmalloc( MAX_COOKIE_LENGTH );
  if (!cookie_pot) {
    ret = -ENOMEM;
  } else {
    memset( cookie_pot, 0, MAX_COOKIE_LENGTH );
    proc_entry = create_proc_entry( "fortune", 0644, NULL );
    if (proc_entry == NULL) {
      ret = -ENOMEM;
      vfree(cookie_pot);
      printk(KERN_INFO "fortune: Couldn't create proc entry\n");
    } else {
      cookie_index = 0;
      next_fortune = 0;
      proc_entry->read_proc = fortune_read;
      proc_entry->write_proc = fortune_write;
      proc_entry->owner = THIS_MODULE;
      printk(KERN_INFO "fortune: Module loaded.\n");
    }
  }
  return ret;
}
void cleanup_fortune_module( void )
{
  remove_proc_entry("fortune", &proc_root);
  vfree(cookie_pot);
  printk(KERN_INFO "fortune: Module unloaded.\n");
}
module_init( init_fortune_module );
module_exit( cleanup_fortune_module );

向這個罐中新寫入一個 cookie 非常簡單(如清單 4所示)。使用這個寫入 cookie 的長度,我們可以檢查是否有這麼多空間可用。如果沒有,就返回 -ENOSPC,它會返回給用戶空間。否則,就說明空間存在,我們使用 copy_from_user 將用戶緩衝區中的數據直接拷貝到 cookie_pot 中。然後增大 cookie_index(基於用戶緩衝區的長度)並使用 NULL 來結束這個字符串。最後,返回實際寫入 cookie_pot 的字符的個數,它會返回到用戶進程。

清單4. 對 fortune 進行寫入操作所使用的函數

unsigned long len, void *data )
    {
      int space_available = (MAX_COOKIE_LENGTH-cookie_index)+1;
      if (len > space_available) {
        printk(KERN_INFO "fortune: cookie pot is full!\n");
        return -ENOSPC;
      }
      if (copy_from_user( &cookie_pot[cookie_index], buff, len )) {
        return -EFAULT;
      }
      cookie_index += len;
      cookie_pot[cookie_index-1] = 0;
      return len;
    }

對 fortune 進行讀取也非常簡單,如清單 5所示。由於我們剛纔寫入數據的緩衝區(page)已經在內核空間中了,因此可以直接對其進行操作,並使用 sprintf 來寫入下一個 fortune。如果 next_fortune 索引大於 cookie_index(要寫入的下一個位置),那麼我們就將 next_fortune 返回爲 0,這是第一個 fortune 的索引。在將這個 fortune 寫入用戶緩衝區之後,在 next_fortune 索引上增加剛纔寫入的 fortune 的長度。這樣就變成了下一個可用 fortune 的索引。這個 fortune 的長度會被返回並傳遞給用戶。

清單 5. 對 fortune 進行讀取操作所使用的函數

int fortune_read( char *page, char **start, off_t off,
                   int count, int *eof, void *data )
{
  int len;
  if (off > 0) {
    *eof = 1;
    return 0;
  }
  /* Wrap-around */
  if (next_fortune >= cookie_index) next_fortune = 0;
  len = sprintf(page, "%s\n", &cookie_pot[next_fortune]);
  next_fortune += len;
  return len;
}

從這個簡單的例子中,我們可以看出通過 /proc 文件系統與內核進行通信實際上是件非常簡單的事情。現在讓我們來看一下這個 fortune 模塊的用法(參見清單 6)。
清單 6. 展示 fortune cookie LKM 的用法

[root@plato]# insmod fortune.ko
[root@plato]# echo "Success is an individual proposition.  
          Thomas Watson" > /proc/fortune
[root@plato]# echo "If a man does his best, what else is there?  
                Gen. Patton" > /proc/fortune
[root@plato]# echo "Cats: All your base are belong to us.  
                      Zero Wing" > /proc/fortune
[root@plato]# cat /proc/fortune
Success is an individual proposition.  Thomas Watson
[root@plato]# cat /proc/fortune
If a man does his best, what else is there?  General Patton
[root@plato]#

/proc 虛擬文件系統可以廣泛地用來報告內核的信息,也可以用來進行動態配置。我們會發現它對於驅動程序和模塊編程來說都是非常完整的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章