<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手把手教你|如何編寫一個Linux內(nèi)核模塊

          共 6268字,需瀏覽 13分鐘

           ·

          2021-10-09 10:09

          Linux 系統(tǒng)為應(yīng)用程序提供了功能強大且容易擴展的 API,但在某些情況下,這還遠遠不夠。與硬件交互或進行需要訪問系統(tǒng)中特權(quán)信息的操作時,就需要一個內(nèi)核模塊。

          Linux 內(nèi)核模塊是一段編譯后的二進制代碼,直接插入 Linux 內(nèi)核中,在?Ring 0(x86–64處理器中執(zhí)行最低和受保護程度最低的執(zhí)行環(huán))上運行。這里的代碼完全不受檢查,但是運行速度很快,可以訪問系統(tǒng)中的所有內(nèi)容。

          Intel x86架構(gòu)使用了4個級別來標(biāo)明不同的特權(quán)級。Ring 0?實際就是內(nèi)核態(tài),擁有最高權(quán)限。而一般應(yīng)用程序處于?Ring 3?狀態(tài)--用戶態(tài)。在Linux中,還存在?Ring 1?和Ring 2?兩個級別,一般歸屬驅(qū)動程序的級別。在Windows平臺沒有?Ring 1?和?Ring 2?兩個級別,只用?Ring 0?內(nèi)核態(tài)和?Ring 3?用戶態(tài)。在權(quán)限約束上,高特權(quán)等級狀態(tài)可以閱讀低特權(quán)等級狀態(tài)的數(shù)據(jù),例如進程上下文、代碼、數(shù)據(jù)等等,但反之則不可。Ring 0?最高可以讀取?Ring 0-3?所有的內(nèi)容,Ring 1?可以讀?Ring 1-3?的,Ring 2?以此類推,Ring 3?只能讀自己的數(shù)據(jù)。

          1. 為什么要開發(fā)內(nèi)核模塊

          編寫Linux內(nèi)核模塊并不是因為內(nèi)核太龐大而不敢修改。直接修改內(nèi)核源碼會導(dǎo)致很多問題,例如:通過更改內(nèi)核,你將面臨數(shù)據(jù)丟失和系統(tǒng)損壞的風(fēng)險。內(nèi)核代碼沒有常規(guī)Linux應(yīng)用程序所擁有的安全防護機制,如果內(nèi)核發(fā)生故障,將鎖死整個系統(tǒng)。

          更糟糕的是,當(dāng)你修改內(nèi)核并導(dǎo)致錯誤后,可能不會立即表現(xiàn)出來。如果模塊發(fā)生錯誤,在其加載時就鎖定系統(tǒng)是最好的選擇,如果不鎖定,當(dāng)你向模塊中添加更多代碼時,你將會面臨失控循環(huán)和內(nèi)存泄漏的風(fēng)險,如果不小心,它們會隨著計算機繼續(xù)運行而持續(xù)增長,最終,關(guān)鍵的存儲器結(jié)構(gòu)甚至緩沖區(qū)都可能被覆蓋。

          編寫內(nèi)核模塊時,基本是可以丟棄傳統(tǒng)的應(yīng)用程序開發(fā)范例。除了加載和卸載模塊之外,你還需要編寫響應(yīng)系統(tǒng)事件的代碼(而不是按順序模式執(zhí)行的代碼)。通過內(nèi)核開發(fā),你正在編寫API,而不是應(yīng)用程序。

          你也無權(quán)訪問標(biāo)準(zhǔn)庫,雖然內(nèi)核提供了一些函數(shù),例如?printk(可替代printf)和?kmalloc(以與malloc相似的方式運行),但你在很大程度上只能使用自己的設(shè)備。此外,在卸載模塊時,你需要將自己清理干凈,系統(tǒng)不會在你的模塊被卸載后進行垃圾回收。

          2. 準(zhǔn)備

          開始編寫Linux內(nèi)核模塊之前,我們首先要準(zhǔn)備一些工具。最重要的是,你需要有一臺Linux機器,盡管可以使用任何Linux發(fā)行版,但本文中,我使用的是Ubuntu 16.04 LTS,如果你使用的其他發(fā)行版,可能需要稍微調(diào)整安裝命令。

          其次,你需要一臺物理機或虛擬機,我不建議你直接使用物理機編寫內(nèi)核模塊,因為當(dāng)你出錯時,主機的數(shù)據(jù)可能會丟失。在編寫和調(diào)試內(nèi)核模塊的過程中,你至少會鎖定機器幾次,內(nèi)核崩潰時,最新的代碼更改可能仍在寫緩沖區(qū)中,因此,你的源文件可能會損壞,在虛擬機中進行測試可以避免這種風(fēng)險。

          最后,你至少需要了解一些C。對于內(nèi)核來說,C++在運行時太大了,因此編寫純C代碼是必不可少的。另外,對于其與硬件的交互,了解一些組件可能會有所幫助。

          3. 安裝開發(fā)環(huán)境

          在Ubuntu上,我們需要運行以下代碼:

          sudo?apt-get?install?build-essential?linux-headers-`uname?-r`

          這將安裝本文所需的基本開發(fā)工具和內(nèi)核頭文件。

          以下示例假定你以普通用戶身份而非?root?用戶身份運行,但你具有?sudo?特權(quán)。sudo?是加載內(nèi)核模塊必需的,但是我們希望盡可能在非?root?權(quán)限下工作。

          4. 入門模塊

          讓我們開始編寫一些代碼,準(zhǔn)備環(huán)境:

          mkdir?-p??/src/lkm_example
          cd??/src/lkm_example

          啟動您喜歡的編輯器(在我的例子中是vim),并創(chuàng)建具有以下內(nèi)容的文件 lkm_example.c

          #include?
          #include?
          #include?

          MODULE_LICENSE("GPL");
          MODULE_AUTHOR("abin");
          MODULE_DESCRIPTION("A?simple?example?Linux?module.");
          MODULE_VERSION("0.01");

          static?int?__init?lkm_example_init(void)?{
          ?printk(KERN_INFO?"Hello,?World!\n");
          ?return?0;
          }
          static?void?__exit?lkm_example_exit(void)?{
          ?printk(KERN_INFO?"Goodbye,?World!\n");
          }

          module_init(lkm_example_init);
          module_exit(lkm_example_exit);

          現(xiàn)在,我們已經(jīng)構(gòu)建了最簡單的內(nèi)核模塊,下面介紹代碼的細節(jié):

          "includes" 包括Linux內(nèi)核開發(fā)所需的必需頭文件。

          根據(jù)模塊的許可證,可以將?MODULE_LICENSE?設(shè)置為各種值。要查看完整列表,請運行:

          grep?"MODULE_LICENSE"?-B?27?/usr/src/linux-headers-`uname?-r`/include/linux/module.h

          我們將?init(加載)和?exit(卸載)函數(shù)都定義為靜態(tài)并返回?int

          注意使用?printk?而不是?printf,另外,printk?與?printf?共享的參數(shù)也不相同。例如,KERN_INFO?是一個標(biāo)志,用于聲明應(yīng)為該行設(shè)置的日志記錄優(yōu)先級,并且不帶逗號。內(nèi)核在printk函數(shù)中對此進行分類以節(jié)省堆棧內(nèi)存。

          在文件末尾,我們調(diào)用?module_init?和?module_exit?函數(shù)告訴內(nèi)核哪些函數(shù)是內(nèi)核模塊的加載和卸載函數(shù)。這使我們可以任意命名這兩個函數(shù)。

          目前,還無法編譯此文件,我們需要一個?Makefile,請注意,make?對于空格和制表符敏感,因此請確保在適當(dāng)?shù)牡胤绞褂弥票矸皇强崭瘛?/p>

          obj-m?+=?lkm_example.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",它將成功編譯你編寫的模塊,編譯后的文件為 "lkm_example.ko",如果收到任何錯誤,請檢查示例源文件中的引號是否正確,并且不要將其粘貼為UTF-8字符。

          現(xiàn)在我們可以將此模塊加載進內(nèi)核進行測試了,命令如下:

          sudo?insmod?lkm_example.ko

          如果一切順利,你將看不到任何輸出,因為?printk?函數(shù)不會輸出到控制臺,而是輸出到內(nèi)核日志。要看到內(nèi)核日志中的內(nèi)容,我們需要運行:

          sudo?dmesg

          你應(yīng)該看到以時間戳為前綴的行:"Hello, World!",這意味著我們的內(nèi)核模塊已加載并成功打印到內(nèi)核日志中。

          我們還可以檢查模塊是否已被加載:

          lsmod?|?grep?"lkm_example"

          要卸載模塊,運行:

          sudo?rmmod?lkm_example

          如果再次運行?dmesg,你將看到"Goodbye, World!" 在日志中。你也可以再次使用?lsmod?命令確認(rèn)它已卸載。

          如你所見,此測試工作流程有點繁瑣,因此要使其自動化,我們可以在?Makefile?中添加:

          test:
          ?sudo?dmesg?-C
          ?sudo?insmod?lkm_example.ko
          ?sudo?rmmod?lkm_example.ko
          ?dmesg

          現(xiàn)在,運行:

          make?test

          測試我們的模塊并查看內(nèi)核日志的輸出,而不必運行單獨的命令。

          現(xiàn)在,我們有了一個功能齊全,但又很簡單的內(nèi)核模塊!

          5. 一般模塊

          讓我們再思考下。盡管內(nèi)核模塊可以完成各種任務(wù),但與應(yīng)用程序進行交互是其最常見的用途之一。

          由于操作系統(tǒng)限制了應(yīng)用程序查看內(nèi)核空間內(nèi)存的內(nèi)容,因此,應(yīng)用程序必須使用API與內(nèi)核進行通信。盡管從技術(shù)上講,有多種方法可以完成此操作,但最常見的方法是創(chuàng)建設(shè)備文件。

          你以前可能已經(jīng)與設(shè)備文件進行過交互。使用?/dev/zero,/dev/null?或類似設(shè)備的命令就是與名為 zero 和 null 的設(shè)備進行交互,這些設(shè)備將返回期望的值。

          在我們的示例中,我們將返回 "Hello,World",雖然這些字符串對于應(yīng)用程序并沒有什么用,但它將顯示通過設(shè)備文件響應(yīng)應(yīng)用程序的過程。

          這是完整代碼:

          #include?
          #include?
          #include?
          #include?
          #include?

          MODULE_LICENSE("GPL");
          MODULE_AUTHOR("Robert?W.?Oliver?II");
          MODULE_DESCRIPTION("A?simple?example?Linux?module.");
          MODULE_VERSION("0.01");

          #define?DEVICE_NAME?"lkm_example"
          #define?EXAMPLE_MSG?"Hello,?World!\n"
          #define?MSG_BUFFER_LEN?15

          /*?Prototypes?for?device?functions?*/
          static?int?device_open(struct?inode?*,?struct?file?*);
          static?int?device_release(struct?inode?*,?struct?file?*);
          static?ssize_t?device_read(struct?file?*,?char?*,?size_t,?loff_t?*);
          static?ssize_t?device_write(struct?file?*,?const?char?*,?size_t,?loff_t?*);
          ???????????????
          static?int?major_num;
          static?int?device_open_count?=?0;
          static?char?msg_buffer[MSG_BUFFER_LEN];
          static?char?*msg_ptr;
          ???????????????
          /*?This?structure?points?to?all?of?the?device?functions?*/
          static?struct?file_operations?file_ops?=?{
          ?.read?=?device_read,
          ?.write?=?device_write,
          ?.open?=?device_open,
          ?.release?=?device_release
          };
          ???????????????
          /*?When?a?process?reads?from?our?device,?this?gets?called.?*/
          static?ssize_t?device_read(struct?file?*flip,?char?*buffer,?size_t?len,?loff_t?*offset)?{
          ?int?bytes_read?=?0;
          ??/*?If?we’re?at?the?end,?loop?back?to?the?beginning?*/
          ??if?(*msg_ptr?==?0)?{
          ???msg_ptr?=?msg_buffer;
          ??}
          ??/*?Put?data?in?the?buffer?*/
          ??while?(len?&&?*msg_ptr)?{
          ????/*?Buffer?is?in?user?data,?not?kernel,?so?you?can’t?just?reference
          ?????*?with?a?pointer.?The?function?put_user?handles?this?for?us?*/

          ????put_user(*(msg_ptr++),?buffer++);
          ????len--;
          ????bytes_read++;
          ?}
          ??return?bytes_read;
          }

          /*?Called?when?a?process?tries?to?write?to?our?device?*/
          static?ssize_t?device_write(struct?file?*flip,?const?char?*buffer,?size_t?len,?loff_t?*offset)?{
          ?/*?This?is?a?read-only?device?*/
          ??printk(KERN_ALERT?"This?operation?is?not?supported.\n");
          ??return?-EINVAL;
          }
          ?????????
          /*?Called?when?a?process?opens?our?device?*/
          static?int?device_open(struct?inode?*inode,?struct?file?*file)?{
          ??/*?If?device?is?open,?return?busy?*/
          ??if?(device_open_count)?{
          ???return?-EBUSY;
          ??}
          ??device_open_count++;
          ??try_module_get(THIS_MODULE);
          ??return?0;
          }
          ?????????
          /*?Called?when?a?process?closes?our?device?*/
          static?int?device_release(struct?inode?*inode,?struct?file?*file)?{
          ??/*?Decrement?the?open?counter?and?usage?count.?Without?this,?the?module?would?not?unload.?*/
          ??device_open_count--;
          ??module_put(THIS_MODULE);
          ??return?0;
          }
          ?????????
          static?int?__init?lkm_example_init(void)?{
          ??/*?Fill?buffer?with?our?message?*/
          ??strncpy(msg_buffer,?EXAMPLE_MSG,?MSG_BUFFER_LEN);
          ??/*?Set?the?msg_ptr?to?the?buffer?*/
          ??msg_ptr?=?msg_buffer;
          ??/*?Try?to?register?character?device?*/
          ??major_num?=?register_chrdev(0,?"lkm_example",?&file_ops);
          ??if?(major_num?0)?{
          ???printk(KERN_ALERT?"Could?not?register?device:?%d\n",?major_num);
          ???return?major_num;
          ??}?else?{
          ???printk(KERN_INFO?"lkm_example?module?loaded?with?device?major?number?%d\n",?major_num);
          ???return?0;
          ??}
          }

          static?void?__exit?lkm_example_exit(void)?{
          ??/*?Remember?—?we?have?to?clean?up?after?ourselves.?Unregister?the?character?device.?*/
          ??unregister_chrdev(major_num,?DEVICE_NAME);
          ??printk(KERN_INFO?"Goodbye,?World!\n");
          }

          /*?Register?module?functions?*/
          module_init(lkm_example_init);
          module_exit(lkm_example_exit);

          既然我們的示例所做的不僅僅是在加載和卸載時打印一條消息,讓我們修改Makefile,使其僅加載模塊而不卸載模塊:

          obj-m?+=?lkm_example.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
          test:
          ??#?We?put?a?—?in?front?of?the?rmmod?command?to?tell?make?to?ignore
          ??#?an?error?in?case?the?module?isn’t?loaded.
          ??-sudo?rmmod?lkm_example
          ??#?Clear?the?kernel?log?without?echo
          ??sudo?dmesg?-C
          ??#?Insert?the?module
          ??sudo?insmod?lkm_example.ko
          ??#?Display?the?kernel?log
          ??dmesg

          現(xiàn)在,當(dāng)您運行 "make test" 時,您將看到設(shè)備主號碼的輸出。在我們的示例中,這是由內(nèi)核自動分配的,但是,你需要此值來創(chuàng)建設(shè)備。

          獲取從 "make test" 獲得的值,并使用它來創(chuàng)建設(shè)備文件,以便我們可以從用戶空間與內(nèi)核模塊進行通信:

          sudo?mknod?/dev/lkm_example?c?MAJOR?0

          在上面的示例中,將MAJOR替換為你運行 "make test" 或 "dmesg" 后得到的值,我得到的MAJOR為236,如上圖,mknod命令中的 "c" 告訴mknod我們需要創(chuàng)建一個字符設(shè)備文件。

          現(xiàn)在我們可以從設(shè)備中獲取內(nèi)容:

          cat?/dev/lkm_example

          或者通過 "dd" 命令:

          dd?if=/dev/lkm_example?of=test?bs=14?count=100

          你也可以通過應(yīng)用程序訪問此設(shè)備,它們不必編譯應(yīng)用程序--甚至Python、Ruby和PHP腳本也可以訪問這些數(shù)據(jù)。

          完成測試后,將其刪除并卸載模塊:

          sudo?rm?/dev/lkm_example
          sudo?rmmod?lkm_example

          6. 結(jié)論

          盡管我提供的示例是簡單內(nèi)核模塊,但你完全可以根據(jù)此結(jié)構(gòu)來構(gòu)造自己的模塊,以完成非常復(fù)雜的任務(wù)。

          請記住,你在內(nèi)核模塊開發(fā)過程中完全靠自己。如果你為客戶提供一個項目的報價,一定要把預(yù)期的調(diào)試時間增加一倍,甚至三倍。內(nèi)核代碼必須盡可能的完美,以確保運行它的系統(tǒng)的完整性和可靠性。

          轉(zhuǎn)自:「https://www.cnblogs.com/sctb/p/13816110.html」

          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产一区二区无码 | 国产女人18水真多18精品 | 欧美久久一级片 | 日韩欧美操逼 | 日韩一级性爱 |