make modules

make modules
2014年04月13日 22:44:30 Alix_sz 阅读数:4100更多
个人分类: ARM学习

1.make modules
error:Building modules, stage 2.
MODPOST 0 modules

rootcause and solution: obj -m should be obj-m in Makefile

拓展:

ifneq (KaTeX parse error: Expected 'EOF', got '#' at position 39: …:=hello.o else #̲generate the pa…(shell pwd)
#the absolute path
LINUX_KERNEL_PATH:=/lib/modules/$(shell uname -r)/build
#complie object
default:
make -C ( L I N U X K E R N E L P A T H ) M = (LINUX_KERNEL_PATH) M= (CURRENT_PATH) modules
clean:
make -C ( L I N U X K E R N E L P A T H ) M = (LINUX_KERNEL_PATH) M= (CURRENT_PATH) clean
endif

说明:
当我们在模块的源代码目录下运行make时,make是怎么执行的呢?
假设模块的源代码目录是/home/study/prog/mod/hello/下。
先说明以下makefile中一些变量意义:
(1)KERNELRELEASE在linux内核源代码中的顶层makefile中有定义
(2)shell pwd会取得当前工作路径
(3)shell uname -r会取得当前内核的版本号
(4)LINUX_KERNEL_PATH变量便是当前内核的源代码目录。
关于linux源码的目录有两个,分别为"/lib/modules/ ( s h e l l u n a m e r ) / b u i l d " " / u s r / s r c / l i n u x h e a d e r (shell uname -r)/build"和"/usr/src/linux-header- (shell uname -r)/",
但如果编译过内核就会知道,usr目录下那个源代码一般是我们自己下载后解压的,而lib目录下的则是在编译时自动copy过去的,
两者的文件结构完全一样,因此有时也将内核源码目录设置成/usr/src/linux-header-$(shell uname -r)/。关于内核源码目录
可以根据自己的存放位置进行修改。
(5)make -C ( L I N U X K E R N E L P A T H ) M = (LINUX_KERNEL_PATH) M= (CURRENT_PATH) modules
这就是编译模块了:首先改变目录到-C选项指定的位置(即内核源代码目录),其中保存有内核的顶层makefile;
M=选项让该makefile在构造modules目标之前返回到模块源代码目录;然后,modueles目标指向obj-m变量中设定的模块;
在上面的例子中,我们将该变量设置成了hello.o。

按照顺序分析以下make的执行步骤:
在模块的源代码目录下执行make,此时,宏“KERNELRELEASE”没有定义,因此进入else。
由于make 后面没有目标,所以make会在Makefile中的第一个不是以.开头的目标作为默认的目标执行。
于是default成为make的目标。
make会执行 $(MAKE) -C ( K E R N E L D I R ) M = (KERNELDIR) M= (PWD) modules ,假设当前内核版本是2.6.13-study,
所以$(shell uname -r)的结果是 2.6.13-study ,这里实际运行的是
make -C /lib/modules/2.6.13-study/build M=/home/study/prog/mod/hello/ modules

-C 表示到存放内核的目录执行其makefile,在执行过程中会定义KERNELRELEASE,
然后M=$(CURDIR)表示返回到当前目录,再次执行makefile,modules表示编译成模块的意思。
而此时KERNELRELEASE已定义,则会执行obj-m += hello.o,表示会将hello_world.o目标编译成.ko模块。
若有多个源文件,则采用如下方法:
obj-m := hello.o
hello-objs := file1.o file2.o file3.o
关于make modules的更详细的过程可以在内核源码目录下的scripts/Makefile.modpost文件的注释 中找到。

如果把hello模块移动到内核源代码中。例如放到/usr/src/linux/driver/中, KERNELRELEASE就有定义了。
在/usr/src/linux/Makefile中有KERNELRELEASE= ( V E R S I O N ) . (VERSION). (PATCHLEVEL). ( S U B L E V E L ) (SUBLEVEL) (EXTRAVERSION)$(LOCALVERSION)。

这时候,hello模块也不再是单独用make编译,而是在内核中用make modules进行编译,此时驱动模块便和内核编译在一起。

===========================================================================================================
关于linux内核驱动的东西网络上有很多,但网上的东西还是感觉有点笼统,读过之后就忘了,还是需要写下来,或者写到本子上,自己形成一个概念好一些。读了这本书上的东西,把觉得好的东西写下来,已备不时之用,也强化记忆。
1 内核模块的概念
介绍内核模块的同时,也说明一下和应用程序的区别。虽然内容很多,但觉得都很有用。
1、内核模块是一些可以让操作系统内核在需要时载入和执行的代码,同时在不需要的时候可以卸载。这是一个好的功能,扩展了操作系统的内核功能,却不需要重新启动系统,是一种动态加载的技术。
特点:动态加载,随时载入,随时卸载,扩展功能
2、内核模块的加载作用:只是向linux内核预先注册自己,以便于将来的请求使用。
也就是告诉内核,它有了新增的功能,而并不马上使用(执行),而应用程序在加载后就开始执行。
3、内核模块的代码编写没有外部的函数库可以用,只能使用内核导出的函数。而应用程序习惯于使用外部的库函数,在编译时将程序与库函数链接在一起。例如对比printf( ) and printk( )
4、内核模块代码运行在内核空间,而应用程序在用户空间。应用程序的运行会形成新的进程,而内核模块一般不会。每当应用程序执行系统调用时,linux执行模式从用户空间切换到内核空间。
2linux内核模块的框架
最少两个入口点
*模块加载函数
*模块卸载函数
通常使用module_init() and module_exit()两个宏定义声明模块的加载函数和卸载函数,这个定义在linux2.6.x/include/linux/init.h中。内容为:
#define module_init(x) __initcall(x)
//在内核启动或模块加载时执行
#define module_exit(x) __exitcall(x)
//在模块卸载时执行
*每一个模块只能有一个module_init 和一个module_exit。
一个简单的内核模块的源代码.c格式如下:

#include<linux/module.h> //所有内核模块都必须包含这个头文件
#include<linux/kernel.h> //使用内核信息优先级时要包含这个
#include<linux/init.h> //一些初始化的函数如module_init()
MODULE_LICENSE(“GPL”); //模块许可声明
static int hello_init(void)
{}
static void hello_exit(void)
{}
module_init(hello_init);
module_exit(hell0_exit);
这两个函数只是一种内部的声明,或者说内部实现加载的一种方式,说到低还是在模块内部的,内核并没有加载模块,真正要加载模块要使用命令insmod卸载用rmmod.
关于linux设备驱动入门请参考另外一篇文章:linux驱动编程入门。

3 2.6 系列内核模块的编译和加载
这里讲到内核模块的编译,2。6版本引入了kbuild,将外部内核模块的编译和内核源码树的编译统一起来了。
内核源码树的目录下都有两个文件Kconfig(2.4版本是Config.in)和Makefile。分布到各目录的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文件相关的内核配置菜单。在内核配置make menuconfig(或xconfig等)时,从Kconfig中读出菜单,用户选择后保存到.config的内核配置文件中。在内核编译时,主 Makefile调用这个.config,就知道了用户的选择。
*上面的内容说明了,Kconfig就是对应着内核的配置菜单。如果要想添加新的驱动到内核的源码中,可以修改Kconfig,这样就可以选择这个驱动,如果想使这个驱动被编译,要修改Makefile
添加新的驱动时需要修改的文件有两种(注意不只是两个)
*Kconfig
*Makefile
要想知道怎么修改这两种文件,就要知道两种文件的语法结构
3.1 Kconfig
每个菜单都有一个关键字标识,最常见的就是config
语法:
config
symbol是一个新的标记的菜单项,options是在这个新的菜单项下的属性和选项
其中options部分有:
1、类型定义:
每个config菜单项都要有类型定义,bool布尔类型、 tristate三态:内建、模块、移除 string字符串、 hex十六进制、 integer整型
例如config HELLO_MODULE
bool “hello test module”
bool类型的只能选中或不选中,tristate类型的菜单项多了编译成内核模块的选项,如果选择编译成内核模块,则会在.config中生成一个CONFIG_HELLO_MODULE=m的配置,如果选择内建,就是直接编译成内核影响,就会在.config中生成一个 CONFIG_HELLO_MODULE=y的配置.
2、依赖型定义depends on或requires
指此菜单的出现与否依赖于另一个定义
config HELLO_MODULE
bool “hello test module”
depends on ARCH_PXA
这个例子表明HELLO_MODULE这个菜单项只对XScale处理器有效。
3、帮助性定义
只是增加帮助用关键字help或者—help—
3.2 内核的Makefile

在linux2.6.x/Documentation/kbuild目录下有详细的介绍有关kernel makefile的知识。
linux2.6内核的Makefile分为5个组成部分:

Makefile 最顶层的Makefile
.config 内核的当前配置文件,编译时成为定层Makefile的一部分
arch/ ( A R C H ) / M a k e f i l e M a k e f i l e s / M a k e f i l e . M a k e f i l e k b u i l d M a k e f i l e 500 M a k e f i l e M a k e f i l e . c o n f i g b u i l d A r c h M a k e f i l e s M a k e f i l e k b u i l d M a k e f i l e . c o n f i g m a k e m e n u c o n f i g K c o n f i g 使 k b u i l d k b u i l d M a k e f i l e 1 o b j y + = f o o . o f o o . c f o o . s f o o . o o b j m y m o b j x . c o n f i g C O N F I G o b j (ARCH)/Makefile 与体系结构相关的Makefile s/ Makefile.* 一些Makefile的通用规则 kbuild Makefile 各级目录下的大概约500个文件,编译时根据上层Makefile传下来的宏定义和其他编译规则,将源代码编译成模块或者编入内核 顶层的Makefile文件读取 .config文件的内容,并总体上负责build内核和模块。Arch Makefile则提供补充体系结构相关的信息。 s目录下的Makefile文件包含了所有用来根据kbuild Makefile 构建内核所需的定义和规则。 (其中.config的内容是在make menuconfig的时候,通过Kconfig文件配置的结果,上面已经说过) 对于大部分内核模块或设备驱动的开发者和使用者来说,最常接触到的就是各层目录下基于kbuild架构的kbuild Makefile文件。主要部分有: 1、目标定义,目标定义就是用来定义哪些内容要做为模块编译,哪些要编译链接进内核。 最简单的只有一行,如 obj-y += foo.o 表示要由foo.c或者foo.s文件编译得到foo.o并链接进内核,而obj-m则表示该文件要作为模块编译。除了y,m以外的obj-x形式的目标都不会被编译。 由于既可以编译成模块,也可以编译进内核,更常见的做法是根据.config文件的CONFIG_ 变量来决定文件的编译方式,如: obj- (CONFIG_HELLO_MODULE) += hello.o ,这个已经在7.2.3.1中说明过。
除了obj-形式的目标以外,还有lib-y library库,hostprogs-y 主机程序等目标,但是基本都应用在特定的目录和场合下
2、多目标
一个内核模块由多个源文件编译而成,这是Makefile有所不同。
采用模块名加 –objs后缀或者 –y后缀的形式来定义模块的组成文件。
如以下例子:
obj- ( C O N F I G E X T 2 F S ) + = e x t 2. o e x t 2 y : = b a l l o c . o b i t m a p . o e x t 2 (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- ( C O N F I G I S D N ) + = i s d n . o i s d n o b j s : = i s d n n e t l i b . o i s d n v 110. o i s d n c o m m o n . o 3 o b j (CONFIG_ISDN) += isdn.o isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o 3、目录的迭代 obj- (CONFIG_EXT2_FS) += ext2/
如果CONFIG_EXT2_FS 的值为y或m,kbuild将会将ext2目录列入向下迭代的目标中,但是其作用也仅限于此,具体ext2目录下的文件是要作为模块编译还是链入内核,还是有ext2目录下的Makefile文件的内容来决定的
4、不同的模块编译方式
编译模块的时候,你可以将模块放在代码树中,用Make modules的方式来编译你的模块,你也可以将模块相关文件目录放在代码树以外的位置,用如下命令来编译模块:
make -C path/to/kernel/src M= P W D m o d u l e s C PWD modules -C指定内核源码的根目录, PWD 或 PWD 是当前目录的环境变量,告诉kbuild回到当前目录来执行build操作。
5、模块安装
当你需要将模块安装到非默认位置的时候,你可以用INSTALL_MOD_PATH 指定一个前缀,如:
make INSTALL_MOD_PATH=/foo modules_install
模块将被安装到 /foo/lib/modules目录下

注意2.6和2.4版本编译结果的不同,内核模块在2.6下是.ko后缀,取代了2.4下的.o后缀,使内核模块和普通的目标文件区别开。(比较好呀)

在FL2440资料的LED驱动编程的编译makefile里面看到这样一句话,-C是表示进入$(KERNELDIR)目录执行makefile,而M不是makefile的选项,是内核根目录下的Makefile中使用的变量。

Use make M=dir to specify directory of external module to build

Old syntax make … SUBDIRS=$PWD is still supported

Setting the environment variable KBUILD_EXTMOD take precedence

ifdef SUBDIRS
KBUILD_EXTMOD ?= ( S U B D I R S ) e n d i f i f d e f M / / M M u n d e f i n e d i f e q ( &quot; (SUBDIRS) endif ifdef M //如果没有定义或赋值M,此处M未定义(undefined) ifeq (&quot; (origin M)", “command line”) //如果定义了,此句用来判断M是否从命令行来
KBUILD_EXTMOD := $(M)
endif
endif

KERN_DIR = /work/system/linux-2.6.22.6

all:
make -C $(KERN_DIR) M=pwd modules

clean:
make -C $(KERN_DIR) M=pwd modules clean
rm -rf modules.order

obj-m += usbmouse_as_key.o

驱动模块Makefile解析

作者:王劲南,华清远见嵌入式学院讲师。

先看一下代码

ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /home/linux/linux-2.6.22.6
PWD := $(shell pwd)
modules:
$(MAKE) -C ( K E R N E L D I R ) M = (KERNELDIR) M= (PWD) modules
modules_install:
$(MAKE) -C ( K E R N E L D I R ) M = (KERNELDIR) M= (PWD) modules_install
clean:
rm -rf *.o ~ core .depend ..cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
else
obj-m := myhello.o
endif

其中有一些是我们常见或是见过的,第一个ifeq ($(KERNELRELEASE),)目前,并无用处,它的由来是指在Linux源码根目录下的Makefile编译内核时,KERNELRELEASE宏会被定义,那么如果是从源码根目录开始的make则会将myhello.o模块编译进内核。

KERNELDIR ?= /home/linux/linux-2.6.22.6,这句是对KERNELDIR进行赋值,这个变量是后面我们用到的指代内核源码目录用的。

PWD := ( s h e l l p w d ) P W D (shell pwd),这句是对PWD变量进行赋值,作用是将 (shell pwd)的返回结果既求得当前目录的路径赋值给PWD,这个变量我们在后面指代我们要编译的驱动程序所在的位置。

modules:
$(MAKE) -C ( K E R N E L D I R ) M = (KERNELDIR) M= (PWD) modules

这句是Makefile的规则:这里的$(MAKE)就相当于make,-C 选项的作用是指将当前工作目录转移到你所指定的位置。“M=”选项的作用是,当用户需要以某个内核为基础编译一个外部模块的话,需要在make modules 命令中加入“M=dir”,程序会自动到你所指定的dir目录中查找模块源码,将其编译,生成KO文件。

modules_install:
$(MAKE) -C ( K E R N E L D I R ) M = (KERNELDIR) M= (PWD) modules_install

这个命令是模块的安装,在Makefile中搜索“lib/modules”可以看到下面的语句,通过阅读你不难找到这个“MODLIB”的用处,它是用来指定安装路径的,而变量“INSTALL_MOD_PATH”往往为空。

MODLIB = ( I N S T A L L M O D P A T H ) / l i b / m o d u l e s / (INSTALL_MOD_PATH)/lib/modules/ (KERNELRELEASE)
Export MODLIB

.PHONY: modules modules_install clean

这句话是的作用是保证modules,modules_install,clean这三个命令能正常完成。.PHONY 这是一个特殊目标名称,还有其它的,如:

.SUFFIXES,.DEFAULT,.PRECIOUS,.INTERMEDIATE,.SECONDARY,.SECONDEXPANSION,.DELETE_ON_ERROR,.IGNORE .LOW_RESOLUTION_TIME .SILENT .EXPORT_ALL_VARIABLES .NOTPARALLEL

它们的具体用法可以参考GNU手册中的Special Built-in Target Names章节。

.PHONY目标的具体意思是如果在Makefile的工作目录中有名如:modules,modules_install,clean等文件时命令会出错。它是防止这出错的方式。

=============================================================================================================

大概经过一个星期的找资料和个人的总结,开发出来了第一个linux 驱动程序:hello word的程序了!下面是我的开发过程,希望的家吸取教训少走弯路!

首先我们最好将我们的机器的源更新一下:
Type the following command(非超级用户)
$ sudo apt-get update
OR as root user enter:(超级用户)

apt-get update

Search for kernel version (optional)(搜寻内核的版本)
Type the following command:

$ apt-cache search linux-headers-$(uname -r)

Install linux-header package under Debina or Ubuntu Linux(更新内核的版本)
Type the following command:(非超级用户)

$ sudo apt-get install linux-headers-$(uname -r)

OR as root user:(超级用户)

apt-get install linux-headers-$(uname -r)

之后要进行重启。

猜你喜欢

转载自blog.csdn.net/AQRSXIAO/article/details/82770080