模块的动态加载和系统配置.ppt

上传人:牧羊曲112 文档编号:6422344 上传时间:2023-10-29 格式:PPT 页数:75 大小:337.99KB
返回 下载 相关 举报
模块的动态加载和系统配置.ppt_第1页
第1页 / 共75页
模块的动态加载和系统配置.ppt_第2页
第2页 / 共75页
模块的动态加载和系统配置.ppt_第3页
第3页 / 共75页
模块的动态加载和系统配置.ppt_第4页
第4页 / 共75页
模块的动态加载和系统配置.ppt_第5页
第5页 / 共75页
点击查看更多>>
资源描述

《模块的动态加载和系统配置.ppt》由会员分享,可在线阅读,更多相关《模块的动态加载和系统配置.ppt(75页珍藏版)》请在三一办公上搜索。

1、第9章 模块的动态加载和系统配置,第9章 模块的动态加载和系统配置,本章介绍了Linux内核动态加载功能模块的工作原理。分析了 Linux 内核中的系统配置结构,解释了 Makefile 和配置文件的格式以及配置语句的含义。最后给出一个简单的例子,说明如何将自行开发的代码加入到 Linux 内核中。,9.1 模块的动态加载,操作系统通常由内核和一些系统服务程序(命令解释、库文件、链接和编译程序等)组成。内核是操作系统的灵魂,它为用户进程提供了一个虚拟机接口。用户进程可以并行运行、公平的占用系统资源而互不干扰。,9.1 模块的动态加载,操作系统通常由内核和一些系统服务程序(命令解释、库文件、链接

2、和编译程序等)组成。内核是操作系统的灵魂,它为用户进程提供了一个虚拟机接口。用户进程可以并行运行、公平的占用系统资源而互不干扰。从结构上来分,可将操作系统分为微内核结构和单块结构两类。Windows NT 和 MINIX 是典型的微内核操作系统,而Linux 则是单块结构的操作系统。微内核结构可方便地在系统中添加新的组件,而单块结构却不容易做到这一点。为此,Linux系统使用可动态加载和卸载的内核模块(Loadable Kernel Modules,LKMs),可方便地在内核中添加新的组件或卸载不再需要的内核组件。Linux使用insmod来显式加载内核模块,使用rmmod来卸载模块。同时内核

3、自身也可以请求内核后台进程kerneld来加载与卸载模块。Linux模块大多数是设备驱动程序以及伪设备驱动程序模块,如网络设备和文件系统等。,动态可加载代码的优点是可以让内核保持很小的尺寸并非常灵活。模块机制可以无需重构内核并频繁重新启动来尝试运行新内核代码。用户可以根据自己系统的需要构筑自己的私有内核。Linux源码的公开更是为改造其内核、重建有特殊要求的操作系统提供了可能。,模块必须能够找到其需要使用的内核资源。例如模块需要分配内存时,要调用内核的内存分配例程kmalloc()。但在构造模块时并不知道kmalloc()在内存中何处,这样内核必须在使用这些模块前修改模块中对 kmalloc(

4、)的引用地址。内核在其内核符号表中维护着一个内核资源链表这样当加载模块时它能够解析出模块 中对内核资源的引用。Linux还允许存在模块堆栈,它在模块之间相互调用时使用。例如,因为VFAT(VirtuaI File Allocation Table)文件系统是从FAT(File Allocation Table)文件系统中扩展而来,VFAT文件系统模块可能需要FAT文件系统模块的服务。某个模块对其他模块的服务或资源的需求类似于模块对内核本身资源或服务的请求。不过此时所请求的服务是来自另外一个事先已加载的模块。当加载模块时,内核就把新近加载模块输出的所有资源和符号添加到内核符号表(Kernel-S

5、ymbol-Table)中。,在/proc/ksyms.里面的每一个表项代表着一个公共的内核符号,这就是内核符号表。这些内核符号是可以被LKM引用的。LKM中所存取的每一个符号(像函数名)也会被列在这个文件里面。在该文件中可以看到LKM到底可以调用那些函数。当试图卸载某个模块时,内核需要知道此模块是否已经没有被使用,同时它需要有种方法来通知此将卸载模块。模块必须能够在从内核种删除之前释放其分配的所有系统资源,如内核内存或中断。当模块被卸载时,内核将从内核符号表中删除所有与之对应的符号。,但是,内核模块的引入也带来了如下问题:,但是,内核模块的引入也带来了如下问题:,(1)有可能同时带来与内核模

6、块相关的性能与内存损失。可加载模块的代码一般较长,且额外的数据结构可能会占据一些内存,对内核资源的间接使用也可能带来效率方面的问题。对系统性能和内存利用有负面影响;,但是,内核模块的引入也带来了如下问题:,(1)有可能同时带来与内核模块相关的性能与内存损失。可加载模块的代码一般较长,且额外的数据结构可能会占据一些内存,对内核资源的间接使用也可能带来效率方面的问题。对系统性能和内存利用有负面影响;(2)一旦Linux模块被加载,则和普通内核代码一样成为内核的一部分,具有与其他内核代码相同的权限与职责。因此,Linux内核模块也可以象所有内核代码和设备驱动一样使内核崩溃;,但是,内核模块的引入也带

7、来了如下问题:,(1)有可能同时带来与内核模块相关的性能与内存损失。可加载模块的代码一般较长,且额外的数据结构可能会占据一些内存,对内核资源的间接使用也可能带来效率方面的问题。对系统性能和内存利用有负面影响;(2)一旦Linux模块被加载,则和普通内核代码一样成为内核的一部分,具有与其他内核代码相同的权限与职责。因此,Linux内核模块也可以象所有内核代码和设备驱动一样使内核崩溃;(3)为了内核模块访问所有内核资源,内核必须维护符号表,并在装入和卸载模块时修改这些符号表;,但是,内核模块的引入也带来了如下问题:,(1)有可能同时带来与内核模块相关的性能与内存损失。可加载模块的代码一般较长,且额

8、外的数据结构可能会占据一些内存,对内核资源的间接使用也可能带来效率方面的问题。对系统性能和内存利用有负面影响;(2)一旦Linux模块被加载,则和普通内核代码一样成为内核的一部分,具有与其他内核代码相同的权限与职责。因此,Linux内核模块也可以象所有内核代码和设备驱动一样使内核崩溃;(3)为了内核模块访问所有内核资源,内核必须维护符号表,并在装入和卸载模块时修改这些符号表;(4)有些模块要求利用其他模块的功能,因此,内核要维护模块之间的依赖性。(5)内核必须能够在卸载模块时通知模块,并且要释放分配给模块的内存和中断等资源;(6)内核版本和模块版本的不兼容,也可能导致系统崩溃,因此,严格的版本

9、检查是必需的。,模块的加载,模块的加载,有两种方法可用来加载模块:(1)利用 insmod 命令手工将模块插入内核;,模块的加载,有两种方法可用来加载模块:(1)利用 insmod 命令手工将模块插入内核;(2)由内核在必要时加载模块,称为“需求加载”。,当内核发现有必要加载某个模块时,如用户安装了内核中不存在的文件系统时,内核将请求内核后台进程(kerneld)准备加载适当的模块。这个内核后台进程仅仅是一个带有超级用户权限的普通用户进程。当系统启动时它也被启动并为内核打开了一个进程间通讯(IPC)通道。内核可以利用该通道向Kerneld进程发送任务的执行请求。Kerneld进程的主要功能是加

10、载和卸载模块,另外,该进程也负责其他一些任务,例如打开和关闭 PPP 链接等。kerneld自身并不执行这些任务,它通过某些程序如insmod来做此工作。因此,该进程实际是代表内核进行调度的代理。,执行 insmod 命令时,必须指定要加载模块的位置;对需求加载的内核模块,通常保存在/lib/modules/kernel-version。和系统的其他程序一样,内核模块实际是经连接的目标文件,但模块是可重定位的,也就是说,为了让装入的模块和已有的内核组件之间可以互相访问,模块不能连接为从特定地址执行的映像文件。模块可以是 a.out 或 elf 格式的目标文件。insmod 利用一个特权系统调用

11、,可找到内核的导出符号表,符号成对出现,一个是符号名称,另外一个是符号的值,例如符号的地址。内核维护一个由 module_list 指针指向的 module 链表,其中第一个 module 数据结构保存有内核的导出符号表(见图 9.1)。并不是所有的内核符号均在符号表中导出,而只有一些特殊的符号才被添加到符号表中。例如,“request_irq”是一个导出符号,它是一个内核例程,可由驱动程序申请控制某个特定的系统中断。利用 ksyms 命令或查看/proc/ksyms 文件内容,可非常方便地看到所有的内核导出符号及其符号值。利用 ksyms 命令,不仅可以看到内核的所有符号,也可以看到只由以加

12、载模块导出的符号。insmod 命令将模块读到它本身的虚拟内存中,然后利用内核导出的符号表,修正尚未解析的对内核例程的引用。这种修正实际是对模块在内存中的映像进行修正,insmod 将符号的地址写入模块中适当的位置而实现修正。,图 9.1 装入 VFAT 和 FAT 之后的内核模块表,insmod 命令修正模块对内核符号的引用之后,再次利用特权系统调用请求内核分配足够的物理内存空间保存新的模块。内核将分配新的 module 数据结构以及足够的内核内存,并将新模块添加在内核模块表的末尾。新的内核模块标记为Uninitialized(未初始化)。图 9.1是装入 VFAT 和 FAT 模块之后的内

13、核模块表。图中并没有表示出第一个模块,它实际是一个伪模块,仅仅用来保存内核的导出符号表。利用 lsmod 命令可列出所有已加载的内核模块以及它们的内在依赖性。内核为新模块分配的内核内存映射到 insmod 进程的地址空间中,这样,insmod 就可以将模块复制到新分配的内存中。insmod 还对模块进行重新定位,经重定位之后,新的模块就可以从新分配的内核地址开始运行了。显然,模块不能期望自己能够在不同的 Linux 系统,或前后两次装入时被加载到相同地址,重定位操作可通过对模块映像中适当的地址进行修正而解决这一问题。,新的模块也要向内核导出符号,由 insmod 建立相应的符号表。每个内核模块

14、必须包含模块的初始化和清除例程,这些例程作为每个模块均具备的例程而不被导出,但 insmod 必须知道它们的地址,并将地址传递给内核。insmod 同样利用特权系统调用将模块的初始化和清除例程地址传递给内核。新的模块添加到内核之后,它必须更新内核符号集并修改使用新模块的模块。由其他模块依赖的模块必须在自身符号表的末尾维护一个引用表,并指向其他模块的 module 结构。例如,图 9.1 表明 VFAT 文件系统模块依赖于 FAT 文件系统模块,因此,FAT 模块包含一个对 VFAT 模块的引用,该引用在装入 VFAT 模块时添加。,内核成功调用模块的初始化例程之后继续模块的安装,最后,模块状态

15、被设置为 Running(运行)。模块的清除例程保存在 module 数据结构中,在卸载模块时由内核调用。,9.1.2 模块的卸载,和模块的加载类似,可利用 rmmod 命令手工卸载模块,当对需求加载的模块则由 kerneld 在不再需要时自动卸载。每次 kerneld 的空闲定时器到期时,它会利用系统调用将当前不再使用的需求加载模块从内核中移走。启动 kerneld 时指定该定时器的时间,通常的时间为 180 秒。,如果内核的其他部分依赖于装入的模块时,该模块不能卸载。例如,如果挂装了 FAT 文件系统,则不能卸载已装入的 FAT 文件系统模块。lsmod 命令的输出会显示已安装模块的使用计

16、数,例如:Module:#pages:Used by:msdos 5 1 vfat 4 1(autoclean)fat 6 2(autoclean),使用计数就是依赖于该模块的内核实体个数。模块的使用计数保存在模块映像的第一个长整型中。但是,这一长整型中还包含有 AUTOCLEAN 和 VISITED 标志。这两个标志均由需求加载的模块使用。具有 AUTOCLEAN 标志的模块是系统认为可以自动卸载的模块。具有 VISITED 标志的模块表明正由其他内核组件使用,当任何其他内核组件使用该模块是设置该标志。当 kerneld 请求系统移走不使用的需求加载模块时,系统首先寻找可以移走的模块,但系统

17、只查看标记为 AUTOCLEAN,并且状态处于 RUNNING 的模块。如果上述模块的 VISITED 标志被清除,则系统将卸载该模块,否则系统会清除 VISITED 标志并查看下一个模块。假定某个模块是可卸载的,则系统调用其清除例程释放分配该模块的内核资源。相应的 module 数据结构被标志为 DELETED 并从内核模块链表中断开。所有由该模块依赖的模块,系统会修改它们的引用表以便取消依赖性。最后,系统释放模块的内核内存。,9.1.3 内核模块的管理,9.1.3 内核模块的管理,在Linux里,除了直接修改系统核心的源代码,把设备驱动程序加进核心里以外,还可以把设备驱动程序作为可加载的模

18、块,由系统管理员动态地加载它,使之成为核心的一部分。也可以由系统管理员把已加载地模块动态地卸载下来。Linux的模块可以用C语言编写,用gcc编译成目标文件(不进行链接,作为*.o文件存在),为此需要在gcc命令行里加上-c的参数。在编译时,还应该在gcc的命令行里加上这样的参数:-D_KERNEL_-DMODULE。由于在不链接时,gcc只允许一个输入文件,因此一个模块的所有部分都必须在一个文件里实现。,编译好的模块*.o放/lib/modules/xxxx/misc下(xxxx表示核心版本,如在核心版本为时应该为/),然后用depmod-a使此模块成为可加载模块。模块用insmod命令加载

19、,用rmmod命令来卸载,并可以用lsmod命令来查看所有已加载的模块的状态。利用 insmod 命令可手工装入内核模块;利用 lsmod 可查看当前装入的内核模块以及需求加载模块的使用计数及标志信息;利用 rmmod 则可以卸载指定的模块。,编写模块程序的时候,必须提供两个函数,一个是intinit_module(void),供insmod在加载此模块的时候自动调用,负责进行设备驱动程序的初始化工作。init_module返回0以表示初始化成功,返回负数表示失败。另一个函数是void cleanup_module(void),在模块被卸载时调用,负责进行设备驱动程序的清除工作。在成功的向系统

20、注册了设备驱动程序后(调用register_chrdev成功后),就可以用mknod命令来把设备映射为一个特别文件,其它程序使用这个设备的时候,只要对此特别文件进行操作就行了。,9.2 Linux 内核配置系统,随着 Linux 操作系统的广泛应用,特别是 Linux 在嵌入式领域的发展,越来越多的人开始投身到 Linux 内核级的开发中。面对日益庞大的 Linux 内核源代码,开发者在完成自己的内核代码后,都将面临着同样的问题,即如何将源代码融入到 Linux 内核中,增加相应的 Linux 配置选项,并最终被编译进 Linux 内核。这就需要了解 Linux 的内核配置系统。Linux 内

21、核是由分布在全球的 Linux 爱好者共同开发的,Linux 内核每天都面临着许多新的变化。但是,Linux 内核的组织并没有出现混乱的现象,反而显得非常的简洁,而且具有很好的扩展性,开发人员可以很方便的向 Linux 内核中增加新的内容。原因之一就是 Linux 采用了模块化的内核配置系统,从而保证了内核的扩展性。,9.2.1 配置系统的基本结构,9.2.1 配置系统的基本结构,Linux内核的配置系统由三个部分组成:(1)Makefile:分布在 Linux 内核源代码中的Makefile 定义 Linux 内核的编译规则;(2)配置文件(config.in):给用户提供配置选择的功能;(

22、3)配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释)和配置用户界面(提供基于字符界面、基于 Ncurses 图形界面以及基于 Xwindows 图形界面的用户配置界面,各自对应于 Make config、Make menuconfig 和Make xconfig)。,这些配置工具都是使用脚本语言,如 Tcl/TK、Perl 编写的(也包含一些用 C 编写的代码)。除非是配置系统的维护者,一般的内核开发者无须了解配置系统的原理,只需要知道如何使用配置系统,如何编写 Makefile 和配置文件就可以。所以,本节只对 Makefile 和配置文件进行讨论。凡是涉及到与具体 CPU

23、 体系结构相关的内容,都以 ARM 为例,这样不仅可以将讨论的问题明确化,而且对内容本身不产生影响。,9.2.2 Makefile,Makefile 的作用是根据配置的情况,构造出需要编译的源文件列表,然后分别编译,并把目标代码链接到一起,最终形成Linux 内核二进制文件。,由于 Linux 内核源代码是按照树形结构组织的,所以 Makefile 也被分布在目录树中。Linux 内核中的 Makefile 以及与 Makefile 直接相关的文件有:Makefile:顶层 Makefile,是整个内核配置、编译的总体控制文件。.config:内核配置文件,包含由用户选择的配置选项,用来存放内

24、核配置后的结果(如 make config)。arch/*/Makefile:位于各种 CPU 体系目录下的 Makefile,如 arch/arm/Makefile,是针对特定平台的 Makefile。各个子目录下的 Makefile:比如 drivers/Makefile,负责所在子目录下源代码的管理。Rules.make:规则文件,被所有的 Makefile 使用。,用户通过 make config 配置后,产生了.config。顶层 Makefile 读入.config 中的配置选择。顶层 Makefile 有两个主要的任务:产生 vmlinux 文件和内核模块(module)。为了达

25、到此目的,顶层 Makefile 递归的进入到内核的各个子目录中,分别调用位于这些子目录中的 Makefile。至于到底进入哪些子目录,取决于内核的配置。在顶层 Makefile 中,有一句:include arch/$(ARCH)/Makefile,包含了特定 CPU 体系结构下的 Makefile,这个 Makefile 中包含了平台相关的信息。位于各个子目录下的 Makefile 同样也根据.config 给出的配置信息,构造出当前配置下需要的源文件列表,并在文件的最后有 include$(TOPDIR)/Rules.make。,Rules.make 文件起着非常重要的作用,它定义了所有

26、 Makefile 共用的编译规则。比如,如果需要将本目录下所有的 c 程序编译成汇编代码,需要在 Makefile 中有以下的编译规则:%.s:%.c$(CC)$(CFLAGS)-S$-o$有很多子目录下都有同样的要求,就需要在各自的 Makefile 中包含此编译规则,这会比较麻烦。而 Linux 内核中则把此类的编译规则统一放置到 Rules.make 中,并在各自的 Makefile 中包含进了 Rules.make(include Rules.make),这样就避免了在多个 Makefile 中重复同样的规则。对于上面的例子,在 Rules.make 中对应的规则为:%.s:%.c$

27、(CC)$(CFLAGS)$(EXTRA_CFLAGS)$(CFLAGS_$(*F)$(CFLAGS_$)-S$-o$,Makefile 中的变量 顶层 Makefile 定义并向环境中输出了许多变量,为各个子目录下的 Makefile 传递一些信息。有些变量,比如 SUBDIRS,不仅在顶层 Makefile 中定义并且赋初值,而且在 arch/*/Makefile 还作了扩充。,常用的变量有以下几类:,(1)版本信息版本信息有:VERSION、PATCHLEVEL、SUBLEVEL、EXTRAVERSION和KERNELRELEASE。版本信息定义了当前内核的版本,例如VERSION=2,

28、PATCHLEVEL=4,SUBLEVEL=18,EXATAVERSION=-rmk7,它们共同构成内核的发行版本KERNELRELEASE:,(2)CPU 体系结构:ARCH在顶层 Makefile 的开头,用 ARCH 定义目标 CPU 的体系结构,比如 ARCH:=arm 等。许多子目录的 Makefile 中,要根据 ARCH 的定义选择编译源文件的列表。,(3)路径信息:TOPDIR和SUBDIRSTOPDIR 定义了 Linux 内核源代码所在的根目录。例如,各个子目录下的 Makefile 通过$(TOPDIR)/Rules.make 就可以找到 Rules.make 的位置。S

29、UBDIRS 定义了一个目录列表,在编译内核或模块时,顶层 Makefile 就是根据 SUBDIRS 来决定进入哪些子目录。SUBDIRS 的值取决于内核的配置,在顶层 Makefile 中 SUBDIRS 赋值为 kernel drivers mm fs net ipc lib;根据内核的配置情况,在 arch/*/Makefile 中扩充了 SUBDIRS 的值。,(4)内核组成信息:HEAD、CORE_FILES、NETWORKS、DRIVERS、LIBSLinux 内核文件 vmlinux 是由以下规则产生的:vmlinux:$(CONFIGURATION)init/main.o i

30、nit/version.o linuxsubdirs$(LD)$(LINKFLAGS)$(HEAD)init/main.o init/version.o-start-group$(CORE_FILES)$(DRIVERS)$(NETWORKS)$(LIBS)-end-group-o vmlinux 可以看出,vmlinux 是由 HEAD、main.o、version.o、CORE_FILES、DRIVERS、NETWORKS 和 LIBS 组成的。这些变量(如 HEAD)都是用来定义连接生成 vmlinux 的目标文件和库文件列表。其中,HEAD在arch/*/Makefile 中定义,用来

31、确定被最先链接进 vmlinux 的文件列表。,例如,对于 ARM 系列的 CPU,HEAD 定义为:HEAD:=arch/arm/kernel/head-$(PROCESSOR).o arch/arm/kernel/init_task.o 表明 head-$(PROCESSOR).o 和 init_task.o 需要最先被链接到 vmlinux 中。PROCESSOR 为 armv 或 armo,取决于目标 CPU。CORE_FILES,NETWORK,DRIVERS 和 LIBS 在顶层 Makefile 中定义,并且由 arch/*/Makefile 根据需要进行扩充。CORE_FILE

32、S 对应着内核的核心文件,有 kernel/kernel.o,mm/mm.o,fs/fs.o,ipc/ipc.o,可以看出,这些是组成内核最为重要的文件。同时,arch/arm/Makefile 对 CORE_FILES 进行了扩充:#arch/arm/Makefile#If we have a machine-specific directory,then include it in the build.MACHDIR:=arch/arm/mach-$(MACHINE)ifeq($(MACHDIR),$(wildcard$(MACHDIR)SUBDIRS+=$(MACHDIR)CORE_FI

33、LES:=$(MACHDIR)/$(MACHINE).o$(CORE_FILES)endifHEAD:=arch/arm/kernel/head-$(PROCESSOR).o arch/arm/kernel/init_task.oSUBDIRS+=arch/arm/kernel arch/arm/mm arch/arm/lib arch/arm/nwfpeCORE_FILES:=arch/arm/kernel/kernel.o arch/arm/mm/mm.o$(CORE_FILES)LIBS:=arch/arm/lib/lib.a$(LIBS),(5)编译信息:CPP,CC,AS,LD,AR

34、,CFLAGS,LINKFLAGS 在 Rules.make 中定义的是编译的通用规则,具体到特定的场合,需要明确给出编译环境,编译环境就是在以上的变量中定义的。针对交叉编译的要求,定义了 CROSS_COMPILE。如:CROSS_COMPILE=arm-linux-CC=$(CROSS_COMPILE)gccLD=$(CROSS_COMPILE)ld.CROSS_COMPILE 定义了交叉编译器前缀 arm-linux-,表明所有的交叉编译工具都是以 arm-linux-开头的,所以在各个交叉编译器工具之前,都加入了$(CROSS_COMPILE),以组成一个完整的交叉编译工具文件名,比如

35、 arm-linux-gcc。CFLAGS 定义了传递给 C 编译器的参数。LINKFLAGS 是链接生成 vmlinux 时,由链接器使用的参数。LINKFLAGS 在 arm/*/Makefile 中定义,如:#arch/arm/Makefile LINKFLAGS:=-p-X-T arch/arm/vmlinux.lds,(6)配置变量CONFIG_*.config 文件中有许多的配置变量等式,用来说明用户配置的结果。例如 CONFIG_MODULES=y 表明用户选择了 Linux 内核的模块功能。.config 被顶层 Makefile 包含后,就形成许多的配置变量,每个配置变量具有

36、确定的值:y 表示本编译选项对应的内核代码被静态编译进 Linux 内核;m 表示本编译选项对应的内核代码被编译成模块;n 表示不选择此编译选项;如果根本就没有选择,那么配置变量的值为空。,2.Rules.make 变量 Rules.make 是编译规则文件,所有的 Makefile 中都会包括 Rules.make。Rules.make 文件定义了许多变量,最为重要是那些编译、链接列表变量。O_OBJS,L_OBJS,OX_OBJS,LX_OBJS:本目录下需要编译进 Linux 内核 vmlinux 的目标文件列表,其中 OX_OBJS 和 LX_OBJS 中的 X 表明目标文件使用了 E

37、XPORT_SYMBOL 输出符号。M_OBJS,MX_OBJS:本目录下需要被编译成可装载模块的目标文件列表。同样,MX_OBJS 中的 X 表明目标文件使用了 EXPORT_SYMBOL 输出符号。O_TARGET,L_TARGET:每个子目录下都有一个 O_TARGET 或 L_TARGET,Rules.make 首先从源代码编译生成 O_OBJS 和 OX_OBJS 中所有的目标文件,然后使用$(LD)-r 把它们链接成一个 O_TARGET 或 L_TARGET。O_TARGET 以.o 结尾,而 L_TARGET 以.a 结尾。,3.Makefile子目录,3.Makefile子目

38、录,子目录Makefile 用来控制本级目录以下源代码的编译规则。下面通过一个例子来讲解子目录 Makefile 的组成:,#Makefile for the linux kernel.#All of the(potential)objects that export symbols.#This list comes from grep-l EXPORT_SYMBOL*.hc.export-objs:=tc.o#Object file lists.obj-y:=obj-m:=obj-n:=obj-:=obj-$(CONFIG_TC)+=tc.oobj-$(CONFIG_ZS)+=zs.oobj

39、-$(CONFIG_VT)+=lk201.o lk201-map.o lk201-remap.o#Files that are both resident and modular:remove from modular.obj-m:=$(filter-out$(obj-y),$(obj-m),#Translate to Rules.make lists.L_TARGET:=tc.aL_OBJS:=$(sort$(filter-out$(export-objs),$(obj-y)LX_OBJS:=$(sort$(filter$(export-objs),$(obj-y)M_OBJS:=$(sor

40、t$(filter-out$(export-objs),$(obj-m)MX_OBJS:=$(sort$(filter$(export-objs),$(obj-m)include$(TOPDIR)/Rules.make(1)注释对 Makefile 的说明和解释,由#开始。(2)编译目标定义 类似于 obj-$(CONFIG_TC)+=tc.o 的语句是用来定义编译的目标,是子目录 Makefile 中最重要的部分。编译目标定义那些在本子目录下,需要编译到 Linux 内核中的目标文件列表。为了只在用户选择了此功能后才编译,所有的目标定义都融合了对配置变量的判断。,前面说过,每个配置变量取值范

41、围是:y,n,m 和空,obj-$(CONFIG_TC)分别对应着 obj-y,obj-n,obj-m,obj-。如果 CONFIG_TC 配置为 y,那么 tc.o 就进入了 obj-y 列表。obj-y 为包含到 Linux 内核 vmlinux 中的目标文件列表;obj-m 为编译成模块的目标文件列表;obj-n 和 obj-中的文件列表被忽略。配置系统就根据这些列表的属性进行编译和链接。export-objs 中的目标文件都使用了 EXPORT_SYMBOL()定义了公共的符号,以便可装载模块使用。在 tc.c 文件的最后部分,有“EXPORT_SYMBOL(search_tc_car

42、d);”,表明 tc.o 有符号输出。这里需要指出的是,对于编译目标的定义,存在着两种格式,分别是老式定义和新式定义。老式定义就是前面 Rules.make 使用的那些变量,新式定义就是 obj-y,obj-m,obj-n 和 obj-。Linux 内核推荐使用新式定义,不过由于 Rules.make 不理解新式定义,需要在 Makefile 中的适配段将其转换成老式定义。,(3)适配段 适配段的作用是将新式定义转换成老式定义。在上面的例子中,适配段就是将 obj-y 和 obj-m 转换成 Rules.make 能够理解的 L_TARGET,L_OBJS,LX_OBJS,M_OBJS,MX_

43、OBJS。L_OBJS:=$(sort$(filter-out$(export-objs),$(obj-y)定义了 L_OBJS 的生成方式:在 obj-y 的列表中过滤掉 export-objs(tc.o),然后排序并去除重复的文件名。这里使用到了 GNU Make 的一些特殊功能,具体的含义可参考 Make 的文档(info make)。(4)include$(TOPDIR)/Rules.make,9.2.3 配置文件,9.2.3 配置文件,除了 Makefile 的编写,另外一个重要的工作就是把新功能加入到 Linux 的配置选项中,提供此项功能的说明,让用户有机会选择此项功能。所有的这

44、些都需要在 config.in 文件中用配置语言来编写配置脚本,在 Linux 内核中,配置命令有多种方式:,以字符界面配置(make config)为例,顶层 Makefile 调用 scripts/Configure,按照 arch/arm/config.in 来进行配置。命令执行完后产生文件.config,其中保存着配置信息。下一次再做 make config 将产生新的.config 文件,原.config 被改名为.config.old。,1.配置语言(1)顶层菜单mainmenu_name/prompt/prompt/是用或包围的字符串,与的区别是中可使用$引用变量的值。mainm

45、enu_name 设置最高层菜单的名字,它只在 make xconfig 时才会显示。(2)询问语句 bool/prompt/symbol/hex/prompt/symbol/word/int/prompt/symbol/word/string/prompt/symbol/word/tristate/prompt/symbol/询问语句首先显示一串提示符/prompt/,等待用户输入,并把输入的结果赋给/symbol/所代表的配置变量。不同的询问语句的区别在于它们接受的输入数据类型不同,比如 bool 接受布尔类型(y 或 n),hex 接受 16 进制数据。有些询问语句还有第三个参数/wor

46、d/,用来给出默认值。,(3)定义语句 define_bool/symbol/word/define_hex/symbol/word/define_int/symbol/word/define_string/symbol/word/define_tristate/symbol/word/不同于询问语句等待用户输入,定义语句显式的给配置变量/symbol/赋值/word/。,(3)定义语句 define_bool/symbol/word/define_hex/symbol/word/define_int/symbol/word/define_string/symbol/word/define_t

47、ristate/symbol/word/不同于询问语句等待用户输入,定义语句显式的给配置变量/symbol/赋值/word/。(4)依赖语句 dep_bool/prompt/symbol/dep/.dep_mbool/prompt/symbol/dep/.dep_hex/prompt/symbol/word/dep/.dep_int/prompt/symbol/word/dep/.dep_string/prompt/symbol/word/dep/.dep_tristate/prompt/symbol/dep/.与询问语句类似,依赖语句也是定义新的配置变量。不同的是,配置变量/symbol/的

48、取值范围将依赖于配置变量列表/dep/。这就意味着:被定义的配置变量所对应功能的取舍取决于依赖列表所对应功能的选择。以dep_bool为例,如果/dep/列表的所有配置变量都取值y,则显示/prompt/,用户可输入任意的值给配置变量/symbol/,但是只要有一个配置变量的取值为n,则/symbol/被强制成n。不同依赖语句的区别在于它们由依赖条件所产生的取值范围不同。,(5)选择语句 choice/prompt/word/word/choice 语句首先给出一串选择列表,供用户选择其中一种。比如 Linux for ARM 支持多种基于 ARM core 的 CPU,Linux 使用 ch

49、oice 语句提供一个 CPU 列表,供用户选择:choice ARM system type Anakin CONFIG_ARCH_ANAKIN Archimedes/A5000 CONFIG_ARCH_ARCA5K Cirrus-CL-PS7500FE CONFIG_ARCH_CLPS7500 SA1100-based CONFIG_ARCH_SA1100 Shark CONFIG_ARCH_SHARK RiscPC Choice 首先显示/prompt/,然后将/word/分解成前后两个部分,前部分为对应选择的提示符,后部分是对应选择的配置变量。用户选择的配置变量为 y,其余的都为 n。

50、,(6)if语句if/expr/;then/statement/.fiif/expr/;then/statement/.else/statement/.fiif 语句对配置变量(或配置变量的组合)进行判断,并作出不同的处理。判断条件/expr/可以是单个配置变量或字符串,也可以是带操作符的表达式。操作符有:=,!=,-o,-a 等。,(7)菜单块(menu block)语句 mainmenu_option next_comment comment.endmenu 引入新的菜单。在向内核增加新的功能后,需要相应的增加新的菜单,并在新菜单下给出此项功能的配置选项。Comment 后带的注释就是新菜

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号