1、Linux内核简介
a、Linux系统是如何构成的?
Linux系统可以分成用户空间和内核空间
用户空间:用户程序,C库;
内核空间:内核,系统调用接口,体系结构相关代码;
b、为什么Linux系统会被划分为用户空间与内核空间?
现代CPU通常实现了不同的工作模式,以ARM为例,实现了7种工作模式:
系统模式(Sys),用户模式(Usr)、管理模式(Svc)、外部中断模式(Irq)、快速中断模式(Fiq)、数据访问中止模式(abt)、未定义指令异常模式(und);X86也实现了4个不同的级别:Ring0——Ring3。Ring0下,可以执行特权指令,可以访问IO设备等,在Ring3则有很多限制。Linux系统利用了CPU这一特性,使用了其中的两极来分别运行Linux内核与应用程序,这样是操作系统本身得到充分的保护。例如:如果使用X86,用户代码运行在Ring3,内核代码运行在Ring0。
c、内核空间与用户空间是程序执行的两种不同的状态,通过系统调用和硬件中断能够完成从用户空间到内核空间的转移。
d、Linux内核是如何构成的?
(7个部分):1>系统调用接口(SCI):SCI层为用户空间提供了一套标准的系统调用函数来访问Linux内核,搭起了用户空间到内核空间的桥梁。
2>进程管理(PM):进程管理的重点是创建进程(fork,exec),停止进程(kill,exit),并控制他们之间的通信(signal或者POSIX机制)。进程管理还包括控制进程如何共享CPU,即进程调度。
3>内存管理(MM):内存管理的主要作用是控制多个进程安全的共享内存区域,对内存回收,映射管理。
4>虚拟文件系统(VFS):VFS隐藏各种文件系统的具体细节,为文件操作提供统一的接口。
5>架构体系相关代码(Arch):内核所支持的各种CPU体系的相关代码,跨平台移植时需要修改。
6>网络协议栈(NS):网络协议栈为Linux提供了丰富的网络协议实现。
7>设备驱动(DD):Linux内核有大量代码都在设备驱动程序中,他们控制特定的硬件设备。
2、Linux内核源代码
Linux内核源代码采用树形结构进行组织,非常合理地把功能相关的文件都放在同一个子目录下,使得程序更具有可读性。内核源代码下载地址:
1>arch目录:architecture的缩写。内核所支持的每种CPU体系,在该目录下都有对应的子目录。每个CPU的子目录,又进一步分解为boot,mm,kernel等子目录,分别包含控制系统引导,内存管理,系统调用等。
2>block目录:部分块设备驱动程序。
3>crypto目录:加密、压缩,CRC校验算法。
4>documention:内核的文档。
5>drivers目录:设备驱动程序。
6>fs目录:存放各种文件系统的实现代码,每个子目录对应一种文件系统的实现,公用的源程序用于实现虚拟文件系统VFS。
||--devpts:/dev/pts虚拟文件系统
||--ext2:第二扩展文件系统
||--fat:MS的fat32文件系统
||--isofs:ISO9660光盘cd-rom上的文件系统
7>include目录:内核所需要的头文件。与平台无关的头文件在include/linux子目录下,与平台有关的头文件则放在相应的子目录中。
8>lib目录:库文件代码。
9>mm目录:mm目录中的文件用于实现内存管理中与体系结构无关的部分。与体系结构有关的部分在arch目录。
10>net目录:网络协议的实现代码
||--802:802无线通讯协议核心支持代码
||--appletalk:与苹果系统联网的协议
||--ax25:AX25无线INTERNET协议
||--bridge:桥接设备
||--ipv4:IP协议族V4版32位寻址模式
||--ipv6:IP协议族V6版
11>samples:一些内核编程的范例。
12>scripts:配置内核的脚本。
13>security:SElinux的模块。
14>sound:音频设备的驱动程序。
15>usr:cpio命令实现。用于制作根文件系统的,把文件系统和内核放置一起的命令。是用户程序。
16>virt:内核虚拟机。
3、LInux内核配置与编译(必备技能)
Linux内核具有可制订的优点,具体步骤如下:
1>清除临时文件,中间文件和配置文件。
* make clean
remove most genrated files but keep the config(删除大多数产生的文件保存配置文件)
* make mrproer
remove all generated files + config files(删除所有产生的文件包括配置文件)
* make distclean
mrproper + remove editor backup and patch files
2>确定目标系统的软硬件配置情况,比如CPU的类型、网卡的型号、所需支持的网络协议等。
3>使用如下命令之一配置内核:
* make config:基于文本模式的交互式配置 。
* make menuconfig:基于文本模式的菜单型配置。(推荐使用)
* make oldconfig: 使用已有的配置文件(.config),但是会询问新增的配置选项。
* make xconfig: 图形化的配置(需安装图形化系统)。
4>make menuconfig是最为常用的内核配置方式,使用方法如下:
a>使用方向键在各选项间移动;
b>使用ENTER键进入下一层选单;每个选项上的高亮字母是键盘快捷方式,使用它可以快速地达到想要设置的选单项。
c>在括号中按“y”将这个项目编译进内核中,按“m”编译为模块,按“n”为不选择(按空格键也可在编译进内核、编译为模块和不编译三者间进行切换),按“h”将显示这个选项的帮助信息,按“Esc”键将返回到上层选单。
5>配置菜单中的项该怎么选择呢?
Code maturity level options(代码成熟度选项)
Prompt for debelopment and/or incomplete code/drivers
显示尚在开发中或尚未完成的代码与驱动,除非你是测试人员或者开发者,否则请勿选择。
General setup(常规设置)
Local version-append to kernel release
在内核版本后面加上自定义的版本字符串(小于64字符)可以用“uname -a”命令看到
Automatically append version ingormation to the version string
自动在版本字符串后面添加版本信息,编译时需要perl以及git仓库支持
Support for paging of anonymous memory(swap)
使用交换分区或者交换文件来作为虚拟内存
System V IPC
System V 进程间通信(IPC)支持许多程序需要这个功能建议选
POSIX Message Queues
POSIX消息队列支持
BSD Process Accounting
将进程的统计信息写入文件的用户级系统调用,主要包括进程的创建时间/创建者/内存占用等信息
BSD Process Accounting version 3 file format
使用新的第三版文件格式,可以包含每个进程的PID和其父进程的PID但是不兼容老版本的文件格式
Export task/process statics through netlink
Loadable module support(可加载模块支持)
Enable loadable module support
打开可加载模块支持,如果打开它则必须通过“make modules install”把内核模块安装在/lib/modules/中
Module unloading
允许卸载已经加载的模块
Forced module unloading
允许强制卸载正在使用中的模块(比较危险)
Module versioning support
允许使用其他版本的模块(可能会出问题)
Source checksum for all modules
为所有的模块校验源码,如果你不是自己编写内核就不需要它
Automatic kernel module loading
让内核通过运行modprobe来自动加载所需要的模块,比如可以自动解决模块的依赖关系
块设备、CPU类型、电源管理、总线、可执行文件的格式、网络、
内核配置通常在一个已有的配置文件基础上,通过修改得到新的配置文件,Linux内核提供了一系列可供参考的内核配置文件位于Arch/$cpu/configs
4>编译内核:
make zImage make bzImage
区别:在X86平台,zImage只能用于小于512K的内核
如果想获取详细编译信息,可使用:make zImage V = 1 make bzImage V = 1
编译好的内核位于arch//boot/目录下
5>编译内核模块:make modules
6>安装内核模块:make modules_install
将编译好的内核模块从内核源代码目录copy至/lib/nodules下。
7>制作init ramdisk
mkinitrd initrd-$version $version
例:
mkinitrd initrd-2.6.29 2.6.29
$version可以通过查询/lib/modules下的目录得到。
8>内核安装(X86)
a、cp arch/x86/boot/bzImage/boot/vmlinuz-$version
b、cp $initrd/boot/
c、修改/etc/grub.conf或者/etc.lilo.conf
$version为所编译的内核版本号
4、内核模块开发
1>什么是内核模块?
Linux内核的整体结构非常庞大,其他包含的组件也非常多,如何使用需要的组件呢:
方法一:把所有的组件都编译进内核文件,即zImage或bzImage,但这样会导致两个问题:一是生成的内核文件过大;二是如果要添加或删除耨个组件,需要重新编译整个内核。
方法二:有一种机制(内核模块机制)能让内核文件(zImage或bzImage)本身并不包含某个组件,而是该组件需要被使用的时候,动态的添加到正在运行的内核中。
2>内核模块特点
*模块本身并不被编译进内核文件(zImage或者bzImage)
*可以根据需求,在内核运行期间动态的安装或卸载。
#include
#include
static int hello_init(void)
{
printk(KERN_WARNING"Hello,world!\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_INFO"Goodbye,world\n");
}
module_init(hello_init);
module_exit(hello_exit);
1、模块加载函数(必需)
安装模块时被系统自动调用的函数,通过module_init宏来指定。
2、模块卸载函数(必需)
卸载模块时被系统自动调用的函数,通过module_exit宏来指定。
在Linux2.6下编译一般使用makefile编译模块。
/*makefile*/
****************************************************************************
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KDIR := /lib/modules/2.6.18-53.e15/build
all:
make -C $(KDIR) M=$(PWD)modules
clean:
rm -f *.ko *.o *.mod.o *.symvers
endif
***************************************************************
内核模块时多个源文件的makefile
***************************************************************
ifneq ($(KERNELRELEASE),)
obj-m :=mymodule.o
mymodule-objs := main.o add.o
else
KDIR := /lib/modules/2.6.18-53.e15/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来查看要加载的模块,看是否还依赖于其他模块,如果是会首先找到这些模块,把它们加载到内核。
模块可选信息
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")
4>模块版本(可选)
MODULE_VERSION("V1.0")
5>模块别名(可选)
MODULE_ALIAS("a simple module");
6>模块参数
通过module_param指定模块参数用于在加载模块时传递参数模块。
module)param(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);
7>内核符号导出
/proc/kallsyms记录了内核中所有导出的符号的名字与地址。
EXPORT_SYMBOL(符号名)
EXPORT_SYMBOL_GPL(符号名)
其中EXPORT_SYMBOL_GPL只能用于包含GPL许可证的模块。
8>常见的问题
版本不匹配:disagrees about version of symbol struct_module insmod:error inserting 'hello.ko':-1 Invalid module format
解决办法:
1、使用modprobe--force-modversion强行插入
2、确保编译内核模块时,所依赖的内核代码版本等同于当前正在运行的内核。
可通过uname -r查看当前运行的版本号。
总结--对比应用
对比应用程序,内核模块具有以下不同:
医用程序是从头到尾执行任务,执行结束后从内存中消失。内核模块是现在内核中注册自己以便服务于将来的某个请求,然后它的初始化函数结束,此时模块任然存在于内核中,知道卸载函数调用,模块才从内核中国消失。
内核打印:
pringk是内核中出现最频繁的函数之一,通过printk与printf对比,将有助于理解。
不同点:printk在内核中使用,printf在应用程序中使用,printk允许根据严重程度,通过附加不同的“优先级”来对消息分类。
在中定义了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_LOGLEVEL优先级,它是一个在kernel/printk.c中定义的整数。在2.6.29内核中#define DEFAULT_MESSAGE_LOGLEVEL 4。
控制优先级配置
/proc/sys/kernel/printk
6 4 1 7
*Console_loglevel
*Default_message_loglevel
*Minimim_console_level
*Default_console_loglevel