精华内容
下载资源
问答
  • Linux模块编程

    2010-07-25 14:51:00
    Linux模块编程

    转载http://blog.chinaunix.net/u/7217/showart.php?id=370441

    摘要

    Linux内核模块编程的资料有些纷繁复杂,有的过于简单,有的过于庞杂,我试图用笔记的形式想读者展示怎样来进程Linux模块编程,力图做到简明扼要,这篇文章也是作为本人备忘的资料,所以有些地方过于简略是难免的。本来这篇文章的目的就是让用户知其然,至于所以然还是请参考相应的资料,其实最好的资料莫过于Linux Kernel Source。

    适用范围:

    • Linux Kernel >= 2.6.0

    Linux模块简介

    首先这个module不同于microkernel的module,microkernel的module是一个个的daemon进程,工作于用户空间,Linux的module只是一个内核的目标代码,内核通过执行运行时的连接,来把它整合到kernel中去,所以说Linux的module机制并没有改变Linux内核为monolithic OS本质,其module也是工作于内核模式,享有内核的所有特权。

    至于为什么要引入Linux Kernle Module(一下简称LKM),我想至少有一下几点:

    • 模块化编程的需要,降低开发和维护成本。
    • 增强系统的灵活性,使得修改一些内核功能而不必重新编译内核和重启系统。
    • 降低内核编程的复杂性,使入门门槛降低。

    相关宏及头文件

    LKM需要包含以下头文件:<linux/kernel.h> <linux/module.h>

    需要定义以下宏:__KERNEL__, MODULE

    一个简单的内核模块示例

    /*file:   hello.c*/
    #ifndef __KERNEL__
    #define __KERNEL__
    #endif
    #ifndef MODULE
    #define MODULE
    #endif
    #include <linux/module.h>
    #include <linux/kernel.h>
    static int __init hello_init(void)
    {
    printk(KERN_ALERT "Hello, my LKM./n");
    return 0;
    }
    static void __exit hello_exit(void)
    {
    printk(KERN_ALERT "Bye, my LKM./n");
    }
    module_init(hello_init);
    module_exit(hello_exit);

    很简答吧,不是吗?这个LKM的功能其实也很简单,就是当通过insmod加载它的时候,他打印Hello, my LKM.通过rmmod卸载它的时候他打印bye, my LKM.一个最基本的内核模块一般都包含有两个函数,一个是初始化函数(比如说这里的hello_init),一个是卸载函数(hello_exit), 当然也可以没有任何函数,只是提供一些变量。但是初始化函数和卸载函数必须成对出现。并且init函数当操作成功时返回值大于等于零,当操作失败时,返回非零。宏module_init和module_exit用于注册初始化函数和卸载函数。

    LKM的编译

    一个示例的Makefile如下所示

    obj-m := hello.o 
    KERNELBUILD := /lib/modules/`uname -r`/build
    default:
    make -C $(KERNELBUILD) M=$(shell pwd) modules
    clean:
    rm -rf *.o .*.cmd *.ko *.mod.c .tmp_versions

    如果这个目录下面还有其它模块,只需要在hello.o后面添加就行了。

    obj-m := hello.o mod.o

    在模块所在目录执行make,等成功后就可以得到我们想要的模块(hello.ko)了。

    如果一个模块存在许多源文件,比如:hello, 由hello1.c hello2.c共同连接而成,需要在Makefile中加入如下行

    hello-objs := hello1.o hello2.o

    LKM的加载

    Linux为用户提供了modutils,用来操纵模块。这个工具集主要包括:

    insmod 安装模块
    rmmod 删除模块
    modprobe 比较高级的加载和删除模块,可以解决模块之间的依赖性
    lsmod 列出已经加载的模块和其信息
    modinfo 用于查询模块的相关信息,比如作者,版权...

    试着用命令insmod hello.ko加载模块,rmmod删除模块,看看有什么事情发生了。你有可能看不见任何输出,难道是有错误发生?No,执行命令tail /var/log/message呵呵,是不是看到了?

    Feb 19 00:07:35 gentux Hello, my LKM.
    Feb 19 00:07:38 gentux Bye, my LKM.

    模块其它信息

    比较常用信息常常包括:作者、描述、版权等,为此LKM为我们提供了如下宏:

    MODULE_AUTHOR("author");
    MODULE_DESCRIPTION("the description");
    MODULE_LICENSE("GPL");
    MODULE_SUPPORTED_DEVICE("dev"); // 设备驱动程序所支持的设备。

    比较常用的Free license有"GPL", "GPL v2", "GPL and additional rights", "Dual BSD/GPL", "Dual MPL/GPL"。

    模块参数

    用户空间的应用程序可以接受用户的参数,LKM也可以做到,只是方式有些不同而已。相关的宏有:

    MODULE_PARM(var, type);
    MODULE_PARM_DESC(var, "the description of the var");

    模块参数的类型(即MODULE_PARM中的type)有一下几种:

    • b byte(unsigned char)
    • h short
    • i int
    • l long
    • s string(char*)

    这些参数最好有默认值,如果有些必要参数用户没有设置可以通过在module_init指定的init函数返回负值来拒绝模块的加载。 LKM还支持数组类型的模块,如果在类型符号前加上数字n则表示最大程度为n的数组,用“-”隔开的数字分别代表最小和最大的数组长度。

    示例:
    MODULE_PARM(var, "4i"); // 最大长度为4的整形数组
    MODULE_PARM(var, "2-6i"); // 最小长度为2,最大长度为6的整形数组

    如何用insmod传入参数,其实man一下就可以了,不过现在的man有些过于简单,所以在此说明一下:

    insmod variable=value[,value2...] ...

    其中value可以用引号括起来,也可以不用。但是有一点“=”前后不能留有空格,并且value中也不能有空格。

    模块符号的导出

    和用户空间的应用程序不同的是,引入一个模块的目的常常是为了给内核提供一些routine,来完成特定的功能,很少有模块什么符号都不导出,为此Linux为用户提供了如下宏:

    EXPORT_SYMBOL(var); // 输出symbol var
    EXPORT_SYMBOL_GPL(var); // 输出的symbol版权为GPL

    模块之间的依赖性

    有的时候两个模块之间可能有依赖性,要加载的模块A,依赖于模块B,此时insmod是无能为力的,只能用modprobe来加载模块和其依赖的模块,否则只能手动一个个加载。

    modprobe通过读取由depmod -a生成的/lib/modules/version/modules.dep来获得其所依赖的模块列表(也有可能是一个模块树),然后调用insmod来一个个按顺序加载。

    命名空间的问题

    1. 对于不需要export的全局symbol最好用static进行修饰,限制其作用域为本文件,以防污染内核的命名空间。
    2. 对于由内核或其它模块export的一些symbol,最好用extern进行修饰,以示其不在本文件。
    3. 在可能用到errno变量的场合,因为内核没有export此symbol,只能有用户自行定义,比如:int errno;

    一个较复杂的模块示例

    /*file:   hello.c*/
    #ifndef __KERNEL__
    #define __KERNEL__
    #endif
    #ifndef MODULE
    #define MODULE
    #endif
    #include <linux/module.h>
    #include <linux/kernel.h>
    MODULE_AUTHOR("xiaosuo <xiaosuo@gmail.com>");
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("This module is a example.");
    static int int_var = 0;
    static const char *str_var = "default";
    static int int_array[6];
    MODULE_PARM(int_var, "i");
    MODULE_PARM_DESC(int_var, "A integer variable");
    MODULE_PARM(str_var, "s");
    MODULE_PARM_DESC(str_var, "A string variable");
    MODULE_PARM(int_array, "2-6i");
    MODULE_PARM_DESC(int_array, "A integer array");
    static int __init hello_init(void)
    {
    int i;
    printk(KERN_ALERT "Hello, my LKM./n");
    printk(KERN_ALERT "int_var %d./n", int_var);
    printk(KERN_ALERT "str_var %s./n", str_var);
    for(i = 0; i < 6; i ++){
    printk("int_array[%d] = %d/n", i, int_array[i]);
    }
    return 0;
    }
    static void __exit hello_exit(void)
    {
    printk(KERN_ALERT "Bye, my LKM./n");
    }
    module_init(hello_init);
    module_exit(hello_exit);

    参考资料

    The Linux Kernel Module Programming Guide 

    Linux 内核编程指南(第三版) 

    Linux内核分析及编程 

    展开全文
  • linux模块编程

    2013-06-27 13:46:35
    linux模块编程的流程: 一,编写源文件,如mymodule.c 点击(此处)折叠或打开#include linux/module.h>#include linux/kernel.h>#include linux/init.h>static int __init lkp_init(void){ printk("hello\n");...
      linux模块编程的流程:
      一,编写源文件,如mymodule.c
     

    点击(此处)折叠或打开

    1. #include <linux/module.h>
    2. #include <linux/kernel.h>
    3. #include <linux/init.h>

    4. static int __init lkp_init(void)
    5. {
    6.         printk("hello\n");
    7.         return 0;
    8. }

    9. static void __exit lkp_exit(void)
    10. {
    11.         printk("exit\n");
    12. }

    13. module_init(lkp_init);
    14. module_exit(lkp_exit);
    15. MODULE_LICENSE("GPL");
    二.编写Makefile文件

    点击(此处)折叠或打开

    1. #Makefile2.6
    2. obj-m :=mymodule.o
    3. CURRENT_PATH:=$(shell pwd)
    4. LINUX_KERNEL:=$(shell uname -r)
    5. LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
    6. all:
    7.         make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
    8. clean:
    9.         make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
    三 .执行 make命令,生成内核模块
    四. 用root身份执行insmod mymodule.ko,插入内核模块。
    五. 用root身份执行rmmod mymodule,删除内核模块。


    <script type=text/javascript charset=utf-8 src="http://static.bshare.cn/b/buttonLite.js#style=-1&uuid=&pophcol=3&lang=zh"></script> <script type=text/javascript charset=utf-8 src="http://static.bshare.cn/b/bshareC0.js"></script>
    阅读(171) | 评论(0) | 转发(0) |
    0

    上一篇:安装linux源码

    下一篇:模拟linux内核哈希表

    给主人留下些什么吧!~~
    评论热议
    展开全文
  • linux 模块编程

    2015-06-08 15:45:24
    M=选项让该makefile在构造modules目标之前返回到模块源代码目录。然后,modules目标指向obj-m变量中指定的模块; 在上面的例子中,我们将该变量设置为hello.o obj-m:=rt_process.o PWD := $(shell pwd) ...

    makefile文件

    obj-m := hello.o 
    KERNELDIR := /lib/modules/2.6.20/build 
    PWD := $(shell pwd) 
    
    modules: 
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 
    
    modules_install: 
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install 

    如果有多个依赖文件的话:

    obj-m := hello.o
    hello-objs := file1.o file2.o
    这里的-c选项是指首先改变目录到-C指定的位置(即内核源代码的目录),其中保存着内核的顶层makefile文件。M=选项让该makefile在构造modules目标之前返回到模块源代码目录。然后,modules目标指向obj-m变量中指定的模块;在上面的例子中,我们将该变量设置为hello.o

    obj-m:=rt_process.o
    	PWD := $(shell pwd)
    modules:
    	$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    
    modules_install:
    	$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules_install

    模块示范代码

    #include<linux/init.h>  
    #include<linux/types.h>  
    #include<linux/kthread.h>  
    #include<linux/module.h>  
    #include<linux/export.h>  
    #include <linux/delay.h>  
    #include<linux/slab.h>  
    #include <linux/fs.h>  
    #include<asm/uaccess.h>  
      
    #ifndef SLEEP_MSEC  
    #define SLEEP_MSEC(nMS) \  
    do{\  
    unsigned long timeout = (nMS)*HZ/1000;\  
    while(timeout>0){\  
    timeout=schedule_timeout(timeout);\  
    }\  
    }while(0);   
    #endif  
      
    static struct task_struct * MyThread = NULL;  
    static int MyPrint(void *data)  
    {  
        printk(KERN_ALERT "beginning  Thd!\n");  
        while(!kthread_should_stop())  
    {  
            SLEEP_MSEC(10);  
         你的测试代码;  
    }  
    return 0;  
    }  
    static int __init   test_init(void)  
    {  
        printk(KERN_ALERT "loading  module!\n");  
        MyThread = kthread_create(MyPrint,NULL,"mythread");  
        if(IS_ERR(MyThread)){  
            printk(KERN_ALERT" task creation fails!\n");              
            return -1;  
        }else{  
            kthread_bind(MyThread,1);  
        }  
        wake_up_process(MyThread);  
        printk(KERN_ALERT "Task waked!\n");  
        return 0;  
    }  
      
    static void __exit test_exit(void)  
    {  
        if(MyThread)  
        {  
            kthread_stop(MyThread);  
            printk(KERN_ALERT "stop  thread!\n");         
            MyThread = NULL;  
        }  
        printk(KERN_ALERT "unload module!\n");    
        return 0;  
    }  
      
    module_init(test_init);  
    module_exit(test_exit);  

    makefile的一些总结:

    在讲述Makefile之前,还是让我们先来粗略地看一看Makefile的规则。

    target ... : prerequisites ...
    command
    ...
    ...

    target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。

    prerequisites就是,要生成那个target所需要的文件或是目标。

    command也就是make需要执行的命令。(任意的Shell命令)

    这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

    在默认的方式下,也就是我们只输入make命令。那么,

    1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
    2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
    3、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
    4、如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
    5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。

    通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。

    makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。于是,我们就可以很方便地在我们的makefile中以“$(objects)”的方式来使用这个变量了。

    自动化变量:

    $@ : 规则的目标所对应的所有文件
    $< : 规则中的第一个相关文件
    $@  target
    $%  file name of an archive member
    $<  first prerequisite
    $?  prerequisites newer than target
    $^  prerequisites
    $+  similar to $^, including duplicates
    $*  stem of the target filename

    文件搜寻


    在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。

    Makefile文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。

    VPATH = src:../headers

    上面的的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)


    另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:

    1、vpath <pattern> <directories>

    为符合模式<pattern>的文件指定搜索目录<directories>。

    2、vpath <pattern>

    清除符合模式<pattern>的文件的搜索目录。

    3、vpath

    清除所有已被设置好了的文件搜索目录。

    vapth使用方法中的<pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。<pattern>指定了要搜索的文件集,而<directories>则指定了<pattern>的文件集的搜索的目录。例如:

    vpath %.h ../headers

    该语句表示,要求make在“../headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)

    我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的<pattern>,或是被重复了的<pattern>,那么,make会按照vpath语句的先后顺序来执行搜索。如:

    vpath %.c foo
    vpath % blish
    vpath %.c bar

    其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。

    vpath %.c foo:bar
    vpath % blish

    而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。


    make的参数


    下面列举了所有GNU make 3.80版的参数定义。其它版本和产商的make大同小异,不过其它产商的make的具体参数还是请参考各自的产品文档。

    “-b”
    “-m”
    这两个参数的作用是忽略和其它版本make的兼容性。

    “-B”
    “--always-make”
    认为所有的目标都需要更新(重编译)。

    “-C <dir>”
    “--directory=<dir>”
    指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:“make –C ~hchen/test –C prog”等价于“make –C ~hchen/test/prog”。

    “—debug[=<options>]”
    输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是<options>的取值:
    a —— 也就是all,输出所有的调试信息。(会非常的多)
    b —— 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。
    v —— 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等。
    i —— 也就是implicit,输出所以的隐含规则。
    j —— 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。
    m —— 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。

    “-d”
    相当于“--debug=a”。

    “-e”
    “--environment-overrides”
    指明环境变量的值覆盖makefile中定义的变量的值。

    “-f=<file>”
    “--file=<file>”
    “--makefile=<file>”
    指定需要执行的makefile。

    “-h”
    “--help”
    显示帮助信息。

    “-i”
    “--ignore-errors”
    在执行时忽略所有的错误。

    “-I <dir>”
    “--include-dir=<dir>”
    指定一个被包含makefile的搜索目标。可以使用多个“-I”参数来指定多个目录。


    “-j [<jobsnum>]”
    “--jobs[=<jobsnum>]”
    指同时运行命令的个数。如果没有这个参数,make运行命令时能运行多少就运行多少。如果有一个以上的“-j”参数,那么仅最后一个“-j”才是有效的。(注意这个参数在MS-DOS中是无用的)

    “-k”
    “--keep-going”
    出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行了。

    “-l <load>”
    “--load-average[=<load]”
    “—max-load[=<load>]”
    指定make运行命令的负载。

    “-n”
    “--just-print”
    “--dry-run”
    “--recon”
    仅输出执行过程中的命令序列,但并不执行。

    “-o <file>”
    “--old-file=<file>”
    “--assume-old=<file>”
    不重新生成的指定的<file>,即使这个目标的依赖文件新于它。

    “-p”
    “--print-data-base”
    输出makefile中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出一堆信息。如果你只是想输出信息而不想执行makefile,你可以使用“make -qp”命令。如果你想查看执行makefile前的预设变量和规则,你可以使用“make –p –f /dev/null”。这个参数输出的信息会包含着你的makefile文件的文件名和行号,所以,用这个参数来调试你的makefile会是很有用的,特别是当你的环境变量很复杂的时候。

    “-q”
    “--question”
    不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是0则说明要更新,如果是2则说明有错误发生。

    “-r”
    “--no-builtin-rules”
    禁止make使用任何隐含规则。

    “-R”
    “--no-builtin-variabes”
    禁止make使用任何作用于变量上的隐含规则。

    “-s”
    “--silent”
    “--quiet”
    在命令运行时不输出命令的输出。

    “-S”
    “--no-keep-going”
    “--stop”
    取消“-k”选项的作用。因为有些时候,make的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的“-k”选项失效。

    “-t”
    “--touch”
    相当于UNIX的touch命令,只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行。

    “-v”
    “--version”
    输出make程序的版本、版权等关于make的信息。

    “-w”
    “--print-directory”
    输出运行makefile之前和之后的信息。这个参数对于跟踪嵌套式调用make时很有用。

    “--no-print-directory”
    禁止“-w”选项。

    “-W <file>”
    “--what-if=<file>”
    “--new-file=<file>”
    “--assume-file=<file>”
    假定目标<file>需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作。如果没有“-n”那么就像运行UNIX的“touch”命令一样,使得<file>的修改时间为当前时间。

    “--warn-undefined-variables”
    只要make发现有未定义的变量,那么就输出警告信息。
    展开全文
  • Linux 模块编程指南

    千次阅读 2016-11-22 15:28:13
    Linux模块编程 1.1 模块学习什么 1.认识什么是模块?跟我学习的驱动有什么关系? 2.熟悉模块的安装,卸载,查看 3.熟悉模块的基本框架 4.熟悉模块的编程方法 1.2 内核模块概述 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]#

    展开全文
  • linux模块编程测试

    2013-04-22 22:04:27
    linux模块编程测试 趁着今天天气好,进行了一下测试。 我的环境是:BT5R3 内核:3.2.6 第一步:在任意一个目录下新建一个C文件。例如/root/Desktop/KernelModule/hello.c 第二步:编辑C文件,输入一下内容: #...
  • Linux内核模块编程的资料有些纷繁复杂,有的过于简单,有的过于庞杂,我试图用笔记的形式想读者展示怎样来进程Linux模块编程,力图做到简明扼要,这篇文章也是作为本人备忘的资料,所以有些地方过于简略是难免的。...
  • Linux模块编程[ZT]

    2011-05-16 00:58:00
    http://blog.chinaunix.net/u/7217/showart.php?id=370441]摘要 Linux内核模块编程的资料有些纷繁复杂,有的过于简单,有的过于庞杂,我试图用笔记的形式想读者展示怎样来进程Linux模块编程,力图做到简明扼要...
  • Linux模块编程框架

    2016-12-13 21:15:00
    Linux是单内核系统,可通用计算平台的外围设备是频繁变化的,不可能将所有的(包括将来即将出现的)设备的驱动程序都一次性编译进内核,为了解决这个问题,Linux提出了可加载内核模块(Loadable Kernel Module,LKM)的...
  • Linux模块编程-Linux(17)

    2018-06-01 16:34:24
    今天主要写一写Linux内核模块编程,是我的操作系统实验课内容,如果想要看看内核系统调用编程的,看这个 https://blog.csdn.net/babybabyup/article/details/79839734 (也是我的实验课内容。。。) 提出问题: ...
  • Linux模块编程方法总结

    千次阅读 2018-06-30 15:29:35
    一、编写一个基本的内核模块1、编辑源文件,代码hello.c#include &lt;linux/init.h&gt;#include &lt;linux/kernel.h&gt;#include &lt;linux/module.h&gt; int init_hello_module(void){ ...
  • 关于linux模块编程

    千次阅读 2015-03-25 17:05:08
    刚开始看linux设备驱动,就是大家都比较推崇的《LINUX设备驱动程序》(第三版)。看到了第一个hellworld的示例,就在自己的系统上试了一下,然后不那么爽。具体代码如下: 1 #include <linux/kernel.h> 2 #include ...
  • 2.Linux模块编程

    2014-12-08 21:09:22
    2.1模块编程结构 1.模块加载函数 int test_init ( void) { ..... return 0; } module_init(test_init); 2.模块卸载函数 void test_exit(void) { ..... module_exit(test_exit); } 3.一个标准的Linux的内核模块程序 ...
  • 编写Linux Module基础知识Modules是可以自由装载进内核(从内核卸载)的一块代码,不用重启系统就能扩展内核功能。设备驱动是一种module本文所说的Linux ...应用编程和内核编程对比Item应用编程内核编程使用函数glibc(...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,826
精华内容 1,530
关键字:

linux模块编程

linux 订阅