linux设备驱动之hello

来源:互联网 发布:linux c 编译器 编辑:程序博客网 时间:2024/06/04 23:16
这段时间对linux设备驱动的学习以及写了某些设备的驱动程序,让我对linux设备驱动的运行机制有了些微了解。现把学习过程进行总结,一是记录下自己的学习历程,对所学的知识进行梳理;二是随着学习的不断深入,在以后的学习中就能通过与自己之前所学进行对比,把当前有些并不是很懂的地方给重新理解,搞透。我认为通过这种学习方式提高自己独立分析问题,解决问题的能力是很有必要的。
      驱动程序在linux内核里扮演者特殊的角色。它们是截然不同的“黑盒子”,使硬件的特殊的一部分响应定义好的内部编程接口,它们完全隐藏了设备工作的细节。例如LED,此属于硬件。对于应用程序开发人员只需要知道操作某些函数让LED Turn On或Turn OFF就行了。它们不需要知道LED通过哪些寄存器点亮点灭,这些是由驱动开发人员打包封装提供给上层应用程序。
     下面开始写一个Hello Woeld的模块来认识如何写一个驱动。
1.hello.c驱动文件



/*********************************************************************************
 *      Copyright:  (C) 2017 Huang Weiming<710564672@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  hello.c
 *    Description:  This file
 *
 *        Version:  1.0.0(2017年04月14日)
 *         Author:  Huang Weiming <710564672@qq.com>
 *      ChangeLog:  1, Release initial version on "2017年04月14日 13时29分29秒"
 *
 ********************************************************************************/

#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");

static __init int hello_init(void)
{
    printk(KERN_ALERT "hello world!\n");
    return 0;
}

static __exit void hello_exit(void)
{
    printk(KERN_ALERT "goodbye!\n");
}

module_init(hello_init);
module_exit(hello_exit);

 #include <linux/init.h> 这个头文件是所有的linux驱动必须包含的,#include <linux/module.h>这个头文件是所有模块都要包含的   MODULE_LICENSE是一个宏,声明了驱动的版权信息,此处使用BSD/GPL双重授权,static静态函数,说明这个函数只被这个C文件引用。接下来对printk进行说明
printk用于内核打印消息,每个printk()声明都会带一个优先级,内核总共定义了八个优先级的宏。

printf根据日志级别(loglevel)对消息进行分类;日志级别用宏定义,日志级别宏展开伟一个字符串,在编译时由预处理器将它和消息文本拼接成一个字符串,因此printk函数中日志级别宏和格式字符串间不能有逗号,例如 printk(KERN_ALERT "Hello world\n");
printk的打印级别一共有8个级别:数字越小说明打印级别越高

#define KERN_EMERG        "<0>" /* system is unusable */
#define KERN_ALERT         "<1>" /* action must be taken immediately */
#define KERN_CRIT            "<2>" /* critical conditions */
#define KERN_ERR             "<3>" /* error conditions */
#define KERN_WARNING   "<4>" /* warning conditions */
#define KERN_NOTICE       "<5>" /* normal but significant condition */
#define KERN_INFO            "<6>" /* informational */
#define KERN_DEBUG       "<7>" /* debug-level messages */

日志级别的范围是0~7,没有指定日志级别的printk语句默认采用的级别是 DEFAULT_ MESSAGE_LOGLEVEL,其定义列出如下
          #define DEFAULT_MESSAGE_LOGLEVEL 4
当优先级低于int console_loglevel,信息将直接打印在你的终端上。如果同时 syslogdklogd都在运行,信息也同时添加在文件 /var/log/messages,而不管是否显示在控制台上与否。
当然了,由于打印级别不够的那些信息,并不是内核没有打印,而是在后台输入到了日志文件中去,我们可以通过dmesg命令查看得到那些打印信息
我们使用像 KERN_ALERT这样的高优先级,来确保printk()将信息输出到 控制台而不是只是添加到日志文件中。 当你编写真正的实用的模块时,你应该针对可能遇到的情况使用合 适的优先级。

module_init是一个宏 linux内核在编译的时候,一个C程序最后一步是链接,链接里面有很多段,堆,栈,一个可执行程序包含数据段和文本段,数据段又包含 .bss: 未初始化的全局变量或static变量、 data: 初始化过的全局变量或static变量、 .rodata: const, #define,char *ptr="string"等 定义的数据常量。linux内核又添加了额外的两个段,init段和exit段,module_init是一个宏,告诉linux内核在连接的时候把这个函数放到init段去。所有init声明的函数都放到init段,linux一启动加载驱动,它把init段里的驱动都加载了
来看看module_init的定义   [weiming@Huangweiming linux-3.0]$ vim include/linux/init.h 

138 typedef int (*initcall_t)(void);
163 #ifndef MODULE
177 #define __define_initcall(level,fn,id) \
178     static initcall_t __initcall_##fn##id __used \
179     __attribute__((__section__(".initcall" level ".init"))) = fn
180
207 #define device_initcall(fn)     __define_initcall("6",fn,6)
212 #define __initcall(fn) device_initcall(fn)
266 #define module_init(x)  __initcall(x);

我们看到如上定义,module_init的真身:__define_initcall(level,fn,id),这是个宏,它把传给module_init的函数名组装成以__initcall为前缀的、以6为后缀的函数名,并把这个函数定义到代码段.initcall6.init里面。所以,module_init(hello_init)展开为:__initcall(hello _init)-> device_initcall(hello _init)-> __define_initcall("6", hello _init,6)-> static initcall_t __initcall_hello_init_6 __attribute__((__section__(".initcall6.init"))) = hello_init;
即是定义了一个类型为initcall_t的函数指针变量__initcall_hello_init_6,并赋值为hello_init,该变量在链接时会链接到section(.initcall6.init).
再来看看linux链接脚本   [weiming@Huangweiming linux-3.0]$ vim arch/arm/kernel/vmlinux.lds

#include <asm-generic/vmlinux.lds.h>

SECTIONS{

INIT_CALLS

}
[weiming@Huangweiming linux-3.0]$ vim include/asm-generic/vmlinux.lds.h
#define INIT_CALLS \

VMLINUX_SYMBOL(__initcall_start) = .; \

INITCALLS \

VMLINUX_SYMBOL(__initcall_end) = .;


#define INITCALLS \

….

*(.initcall6.init) \

__initlist_start是.initlist区段的开始,__initlist_end是结束,通过这两个变量我们就可以访问到所有的初始化函数了,可见__initcall_hello_init_6将会链接到section(.initcall6.init).
接着就等着linux内核启动调用它。

2.Makefile


obj-m := hello.o

modules:
        make -C /lib/modules/`uname -r`/build/ M=`pwd` modules
            make clean

clean:
        rm -f *.ko.* *.o *mod.c *.order *.symvers

[weiming@Huangweiming hello]$ ls
hello.c    Makefile
[weiming@Huangweiming hello]$ make
make -C /lib/modules/`uname -r`/build/ M=`pwd` modules
make[1]: Entering directory `/usr/src/kernels/2.6.32-573.el6.x86_64'
  CC [M]  /home/weiming/fl2440/driver/hello/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/weiming/fl2440/driver/hello/hello.mod.o
  LD [M]  /home/weiming/fl2440/driver/hello/hello.ko.unsigned
  NO SIGN [M] /home/weiming/fl2440/driver/hello/hello.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.32-573.el6.x86_64'
make clean
make[1]: Entering directory `/home/weiming/fl2440/driver/hello'
rm -f *.ko.* *.o *mod.c *.order *.symvers
make[1]: Leaving directory `/home/weiming/fl2440/driver/hello'
[weiming@Huangweiming hello]$ ls
hello.c  hello.ko    Makefile
[weiming@Huangweiming hello]$

3.开发板测试
>: tftp -gr hello.ko 192.168.1.2
hello.ko             100% |*******************************| 25335   0:00:00 ETA
>: ls
apps      dev       info      linuxrc   root      tmp
bin       etc       init      mnt       sbin      usr
data     hello.ko  lib       proc      sys       var
>: insmod hello.ko
hello world!
>: rmmod hello.ko
goodbye!

到此,第一个hello world驱动流程就结束了。

0 0
原创粉丝点击