13 linux设备驱动基础

本文介绍了Linux内核中驱动模块的基本概念与开发流程,包括模块的静态与动态加载方式、编写驱动模块所需的宏定义及代码示例、编译驱动模块的方法、查看驱动模块信息的命令、以及 printk 的输出级别控制等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内核功能模块: 进程调度, 内存管理(mmu, 分配进程内存), 文件系统管理(如支持的文件系统格式), 设备驱动(硬件驱动好后由内核来统一管理), 网络协议栈

1). 模块机制
静态加载, 把驱动模块编进内核, 在内核启动时自动加载
动态加载, 把驱动模块编为ko, 在内核启动后,需要用时加载

2). 编写内核驱动

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

    static int __init test_init(void)  
    {
        return 0; //返回0表示成功, 返加负数退出加载模块, 如返1,2,3等正数,会有警告但还是会加载
    }
    //__init 把test_init的函数代码放入统一的初始化段里,当内核把驱动初始化完后, 释放此函数的代码指令空间

    static void __exit test_exit(void)
    {
        ....
    }
    //__exit 指定此函数只在驱动卸载时使用, 用完后释放

    module_init(test_init); //指定test_init为模块初始化函数
    module_exit(test_exit); //指定test_exit为模块退出时执行的卸载函数

    MODULE_LICENSE("GPL"); //指定所支持的协议
    MODULE_AUTHOR("作者");
    MODULE_DESCRIPTION("描述");
    MODULE_VERSION("版本");

3). 所用的宏定义:

#define __init      __section(.init.text)
#define __initdata  __section(.init.data)
#define __section(S) __attribute__ ((__section__(#S)))

char __initdata buf[] = "hello world"; //表示此字符数组在驱动初始化后可以回收空间
// 段".init*"其实就是表示只要初始化后不会再使用,内核可以把这段里的空间回收使用。

#define __exitdata  __section(.exit.data)
#define __exit      __section(.exit.text)
// 段".exit*"应是用于集中管理只有驱动模块卸载时才会触发调用的资源,防止被误调用。

//linux内核2.4版本时,设备驱动模块的初始化函数名必须是"init_module", 卸载函数名必须是"cleanup_module".
#define module_init(initfn)                                     \
         static inline initcall_t __inittest(void)               \
         { return initfn; }                                      \
         int init_module(void) __attribute__((alias(#initfn)));
 module_init这宏其实就是把我们的初始化函数名多加个别名("init_module")
 module_exit宏用于把卸载函数多加个别名("cleanup_module")
// 现在我们写驱动模块,初始化函数和卸载函数名可以随便命名,其实内核里还是没变。

4). 编译驱动模块,需要调用内核源码目录里的Makefile, 并在驱动源码目录下创建一个Makefile文件指定编译目标:

    obj-m += test.o

    KSRC := /disk3/myown/h3/orangepi_sdk/source/linux-3.4.112/
    export ARCH := arm
    export CROSS_COMPILE := arm-linux-gnueabihf-

    all:
        make -C $(KSRC) modules M=`pwd` 


    .PHONY : clean
    clean:
        make -C $(KSRC) modules clean M=`pwd` 
   编译好后:
    insmod test.ko   //加载驱动模块
    rmmod  test      //卸载驱动模块

5). 查看驱动模块信息:


    modinfo test.ko 查看模块的信息

    cat /proc/modules 查看当前系统的动态加载模块  相当于lsmod
    test 1768 0 - Live 0xbf03c000
    模块名, 使用的内存大小, 正在被调用次数,  有效 , 模块所在的内存地址 

    ls /sys/module 查看系统里现有的驱动模块,包括动静态驱动模块

6). 查看驱动输出的消息

    cat /var/log/messages
    tail /var/log/messages
    dmesg | tail

7). printk的输出级别控制

#include <linux/kernel.h>
#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         */

默认的级别为13: "<d>"        
#define KERN_DEFAULT    "<d>"


使用 : printk(KERN_INFO"内容"); // printk("<6>kskdlfj");

查看当前内核的输出级别 cat /proc/sys/kernel/printk
    7       7       1       7
    7:console_loglevel 
    7:default_message_loglevel 
    1:minimum_console_loglevel
    7:default_console_loglevel
    当printk函数使用的级别小于当前console_loglevel级别时, 则可以输出, 否则不输出

修改级别输出  echo "8 4" > /proc/sys/kernel/printk    
    //输出级别只要小于8就可以输出, 默认级别为4(即不指定级别时使用此级别)

8). 代码里用于调试输出的宏:

    #ifdef DEBUG
        #define TS_DEBUG(fmt,args...)   do { printk(fmt, ##args); } while (0)
    #else
        #define TS_DEBUG(fmt,args...)   do { } while (0)
    #endif

    usage:
        TS_DEBUG("hello\n");
        TS_DEBUG("%d, %d\n", num , num2);
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值