linux设备驱动--HelloWorld

linux设备驱动--HelloWorld

最近正在学习设备驱动开发,因此打算写一个系列博客,即是对自己学习的一个总结,也是对自己的一个督促,有不对,不足,需要改正的地方还望大家指出,而且希望结识志同道合的朋友一起学习技术,共同进步。

作者:liufei_learning(转载请注明出处)

email:flying0216@foxmail.com 

IT学习交流群:160855096

开发环境Win7(主机) + VisualBox + ubuntu10.10(虚拟机) + TQ2440开发板(2.6.30.4内核)

功能:    1.ubuntu下helloworld模块的实现

             2.TQ2440下helloworld模块的实现

目录:   1.源码

         2.实现步骤
           Ubuntu下helloworld模块的实现
           遇到的问题及解决方案
           TQ2440下helloworld模块的实现
           遇到的问题及解决方案

         3.分析
           1)__init__exit
           2)module_init() 与 module_exit()
           3)MODULE_LICENSE
           4)Makefile解析
           5)printk()解析    

源码:

    /* hello.c */  
    #include <linux/module.h>  
    #include <linux/init.h>  
      
    MODULE_LICENSE("GPL");  
    MODULE_AUTHOR("liufei_learning");  
    MODULE_DESCRIPTION("Hello world module");  
      
    static int __init hello_init(void)  
    {  
        printk(KERN_NOTICE "hello world!\n");  
        return 0;  
    }  
    static void __exit hello_exit(void)  
    {  
        printk(KERN_NOTICE "hello exit!\n");  
    }  
      
    module_init(hello_init);  
    module_exit(hello_exit);  

Makefile
    ifneq ($(KERNELRELEASE),)  
      
    obj-m :=hello.o  
      
    else  
    KDIR:= /lib/modules/2.6.35-22-generic/build  
      
    all:  
        make -C $(KDIR) M=$(PWD) modules   
    clean:  
        rm -f *.ko *.o *.mod.o *.mod.c .symvers  
      
      
    endif  


实现步骤:

ubuntu下helloworld模块的实现

1.建立hello.c Makefile文件

Makefile文件注意红色部分,2.6.35-22-generic改为本机的

ifneq($(KERNELRELEASE),)

obj-m :=hello.o

else

KDIR:= /lib/modules/2.6.35-22-generic/build

2.进入所在目录执行 make命令
3.加载模块insmod hello.ko
4.查看以安装模块 lsmod 
实验结果:


5.卸载模块rmmod
遇到的问题:

printk无法打印,这是因为printk无法再图形界面下显示在ubuntu里使用printk函数打印的信息被写到/var/log/syslog里, 使用dmesg-c 也可查看

实验结果:



TQ2440下helloworld模块的实现

1.在内核源码 drivers/char/下建立一个 fly_hello.c文件(内容同上)

2.修改同目录下Kconfig

menu "Character devices"后添加

config FLY_HELLO
tristate"TQ2440 Hello Driver"
dependson ARCH_S3C2440

---help---
  TQ2440 Hello Driver

3.修改同目录下Makefile文件

obj-y        += mem.o random.o tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o tty_buffer.otty_port.o后添加

obj-$(CONFIG_FLY_HELLO)        +=fly_hello.o

4.配置内核make menuconfig

DeviceDrivers--->

          Character device---->

                  <M>TQ2440 Hello Driver

5.makeSUBDIR=drivers/char/ modules

6.复制drivers/char/下的fly_hello.ko开发板/lib/modules/2.6.30.4-EmbedSky

7.insmod rmmod

结果截图:


 

遇到的问题:

卸载的时候出现rmmod: chdir(/lib/modules): No such file or directory错误

 现在的内核模块在插入卸载时都会要转到/lib/modules/内核版本号/这个目录里。所以只要建立这个目录并且把要使用的模块.ko文件复制到这个目录就行了。


分析:

.__init __exit

/* These macros are used to mark some functions or  
* initialized data (doesn't apply to uninitialized data) 
* as `initialization' functions. The kernel can take this 
* as hint that the function is used only during the initialization 
* phase and free up used memory resources after 

* Usage: 
* For functions: 
*  
* You should add __init immediately before the function name, like: 
* 
* static void __init initme(int x, int y) 
* { 
*    extern int z; z = x * y; 
* } 
* 
* If the function has a prototype somewhere, you can also add 
* __init between closing brace of the prototype and semicolon: 
* 
* extern int initialize_foobar_device(int, int, int) __init; 
* 
* For initialized data: 
* You should insert __initdata between the variable name and equal 
* sign followed by value, e.g.: 
* 
* static int init_variable __initdata = 0; 
* static const char linux_logo[] __initconst = { 0x32, 0x36, ... }; 
* 
* Don't forget to initialize data not at file scope, i.e. within a function, 
* as gcc otherwise puts the data into the bss section and not into the init 
* section. 
*  
* Also note, that this data cannot be "const". 
*/  
 
/* These are for everybody (although not all archs will actually discard it in modules) */  
#define __init          __section(.init.text) __cold notrace  
#define __initdata      __section(.init.data)  
#define __initconst     __section(.init.rodata)  
#define __exitdata      __section(.exit.data)  
#define __exit_call     __used __section(.exitcall.exit)  

这些宏被用来标记一些函数或者初始化数据(不适用于未初始化数据)作为初始化函数。此功能仅仅被用在初始化阶段,内核以此作为线索在使用后释放内存资源

用法:

static void __initinitme(int x, int y)
{
       extern int z; z = x * y;
}

extern intinitialize_foobar_device(int, int, int) __init;

static intinit_variable __initdata = 0;

static const char

 __init的使用会在初始化完成后丢弃该函数并收回所占内存,如果该模块被编译进内核,而不是动态加载。

宏__initdata同__init 类似,只不过对变量有效。

宏__exit将忽略“清理收尾”的函数如果该模块被编译进内核。同宏 __init一样,对动态加载模块是无效的。这很容易理解。编译进内核的模块是没有清理收尾工作的,而动态加载的却需要自己完成这些工作。

这些宏在头文件linux/init.h定义,用来释放内核占用的内存。 当你在启动时看到这样的Freeingunused kernel memory: 236k freed内核输出,上面的 那些正是内核所释放的。


.module_init()module_exit()

    258/** 259 * module_init() - driver initialization entry point 
     260 * @x: function to be run at kernel boot time or module insertion 
     261 *  
     262 * module_init() will either be called during do_initcalls() (if 
     263 * builtin) or at module insertion time (if a module).  There can only 
     264 * be one per module. 
     265 */  
     266#define module_init(x)  __initcall(x);  
     267  
     268/** 
     269 * module_exit() - driver exit entry point 
     270 * @x: function to be run when driver is removed 
     271 *  
     272 * module_exit() will wrap the driver clean-up code 
     273 * with cleanup_module() when used with rmmod when 
     274 * the driver is a module.  If the driver is statically 
     275 * compiled into the kernel, module_exit() has no effect. 
     276 * There can only be one per module. 
     277 */  
     278#define module_exit(x)  __exitcall(x);  
module_init():驱动的入口点,在内核启动或动态加载模块时被调用

module_exit():驱动的结束点,动态卸载模块时被调用,如果被静态链接在内核,则module_exit()没有效果

.MODULE_LICENSE

    /* 
     115 * The following license idents are currently accepted as indicating free 
     116 * software modules 
     117 * 
     118 *      "GPL"                           [GNU Public License v2 or later] 
     119 *      "GPL v2"                        [GNU Public License v2] 
     120 *      "GPL and additional rights"     [GNU Public License v2 rights and more] 
     121 *      "Dual BSD/GPL"                  [GNU Public License v2 
     122 *                                       or BSD license choice] 
     123 *      "Dual MIT/GPL"                  [GNU Public License v2 
     124 *                                       or MIT license choice] 
     125 *      "Dual MPL/GPL"                  [GNU Public License v2 
     126 *                                       or Mozilla license choice] 
     127 * 
     128 * The following other idents are available 
     129 * 
     130 *      "Proprietary"                   [Non free products] 
     131 * 
     132 * There are dual licensed components, but when running with Linux it is the 
     133 * GPL that is relevant so this is a non issue. Similarly LGPL linked with GPL 
     134 * is a GPL combined work. 
     135 * 
     136 * This exists for several reasons 
     137 * 1.   So modinfo can show license info for users wanting to vet their setup  
     138 *      is free 
     139 * 2.   So the community can ignore bug reports including proprietary modules 
     140 * 3.   So vendors can do likewise based on their own policies 
     141 */  
     142#define MODULE_LICENSE(_license) MODULE_INFO(license, _license)  
     143  
     144/* 
     145 * Author(s), use "Name <email>" or just "Name", for multiple 
     146 * authors use multiple MODULE_AUTHOR() statements/lines. 
     147 */  
     148#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)  
     149    
     150/* What your module does. */  
     151#define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description)  


MODULE_DESCRIPTION()用来描述模块的用途。

 MODULE_AUTHOR()用来声明模块的作者。

MODULE_SUPPORTED_DEVICE() 声明模块支持的设备….


.Makefile解析

KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make将读取执行else之后的内容,如果make的目标是clean,直接执行clean操作,然后结束。当make的目标为all时,-C $(KDIR)指明跳转到内核源码目录下读取那里的MakefileM=$(PWD)表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句,指明模块源码中各文件的依赖关系,以及要生成的目标模块名。


.printk()

内核通过printk() 输出的信息具有日志级别,日志级别是通过在 printk() 输出的字符串前加一个带尖括号的整数来控制的,如printk("<6>Hello, world!/n");。内核中共提供了八种不同的日志级别,在 linux/kernel.h中有相应的宏对应。

#defineKERN_EMERG    "<0>"    /* systemis unusable */
#defineKERN_ALERT    "<1>"    /* actionmust be taken immediately */
#defineKERN_CRIT     "<2>"    /*critical conditions */
#defineKERN_ERR      "<3>"   /* error conditions */
#defineKERN_WARNING  "<4>"    /* warningconditions */
#defineKERN_NOTICE   "<5>"    /* normal butsignificant */
#defineKERN_INFO     "<6>"    /*informational */
#defineKERN_DEBUG    "<7>"    /*debug-level messages */

所以 printk()可以这样用:printk(KERN_INFO "Hello, world!/n");。

未指定日志级别的 printk()采用的默认级别是 DEFAULT_MESSAGE_LOGLEVEL,这个宏在 kernel/printk.c 中被定义为整数4,即对应KERN_WARNING。

在/proc/sys/kernel/printk 会显示4个数值(可由 echo修改),分别表示当前控制台日志级别、未明确指定日志级别的默认消息日志级别、最小(最高)允许设置的控制台日志级别、引导时默认的日志级别。当 printk()中的消息日志级别小于当前控制台日志级别时,printk 的信息(要有/n符)就会在控制台上显示。但无论当前控制台日志级别是何值,通过 /proc/kmsg(或使用dmesg)总能查看。另外如果配置好并运行了 syslogd 或 klogd,没有在控制台上显示的 printk 的信息也会追加到 /var/log/syslog 中。 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值