内核定制与编译准备工作

来源:互联网 发布:安卓系统怎么关闭网络 编辑:程序博客网 时间:2024/05/22 20:20

1   CentOS5.X/6.X平台内核定制与编译

1.1 为什么选择centos

centos是redhat红帽公司企业发行版rhel系列的开源发行版本,它也是面向企业服务器架构的,由于紧跟redhat rhel商业版本的步伐,centos的成熟度/稳定性都有保证,特别是能够全面并及时支持新型的驱动著称,更为重要的是centos发行版本更适合内核开发者进行内核开发,提供了非常便捷的开发环境,基本上不需要配置即可立刻开始内核开发。当然市面上还有ubuntu,suse等一批优秀的发行版本,有的适用于pc桌面用户,有的不具备灵活快捷的内核开发支撑,我们推荐在此次系列课程培训中使用centos发行版本,当然只要我们具备了linux系统扎实的基础和经验,大家一定会快速的无缝的切换到其它发行版本的使用上,这一块大家不用太过纠结和担心,内核是一样的相通的,发行版本只是提供了桌面化的窗口使用环境和系统服务。

1.2 为什么centos5.2和centos6.2都要介绍

当前centos重点推进维护的是5.x/6.x系列,有的企业环境长时间内还在使用5.x系列系统,因为5.x非常成熟稳定,有的则紧跟步伐shiyong 6.x系列,我们的系列课程从5.x/6.x上各选择一款版本进行介绍,一个方面的考虑是5.x/6.x在内核编译上细节上略有不同,通过比较式的学习大家能够更全面的掌握编译技能,同时我们在后续过滤块设备驱动开发实战的课程中也有所考虑,5.x/6.x列列的内核版本是不同的,通过在不同的内核版本上进行驱动开发实战可以让大家实际感受和练习一下如何进行内核代码的移植,比如将自己写好的在5.2版本内核上运行的驱动移植到6.2内核上运行。

1.3 什么是内核编译与定制

操作系统就是内核,内核也是一堆程序代码,这堆代码一样要通过编译器编译成二进制代码后才能在机器上运行,内核代码又是很庞大的,为了编译它,优秀的内核开发人员精心设计了编译步骤,并尽量封装简化过程,让其操作方便,这就是内核编译,也是我们上册课程主要跟大家分享的。

内核定制,顾名思义就是有选择性的加入或者拿掉内核的功能或者模块,平常老听到所谓的内核裁减其实就是这个意思,举个例子来说,就跟汽车选择配件一样,发动机引擎是必不可少的,真皮座椅是可选择的,定制也就是这样,聪明的内核作者们精心设计了一个简单可操作的界面让你进行定制选择,接下来我们赶紧揭开这个面纱吧。

1.4 热热身,作点准备工作- 写一个简单的内核模块

我们在实战内核编译前,先热热身,大家直接来写一个内核代码模块,先体会一下如何进行内核代码开发,这个例子非常简单,并且在后续章节介绍相关功能时都会应用到此模块。

我们首先贴一下内核代码源文件talk.c

 

#include<linux/module.h>

 

static int__init talk_init(void)

{

      printk(”Welcome to Trace the linuxkernel!\n");

      return 0;     

}

staticvoid __exit talk_exit(void)

{

      printk("mod exitsuccessfuly!\n");

}

module_init(talk_init);

module_exit(talk_exit);

MODULE_LICENSE("GPL");

 

大家可以实际在自己虚拟机上把这几行代码敲一下,在这里我们先不做过多介绍这个代码的解读,里面有很多奇怪的名字或者关键字,有些是内核特定的宏定义,大家先认识一下,先不要尝试一下子全部理解,我们在下册内核块设备开发时会慢慢告诉大家如何理解这个代码,现在先让我们照葫芦画瓢,动手写写,主要是为接下来我们的内核编译作点准备工作,然后我们再写一个Makefile文件,来指导我们如何编译talk.c文件内核代码,Makefile如下:

obj-m:=talk.o

KERNELDIR?= /lib/modules/$(shell uname -r)/build

PWD := $(shell pwd)

default:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:

rm -rf *.o *~ core .depend .*.cmd *.ko*.mod.c *.tmp_versions *.symvers .tmp_versions

 

好了,至此我们写好了一个简单的内核模块,执行make即可把talk.c编译为内核模块,然后执行insmod talk.ko,我们就把talk.ko插入内核中了,dmesg敲一下看看,最后输出信息是否打出了talk_init函数中的打印的信息,然后执行rmmod talk再dmesg看看是否再最后又打出了talk_exit函数中打印的信息。注意这两个函数很简单,都是调用printk打印信息,并且要注意这不同于我们写应用程序代码中使用的printf函数,而是换了个名字叫printk,这是内核的打印信息函数,这也是我们遇到的第一内核API函数,insmod和rmmod命令是用于加载和卸载内核模块用的。下面针对内核模块的构造和运行进行了几个知识点的分析,大家可以预先熟悉一下,在下册块设备驱动实战中会继续剖析。

 

   关于头文件

      大部分内核代码中都要包含相当数量的头文件,以便获得函数、数据类型和变量的定义。有些头文件是专门用于模块的,必须包含在每个可装载的模块中。所有的模块代码中都包含下面这代码:

      #include <linux/module.h>

      module.h包含有可装载模块需要的大量符号和函数的定义。

   许可证

尽管不是严格要求的,但模块应该指定代码所使用的许可证,需要包含:

MODULE_LICENSE("DualBSD/GPL");

表示模块遵行公共许可,一般来说都要加上,这样不会出现警告。"Dual BSD/GPL"是BSD/GPL双重许可证。

函数printk在Linux内核中定义,模块能够调用它是因为在insmod函数装入模块后,模块就连接到了内核,因而可以访问内核的公用符号。

 

   编译过程- Makefile

该makefile文件被执行了两次,当命令行执行make时调用此makefile,此时KERNELRELEASE变量尚未设置,KERNELDIR=选项可以定位内核源码目录,在找到内核源代码树之后,这个makefile会调用default:目标,这个目标会先读源码目录的顶层Makefile文件,M=选项让该makefile在构造modules目标之前返回到模块源代码目录,此时会第二次读取该目录下的Makefile文件,modules目标指向Makefile中obj-m变量设定的模块,然后内核的makefile负责真正构造模块。

 

   初始化函数

模块的初始化函数负责注册模块所提供的任何设施。设施指的是一个可以被应用程序访问的新功能,它可能是一个完整的驱动程序或者仅仅是一个新的软件抽象。初始化函数的实际定义通常如下:

      static int __intinitialization_function(void)

      {

             /*初始化代码*/

      }

      module_init(initialization_function);

初始化函数应该被声明为static,因为它在特定文件之外毫无意义。而一个模块函数如果要对内核其他部分可见,必须被显示导出方可。__init标记表示该函数仅在初始化期间使用。在模块被卸载之后,模块装载器会将初始化函数扔掉,释放内存。因此不要在结束初始化后仍要使用的函数(或者数据结构)上使用__init标记(和__initdata)两个标记。module_init的使用是强制性的。这个宏会在模块的目标代码中增加一个特殊的段,用于说明内核初始化函数所在的位置。没有这个定义,初始化函数永远不会被调用。

 

   模块清除函数

每个重要的模块都需要一个清除函数,该函数在模块被移除前注销接口并向系统中返回所有资源。该函数定义如下:

      static void __exit cleanup_function(void)

      {

             /*清除代码*/

      }

      module_exit(cleanup_function);

清除函数没有返回值,因此被声明为void。__exit标记代码仅用于模块卸载。如果模块被直接内嵌到内核中,或者内核的配置不允许卸载模块,则被标记为__exit的函数将被简单地丢弃。因此被标记为__exit的函数只能在模块被卸载或者系统关闭时被调用,其他的任何用法都是错误的。如果一个模块未定义清除函数,则内核不允许卸载该模块。