一贮缕、Linux內(nèi)核簡(jiǎn)介
1.宏內(nèi)核與微內(nèi)核
內(nèi)核分為四大類:?jiǎn)蝺?nèi)核(宏內(nèi)核)惜浅;微內(nèi)核;混合內(nèi)核山孔;外內(nèi)核懂讯。
- 宏內(nèi)核(Monolithickernel)是將內(nèi)核從整體上作為一個(gè)大過(guò)程來(lái)實(shí)現(xiàn),所有的內(nèi)核服務(wù)都在一個(gè)地址空間運(yùn)行台颠,相互之間直接調(diào)用函數(shù)褐望,簡(jiǎn)單高效。
- Linux雖是宏內(nèi)核串前,但已吸收了微內(nèi)核的部分精華瘫里。Linux是模塊化的、多線程的荡碾、內(nèi)核本身可調(diào)度的系統(tǒng)减宣,既吸收了微內(nèi)核的精華,又保留了宏內(nèi)核的優(yōu)點(diǎn)玩荠,無(wú)需消息傳遞,避免性能損失贼邓。
- 微內(nèi)核(Microkernel)功能被劃分成獨(dú)立的過(guò)程阶冈,過(guò)程間通過(guò)IPC進(jìn)行通信,模塊化程度高塑径,一個(gè)服務(wù)失效不會(huì)影響另外一個(gè)服務(wù)女坑。
2.Linux體系架構(gòu)
從兩個(gè)層次上來(lái)考慮操作系統(tǒng)
- 用戶空間:包含了用戶的應(yīng)用程序和C庫(kù)
- GNU C Library (glibc)提供了連接內(nèi)核的系統(tǒng)調(diào)用接口,還提供了在用戶空間應(yīng)用程序和內(nèi)核之間進(jìn)行轉(zhuǎn)換的機(jī)制统舀。
- 內(nèi)核空間:包含了系統(tǒng)調(diào)用匆骗,內(nèi)核,以及與平臺(tái)架構(gòu)相關(guān)的代碼
劃分原因
-
現(xiàn)代CPU通常都實(shí)現(xiàn)了不同的工作模式
-
以ARM為例:ARM實(shí)現(xiàn)了7種工作模式誉简,不同模式下CPU可以執(zhí)行的指令或者訪問(wèn)的寄存器不同:
- (1)用戶模式 usr
- (2)系統(tǒng)模式 sys
- (3)管理模式 svc
- (4)快速中斷 fiq
- (5)外部中斷 irq
- (6)數(shù)據(jù)訪問(wèn)終止 abt
- (7)未定義指令異常碉就;
以X86為例:X86實(shí)現(xiàn)了4個(gè)不同級(jí)別的權(quán)限,Ring0—Ring3 ;Ring0下可以執(zhí)行特權(quán)指令闷串,可以訪問(wèn)IO設(shè)備瓮钥;Ring3則有很多的限制。
-
為了保護(hù)內(nèi)核的安全烹吵,把系統(tǒng)分成了2部分:用戶空間和內(nèi)核空間是程序執(zhí)行的兩種不同狀態(tài)碉熄,我們可以通過(guò)“系統(tǒng)調(diào)用”和“硬件中斷“來(lái)完成用戶空間到內(nèi)核空間的轉(zhuǎn)移;
3.Linux的內(nèi)核結(jié)構(gòu)
Linux內(nèi)核是整體式結(jié)構(gòu)(宏內(nèi)核)肋拔,各個(gè)子系統(tǒng)聯(lián)系緊密锈津,作為一個(gè)大程序在內(nèi)核空間運(yùn)行。
系統(tǒng)調(diào)用接口(system call interface凉蜂,SCI)提供了某些機(jī)制執(zhí)行從用戶空間到內(nèi)核的函數(shù)調(diào)用琼梆。
1)Linux內(nèi)核組成(子系統(tǒng))
進(jìn)程調(diào)度(SCHED):控制多個(gè)進(jìn)程對(duì)CPU的訪問(wèn)性誉。當(dāng)需要選擇下一個(gè)進(jìn)程運(yùn)行時(shí),由調(diào)度程序選擇最值得運(yùn)行的進(jìn)程叮叹“埃可運(yùn)行進(jìn)程實(shí)際上是僅等待CPU資源的進(jìn)程,如果某個(gè)進(jìn)程在等待其它資源蛉顽,則該進(jìn)程是不可運(yùn)行進(jìn)程蝗砾。Linux使用了比較簡(jiǎn)單的基于優(yōu)先級(jí)的進(jìn)程調(diào)度算法選擇新的進(jìn)程。
-
內(nèi)存管理(memory management携冤,MM):允許多個(gè)進(jìn)程安全的共享主內(nèi)存區(qū)域悼粮。Linux 的內(nèi)存管理支持虛擬內(nèi)存,即在計(jì)算機(jī)中運(yùn)行的程序曾棕,其代碼扣猫,數(shù)據(jù),堆棧的總量可以超過(guò)實(shí)際內(nèi)存的大小翘地,操作系統(tǒng)只是把當(dāng)前使用的程序塊保留在內(nèi)存中申尤,其余的程序塊則保留在磁盤中。必要時(shí)衙耕,操作系統(tǒng)負(fù)責(zé)在磁盤和內(nèi)存間交換程序塊昧穿。內(nèi)存管理從邏輯上分為硬件無(wú)關(guān)部分和硬件有關(guān)部分。硬件無(wú)關(guān)部分提供了進(jìn)程的映射和邏輯內(nèi)存的對(duì)換橙喘;硬件相關(guān)的部分為內(nèi)存管理硬件提供了虛擬接口时鸵。
- 一般而言,Linux的每個(gè)進(jìn)程享有4GB的內(nèi)存空間厅瞎,03GB屬于用戶空間饰潜,34GB屬于內(nèi)核空間。
-
虛擬文件系統(tǒng)(Virtual File System,VFS):隱藏了各種硬件的具體細(xì)節(jié)和簸,為所有的設(shè)備提供了統(tǒng)一的接口彭雾,VFS提供了多達(dá)數(shù)十種不同的文件系統(tǒng)。虛擬文件系統(tǒng)可以分為邏輯文件系統(tǒng)和設(shè)備驅(qū)動(dòng)程序比搭。邏輯文件系統(tǒng)指Linux所支持的文件系統(tǒng)冠跷,如ext2,fat等,設(shè)備驅(qū)動(dòng)程序指為每一種硬件控制器所編寫的設(shè)備驅(qū)動(dòng)程序模塊身诺。
image -
網(wǎng)絡(luò)接口(NET):提供了對(duì)各種網(wǎng)絡(luò)標(biāo)準(zhǔn)的存取和各種網(wǎng)絡(luò)硬件的支持蜜托。網(wǎng)絡(luò)接口可分為網(wǎng)絡(luò)協(xié)議和網(wǎng)絡(luò)驅(qū)動(dòng)程序。網(wǎng)絡(luò)協(xié)議部分負(fù)責(zé)實(shí)現(xiàn)每一種可能的網(wǎng)絡(luò)傳輸協(xié)議霉赡。網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序負(fù)責(zé)與硬件設(shè)備通訊橄务,每一種可能的硬件設(shè)備都有相應(yīng)的設(shè)備驅(qū)動(dòng)程序。
image -
進(jìn)程間通訊(inter-process communication穴亏,IPC): 支持進(jìn)程間各種通信機(jī)制蜂挪。
- 共享內(nèi)存
- 管道
- 信號(hào)量
- 消息隊(duì)列
- 套接字
4.內(nèi)核模塊
Linux內(nèi)核是模塊化組成的重挑,它允許內(nèi)核在運(yùn)行時(shí)動(dòng)態(tài)地向其中插入或刪除代碼。
二棠涮、內(nèi)核模塊結(jié)構(gòu)
1.頭文件
內(nèi)核模塊頭文件<linux/module.h>和<linux/init.h>是必不可少的 谬哀,不同模塊根據(jù)功能的差異,所需要的頭文件也不相同 严肪。
#include <linux/module.h>
#include <linux/init.h>
2.模塊初始化
模塊的初始化負(fù)責(zé)注冊(cè)模塊本身 史煎,只有已注冊(cè)模塊的各種方法才能夠被應(yīng)用程序使用并發(fā)揮各方法的實(shí)際功能。
模塊并不是內(nèi)核內(nèi)部的代碼驳糯,而是獨(dú)立于內(nèi)核之外篇梭,通過(guò)初始化,能夠讓內(nèi)核之外的代碼來(lái)替內(nèi)核完成本應(yīng)該由內(nèi)核完成的功能酝枢,模塊初始化的功能相當(dāng)于模塊與內(nèi)核之間銜接的橋梁恬偷,告知內(nèi)核已經(jīng)準(zhǔn)備好模塊了。
內(nèi)核模塊初始化函數(shù)
//模塊初始化函數(shù)一般都需聲明為 static
//__init 表示初始化函數(shù)僅僅在初始化期間使用帘睦,一旦初始化完畢袍患,將釋放初始化函數(shù)所占用的內(nèi)存
static int __init module_init_func(void)
{
初始化代碼
}
module_init(module_init_func);
//module_init宏定義會(huì)在模塊的目標(biāo)代碼中增加一個(gè)特殊的代碼段,用于說(shuō)明該初始化函數(shù)所在的位置竣付。
當(dāng)使用 insmod 將模塊加載進(jìn)內(nèi)核的時(shí)候协怒,初始化函數(shù)的代碼將會(huì)被執(zhí)行。
3.模塊退出
模塊的退出相當(dāng)于告知內(nèi)核“我要離開(kāi)了卑笨,將不再為您服務(wù)了”。
內(nèi)核模塊退出函數(shù)
//模塊退出函數(shù)沒(méi)有返回值仑撞;
//__exit 標(biāo)記這段代碼僅用于模塊卸載赤兴;
static void __exit module_exit_func(void)
{
//模塊退出代碼
}
module_exit(module_exit_func);
//沒(méi)有 module_exit 定義的模塊無(wú)法被卸載
當(dāng)使用 rmmod 卸載模塊時(shí),退出函數(shù)的代碼將被執(zhí)行隧哮。
注意:如果模塊被編譯進(jìn)內(nèi)核桶良,而不是動(dòng)態(tài)加載,則__init的使用會(huì)在模塊初始化完成后丟棄該函數(shù)并回收所占內(nèi)存, _exit宏將忽略“清理收尾”的函數(shù)沮翔。
4.模塊許可證聲明
Linux 內(nèi)核是開(kāi)源的陨帆,遵守 GPL 協(xié)議,所以要求加載進(jìn)內(nèi)核的模塊也最好遵循相關(guān)協(xié)議采蚀。
為模塊指定遵守的協(xié)議用 MODULE_LINCENSE 來(lái)聲明 :
MODULE_LICENSE("GPL");
- 內(nèi)核能夠識(shí)別的協(xié)議有
- “GPL”
- “GPL v2”
- “GPL and additional rights(GPL 及附加權(quán)利)”
- “Dual BSD/GPL(BSD/GPL 雙重許可)”
- “Dual MPL/GPL(MPL/GPL 雙重許可)”
- “Proprietary(私有)”
5.模塊導(dǎo)出符號(hào) 【可選】
使用模塊導(dǎo)出符號(hào)疲牵,方便其它模塊依賴于該模塊,并使用模塊中的變量和函數(shù)等榆鼠。
-
在Linux2.6的內(nèi)核中纲爸,/proc/kallsyms文件對(duì)應(yīng)著符號(hào)表,它記錄了符號(hào)和符號(hào)對(duì)應(yīng)的內(nèi)存地址妆够。
$ 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 ...
-
使用一下宏定義導(dǎo)出符號(hào)
EXPORT_SYMBOL(module_symbol); //或 EXPORT_GPL_SYMBOL(module_symbol);
6.模塊描述 [可選]
模塊編寫者還可以為所編寫的模塊增加一些其它描述信息识啦,如模塊作者负蚊、模塊本身的描述或者模塊版本等
MODULE_AUTHOR("Abing <Linux@zlgmcu.com>");
MODULE_DESCRIPTION("ZHIYUAN ecm1352 beep Driver");
MODULE_VERSION("V1.00");
模塊描述以及許可證聲明一般放在文件末尾。
三颓哮、向Linux內(nèi)核添加新內(nèi)核模塊
1.添加模塊驅(qū)動(dòng)文件
在linux/drivers/下新建目錄hello家妆,并且在hello/目錄下新建hello.c、Makefile冕茅、Kconfig三個(gè)文件伤极。
1)內(nèi)核模塊程序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");
-
內(nèi)核通過(guò) printk() 輸出的信息具有日志級(jí)別,日志級(jí)別是通過(guò)在 printk() 輸出的字符串前加一個(gè)帶尖括號(hào)的整數(shù)來(lái)控制的嵌赠,如 printk(“<6>Hello, world!/n”);塑荒。內(nèi)核中共提供了八種不同的日志級(jí)別
// 在 linux/kernel.h 中有相應(yīng)的宏對(duì)應(yīng) #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”配置選項(xiàng)姜挺,選項(xiàng)默認(rèn)為m齿税。
- 保存menuconfig后,會(huì)在kernel根目錄下的.config文件中生成“CONFIG_HELLO=m”炊豪,在編譯的時(shí)候會(huì)添加到臨時(shí)環(huán)境變量中凌箕。
3)Makefile
obj-$(CONFIG_HELLO) += hello.o
可用于動(dòng)態(tài)模塊外部編譯的寫法
-
編譯模塊的內(nèi)核配置必須與所運(yùn)行內(nèi)核的編譯配置一樣 。
ifneq ($(KERNELRELEASE),) obj-m += hello.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build # 定義內(nèi)核路徑 PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules # 表示在當(dāng)前目錄下編譯 clean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions endif
- KERNELRELEASE是在內(nèi)核源碼的頂層Makefile中定義的一個(gè)變量词渤,在第一次讀取執(zhí)行此Makefile時(shí)牵舱,KERNELRELEASE沒(méi)有被定義,所以make將讀取執(zhí)行else之后的內(nèi)容缺虐。
- 當(dāng)從內(nèi)核源碼目錄返回時(shí)芜壁,KERNELRELEASE已被定義,kbuild也被啟動(dòng)去解析kbuild語(yǔ)法的語(yǔ)句高氮,make將繼續(xù)讀取else之前的內(nèi)容慧妄,生成的目標(biāo)模塊名。
2.修改上一級(jí)目錄的Kconfig和Makefile
進(jìn)入linux/drivers/
-
編輯Makefile剪芍,在后面添加一行:
obj-$(CONFIG_HELLO) += hello/
-
編輯Kconfig塞淹,在后面添加一行:
source "drivers/hello/Kconfig"
- 注:某些內(nèi)核版本需要同時(shí)在arch/arm/Kconfig中添加:source "drivers/hello/Kconfig"
3.make menuconfig配置和編譯
- 執(zhí)行:make menuconfig ARCH=arm進(jìn)入配置菜單
- 選擇并進(jìn)入:Device Drivers選項(xiàng)
-
進(jìn)入 HELLO TEST Driver選項(xiàng)
image- 可以選擇<m> <y> <n>,分別為編譯成內(nèi)核模塊罪裹、編譯進(jìn)內(nèi)核饱普、不編譯。
如果選擇編譯成動(dòng)態(tài)模塊<m>
-
編譯內(nèi)核過(guò)程中状共,會(huì)有如下輸出:
LD drivers/hello/built-in.o CC [M] drivers/hello/hello.o CC drivers/hello/hello.mod.o LD [M] drivers/hello/hello.ko
如果選擇編譯進(jìn)內(nèi)核<y>
-
編譯內(nèi)核過(guò)程中套耕,會(huì)有如下輸出:
CC drivers/hello/hello.o LD drivers/hello/built-in.o
4.動(dòng)態(tài)模塊加載和卸載
加載模塊使用 insmod 命令,卸載模塊使用 rmmod 命令峡继。
$ insmod hello.ko
$ rmmod hello.ko
#加載和卸載模塊必須具有 root 權(quán)限 箍铲。
對(duì)于可接受參數(shù)的模塊,在加載模塊的時(shí)候?yàn)樽兞抠x值即可鬓椭,卸載模塊無(wú)需參數(shù)颠猴。
$ insmod hello.ko num=8
$ rmmod hello.ko
四关划、帶參數(shù)的內(nèi)核模塊
模塊參數(shù)必須使用 module_param 宏來(lái)聲明,通常放在文件頭部翘瓮。
module_param 需要 3個(gè)參數(shù):變量名稱贮折、類型以及用于 sysfs 入口的訪問(wèn)掩碼。
static int num = 5;
module_param(num, int, S_IRUGO);
- 內(nèi)核模塊支持的參數(shù)類型有: bool资盅、 invbool调榄、 charp、 int呵扛、 short每庆、 long、 uint今穿、 ushort和 ulong缤灵。
- 訪問(wèn)掩碼的值在<linux/stat.h>定義, S_IRUGO 表示任何人都可以讀取該參數(shù)蓝晒,但不能修改腮出。
- 支持傳參的模塊需包含 moduleparam.h 頭文件。
能夠接收參數(shù)的模塊范例
#include <linux/module.h>
#include <linux/init.h>
// moduleparam.h 文件已經(jīng)包含在 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 表示這條打印信息的級(jí)別
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");