高效学习Linux内核——内核模块编译

本文主要讲解什么是Linux内核,以及通过多张图片展示Linux内核的作用、功能及基本编程方法,以便于读者能快速理解什么是Linux内核,能看懂Linux内核。
拥有超过1300万行的代码,Linux内核是世界上最大的开源项目之一,但是内核是什么,它用于什么?

一、什么是linux内核模块?

内核是与计算机硬件接口的易替换软件的最低级别。它负责将所有以“用户模式”运行的应用程序连接到物理硬件,并允许称为服务器的进程使用进程间通信(IPC)彼此获取信息。

 Linux内核与硬件的关系

内核可以通过所谓的中断来管理系统的硬件。 当硬件要与系统接口时,会发出一个中断,中断处理器,从而对内核执行相同的操作。 为了提供同步,内核可以禁用中断,无论是单个中断还是全部中断。 但是,在Linux中,中断处理程序不是在进程上下文中运行,而是在不与任何进程相关联的中断上下文中运行,这种特殊的中断上下文仅是为了让中断处理程序快速响应单个中断然后最终退出而存在 。

linux内核整体非常庞大,包含组件特别多,当我们把需要的部分包含到内核中,直接把需要的所有功能都编译到内核中会导致内核很大,而且当需要新增或者删除功能,又要重新编译,非常麻烦,因此linux提供了模块(Modele)的机制。

可以把内核比喻成一个很长的火车,每个车厢就是一个内核模块,内核在运行这个火车就会一直在开动,但是我们想在火车开动的情况下增加新的车厢,这个时候就需要insmod,意思就是往这个长长的火车车厢增加一个内核模块。

模块的特点:不编译入内核镜像;一旦加载和内核其他部分完全一样。

为了使读者对模块有个感性认知,先看一下简单的HelloWorld模块,代码如下:

二、Linux内核模块组成结构

一个Linux内核模块主要由以下几个部分组成。

1)模块加载函数(必须)

当通过 insmod 或 modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。模块加载函数一般以__init标识声明

static int __init FuntionA(void)

{

}

module_init( FuntionA);


2)模块卸载函数(必须)

当通过 rmmod 命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。 模块卸载函数一般以__exit标识声明:

static void __exit  FuntionB(void)

{

}

module_exit( FuntionB );

通常来说,模块卸载函数要完成与模块加载函数相反的功能,如下所示。

a)若模块加载函数注册了XXX,则模块卸载函数应该注销XXX。

b)若模块加载函数动态申请了内存,则模块卸载函数应释放该内存。

c)若模块加载函数申请了硬件资源(中断,DMA通道,I/O端口和I/O内存等)的占用,则模块卸载函数应释   放这些硬件资源。

d)若模块加载函数开启了硬件,则卸载函数中一般要关闭硬件。


3)模块许可证声明(必须)

模块许可证(LICENSE)声明描述内核模块的许可权限,如果不声明 LICENSE,模块被加载时,将收到内核被污染的警告。大多数情况下,内核模块应遵循GPL 兼容许可权。

Linux2.6 内核模块最常见的是以MODULE_LICENSE(“Dual BSD/GPL”)语句声明模块采用BSD/GPL 双LICENSE


4)模块参数(可选)是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量

用“module_param(参数名,参数类型,参数读/写权限)”为模块定义一个参数

例如: module_param(num,int,S_IRUGO);


5)模块导出符号(可选)可以导出符号(symbol,对应于函数或变量),这样其它模块可以使用本模块中的变量或函数

可以使用如下宏导出符号到内核符号表:

EXPORT_SYMBOL(符号名);

EXPORT_SYMBOL_GPL(符号名);


6)模块作者等信息声明(可选)

我们可以使用MODULE_AUTHOR,MODULE_DESCRIPTION,MODULE_VERSION,MODULE_DEVICE_TABLE,MODULE_ALLAS

分别声明模块的作者,描述,版本,设备表和别名

例如:

MODULE_AUTHOR(author);

MODULE_DESCRIPTION(description);

三、Linux内核模块的编译

首先为HelloWorld模块编写MakeFile文件

该MakeFile文件应该与源码位于同一目录

在Makefile中,在obj-m := helloworld.o这句中,.o的文件名要与编译的.c文件名一致。 

如果一个模块包含多个.c文件(如file1.c、file2.c)则应该使用如下方式编写MakeFile:

Obj -m :=modulename.o

Modulename -obj :-file1.o file2.o

KERNELDIR ?= /usr/src/linux-headers-$(shell uname -r)指示当前linux系统内核的源码位置。 

1.在Makefile及helloworld.c所在目录下,直接make,成功后查看当前目录下有无helloworld.ko文件产生,有则内核模块生成成功。

2.使用insmod命令,把此内核模块程序加载到内核中运行。结合lsmod及管道命令,查看内核模块程序在内核中是否正确运行。

四、总结

本文主要讲解了linux内核模块的概念和基本编程方法、内核模块组成结构,由于linux设备驱动以内核模块的形式存在,因此了解本文内容是编写任何设备驱动的必需。