1. 程式人生 > >Linux核心模組程式設計指南(一)

Linux核心模組程式設計指南(一)

Peter Jay Salzman
Michael Burian
Ori Pomerantz
Copyright © 2001 Peter Jay Salzman
2007-05-18 ver 2.6.4

Linux核心模組程式設計指南是一本免費的書; 您可以根據開放軟體許可1.1版的條款複製和/或修改它。 您可以在http://opensource.org/licenses/osl.php上獲取此許可證的副本。
本書的發行是希望它有用,但沒有任何保證,甚至沒有適銷性或適用於特定用途的默示保證。

第1章簡介

什麼是核心模組?

所以,你想編寫一個核心模組。 你知道C,你已經編寫了一些正常的程式作為程序執行,現在你想要到達實際操作的位置,一個狂野指標可以消滅你的檔案系統,核心轉儲意味著重啟。

什麼是核心模組? 模組是可以根據需要載入和解除安裝到核心中的程式碼片段。 它們擴充套件了核心的功能,而無需重啟系統。 例如,一種型別的模組是裝置驅動程式,它允許核心訪問連線到系統的硬體。 沒有模組,我們必須構建單片核心並將新功能直接新增到核心映像中。 除了擁有更大的核心之外,這還有一個缺點,即每次我們想要新功能時都需要我們重建和重啟核心。

模組如何進入核心?

您可以通過執行lsmod來檢視已經載入到核心中的模組, lsmod通過讀取檔案/proc/modules來獲取其資訊。

這些模組如何進入核心? 當核心需要一個不駐留在核心中的特性時,核心模組守護程序kmod [1]執行modprobe來載入模組.modprobe以兩種形式之一傳遞一個字串:

模組名稱,如softdog或ppp 。

一個更通用的識別符號,如char-major-10-30 。

如果modprobe被賦予通用識別符號,它首先在檔案/etc/modprobe.conf中查詢該字串。 [2]如果找到如下的別名行:

alias char-major-10-30 softdog

它知道通用識別符號引用模組softdog.ko 。

接下來,modprobe檢視檔案 /lib/modules/version/modules.dep ,以檢視是否必須載入其他模組才能載入所請求的模組。 該檔案由depmod -a建立,包含模組依賴項。 例如, msdos.ko要求fat.ko模組已載入到核心中。 如果另一個模組定義了所請求模組使用的符號(變數或函式),則請求的模組依賴於另一個模組。

最後,modprobe使用insmod首先將任何必備模組載入到核心中,然後載入所請求的模組。 modprobe將insmod指向 /lib/modules/version /[3] ,模組的標準目錄。 insmod對於模組的位置是相當愚蠢的,而modprobe知道模組的預設位置,知道如何找出依賴關係並以正確的順序載入模組。 例如,如果要載入msdos模組,則必須執行

insmod /lib/modules/2.6.11/kernel/fs/fat/fat.ko 
insmod /lib/modules/2.6.11/kernel/fs/msdos/msdos.ko 

或者

modprobe msdos

我們在這裡看到的是: insmod要求你傳遞完整的路徑名並以正確的順序插入模組,而modprobe只取名字,沒有任何副檔名,並通過解析/ lib找出它需要知道的所有內容/modules/version/modules.dep 。

Linux發行版提供modprobe,insmod和depmod作為名為module-init-tools的包。 在以前的版本中,該包稱為modutils。 一些發行版還設定了一些包裝器,允許兩個包並行安裝並做正確的事情,以便能夠處理2.4和2.6核心。 使用者不應該關心細節,只要他們執行這些工具的最新版本。

現在你知道模組如何進入核心了。 如果您想編寫依賴於其他模組的自己的模組(我們稱之為“堆疊模組”),那麼故事還有更多內容。 但這將不得不等待未來的一章。 在解決這個相對高級別的問題之前,我們需要做很多工作。

在我們開始之前

在我們深入研究程式碼之前,我們需要解決一些問題。 每個人的系統都不同,每個人都有自己的溝槽。 讓你的第一個“hello world”程式正確編譯和載入有時候會成為一種技巧。 請放心,在您第一次克服了最初的障礙後,此後將順利進行。

Modversioning

除非在核心中啟用CONFIG_MODVERSIONS ,否則如果引導其他核心,則不會載入為一個核心編譯的模組。 我們不會在本指南的後面部分進行模組版本控制。 在我們介紹modversions之前,如果您執行的是啟用了modversion的核心,則指南中的示例可能無效。 但是,大多數現有的Linux發行版核心隨之開啟。 如果由於版本控制錯誤而無法載入模組,請在關閉modversion的情況下編譯核心。

使用X.

強烈建議您輸入,編譯和載入本指南討論的所有示例。 強烈建議您從控制檯執行此操作。 你不應該在X中處理這些東西。

模組不能像printf()那樣列印到螢幕上,但它們可以記錄資訊和警告,最終會在螢幕上列印,但只能在控制檯上列印。 如果從xterm中修改模組,將記錄資訊和警告,但僅記錄日誌檔案。 除非檢視日誌檔案,否則不會看到它。 要立即訪問此資訊,請從控制檯執行所有工作。

編譯問題和核心版本

通常情況下,Linux發行版將分發以各種非標準方式修補的核心原始碼,這可能會帶來麻煩。

一個更常見的問題是某些Linux發行版分發不完整的核心標頭檔案。 您需要使用Linux核心中的各種標頭檔案編譯程式碼。 Murphy定律指出缺少的標題正是模組工作所需的標題。

為了避免這兩個問題,我強烈建議您下載,編譯並啟動到可以從任何Linux核心映象站點下載的全新Linux核心。 有關更多詳細資訊,請參閱Linux Kernel HOWTO。

具有諷刺意味的是,這也可能導致問題。 預設情況下,系統上的gcc可能會在預設位置查詢核心標頭檔案,而不是安裝核心新副本的位置(通常在/ usr / src /中 。這可以通過使用gcc的-I開關來修復。

第2章Hello World

Hello,World(第1部分):最簡單的模組

當第一個穴居人程式設計師在第一個洞穴計算機的牆壁上鑿出第一個程式時,它是一個在Antelope圖片中繪製字串“Hello,world”的程式。 羅馬程式設計教科書以“Salut,Mundi”計劃開始。 我不知道那些打破這種傳統的人會發生什麼,但我認為不發現更安全。 我們將從一系列hello world程式開始,這些程式演示了編寫核心模組的基本知識的不同方面。

這是最簡單的模組。 不要編譯它; 我們將在下一節中介紹模組編譯。

例2-1。 HELLO-1.C

/*  
 *  hello-1.c - The simplest kernel module.
 */
#include <linux/module.h>   /* Needed by all modules */
#include <linux/kernel.h>   /* Needed for KERN_INFO */

int init_module(void)
{
    printk(KERN_INFO "Hello world 1.\n");

    /* 
     * A non 0 return means init_module failed; module can't be loaded. 
     */
    return 0;
}

void cleanup_module(void)
{
    printk(KERN_INFO "Goodbye world 1.\n");
}

核心模組必須至少有兩個函式:一個名為init_module()的“start”(初始化)函式,當模組被編入核心時呼叫,以及一個名為cleanup_module()的“end”(清理)函式,只調用在它被破壞之前。 實際上,從核心2.3.13開始,情況發生了變化。 您現在可以使用您喜歡的任何名稱作為模組的開始和結束功能,您將在第2.3節中學習如何執行此操作。 實際上,新方法是首選方法。 但是,許多人仍然使用init_module()和cleanup_module()作為其開始和結束函式。

通常, init_module()要麼為核心註冊一個處理程式,要麼用自己的程式碼替換其中一個核心函式(通常程式碼執行某些操作然後呼叫原始函式)。 cleanup_module()函式應該撤消init_module()所做的任何操作,因此可以安全地解除安裝模組。

最後,每個核心模組都需要包含linux / module.h 。 我們需要包含linux / kernel.h,僅用於printk()日誌級別的KERN_ALERT的巨集擴充套件,您將在第2.1.1節中瞭解它。

介紹printk()

儘管你可能會想到, printk()並不是要向用戶傳達資訊,即使我們在hello-1中將它用於此目的! 它恰好是核心的日誌記錄機制,用於記錄資訊或發出警告。 因此,每個printk()語句都帶有一個優先順序,即您看到的<1>和KERN_ALERT 。 有8個優先順序,核心有巨集,所以你不必使用神祕的數字,你可以在linux / kernel.h中檢視它們(及其含義)。 如果未指定優先順序,則將使用預設優先順序DEFAULT_MESSAGE_LOGLEVEL 。

花點時間閱讀優先順序巨集。 標頭檔案還描述了每個優先順序的含義。 在實踐中,不要使用數字,如<4> 。 始終使用巨集,如KERN_WARNING 。

如果優先順序小於int console_loglevel ,則會在當前終端上列印該訊息。 如果syslogd和klogd都在執行,那麼該訊息也會附加到/ var / log / messages ,無論它是否列印到控制檯。 我們使用高優先順序(如KERN_ALERT )來確保將printk()訊息列印到控制檯而不是僅記錄到日誌檔案中。 編寫實際模組時,您需要使用對當前情況有意義的優先順序。

編譯核心模組

核心模組的編譯需要與常規使用者空間應用程式略有不同。 以前的核心版本要求我們關注這些設定,這些設定通常儲存在Makefile中。 雖然按層次結構組織,但許多冗餘設定在次級Makefile中累積並使它們變大並且難以維護。 幸運的是,有一種新方法可以做這些事情,稱為kbuild,外部可載入模組的構建過程現在完全整合到標準核心構建機制中。 要了解有關如何編譯不屬於官方核心的模組的更多資訊(例如本指南中的所有示例),請參閱檔案linux / Documentation / kbuild / modules.txt 。

那麼,讓我們看一個簡單的Makefile來編譯一個名為hello-1.c的模組:

例2-2。 Makefile用於基本核心模組

obj-m += hello-1.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

從技術角度來看,第一行確實是必要的,為了方便起見,添加了“全部”和“清潔”目標。

現在您可以通過發出命令make來編譯模組。 您應該獲得類似於以下內容的輸出:

hostname:~/lkmpg-examples/02-HelloWorld# make
make -C /lib/modules/2.6.11/build M=/root/lkmpg-examples/02-HelloWorld modules
make[1]: Entering directory `/usr/src/linux-2.6.11'
  CC [M]  /root/lkmpg-examples/02-HelloWorld/hello-1.o
 Building modules, stage 2.
  MODPOST
  CC      /root/lkmpg-examples/02-HelloWorld/hello-1.mod.o
  LD [M]  /root/lkmpg-examples/02-HelloWorld/hello-1.ko
make[1]: Leaving directory `/usr/src/linux-2.6.11'
hostname:~/lkmpg-examples/02-HelloWorld#

請注意,核心2.6引入了一種新的檔案命名約定:核心模組現在具有.ko副檔名(代替舊的.o副檔名),可以輕鬆地將它們與傳統的目標檔案區分開來。 這樣做的原因是它們包含一個額外的.modinfo部分,其中保留了有關該模組的其他資訊。 我們很快就會看到這些資訊有什麼用處。

使用modinfo hello - * .ko來檢視它是什麼型別的資訊。

hostname:~/lkmpg-examples/02-HelloWorld# modinfo hello-1.ko
filename:       hello-1.ko
vermagic:       2.6.11 preempt PENTIUMII 4KSTACKS gcc-3.3
depends:

到目前為止,沒什麼了不起的。 一旦我們在後面的一個示例hello-5.ko上使用modinfo,這就會改變。

hostname:~/lkmpg-examples/02-HelloWorld# modinfo hello-5.ko
filename:       hello-5.ko
license:        GPL
author:         Peter Jay Salzman
vermagic:       2.6.11 preempt PENTIUMII 4KSTACKS gcc-3.3
depends:
parm:           myintArray:An array of integers (array of int)
parm:           mystring:A character string (charp)
parm:           mylong:A long integer (long)
parm:           myint:An integer (int)
parm:           myshort:A short integer (short)
hostname:~/lkmpg-examples/02-HelloWorld# 

很多有用的資訊可以在這裡看到。 bug報告的作者字串,許可證資訊,甚至是它接受的引數的簡短描述。

有關核心模組的Makefile的更多詳細資訊,請參見linux / Documentation / kbuild / makefiles.txt 。 在開始破解Makefile之前,請務必閱讀此檔案和相關檔案。 它可能會為你節省大量的工作。

現在是時候用insmod ./hello-1.ko將新編譯的模組插入到核心中(忽略任何你看到的汙染核心;我們很快就會介紹它)。

載入到核心中的所有模組都列在/ proc / modules中 。 來吧,抓住那個檔案,看看你的模組真的是核心的一部分。 恭喜,您現在是Linux核心程式碼的作者! 當新穎性消失時,使用rmmod hello-1從核心中刪除模組。 檢視/ var / log / messages只是為了看到它已記錄到您的系統日誌檔案中。

這是讀者的另一個練習。 請參閱init_module()中 return語句上方的註釋? 將返回值更改為負值,重新編譯並再次載入模組。 怎麼了?

Hello World(第2部分)

從Linux 2.4開始,您可以重新命名模組的init和cleanup功能; 它們不再需要分別被稱為init_module()和cleanup_module() 。 這是通過module_init()和module_exit()巨集完成的。 這些巨集在linux / init.h中定義。 唯一需要注意的是,必須在呼叫巨集之前定義init和cleanup函式,否則會出現編譯錯誤。 以下是此技術的示例:

例2-3。 HELLO-2.C

/*  
 *  hello-2.c - Demonstrating the module_init() and module_exit() macros.
 *  This is preferred over using init_module() and cleanup_module().
 */
#include <linux/module.h>   /* Needed by all modules */
#include <linux/kernel.h>   /* Needed for KERN_INFO */
#include <linux/init.h>     /* Needed for the macros */

static int __init hello_2_init(void)
{
    printk(KERN_INFO "Hello, world 2\n");
    return 0;
}

static void __exit hello_2_exit(void)
{
    printk(KERN_INFO "Goodbye, world 2\n");
}

module_init(hello_2_init);
module_exit(hello_2_exit);

所以現在我們有兩個真正的核心模組。 新增另一個模組就像這樣簡單:

例2-4。 我們的模組的Makefile

obj-m += hello-1.o
obj-m += hello-2.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

現在看一下linux / drivers / char / Makefile的真實示例。 正如你所看到的,有些東西被硬體連線到核心(obj-y)但是那些obj-m去了哪裡? 熟悉shell指令碼的人很容易發現它們。 對於那些沒有的,你看到的obj - $(CONFIG_FOO)條目會擴充套件為obj-y或obj-m,具體取決於CONFIG_FOO變數是否設定為y或m。 雖然我們在這裡,但那些正是你在linux / .config檔案中設定的那種變數,上次你說make menuconfig之類的東西。

Hello World(第3部分): __ init和__exit巨集

這演示了核心2.2及更高版本的功能。 注意init和cleanup函式定義的變化。 __init巨集導致init函式被丟棄,一旦init函式完成內建驅動程式而不是可載入模組,它的記憶體就會被釋放。 如果你考慮呼叫init函式的時候,這是完全合理的。

還有一個__initdata與__init類似,但是對於init變數而不是函式。

當模組內建到核心中時, __ exit巨集會導致省略函式,而像__exit一樣,對可載入模組沒有影響。 同樣,如果你考慮清理功能何時執行,這就完全合情合理; 內建驅動程式不需要清理功能,而可載入模組則需要清理功能。

這些巨集在linux / init.h中定義,用於釋放核心記憶體。 當您啟動核心並看到釋放未使用的核心記憶體時:釋放236k ,這正是核心正在釋放的內容。

例2-5。 HELLO-3.C


/*  
 *  hello-3.c - Illustrating the __init, __initdata and __exit macros.
 */
#include <linux/module.h>   /* Needed by all modules */
#include <linux/kernel.h>   /* Needed for KERN_INFO */
#include <linux/init.h>     /* Needed for the macros */

static int hello3_data __initdata = 3;

static int __init hello_3_init(void)
{
    printk(KERN_INFO "Hello, world %d\n", hello3_data);
    return 0;
}

static void __exit hello_3_exit(void)
{
    printk(KERN_INFO "Goodbye, world 3\n");
}

module_init(hello_3_init);
module_exit(hello_3_exit);

Hello World(第4部分):許可和模組文件

如果您執行的是核心2.4或更高版本,則在載入專有模組時可能會注意到這樣的內容:

# insmod xxxxxx.o
Warning: loading xxxxxx.ko will taint the kernel: no license
  See http://www.tux.org/lkml/#export-tainted for information about tainted modules
Module xxxxxx loaded, with warnings

在核心2.4及更高版本中,設計了一種機制來識別在GPL(和朋友)下許可的程式碼,以便可以警告人們程式碼是非開源的。 這是通過MODULE_LICENSE()巨集實現的,該巨集在下一段程式碼中進行了演示。 通過將許可證設定為GPL,您可以防止列印警告。 此許可證機制在linux / module.h中定義並記錄:

/*
 * The following license idents are currently accepted as indicating free
 * software modules
 *
 *  "GPL"               [GNU Public License v2 or later]
 *  "GPL v2"            [GNU Public License v2]
 *  "GPL and additional rights" [GNU Public License v2 rights and more]
 *  "Dual BSD/GPL"          [GNU Public License v2
 *                   or BSD license choice]
 *  "Dual MIT/GPL"          [GNU Public License v2
 *                   or MIT license choice]
 *  "Dual MPL/GPL"          [GNU Public License v2
 *                   or Mozilla license choice]
 *
 * The following other idents are available
 *
 *  "Proprietary"           [Non free products]
 *
 * There are dual licensed components, but when running with Linux it is the
 * GPL that is relevant so this is a non issue. Similarly LGPL linked with GPL
 * is a GPL combined work.
 *
 * This exists for several reasons
 * 1.   So modinfo can show license info for users wanting to vet their setup 
 *  is free
 * 2.   So the community can ignore bug reports including proprietary modules
 * 3.   So vendors can do likewise based on their own policies
 */

類似地, MODULE_DESCRIPTION()用於描述模組的功能, MODULE_AUTHOR()宣告模組的作者, MODULE_SUPPORTED_DEVICE()宣告模組支援哪些型別的裝置。

這些巨集都在linux / module.h中定義,並且核心本身不使用它們。 它們只是用於文件,可以通過像objdump這樣的工具檢視。 作為讀者的練習,嘗試在linux / drivers中搜索這些巨集,以瞭解模組作者如何使用這些巨集來記錄他們的模組。

我建議在/usr/src/linux-2.6.x/中使用像grep -inr MODULE_AUTHOR *這樣的東西。 不熟悉命令列工具的人可能會喜歡一些基於Web的解決方案,搜尋提供使用LXR索引的核心樹的站點。 (或在本地計算機上設定)。

傳統Unix編輯器的使用者,如emacs或vi,也會發現標籤檔案很有用。 它們可以通過make標籤生成,也可以在/usr/src/linux-2.6.x/中生成TAGS 。 一旦你在kerneltree中有了這樣的標記檔案,就可以將游標放在一些函式呼叫上,並使用一些組合鍵直接跳轉到定義函式。

例2-6。 hello-4.c

/*  
 *  hello-4.c - Demonstrates module documentation.
 */
#include <linux/module.h>   /* Needed by all modules */
#include <linux/kernel.h>   /* Needed for KERN_INFO */
#include <linux/init.h>     /* Needed for the macros */
#define DRIVER_AUTHOR "Peter Jay Salzman <[email protected]>"
#define DRIVER_DESC   "A sample driver"

static int __init init_hello_4(void)
{
    printk(KERN_INFO "Hello, world 4\n");
    return 0;
}

static void __exit cleanup_hello_4(void)
{
    printk(KERN_INFO "Goodbye, world 4\n");
}

module_init(init_hello_4);
module_exit(cleanup_hello_4);

/*  
 *  You can use strings, like this:
 */

/* 
 * Get rid of taint message by declaring code as GPL. 
 */
MODULE_LICENSE("GPL");

/*
 * Or with defines, like this:
 */
MODULE_AUTHOR(DRIVER_AUTHOR);   /* Who wrote this module? */
MODULE_DESCRIPTION(DRIVER_DESC);    /* What does this module do */

/*  
 *  This module uses /dev/testdevice.  The MODULE_SUPPORTED_DEVICE macro might
 *  be used in the future to help automatic configuration of modules, but is 
 *  currently unused other than for documentation purposes.
 */
MODULE_SUPPORTED_DEVICE("testdevice");

將命令列引數傳遞給模組

模組可以使用命令列引數,但不能使用您可能習慣使用的argc / argv 。

要允許將引數傳遞給模組,請宣告將命令列引數的值作為全域性變數的變數,然後使用module_param()巨集(在linux / moduleparam.h中定義)來設定機制。 在執行時,insmod將使用給定的任何命令列引數填充變數,例如./insmod mymodule.ko myvariable = 5 。 為清楚起見,變數宣告和巨集應放在模組的開頭。 示例程式碼應該清除我公認的糟糕解釋。

module_param()巨集有3個引數:變數的名稱,它在sysfs中對應檔案的型別和許可權。 整數型別可以照常簽名或無符號簽名。 如果您想使用整數或字串數​​組,請參閱module_param_array()和module_param_string() 。

int myint = 3;
module_param(myint, int, 0);

也支援陣列,但現在的情況與2.4中的情況有所不同。 要跟蹤將指標作為第三個引數傳遞給計數變數所需的引數數量。 根據您的選擇,您也可以忽略計數並傳遞NULL。 我們在這裡展示兩種可能性

int myintarray[2];
module_param_array(myintarray, int, NULL, 0); /* not interested in count */

int myshortarray[4];
int count;
module_parm_array(myshortarray, short, , 0); /* put count into "count" variable */

一個很好的用途是設定模組變數的預設值,如埠或IO地址。 如果變數包含預設值,則執行自動檢測(在別處解釋)。 否則,保持當前值。 這將在稍後闡明。

最後,有一個巨集函式MODULE_PARM_DESC() ,用於記錄模組可以採用的引數。 它需要兩個引數:變數名稱和描述該變數的自由格式字串。

例2-7。 HELLO-5.C

/*
 *  hello-5.c - Demonstrates command line argument passing to a module.
 */
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/stat.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Peter Jay Salzman");

static short int myshort = 1;
static int myint = 420;
static long int mylong = 9999;
static char *mystring = "blah";
static int myintArray[2] = { -1, -1 };
static int arr_argc = 0;

/* 
 * module_param(foo, int, 0000)
 * The first param is the parameters name
 * The second param is it's data type
 * The final argument is the permissions bits, 
 * for exposing parameters in sysfs (if non-zero) at a later stage.
 */

module_param(myshort, short, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(myshort, "A short integer");
module_param(myint, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
MODULE_PARM_DESC(myint, "An integer");
module_param(mylong, long, S_IRUSR);
MODULE_PARM_DESC(mylong, "A long integer");
module_param(mystring, charp, 0000);
MODULE_PARM_DESC(mystring, "A character string");

/*
 * module_param_array(name, type, num, perm);
 * The first param is the parameter's (in this case the array's) name
 * The second param is the data type of the elements of the array
 * The third argument is a pointer to the variable that will store the number 
 * of elements of the array initialized by the user at module loading time
 * The fourth argument is the permission bits
 */
module_param_array(myintArray, int, &arr_argc, 0000);
MODULE_PARM_DESC(myintArray, "An array of integers");

static int __init hello_5_init(void)
{
    int i;
    printk(KERN_INFO "Hello, world 5\n=============\n");
    printk(KERN_INFO "myshort is a short integer: %hd\n", myshort);
    printk(KERN_INFO "myint is an integer: %d\n", myint);
    printk(KERN_INFO "mylong is a long integer: %ld\n", mylong);
    printk(KERN_INFO "mystring is a string: %s\n", mystring);
    for (i = 0; i < (sizeof myintArray / sizeof (int)); i++)
    {
        printk(KERN_INFO "myintArray[%d] = %d\n", i, myintArray[i]);
    }
    printk(KERN_INFO "got %d arguments for myintArray.\n", arr_argc);
    return 0;
}

static void __exit hello_5_exit(void)
{
    printk(KERN_INFO "Goodbye, world 5\n");
}

module_init(hello_5_init);
module_exit(hello_5_exit);

我建議玩這個程式碼:

satan# insmod hello-5.ko mystring="bebop" mybyte=255 myintArray=-1
mybyte is an 8 bit integer: 255
myshort is a short integer: 1
myint is an integer: 20
mylong is a long integer: 9999
mystring is a string: bebop
myintArray is -1 and 420

satan# rmmod hello-5
Goodbye, world 5

satan# insmod hello-5.ko mystring="supercalifragilisticexpialidocious" \
> mybyte=256 myintArray=-1,-1
mybyte is an 8 bit integer: 0
myshort is a short integer: 1
myint is an integer: 20
mylong is a long integer: 9999
mystring is a string: supercalifragilisticexpialidocious
myintArray is -1 and -1

satan# rmmod hello-5
Goodbye, world 5

satan# insmod hello-5.ko mylong=hello
hello-5.o: invalid argument syntax for mylong: 'h'

跨越多個檔案的模組

有時在幾個原始檔之間劃分核心模組是有意義的。

這是一個這樣的核心模組的例子。

例2-8。 start.c

/*
 *  start.c - Illustration of multi filed modules
 */

#include <linux/kernel.h>   /* We're doing kernel work */
#include <linux/module.h>   /* Specifically, a module */

int init_module(void)
{
    printk(KERN_INFO "Hello, world - this is the kernel speaking\n");
    return 0;
}

下一個檔案:

例2-9。 stop.c

/*
 *  stop.c - Illustration of multi filed modules
 */

#include <linux/kernel.h>   /* We're doing kernel work */
#include <linux/module.h>   /* Specifically, a module  */

void cleanup_module()
{
    printk(KERN_INFO "Short is the life of a kernel module\n");
}

最後,makefile:

例2-10。 Makefile檔案

obj-m += hello-1.o
obj-m += hello-2.o
obj-m += hello-3.o
obj-m += hello-4.o
obj-m += hello-5.o
obj-m += startstop.o
startstop-objs := start.o stop.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

這是我們到目前為止看到的所有示例的完整makefile。 前五行並不特別,但對於最後一個例子,我們需要兩行。 首先,我們為組合模組建立一個物件名稱,然後我們告訴make什麼物件檔案是該模組的一部分。

構建預編譯核心的模組

顯然,我們強烈建議您重新編譯核心,以便啟用許多有用的除錯功能,例如強制模組解除安裝( MODULE_FORCE_UNLOAD ):啟用此選項後,您可以強制核心解除安裝模組,即使它已經解除安裝認為它是不安全的,通過rmmod -f模組命令。 此選項可以在開發模組期間為您節省大量時間和大量重新啟動。

然而,在許多情況下,您可能希望將模組載入到預編譯的執行核心中,例如通用Linux發行版附帶的核心,或者您過去編譯的核心。 在某些情況下,您可能需要編譯並將模組插入到不允許重新編譯的正在執行的核心中,或者在您不希望重新啟動的計算機上。 如果您不能想到會強制您使用預編譯核心模組的情況,您可能希望跳過這一點,並將本章的其餘部分作為一個重要注意事項處理。

現在,如果您只是安裝核心原始碼樹,使用它來編譯核心模組,並嘗試將模組插入核心,在大多數情況下,您將獲得如下錯誤:

insmod: error inserting 'poet_atkm.ko': -1 Invalid module format

將更少的密碼資訊記錄到/ var / log / messages中 :

Jun  4 22:07:54 localhost kernel: poet_atkm: version magic '2.6.5-1.358custom 686 
REGPARM 4KSTACKS gcc-3.3' should be '2.6.5-1.358 686 REGPARM 4KSTACKS gcc-3.3'

換句話說,你的核心拒絕接受你的模組,因為版本字串(更確切地說,版本魔法)不匹配。 順便提一下,版本魔法以靜態字串的形式儲存在模組物件中,以vermagic:開頭。 當版本資料與init / vermagic.o檔案連結時,會在模組中插入。 要檢查儲存在給定模組中的版本魔法和其他字串,請發出modinfo module.ko命令

[[email protected] 02-HelloWorld]# modinfo hello-4.ko 
license:        GPL
author:         Peter Jay Salzman <[email protected].org>
description:    A sample driver
vermagic:       2.6.5-1.358 686 REGPARM 4KSTACKS gcc-3.3
depends: 

為了克服這個問題,我們可以採用–force-vermagic選項,但這種解決方案可能不安全,而且在生產模組中無疑是不可接受的。 因此,我們希望在與構建預編譯核心的環境相同的環境中編譯模組。 如何做到這一點,是本章其餘部分的主題。

首先,確保核心原始碼樹可用,與當前核心的版本完全相同。 然後,找到用於編譯預編譯核心的配置檔案。 通常,這在您當前的/ boot目錄中,在config-2.6.x之類的名稱下可用。 您可能只想將其複製到核心原始碼樹: cp / boot / config-uname -r / usr / src / linux -uname -r / .config 。

讓我們再次關注上一個錯誤訊息:仔細檢視版本魔術字串表明,即使兩個配置檔案完全相同,版本魔法也可能略有不同,並且足以防止插入將模組放入核心。 這個細微的差別,即出現在模組版本魔術而不是核心版本中的自定義字串,是由於在某些分發包含的makefile中對原始檔案進行了修改。 然後,檢查/ usr / src / linux / Makefile ,並確保指定的版本資訊與用於當前核心的版本資訊完全匹配。 例如,makefile可以如下開始:

VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 5
EXTRAVERSION = -1.358custom
...

在這種情況下,您需要將符號EXTRAVERSION的值恢復為-1.358 。 我們建議在/lib/modules/2.6.5-1.358/build中保留用於編譯核心的makefile的備份副本。 一個簡單的cp / lib / modules /uname -r / build / Makefile / usr / src / linux -uname -r就足夠了。 另外,如果你已經使用之前的(錯誤的) Makefile啟動了核心構建,你還應該重新執行make ,或者直接修改檔案/usr/src/linux-2.6.x/include/linux/version.h中的符號UTS_RELEASE 。檔案/lib/modules/2.6.x/build/include/linux/version.h的內容,或用第一個覆蓋後者。

現在,請執行make來更新配置和版本標頭和物件:

[[email protected] linux-2.6.x]# make
CHK     include/linux/version.h
UPD     include/linux/version.h
SYMLINK include/asm -> include/asm-i386
SPLIT   include/linux/autoconf.h -> include/config/*
HOSTCC  scripts/basic/fixdep
HOSTCC  scripts/basic/split-include
HOSTCC  scripts/basic/docproc
HOSTCC  scripts/conmakehash
HOSTCC  scripts/kallsyms
CC      scripts/empty.o
...

如果您不希望實際編譯核心,可以在SPLIT行之後中斷構建過程( CTRL-C ),因為那時您需要的檔案已準備就緒。 現在您可以返回到模組的目錄並進行編譯:它將完全根據您當前的核心設定構建,並且將載入到其中而不會出現任何錯誤。

第3章 Preliminaries

模組與程式

模組如何開始和結束

程式通常以main()函式開始,執行一堆指令並在完成這些指令後終止。 核心模組的工作方式略有不同。 模組始終以init_module或您通過module_init呼叫指定的函式開頭。 這是模組的入口功能; 它告訴核心模組提供了哪些功能,並設定核心以在需要時執行模組的功能。 一旦它執行此操作,入口函式返回,並且模組不執行任何操作,直到核心想要對模組提供的程式碼執行某些操作。

所有模組都通過呼叫cleanup_module或您使用module_exit呼叫指定的函式來結束。 這是模組的退出功能; 它取消了任何輸入功能。 它取消註冊入口函式註冊的功能。

每個模組都必須具有入口功能和退出功能。 由於指定入口和出口函式的方法不止一種,我會盡量使用“入口函式”和“退出函式”這兩個術語,但如果我滑動並簡單地將它們稱為init_module和cleanup_module ,我想你我會明白我的意思。

模組可用的功能

程式設計師使用他們不會一直定義的函式。 一個主要的例子是printf() 。 您可以使用標準C庫libc提供的這些庫函式。 這些函式的定義實際上不會進入程式,直到連結階段,這確保程式碼(例如printf() )可用,並修復呼叫指令以指向該程式碼。

核心模組也在這裡不同。 在hello world示例中,您可能已經注意到我們使用了一個函式printk()但沒有包含標準I / O庫。 那是因為模組是目標檔案,其符號在insmod’ing時得到解決。 符號的定義來自核心本身; 您可以使用的唯一外部函式是核心提供的函式。 如果您對核心匯出的符號感到好奇,請檢視/ proc / kallsyms 。

需要記住的一點是庫函式和系統呼叫之間的區別。 庫函式是更高級別的,完全在使用者空間中執行,併為程式設計師提供了更方便的介面,使其能夠執行真正的工作 - 系統呼叫。 系統呼叫代表使用者以核心模式執行,由核心本身提供。 庫函式printf()可能看起來像一個非常通用的列印函式,但它真正做的就是將資料格式化為字串並使用低階系統呼叫write()寫入字串資料,然後將資料傳送到標準輸出。

您想檢視printf()進行的系統呼叫嗎? 這很容易! 編譯以下程式:

#include <stdio.h>
int main(void)
{ printf("hello"); return 0; }

使用gcc -Wall -o hello hello.c 。 使用strace ./hello執行exectable 。 你印象深刻嗎? 您看到的每一行都對應一個系統呼叫。 strace [4]是一個方便的程式,它為您提供有關程式正在進行的系統呼叫的詳細資訊,包括呼叫哪個呼叫,它的引數是什麼。 它是一個非常寶貴的工具,用於確定程式試圖訪問的檔案。 接近尾聲,你會看到一條看起來像寫的行(1,“你好”,5hello) 。 它就是。 printf()面具後面的面孔。 您可能不熟悉寫入,因為大多數人使用庫函式進行檔案I / O(如fopen,fputs,fclose)。 如果是這種情況,請嘗試檢視man 2 。 第二個人部分專門用於系統呼叫(如kill()和read() 。第三個人部分專門用於庫呼叫,你可能會更熟悉它們(如cosh()和random() )。

您甚至可以編寫模組來替換核心的系統呼叫,我們很快就會這樣做。 破解者經常使用這種東西來做後門或特洛伊木馬,但是你可以編寫自己的模組來做更多的良性事情,就像核心寫的Tee嘻嘻,發癢! 每次有人試圖刪除系統上的檔案。

使用者空間與核心空間

核心就是對資源的訪問,無論所討論的資源是視訊卡,硬碟還是記憶體。 程式通常競爭相同的資源。 在我剛剛儲存此文件時,updatedb開始更新locate資料庫。 我的vim會話和updatedb都同時使用硬碟驅動器。 核心需要保持整齊有序,並且不會讓使用者隨時訪問資源。 為此, CPU可以以不同的模式執行。 每種模式都提供了不同的自由度,可以在系統上執行您想要的操作。 英特爾80386架構有4種這樣的模式,稱為環。 Unix只使用兩個環; 最高的環(環0,也稱為“管理員模式”,允許一切都發生)和最低環,稱為“使用者模式”。

回想一下有關庫函式與系統呼叫的討論。 通常,您在使用者模式下使用庫函式。 庫函式呼叫一個或多個系統呼叫,這些系統呼叫代表庫函式執行,但是在管理員模式下執行,因為它們是核心本身的一部分。 一旦系統呼叫完成其任務,它將返回並執行轉移回使用者模式。

名稱空間

當您編寫一個小型C程式時,您可以使用方便且對讀者有意義的變數。 另一方面,如果你正在編寫將成為更大問題的一部分的例程,那麼你擁有的任何全域性變數都是其他人的全域性變數社群的一部分; 一些變數名稱可能會發生衝突。 當一個程式有許多全域性變數,這些變數沒有足夠的意義可以區分時,就會產生名稱空間汙染 。 在大型專案中,必須努力記住保留名稱,並找到開發用於命名唯一變數名稱和符號的方案的方法。

在編寫核心程式碼時,即使最小的模組也會連結到整個核心,所以這肯定是個問題。 處理此問題的最佳方法是將所有變數宣告為靜態變數,併為符號使用明確定義的字首。 按照慣例,所有核心字首都是小寫的。 如果您不想將所有內容宣告為靜態 ,則另一個選項是宣告符號表並將其註冊到核心。 我們稍後會談到這個。

檔案/ proc / kallsyms包含核心知道的所有符號,因此它們可以訪問模組,因為它們共享核心的程式碼空間。

程式碼空間

記憶體管理是一個非常複雜的主題— O’Reilly的大部分“理解Linux核心”僅僅是記憶體管理! 我們不打算成為記憶體管理方面的專家,但我們確實需要了解一些事實,甚至開始擔心編寫真正的模組。

如果您還沒有想過段錯誤的真正含義,您可能會驚訝地發現指標實際上並未指向記憶體位置。 不管怎麼說,不是真的。 當建立程序時,核心會留出一部分真實實體記憶體並將其交給程序,以用於執行程式碼,變數,堆疊,堆和電腦科學家所知道的其他事情[5] 。 該儲存器以0x00000000開頭,並擴充套件到它需要的任何內容。 由於任何兩個程序的記憶體空間不重疊,因此每個可以訪問記憶體地址的程序(例如0xbffff978 )都將訪問實際實體記憶體中的不同位置! 這些程序將訪問名為0xbffff978的索引,該索引指向為該特定程序留出的記憶體區域中的某種偏移量。 在大多數情況下,像我們的Hello,World程式這樣的過程無法訪問另一個程序的空間,儘管我們稍後會討論一些方法。

核心也有自己的記憶體空間。 由於模組是可以在核心中動態插入和刪除的程式碼(而不是半自治物件),因此它共享核心的程式碼空間而不是自己的程式碼空間。 因此,如果你的模組是segfaults,那麼核心會出現段錯誤。 如果你因為一個錯誤的錯誤而開始寫資料,那麼你就是在踐踏核心資料(或程式碼)。 這比聽起來還要糟糕,所以儘量小心。

順便提一下,我想指出上述討論適用於任何使用單片核心的作業系統[6] 。 有一些稱為微核心的東西,它們有模組可以獲得自己的程式碼空間。 GNU Hurd和QNX Neutrino是微核心的兩個例子。

裝置驅動程式

一類模組是裝置驅動程式,它為電視卡或串列埠等硬體提供功能。 在unix上,每個硬體都由位於/ dev中的檔案表示,該檔案命名為裝置檔案 ,該檔案提供與硬體通訊的方法。 裝置驅動程式代表使用者程式提供通訊。 所以es1370.o音效卡裝置驅動程式可能會將/ dev / sound裝置檔案連線到Ensoniq IS1370音效卡。 像mp3blaster這樣的使用者空間程式可以使用/ dev / sound而不知道安裝了什麼型別的音效卡。

主要和次要號碼

我們來看一些裝置檔案。 以下是代表主要主IDE硬碟驅動器上前三個分割槽的裝置檔案:

# ls -l /dev/hda[1-3]
brw-rw----  1 root  disk  3, 1 Jul  5  2000 /dev/hda1
brw-rw----  1 root  disk  3, 2 Jul  5  2000 /dev/hda2
brw-rw----  1 root  disk  3, 3 Jul  5  2000 /dev/hda3

注意用逗號分隔的數字列? 第一個數字稱為裝置的主要編號。 第二個數字是次要數字。 主要編號告訴您使用哪個驅動程式訪問硬體。 為每個驅動程式分配一個唯一的主編號; 具有相同主要編號的所有裝置檔案由同一驅動程式控制。 所有上述主要數字均為3,因為它們都由同一個驅動程式控制。

驅動程式使用次要編號來區分它控制的各種硬體。 回到上面的例子,雖然所有三個裝置都由相同的驅動程式處理,但它們具有唯一的次要編號,因為驅動程式將它們視為不同的硬體。

裝置分為兩種型別:字元裝置和塊裝置。 區別在於塊裝置具有請求緩衝區,因此它們可以選擇響應請求的最佳順序。 這在儲存裝置的情況下是重要的,其中讀取或寫入彼此接近的扇區更快,而不是那些相距更遠的扇區。 另一個區別是塊裝置只能接受塊中的輸入和返回輸出(其大小可以根據裝置而變化),而字元裝置允許使用它們喜歡的儘可能多的位元組。 世界上大多數裝置都是字元,因為它們不需要這種型別的緩衝,並且它們不以固定的塊大小執行。 您可以通過檢視ls -l輸出中的第一個字元來判斷裝置檔案是用於塊裝置還是字元裝置。 如果它是’b’那麼它就是一個塊裝置,如果它是’c’那麼它就是一個字元裝置。 您在上面看到的裝置是塊裝置。 以下是一些字元裝置(串列埠):

crw-rw----  1 root  dial 4, 64 Feb 18 23:34 /dev/ttyS0
crw-r-----  1 root  dial 4, 65 Nov 17 10:26 /dev/ttyS1
crw-rw----  1 root  dial 4, 66 Jul  5  2000 /dev/ttyS2
crw-rw----  1 root  dial 4, 67 Jul  5  2000 /dev/ttyS3

如果要檢視已分配的主要編號,可以檢視/usr/src/linux/Documentation/devices.txt 。

安裝系統後,所有這些裝置檔案都是由mknod命令建立的。 要建立一個名為“coffee”且主要/次要編號為12和2的新char裝置,只需執行mknod / dev / coffee c 12 2即可 。 您不必將裝置檔案放入/ dev ,但它是按慣例完成的。 Linus將他的裝置檔案放在/ dev中 ,所以你應該這樣做。 但是,在建立用於測試目的的裝置檔案時,可以將它放在編譯核心模組的工作目錄中。 完成編寫裝置驅動程式後,請務必將其放在正確的位置。

我想提出一些隱含在上述討論中的最後幾點,但為了以防萬一,我想明確一些。 訪問裝置檔案時,核心使用檔案的主編號來確定應使用哪個驅動程式來處理訪問。 這意味著核心實際上並不需要使用甚至不知道次要編號。 司機本身是唯一關心次要號碼的人。 它使用次要編號來區分不同的硬體。

順便說一句,當我說“硬體”時,我的意思是比你手裡拿著的PCI卡更抽象。 看看這兩個裝置檔案:

% ls -l /dev/fd0 /dev/fd0u1680
brwxrwxrwx   1 root  floppy   2,  0 Jul  5  2000 /dev/fd0
brw-rw----   1 root  floppy   2, 44 Jul  5  2000 /dev/fd0u1680

到目前為止,您可以檢視這兩個裝置檔案並立即知道它們是塊裝置並由相同的驅動程式處理(塊主要2 )。 您甚至可能知道這些都代表您的軟盤驅動器,即使您只有一個軟盤驅動器。 為什麼兩個檔案? 一個代表具有1.44 MB儲存空間的軟盤驅動器。 另一個是具有1.68 MB儲存空間的相同軟盤驅動器,並且對應於某些人稱之為“超格式化”的磁碟。 比標準格式化軟盤擁有更多資料的資料。 所以這裡有兩個具有不同次要編號的裝置檔案實際上代表同一塊物理硬體的情況。 所以請注意,我們討論中的“硬體”這個詞可能意味著非常抽象的東西。

相關推薦

Linux核心模組程式設計指南()

Peter Jay Salzman Michael Burian Ori Pomerantz Copyright © 2001 Peter Jay Salzman 2007-05-18 ver 2.6.4 Linux核心模組程式設計指南是一本免費的

linux 核心模組程式設計之環境搭建(

這裡介紹些關於Tiny6410開發板核心的編譯,為後期驅動開發做前期的準備。 開發環境:64位的Ubuntu 14.01虛擬機器 目標機:友善之臂Tiny6410開發板 核心:linux-2.6.38-20110325.tar.gz 核心原始碼下載地址 htt

Linux核心模組程式設計

Linux核心模組程式設計 (作者:Baron_wu 禁止轉載) 首先,建立一個核心模組並插入Linux核心中。這是實驗第一部分 首先檢視當前核心模組使用情概況:lsmod Module:模組名 Size:模組大小 Used by:這些模組在哪被使用 接下來編寫一個simple.c

linux 核心模組程式設計之LED驅動程式(六)

我使用的是tiny6410的核心板,板子如下,淘寶可以買到 為了不與板子上的任何驅動發生IO衝突,我使用CON1那一排沒用到的IO口,引腳如下   LED1 LED2 LED3 LED4

linux 核心模組程式設計核心符號匯出(五)

/proc/kallsyms 記錄了核心中所有匯出的符號的名字與地址 我們需要編譯2個核心模組,然後其中一個核心模組去呼叫另一個核心模組中的函式 hello.c程式碼如下 #include <linux/module.h> #include <linux/in

linux 核心模組程式設計模組引數(四)

通過巨集module_param指定模組引數,模組引數用於在載入模組時傳遞給模組。 module_param(name, type, perm) name是模組引數的名字 type是這個引數的型別,常見值:bool、int、charp(字串型) perm是模組

linux 核心模組程式設計之編譯多個原始檔(三)

編譯擁有多個原始檔的核心模組的方式和編譯一個原始檔的方式差不多,我們先來看下我們需要的檔案都有哪些。 首先是main.c檔案 #include <linux/module.h> #include <linux/init.h> MODULE_LICENSE

linux 核心模組程式設計之hello word(二)

我們的目的是要編譯個hello.ko的檔案,然後安裝到核心中。 先來看下需要的程式碼,hello.c檔案如下 #include <linux/module.h> #include <linux/init.h> static int hello_init(vo

Linux核心模組程式設計——Hello World

一、實驗環境: 環境配置:VMware® Workstation 15 Pro、ubuntu Desktop 18.10、記憶體 2GB、處理器數量2、每個處理器核心數量1、硬碟大小30GB……還有一個就是用的咱Xidian的源(因為校內不需要流量啊,而且還很快!) 二、知識儲備

Linux核心模組程式設計系列1

1.準備工作 使用如下命令檢視自己Linux的核心版本 uname -a 結果如下: Linux VM-73-203-debian 4.9.0-6-amd64 #1 SMP Debian 4.9.88-1+deb9u1 (2018-05-07) x86_64 GNU/Lin

linux 核心模組程式設計

一個linux核心模組主要由以下幾個部分組成。 1、模組載入函式"用module_init()來指定"(必須)    當通過insmod和modprobe命令載入核心模組時,模組的載入函式會自動被核心執行,完成本模組的相關初始化工作。 linux模組載入函式一般以 __init表示宣告。典型宣告如下:: s

Linux核心模組程式設計(列出系統中所有核心執行緒的程式名、PID 號、程序狀態及程序優先順序、父程序的PID)

(1) 設計一個模組,要求列出系統中所有核心執行緒的程式名、PID 號、程序狀態及程序優先順序、父程序的PID。 1.首先,我們開始編寫模組程式碼,這是Linux核心程式設計的核心程式碼,程式碼如下: #include <linux/init.h&

Linux核心模組程式設計-proc檔案系統

什麼是proc proc檔案系統是一個偽檔案系統,它只存在記憶體當中,而不佔用外存空間。它以檔案系統的方式為訪問系統核心資料的操作提供介面。使用者和應用程式可以通過proc得到系統的資訊,並可以改變核心的某些引數。由於系統的資訊,如程序,是動態改變的,所以使用

Linux核心模組程式設計——hello,world

檔案hello.c(放在目錄/root/lnq/modules/hello下): #include<linux/kernel.h> #include<linux/module.h

Linux核心模組程式設計核心模組LICENSE -《詳解(第3版)》預讀

Linux核心模組簡介Linux核心的整體結構已經非常龐大,而其包含的元件也非常多。我們怎樣把需要的部分都包含在核心中呢?一種方法是把所有需要的功能都編譯到Linux核心。這會導致兩個問題,一是生成的核心會很大,二是如果我們要在現有的核心中新增或刪除功能,將不得不重新編譯核心

Linux核心模組程式設計-字元裝置驅動

裝置驅動簡介 裝置被大概的分為兩類: 字元裝置和塊裝置。 字元裝置 提供連續的資料流,應用程式可以順序讀取,通常不支援隨機存取。相反,此類裝置支援按位元組/字元來讀寫資料。舉例來說,鍵盤、串列埠、調變解調器都是典型的字元裝置。 塊裝置 應用程式可以隨機

Linux核心模組程式設計-HelloWorld

HelloWorld核心 開始斷斷續續學習核心,大概半年了,多少開始對核心有點感悟了,但是對於這個龐然大物我顯得很渺小,在枯燥的核心原始碼之中似乎沒有一點點成功的喜悅,因此我選擇學習核心模組程式設計,通過編寫一些核心模組來體驗那一點點小小的成就感吧! 什麼是

Linux 核心模組程式設計 Hello World 模組

         Linux 核心的整體結構非常龐大,其包含的元件也非常多。怎麼樣把需要的部分包含在核心中呢?         一種是將所需要的功能都編譯到Linux 核心。但會導致兩個問題,一是生成的核心會很大,二是如要在現有的核心中新增或刪除功能,將不得不重新編譯。因此L

Linux 模組程式設計指南

Linux模組程式設計 1.1 模組學習什麼 1.認識什麼是模組?跟我學習的驅動有什麼關係? 2.熟悉模組的安裝,解除安裝,檢視 3.熟悉模組的基本框架 4.熟悉模組的程式設計方法 1.2 核心模組概述 Linux 核心的整體結構非常龐大,其包含的元件也非常多。我

嵌入式Linux驅動開發()一個簡單的Linux核心模組框架

#include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> static