linux 内核模块 编写例子,LINUX内核模块编程8
Chapter 8. System Calls
系統(tǒng)調(diào)用
到目前為止,我們所做的只是使用完善的內(nèi)核機(jī)制注冊/proc文件和處理設(shè)備的對象。如果只是想寫一個設(shè)備驅(qū)動, 這些內(nèi)核程序員設(shè)定的方式已經(jīng)足夠了。但是,你不想做一些不尋常的事嗎, 想使你的系統(tǒng)看起來不一樣嗎?當(dāng)然,這取決你自己。
這里可是一個危險的地方。下面的這個例子中,我關(guān)閉了系統(tǒng)調(diào)用 open()。這意味著我無法打開任何文件,執(zhí)行任何程序,連使用
shutdown關(guān)機(jī)都不行,關(guān)機(jī)只能靠摁電源按鈕了。幸運的話,不會有文件丟失。 要保證不丟失文件的話,在insmod和
rmmod之前請執(zhí)行sync命令。
別管什么/proc文件和什么設(shè)備文件了, 它們只是小的細(xì)節(jié)問題。所有進(jìn)程同內(nèi)核打交道的根本方式是系統(tǒng)調(diào)用。
當(dāng)一個進(jìn)程需要內(nèi)核提供某項服務(wù)時(像打開一個文件,生成一個新進(jìn)程,或要求更多的內(nèi)存),
就會發(fā)生系統(tǒng)調(diào)用。如果你想你的系統(tǒng)運作方式看起來有意思點,這就是你動手的地方。
順便說一句,如果你想知道沒個程序使用了哪些系統(tǒng)調(diào)用,運行strace 。
總的來說,一個用戶進(jìn)程是不應(yīng)該也不能夠直接訪問內(nèi)核的。它不能訪問內(nèi)核的內(nèi)存, 也不能調(diào)用內(nèi)核的函數(shù)。這是CPU的硬件保護(hù)機(jī)制決定的(這也是為什么叫做“保護(hù)模式”的原因)。
系統(tǒng)調(diào)用是這條規(guī)則的例外。所發(fā)生的事是一個進(jìn)程用合適的值填充寄存器,
然后調(diào)用一條跳轉(zhuǎn)到已被定義過的內(nèi)核中的位置的指令(當(dāng)然,這些定義過的位置是對于用戶進(jìn)程可讀的,
但是顯然是不可寫的)。在Intel架構(gòu)中,這是通過 0x80 中斷完成的。硬件明白一旦你跳轉(zhuǎn)到這個位置,
你就不再是在處處受限的用戶態(tài)中運行了,而是在無所不能的內(nèi)核態(tài)中。
內(nèi)核中的進(jìn)程可以跳轉(zhuǎn)過去的位置叫做系統(tǒng)調(diào)用。那兒將檢查系統(tǒng)調(diào)用的序號, 這些序號將告訴內(nèi)核用戶進(jìn)程需要什么樣的服務(wù)。然后,通過查找系統(tǒng)調(diào)用表(
sys_call_table) 找到內(nèi)核函數(shù)的地址,調(diào)用該函數(shù)。當(dāng)函數(shù)返回時,
再做一些系統(tǒng)檢查,接著就返回用戶進(jìn)程(或是另一個進(jìn)程,如果該進(jìn)程的時間用完了)。 如果你想閱讀一下這方面的源代碼,它們就在文件
arch/$$/kernel/entry.S中 ENTRY(system_call)行的下面。
所以,如果我們想改變某個系統(tǒng)調(diào)用的運作方式,我們只需要用我們自己的函數(shù)去實現(xiàn)它
(通常只是加一點我們自己的代碼,然后調(diào)用原函數(shù))然后改變系統(tǒng)調(diào)用表
(sys_call_table)中的指針值使它指向我們的函數(shù)。因為這些模塊將在以后卸載,
我們不想系統(tǒng)因此而不穩(wěn)定,所以cleanup_module中恢復(fù)系統(tǒng)調(diào)用表是非常重要的。
這就是這樣的一個模塊。我們可以“監(jiān)視”一個特定的用戶,然后使用 printk()輸出該用戶打開的每個文件的消息。在結(jié)束前,我們用自己的
our_sys_open函數(shù)替換了打開文件的系統(tǒng)調(diào)用。該函數(shù)檢查當(dāng)前進(jìn)程的用戶序號(uid,user's id),
如果匹配我們監(jiān)視的用戶的序號,它調(diào)用printk()輸出將要打開的文件的名字。
要不然,就用同樣的參數(shù)調(diào)用原始的open()函數(shù),真正的打開文件。
函數(shù)init_module改變了系統(tǒng)調(diào)用表中的恰當(dāng)位置的值然后用一個變量保存下來。函數(shù)
cleanup_module則使用該變量將所有東西還原。這種處理方法其實是很危險的。想象一下,
如果我們有兩個這樣的模塊,A和B。A用A_open替換了系統(tǒng)的sys_open函數(shù),而B用B_open。現(xiàn)在,我們先把模塊A加載,
那么原先的系統(tǒng)調(diào)用被A_open替代了,A_open在完成工作后自身又會調(diào)用原始的sys_open函數(shù) 。接著,我們加載B模塊,
它用B_open更改了現(xiàn)在的已更改為A_open(顯然它認(rèn)為是原始的sys_open系統(tǒng)調(diào)用)的系統(tǒng)調(diào)用。
現(xiàn)在,如果B先卸載,一切正常。系統(tǒng)調(diào)用會還原到A_open,而A_open又會調(diào)用原始的sys_open。
但是,一旦A先卸載,系統(tǒng)就會崩潰。A的卸載會將系統(tǒng)調(diào)用還原到原始的sys_open,把B從鏈中切斷。
此時再卸載B,B會將系統(tǒng)調(diào)用恢復(fù)到它認(rèn)為的初始狀態(tài),也就是A_open,但A_open已經(jīng)不在內(nèi)存中了。
乍一看來,我們似乎可以通過檢測系統(tǒng)調(diào)用是否與我們的open函數(shù)相同,如果不相同則什么都不做
(這樣B就不會嘗試在卸載時恢復(fù)系統(tǒng)調(diào)用表)。但其實這樣更糟。當(dāng)A先被卸載時,它將檢測到系統(tǒng)
調(diào)用已被更改為B_open,所以A將不會在卸載時恢復(fù)系統(tǒng)調(diào)用表中相應(yīng)的項。此時不幸的事發(fā)生了,
B_open將仍然調(diào)用已經(jīng)不存在的A_open,這樣即使你不卸載B模塊,系統(tǒng)也崩潰了。
但是這種替換系統(tǒng)調(diào)用的方法是違背正式應(yīng)用中系統(tǒng)的穩(wěn)定和可靠原則的。所以,為了防止?jié)撛诘膶ο到y(tǒng)調(diào)用表
修改帶來的危害,系統(tǒng)調(diào)用表sys_call_table不再被內(nèi)核導(dǎo)出。這意味著如果你想順利的運行這個例子,你必須為你的
內(nèi)核樹打補(bǔ)丁來導(dǎo)出sys_call_table,在example目錄內(nèi)你將找到相關(guān)的補(bǔ)丁和說明。正如同你想像的那樣,這可不是
兒戲,如果你的系統(tǒng)非常寶貴(例如這不是你的系統(tǒng),或系統(tǒng)很難恢復(fù)),你最好還是放棄。如果你仍然堅持,我可以
告訴你的是打補(bǔ)丁雖然不會有多大問題,但內(nèi)核維護(hù)者他們肯定有足夠的理由在2.6內(nèi)核中不支持這種hack。詳情請參考README。
如果你選擇了N,跳過這個例子是一個安全的選擇。
Example 8-1. syscall.c
/*
* syscall.c
*
* System call "stealing" sample.
*/
/*
* Copyright (C) 2001 by Peter Jay Salzman
*/
/*
* The necessary header files
*/
/*
* Standard in kernel modules
*/
#include /* We're doing kernel work */
#include /* Specifically, a module, */
#include /* which will have params */
#include /* The list of system calls */
/*
* For the current (process) structure, we need
* this to know who the current user is.
*/
#include
#include
/*
* The system call table (a table of functions). We
* just define this as external, and the kernel will
* fill it up for us when we are insmod'ed
*
* sys_call_table is no longer exported in 2.6.x kernels.
* If you really want to try this DANGEROUS module you will
* have to apply the supplied patch against your current kernel
* and recompile it.
*/
extern void *sys_call_table[];
/*
* UID we want to spy on - will be filled from the
* command line
*/
static int uid;
module_param(uid, int, 0644);
/*
* A pointer to the original system call. The reason
* we keep this, rather than call the original function
* (sys_open), is because somebody else might have
* replaced the system call before us. Note that this
* is not 100% safe, because if another module
* replaced sys_open before us, then when we're inserted
* we'll call the function in that module - and it
* might be removed before we are.
*
* Another reason for this is that we can't get sys_open.
* It's a static variable, so it is not exported.
*/
asmlinkage int (*original_call) (const char *, int, int);
/*
* The function we'll replace sys_open (the function
* called when you call the open system call) with. To
* find the exact prototype, with the number and type
* of arguments, we find the original function first
* (it's at fs/open.c).
*
* In theory, this means that we're tied to the
* current version of the kernel. In practice, the
* system calls almost never change (it would wreck havoc
* and require programs to be recompiled, since the system
* calls are the interface between the kernel and the
* processes).
*/
asmlinkage int our_sys_open(const char *filename, int flags, int mode)
{
int i = 0;
char ch;
/*
* Check if this is the user we're spying on
*/
if (uid == current->uid) {
/*
* Report the file, if relevant
*/
printk("Opened file by %d: ", uid);
do {
get_user(ch, filename + i);
i++;
printk("%c", ch);
} while (ch != 0);
printk("\n" );
}
/*
* Call the original sys_open - otherwise, we lose
* the ability to open files
*/
return original_call(filename, flags, mode);
}
/*
* Initialize the module - replace the system call
*/
int init_module()
{
/*
* Warning - too late for it now, but maybe for
* next time...
*/
printk("I'm dangerous. I hope you did a " );
printk("sync before you insmod'ed me.\n" );
printk("My counterpart, cleanup_module(), is even" );
printk("more dangerous. If\n" );
printk("you value your file system, it will " );
printk("be \"sync; rmmod\" \n" );
printk("when you remove this module.\n" );
/*
* Keep a pointer to the original function in
* original_call, and then replace the system call
* in the system call table with our_sys_open
*/
original_call = sys_call_table[__NR_open];
sys_call_table[__NR_open] = our_sys_open;
/*
* To get the address of the function for system
* call foo, go to sys_call_table[__NR_foo].
*/
printk("Spying on UID:%d\n", uid);
return 0;
}
/*
* Cleanup - unregister the appropriate file from /proc
*/
void cleanup_module()
{
/*
* Return the system call back to normal
*/
if (sys_call_table[__NR_open] != our_sys_open) {
printk("Somebody else also played with the " );
printk("open system call\n" );
printk("The system may be left in " );
printk("an unstable state.\n" );
}
sys_call_table[__NR_open] = original_call;
}
總結(jié)
以上是生活随笔為你收集整理的linux 内核模块 编写例子,LINUX内核模块编程8的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: maven项目编译中文乱码和myecli
- 下一篇: linux shell中环境变量$PS1