Linux 模块编程指南

Linux模块编程

1.1 模块学习什么

1.认识什么是模块?跟我学习的驱动有什么关系?

2.熟悉模块的安装,卸载,查看

3.熟悉模块的基本框架

4.熟悉模块的编程方法

1.2 内核模块概述

Linux 内核的整体结构非常庞大,其包含的组件也非常多。我们怎样把需要的部分都包含在内核中呢?一种方法是把所有需要的功能都编译到 Linux 内核。这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核。

有没有一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码可被动态地加载到内核中呢?

Linux 提供了这样的一种机制,这种机制被称为模块(Module,可以实现以上效果。模块具有以下特点。

1.模块本身不被编译入内核映像,从而控制了内核的大小。

2.模块一旦被加载,它就和内核中的其他部分完全一样。

1.3 认识模块

我们在配置内核的时候,在配置菜单看到有一种选项可以选择为<M> <*> < > 。

其中,<M>表示编译成模块,即modules,这个便于选项编译的代码不编译进内核zImage,而是编译成一个单独的文件,通常为后缀.ko(2.6以上内核版本是.ko,2.6内核之前是.o)文件,那么我们可以像软件一样选择安装和卸载这些.ko模块文件。

<*>表示编译进内核,就是将这一段模块代码编译到了zImage镜像文件去了,在内核启动的时候自动安装执行我们的模块代码,这样做类似于一些安装系统的时候自带的驱动,在安装系统的时候就已经安装好了。

< > 表示没有选择,不做任何事情。

所以,我们所说的模块就是linux下的那些.ko文件

1.3.1 tiny4412提供的模块测试程序

在我们学习的Tiny4412提供的 Linux3.5 源码中有一项模块测试选项,如下:

         Tiny4412module sample.

         Symbol:TINY4412_HELLO_MODULE [=m]

         Type: tristate

         Prompt:Tiny4412 module sample

         Definedat drivers/char/Kconfig:49

         Dependson: ARCH_EXYNOS4 [=y]

Location:

          -> DeviceDrivers

                   ->Character devices      

注意:因为有依赖项,所以它的依赖必须被选上,即ARCH_EXYNOS4 [=y]

 CONFIG_ARCH_EXYNOS4:                                                                                

 Samsung EXYNOS4 SoCs based systems                                                                   

 Symbol: ARCH_EXYNOS4[=y]                            

 Type  :boolean                                      

 Prompt: SAMSUNGEXYNOS4                              

  Defined at arch/arm/mach-exynos/Kconfig:14         

  Depends on: ARCH_EXYNOS [=y]                       

  Location:                                          

     -> SystemType                                    

       -> SAMSUNGEXYNOS SoCs Support                 

  Selects: HAVE_SMP [=y] && MIGHT_HAVE_CACHE_L2X0 [=y]

这个测试文件在drivers/char/目录下的tiny4412_hello_moduls.c当把它选择为<M>之后,编译就会在同级目录下生成.ko文件。

编译命令:make modules

在 Linux 系统中,几乎所有驱动都可以编译模块的形式。

总之,模块代码我们可以在系统启动后再安装(zImage文件中并不包含该选项对应的C代码)。也可以编译到内核,像内核配置菜单中选择为y的选项,对应的C代码会被编译到zImage文件中。

这里记住一句话:linux下模块不一定是驱动,但是驱动肯是一种模块

 

示例:编译成模块

1)在linux内核内核配置菜单makememuconfig上把模块测试选项为M,然后退出保存。

2)编译成模块,如下:

[root@localhost linux-3.5]# make modules

scripts/kconfig/conf--silentoldconfig Kconfig

 CHK     include/linux/version.h

 CHK    include/generated/utsrelease.h

make[1]: “include/generated/mach-types.h”是最新的。

 CALL    scripts/checksyscalls.sh

 CC [M]  crypto/ansi_cprng.o

 CC [M] drivers/char/tiny4412_hello_module.o

 CC [M] drivers/scsi/scsi_wait_scan.o

 Building modules, stage 2.

 MODPOST 3 modules

 CC      crypto/ansi_cprng.mod.o

 LD [M]  crypto/ansi_cprng.ko

 CC     drivers/char/tiny4412_hello_module.mod.o

 LD [M] drivers/char/tiny4412_hello_module.ko

 CC     drivers/scsi/scsi_wait_scan.mod.o

 LD [M] drivers/scsi/scsi_wait_scan.ko

[root@localhost linux-3.5]#

以上就会在drivers/char/生成了tiny4412_hello_module.ko文件。

然后我们可以查看一下它的存在:

[root@localhost linux-3.5]# cd drivers/char/

[root@localhost char]# ls tiny4412_hello_module.ko -l

-rw-r--r--. 1root root 29062 9 25 11:26 tiny4412_hello_module.ko

[root@localhost char]#

 

有了.ko文件,那么我们可在以外部独立的来加载到内核,怎么加载呢?又怎么卸载呢?

Linux系统提供专业的命令来完成这样的事情,下面来介绍这几个命令。

1.3.2 Linux模块安装,卸载,查看

Linux下模块提供有安装,卸载,查看安装了那个模块以及查看模块信息等命令。

1.3.1、模块相关命令

insmod <file name.ko>

安装模块进内核

rmmod <file name>

卸载指定的模块

lsmod

查看当前系统安装了哪些模块

modinfo <file name>

查看模块信息

1.insmod   -- 安装模块

示例

[root@JUNJIA /home]# insmod tiny4412_hello_module.ko

[ 140.030000] Hello, Tiny4412 module is installed !

[root@JUNJIA /home]#

2.lsmod    -- 列出当前系统已经安装的模块及模块间的依赖关系(不包含被编译的内核的模块代码)

示例

[root@JUNJIA /home]# lsmod

tiny4412_hello_module 773 0 - Live 0xbf000000

第1列:模块名

第2列:模块大小

第3列:被引用多少次(本模块的代码被多少个模块调用)

第4列:被哪个模块引用

说明

lsmod 命令实际上读取并分析/proc/modules文件。

 

 

在安装模块的时候我们可能会遇到这样的问题,模块版本和要装的linux内核版本不同会出现问题。

比如我修改了编译模块的内核版本号,然后编译查看模块信息:

         [root@localhostlinux-3.5]# modinfo drivers/char/tiny4412_hello_module.ko

         filename:       drivers/char/tiny4412_hello_module.ko

         license:        GPL

         depends:       

         intree:         Y

         vermagic:       3.5.0-FriendlyARM_JUNJIASMP preempt mod_unload ARMv7 p2v8

         [root@localhostlinux-3.5]#

安装会出现以下问题:

         [root@JUNJIA/home]# insmod tiny4412_hello_module.ko

         [3212.730000] tiny4412_hello_module: version magic '3.5.0-FriendlyARM_JUNJIASMP preempt mod_unload ARMv7 p2v8 ' should be '3.5.0-FriendlyARMSMP preempt mod_unload ARMv7 p2v8 '

         insmod:can't insert 'tiny4412_hello_module.ko': invalid module format

         [root@JUNJIA/home]#

这个错误是说模块 版本号是3.5.0-FriendlyARM_JUNJIA,而内核版本号是3.5.0-FriendlyARM,不匹配不能进行加载。那么我就只能

结论:模块版本和内核版本不同是不能安装模块的。

 

3.rmmod    -- 卸载模块(编译在内核的模块代码不可以移除)

示例:

[root@JUNJIA /home]# rmmodtiny4412_hello_module.ko

[root@JUNJIA /home]# lsmod

tiny4412_hello_module 773 0 - Live 0xbf000000 (O)    //发现没有卸载掉

[root@JUNJIA /home]# rmmodtiny4412_hello_module  //不加后缀

[  231.660000] Good-bye, Tiny4412 module was removed! //打印卸载函数里的信息

rmmod: module 'tiny4412_hello_module' not found//这个提示是busybos存在的BUG

[root@JUNJIA /home]#

注意:使用就一些的busybox制作的根文件系统的rmmod命令不能带扩展名,带了不能卸载;但是X86是可以带的,也可以不带。现在的新版本根文件系统制作工具busybox修复了这个功能,带.ko也能卸载。

 

有时候我们在卸载模块会遇到出错等问题,下面是一个常见的问题。

如果busybox下makemenuconfig配置如下:

                   Symbol:MODPROBE_SMALL [=y] 

                   Prompt:Simplified modutils  

                   Definedat modutils/Config.in:15                    

                 Location:                                                                        

                            ->Linux Module Utilities 

模块安装后如果需要卸载,需要在lib下创建modules目录,然后再创建一个以内核版本名命名的子目录。

格式:mkdir/lib/modules/内核版本号

示例:

mkdir/lib/modules/3.5.0-FriendlyARM

内核版本号:(1)可以通过 uname -r 得到;(2)也可以通过查看内核配置,子版本是我们在配置内核的时候写上去的。如下:

         Symbol:LOCALVERSION [=-FriendlyARM]                                           

           │ Type  : string                                                            

           │ Prompt: Local version - append to kernel release                          

           │   Defined at init/Kconfig:91                                              

           │   Location:                 

           │     -> Generalsetup 

那么内核版本号是内核主版本号(3.5.0)接以上的本地版本号(-FriendlyARM)构成。

我们可以有根治的方法,把Simplified modutils选项取消,然后选择出现的lsmodinsmodrmmod命令,重新编译安装。

 

4.modinfo  -- 查看模块的基本信息

示例:            

[root@localhost 桌面]# modinfo dm_mod

         filename:     /lib/modules/2.6.32-279.el6.i686/kernel/drivers/md/dm-mod.ko

         license:        GPL

         author:         Joe Thornber<dm-devel@redhat.com>

         description:    device-mapper driver

         srcversion:     55E98DC47312D5D1A682B77

         depends:       

         vermagic:       2.6.32-279.el6.i686 SMP mod_unloadmodversions 686

         parm:           major:The major number of the devicemapper (uint)

说明:

filename:                          模块路径(相对的)

license:                             模块发布许可协议

author:                             模块作者

description:                     模块是功能描述

srcversion:                       源码版本 --- 不是由编程者决定,不可以控制

depends:                          依赖

vermagic:                        内核的版本魔数,简单就是一版本的ID,由内核源码决定,不可控制

parm:                                模块可以传递的参数介绍

 

使用这个命令可能遇到问题

[root@JUNJIA /home]# modinfo tiny4412_hello_module.ko

modinfo: can't open'/lib/modules/3.5.0-FriendlyARM/modules.dep': No such file or directory

解决办法

cd /lib/

mkdir modules

cd modules/

mkdir <linux version number>

cd <linux version number>

cp *.ko ./

depmod

mv modules.dep.bb modules.dep

modinfo *.ko

 

1.4模块程序框架

我们看一下tiny4412_hello_modules.c,内容如下:

         #include<linux/kernel.h>

         #include<linux/module.h>

 

         staticint __init tiny4412_hello_module_init(void)

         {

             printk("Hello, Tiny4412 module isinstalled !\n");

             return 0;

         }

 

         staticvoid __exit tiny4412_hello_module_cleanup(void)

         {

             printk("Good-bye, Tiny4412 module wasremoved!\n");

         }

        

         module_init(tiny4412_hello_module_init);

         module_exit(tiny4412_hello_module_cleanup);

         MODULE_LICENSE("GPL");

以上是一个模块最基本的框架,我们来说明这个框架的组成都有哪些?

1.必须的头文件

#include <linux/kernel.h>

#include <linux/module.h>

包含了我们这个基本模块所用到的函数声明。

2.模块的初始化(加载)函数(必须)

以下模块初始化函数,insmod 模块时会执行,如果是编译到内核,在系统启动阶段会自动执行。典型的模块初始化函数形式入下:

static int __init tiny4412_hello_module_init(void)

         {

                 /* 初始化代码 */

                   printk("Hello,Tiny4412 module is installed !\n");

                   return0;

         }

module_init(tiny4412_hello_module_init);

说明:

(1)声明static,限定它的作用域,防止与其他文件下的同名函数起冲突。

(2)__init 声明它是一个初始化的函数,在insmod或当编译到内核,在系统启动阶段会自动执行的函数。         在 Linux 内核中,所有标识为__init的函数在连接的时候都放在.init.text 这个区段内,此外,所有的_ _init 函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些__init 函数,并在初始化完成后释放init区段(包括.init.text,.initcall.init 等)。

(3)参数是void类型,没有形参。

(4)返回值是int类型,通常正常执行返回0,出现错误返回错误码。在Linux内核里,错误编码是一个负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM 之类的符号值。返回相应的错误编码是种非常好的习惯,因为只有这样,用户程序才可以利用 perror等方法把它们转换成有意义的错误信息字符串。

(5)做为外部模块的时候,模块必须以module_init(函数名的形式被指定)。

(6)在Linux 2.6内核中,可以使用 request_module(const char *fmt,)函数加载内核模块,驱动开发人员可以通过调用:

request_module(module_name);

request_module("char-major-%d-%d",MAJOR(dev), MINOR(dev));

来加载其他内核模块。

3.模块卸载函数(必须)

当rmmod时,或则通过其他方式卸载模块时候会执行。

static void __exittiny4412_hello_module_cleanup(void)

{

                 /* 释放代码 */

          printk("Good-bye, Tiny4412 module wasremoved!\n");

}

module_exit(tiny4412_hello_module_cleanup);

说明:

(1)声明static,限定它的作用域,防止与其他文件下的同名函数起冲突。

(2)__exit声明它是一个卸载类型函数,在执行rmmod命令的时候执行。和__init 一样,__exit 也可以使对应函数在运行完成后自动回收内存。实际上,__init 和__exit 都是宏,其定义分别为:

#define __init __attribute__ ((__section__(".init.text")))

#ifdef MODULE

#define __exit __attribute__((__section__(".exit.text")))

#else

#define __exit __attribute_used____attribute__((__section__(".exit.text")))

#endif

对于数据也可以被定义为__initdata __exitdata,这两个宏分别为:

#define __initdata__attribute__ ((__section__(".init.data")))

#define __exitdata__attribute__((__section__(".exit.data")))

(3)没有形参,没有返回值。

(4)做为外部模块的时候,必须以“module_exit(函数名)”的形式来指定。

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

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

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

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

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

4.module_init和module_exit

(1)module_init 声明tiny4412_hello_module_init是一个初始化函数。

(2)module_exit 声明tiny4412_hello_module_cleanup是一个卸载函数。

        module_init(tiny4412_hello_module_init);

module_exit(tiny4412_hello_module_cleanup);

这两句话的作用跟__init,__exit作用相同,这里是为了双重保险。通常__init,__exit这个编译到内核的时候使用,而module_init,module_exit这两编译成模块使用。

5.MODULE_LICENSE("GPL");

声明该模块是基于GPL协议创建的,如果没有声明,当安装的时候提示该程序会污染我的内核。在Linux 2.6内核中,可接受的LICENSE包括“GPL”、“GPL v2”、“GPL and additional rights”、“Dual BSD/GPL”、“DualMPL/GPL”和“Proprietary”。

大多数情况下,内核模块应遵循 GPL 兼容许可权。

6.模块参数(可选)

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

7.模块导出符号(可选)

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

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

在 Linux 内核模块中, 我们可以用 MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_ VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS 分别声明模块的作者、描述、版本、设备表和别名,例如:

MODULE_AUTHOR(author);

MODULE_DESCRIPTION(description);

MODULE_VERSION(version_string);

MODULE_DEVICE_TABLE(table_info);

MODULE_ALIAS(alternate_name);

对于 USB、PCI 等设备驱动,通常会创建一个MODULE_DEVICE_TABLE驱动所支持的设备列表,如下所示:

 /* 对应此驱动的设备表 */

 staticstruct usb_device_id skel_table [] = {

        { USB_DEVICE(USB_SKEL_VENDOR_ID,

         USB_SKEL_PRODUCT_ID)},

         { }/* 表结束 */

};

 

MODULE_DEVICE_TABLE (usb, skel_table);

此时,并不需要读者理解MODULE_DEVICE_TABLE 的作用,后续相关章节会有详细介绍

小知识:

内核模块中用于输出的函数是内核空间的 printk()而非用户空间的 printf(), printk()的用法和 printf()相似,但前者可定义输出级别。printk()可作为一种最基本的内核调试手段。

 

1.5模块的编译方式

模块的编译通常有两种,一种是修改内核配置菜单,利用内核配置菜单编译;另一种是自己编写makefike文件,读取内核的makefile来编译。

第一种:修改内核配置菜单

1)把模块添加到内核源码目录;

2)然后做一个菜单,再把菜单配置为M选项;

3)再使用 make modules命令编译成.ko文件。

这种方式比较麻烦,繁琐。

第二种:自己编写makefile

独立编写一个Makefile来编译模块;这样模块源码可以放在任何地方,这种方法在开发中最常用。其实可以不需要Makefile,只需要一条命令就能搞定的。这条命令如下:

make -C 内核源码绝对路径M=模块源码文件所在的绝对路径 modules 

但是写成makefile是为了方便我们执行多步操作。

示例:编写一个编译模块的makefile文件通常包含以下内容。

指定编译目标:obj-m:=xxx.o

ARM板的内核源码路径:/works/linux-3.5/

模块源码路径 :/linux_share/hello_model_single

命令

make -C /root/work/linux-3.5/ M=/linux_share/hello_model_singlemodules 

这条命令完成的工作是进入到内核源码目录,读取内核源码目录的Makefile。(-C dir 是读入指定目录下的makefile),执行内核源码Makefile中的modules目标(这里我们自己知道了目标obj-m:=xxx.o),根据 modules 目标编译M所指向的文件路径下的 C文件。

为了更方便,一般我们写成Makefile文件。

单个文件单模块Makefile的模板

# Makefile 2.6

#hello最终的模块名,单文件单模块时,这个名字就是源码文件名,hello.o对应于hello.c

obj-m :=hello.o

#KDIR是内核源码路径,当编译用于X86平台模块的时候使用X86上的内核源码,当编译ARM的模块时候使用自己配套的Linux内核源码

#KDIR  :=/lib/modules/$(shell uname-r)/build

KDIR   := /works/linux-3.5/

all:

         make -C $(KDIR) M=$(PWD) modules  #$(PWD) 是代表当前路径,也就是模块源码路径

clean:

         rm -f *.ko *.o *.mod.o *.mod.c*.symvers *.markers *.unsigned *.order *~

一般只需根据自己的情况修改KDIR 和 obj-m := ? 内容就可以了。

         注意:KDIR所指向的内核源码一定要被成功编译过,没有清除工程才能编译模块。

1.6模块几种常见模型示例

1.6.1 单文件单模块示例

1.模块代码清单

/* hello.c */

#include<linux/module.h>       /* Needed byall modules */

#include <linux/init.h>         /* Needed for the module-macros */

 

static int __init hello_init(void)

{

         printk(KERN_DEBUG"Hello world, priority = 7\n");

         printk(KERN_INFO"Hello world, priority = 6\n");

         printk("Helloworld, priority = DEFAULT_MESSAGE_LOGLEVEL\n");

         printk(KERN_NOTICE"Hello world, priority = 5\n");

         printk(KERN_WARNING"Hello world, priority = 4\n");

         printk(KERN_ERR"Hello world, priority = 3\n");

         printk(KERN_CRIT"Hello world, priority = 2\n");

         printk(KERN_ALERT"Hello world, priority = 1\n");

         printk(KERN_EMERG"Hello world, priority = 0\n");

  return 0;

}

 

static void  __exit hello_exit(void) 

{

         printk(KERN_DEBUG"Goodbye,cruel world!, priority = 7\n");

         printk(KERN_INFO"Goodbye,cruel world!, priority = 6\n");

         printk("Goodbye,cruelworld!, priority = DEFAULT_MESSAGE_LOGLEVEL\n");

         printk(KERN_NOTICE"Goodbye,cruel world!, priority = 5\n");

         printk(KERN_WARNING"Goodbye,cruel world!, priority = 4\n");

         printk(KERN_ERR"Goodbye,cruel world!, priority = 3\n");

         printk(KERN_CRIT"Goodbye,cruel world!, priority = 2\n");

         printk(KERN_ALERT"Goodbye,cruel world!, priority = 1\n");

         printk(KERN_EMERG"Goodbye,cruel world!, priority = 0\n");

}

module_init(hello_init);

module_exit(hello_exit);

 

MODULE_LICENSE("DualBSD/GPL"); 

MODULE_AUTHOR("BENSON");

MODULE_DESCRIPTION("STUDY_MODULE");

2.makefile模板

# Makefile 2.6

obj-m := hello.o

KDIR  :=/lib/modules/$(shelluname -r)/build

#KDIR   :=/root/work/linux-3.5/

all:

         make -C $(KDIR)M=$(PWD) modules  

         @rm -f *.o *.mod.o*.mod.c *.symvers *.markers *.unsigned *.order *~

clean:

         rm -f *.ko *.o *.mod.o*.mod.c *.symvers *.markers *.unsigned *.order *~

3.编译,安装模块

如果编译给X86用的,因为printk的打印优先级问题,不会都打印到终端。

我们可以在终端输入tail/var/log/messages可以查看到日志文件最后10行,注意查看动作快点,要不然日志更新之后就看不到了。

如果编译给开发版使用,因为没有设置优先级,可以全部从串口打印输出。

1.6.2多模块之间有依赖关系示例

1.示例代码清单

首先我编写两个模块,它们之间是有依赖的。代码如下:

Calculate.c文件代码清单:

#include <linux/init.h>

#include <linux/module.h>

 

static int add_integar(int a,intb)

{

         returna+b;

}

 

static int sub_integar(int a,intb)

{

         returna-b;

}

 

static int __init sym_init()

{

         return0;

}

static void __exit sym_exit() {}

module_init(sym_init);

module_exit(sym_exit);

MODULE_LICENSE("GPL");

        //提供接口

EXPORT_SYMBOL(add_integar);

EXPORT_SYMBOL(sub_integar);

 

Hello.c代码清单:

#include <linux/module.h>

#include <linux/init.h>

 

//在其他地方定义,在这里声明之后可以在本文件使用

extern int add_integar(int a,int b);

extern int sub_integar(int a,int b);

 

static int __init hello_init(void)

{

         intres=add_integar(1,2);

         printk(KERN_EMERG"helloinit , res=%d\n",res);

         return0;

}

 

static void __exit hello_exit()

{

         intres=sub_integar(2,1);

         printk(KERN_EMERG"helloexit,res=%d\n",res);

}

 

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE("GPL");

 

2.makefile模板

依赖关系的Makefile可以写成以下形式:

# hello.o对应hello.c ,calculate.o对应calculate.c,编译后会生成两个ko文件。

obj-m := hello.ocalculate.o

#KDIR := /lib/modules/$(shell uname -r)/build

KDIR :=/root/work/linux-3.5/

all:

         @make -C $(KDIR) M=$(PWD) modules  

         @rm -f *.o *.mod.o *.mod.c *.symvers*.markers *.unsigned *.order *~ *.bak

clean:

         rm -f *.ko *.o *.mod.o *.mod.c*.symvers *.markers *.unsigned *.order *~ *.bak

注意:hello.c,calculate.c 必须是以模块代码框架格式编写,不能普通c代码格式

 

3.代码安装卸载操作分析

操作示例

[root@JUNJIA /home]# ls

calculate.ko            hello.ko                  //把以上的两个.C文件编译成模块        

[root@JUNJIA /home]# lsmod                        

[root@JUNJIA /home]# insmod hello.ko      //先安装hello.c

[ 7103.030000] hello: Unknown symboladd_integar (err 0)

[ 7103.030000] hello: Unknown symbolsub_integar (err 0)

insmod: can't insert 'hello.ko': unknown symbolin module or invalid parameter

提示错误,找不到add_integar,sub_integar符号。原因这两个函数具体实现代码不存在 hello.ko中,也不在当前的内核中,而是在calculate.c中。所以安装不成功。

那么怎么办呢?先安装 calculate.ko,这样add_integar,sub_integar这两个函数就在内核中了。

[root@JUNJIA /home]# insmod calculate.ko

再来安装hello.ko,在内核中就可以找到add_integar,sub_integar这两个函数,所以安装成功。

[root@JUNJIA /home]# insmod hello.ko

[ 7154.985000] hello init , res=3

 

如果先卸载calculate.ko提示失败,因为hello.ko还在使用这个模块中的函数。

[root@JUNJIA /home]# rmmod calculate

rmmod: remove 'calculate': Resource temporarily unavailable

查看,没有卸载成功。

[root@JUNJIA /home]# lsmod

hello 746 0 - Live 0xbf014000 (O)

calculate 951 1 hello, Live 0xbf010000 (O)

 

应该先卸载 hello.ko

[root@JUNJIA /home]# rmmod hello

[ 7214.570000] hello exit,res=1

rmmod: module 'hello' not found   //这一句busyboxBUG,并非是代码的错误

[root@JUNJIA /home]# lsmod

calculate 951 0 - Live 0xbf010000 (O)

再卸载calculate.ko,成功!

[root@JUNJIA /home]# rmmod calculate

rmmod: module 'calculate' not found

[root@JUNJIA /home]# lsmod

 

结论

1.一个模块可以提供函数给另外一个模块调用。

2.安装时候,先安装被调用的模块,再安装主调模块

3.卸载时候和安装顺序相反。

4.模块可以依赖多个模块。

 

如果修改代码,把 calculate.c中的以下两行注释掉:

//EXPORT_SYMBOL(add_integar);

//EXPORT_SYMBOL(sub_integar);

重新编译模块,重新安装:

[root@JUNJIA /home]# insmod calculate.ko

[root@JUNJIA /home]# lsmod

calculate 595 0 - Live 0xbf018000 (O)

[root@JUNJIA /home]# insmod hello.ko

[ 7766.860000] hello: Unknown symbol add_integar (err 0)

[ 7766.860000] hello: Unknown symbol sub_integar (err 0)

insmod: can't insert 'hello.ko': unknown symbol in module or invalidparameter

[root@JUNJIA /home]#

结论:如果一个模块的函数要给其他模块调用,需要本模块使用 EXPORT_SYMBOL导出。

1.6.3多文件单模块示例

就是多个C文件编译成一个.ko文件。

这种情况,只能有一个C文件是以代码模块编程格式编写其他的C文件只能是普通C代码文件。

1.示例代码

Calculate.c代码清单:

int add_integar(int a,int b)

{

         returna+b;

}

 

int sub_integar(int a,int b)

{

         returna-b;

}

 

Hello.c代码清单:

#include <linux/module.h>

#include <linux/init.h>

 

extern int add_integar(int a,intb);

extern int sub_integar(int a,intb);

 

static int __init hello_init(void)

{

         intres=add_integar(1,2);

         printk(KERN_EMERG"helloinit , res=%d\n",res);

         return0;

}

static void __exit hello_exit()

{

         intres=sub_integar(2,1);

         printk(KERN_EMERG"helloexit,res=%d\n",res);

}

 

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE("GPL");

Makefile 模板和单文件单模块有点不一样。

2.多文件单模块Makefiel模板示例

# mulc就是最终模块的名字,自己定义,并且不能和任何一个c文件同名

obj-m := mulc.o

 

#以下的格式:

#模块名-objs =模块的源文件对就的.o文件列表

#calculate.o hello.o分别对应calculate.c hello.c

mulc-objs = calculate.o hello.o

 

#linux内核源码路径

#KDIR  :=/lib/modules/$(shell uname -r)/build

KDIR := /root/work/linux-3.5/

 

all:

         @make-C $(KDIR) M=$(PWD) modules  

         @rm-f *.o *.mod.o *.mod.c *.symvers *.markers *.unsigned *.order *~ *.bak

clean:

         rm-f *.ko *.o *.mod.o *.mod.c *.symvers *.markers *.unsigned *.order *~ *.bak

其实这种Makefile 模板通用性比较,也适用单文件单模块的编译。

 

1.7模块传递参数

模块安装时候可以给模块中的变量传递数值。安装后,模块中变量的值就是安装时所传入的值;如果没有传递的变量,则使用代码中默认值。

1.7.1、模块传递参数知识点

带参数的模块安装后会生成/sys/module/模块名/parameters/这样的一个目录。

例如如果安装了名字为  hello_model_param 的模块;则会生成/sys/module/hello_model_param/parameters/。

这个文件夹下会生成以参数为名字文件,其内容就是参数的值。可以使用cat 或echo操作这个文件,修改变量的值。但是一般情况下都不建议修改,所以这个权限的值一般设置只读的

模块中如果变量要通过安装时传参数覆盖默认值,则需要使用以下宏修饰

module_param,module_param_array

1)对于非数组类型变量使用module_param

语法:module_param(变量名,变量类型,变量的访问权限) 

变量的访问权限:变量在文件系统以一个文件形式呈现,这个文件权限属性就是“变量的访问权限”。

变量传递参数:insmod xx.ko 变量名=值

 

2)对于数组类型变量使用module_param_array

语法:module_param_array(数组名,数组元素类型,&num,变量的访问权限)

         num 是一个 char ,int,long 或uchar,uint,ulong类型的变量,用于保存传递给数组的元素个数。

数组传递参数: insmod xx.ko 数组名=元素0,元素1,元素2,  ……

1.7.2所支持的数据类型

module_param,module_param_array所支持数据类型是限的

type支持的基本类型有:

         bool   :布尔类型

         invbool:颠倒了值的bool类型;

         charp  :字符指针类型,内存为用户提供的字符串分配;(char *)

         int    :整型

         long   :长整型

         short  :短整型

         uint   :无符号整型(unsigned int)

         ulong  :无符号长整型(unsigned long)

         ushort :无符号短整型(unsignedshort)

      指针类只能char*,对应名字 charp .

1.7.3权限类型

         使用 S_IRUGO 作为参数可以被所有人读取,但是不能改变;

S_IRUGO|S_IWUSR 允许 root 来改变参数。

module_param(age, int,S_IRUGO|S_IWUSR);  

         module_param(name,charp ,S_IRUGO);

         module_param_array(aa,int, &num, S_IRUGO);

1.7.4 模块传递示例

示例代码:

#include <linux/module.h>

#include <linux/init.h>

 

static int num = 0;

static char *name ="DAVID";

static int age  = 30;

static int aa[10] ={1,2,3,4,5,6,7,8,9,0};

 

//使用 S_IRUGO 作为参数可以被所有人读取,但是不能改变;

// S_IRUGO|S_IWUSR 允许 root 来改变参数。

 //S_IWUSR给用户写权限

module_param(age, int,S_IRUGO|S_IWUSR); 

module_param(name, charp,S_IRUGO);

module_param_array(aa, int,&num, S_IRUGO);

 

static int __init hello_init(void)

{

         inti = 0;

                  

         printk(KERN_EMERG"Name:%s\n",name);

         printk(KERN_EMERG"Age:%d\n",age); 

         for(i=0;i<10;i++)

                   printk(KERN_EMERG"%d  ",aa[i]);                

         printk(KERN_EMERG"num:%d  \n",num);            

         return0;

}

 

static void __exithello_exit(void)

{

         printk(KERN_EMERG"Moduleexit!\n");

}

 

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE("GPL");

这个例子是单个文件单个模块方式。编译它生成hello_model_param.ko

在开发板安装如下:

aa数组传递了3 个元素,输出num值就是3。

[root@JUNJIA /home]# insmodhello_model_param.ko aa=1,2,3

         [11294.715000] Name:DAVID

         [11294.715000] Age:30

         [11294.715000] 1 

         [11294.715000] 2  [11294.715000] 3 

         4 [11294.715000] 5 

         6 [11294.715000] 7 

         8 [11294.715000] 9 

         0 [11294.715000] num:3 

         [root@JUNJIA /home]#

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值