Linux菜鸟的内核学习笔记
by Echoa
本次总结内容分三部分:配置文件Makefile等,内核模块和文件系统。
1.Makefile,.config和Kconfig
(1)理论部分
Makefile,Kconfig和配置工具组成了Linux 2.6内核的配置系统。
其中Makefile定义了Linux内核的编译规则,它是大型项目开发的产物。Linux环境下的大型项目开发中,系统被分为很多模块,而这些模块一般会经历几次修改,而在修改后的编译过程中,由于某些文件中存在依赖关系,人工编译效率低(有些文件不需要重新编译)且易出错,Makefile文件便应运而生。Makefile文件定义了模块间的依赖关系,指定文件的编译顺序,以及编译所使用的命令。它和make命令使得项目的源程序文件可以自动编译,提高了软件开发效率。到此,再谈一下make,它是用来维护程序模块关系和生成可执行程序的工具,它可以根据程序模块的修改情况重新编译链接生成中间代码或最终的可执行程序,省去那些重复的不必要的编译工作,提高编译效率。
Kconfig给用户提供配置选择的功能。通常配置内核会有四种方法,make config(字符界面配置),make menuconfig(菜单界面配置),make xconfig(依赖QT),make gconfig(依赖GTK+)。make config 比较适合专业人员,像初学者比较适合make menuconfig,让我们重点关注一下它。当我们运行make menuconfig时,配置工具会首先分析与体系结构相对应的/arch/xxx/Kconfig文件(xxx为传入的arch参数),它里面包含了除一些与体系结构相关的配置项和配置菜单外,还通过source语句引入了一系列Kconfig,配置工具依据这些Kconfig包含的菜单和项目就可以描绘出一个分层结构。
例如当我们运行make zImagine、make bzImagine等生成映像的命时,会先检索顶层的Makefie(在arch/xxx/目录下的Makefile为顶层Makefile补充体系结构相关的信息),顶层Makefile的两个主要任务是:产生内核映像文件和内核模块。接着顶层Makefile会去递归地进入内核的各个子目录,然后分别调用子目录中的Makefile(这些Makefile记录编译目标),而进入哪些子目录取决于内核的配置。
当使用make menuconfig,make config命令时,生成的.config会在源码目录下记录哪些部分被编译入内核,哪些部分被编译为内核模块。
简而言之,它是保存内核配置结果的文件。当我们装上Linux系统时,第一次查看源码下的所有文件,会发现没有.config文件,那是因为从来没配置过内核。当你运行make menuconfig保存并退出时,再次查看就有这个文件了。
配置工具,包括配置命令解释器(对配置脚本中使用的命令进行解释)和配置用户界面(提供字符界面和图形界面),配置工具都是用脚本语言编写的。
(2)实践部分
在源码目录的drivers目录下新加test目录和其子目录,并将它添加到menuconfig配置菜单中。
这个过程主要有三步:
a.创建目录及编写相关的文件
b.编写Kconfig文件
c.编写Makefile文件
Test树形目录结构如下:
|--test
|--cpu
|--cpu.c
|--test.c
为了在内核菜单中添加它,必须编写相应的Makefile文件和Kconfig 文件,树形目录如下:
|--test
|--cpu
|--cpu.c
|--Makefile
|--test.c
|--Makefile
|--Kconfig
Kconfig和Makefile内容详见课本P62,为了使本层的Kconfig文件能起作用,我们需要修改父目录的Kconfig文件,加入source语句,如在test 父目录中加入source“drivers/test/Kconfig”,就可以通知上层配置文件本层的配置。
下面分析一下testx目录下的Kconfig文件
1#
2#TEST driver configuration/*#表示注释*/
3#
4menu"TEST Driver"/*配置菜单*/
5comment"TEST Driver"
6
7config CONFIG_TEST
8bool"Test support"/*bool型变量,只有y或n选项*/ 9
10config CONFIG_TEST_USER
11tristate"TEST user-space interface"/*三态变量,有y,n和M选项*/
12depends on CONFIG_TEST/*CONFIG_TEST_USER依赖CONFIG_TEST的选择,只有后者被选为y时才出现此选项*/ 13
14endmenu
正确配置好后,我们在源码下执行sudo make menuconfig后,在出现的Linux Kernel Configuration图形界面中选择Device Drivers,我们将会看到新加的Test driver菜单,
选择后可看到的菜单界面如下:
**TEST Driver***
[*]Test support
<>TEST user-space interface
其中[]表示bool型选项,<>表示三态选项,*表示y选项,编译进内核,空表示n选项,M表示编译成模块。由于有依赖关系,当Test support 被选为n时,显示的菜单界面如下:
***TEST Driver***
[]Test support
2.内核模块
(1)一个简单的c语言hello world程序
#include<stdio.h>
int main(void)
{
printf(“Hello world!\n”);
return0;
}
(2)一个最简单的Linux内核模块
1#include<linux/init.h>/*内核头文件*/
2#include<linux/module.h>/*内核头文件*/
3MODULE_LICENSE("Dual BSD/GPL");/*模块许可证声明*/
4static int hello_init(void)/*模块初始化函数*/
5{
6printk(KERN_ALERT"Hello world enter\n");
7return0;
8}
9
10static void hello_exit(void)/*模块退出函数*/
11{
12printk(KERN_ALERT"Hello world exit\n");/*内核中的输出函数*/
13}
14
15module_init(hello_init);/*模块加载函数,必须有*/
16module_exit(hello_exit);/*模块卸载函数,一般和加载函数成对出现,完成相反的功能*/
17
18MODULE_AUTHOR("WU");/*模块作者,可以不写*/
19MODULE_DESCRIPTION("A simple Hello world module");
/*模块描述,可不写*/
20MODULE_ALIAS("a simplest module")/*模块别名,可没有*/
由普通c语言程序到内核模块程序的转变:
可以看出内核模块程序长了很多,还有printf和printk两个看起来有点相似的函数。printk函数属于内核中
的函数,它只能在内核空间中执行它由输出级别和输出内容构成;而printf函数属于用户态函数,只能在用户空间执行,仅由输出内容构成。在编译连接时普通C程序会用到usr/include下的头文件如stdio.h,而内核中的头文件都在源码目录下的include下。printk函数的输出记录级定义在源码下的
include/linux/kernel.h头文件中,共有八个记录级,其中KERN_ALERT
表示在需要立即操作的情况下使用此消息,这个记录级下,输出内容会在系统日志中。
可以看出一个内核模块主要包括了四个部分:
a.模块头文件和模块许可证声明
b.模块初始化函数和模块退出函数
c.模块加载函数和模块卸载函数
d.模块的描述信息,如模块作者,模块功能的描述,模块别名等。
我们知道一般的C普通程序从主函数作为入口开始执行,最后由主函数作为出口退出,
而模块的执行就差别很大了。程序的模块加载函数只是告诉内核当你加载这个模块时从这儿作为入口开
始执行,执行时它会调用模块初始化函数。程序的模块卸载函数的作用是告诉内核当卸载这个模块时从这儿作为入口开始执行,执行时它会调用模块退出函数。真正的加载和卸载模块是通过insmod./模块名.ko命令和rmmod模块名命令执行的。具体到上面的模块,加载模块命令insmod hello.ko,卸载模块命令rmmod hello。我们还可以通过lsmod命令获得系统中已加载的模块和模块间的依赖关系。我们可以通过dmesg命令查看模块执行结果,其中dmesg为查看系统日志命令。
当然在执行加载模块命令之前,我们须给出编译规则,如下内容的Makefile文件。
linux退出vim命令1obj-m+=hello.o/*编译规则*/
2
3CURRENT_PATH:=$(shell pwd)/*shell命令显示当前目录,$()为引用变量结果*/
4
5LINUX_KERNEL:=$(shell uname-r)/*shell命令显示系统版本号*/
6
7
LINUX_KERNL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL) /*内核源码目录*/
8
9all:
10make-C$(LINUX_KERNL_PATH)M=$(CURRENT_PATH) modules/*编译成模块*/
11clean:
12make-C$(LINUX_KERNL_PATH)M=$(CURRENT_PATH)clean
/*清除编译结果*/
其实,我们可以只写第一行,但是这样写有比较好的移植性,make modules直接在Makefile文件中,省去了在终端运行命令的麻烦,而12行的编写也体现了良好的编程规范。
3.file_operation函数的作用和设备文件系统
文件系统也算Linux系统实现的一大核心功能,我们可以发现linux 下,一切皆文件,甚至包括磁盘设备,这不能不说是文件系统设计的成功和设计者设计fs时策略的高瞻远瞩。
由上图可以看出,应用程序通过系统调用访问VFS(虚拟文件系统),而VFS则通过file_operation函数访问各种文件,文件通过相应驱动访问到设备中存放的数据。由此可看到file_operation函数是字符设备驱动的核心。
在设备驱动程序设计中有两个重要的结构体,file结构体和inode结构体。file即文件结构体代表一个打开的文件,系统中每个打开的文件在内核空间中都有一个关联struct file。
它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论