Linux核心模組程式設計-將/proc作為輸入(轉)

worldblog發表於2007-08-10
Linux核心模組程式設計-將/proc作為輸入(轉)[@more@]

  將 /proc 作為輸入

  迄今為止,我們有兩中辦法從核心模組中產生輸出:我們可以登記一個裝置驅動程式並 mknod 一個裝置檔案,或者我們可以建立一個/proc檔案。這可以讓核心模組告訴我們任何它可能告訴我們的事情。唯一的問題是這沒有辦法讓我們告訴它。我們將輸入傳送給核心模組的第一個辦法將是透過寫回 /proc 檔案。

  因為 proc 檔案系統主要是為了讓核心報告它對程式的狀態,所以對輸入沒有專門的預備。 proc_dir_entry結構沒有包含一個指向輸入函式的指標而包含輸出函式的指標。為了向/proc 檔案中寫,我們需要使用標準的檔案系統機制。

  在 Linux 中對檔案系統登記有標準的機制。既然每個檔案系統必須有它自己的處理節點和檔案操作(兩者的不同在於檔案操作處理檔案自己,而節點操作處理對檔案的引用,例如建立對它的連線)的函式, 所以有一個特殊的結構儲存所有這些函式的指標, inode_operations 結構, 包含一個指向 file_operations結構的指標。在 /proc 中,任何時候登記一個新檔案我們都允許特別指定哪個 inode_operations 結構將用於訪問它。這就是我們使用的機制, inode_operations 結構包含指向 file_operations 結構的指標,而它又包含指向我們的module_input 和 module_output 函式的指標。

  注意在核心中標準的讀寫的任務是顛倒的。讀函式用作輸出而寫函式用於輸入。造成這個局面的原因是讀寫是依據使用者的觀點--如果一個程式從核心中讀什麼,那麼核心就需要輸出它,而如果程式向核心中寫什麼,那麼核心就需要將它作為輸入接收。

  這兒另一個引起注意的地方是 module_permission 函式。這個函式在程式試圖用 /proc檔案做什麼的時候被呼叫,並且它決定是否允許訪問。現在它僅僅基於操作和當前使用者的UID(就像 current 中的那樣,一個指向包含當前執行程式的資訊的結構的指標),但它也可以基於任何我們喜歡的東西,例如其他程式在用該檔案做什麼,時間,或者我們上次的接收的輸入。

  使用 put_user 和 get_user 的原因是在 Linux 中記憶體 (在Intel 架構下,在其他處理器下可能不同)是分段的。這意味著指標不能單獨由它自己指向一個唯一的記憶體位置,只是在記憶體的段中的位置,你需要知道它可以使用哪個記憶體段。對核心只有一個記憶體段,其他程式也各有一個。

  程式只能訪問自己的記憶體段,因此當寫普通的作為程式執行的程式時我們不必為段操心。當你寫核心模組時,通常你想訪問核心的記憶體段,它由系統自動的處理。然而,當記憶體緩衝區中的內容需要在當前程式和核心中傳遞時,核心的函式收到的是指向位於程式的記憶體段中的記憶體緩衝區的指標。 put_user 和 get_user 宏可以讓你訪問那些記憶體。

  範例 procfs.c

  /* procfs.c - 建立一個可以輸入輸出的在 /proc 中的“檔案”。 */

/* Copyright (C) 1998-1999 by Ori Pomerantz */

/* 必要標頭檔案 */

/* 標準標頭檔案 */

#include /* 核心工作 */

#include /* 明確指定是模組 */

/* 處理 CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include

#endif

/* 使用 proc 檔案系統所必要的 */

#include

/* 在 2.2.3 版/usr/include/linux/version.h 包含這個宏,但是

* 在 2.0.35 版中不包含--因此加入這個以備需要。 */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

#include /* 得到 get_user 和 put_user */

#endif

/* 模組的檔案函式 ********************** */

/* 在這兒儲存上次收到的資訊以證明我們可以處理我們的輸入。 */

#define MESSAGE_LENGTH 80

static char Message[MESSAGE_LENGTH];

/* 既然我們使用了檔案操作結構我們就不能使用預備的那個特殊的proc輸出函式

* 我們不得不使用標準的讀函式,就是這個函式。*/

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t module_output(

struct file *file, /* 要讀的檔案 */

char *buf, /* 要將資料放入的緩衝區(在使用者記憶體段中) */

size_t len, /* 緩衝區長度 */

loff_t *offset) /* 檔案偏移量--忽略 */

#else

static int module_output(

struct inode *inode, /* 要讀的節點 */

struct file *file, /* 要讀的檔案 */

char *buf, /* 要將資料放入的緩衝區(在使用者記憶體段中) */

int len) /* 緩衝區長度 */

#endif

{

static int finished = 0;

int i;

char message[MESSAGE_LENGTH+30];

/* 在沒有更多資訊的時候返回0以指明檔案尾。否則程式會從我們的無窮迴圈中不斷的讀。 */

if (finished) {

finished = 0;

return 0;

}

/* 我們使用 put_user 將字串從核心的記憶體段中複製到呼叫我們的檔案的程式的記憶體段中。

* 順便說一下, get_user的用法相反。*/

sprintf(message, "Last input:%s", Message);

for(i=0; i put_user(message[i], buf+i);

/* 注意,我們假設訊息的長度小於 len,或者被截短。在真實的情況下,如果訊息的長度小於

* len 那麼我們會返回 len 而在下次呼叫時將用訊息的第 len+1 個位元組開始填充。 */

finished = 1;

return i; /* 返回“讀”到的位元組 */

}

/* 當使用者向/proc檔案中寫時這個函式接收從使用者來的輸入。 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t module_input(

struct file *file, /* 檔案自己 */

const char *buf, /* 存有輸入的緩衝區 */

size_t length, /* 緩衝區長度 */

loff_t *offset) /* 檔案偏移量--忽略 */

#else

static int module_input(

struct inode *inode, /* 檔案節點 */

struct file *file, /* 檔案自己 */

const char *buf, /* 存有輸入的緩衝區 */

int length) /* 緩衝區長度 */

#endif

{

int i;

/* 將輸入存入 Message,module_output 將在以後能使用它。 */

for(i=0; i #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

get_user(Message[i], buf+i);

/* 在 2.2 版中 get_user 的語義改變了,它不再返回一個字元而是期待將一個變數作為它的第一引數

* 和一個使用者記憶體段的指標作為第二引數。

*

* 這個改變的原因是在 2.2 版中, get_user 也可以讀短整型數或整數。它是透過使用sizeof來知道它將 * 讀到何種變數的,為此,它需要那個變數自身。*/

#else

Message[i] = get_user(buf+i);

#endif

Message[i] = '; /* 標準的以0作終止符的字串 */

/* 我們需要返回使用的輸入位元組數。 */

return i;

}

/* 這個函式決定是否允許一個操作(返回0允許,非0不允許同時指明為什麼不允許)。

*

* 操作可以是下面的值:

* 0 - 執行 (執行“檔案”在我們的情況中是沒有意義的)。

* 2 - 寫(向核心模組輸入)。

* 4 - 讀(從核心模組輸出)。

*

* 這是一個真實的檢查檔案許可權的函式。由 ls -l 返回的許可權只做參考並可以被忽略。 */

static int module_permission(struct inode *inode, int op)

{

/* 我們可以允許任何人從我們的模組讀但只有 root (UID為 0) 可以向它寫 */

if (op == 4 || (op == 2 && current->euid == 0))

return 0;

/* 如果是其他值,訪問被禁止 */

return -EACCES;

}

/* 檔案被開啟--我們並不真正關心這個而是這意味著我們需要增加模組的引用計數。 */

int module_open(struct inode *inode, struct file *file)

{

MOD_INC_USE_COUNT;

return 0;

}

/* 檔案被關閉--同樣只關心引用計數 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

int module_close(struct inode *inode, struct file *file)

#else

void module_close(struct inode *inode, struct file *file)

#endif

{

MOD_DEC_USE_COUNT;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

return 0; /* success */

#endif

}

/* 作為 /proc 檔案登記的結構,含有所有相關函式的指標。 ********** */

/* 對我們的 proc 檔案的操作。這是放當某人試圖對我們的檔案做什麼的時候需要呼叫的函式指標的地方。

* NULL意味著我們不想處理什麼事情。 */

static struct file_operations File_Ops_4_Our_Proc_File =

{

NULL, /* lseek */

module_output, /* 從檔案“讀” */

module_input, /* 向檔案“寫” */

NULL, /* readdir */

NULL, /* select */

NULL, /* ioctl */

NULL, /* mmap */

module_open, /* 某人開啟了檔案 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

NULL, /* 重新整理,在 2.2 版中加到這兒*/

#endif

module_close, /* 某人關閉了檔案 */

/* 等等,等等。(在/usr/include/linux/fs.h中它們都被給出)。因為我們在這兒沒有放任何東西,系 * 統將保持預設的資料,在Unix 中為0 (當作為指標時為NULLs )。*/

};

/* 對我們的proc檔案的節點操作。我們需要它,因此我們將在某些地方指定我們想使用的檔案操作的結構

* 和用於許可權的函式。當然指定任何其他對節點做什麼時被呼叫的函式也是可能的。

* (儘管我們不操心還是放置了 NULL)。 */

static struct inode_operations Inode_Ops_4_Our_Proc_File =

{

&File_Ops_4_Our_Proc_File,

NULL, /* 建立 */

NULL, /* 查詢 */

NULL, /* 連線 */

NULL, /* 刪除連線 */

NULL, /* 符號連線 */

NULL, /* 建立目錄 */

NULL, /* 刪除目錄 */

NULL, /* 建立裝置 */

NULL, /* 更名 */

NULL, /* 讀連線 */

NULL, /* 連線跟隨 */

NULL, /* 讀頁 */

NULL, /* 寫頁 */

NULL, /* bmap */

NULL, /* 截短 */

module_permission /* 許可權檢查 */

};

/* 目錄項 */

static struct proc_dir_entry Our_Proc_File =

{

0, /* 節點數--忽略,將被 proc_register[_dynamic] 代替 */

7, /* 檔名長度 */

"rw_test", /* 檔名 */

S_IFREG | S_IRUGO | S_IWUSR,

/* 檔案模式--這是一個可以被擁有者,使用者組以及其他人讀的普通檔案。

* 當然,它的擁有者可以寫。

*

* 實際上這個成員僅僅用於引用,它的 module_permission 做實際的檢查。

* 它可以使用這個成員,但我們的實現為了簡單而沒有用 */

1, /* 連線數(檔案被引用的目錄) */

0, 0, /* 檔案的UID和GID -我們將它賦予 root */

80, /* 由ls報告的檔案大小。 */

&Inode_Ops_4_Our_Proc_File,

/* 指向檔案的節點結構的指標,如果我們需要。在我們的情況中我們需要因為我們需要寫函式。*/

NULL

/* 檔案的讀函式。不相關,因為我們將它放入上面的節點結構。 */

};

/* 初始化模組和清除模組 ******************* */

/* 初始化模組-登記 proc 檔案 */

int init_module()

{

/* 如果 proc_register[_dynamic] 成功則成功,否則失敗。 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

/* 在 2.2版中,如果它在結構中的值為0, proc_register自動分配一個動態的節點數。因此這兒不再需要

* proc_register_dynamic */

return proc_register(&proc_root, &Our_Proc_File);

#else

return proc_register_dynamic(&proc_root, &Our_Proc_File);

#endif

}

/* 清除 - 從 /proc 中登出我們的檔案 */

void cleanup_module()

{

proc_unregister(&proc_root, Our_Proc_File.low_ino);

}

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-940228/,如需轉載,請註明出處,否則將追究法律責任。

相關文章