内核开发基础

来源:互联网 发布:linux定时重启机器 编辑:程序博客网 时间:2024/05/17 04:14
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内核源代码采用树形结构进行组织,非常合理地把功能相关的文件都放在同一个子目录下,使得程序更具有可读性。内核源代码下载地址:www.kernel.org

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/<cpu>/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<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);

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允许根据严重程度,通过附加不同的“优先级”来对消息分类。

在<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_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



http://blog.chinaunix.net/uid-28732854-id-3848053.html
原创粉丝点击