Linux系统内核按体积和功能的不同,可以分为两种:微内核与单内核。
微内核,体积小,包含的功能也少,只负责进行进程调度、进程通信、底层中断等工作,而把传统操作系统内核的其他功能模块,如设备驱动、内存管理、文件系统、网络协议等作为服务器运行于内核之上。每个功能模块都一个单独的进程,它们通过内核转发消息,进行联系,因此微内核更像是一个消息转发站。这种内核结构有利于降低内核各功能模块之时的耦合性,使得在不影响系统其他部分工作的前提下,用更高效的实现代替现有的功能模块的工作更加容易,同时,具有更好的可扩展性。但是,不同功能模块之间的消息传递需要一定的开销,这势必会影响到系统运行的效率。
单内核操作系统采用了内核单一化设计,内核是一个单独的二进制映像,包含操作系统内核的各个组成部分,其模块间的通信是通过直接调用其他模块中的函数实现的,而不是消息传递。单内核又被称做单一内核、大内核、宏内核等。单内核运行时避免了频繁的消息传递,因此执行效率较高,但是从软件工程的角度来说,所有功能模块结合在一起作为一个进行运行,导致内核难以维护和增加新的功能。典型的单内核操作系统有UNIX、Linux、OS/360等。
微内核和单内核各有优缺点,在Linux诞生之初,由于内核结构还曾经引起论战。但如今,Linux已被移植到各平台,早已证明其蓬勃的生命力。
Linux采用单内核结构,同时支持模块特性。模块的全称为动态可加载内核模块,是一种目标对象文件,一般由一组函数或数据结构组成,运行于内核空间,不能被交换出内存。模块没有经过链接,不能独立运行,但是其代码可以在运行时链接到系统中作为内核的一部分运行,动态扩充内核的功能,也可以从内核中卸载。模块一旦被插入到内核,就获得和内核同等的地位,代码与内核代码完全等价。采用模块机制之后,更改内核特性时不再需要重新编译内核,可以把内核编译的很小,只包括一些最常用的功能,而把大部分功能作为模块编译,需要时再动态插入内核,利用模块来实现系统的可扩展性,使得内核结构更加紧凑灵活,这是Linux内核模块的重要作用。
但,操作系统采用内核模块也有不足之处,模块装入内核之后即作为内核一部分运行,获得和内核完全等同的权限,可以访问内核的任意部分,如果模块出了问题,可能会影响到整个操作系统的稳定性,严重时甚至导致内核崩溃。内核中维护了一张符号表,包含了内核中所有全局变量和函数地址,当模块插入到内核时,会所自身的全局变量和函数插入到内核符号表中,当模块卸载时,需要把相应的标识符从内核符号表中删去,这些都产生了系统运行的开销。某些需要插入到内核中的模块,其运行可能依赖于早先插入的模块,因此内核还需要维护模块之间的依赖关系,这些也会造成一定开销。可以用模块机制来实现文件系统、驱动程序等功能,但是操作系统最基础最核心的部分不能使用模块机制,如进程调试、内存管理等功能。
学习资料,学视频可以找V:xyd118188
Linux系统包含对内核操作的实用工具软件,如modutils,其包含以下几个程序:
1. insmod:将编译好的模块插入到内核当中。insmod运行时会自动调用模块中的Init_module()。只有超级用户才有使用insmod的权限。
2. rmmod:用来把插入到内核中的模块卸载掉。rmmod运行时会自动调用模块中的cleanup_module()。只有超级用户才有使用rmmod的权限。
3. lsmod:用来显示当前系统中所有正在运行的模块信息。
4. ksyms:用来显示内核符号和模块符号表信息。
5. depmod:处理可加载内核模块的依赖关系。
6. modprobe:根据模块之间的依赖关系自动插入所需模块。
Linux内核模块简单示例:
#define MODULE
#include <linux/module.h>
int init_module(void)
{
printk(“<1>”hello!\n);
return 0;
}
void cleanup_module(void)
{
printk(“<1>”goodbye!\n);
}
上例中定义了宏MODULE,它在程序中并未显示使用,但相当于一个开关,在头文件linux/module.h中,会根据这个宏是否定义过来执行一些操作,因此宏MODULE必须置于头文件语句#include 之间。
init_module()是模块的必要组成部分,负责向内核注册模块提供新功能,当使用Insmod命令插入模块时,init_module()会被调用。同理,cleanup_module()也必不可少,每当卸载模块时,该函数会被调用,负责通知内核当前模块已经被卸载。
printk()是内核输出函数,功能与常用的printf()类似,但是不支持浮点数显示。printk()是一个内核函数,实现代码包含在内核中,工作在内核空间,而printf()实现代码在libc中,工作在用户空间。内核代码不能使用用户空间的资源,因此模块只能使用printk()。printk()可以对显示信息的优先级进行分类,这通过在消息前加一个代表消息优先级的数字或者宏来实现。中定义了8种消息优先级。
1. KERN_EMERG:优先级别最高,表示最紧急的情况,一般代表系统即将崩溃;
2. KERN_ALERT:优先级别次高,表示非常紧急的情况,需要立刻采取行动;
3. KENR_CRIT:很紧急,常用于发生了严重的软硬件操作失败的情况;
4. KERN_ERR:用于提示发生了错误,常用在驱动程序中,报告来自于硬件的错误;
5. KERN_WARNING:警告级别,表示可能发生错误,但是这类错误通常不会有很严重的后果;
6. KERN_NOTICE:告知级别,表示目前系统出现了一些情况,但属于正常情况。
7. KERN_INFO:提示性信息;
8. KERN_debug:调试信息,级别最低。
每个宏将来被展开后都表现为一个尖括号中的整数值,范围为0~7,数字越小代表优先级越高。