Fork Me on GitHub

源码开放学ARM

LASO - Learn ARM with Source Open

首页         目录索引         资料下载         代码下载         联系作者        
下载PDF打印版本

内核模块程序结构

内核

在计算机科学中,内核是操作系统最基本的部分。 它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并且内核决定一个程序在什么时候对某部分硬件操作多长时间。直接对硬件操作是非常复杂的,所以内核通常提供一种硬件抽象的方法来完成这些操作。硬件抽象隐藏了复杂性,为应用软件和硬件提供了一套简洁,统一的接口,使程序设计更为简单。

宏内核结构在硬件之上定义了一个高阶的抽象界面,应用一组原语(或者叫系统调用)来实现操作系统的功能,例如进程管理,文件系统,和存储管理等等,这些功能由多个运行在核心态的模块来完成。 尽管每一个模块都是单独地服务这些操作,内核代码是高度集成的,而且难以编写正确。因为所有的模块都在同一个内核空间上运行,一个很小的bug都会使整个系统崩溃。然而,如果开发顺利,单内核结构就可以从运行效率上得到好处。

宏内核结构的例子:

  • 传统的UNIX内核,例如伯克利大学发行的版本
  • Linux内核
  • MS-DOS, Windows 9x (Windows 95, 98, Me)

微内核结构由一个非常简单的硬件抽象层和一组比较关键的原语或系统调用组成,这些原语仅仅包括了建立一个系统必需的几个部分,如 线程管理,地址空间和进程间通信等。 微核的目标是将系统服务的实现和系统的基本操作规则分离开来。例如,进程的输入/输出锁定服务可以由运行在微核之外的一个服务组件来提供。这些非常模块化的用户态服务器用于完成操作系统中比较高级的操作,这样的设计使内核中最核心的部分的设计更简单。一个服务组件的失效并不会导致整个系统的崩溃,内核需要做的,仅仅是重新启动这个组件,而不必影响其它的部分。

微内核结构的例子:

模块的由来

一般Linux kernel编译后生成一个vmlinux(非压缩格式)或者zImage(压缩格式)文件。 我们统称为kernel image。一般kernel image包含很多驱动,例如:键盘、鼠标、网卡等等。 当我们往kernel里添加一个新的驱动时,例如:声卡驱动。在调试阶段,我们会经常编译整个kernel,然后重启机器来调试驱动。

以上操作很耗时,很繁琐。因此Linux kernel开发者采用了一种的机制,将kernel分成静态的kernel image部分和可动态加载部分。 一般我们调试新的驱动就采用动态加载机制,这样我们无须编译整个kernel也无须重启机器。这就是模块。

$ make menuconfig

模块的定义

模块是Linux kernel 的一部分(通常是设备驱动)。它能被静态编译到kernel image里面,也可以动态加载。按需动态装入模块使内核空间达到最小。一旦模块被装入到Linux内核,那么它就像任何标准的内核代码一样成为内核的一部分,具有相同的权限和职责。

关于模块的几个描述如下:

  • 运行在kernel空间;
  • 不能使用printf等用户库的函数; * 可以动态加载和卸载。

我们来看一个模块的例子:

Makefile 文件

obj-m := hello.o

KDIR := /home/limingth/tiny210/src/linux-2.6.35.7

all:
	make -C $(KDIR)	SUBDIRS=$(PWD) 	modules

clean:
	rm -rf *.o *.ko *.mod.* *.cmd 
	rm -rf .*

hello.c 文件

#include <linux/module.h>
#include <linux/kernel.h>

MODULE_AUTHOR("AKAEDU");
MODULE_DESCRIPTION("module example ");
MODULE_LICENSE("GPL");

int __init akae_init (void)
{

	printk ("Hello, akaedu\n");
	return 0;
}

void __exit akae_exit (void)
{
	printk ("module exit\n");
	return ;
}

module_init(akae_init);
module_exit(akae_exit);

运行在kernel空间

因为模块是kernel的一部分,而不是一个普通的应用程序,所以它在kernel空间运行。

不能使用库函数

看上面例子中打印”hello, akaedu”。使用的是printk,而不是printf。因为库函数提供的接口是给应用程序使用的,并且运行空间在应用空间。所以类似于printf这样的库函数不能在模块中使用。

关于kernel 的API,我们可以访问 http://kernelbook.sf.net 我们可以通过这个网站查找哪些函数可以在模块中使用。当然最直接的还是通过查看kernel源代码,来获取这些函数。

模块与驱动的关系

模块并不全是驱动,例如我们看的第一个例子,它就是显示信息而已。但大多数模块都是驱动。 后面的课程我们会详细描述驱动模块的写法。

模块的基本元素

函数名

模块需要有自己的入口函数和退出函数。如下表所示:
int init_module(void)
{
	return 0;
} 

void cleanup_module(void)
{
	return;
}

注意函数名称的写法。 特别注意:return 0; 不可缺少!!! 但我们经常会看到kernel里的入口函数如下:

/* see linux/init.h */

module_init(akae_init);
module_exit(akae_exit);

并没有我们上面描述的函数名称,这是为什么呢?它怎么实现的呢? 答案很简单:其实 module_init 是一个宏定义。将akae_init函数别名为init_module。 见kernel代码的 include/linux/init.h 文件(此处不再重点讲解)。

详见 http://bbs.chinaunix.net/thread-1350305-1-1.html

在linux下的驱动函数都有module_init(x)和module_exit(x)在linux内核里都定义成一个宏例如:

#define module_init(x) \
        int init_module(void) __attribute__((alias(#x))); \
        extern inline __init_module_func_t __init_module_inline(void) \
        { return x; }

头文件

一般来说Linux kernel的模块都有如下的头文件:

#include <linux/module.h>
#include <linux/kernel.h>

函数修饰 initexit

这2个修饰符起到什么作用,如果不写对内核模块的插入和卸载有影响吗?
( 提示: cat /proc/kallsyms | grep akae )

上一节 | 目录索引 | 下一节

blog comments powered by Disqus