天天看點

Linux核心與驅動學習記錄-最簡單的核心子產品-Hello核心子產品

By: Ailson Jack

Date: 2021.05.09

個人部落格:http://www.only2fire.com/

本文在我部落格的位址是:http://www.only2fire.com/archives/134.html,排版更好,便于學習,也可以去我部落格逛逛,興許有你想要的内容呢。

1.核心子產品的概念

因為Linux 作業系統采用了宏核心結構,宏核心的優點是執行效率非常高,但缺點也是十分明顯的,一旦我們想要修改、增加核心某個功能時(如增加裝置驅動程式)都需要重新編譯一遍核心。為了解決這一缺點,Linux 中引入了核心子產品這一機制。

核心子產品就是實作了某個功能的一段核心代碼,在核心運作過程,可以加載這部分代碼到核心中,進而動态地增加了核心的功能。基于這種特性,我們進行裝置驅動開發時,以核心子產品的形式編寫裝置驅動,隻需要編譯相關的驅動代碼即可,無需對整個核心進行編譯。核心子產品的引入不僅提高了系統的靈活性,對于開發人員來說更是提供了極大的友善。

核心子產品定義:核心子產品全稱 Loadable Kernel Module(LKM),是一種在核心運作時加載一組目标代碼來實作某個特定功能的機制。

核心子產品特點:

  • 子產品本身不被編譯入核心映像,這控制了核心的大小;
  • 子產品一旦被加載,它就和核心中的其它部分完全一樣。

我們編寫的核心子產品,經過編譯,最終形成以.ko為字尾的檔案。ko 檔案在資料組織形式上是 ELF(Excutable And Linking Format) 格式,是一種普通的可重定位目标檔案。

2.編寫Hello核心子產品

對于程式入門學習來說,Hello World程式是經典的例子,這裡我們也實作一個簡單的Hello核心子產品用于了解核心子產品程式設計的基本架構。

hello_module.c檔案的内容如下所示:

/**
 * @file hello_module.c
 * @author Ailson Jack ([email protected])
 * @brief
 * @version 1.0
 * @date 2021-05-08
 *
 * @copyright Copyright (c) 2021
 *
 * @note blog:www.only2fire.com
 *
 */

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

/* 核心子產品加載函數 */
static int __init hello_module_init(void)
{
    printk(KERN_EMERG "[KERN_EMERG] Hello Module init!\r\n");
    printk("[default] Hello Module init!\r\n");

    return 0;
}

/* 核心子產品解除安裝函數 */
static void __exit hello_module_exit(void)
{
    printk(KERN_EMERG "[KERN_EMERG] Hello Module exit!\r\n");
    printk("[default] Hello Module exit!\r\n");
}

module_init(hello_module_init);
module_exit(hello_module_exit);

MODULE_LICENSE("GPL v2"); //表示子產品代碼接受的軟體許可協定
MODULE_AUTHOR("Ailson Jack"); //描述子產品的作者資訊
MODULE_DESCRIPTION("hello module"); //對子產品的簡單介紹
MODULE_ALIAS("test_module"); //給子產品設定一個别名
           

2.1.Hello核心子產品代碼架構分析

Linux 核心子產品的代碼架構通常由下面幾個部分組成:

  • 子產品加載函數 (必須):當通過 insmod 或 modprobe 指令加載核心子產品時,子產品的加載函數就會自動被核心執行,完成本子產品相關的初始化工作。
  • 子產品解除安裝函數 (必須):當執行 rmmod 指令解除安裝子產品時,子產品解除安裝函數就會自動被核心自動執行,完成相關清理工作。
  • 子產品許可證聲明 (必須):許可證聲明描述核心子產品的許可權限,如果子產品不聲明,子產品被加載時,将會有核心被污染的警告。
  • 子產品參數:子產品參數是子產品被加載時,可以傳值給子產品中的參數。
  • 子產品導出符号:子產品可以導出準備好的變量或函數作為符号,以便其他核心子產品調用。
  • 子產品的其他相關資訊:可以聲明子產品作者等資訊。

2.2.核心子產品頭檔案

Hello核心子產品中,使用3個頭檔案,下面說說這3個頭檔案具體提供的資訊:

  • #include <linux/module.h>:包含核心子產品資訊聲明的相關函數;
  • #include <linux/init.h>:包含了 module_init() 和 module_exit() 函數的聲明;
  • #include <linux/kernel.h>: 包含核心提供的各種函數,如 printk。

2.3.核心子產品加載/解除安裝函數

module_init():聲明核心子產品加載函數,加載核心子產品的時候會調用聲明的核心子產品加載函數,子產品加載成功,會在**/sys/module**下建立一個以子產品名為名的目錄。

module_exit():聲明核心子產品解除安裝函數,解除安裝核心子產品的時候會調用聲明的核心子產品解除安裝函數。

__init 用于修飾函數, __initdata 用于修飾變量。帶有 __init 的修飾符,表示将該函數放到可執行檔案的 __init 節區中,該節區的内容隻能用于子產品的初始化階段,初始化階段執行完畢之後,這部分的内容就會被釋放掉,真可謂是“針尖也要削點鐵”。

__exit 用于修飾函數,__exitdata 用于修飾變量。帶有__exit的修飾符,表示将該函數放到可執行檔案的__exit節區,當執行完子產品解除安裝階段之後,就會自動釋放該區域的空間。

注意:hello_module_init()函數的傳回值是int,hello_module_exit()的傳回值是void,并且這兩個函數都使用static進行修飾,表示函數隻能在本檔案進行調用,不能被其他檔案調用。

2.4.核心列印函數-printk

printk函數的列印等級:

#define KERN_EMERG      "<0>" //通常是系統崩潰前的資訊
#define KERN_ALERT      "<1>" //需要立即處理的消息
#define KERN_CRIT       "<2>" //嚴重情況
#define KERN_ERR        "<3>" //錯誤情況
#define KERN_WARNING    "<4>" //有問題的情況
#define KERN_NOTICE     "<5>" //注意資訊
#define KERN_INFO       "<6>" //普通消息
#define KERN_DEBUG      "<7>" //調試資訊
           

printk函數可以指定列印等級,當不指定列印等級的時候,會使用預設的列印等級。

檢視目前系統 printk 列印等級: cat /proc/sys/kernel/printk,從左到右依次對應控制台日志級别、預設消息日志級别、最小的控制台日志級别、預設控制台日志級别。

Linux核心與驅動學習記錄-最簡單的核心子產品-Hello核心子產品

控制台日志級别:優先級高于該值得消息将被列印到到控制台;

預設消息日志級别:将用該優先級來列印沒有指定優先級的消息;

最小的控制台日志級别:控制台日志級别可被設定的最小值(最高優先級);

預設控制台日志級别:控制台日志級别的預設值。

以上的數值設定,數值越小,優先級越高。

假設你想讓hello_module_init()或者hello_module_exit()函數中,沒有指定列印等級的printk的内容輸出到控制台,那麼你可以将"預設消息日志級别"設定為小于4,可以設定為3(隻需要數值小于控制台日志級别即可),執行的指令如下:

然後執行加載或者解除安裝子產品,就可以看到未指定列印等級的消息輸出到控制台了。

檢視核心所有列印資訊: dmesg,注意核心 log 緩沖區大小有限制,緩沖區資料可能被覆寫掉。

3.核心子產品的makefile

對于核心子產品而言,它是屬于核心的一段代碼,隻不過它并不在核心源碼中。為此,我們在編譯時需要到核心源碼目錄下進行編譯。編譯核心子產品使用的 Makefile 檔案,和我們前面編譯 C 代碼使用的 Makefile 大緻相同,這得益于編譯 Linux 核心所采用的 Kbuild 系統,是以在編譯核心子產品時,我們也需要指定環境變量 ARCH 和CROSS_COMPILE 的值。

編譯Hello核心子產品使用的Makefile檔案内容如下:

# 指向編譯出來的 linux 核心具體路徑
KERNEL_DIR = ../kernel/ebf-buster-linux/build_image/build

# 定義變量,并且導出變量給子 Makefile 使用
ARCH = arm
CROSS_COMPILE = arm-linux-gnueabihf-
export ARCH CROSS_COMPILE

# obj-m := <子產品名>.o: 定義要生成的子產品
obj-m := hello_module.o

# 選項 "-C":讓 make 工具跳轉到 linux 核心目錄下讀取頂層 Makefile
# "M=" 表示核心子產品源碼目錄
# $(CURDIR): Makefile 預設變量,值為目前目錄所在路徑
# make modules: 執行 Linux 頂層 Makefile 的僞目标,它實作核心子產品的源碼讀取并編譯為.ko檔案
all:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules

.PHONY:clean copy

clean:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean

copy:
	cp *.ko /home/ailsonjack/share/nfs/temp
           

在核心子產品的目錄中,執行make指令編譯核心子產品,生成hello_module.ko檔案,将hello_module.ko檔案通過nfs或者scp拷貝到開發闆,即可加載該核心子產品。

4.核心子產品常用指令

4.1.lsmod

lsmod 列出目前核心中的所有子產品,格式化顯示在終端,其原理就是将/proc/module 中的資訊調整一下格式輸出。 lsmod 輸出清單有一列 Used by,它表明此子產品正在被其他子產品使用,顯示了子產品之間的依賴關系。

4.2.insmod

如果要将一個子產品加載到核心中, insmod 是最簡單的辦法, insmod+子產品完整路徑就能達到目的,前提是你的子產品不依賴其他子產品,還要注意需要 sudo 權限。如果你不确定是否使用到其他子產品的符号,你也可以嘗試modprobe,後面會有它的詳細用法。

4.3.rmmod

rmod 工具僅僅是将核心中運作的子產品删除,隻需要傳給它路徑就能實作。

rmmod 不會解除安裝一個子產品所依賴的子產品,需要依次解除安裝,當然用 modprobe -r 可以一鍵解除安裝。

4.4.modprobe

modprobe 和 insmod 具備同樣的功能,同樣可以将子產品加載到核心中,除此以外 modprobe 還能檢查子產品之間的依賴關系,并且按照順序加載這些依賴,可以了解為按照順序多次執行 insmod。

4.5.depmod

modprobe 是怎麼知道一個給定子產品所依賴的其他的子產品呢?在這個過程中, depend 起到了決定性作用,當執行 modprobe 時,它會在子產品的安裝目錄下搜尋 module.dep 檔案,這是 depmod 建立的子產品依賴關系的檔案。

4.6.modinfo

modinfo 用來顯示核心子產品一些資訊。比如:modinfo hello_module.ko

5.系統自動加載核心子產品

我們自己編寫了一個子產品,或者說怎樣讓它在闆子開機自動加載呢?這裡就需要用到上述的 depmod 和 modprobe 工具了。

首先需要将我們想要自動加載的子產品統一放到”/lib/modules/核心版本”目錄下,核心版本使用 'uname -r’查詢;其次使用 depmod 建立子產品之間的依賴關系,指令’ depmod -a’;這個時候我們就可以在 modules.dep 中看到子產品依賴關系。

最後在/etc/modules 加上我們自己的子產品,注意在該配置檔案中,子產品不寫成.ko 形式代表該子產品與核心緊耦合,有些是系統必須要跟核心緊耦合,比如 mm 子系統,一般寫成.ko 形式比較好,如果出現錯誤不會導緻核心出現 panic 錯誤,如果內建到核心,出錯了就會出現panic。

歡迎關注部落客的公衆号呀:

Linux核心與驅動學習記錄-最簡單的核心子產品-Hello核心子產品

如果文中有什麼問題歡迎指正,畢竟部落客的水準有限。

如果這篇文章對你有幫助,記得點贊和關注部落客就行了^_^。

排版更好的内容見我部落格的位址:http://www.only2fire.com/archives/134.html

注:轉載請注明出處,謝謝!^_^

繼續閱讀