Advertisement

【笔记】Linux驱动学习第一章

阅读量:

作者:Exculivor
日期:2015年6月25日

    • 本次学习内容
    • 简单模块Hello的编写
    • 代码分析
    • 简单Makefile的编写以及分析
    • 模块的加载和卸载

本次学习内容:

  • 简单模块——Hello的编写
  • 代码分析
  • 简单Makefile的编写以及分析
  • 模块的加载和卸载

简单模块——Hello的编写

按照惯例,我们仍然通过helloworld这个简单的小程序来进入驱动编写的世界。
首先来看代码:

复制代码
    #include <linux/init.h>
    #include <linux/module.h>
    
    MODULE_LICENSE("Dual BSD/GPL");
    
    static int hello_init(void)
    {
        printk(KERN_ALERT "hello module init\n");
        return 0;
    }
    
    static void hello_exit(void)
    {
        printk(KERN_ALERT "hello module exit\n");
    }
    
    module_init(hello_init);
    module_exit(hello_exit);

代码分析

代码中包含的两个头文件:

复制代码
    #include <linux/init.h>

包含了模块初始化所需要的函数,

复制代码
    #include <linux/module.h>

则包含了动态的将模块加载到内核中去的一些函数。

接下来的的宏定义内容:

复制代码
    MODULE_LICENSE("Dual BSD/GPL");

则负责告诉内核,本模块采用的许可证是自由许可证;

“如果没有这样的许可证,内核在装载该模块时会产生抱怨。”

《linux设备驱动程序》中如是说,但是并没有详细阐明原因以及结果。我们将之注释掉后运行测试的结果为:

[13979.742518] hello: module license ‘unspecified’ taints kernel.
[13979.742572] Disabling lock debugging due to kernel taint
[13979.750285] hello module init

Linux遵循GPL协议,通常排斥非GPL软件。强行加入没有GPL许可证的模块直接导致内核判断自己被污染!

Linux内核识别的许可证有:

协议 简介
GPL 任意版本的GNU通用公共许可证
GPL v2 GPL第二版
GPL and additional rignts GPL及附加权利
Dual BSD/GPL BSD/GPL双许可证
Proprietary 专有

我们可根据相关协议的具体内容判断自己应该使用哪个协议,并包含进去。

接下来就是重点了。
这个模块的两个函数:

复制代码
    static int hello_init(void)
    {
        printk(KERN_ALERT "hello module init\n");
        return 0;
    }

的作用为打印一条语句:

hello module init

至于使用的打印函数则要注意并不是我们常用的 printf而是printk
这两个函数的功能类似,区别是printf的运行需要C库,而printk是内和自己的打印输出函数,并不依赖C库。
当模块加载到内核之中后,就可以访问内核的公用符号(包括函数和变量)。
后面的KERN_ALERT定义了这条消息的优先级。
根据不同的使用环境还有不同的的优先级:


字符串 优先级 简介
KERN_EMERG 0 紧急事件消息,系统崩溃之前提示,表示系统不可用
KERN_ALERT 1 报告消息,表示必须立即采取措施
KERN_CRIT 2 临界条件,通常涉及严重的硬件或软件操作失败
KERN_ERR 3 错误条件,驱动程序常用KERN_ERR来报告硬件错误
KERN_WARNING 4 警告条件,对可能出现问题的情况进行警告
KERN_NOTICE 5 正常但又重要的条件。常用于与安全相关的消息
KERN_INFO 6 提示信息,如驱动程序启动时,打印硬件信息
KERN_DEBUG 7 调试级别的消息

第二个函数:

复制代码
    static void hello_exit(void)
    {
        printk(KERN_ALERT "hello module exit\n");
    }

作用为输出另一条语句:

hello module exit

函数有了,按照我们的预想,加载模块的时候输出** hello module init** 卸载模块的时候输出** hello module exit** 。
但是写到这里还没完,内核并不知道什么时候该用哪个函数。所以我们需要为内核标记一下。于是我们调用两个函数:module_initmodule_exit来为内核指路。
加载模块时,内核会调用模块内传递给module_init的参数对应的函数,这里即hello_init函数。同样,退出时会调用传递给module_exit的参数对应的函数,这里是hello_exit


简单Makefile的编写以及分析

接下来我们需要编写Makefile来编译代码

复制代码
    ifeq ($(KERNELRELEASE),)
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    modules:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    clean:
        rm -rf *.o *.ko *.mod.c *.order *.symvers
    
    .PHONY: modules clean
    
    else
    obj-m := hello.o
    endif

注意格式,Makefile对格式要求严格。

复制代码
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

-C指定内核源代码目录,此处保存有顶层的Makefile,这个顶层Makefile即Linux内核编译系统的入口。
M=指定在构造modules目标之前返回到当前目录,即把要生成的modules目标放在当前目录下。
在此的构造过程中,该Makefile将被读取两次。
当从命令行执行make命令时,该Makefile将会被调用,此时是第一次读取该Makefile,变量KERNELRELEASE没有设置,ifeq条件不满足,所以会运行默认分支,定义KERNELDIRPWD:

复制代码
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)

并且执行默认编译选项modules

复制代码
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

从而第二次运行make命令,此次由于前面定义了KERNELDIR

复制代码
    ifeq ($(KERNELRELEASE),)

不成立,执行else分支:

复制代码
    obj-m := hello.o

即通过hello.o文件来生成一个目标模块,将目标模块名为hello.ko。
如果我们需要从多个文件生成目标模块,则可以使用:

复制代码
    obj-m := module.o
    module-objs := file1.o file2.o

之后可以通过shell命令make来生成hello.ko和通过shell命令make clean来清除构造生成的文件。

模块的加载和卸载

模块的加载使用shell命令:

复制代码
    insmod

模块的卸载使用shell命令:

复制代码
    rmmod

另外还可以通过shell命令:

复制代码
    lsmod

来列出当前加载运行的模块。

全部评论 (0)

还没有任何评论哟~