Linux kernel map
一.Linux体系结构分为用户空间和kernel space。中用户空间分为用户应用程序和GNU C library(glibc);kernel space分为 System Call Interface,kernel, 基于体系结构的内核代码
内核空间与用户空间是程序执行的两种不同状态, 通过系统调用(用户主动发起)和硬件中断(由外部原因引起,是被动的)能够完成从用户空间到内核空间的转移。
Linux内核架构由7部分组成:SCI, PM, VFS(隐藏各种文件系统(如EXT2、FAT、NFS、设备文件等)的具体细节,为文件操作提供统一的接口例如open()函数), MM(完成虚地址到物理地址的转换), Network Stack, Arch(与CPU密切相关), DD
二.目录结构
Linux内核源代码采用树形结构进行组织,非常合理地把功能相关的文件都放在同一个子目录下,使得程序更具可读性
内核源代码下载地址:www.kernel.org
---Arch目录:
arch是architecture的缩写。内核所支持的每种CPU体系,在该目录下都有对应的子目录。每个CPU的子目录,又进一步分解为boot,mm,kernel等子目录,分别包含控制系统引导,内存管理,系统调用等。(和开发板有关的放在“mach”开头的目录中)
----Block目录:
部分块设备驱动程序
----Crypto目录:
加密、压缩、CRC校验算法
----Documentation:
内核的文档
-----Drivers目录:
设备驱动程序
----Fs目录:
存放各种文件系统的实现代码。每个子目录对应一种文件系统的实现,公用的源程序用于实现虚拟文件系统nfs
||---devpts/* /dev/pts虚拟文件系统*/
||---ext2/*第二扩展文件系统*/
||---fat/*MS的fat32文件系统*/
||---isofs/*ISO9660光盘cd-rom上的文件系统*/
----include目录:
内核所需要的头文件。与平台无关的头文件在include/linux子目录下, 与平台相关的头文件则放在相应的子目录中。
----lib目录:
库文件代码
----mm(memory management)目录:
Mm目录中的文件用于实现内存管理中与体系结构无关的部分(与体系结构相关的部分在arch目录中)
----net目录:
网络协议的实现代码
||---802 /*802无限通信协议核心支持代码*/
||---appletalk /*与苹果系统联网的协议*/
||---ax25 /*AX25无限INTERNET协议*/
||---bridge /*桥接设备*/
||---ipv4 /*IP协议族V4版32位寻址模式*/
||---ipv6 /*IP协议族V6版*/
---samples:
一些内核编程的范例
---scripts:
配置内核的脚本
---security:
SElinux的模块
---sound:
音频设备的驱动程序
---usr:
cpio命令实现
---virt:
内核虚拟机
经常会修改到的目录是arch目录和drivers目录
三.内核配置与编译
Linux内核具有可定制的优点,具体步骤如下:
1.清除临时文件、中间文件和配置文件.
----make clean:
remove most generated files but keep the config
----make mrproper:
remove all generated files and config files
----make distclean
mrproper and remove editor backup and patch files
在根目录位置下执行上述命令。
2.确定目标系统的软硬件配置情况,比如CPU的类型、网卡的型号、所需支持的网络协议等
3.使用如下命令之一配置内核:
----make config :基于文本模式的交互式配置
----make menuconfig :基于文本模式的菜单型配置(推荐使用)(需安装sudo apt-get install libncurses5-dev)
----make oldconfig :使用已有的配置文件(.config),但是会访问新增的配置选项
----make xconfig : 图形化的配置(需安装图形化系统)
使用make menuconfig时配置菜单中的高亮行:[ _ ]表示不编译;[M]表示仅仅是编译;[*]表示诸*文件编译链接产生镜像文件。
配置好后有个.config隐藏文件(ls -a可以看到)
内核配置通常在一个已有的配置文件基础上,通过修改得到新的配置文件,linux内核提供了一系列可供参考的内核配置文件,位于arch/$cpu/configs
4.编译内核:make zlmage
make bzlmage (ubuntu下make命令即可)
区别:在X86 平台,zlmage只能用小于512K的内核
如需获取详细编译信息,可使用:
make zlmage V = 1
make bzlmage V = 1
编译好的内核位于arch/<cpu>/boot/目录下
5.编译内核模块:make modules
6.安装内核模块:make modules_install
将编译好的内核模块从内核源代码目录copy至/lib/modules下
7.制作init ramdisk:mkinitrd initrd -$version $version
例:mkinitrd initrd-2.6.29 2.6.29
$version可以通过查询/lib/modules下的目录得到(该目录是经过第6步之后就产生的)
利用该命令将/lib/modules下的目录做成一个文件
内核安装(X86平台)
1.cp arch/x86/boot/bzlmage /boot/vmlinuz-$version(拷贝内核文件)
2.cp $initrd /boot/(拷贝randisk)
3.修改/etc/grub.conf或者/etc/lilo.conf
$version为所编译的内核版本号
四.Linux内核模块开发
Linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用需要的组件呢:
----方法一:把所有的组件都编译进内核文件,即:zlmage或bzlmage,但这样会导致两个问题:一是生成的内核文件过大(bzlmage过大);二是如果要添加或删除某个组件,需要重新编译整个内核
那么有没有一种机制能让内核文件(zlmage或bzlmage)本身并不包含某组件,而是在该组件需要被使用的时候,动态地添加到正在运行的内核中呢?
——有,linux提供了一种叫做“内核模块”的机制,可以实现以上效果。
内核模块具有以下特点:a.模块本身并不被编译进内核文件(zlmage或bzlmage);b.可以根据需求,在内核运行期间动态地安装和卸载
例(hello_world.c),从中可以看见模块函数没有main函数
#include <linux/init.h>
#include <linux/module.h>
static int __int hello_int()
{
printk("Hello World!\n");
return 0;
}
static void __exit hello_exit()
{
printk("<7>hello <0>exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
内核模块程序结构
1.模块加载函数(必须)
安装模块时被系统自动调用的函数,通过module_init宏来指定(指定程序的入口函数,相当于应用程序中的main函数),在HelloWorld模块中,模块加载函数为hello_init
2.模块卸载函数(必须)
卸载模块时被系统自动调用的函数,通过module_exit宏来指定,在HelloWorld模块中,模块卸载函数为hello_exit
内核模块的编译
在linux 2.6下编译内核模块,通常使用makefile(不再像linux 2.4下面可以使用gcc来编译)
例——内核模块由一个源文件构成,该如何编写makefile:
ifneq($(KERNELRELEASE),)
obj-m := hello_world.o
else
KDIR :=/lib/modules/2.6.38-14-generic/build
all:
make -C $(KDIR) M=$(PWD) modules
clean: rm -f *.ko *.o *.mod *.mod.c *.symvers
Endif
说明:
Obj-m:这个变量是指定你要生成哪些模块。模块的格式为 obj-m :=<模块名>.o
KDIR:这是我们正在运行的操作系统内核编译目录。也就是编译模块需要的环境
M = :指定我们源文件的位置
PWD :这是当前工作路径$(shell )是make 的一个内置函数。用来执行shell命令。
除了obj-m和KDIR的内容需改变之外,其他的可以不做修改
例——内核模块由多个源文件构成(main.c 和add.c),该如何编写makefile:
ifneq($(KERNELRELEASE), )
obj-m := hello.o //要生成的模块名
hello-objs :=main.o add.o //生成这个模块所需要的目标文件,模块的名字不能和目标文件相同名字
else
KDIR := /lib/modules/2.6.38-14-generic/build
all:make -C $(KDIR) M=$(PWD) modules
clean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers
Endif
安装与卸载
----加载insmod(insmod hello.ko)
----卸载rmmod(rmmod hello)
----lsmod命令用于查看有哪些内核模块被安装
----加载modprobe(modprobe hello)
Modprobe如同insmod,也是加载一个模块到内核。它的不同之处在于它会根据文件/lib/modules/<$version>/modules.dep
来查看要加载的模块,看它是否还依赖于其他模块,如果是,modprobe会首先找到这些模块,把他们先加载到内核。
模块可选信息
1.许可证申明
宏MODULE_LICENSE用来告知内核,该模块带有一个许可证,没有这样的说明,加载模块时内核会抱怨,有效地许可证有“GPL”、“GPLv2”、“GPL and additional rights”、“DualBSD/GPL”、“Dual MPL/GPL”和“Proprietary”
2.作者申明(可选)
MODULE_AUTHOR("Simon Li");
3.模块描述(可选)
MODULE_DESCRIPTION("Hello World Module");
4.模块版本(可选)
MODULE_VERSION("V1.0");
5.模块别名(可选)
MODULE_ALIAS(" a simple module");
6.模块参数
通过宏module_param指定模块参数(在应用型程序中,命令行参数是char*类型的(即字符串),保存在char *argv[]中),模块参数用于在加载模块时传递参数给模块。
module_pram(name, type, perm)
----name:是模块参数的名称,type是这个参数的类型,perm是模块参数的访问权限
type常见值:bool,布尔型;int,整型;charp,字符串型
perm常见值:
S_IRUGO:任何用户都对/sys/module中出现的该参数具有读权限
S_IWUSR:允许root用户修改/sys/module中出现的该参数
例如:
int a = 3;
char *st;
module_param(a, int, S_IRUGO);
module_param(st, charp, S_IRUGO);
例param.c
内核符号导出
/proc/kallsyms记录了内核中所有导出的符号的名字与地址
内核符号的导出使用:EXPORT_SYMBOL(符号名)
EXPORT_SYMBOL_FPL(符号名)
其中EXPORT_SYMBOL_GPL只能用于包含GPL许可证的模块
常见问题:版本不匹配
内核模块的版本由其所依赖的内核代码版本所决定,在加载内核模块时,insmod程序会将内核模块版本与当前正在运行的内核版本比较,如果不一致时,就会出现类似下面的错误:
insmod hello.ko
disagrees about version of symbol struct_module
Insmod: error inserting 'hello.ko': -1 invalid module format
可以使用uname -r查看当前使用的内核版本
就是在makefile中指定编译的内核代码的版本即是内核模块的版本,当两个版本不一致的时候加载不进去
内核模块版本是由它的内核代码所决定的(可以通过查看内核的Makefile知道其版本)
解决方法:
1.使用modprobe ----force-modversion强行插入
2.确保编译内核模块时,所依赖的内核代码版本等同于当前正在运行的内核(推荐)
总结--对比应用
对比应用程序,内核模块具有以下不同:
应用程序是从头(main)在尾执行任务,执行结束后从内存中消失。内核模块则是先在内核中注册自己以便服务于将来的某个请求,然后它的初始化函数结束,此时模块依然存在于内核中,知道卸载函数被调用,模块才从内核中消失。
内核打印
printk与printf对比:printk只在内核中使用,printf只在应用程序中使用
printk允许根据严重程度,通过附加不同的“优先级”来对消息分类:在<linux/kernel.h>中定义了8种记录级别。按照优先级递减的顺序分别是:
KERN_EMERG "<0>":用于紧急消息,常常是那些崩溃前的消息
KERN_ALERT "<1>":需要立刻行动的消息
KERN_CRIT "<2>":严重情况
KERN_ERR "<3>":错误情况
KERN_WARNING "<4>":有问题的警告
KERN_NOTICE "<5>":正常情况,但是任然值得注意
KERN_INFO "<6>":信息型消息
KERN_DEBUG "<7>":用作调试消息
例.通过优先级控制消息是否打印到屏幕
没有指定优先级的printk默认使用DEFAULT_MESSAGE_LOCAL优先级,它是一个在kernel/printk.c中定义的整数。
在2.6.29内核中
#define DEFAULT_MESSAGE_LOGLEVEL 4
/*KERN_WARNING*/
不管信息是否打印到屏幕上,在/var/message中肯定有相关信息(也可以在命令行使用dmesg | tail -12来查看/var/log目录中,名为dmesg的文件内容。
dmesg用来显示开机信息,kernel会将开机信息存储在ring buffer中。您若是开机时来不及查看信息,可利用dmesg来查看)
纯字符控制台优先级配置
/proc/sys/kernel/printk
6 4 1 7
----console_loglevel
----default_message_loglevel
----minimum_console_level
----default_console_loglevel