Linux核心模組程式設計--阻塞程式(轉)

worldblog發表於2007-08-10
Linux核心模組程式設計--阻塞程式(轉)[@more@]

  阻塞程式

  當某人要求你什麼事而你當時不能時你在做什麼?如果你是人而你被別人打擾,你唯一能說的是:‘現在不行,我正忙著呢。 走開!’。但是如果你是一個核心模組而你被一個程式打擾,你有另外的可能。你可以讓那個程式睡眠直到你能為它服務。畢竟,核心可以讓程式睡眠並且可以隨時喚醒它(那就是在單CPU上呈現同一時間多個程式執行的方式)。

  這個核心模組就是這樣的例子。那個檔案(被稱為 /proc/sleep)在同一時間只能被一個程式開啟。如果那個檔案已經開啟了,核心模組呼叫module_interruptible_sleep_on(保持一個檔案開啟的最簡單的辦法是用 tail -f)。這個函式改變那個任務(任何任務是包含有關程式的資訊和系統呼叫的核心的一種資料結構)的狀態為TASK_INTERRUPTIBLE,它的意思是任務不能執行,除非它被喚醒。並且該任務被加入 WaitQ-- 等待訪問該檔案的任務佇列。然後函式呼叫排程程式進行上下文轉換到一個還要使用CPU的不同的程式。

  當一個程式用完該檔案,它關閉該檔案,然後module_close 被呼叫。那個函式喚醒佇列中的所有程式(沒有機制只喚醒其中的一個)。然後它返回而剛剛關閉該檔案的程式可以繼續執行。排程程式及時地決定那個程式已經用了夠多的時間而將CPU的控制權交給另一個程式。最後,佇列中的某個程式會獲得排程程式賦予的CPU的控制權。它正好在對module_interruptible_sleep_on(這意味著程式仍然在核心模式--直到程式被關照,它釋出 open 系統呼叫然而系統呼叫還沒有返回。程式不知道別人在它釋出呼叫和它返回之前的大部分時間內使用CPU)的呼叫後開始。然後它能繼續設定一個全域性變數以告訴所有其他程式該檔案仍然開啟,它們將繼續它們等待的生活。當另一個程式得到CPU時間片,它們將看到那個全域性變數而繼續去睡眠。

  為了使我們的生活更有趣, module_close 沒有喚醒等待訪問該檔案的程式的壟斷權。一個訊號,例如Ctrl-C (SIGINT)也可以喚醒一個程式(這是因為我們使用 module_interruptible_sleep_on。我們不能使用module_sleep_on 作為代替,但那將導致那些他們控制的計算機被忽略的使用者的極度的憤怒)。在那種情況下,我們想立即用 -EINTR 返回。這是很重要的,例如使用者因此可以在程式收到該檔案前將它殺死。

  還有一點需要記住。有時候程式不想睡眠,它們想要麼立即得到它們需要的要麼被告知它不能完成。當檔案開啟時這類程式使用 O_NONBLOCK 標誌。例如當檔案開啟時,核心被假設從阻塞操作中返回錯誤程式碼-EAGAIN作為回應。在這章的源目錄中有的程式 cat_noblock 能用O_NONBLOCK開啟檔案。

  範例 sleep.c

  /* sleep.c - 建立一個 /proc 檔案,而如果幾個程式同時試圖開啟它,除了一個外使其他所有的睡眠 */

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

/* 必要標頭檔案 */

/* 標準標頭檔案 */

#include /* 核心工作 */

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

/* 處理 CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include

#endif

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

#include

/* 為了使程式睡眠和喚醒 */

#include

#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;

}

/* 如果你到現在還不懂這個,你沒有希望成為核心程式設計師 */

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

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

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);

#else

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

#endif

/* 我們需要一個標準的以0終止的字串 */

Message[i] = ';

/* 返回使用過的輸入的字元數 */

return i;

}

/* 如果檔案當前被某人開啟則為1 */

int Already_Open = 0;

/* 需要我們的檔案的程式佇列 */

static struct wait_queue *WaitQ = NULL;

/* 當 /proc 檔案被開啟時被呼叫 */

static int module_open(struct inode *inode,

struct file *file)

{

/* 如果檔案的標誌包含 O_NONBLOCK 則意味著程式不想等待該檔案。

* 在這種情況下,如果檔案已經開啟,我們將用返回 -EAGAIN 表示失敗,意思是“你將再試一次”

* 而不讓寧願保持醒狀態的程式阻塞。 */

if ((file->f_flags & O_NONBLOCK) && Already_Open)

return -EAGAIN;

/* 這是放置 MOD_INC_USE_COUNT 的合適的位置,因為如果一個程式處於核心模組的迴圈中,

* 核心模組不應被清除。 */

MOD_INC_USE_COUNT;

/* 如果檔案已經開啟,等待,直到它不再如此 */

while (Already_Open)

{

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

int i, is_sig=0;

#endif

/* 該函式使當前的程式睡眠,包括任何系統呼叫,例如我們的。

* 執行將在函式呼叫後恢復,要麼因為某人呼叫了 wake_up(&WaitQ) (只有當檔案被關閉時

* 由 module_close 做這個),要麼諸如 Ctrl-C 之類的訊號傳送到程式 */

module_interruptible_sleep_on(&WaitQ);

/* 如果因為得到一個訊號我們醒來,我們不再阻塞,返回 -EINTR (系統呼叫失敗)。

* 這允許程式被殺死或停止。 */

/*

* Emmanuel Papirakis:

*

* 在 2.2.* 上有一點更新。訊號現在被包含在一個雙字中(64 位) 並被儲存在一個

* 包含雙無符號長整型陣列的結構中。必要的話我們需要做兩次檢查。

*

* Ori Pomerantz:

*

* 沒有人向我許諾他們決不使用多於64位,或者這本書不在16位字長的 Linux 版本上使用。

* 這些程式碼無論如何都可以工作。

*/

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

for(i=0; i<_nsig_words i="">

is_sig = current->signal.sig[i] &

~current->blocked.sig[i];

if (is_sig) {

#else

if (current->signal & ~current->blocked) {

#endif

/* 在這兒放置MOD_DEC_USE_COUNT 是很重要的,因為對於程式開啟操作被中斷的地方永遠相應關閉

* 如果我們不在這兒減小使用計數,我們將留下一個正的使用計數而他沒有辦法降為0而

* 給我們一個不朽的只能透過重新啟動機器殺死的模組。 */

MOD_DEC_USE_COUNT;

return -EINTR;

}

}

/* 如果我們到這兒, Already_Open 必須為0 */

/* 開啟檔案 */

Already_Open = 1;

return 0; /* 允許訪問 */

}

/* 當 /proc 檔案被關閉時呼叫 */

#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

{

/* 將 Already_Open 設為0,所以等待佇列 WaitQ 中的某個程式能將 Already_Open設回1以開啟檔案。

* 所有的其他程式在 Already_Open 為1時被呼叫而它們將回去繼續睡眠。 */

Already_Open = 0;

/* 喚醒等待佇列 WaitQ中的所有程式,所以如果某個正在等待該檔案,它們能有機會。 */

module_wake_up(&WaitQ);

MOD_DEC_USE_COUNT;

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

return 0; /* success */

#endif

}

/* 這個函式決定是否允許一個操作(返回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;

}

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

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

* NULL 意味著我們不想處理某些事。 */

static struct file_operations File_Ops_4_Our_Proc_File =

{

NULL, /* lseek */

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

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

NULL, /* 讀目錄 */

NULL, /* 選擇 */

NULL, /* ioctl */

NULL, /* mmap */

module_open,/* 當 /proc 檔案被開啟時呼叫 */

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

NULL, /* 重新整理 */

#endif

module_close /* 當它被關閉時呼叫 */

};

/* 對我們的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, /* mknod */

NULL, /* 更名 */

NULL, /* 讀連線 */

NULL, /* 跟隨連線 */

NULL, /* 讀頁 */

NULL, /* 寫頁 */

NULL, /* bmap */

NULL, /* 截短 */

module_permission /* 許可權檢查 */

};

/* 目錄項 */

static struct proc_dir_entry Our_Proc_File =

{

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

5, /* 檔名長度 */

"sleep", /* 檔名 */

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)

return proc_register(&proc_root, &Our_Proc_File);

#else

return proc_register_dynamic(&proc_root, &Our_Proc_File);

#endif

/* proc_root 是 proc 檔案系統的根目錄(/proc)。這是我們想將我們的檔案放置的地方。 */

}

/* 清除 - 從 /proc 中登出檔案。如果在 WaitQ 中仍然有程式這將變得危險,因為它們在我們的開啟函式里

* 而它將不能被解除安裝。我將在第10章裡面解釋在這樣的情況下如何避免移除核心模組。*/

void cleanup_module()

{

proc_unregister(&proc_root, Our_Proc_File.low_ino);

}

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

相關文章