1) Linux内核的组成部分
Linux内核主要有进程调度(SCHED)、内存管理(MM)、虚拟文件系统(VFS)、网络接口(NET)和进程间通信(IPC)5个子系统组成。
2) 进程调度处于系统的中心位置,内核中的其他子系统都依赖它,因为每个子系统都需要挂起或回复进程。
3) 在设备驱动编程中,当请求的资源不能得到满足时,驱动一般会调度其他进程执行,并使驱动对应的进程进入睡眠状态,直到它请求的资源被释放,才会被唤醒而进入就绪状态。睡眠分为可被打断的睡眠和不可被打断的睡眠,两者的区别在于可被打断的睡眠在收到信号的时候会醒来。
4) 设备驱动中,如果需要几个并发执行的任务,可以启动内核线程,启动内核线程的函数为:
int kernel_thread(int , void * arg, unsigned long flags);
具体的使用环境及使用方式需要继续研究。
5) 内存管理的主要作用是控制多个进程安全地恭喜那个主内存区域。
6) 虚拟文件系统独立于各个具体的文件系统,是对各种文件系统的一个抽象,它使用超级块super block存放文件系统的相关信息,使用索引节点inode存放文件的物理信息,使用目录项dentry存放文件的逻辑信息。
super block, inode, dentry的工作方式需要继续研究。
7) 网络接口可分为网络协议和网络驱动程序,网络协议部分负责实现每一种可能的网络传输协议,网络设备驱动程序负责与硬件设备进行通信。
8) ARM处理器的7种工作模式:
- 用户模式(usr):大多数的应用程序运行在此模式下,在此模式下,某些被保护的系统资源是不能被访问的。
- 快速终端模式(fiq):用于高速数据传输或通道处理。
- 外部中断模式(irq):用于通用的中断处理。
- 管理模式(svc):操作系统使用的保护模式。uboot/裸机程序运行在此模式下。
- 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护。
- 系统模式(sys):运行具有特权的操作系统任务。
- 未定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件些处理器的软件仿真。
9) X86处理器由Ring0~Ring3四个不同的特权级。Ring0下可以执行特权级指令,对任何I/O设备都有访问权。Ring3则有很多操作限制,Linux系统使用了Ring0和Ring3这两级。在Linux系统中,内核可以进行任何操作,而应用程序则被禁止对硬件的直接访问和对内存的未授权访问。若使用x86处理器,则用户代码运行在Ring3,而系统内核代码则运行在Ring0。
10) initrd (Bootloader initialized RAM disk)是指由Bootloader初始化的内存盘。在Linux内核启动前,Bootloader会将存储介质中的initrd文件加载到内存,内核启动时会在访问真正的根文件系统前先访问该内存中的initrd文件系统。为使得启动时加载initrd,只需修改grub.conf引导配置文件,在最后添加“initrd /boot/initrd-2.6.15.5.img”。在bootloader配置了initrd的情况下,内核启动被分为两个阶段,第一阶段先执行initrd文件系统的文件,完成加载驱动模块等任务,第二阶段才会执行真正的根文件系统中的/sbin/init进程。第一阶段启动的目的是为第二阶段的启动扫清障碍,最主要的是加载根文件系统存储介质的驱动模块。
11) 编译内核:
make ARCH=arm CROSS_COMPILE=arm-linux- smdk2410_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux- zImage
make ARCH=arm CROSS_COMPILE=arm-linux- modules
指定体系结构为ARM,交叉编译器为arm-linux-gcc, 采用smdk_2410defconfig配置。
如果在源代码根目录的Makefile中将ARCH和CROSS_COMPILE直接定为arm和arm-linux-后,就没有必要每次编译的时候都指定体系结构和交叉编译器。
12) 使用make config、make menuconfig命令之后,会生成一个config配置文件(隐含在顶层的Makefile中),记录哪些部分被编译入内核、哪些部分被编译为内核模块。
13) 在Linux内核中增加程序需要完成以下三项工作:
- 将编写的源代码复制到Linux内核源代码的相应目录。
- 在目录的Kconfig文件中增加新源码对应项目的编译配置选项。
- 在目录的Makefile文件中增加对新源代码的编译条目。
14) 多文件模块的定义
如果一个模块由多个文件组成,这个时候应采用模块名加-objs后缀或者-y后缀的形式来定义模块的组成文件。例如:
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o bitmap.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o
模块的名字为ext2,由balloc.o和bitmap.o两个目标文件最终链接生成ext2.o直至ext2.ko文件,是否包括xattr.o取决于内核配置文件的配置情况。如果CONFIG_EXT2_FS的值是y也没有关系,在此过程中生成的ext2.o将被链接进built-in.o最终连接进内核。这里需要注意的一点是,该kbuild Makefile所在的目录中不能再包含和模块名相同的源文件如ext2.c/ext2.s。
或者写成如-objs的形式:
obj-$(CONFIG_ISDN) += isdn.o
isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o
15) 实例:在内核中新增驱动代码目录和子目录
目标:在内核源码drivers目录下为ARM体系结构新增如下用于test driver的树形目录:
--
--
-- .
-- .
-- .
-- .
-- .
-- .
在内核中增加目录和子目录,我们需要为相应的新增目录创建Kconfig和Makefile文件,而新增目录的父目录中的Kconfig和Makefile也需要修改,以便新增的Kconfig和Makefile文件能被引用。
在新增的test目录下,应该包含如下Kconfig文件:
#
# TEST driver configuration
#
menu "TEST Driver"
comment "TEST Driver"
config CONFIG_TEST
bool "TEST support"
config CONFIG_TEST_USER
tristate "TEST user-space interface"
depends on CONFIG_TEST
endmenu
由于TEST driver对于内核来说是新的功能,所以首先需要创建一个菜单TEST Driver;然后显示“TEST Support”等待用户选择;接下来判断用户是否选择了TEST Driver,如果是(CONFIG_TEST=y),则进一步显示子功能:用户接口与CPU功能支持;由于用户接口功能可以被编译成内核模块,所以这里的询问语句使用了tristate。
为了使这个Kconfig文件能起作用,需要修改arch/arm/Kconfig文件,增加一下内容:
source "drivers/test/Kconfig"
脚本中的source意味着引用新的Kconfig文件。
在新增的test目录下,应该包含如下Makefile文件:
# drivers/test/Makefile
#
# Makefile for the TEST
#
obj-$(CONFIG_TEST) += test.o test_queue.o test_client.o
obj-$(CONFIG_TEST_USER) += test_ioctl.o
obj-$(CONFIG_PROC_FS) += test_proc.o
obj-$(CONFIG_TEST_CPU) += cpu/
该脚本根据配置变量的取之构建obj-*列表。由于test目录中包含一个子目录cpu,当CONFIG_TEST_CPU=y时,需要将cpu目录加入列表。
test目录中的cpu子目录也需要包含如下Makefile文件:
# drivers/test/cpu/makefile
#
# Makefile for the TEST CPU
#
obj-$(CONFIG_TEST_CPU) += cpu.o
为了使整个test目录能够被编译命令作用到,test目录父目录中的Makefile文件也需要增加如下脚本:
obj-$(CONFIG_TEST) += test/
在drivers/Makefile中加入obj-$(CONFIG_TEST) += test/, 使得用户在进行编译时能够进入test目录。
增加了Kconfig和Makefile文件之后的新的test树形目录如下所示:
--
--
-- .
--
-- .
-- .
-- .
-- .
-- .
--
--
16) 在Linux2.6 内核中,可以使用request_module(const char *fmt, …)函数加载内核模块,驱动开发人员可以通过调用:
request_module(module_name);
或
request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev));
来加载其他内核模块。
17) 在Linux内核中,所有标识为_
init的函数在连接的时候都放在.init.text这个区段内,此外,所有的 init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些 _init函数,并在初始化完成后释放init区段(包括.init.text, .initcall.init等)。
参考资料:《linux设备驱动开发详解》 宋宝华