- linux体系结构
- 用户空间
- 内核空间
- Linux划分用户控件与内核空间的作用
现代CPU通常实现了不同的工作模式,具有不同级别的硬件权限.
Linux系统利用CPU这一特性,使用其中的两级来分别运行内核程序与应用程序,这样使得操作系统本身得到充分的保护.
内核空间与用户空间是程序执行的两种不同状态,通过系统调用和硬件中断能够完成从用户空间到内核空间的转移.
- Linux内核架构
- System Call Interface (SCI) - 系统调用接口
- Process Management (PM) - 进程管理
- Memory Management (MM) - 内存管理
- Network Stack - 网络协议栈
- Virtual File System (VFS) - 虚文件系统
- Device Drivers (DD) - 设备驱动
- Arch - CPU相关代码
-
Linux内核源码下载地址
www.kernel.org
-
Linux内核源码目录结构
- arch - (architecture)
内核所支持的每种CPU体系,在该目录下都有对应的子目录。每个CPU的子目录,又进一步分解为boot(系统引导),mm(内存管理),kernel(系统调用)等子目录.
- block - 部分块设备驱动程序
- crypto - 加密、压缩、CRC校验算法
- documentation - 内核文档
- drivers - 设备驱动程序
- fs - 各种文件系统的实现代码
- include - 内核所需要的头文件
与平台无关的头文件在
include/linux
子目录下;与平台相关的头文件在相应的子目录下;
- init - 内核初始化代码
- ipc - 进程间通信的实现代码
- kernel - 内核大多数关键的核心功能实现代码
- lib - 库文件代码
- mm - 用于实现内存管理中与体系结构无关的部分
- net - 网络协议的实现代码
- samples - 一些内核编程范例
- scripts - 配置内核的脚本
- security - SELinux的模块
- sound - 音频设备的驱动程序
- usr - cpio命令实现(用于制作根文件系统时, 把文件系统与内核放到一起)
- virt - 内核虚拟机
- arch - (architecture)
-
Linux内核具有可定制的优点,具体步骤如下:
-
清除临时文件、中间文件和配置文件
make clean
- remove most generated files but keep the config filesmake mrproper
- remove all generated files and config filesmake distclean
- mrproper + remove editor backup and patch files
-
确定目标系统的软硬件配置情况
比如CPU的类型、网卡的型号、所需支持的网络协议等.
-
使用如下命令之一配置内核:
配置选项说明:
内核配置通常在一个已有的配置文件基础上,通过修改得到新的配置文件, Linux内核提供了一系列可供参考的内核配置文件,位于
arch/<cpu>/configs
.make config
: 基于文本模式的交互式配置make menuconfig
: 基于文本模式的菜单型配置(推荐),使用方法如下:- 使用方向键在各选项间移动;
- 使用
Enter
键或快捷键(选项上的高亮字母)进入下一层选单; - 在括号中按
y
将这个项目编译进内核中,按m
编译为模块,按n
为不选择,按h
可显示该选项的帮助信息; 按空格可在y
m
n
三者间切换; 按Esc
将返回到上层选单;
make oldconfig
: 使用已有的配置文件(.config),但会询问新增的配置选项.make xconfig
: 图形化的配置(需安装图形化系统)
-
编译内核
使用如下命令之一编译内核 编译好的内核位于
arch/<cpu>/boot/
目录下make zImage
- X86平台下只能用于小于512K的内核make bzImage
make zImage V=1
- 获取详细编译信息make bzImage V=1
-
编译内核模块 -
make modules
-
安装内核模块 -
make modules_install
-
制作init ramdisk - (启动Linux内核专用的一个被压缩过的小型根文件系统)
mkinitrd initrd-$version $version
例:
mkinitrd initrd-2.6.29 2.6.29
$version 可以通过查询
lib/modules
下的目录得到 -
内核安装(X86)
- 拷贝内核文件至指定目录
cp arch/x86/boot/bzImage /boot/vmlinuz-$version
- 拷贝ramdisk至指定目录
cp $initrd /boot/
-
设置启动项,并将启动项关联内核文件及ramdisk
修改
/etc/grub.conf
或者/etc/lilo.conf
-
- 什么是内核模块?
Linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用需要的组件呢?
- 把所有的组件都编译紧内核文件(zImage/bzImage), 缺点:
- 生成的内核文件过大;
- 如果要添加或删除某个组件,需要重新编译整个内核.
- 把部分组件以内核模块的方式编译调用,特点:
- 模块本身并不被编译紧内核文件
- 可根据需求,在内核运行期间动态的安装/卸载
- 内核模块程序结构
- 模块加载函数(必需) -
module_init
宏指定 - 模块卸载函数(必需) -
module_exit
宏指定
- 模块加载函数(必需) -
// 范例:
#include <linux/init.h>
#include <linux/module.h>
// 模块加载函数(必需)
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);
- 内核模块的编译 - 通常使用
makefile
- 如何编写
makefile
, 使得编译中增加内核模块?
// 1. 内核模块由一个源文件构成
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KDIR := /lib/modules/2.6.29/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers
endif
// 2. 内核模块由多个源文件构成
// --- main.c ---
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jacey Wu");
MODULE_DESCRIPTION("Hello World Module");
MODULE_ALIAS("a simplest module");
extern int add(int a, int b);
static int __init hello_init()
{
printk("Hello World!\n");
add(1, 2);
return 0;
}
static void __exit hello_exit()
{
printk("<7>hello <0>exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
// --- main.c ---
// --- add.c ---
int add(int a, int b)
{
return a+b;
}
// --- add.c ---
// --- makefile ---
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
hello-objs := main.o add.o
else
KDIR := /lib/modules/2.6.29/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers
endif
// --- makefile ---
- 内核模块的安装与卸载
- 加载
insmod
- 卸载
rmmod
- 查看
lsmod
- 加载
modprobe
- 加载
modprobe
与insmod
区别在于, 前者会根据文件/lib/modules/<$version>/modules.dep
来查看要加载的模块,是否依赖于其他模块,如果是,将会首先找到这些模块,把它们先加载到内核.
- 内核模块可选信息
- 许可证申明 - 宏
MODULE_LICENSE
- 作者申明 - 宏
MODULE_AUTHOR
- 模块描述 - 宏
MODULE_DESCRIPTION
- 模块版本 - 宏
MODULE_VERSION
- 模块别名 - 宏
MODULE_ALIAS
- 模块参数 - 宏
module_param(name, type, perm)
- name - 模块参数的名称(变量名).
- type - 模块参数的类型.
bool
:布尔型;int
:整型;charp
:字符串型; - perm - 模块参数的访问权限.
S_IRUGO
: 任何用户都对/sys/module
中出现的该参数具有读权限;S_IWUSR
: 允许root用户修改/sys/module
中出现的该参数;
- 许可证申明 - 宏
// --- param.c ---
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
static char* name = "Jacey Wu";
static int age = 100;
module_param(age, int, S_IRUGO);
module_param(name, charp, S_IRUGO);
static int hello_init(void)
{
printk(KERN_EMERG" Name:%s\n", name);
printk(KERN_EMERG" Age:%d\n", age);
return 0;
}
static void hello_exit(void)
{
printk(KERN_INFO" Module Exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
// --- param.c ---
- 内核符号导出
/proc/kallsyms
记录了内核中所有导出的符号的名字与地址- 为什么要导出内核符号? - 供其他内核模块使用这些符号(模块之间的依赖关系)
- 内核符号的导出使用:
EXPORT_SYMBOL()
EXPORT_SYMBOL_GPL()
- 只能用于包含GPL许可证的模块.
// hello.c
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jacey Wu");
MODULE_DESCRIPTION("Hello World Module");
MODULE_ALIAS("a simplest module");
extern int add_integar(int a, int b);
extern int sub_integar(int a, int b);
static int __init hello_init()
{
int res = add_integar(1, 2);
return 0;
}
static void __exit hello_exit()
{
int res = sub_integar(2, 1);
}
module_init(hello_init);
module_exit(hello_exit);
// calculate.c
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
int add_integar(int a, int b)
{
return a+b;
}
int sub_integar(int a, int b)
{
return a-b;
}
static int __init sym_init()
{
return 0;
}
static int __exit sym_exit()
{
}
module_init(sym_init);
module_exit(sym_exit);
EXPORT_SYMBOL(add_integar); // 内核符号导出
EXPORT_SYMBOL(sub_integar); // 内核符号导出
-
常见问题
- 版本不匹配:
- 原因: 内核模块的版本由其所依赖的内核代码版本所决定,在加载内核模块时,
insmod
程序会将内核模块版本与当前正在运行的内核版本比较,如果不一致将会报错. - 解决方法:
- 使用
modprobe --force-modversion
强行插入 - 确保编译内核模块时,所依赖的内核代码版本与当前正在运行的内核版本一致.
- 可通过
uname -r
查看当前运行的内核版本
- 使用
- 原因: 内核模块的版本由其所依赖的内核代码版本所决定,在加载内核模块时,
- 版本不匹配:
-
内核模块与应用程序的区别:
- 应用程序是从头到尾执行任务,执行结束后从内存中消失;
- 内核模块是先在内核中注册自己以便服务于将来的某个请求,然后它的初始化函数结束,此时模块仍然存在于内核中,知道卸载函数被调用,模块才从内核中消失;
-
内核打印 -
printk
与printf
区别:printk
在内核中使用;printf
在应用程序中使用;printk
允许根据严重程度,通过附加不同的"优先级"来对消息分类; 按照优先级递减的顺序分别为:KERN_EMERG
- 用于紧急消息KERN_ALERT
- 需要立即行动的消息KERN_CRIT
- 严重情况KERN_ERR
- 错误情况KERN_WARNING
- 有问题的警告KERN_NOTICE
- 正常情况,但是仍然值得注意KERN_INFO
- 信息型消息KERN_DEBUG
- 用作调试信息
没有指定优先级的
printk
默认使用DEFAULT_MESSAGE_LOGLEVEL
优先级, 是一个在kernel/printk.c
中定义的整数控制台优先级配置
/proc/sys/kernel/printk 6 4 1 7
Console_loglevel
Default_message_loglevel
Minimum_console_level
Default_console_loglevel