'走進linux 驅動開發 之 內核模塊'

Linux 操作系統 ARM GCC 中央處理器 硬件 GNU X86 電腦 算法 通信 Linux開發者小雪兒 2019-09-15
"

一、Linux內核簡介

1.宏內核與微內核

內核分為四大類:單內核(宏內核);微內核;混合內核;外內核。

  • 宏內核(Monolithickernel)是將內核從整體上作為一個大過程來實現,所有的內核服務都在一個地址空間運行,相互之間直接調用函數,簡單高效。
  • Linux雖是宏內核,但已吸收了微內核的部分精華。Linux是模塊化的、多線程的、內核本身可調度的系統,既吸收了微內核的精華,又保留了宏內核的優點,無需消息傳遞,避免性能損失。
  • 微內核(Microkernel)功能被劃分成獨立的過程,過程間通過IPC進行通信,模塊化程度高,一個服務失效不會影響另外一個服務。


"

一、Linux內核簡介

1.宏內核與微內核

內核分為四大類:單內核(宏內核);微內核;混合內核;外內核。

  • 宏內核(Monolithickernel)是將內核從整體上作為一個大過程來實現,所有的內核服務都在一個地址空間運行,相互之間直接調用函數,簡單高效。
  • Linux雖是宏內核,但已吸收了微內核的部分精華。Linux是模塊化的、多線程的、內核本身可調度的系統,既吸收了微內核的精華,又保留了宏內核的優點,無需消息傳遞,避免性能損失。
  • 微內核(Microkernel)功能被劃分成獨立的過程,過程間通過IPC進行通信,模塊化程度高,一個服務失效不會影響另外一個服務。


走進linux 驅動開發 之 內核模塊


image

2.Linux體系架構

從兩個層次上來考慮操作系統

  • 用戶空間:包含了用戶的應用程序和C庫
  • GNU C Library (glibc)提供了連接內核的系統調用接口,還提供了在用戶空間應用程序和內核之間進行轉換的機制。
  • 內核空間:包含了系統調用,內核,以及與平臺架構相關的代碼


"

一、Linux內核簡介

1.宏內核與微內核

內核分為四大類:單內核(宏內核);微內核;混合內核;外內核。

  • 宏內核(Monolithickernel)是將內核從整體上作為一個大過程來實現,所有的內核服務都在一個地址空間運行,相互之間直接調用函數,簡單高效。
  • Linux雖是宏內核,但已吸收了微內核的部分精華。Linux是模塊化的、多線程的、內核本身可調度的系統,既吸收了微內核的精華,又保留了宏內核的優點,無需消息傳遞,避免性能損失。
  • 微內核(Microkernel)功能被劃分成獨立的過程,過程間通過IPC進行通信,模塊化程度高,一個服務失效不會影響另外一個服務。


走進linux 驅動開發 之 內核模塊


image

2.Linux體系架構

從兩個層次上來考慮操作系統

  • 用戶空間:包含了用戶的應用程序和C庫
  • GNU C Library (glibc)提供了連接內核的系統調用接口,還提供了在用戶空間應用程序和內核之間進行轉換的機制。
  • 內核空間:包含了系統調用,內核,以及與平臺架構相關的代碼


走進linux 驅動開發 之 內核模塊


image

劃分原因

  • 現代CPU通常都實現了不同的工作模式
  • 以ARM為例:ARM實現了7種工作模式,不同模式下CPU可以執行的指令或者訪問的寄存器不同:
  • (1)用戶模式 usr
  • (2)系統模式 sys
  • (3)管理模式 svc
  • (4)快速中斷 fiq
  • (5)外部中斷 irq
  • (6)數據訪問終止 abt
  • (7)未定義指令異常;
  • 以X86為例:X86實現了4個不同級別的權限,Ring0—Ring3 ;Ring0下可以執行特權指令,可以訪問IO設備;Ring3則有很多的限制。
  • 為了保護內核的安全,把系統分成了2部分:用戶空間和內核空間是程序執行的兩種不同狀態,我們可以通過“系統調用”和“硬件中斷“來完成用戶空間到內核空間的轉移;

3.Linux的內核結構

Linux內核是整體式結構(宏內核),各個子系統聯繫緊密,作為一個大程序在內核空間運行。

系統調用接口(system call interface,SCI)提供了某些機制執行從用戶空間到內核的函數調用。


"

一、Linux內核簡介

1.宏內核與微內核

內核分為四大類:單內核(宏內核);微內核;混合內核;外內核。

  • 宏內核(Monolithickernel)是將內核從整體上作為一個大過程來實現,所有的內核服務都在一個地址空間運行,相互之間直接調用函數,簡單高效。
  • Linux雖是宏內核,但已吸收了微內核的部分精華。Linux是模塊化的、多線程的、內核本身可調度的系統,既吸收了微內核的精華,又保留了宏內核的優點,無需消息傳遞,避免性能損失。
  • 微內核(Microkernel)功能被劃分成獨立的過程,過程間通過IPC進行通信,模塊化程度高,一個服務失效不會影響另外一個服務。


走進linux 驅動開發 之 內核模塊


image

2.Linux體系架構

從兩個層次上來考慮操作系統

  • 用戶空間:包含了用戶的應用程序和C庫
  • GNU C Library (glibc)提供了連接內核的系統調用接口,還提供了在用戶空間應用程序和內核之間進行轉換的機制。
  • 內核空間:包含了系統調用,內核,以及與平臺架構相關的代碼


走進linux 驅動開發 之 內核模塊


image

劃分原因

  • 現代CPU通常都實現了不同的工作模式
  • 以ARM為例:ARM實現了7種工作模式,不同模式下CPU可以執行的指令或者訪問的寄存器不同:
  • (1)用戶模式 usr
  • (2)系統模式 sys
  • (3)管理模式 svc
  • (4)快速中斷 fiq
  • (5)外部中斷 irq
  • (6)數據訪問終止 abt
  • (7)未定義指令異常;
  • 以X86為例:X86實現了4個不同級別的權限,Ring0—Ring3 ;Ring0下可以執行特權指令,可以訪問IO設備;Ring3則有很多的限制。
  • 為了保護內核的安全,把系統分成了2部分:用戶空間和內核空間是程序執行的兩種不同狀態,我們可以通過“系統調用”和“硬件中斷“來完成用戶空間到內核空間的轉移;

3.Linux的內核結構

Linux內核是整體式結構(宏內核),各個子系統聯繫緊密,作為一個大程序在內核空間運行。

系統調用接口(system call interface,SCI)提供了某些機制執行從用戶空間到內核的函數調用。


走進linux 驅動開發 之 內核模塊


image

1)Linux內核組成(子系統)

  • 進程調度(SCHED):控制多個進程對CPU的訪問。當需要選擇下一個進程運行時,由調度程序選擇最值得運行的進程。可運行進程實際上是僅等待CPU資源的進程,如果某個進程在等待其它資源,則該進程是不可運行進程。Linux使用了比較簡單的基於優先級的進程調度算法選擇新的進程。
  • 內存管理(memory management,MM):允許多個進程安全的共享主內存區域。Linux 的內存管理支持虛擬內存,即在計算機中運行的程序,其代碼,數據,堆棧的總量可以超過實際內存的大小,操作系統只是把當前使用的程序塊保留在內存中,其餘的程序塊則保留在磁盤中。必要時,操作系統負責在磁盤和內存間交換程序塊。內存管理從邏輯上分為硬件無關部分和硬件有關部分。硬件無關部分提供了進程的映射和邏輯內存的對換;硬件相關的部分為內存管理硬件提供了虛擬接口。
  • 一般而言,Linux的每個進程享有4GB的內存空間,03GB屬於用戶空間,34GB屬於內核空間。
  • 虛擬文件系統(Virtual File System,VFS):隱藏了各種硬件的具體細節,為所有的設備提供了統一的接口,VFS提供了多達數十種不同的文件系統。虛擬文件系統可以分為邏輯文件系統和設備驅動程序。邏輯文件系統指Linux所支持的文件系統,如ext2,fat等,設備驅動程序指為每一種硬件控制器所編寫的設備驅動程序模塊。

"

一、Linux內核簡介

1.宏內核與微內核

內核分為四大類:單內核(宏內核);微內核;混合內核;外內核。

  • 宏內核(Monolithickernel)是將內核從整體上作為一個大過程來實現,所有的內核服務都在一個地址空間運行,相互之間直接調用函數,簡單高效。
  • Linux雖是宏內核,但已吸收了微內核的部分精華。Linux是模塊化的、多線程的、內核本身可調度的系統,既吸收了微內核的精華,又保留了宏內核的優點,無需消息傳遞,避免性能損失。
  • 微內核(Microkernel)功能被劃分成獨立的過程,過程間通過IPC進行通信,模塊化程度高,一個服務失效不會影響另外一個服務。


走進linux 驅動開發 之 內核模塊


image

2.Linux體系架構

從兩個層次上來考慮操作系統

  • 用戶空間:包含了用戶的應用程序和C庫
  • GNU C Library (glibc)提供了連接內核的系統調用接口,還提供了在用戶空間應用程序和內核之間進行轉換的機制。
  • 內核空間:包含了系統調用,內核,以及與平臺架構相關的代碼


走進linux 驅動開發 之 內核模塊


image

劃分原因

  • 現代CPU通常都實現了不同的工作模式
  • 以ARM為例:ARM實現了7種工作模式,不同模式下CPU可以執行的指令或者訪問的寄存器不同:
  • (1)用戶模式 usr
  • (2)系統模式 sys
  • (3)管理模式 svc
  • (4)快速中斷 fiq
  • (5)外部中斷 irq
  • (6)數據訪問終止 abt
  • (7)未定義指令異常;
  • 以X86為例:X86實現了4個不同級別的權限,Ring0—Ring3 ;Ring0下可以執行特權指令,可以訪問IO設備;Ring3則有很多的限制。
  • 為了保護內核的安全,把系統分成了2部分:用戶空間和內核空間是程序執行的兩種不同狀態,我們可以通過“系統調用”和“硬件中斷“來完成用戶空間到內核空間的轉移;

3.Linux的內核結構

Linux內核是整體式結構(宏內核),各個子系統聯繫緊密,作為一個大程序在內核空間運行。

系統調用接口(system call interface,SCI)提供了某些機制執行從用戶空間到內核的函數調用。


走進linux 驅動開發 之 內核模塊


image

1)Linux內核組成(子系統)

  • 進程調度(SCHED):控制多個進程對CPU的訪問。當需要選擇下一個進程運行時,由調度程序選擇最值得運行的進程。可運行進程實際上是僅等待CPU資源的進程,如果某個進程在等待其它資源,則該進程是不可運行進程。Linux使用了比較簡單的基於優先級的進程調度算法選擇新的進程。
  • 內存管理(memory management,MM):允許多個進程安全的共享主內存區域。Linux 的內存管理支持虛擬內存,即在計算機中運行的程序,其代碼,數據,堆棧的總量可以超過實際內存的大小,操作系統只是把當前使用的程序塊保留在內存中,其餘的程序塊則保留在磁盤中。必要時,操作系統負責在磁盤和內存間交換程序塊。內存管理從邏輯上分為硬件無關部分和硬件有關部分。硬件無關部分提供了進程的映射和邏輯內存的對換;硬件相關的部分為內存管理硬件提供了虛擬接口。
  • 一般而言,Linux的每個進程享有4GB的內存空間,03GB屬於用戶空間,34GB屬於內核空間。
  • 虛擬文件系統(Virtual File System,VFS):隱藏了各種硬件的具體細節,為所有的設備提供了統一的接口,VFS提供了多達數十種不同的文件系統。虛擬文件系統可以分為邏輯文件系統和設備驅動程序。邏輯文件系統指Linux所支持的文件系統,如ext2,fat等,設備驅動程序指為每一種硬件控制器所編寫的設備驅動程序模塊。

走進linux 驅動開發 之 內核模塊


  • image
  • 網絡接口(NET):提供了對各種網絡標準的存取和各種網絡硬件的支持。網絡接口可分為網絡協議和網絡驅動程序。網絡協議部分負責實現每一種可能的網絡傳輸協議。網絡設備驅動程序負責與硬件設備通訊,每一種可能的硬件設備都有相應的設備驅動程序。

"

一、Linux內核簡介

1.宏內核與微內核

內核分為四大類:單內核(宏內核);微內核;混合內核;外內核。

  • 宏內核(Monolithickernel)是將內核從整體上作為一個大過程來實現,所有的內核服務都在一個地址空間運行,相互之間直接調用函數,簡單高效。
  • Linux雖是宏內核,但已吸收了微內核的部分精華。Linux是模塊化的、多線程的、內核本身可調度的系統,既吸收了微內核的精華,又保留了宏內核的優點,無需消息傳遞,避免性能損失。
  • 微內核(Microkernel)功能被劃分成獨立的過程,過程間通過IPC進行通信,模塊化程度高,一個服務失效不會影響另外一個服務。


走進linux 驅動開發 之 內核模塊


image

2.Linux體系架構

從兩個層次上來考慮操作系統

  • 用戶空間:包含了用戶的應用程序和C庫
  • GNU C Library (glibc)提供了連接內核的系統調用接口,還提供了在用戶空間應用程序和內核之間進行轉換的機制。
  • 內核空間:包含了系統調用,內核,以及與平臺架構相關的代碼


走進linux 驅動開發 之 內核模塊


image

劃分原因

  • 現代CPU通常都實現了不同的工作模式
  • 以ARM為例:ARM實現了7種工作模式,不同模式下CPU可以執行的指令或者訪問的寄存器不同:
  • (1)用戶模式 usr
  • (2)系統模式 sys
  • (3)管理模式 svc
  • (4)快速中斷 fiq
  • (5)外部中斷 irq
  • (6)數據訪問終止 abt
  • (7)未定義指令異常;
  • 以X86為例:X86實現了4個不同級別的權限,Ring0—Ring3 ;Ring0下可以執行特權指令,可以訪問IO設備;Ring3則有很多的限制。
  • 為了保護內核的安全,把系統分成了2部分:用戶空間和內核空間是程序執行的兩種不同狀態,我們可以通過“系統調用”和“硬件中斷“來完成用戶空間到內核空間的轉移;

3.Linux的內核結構

Linux內核是整體式結構(宏內核),各個子系統聯繫緊密,作為一個大程序在內核空間運行。

系統調用接口(system call interface,SCI)提供了某些機制執行從用戶空間到內核的函數調用。


走進linux 驅動開發 之 內核模塊


image

1)Linux內核組成(子系統)

  • 進程調度(SCHED):控制多個進程對CPU的訪問。當需要選擇下一個進程運行時,由調度程序選擇最值得運行的進程。可運行進程實際上是僅等待CPU資源的進程,如果某個進程在等待其它資源,則該進程是不可運行進程。Linux使用了比較簡單的基於優先級的進程調度算法選擇新的進程。
  • 內存管理(memory management,MM):允許多個進程安全的共享主內存區域。Linux 的內存管理支持虛擬內存,即在計算機中運行的程序,其代碼,數據,堆棧的總量可以超過實際內存的大小,操作系統只是把當前使用的程序塊保留在內存中,其餘的程序塊則保留在磁盤中。必要時,操作系統負責在磁盤和內存間交換程序塊。內存管理從邏輯上分為硬件無關部分和硬件有關部分。硬件無關部分提供了進程的映射和邏輯內存的對換;硬件相關的部分為內存管理硬件提供了虛擬接口。
  • 一般而言,Linux的每個進程享有4GB的內存空間,03GB屬於用戶空間,34GB屬於內核空間。
  • 虛擬文件系統(Virtual File System,VFS):隱藏了各種硬件的具體細節,為所有的設備提供了統一的接口,VFS提供了多達數十種不同的文件系統。虛擬文件系統可以分為邏輯文件系統和設備驅動程序。邏輯文件系統指Linux所支持的文件系統,如ext2,fat等,設備驅動程序指為每一種硬件控制器所編寫的設備驅動程序模塊。

走進linux 驅動開發 之 內核模塊


  • image
  • 網絡接口(NET):提供了對各種網絡標準的存取和各種網絡硬件的支持。網絡接口可分為網絡協議和網絡驅動程序。網絡協議部分負責實現每一種可能的網絡傳輸協議。網絡設備驅動程序負責與硬件設備通訊,每一種可能的硬件設備都有相應的設備驅動程序。

走進linux 驅動開發 之 內核模塊


  • image
  • 進程間通訊(inter-process communication,IPC): 支持進程間各種通信機制。
  • 共享內存
  • 管道
  • 信號量
  • 消息隊列
  • 套接字

4.內核模塊

Linux內核是模塊化組成的,它允許內核在運行時動態地向其中插入或刪除代碼。

二、內核模塊結構

1.頭文件

內核模塊頭文件<linux/module.h>和<linux/init.h>是必不可少的 ,不同模塊根據功能的差異,所需要的頭文件也不相同 。

#include <linux/module.h> #include <linux/init.h> 2.模塊初始化

模塊的初始化負責註冊模塊本身 ,只有已註冊模塊的各種方法才能夠被應用程序使用併發揮各方法的實際功能。

模塊並不是內核內部的代碼,而是獨立於內核之外,通過初始化,能夠讓內核之外的代碼來替內核完成本應該由內核完成的功能,模塊初始化的功能相當於模塊與內核之間銜接的橋樑,告知內核已經準備好模塊了。

內核模塊初始化函數

//模塊初始化函數一般都需聲明為 static //__init 表示初始化函數僅僅在初始化期間使用,一旦初始化完畢,將釋放初始化函數所佔用的內存 static int __init module_init_func(void) { 初始化代碼 } module_init(module_init_func); //module_init宏定義會在模塊的目標代碼中增加一個特殊的代碼段,用於說明該初始化函數所在的位置。 當使用 insmod 將模塊加載進內核的時候,初始化函數的代碼將會被執行。

3.模塊退出

模塊的退出相當於告知內核“我要離開了,將不再為您服務了”。

內核模塊退出函數

//模塊退出函數沒有返回值;
//__exit 標記這段代碼僅用於模塊卸載;
static void __exit module_exit_func(void)
{
//模塊退出代碼
}
module_exit(module_exit_func);
//沒有 module_exit 定義的模塊無法被卸載

當使用 rmmod 卸載模塊時,退出函數的代碼將被執行。

注意:如果模塊被編譯進內核,而不是動態加載,則__init的使用會在模塊初始化完成後丟棄該函數並回收所佔內存, _exit宏將忽略“清理收尾”的函數。

4.模塊許可證聲明

Linux 內核是開源的,遵守 GPL 協議,所以要求加載進內核的模塊也最好遵循相關協議。

為模塊指定遵守的協議用 MODULE_LINCENSE 來聲明 :

  • MODULE_LICENSE("GPL"); 內核能夠識別的協議有
  • “GPL”
  • “GPL v2”
  • “GPL and additional rights(GPL 及附加權利)”
  • “Dual BSD/GPL(BSD/GPL 雙重許可)”
  • “Dual MPL/GPL(MPL/GPL 雙重許可)”
  • “Proprietary(私有)”

5.模塊導出符號 【可選】

使用模塊導出符號,方便其它模塊依賴於該模塊,並使用模塊中的變量和函數等。

  • 在Linux2.6的內核中,/proc/kallsyms文件對應著符號表,它記錄了符號和符號對應的內存地址。
  • $ cat /proc/kallsyms ... ffffff80084039b8 t shash_digest_unaligned ffffff8008403a30 T crypto_shash_digest ffffff8008403ac0 t shash_async_final ffffff8008403af0 T shash_ahash_update ffffff8008403b50 t shash_async_update ffffff8008403b80 t crypto_exit_shash_ops_async ffffff8008403bb0 t crypto_shash_report ffffff8008403c18 t crypto_shash_show ffffff8008403c78 T crypto_alloc_shash ffffff8008403cc8 T crypto_register_shash ffffff8008403d00 T crypto_unregister_shash ffffff8008403d30 T crypto_register_shashes ffffff8008403df8 T crypto_unregister_shashes ffffff8008403e90 T shash_register_instance ffffff8008403ed0 T shash_free_instance ffffff8008403f08 T crypto_init_shash_spawn ffffff8008403f58 T shash_attr_alg ffffff8008403fb0 T shash_ahash_finup ffffff8008404068 t shash_async_finup ffffff80084040b0 T shash_ahash_digest ffffff80084041e0 t shash_async_digest ... 使用一下宏定義導出符號
EXPORT_SYMBOL(module_symbol);
//或
EXPORT_GPL_SYMBOL(module_symbol);

6.模塊描述 [可選]

模塊編寫者還可以為所編寫的模塊增加一些其它描述信息,如模塊作者、模塊本身的描述或者模塊版本等

MODULE_AUTHOR("Abing <[email protected]>");
MODULE_DESCRIPTION("ZHIYUAN ecm1352 beep Driver");
MODULE_VERSION("V1.00");

模塊描述以及許可證聲明一般放在文件末尾。

三、向Linux內核添加新內核模塊

1.添加模塊驅動文件

在linux/drivers/下新建目錄hello,並且在hello/目錄下新建hello.c、Makefile、Kconfig三個文件。

1)內核模塊程序hello.c

/* hello world module */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello, I'm ready!\\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "I'll be leaving, bye!\\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("michael");
MODULE_DESCRIPTION("hello world module");
  • 內核通過 printk() 輸出的信息具有日誌級別,日誌級別是通過在 printk() 輸出的字符串前加一個帶尖括號的整數來控制的,如 printk(“<6>Hello, world!/n”);。內核中共提供了八種不同的日誌級別

// 在 linux/kernel.h 中有相應的宏對應 #define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "<3>" /* error conditions */ #define KERN_WARNING "<4>" /* warning conditions */ #define KERN_NOTICE "<5>" /* normal but significant */ #define KERN_INFO "<6>" /* informational */ #define KERN_DEBUG "<7>" /* debug-level messages */ 2)Kconfig

menu "HELLO TEST Driver "
comment "HELLO TEST Driver Config"

config HELLO
tristate "hello module test"
default m
help
This is the hello test driver.

endmenu
  • 在menuconfig的“driver”菜單下添加“HELLO TEST Driver”子菜單,並加入“HELLO”配置選項,選項默認為m。
  • 保存menuconfig後,會在kernel根目錄下的.config文件中生成“CONFIG_HELLO=m”,在編譯的時候會添加到臨時環境變量中。

3)Makefile

obj-$(CONFIG_HELLO) += hello.o

可用於動態模塊外部編譯的寫法

  • 編譯模塊的內核配置必須與所運行內核的編譯配置一樣 。
  • ifneq ($(KERNELRELEASE),) obj-m += hello.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build # 定義內核路徑 PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules # 表示在當前目錄下編譯 clean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions endif KERNELRELEASE是在內核源碼的頂層Makefile中定義的一個變量,在第一次讀取執行此Makefile時,KERNELRELEASE沒有被定義,所以make將讀取執行else之後的內容。
  • 當從內核源碼目錄返回時,KERNELRELEASE已被定義,kbuild也被啟動去解析kbuild語法的語句,make將繼續讀取else之前的內容,生成的目標模塊名。

2.修改上一級目錄的Kconfig和Makefile

進入linux/drivers/

  • 編輯Makefile,在後面添加一行:
  • obj-$(CONFIG_HELLO) += hello/ 編輯Kconfig,在後面添加一行:
source "drivers/hello/Kconfig"
  • 注:某些內核版本需要同時在arch/arm/Kconfig中添加:source "drivers/hello/Kconfig"

3.make menuconfig配置和編譯

  • 執行:make menuconfig ARCH=arm進入配置菜單
  • 選擇並進入:Device Drivers選項


"

一、Linux內核簡介

1.宏內核與微內核

內核分為四大類:單內核(宏內核);微內核;混合內核;外內核。

  • 宏內核(Monolithickernel)是將內核從整體上作為一個大過程來實現,所有的內核服務都在一個地址空間運行,相互之間直接調用函數,簡單高效。
  • Linux雖是宏內核,但已吸收了微內核的部分精華。Linux是模塊化的、多線程的、內核本身可調度的系統,既吸收了微內核的精華,又保留了宏內核的優點,無需消息傳遞,避免性能損失。
  • 微內核(Microkernel)功能被劃分成獨立的過程,過程間通過IPC進行通信,模塊化程度高,一個服務失效不會影響另外一個服務。


走進linux 驅動開發 之 內核模塊


image

2.Linux體系架構

從兩個層次上來考慮操作系統

  • 用戶空間:包含了用戶的應用程序和C庫
  • GNU C Library (glibc)提供了連接內核的系統調用接口,還提供了在用戶空間應用程序和內核之間進行轉換的機制。
  • 內核空間:包含了系統調用,內核,以及與平臺架構相關的代碼


走進linux 驅動開發 之 內核模塊


image

劃分原因

  • 現代CPU通常都實現了不同的工作模式
  • 以ARM為例:ARM實現了7種工作模式,不同模式下CPU可以執行的指令或者訪問的寄存器不同:
  • (1)用戶模式 usr
  • (2)系統模式 sys
  • (3)管理模式 svc
  • (4)快速中斷 fiq
  • (5)外部中斷 irq
  • (6)數據訪問終止 abt
  • (7)未定義指令異常;
  • 以X86為例:X86實現了4個不同級別的權限,Ring0—Ring3 ;Ring0下可以執行特權指令,可以訪問IO設備;Ring3則有很多的限制。
  • 為了保護內核的安全,把系統分成了2部分:用戶空間和內核空間是程序執行的兩種不同狀態,我們可以通過“系統調用”和“硬件中斷“來完成用戶空間到內核空間的轉移;

3.Linux的內核結構

Linux內核是整體式結構(宏內核),各個子系統聯繫緊密,作為一個大程序在內核空間運行。

系統調用接口(system call interface,SCI)提供了某些機制執行從用戶空間到內核的函數調用。


走進linux 驅動開發 之 內核模塊


image

1)Linux內核組成(子系統)

  • 進程調度(SCHED):控制多個進程對CPU的訪問。當需要選擇下一個進程運行時,由調度程序選擇最值得運行的進程。可運行進程實際上是僅等待CPU資源的進程,如果某個進程在等待其它資源,則該進程是不可運行進程。Linux使用了比較簡單的基於優先級的進程調度算法選擇新的進程。
  • 內存管理(memory management,MM):允許多個進程安全的共享主內存區域。Linux 的內存管理支持虛擬內存,即在計算機中運行的程序,其代碼,數據,堆棧的總量可以超過實際內存的大小,操作系統只是把當前使用的程序塊保留在內存中,其餘的程序塊則保留在磁盤中。必要時,操作系統負責在磁盤和內存間交換程序塊。內存管理從邏輯上分為硬件無關部分和硬件有關部分。硬件無關部分提供了進程的映射和邏輯內存的對換;硬件相關的部分為內存管理硬件提供了虛擬接口。
  • 一般而言,Linux的每個進程享有4GB的內存空間,03GB屬於用戶空間,34GB屬於內核空間。
  • 虛擬文件系統(Virtual File System,VFS):隱藏了各種硬件的具體細節,為所有的設備提供了統一的接口,VFS提供了多達數十種不同的文件系統。虛擬文件系統可以分為邏輯文件系統和設備驅動程序。邏輯文件系統指Linux所支持的文件系統,如ext2,fat等,設備驅動程序指為每一種硬件控制器所編寫的設備驅動程序模塊。

走進linux 驅動開發 之 內核模塊


  • image
  • 網絡接口(NET):提供了對各種網絡標準的存取和各種網絡硬件的支持。網絡接口可分為網絡協議和網絡驅動程序。網絡協議部分負責實現每一種可能的網絡傳輸協議。網絡設備驅動程序負責與硬件設備通訊,每一種可能的硬件設備都有相應的設備驅動程序。

走進linux 驅動開發 之 內核模塊


  • image
  • 進程間通訊(inter-process communication,IPC): 支持進程間各種通信機制。
  • 共享內存
  • 管道
  • 信號量
  • 消息隊列
  • 套接字

4.內核模塊

Linux內核是模塊化組成的,它允許內核在運行時動態地向其中插入或刪除代碼。

二、內核模塊結構

1.頭文件

內核模塊頭文件<linux/module.h>和<linux/init.h>是必不可少的 ,不同模塊根據功能的差異,所需要的頭文件也不相同 。

#include <linux/module.h> #include <linux/init.h> 2.模塊初始化

模塊的初始化負責註冊模塊本身 ,只有已註冊模塊的各種方法才能夠被應用程序使用併發揮各方法的實際功能。

模塊並不是內核內部的代碼,而是獨立於內核之外,通過初始化,能夠讓內核之外的代碼來替內核完成本應該由內核完成的功能,模塊初始化的功能相當於模塊與內核之間銜接的橋樑,告知內核已經準備好模塊了。

內核模塊初始化函數

//模塊初始化函數一般都需聲明為 static //__init 表示初始化函數僅僅在初始化期間使用,一旦初始化完畢,將釋放初始化函數所佔用的內存 static int __init module_init_func(void) { 初始化代碼 } module_init(module_init_func); //module_init宏定義會在模塊的目標代碼中增加一個特殊的代碼段,用於說明該初始化函數所在的位置。 當使用 insmod 將模塊加載進內核的時候,初始化函數的代碼將會被執行。

3.模塊退出

模塊的退出相當於告知內核“我要離開了,將不再為您服務了”。

內核模塊退出函數

//模塊退出函數沒有返回值;
//__exit 標記這段代碼僅用於模塊卸載;
static void __exit module_exit_func(void)
{
//模塊退出代碼
}
module_exit(module_exit_func);
//沒有 module_exit 定義的模塊無法被卸載

當使用 rmmod 卸載模塊時,退出函數的代碼將被執行。

注意:如果模塊被編譯進內核,而不是動態加載,則__init的使用會在模塊初始化完成後丟棄該函數並回收所佔內存, _exit宏將忽略“清理收尾”的函數。

4.模塊許可證聲明

Linux 內核是開源的,遵守 GPL 協議,所以要求加載進內核的模塊也最好遵循相關協議。

為模塊指定遵守的協議用 MODULE_LINCENSE 來聲明 :

  • MODULE_LICENSE("GPL"); 內核能夠識別的協議有
  • “GPL”
  • “GPL v2”
  • “GPL and additional rights(GPL 及附加權利)”
  • “Dual BSD/GPL(BSD/GPL 雙重許可)”
  • “Dual MPL/GPL(MPL/GPL 雙重許可)”
  • “Proprietary(私有)”

5.模塊導出符號 【可選】

使用模塊導出符號,方便其它模塊依賴於該模塊,並使用模塊中的變量和函數等。

  • 在Linux2.6的內核中,/proc/kallsyms文件對應著符號表,它記錄了符號和符號對應的內存地址。
  • $ cat /proc/kallsyms ... ffffff80084039b8 t shash_digest_unaligned ffffff8008403a30 T crypto_shash_digest ffffff8008403ac0 t shash_async_final ffffff8008403af0 T shash_ahash_update ffffff8008403b50 t shash_async_update ffffff8008403b80 t crypto_exit_shash_ops_async ffffff8008403bb0 t crypto_shash_report ffffff8008403c18 t crypto_shash_show ffffff8008403c78 T crypto_alloc_shash ffffff8008403cc8 T crypto_register_shash ffffff8008403d00 T crypto_unregister_shash ffffff8008403d30 T crypto_register_shashes ffffff8008403df8 T crypto_unregister_shashes ffffff8008403e90 T shash_register_instance ffffff8008403ed0 T shash_free_instance ffffff8008403f08 T crypto_init_shash_spawn ffffff8008403f58 T shash_attr_alg ffffff8008403fb0 T shash_ahash_finup ffffff8008404068 t shash_async_finup ffffff80084040b0 T shash_ahash_digest ffffff80084041e0 t shash_async_digest ... 使用一下宏定義導出符號
EXPORT_SYMBOL(module_symbol);
//或
EXPORT_GPL_SYMBOL(module_symbol);

6.模塊描述 [可選]

模塊編寫者還可以為所編寫的模塊增加一些其它描述信息,如模塊作者、模塊本身的描述或者模塊版本等

MODULE_AUTHOR("Abing <[email protected]>");
MODULE_DESCRIPTION("ZHIYUAN ecm1352 beep Driver");
MODULE_VERSION("V1.00");

模塊描述以及許可證聲明一般放在文件末尾。

三、向Linux內核添加新內核模塊

1.添加模塊驅動文件

在linux/drivers/下新建目錄hello,並且在hello/目錄下新建hello.c、Makefile、Kconfig三個文件。

1)內核模塊程序hello.c

/* hello world module */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello, I'm ready!\\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "I'll be leaving, bye!\\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("michael");
MODULE_DESCRIPTION("hello world module");
  • 內核通過 printk() 輸出的信息具有日誌級別,日誌級別是通過在 printk() 輸出的字符串前加一個帶尖括號的整數來控制的,如 printk(“<6>Hello, world!/n”);。內核中共提供了八種不同的日誌級別

// 在 linux/kernel.h 中有相應的宏對應 #define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "<3>" /* error conditions */ #define KERN_WARNING "<4>" /* warning conditions */ #define KERN_NOTICE "<5>" /* normal but significant */ #define KERN_INFO "<6>" /* informational */ #define KERN_DEBUG "<7>" /* debug-level messages */ 2)Kconfig

menu "HELLO TEST Driver "
comment "HELLO TEST Driver Config"

config HELLO
tristate "hello module test"
default m
help
This is the hello test driver.

endmenu
  • 在menuconfig的“driver”菜單下添加“HELLO TEST Driver”子菜單,並加入“HELLO”配置選項,選項默認為m。
  • 保存menuconfig後,會在kernel根目錄下的.config文件中生成“CONFIG_HELLO=m”,在編譯的時候會添加到臨時環境變量中。

3)Makefile

obj-$(CONFIG_HELLO) += hello.o

可用於動態模塊外部編譯的寫法

  • 編譯模塊的內核配置必須與所運行內核的編譯配置一樣 。
  • ifneq ($(KERNELRELEASE),) obj-m += hello.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build # 定義內核路徑 PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules # 表示在當前目錄下編譯 clean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions endif KERNELRELEASE是在內核源碼的頂層Makefile中定義的一個變量,在第一次讀取執行此Makefile時,KERNELRELEASE沒有被定義,所以make將讀取執行else之後的內容。
  • 當從內核源碼目錄返回時,KERNELRELEASE已被定義,kbuild也被啟動去解析kbuild語法的語句,make將繼續讀取else之前的內容,生成的目標模塊名。

2.修改上一級目錄的Kconfig和Makefile

進入linux/drivers/

  • 編輯Makefile,在後面添加一行:
  • obj-$(CONFIG_HELLO) += hello/ 編輯Kconfig,在後面添加一行:
source "drivers/hello/Kconfig"
  • 注:某些內核版本需要同時在arch/arm/Kconfig中添加:source "drivers/hello/Kconfig"

3.make menuconfig配置和編譯

  • 執行:make menuconfig ARCH=arm進入配置菜單
  • 選擇並進入:Device Drivers選項


走進linux 驅動開發 之 內核模塊


image

  • 進入 HELLO TEST Driver選項

"

一、Linux內核簡介

1.宏內核與微內核

內核分為四大類:單內核(宏內核);微內核;混合內核;外內核。

  • 宏內核(Monolithickernel)是將內核從整體上作為一個大過程來實現,所有的內核服務都在一個地址空間運行,相互之間直接調用函數,簡單高效。
  • Linux雖是宏內核,但已吸收了微內核的部分精華。Linux是模塊化的、多線程的、內核本身可調度的系統,既吸收了微內核的精華,又保留了宏內核的優點,無需消息傳遞,避免性能損失。
  • 微內核(Microkernel)功能被劃分成獨立的過程,過程間通過IPC進行通信,模塊化程度高,一個服務失效不會影響另外一個服務。


走進linux 驅動開發 之 內核模塊


image

2.Linux體系架構

從兩個層次上來考慮操作系統

  • 用戶空間:包含了用戶的應用程序和C庫
  • GNU C Library (glibc)提供了連接內核的系統調用接口,還提供了在用戶空間應用程序和內核之間進行轉換的機制。
  • 內核空間:包含了系統調用,內核,以及與平臺架構相關的代碼


走進linux 驅動開發 之 內核模塊


image

劃分原因

  • 現代CPU通常都實現了不同的工作模式
  • 以ARM為例:ARM實現了7種工作模式,不同模式下CPU可以執行的指令或者訪問的寄存器不同:
  • (1)用戶模式 usr
  • (2)系統模式 sys
  • (3)管理模式 svc
  • (4)快速中斷 fiq
  • (5)外部中斷 irq
  • (6)數據訪問終止 abt
  • (7)未定義指令異常;
  • 以X86為例:X86實現了4個不同級別的權限,Ring0—Ring3 ;Ring0下可以執行特權指令,可以訪問IO設備;Ring3則有很多的限制。
  • 為了保護內核的安全,把系統分成了2部分:用戶空間和內核空間是程序執行的兩種不同狀態,我們可以通過“系統調用”和“硬件中斷“來完成用戶空間到內核空間的轉移;

3.Linux的內核結構

Linux內核是整體式結構(宏內核),各個子系統聯繫緊密,作為一個大程序在內核空間運行。

系統調用接口(system call interface,SCI)提供了某些機制執行從用戶空間到內核的函數調用。


走進linux 驅動開發 之 內核模塊


image

1)Linux內核組成(子系統)

  • 進程調度(SCHED):控制多個進程對CPU的訪問。當需要選擇下一個進程運行時,由調度程序選擇最值得運行的進程。可運行進程實際上是僅等待CPU資源的進程,如果某個進程在等待其它資源,則該進程是不可運行進程。Linux使用了比較簡單的基於優先級的進程調度算法選擇新的進程。
  • 內存管理(memory management,MM):允許多個進程安全的共享主內存區域。Linux 的內存管理支持虛擬內存,即在計算機中運行的程序,其代碼,數據,堆棧的總量可以超過實際內存的大小,操作系統只是把當前使用的程序塊保留在內存中,其餘的程序塊則保留在磁盤中。必要時,操作系統負責在磁盤和內存間交換程序塊。內存管理從邏輯上分為硬件無關部分和硬件有關部分。硬件無關部分提供了進程的映射和邏輯內存的對換;硬件相關的部分為內存管理硬件提供了虛擬接口。
  • 一般而言,Linux的每個進程享有4GB的內存空間,03GB屬於用戶空間,34GB屬於內核空間。
  • 虛擬文件系統(Virtual File System,VFS):隱藏了各種硬件的具體細節,為所有的設備提供了統一的接口,VFS提供了多達數十種不同的文件系統。虛擬文件系統可以分為邏輯文件系統和設備驅動程序。邏輯文件系統指Linux所支持的文件系統,如ext2,fat等,設備驅動程序指為每一種硬件控制器所編寫的設備驅動程序模塊。

走進linux 驅動開發 之 內核模塊


  • image
  • 網絡接口(NET):提供了對各種網絡標準的存取和各種網絡硬件的支持。網絡接口可分為網絡協議和網絡驅動程序。網絡協議部分負責實現每一種可能的網絡傳輸協議。網絡設備驅動程序負責與硬件設備通訊,每一種可能的硬件設備都有相應的設備驅動程序。

走進linux 驅動開發 之 內核模塊


  • image
  • 進程間通訊(inter-process communication,IPC): 支持進程間各種通信機制。
  • 共享內存
  • 管道
  • 信號量
  • 消息隊列
  • 套接字

4.內核模塊

Linux內核是模塊化組成的,它允許內核在運行時動態地向其中插入或刪除代碼。

二、內核模塊結構

1.頭文件

內核模塊頭文件<linux/module.h>和<linux/init.h>是必不可少的 ,不同模塊根據功能的差異,所需要的頭文件也不相同 。

#include <linux/module.h> #include <linux/init.h> 2.模塊初始化

模塊的初始化負責註冊模塊本身 ,只有已註冊模塊的各種方法才能夠被應用程序使用併發揮各方法的實際功能。

模塊並不是內核內部的代碼,而是獨立於內核之外,通過初始化,能夠讓內核之外的代碼來替內核完成本應該由內核完成的功能,模塊初始化的功能相當於模塊與內核之間銜接的橋樑,告知內核已經準備好模塊了。

內核模塊初始化函數

//模塊初始化函數一般都需聲明為 static //__init 表示初始化函數僅僅在初始化期間使用,一旦初始化完畢,將釋放初始化函數所佔用的內存 static int __init module_init_func(void) { 初始化代碼 } module_init(module_init_func); //module_init宏定義會在模塊的目標代碼中增加一個特殊的代碼段,用於說明該初始化函數所在的位置。 當使用 insmod 將模塊加載進內核的時候,初始化函數的代碼將會被執行。

3.模塊退出

模塊的退出相當於告知內核“我要離開了,將不再為您服務了”。

內核模塊退出函數

//模塊退出函數沒有返回值;
//__exit 標記這段代碼僅用於模塊卸載;
static void __exit module_exit_func(void)
{
//模塊退出代碼
}
module_exit(module_exit_func);
//沒有 module_exit 定義的模塊無法被卸載

當使用 rmmod 卸載模塊時,退出函數的代碼將被執行。

注意:如果模塊被編譯進內核,而不是動態加載,則__init的使用會在模塊初始化完成後丟棄該函數並回收所佔內存, _exit宏將忽略“清理收尾”的函數。

4.模塊許可證聲明

Linux 內核是開源的,遵守 GPL 協議,所以要求加載進內核的模塊也最好遵循相關協議。

為模塊指定遵守的協議用 MODULE_LINCENSE 來聲明 :

  • MODULE_LICENSE("GPL"); 內核能夠識別的協議有
  • “GPL”
  • “GPL v2”
  • “GPL and additional rights(GPL 及附加權利)”
  • “Dual BSD/GPL(BSD/GPL 雙重許可)”
  • “Dual MPL/GPL(MPL/GPL 雙重許可)”
  • “Proprietary(私有)”

5.模塊導出符號 【可選】

使用模塊導出符號,方便其它模塊依賴於該模塊,並使用模塊中的變量和函數等。

  • 在Linux2.6的內核中,/proc/kallsyms文件對應著符號表,它記錄了符號和符號對應的內存地址。
  • $ cat /proc/kallsyms ... ffffff80084039b8 t shash_digest_unaligned ffffff8008403a30 T crypto_shash_digest ffffff8008403ac0 t shash_async_final ffffff8008403af0 T shash_ahash_update ffffff8008403b50 t shash_async_update ffffff8008403b80 t crypto_exit_shash_ops_async ffffff8008403bb0 t crypto_shash_report ffffff8008403c18 t crypto_shash_show ffffff8008403c78 T crypto_alloc_shash ffffff8008403cc8 T crypto_register_shash ffffff8008403d00 T crypto_unregister_shash ffffff8008403d30 T crypto_register_shashes ffffff8008403df8 T crypto_unregister_shashes ffffff8008403e90 T shash_register_instance ffffff8008403ed0 T shash_free_instance ffffff8008403f08 T crypto_init_shash_spawn ffffff8008403f58 T shash_attr_alg ffffff8008403fb0 T shash_ahash_finup ffffff8008404068 t shash_async_finup ffffff80084040b0 T shash_ahash_digest ffffff80084041e0 t shash_async_digest ... 使用一下宏定義導出符號
EXPORT_SYMBOL(module_symbol);
//或
EXPORT_GPL_SYMBOL(module_symbol);

6.模塊描述 [可選]

模塊編寫者還可以為所編寫的模塊增加一些其它描述信息,如模塊作者、模塊本身的描述或者模塊版本等

MODULE_AUTHOR("Abing <[email protected]>");
MODULE_DESCRIPTION("ZHIYUAN ecm1352 beep Driver");
MODULE_VERSION("V1.00");

模塊描述以及許可證聲明一般放在文件末尾。

三、向Linux內核添加新內核模塊

1.添加模塊驅動文件

在linux/drivers/下新建目錄hello,並且在hello/目錄下新建hello.c、Makefile、Kconfig三個文件。

1)內核模塊程序hello.c

/* hello world module */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello, I'm ready!\\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "I'll be leaving, bye!\\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("michael");
MODULE_DESCRIPTION("hello world module");
  • 內核通過 printk() 輸出的信息具有日誌級別,日誌級別是通過在 printk() 輸出的字符串前加一個帶尖括號的整數來控制的,如 printk(“<6>Hello, world!/n”);。內核中共提供了八種不同的日誌級別

// 在 linux/kernel.h 中有相應的宏對應 #define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "<3>" /* error conditions */ #define KERN_WARNING "<4>" /* warning conditions */ #define KERN_NOTICE "<5>" /* normal but significant */ #define KERN_INFO "<6>" /* informational */ #define KERN_DEBUG "<7>" /* debug-level messages */ 2)Kconfig

menu "HELLO TEST Driver "
comment "HELLO TEST Driver Config"

config HELLO
tristate "hello module test"
default m
help
This is the hello test driver.

endmenu
  • 在menuconfig的“driver”菜單下添加“HELLO TEST Driver”子菜單,並加入“HELLO”配置選項,選項默認為m。
  • 保存menuconfig後,會在kernel根目錄下的.config文件中生成“CONFIG_HELLO=m”,在編譯的時候會添加到臨時環境變量中。

3)Makefile

obj-$(CONFIG_HELLO) += hello.o

可用於動態模塊外部編譯的寫法

  • 編譯模塊的內核配置必須與所運行內核的編譯配置一樣 。
  • ifneq ($(KERNELRELEASE),) obj-m += hello.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build # 定義內核路徑 PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules # 表示在當前目錄下編譯 clean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions endif KERNELRELEASE是在內核源碼的頂層Makefile中定義的一個變量,在第一次讀取執行此Makefile時,KERNELRELEASE沒有被定義,所以make將讀取執行else之後的內容。
  • 當從內核源碼目錄返回時,KERNELRELEASE已被定義,kbuild也被啟動去解析kbuild語法的語句,make將繼續讀取else之前的內容,生成的目標模塊名。

2.修改上一級目錄的Kconfig和Makefile

進入linux/drivers/

  • 編輯Makefile,在後面添加一行:
  • obj-$(CONFIG_HELLO) += hello/ 編輯Kconfig,在後面添加一行:
source "drivers/hello/Kconfig"
  • 注:某些內核版本需要同時在arch/arm/Kconfig中添加:source "drivers/hello/Kconfig"

3.make menuconfig配置和編譯

  • 執行:make menuconfig ARCH=arm進入配置菜單
  • 選擇並進入:Device Drivers選項


走進linux 驅動開發 之 內核模塊


image

  • 進入 HELLO TEST Driver選項

走進linux 驅動開發 之 內核模塊


  • image
  • 可以選擇<m> <y> <n>,分別為編譯成內核模塊、編譯進內核、不編譯。

如果選擇編譯成動態模塊<m>

  • 編譯內核過程中,會有如下輸出:
LD drivers/hello/built-in.o
CC [M] drivers/hello/hello.o
CC drivers/hello/hello.mod.o
LD [M] drivers/hello/hello.ko

如果選擇編譯進內核<y>

  • 編譯內核過程中,會有如下輸出:

CC drivers/hello/hello.o LD drivers/hello/built-in.o 4.動態模塊加載和卸載

加載模塊使用 insmod 命令,卸載模塊使用 rmmod 命令。

$ insmod hello.ko $ rmmod hello.ko #加載和卸載模塊必須具有 root 權限 。 對於可接受參數的模塊,在加載模塊的時候為變量賦值即可,卸載模塊無需參數。

$ insmod hello.ko num=8
$ rmmod hello.ko

四、帶參數的內核模塊

模塊參數必須使用 module_param 宏來聲明,通常放在文件頭部。

module_param 需要 3個參數:變量名稱、類型以及用於 sysfs 入口的訪問掩碼。

  • static int num = 5; module_param(num, int, S_IRUGO); 內核模塊支持的參數類型有: bool、 invbool、 charp、 int、 short、 long、 uint、 ushort和 ulong。
  • 訪問掩碼的值在<linux/stat.h>定義, S_IRUGO 表示任何人都可以讀取該參數,但不能修改。
  • 支持傳參的模塊需包含 moduleparam.h 頭文件。

能夠接收參數的模塊範例

 <linux/module.h>
#include <linux/init.h>
// moduleparam.h 文件已經包含在 module.h 文件中
static int num = 3;
static char *whom = "master";
module_param(num, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
static int __init hello_init(void)
{
printk(KERN_INFO "%s, I get %d\\n", whom, num); //KERN_INFO 表示這條打印信息的級別
return 0;
}
static void __exit hello_exit(void)
{
printk("I'll be leaving, bye!\\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("luxiaodai");
MODULE_DESCRIPTION("this is my first module");

更多linux免費視頻資料獲取 後臺私信【架構】

"

一、Linux內核簡介

1.宏內核與微內核

內核分為四大類:單內核(宏內核);微內核;混合內核;外內核。

  • 宏內核(Monolithickernel)是將內核從整體上作為一個大過程來實現,所有的內核服務都在一個地址空間運行,相互之間直接調用函數,簡單高效。
  • Linux雖是宏內核,但已吸收了微內核的部分精華。Linux是模塊化的、多線程的、內核本身可調度的系統,既吸收了微內核的精華,又保留了宏內核的優點,無需消息傳遞,避免性能損失。
  • 微內核(Microkernel)功能被劃分成獨立的過程,過程間通過IPC進行通信,模塊化程度高,一個服務失效不會影響另外一個服務。


走進linux 驅動開發 之 內核模塊


image

2.Linux體系架構

從兩個層次上來考慮操作系統

  • 用戶空間:包含了用戶的應用程序和C庫
  • GNU C Library (glibc)提供了連接內核的系統調用接口,還提供了在用戶空間應用程序和內核之間進行轉換的機制。
  • 內核空間:包含了系統調用,內核,以及與平臺架構相關的代碼


走進linux 驅動開發 之 內核模塊


image

劃分原因

  • 現代CPU通常都實現了不同的工作模式
  • 以ARM為例:ARM實現了7種工作模式,不同模式下CPU可以執行的指令或者訪問的寄存器不同:
  • (1)用戶模式 usr
  • (2)系統模式 sys
  • (3)管理模式 svc
  • (4)快速中斷 fiq
  • (5)外部中斷 irq
  • (6)數據訪問終止 abt
  • (7)未定義指令異常;
  • 以X86為例:X86實現了4個不同級別的權限,Ring0—Ring3 ;Ring0下可以執行特權指令,可以訪問IO設備;Ring3則有很多的限制。
  • 為了保護內核的安全,把系統分成了2部分:用戶空間和內核空間是程序執行的兩種不同狀態,我們可以通過“系統調用”和“硬件中斷“來完成用戶空間到內核空間的轉移;

3.Linux的內核結構

Linux內核是整體式結構(宏內核),各個子系統聯繫緊密,作為一個大程序在內核空間運行。

系統調用接口(system call interface,SCI)提供了某些機制執行從用戶空間到內核的函數調用。


走進linux 驅動開發 之 內核模塊


image

1)Linux內核組成(子系統)

  • 進程調度(SCHED):控制多個進程對CPU的訪問。當需要選擇下一個進程運行時,由調度程序選擇最值得運行的進程。可運行進程實際上是僅等待CPU資源的進程,如果某個進程在等待其它資源,則該進程是不可運行進程。Linux使用了比較簡單的基於優先級的進程調度算法選擇新的進程。
  • 內存管理(memory management,MM):允許多個進程安全的共享主內存區域。Linux 的內存管理支持虛擬內存,即在計算機中運行的程序,其代碼,數據,堆棧的總量可以超過實際內存的大小,操作系統只是把當前使用的程序塊保留在內存中,其餘的程序塊則保留在磁盤中。必要時,操作系統負責在磁盤和內存間交換程序塊。內存管理從邏輯上分為硬件無關部分和硬件有關部分。硬件無關部分提供了進程的映射和邏輯內存的對換;硬件相關的部分為內存管理硬件提供了虛擬接口。
  • 一般而言,Linux的每個進程享有4GB的內存空間,03GB屬於用戶空間,34GB屬於內核空間。
  • 虛擬文件系統(Virtual File System,VFS):隱藏了各種硬件的具體細節,為所有的設備提供了統一的接口,VFS提供了多達數十種不同的文件系統。虛擬文件系統可以分為邏輯文件系統和設備驅動程序。邏輯文件系統指Linux所支持的文件系統,如ext2,fat等,設備驅動程序指為每一種硬件控制器所編寫的設備驅動程序模塊。

走進linux 驅動開發 之 內核模塊


  • image
  • 網絡接口(NET):提供了對各種網絡標準的存取和各種網絡硬件的支持。網絡接口可分為網絡協議和網絡驅動程序。網絡協議部分負責實現每一種可能的網絡傳輸協議。網絡設備驅動程序負責與硬件設備通訊,每一種可能的硬件設備都有相應的設備驅動程序。

走進linux 驅動開發 之 內核模塊


  • image
  • 進程間通訊(inter-process communication,IPC): 支持進程間各種通信機制。
  • 共享內存
  • 管道
  • 信號量
  • 消息隊列
  • 套接字

4.內核模塊

Linux內核是模塊化組成的,它允許內核在運行時動態地向其中插入或刪除代碼。

二、內核模塊結構

1.頭文件

內核模塊頭文件<linux/module.h>和<linux/init.h>是必不可少的 ,不同模塊根據功能的差異,所需要的頭文件也不相同 。

#include <linux/module.h> #include <linux/init.h> 2.模塊初始化

模塊的初始化負責註冊模塊本身 ,只有已註冊模塊的各種方法才能夠被應用程序使用併發揮各方法的實際功能。

模塊並不是內核內部的代碼,而是獨立於內核之外,通過初始化,能夠讓內核之外的代碼來替內核完成本應該由內核完成的功能,模塊初始化的功能相當於模塊與內核之間銜接的橋樑,告知內核已經準備好模塊了。

內核模塊初始化函數

//模塊初始化函數一般都需聲明為 static //__init 表示初始化函數僅僅在初始化期間使用,一旦初始化完畢,將釋放初始化函數所佔用的內存 static int __init module_init_func(void) { 初始化代碼 } module_init(module_init_func); //module_init宏定義會在模塊的目標代碼中增加一個特殊的代碼段,用於說明該初始化函數所在的位置。 當使用 insmod 將模塊加載進內核的時候,初始化函數的代碼將會被執行。

3.模塊退出

模塊的退出相當於告知內核“我要離開了,將不再為您服務了”。

內核模塊退出函數

//模塊退出函數沒有返回值;
//__exit 標記這段代碼僅用於模塊卸載;
static void __exit module_exit_func(void)
{
//模塊退出代碼
}
module_exit(module_exit_func);
//沒有 module_exit 定義的模塊無法被卸載

當使用 rmmod 卸載模塊時,退出函數的代碼將被執行。

注意:如果模塊被編譯進內核,而不是動態加載,則__init的使用會在模塊初始化完成後丟棄該函數並回收所佔內存, _exit宏將忽略“清理收尾”的函數。

4.模塊許可證聲明

Linux 內核是開源的,遵守 GPL 協議,所以要求加載進內核的模塊也最好遵循相關協議。

為模塊指定遵守的協議用 MODULE_LINCENSE 來聲明 :

  • MODULE_LICENSE("GPL"); 內核能夠識別的協議有
  • “GPL”
  • “GPL v2”
  • “GPL and additional rights(GPL 及附加權利)”
  • “Dual BSD/GPL(BSD/GPL 雙重許可)”
  • “Dual MPL/GPL(MPL/GPL 雙重許可)”
  • “Proprietary(私有)”

5.模塊導出符號 【可選】

使用模塊導出符號,方便其它模塊依賴於該模塊,並使用模塊中的變量和函數等。

  • 在Linux2.6的內核中,/proc/kallsyms文件對應著符號表,它記錄了符號和符號對應的內存地址。
  • $ cat /proc/kallsyms ... ffffff80084039b8 t shash_digest_unaligned ffffff8008403a30 T crypto_shash_digest ffffff8008403ac0 t shash_async_final ffffff8008403af0 T shash_ahash_update ffffff8008403b50 t shash_async_update ffffff8008403b80 t crypto_exit_shash_ops_async ffffff8008403bb0 t crypto_shash_report ffffff8008403c18 t crypto_shash_show ffffff8008403c78 T crypto_alloc_shash ffffff8008403cc8 T crypto_register_shash ffffff8008403d00 T crypto_unregister_shash ffffff8008403d30 T crypto_register_shashes ffffff8008403df8 T crypto_unregister_shashes ffffff8008403e90 T shash_register_instance ffffff8008403ed0 T shash_free_instance ffffff8008403f08 T crypto_init_shash_spawn ffffff8008403f58 T shash_attr_alg ffffff8008403fb0 T shash_ahash_finup ffffff8008404068 t shash_async_finup ffffff80084040b0 T shash_ahash_digest ffffff80084041e0 t shash_async_digest ... 使用一下宏定義導出符號
EXPORT_SYMBOL(module_symbol);
//或
EXPORT_GPL_SYMBOL(module_symbol);

6.模塊描述 [可選]

模塊編寫者還可以為所編寫的模塊增加一些其它描述信息,如模塊作者、模塊本身的描述或者模塊版本等

MODULE_AUTHOR("Abing <[email protected]>");
MODULE_DESCRIPTION("ZHIYUAN ecm1352 beep Driver");
MODULE_VERSION("V1.00");

模塊描述以及許可證聲明一般放在文件末尾。

三、向Linux內核添加新內核模塊

1.添加模塊驅動文件

在linux/drivers/下新建目錄hello,並且在hello/目錄下新建hello.c、Makefile、Kconfig三個文件。

1)內核模塊程序hello.c

/* hello world module */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello, I'm ready!\\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "I'll be leaving, bye!\\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("michael");
MODULE_DESCRIPTION("hello world module");
  • 內核通過 printk() 輸出的信息具有日誌級別,日誌級別是通過在 printk() 輸出的字符串前加一個帶尖括號的整數來控制的,如 printk(“<6>Hello, world!/n”);。內核中共提供了八種不同的日誌級別

// 在 linux/kernel.h 中有相應的宏對應 #define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "<3>" /* error conditions */ #define KERN_WARNING "<4>" /* warning conditions */ #define KERN_NOTICE "<5>" /* normal but significant */ #define KERN_INFO "<6>" /* informational */ #define KERN_DEBUG "<7>" /* debug-level messages */ 2)Kconfig

menu "HELLO TEST Driver "
comment "HELLO TEST Driver Config"

config HELLO
tristate "hello module test"
default m
help
This is the hello test driver.

endmenu
  • 在menuconfig的“driver”菜單下添加“HELLO TEST Driver”子菜單,並加入“HELLO”配置選項,選項默認為m。
  • 保存menuconfig後,會在kernel根目錄下的.config文件中生成“CONFIG_HELLO=m”,在編譯的時候會添加到臨時環境變量中。

3)Makefile

obj-$(CONFIG_HELLO) += hello.o

可用於動態模塊外部編譯的寫法

  • 編譯模塊的內核配置必須與所運行內核的編譯配置一樣 。
  • ifneq ($(KERNELRELEASE),) obj-m += hello.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build # 定義內核路徑 PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules # 表示在當前目錄下編譯 clean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions endif KERNELRELEASE是在內核源碼的頂層Makefile中定義的一個變量,在第一次讀取執行此Makefile時,KERNELRELEASE沒有被定義,所以make將讀取執行else之後的內容。
  • 當從內核源碼目錄返回時,KERNELRELEASE已被定義,kbuild也被啟動去解析kbuild語法的語句,make將繼續讀取else之前的內容,生成的目標模塊名。

2.修改上一級目錄的Kconfig和Makefile

進入linux/drivers/

  • 編輯Makefile,在後面添加一行:
  • obj-$(CONFIG_HELLO) += hello/ 編輯Kconfig,在後面添加一行:
source "drivers/hello/Kconfig"
  • 注:某些內核版本需要同時在arch/arm/Kconfig中添加:source "drivers/hello/Kconfig"

3.make menuconfig配置和編譯

  • 執行:make menuconfig ARCH=arm進入配置菜單
  • 選擇並進入:Device Drivers選項


走進linux 驅動開發 之 內核模塊


image

  • 進入 HELLO TEST Driver選項

走進linux 驅動開發 之 內核模塊


  • image
  • 可以選擇<m> <y> <n>,分別為編譯成內核模塊、編譯進內核、不編譯。

如果選擇編譯成動態模塊<m>

  • 編譯內核過程中,會有如下輸出:
LD drivers/hello/built-in.o
CC [M] drivers/hello/hello.o
CC drivers/hello/hello.mod.o
LD [M] drivers/hello/hello.ko

如果選擇編譯進內核<y>

  • 編譯內核過程中,會有如下輸出:

CC drivers/hello/hello.o LD drivers/hello/built-in.o 4.動態模塊加載和卸載

加載模塊使用 insmod 命令,卸載模塊使用 rmmod 命令。

$ insmod hello.ko $ rmmod hello.ko #加載和卸載模塊必須具有 root 權限 。 對於可接受參數的模塊,在加載模塊的時候為變量賦值即可,卸載模塊無需參數。

$ insmod hello.ko num=8
$ rmmod hello.ko

四、帶參數的內核模塊

模塊參數必須使用 module_param 宏來聲明,通常放在文件頭部。

module_param 需要 3個參數:變量名稱、類型以及用於 sysfs 入口的訪問掩碼。

  • static int num = 5; module_param(num, int, S_IRUGO); 內核模塊支持的參數類型有: bool、 invbool、 charp、 int、 short、 long、 uint、 ushort和 ulong。
  • 訪問掩碼的值在<linux/stat.h>定義, S_IRUGO 表示任何人都可以讀取該參數,但不能修改。
  • 支持傳參的模塊需包含 moduleparam.h 頭文件。

能夠接收參數的模塊範例

 <linux/module.h>
#include <linux/init.h>
// moduleparam.h 文件已經包含在 module.h 文件中
static int num = 3;
static char *whom = "master";
module_param(num, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
static int __init hello_init(void)
{
printk(KERN_INFO "%s, I get %d\\n", whom, num); //KERN_INFO 表示這條打印信息的級別
return 0;
}
static void __exit hello_exit(void)
{
printk("I'll be leaving, bye!\\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("luxiaodai");
MODULE_DESCRIPTION("this is my first module");

更多linux免費視頻資料獲取 後臺私信【架構】

走進linux 驅動開發 之 內核模塊

"

相關推薦

推薦中...