最全Linux驱动开发八股文(二)

你好,我是拉依达。

这是我的Linux驱动开发八股文详细解析系列

本系列最开始是我在csdn上更新的文章,目前已经是csdn搜索“linux驱动”综合推荐第一名,累计阅读次数4w次。

全文总字数近8w字,是目前全网最全面,最清晰的入门linux驱动学习资料

现重新对内容进行整理,希望可以帮助到更多学习嵌入式的同学。

【下面是拉依达推荐学习相关专栏:】
一、Linux驱动学习专栏:拉依达的Linux驱动八股文 - 牛客网
二、Linux应用学习专栏:拉依达的Linux应用八股文 - 牛客网
【我的嵌入式学习和校招经验】 拉依达的嵌入式学习和秋招经验-CSDN博客
嵌入式学习规划/就业经验指导,可私信咨询

———————————————————————————————————————————————————

4.3 内核模块

Linux 驱动有两种运行方式

  • 驱动编译进 Linux 内核中,当 Linux 内核启动的时就会自动运行驱动程序。
  • 驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用相应命令加载驱动模块。
    • 内核模块是Linux内核向外部提供的一个插口
    • 内核模块是具有独立功能的程序,他可以被单独编译,但不能单独运行。他在运行时被链接到内核作为内核的一部分在内核空间运行
    • 内核模块便于驱动、文件系统等的二次开发

内核模块组成

  1. 模块加载函数

    module_init(xxx_init);
    
    • module_init 函数用来向 Linux 内核注册一个模块加载函数,
    • 参数 xxx_init 就是需要注册的具体函数(理解是模块的构造函数)
    • 当加载驱动的时, xxx_init 这个函数就会被调用
  2. 模块卸载函数

    module_exit(xxx_exit);
    
    • module_exit函数用来向 Linux 内核注册一个模块卸载函数,
    • 参数 xxx_exit 就是需要注册的具体函数(理解是模块的析构函数)
    • 当使用“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用
  3. 模块许可证明

    MODULE_LICENSE("GPL") //添加模块 LICENSE 信息 ,LICENSE 采用 GPL 协议
    
  4. 模块参数(可选) 模块参数是一种内核空间与用户空间的交互方式,只不过是用户空间 --> 内核空间单向的,他对应模块内部的全局变量

  5. 模块信息(可选)

    MODULE_AUTHOR("songwei") //添加模块作者信息
    
  6. 模块打印 printk printk在内核中用来记录日志信息的函数,只能在内核源码范围内使用。和printf非常相似。 printk函数主要做两件事情:①将信息记录到log中 ②调用控制台驱动来将信息输出

  • printk 可以根据日志级别对消息进行分类,一共有 8 个日志级别

    #define KERN_SOH  "\001" 
    #define KERN_EMERG KERN_SOH "0"  /* 紧急事件,一般是内核崩溃 */
    #define KERN_ALERT KERN_SOH "1"  /* 必须立即采取行动 */
    #define KERN_CRIT  KERN_SOH "2"  /* 临界条件,比如严重的软件或硬件错误*/
    #define KERN_ERR  KERN_SOH "3"  /* 错误状态,一般设备驱动程序中使用KERN_ERR 报告硬件错误 */
    #define KERN_WARNING KERN_SOH "4"  /* 警告信息,不会对系统造成严重影响 */
    #define KERN_NOTICE  KERN_SOH "5"  /* 有必要进行提示的一些信息 */
    #define KERN_INFO  KERN_SOH "6"  /* 提示性的信息 */
    #define KERN_DEBUG KERN_SOH "7"  /* 调试信息 */
    
  • 以下代码就是设置“gsmi: Log Shutdown Reason\n”这行消息的级别为 KERN_EMERG。

    printk(KERN_DEBUG"gsmi: Log Shutdown Reason\n");
    

    如果使用 printk 的时候不显式的设置消息级别,那 么printk 将会采用默认级别MESSAGE_LOGLEVEL_DEFAULT,默认为 4

  • 在 include/linux/printk.h 中有个宏 CONSOLE_LOGLEVEL_DEFAULT,定义如下:

    #define CONSOLE_LOGLEVEL_DEFAULT 7
    

    CONSOLE_LOGLEVEL_DEFAULT 控制着哪些级别的消息可以显示在控制台上,此宏默认为 7,意味着只有优先级高于 7 的消息才能显示在控制台上。

    这个就是 printk 和 printf 的最大区别,可以通过消息级别来决定哪些消息可以显示在控制台上。默认消息级别为 4,4 的级别比 7 高,所示直接使用 printk 输出的信息是可以显示在控制台上的。

模块操作命令

  1. 加载模块
    • insmod XXX.ko
      • 为模块分配内核内存、将模块代码和数据装入内存、通过内核符号表解析模块中的内核引用、调用模块初始化函数(module_init)
      • insmod要加载的模块有依赖模块,且其依赖的模块尚未加载,那么该insmod操作将失败
    • modprobe XXX.ko
      • 加载模块时会同时加载该模块所依赖的其他模块,提供了模块的依赖性分析、错误检查、错误报告
      • modprobe 提示无法打开“modules.dep”这个文件 ,输入 depmod 命令即可自动生成 modules.dep
  2. 卸载模块
    • rmmod XXX.ko
  3. 查看模块信息
    • lsmod
      • 查看系统中加载的所有模块及模块间的依赖关系
    • modinfo (模块路径)
      • 查看详细信息,内核模块描述信息,编译系统信息

4.4 设备号

  • Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成
  • 主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。
  • Linux 提供了一个名为 dev_t 的数据类型表示设备号其中高 12 位为主设备号, 低 20 位为次设备
  • 使用"cat /proc/devices"命令即可查看当前系统中所有已经使用了的设备号(主)
MAJOR // 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
MINOR //用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
MKDEV //用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。

4.5 地址映射

MMU(Memory Manage Unit)内存管理单元

  1. 完成虚拟空间到物理空间的映射
  2. 内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性
  3. 对于 32 位的处理器来说,虚拟地址(VA,Virtual Address)范围是 2^32=4GB

alt

内存映射函数

CPU只能访问虚拟地址,不能直接向寄存器地址写入数据,必须得到寄存器物理地址在Linux系统中对应的虚拟地址

物理内存和虚拟内存之间的转换,需要用到: ioremap 和 iounmap两个函数

  • ioremap,用于获取指定物理地址空间对应的虚拟地址空间

    /*
    phys_addr:要映射给的物理起始地址(cookie)
    size:要映射的内存空间大小
    mtype: ioremap 的类型,可以选择 MT_DEVICE、 MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和 MT_DEVICE_WC, 
    ioremap 函数选择 MT_DEVICE
    返回值: __iomem 类型的指针,指向映射后的虚拟空间首地址
    */
    #define ioremap(cookie,size) __arm_ioremap((cookie), (size),MT_DEVICE)
     
    void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype)
    {
        return arch_ioremap_caller(phys_addr, size, mtype, __builtin_return_address(0));
    }
    

    例:获取某个寄存器对应的虚拟地址

    #define addr (0X020E0068)  // 物理地址
    static void __iomem*  va; //指向映射后的虚拟空间首地址的指针
    va=ioremap(addr, 4);   // 得到虚拟地址首地址
    
  • iounmap,卸载驱动使用 iounmap 函数释放掉 ioremap 函数所做的映射。 参数 addr:要取消映射的虚拟地址空间首地址

    iounmap(va);
    

I/O内存访问函数

外部寄存器外部内存映射到内存空间时,称为 I/O 内存。但是对于 ARM 来说没有 I/O 空间,因此 ARM 体系下只有 I/O 内存(可以直接理解为内存)。

使用 ioremap 函数将寄存器的物理地址映射到虚拟地址后,可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作

  • 读操作函数

    u8 readb(const volatile void __iomem *addr)
    u16 readw(const volatile void __iomem *addr)
    u32 readl(const volatile void __iomem *addr)
    

    readb、 readw 和 readl 分别对应 8bit、 16bit 和 32bit 读操作,参数 addr 就是要读取写内存地址,返回值是读取到的数据

  • 写操作函数

    void writeb(u8 value, volatile void __iomem *addr)
    void writew(u16 value, volatile void __iomem *addr)
    void writel(u32 value, volatile void __iomem *addr)
    

    writeb、 writew 和 writel分别对应 8bit、 16bit 和 32bit 写操作,参数 value 是要写入的数值, addr 是要写入的地址。

#嵌入式##校招##linux##linux驱动##八股文#
拉依达的Linux驱动八股文 文章被收录于专栏

你好,我是拉依达。 这是我的Linux驱动开发八股文详细解析系列。 本系列最开始是我在csdn上更新的文章,目前已经是csdn搜索“linux驱动”综合推荐第一名,累计阅读次数4w次。 全文总字数近8w字,是目前全网最全面,最清晰的入门linux驱动学习资料。 现在我重新对内容进行整理,已专栏的形式发布在牛客上,希望可以帮助到更多学习嵌入式的同学。

全部评论

相关推荐

1. 如何实现任务的优先级动态调整?2. FreeRTOS中如何实现消息队列的使用?3. 如何处理内存泄漏问题?4. FreeRTOS中如何进行任务的时间管理?5. 如何使用软件定时器?6. 任务间如何共享数据而不发生冲突?7. FreeRTOS支持哪些调度算法?8. 如何使用信号量进行任务同步?9. 如何配置系统时钟?10. 如何解决任务的优先级反转问题?11. FreeRTOS中如何进行任务的挂起与恢复?12. 如何使用事件标志组?13. FreeRTOS如何实现多核处理?14. 如何调试任务?15. 如何使用临界区保护共享资源?16. FreeRTOS中如何实现任务的堆栈监控?17. 如何使用回调函数?18. FreeRTOS中如何处理硬件中断?19. 如何实现任务的延迟执行?20. 如何进行内存管理?21. FreeRTOS中如何实现消息发送与接收?22. 如何实现优先级调度?23. 如何使用互斥量防止资源竞争?24. 如何监控任务的运行状态?25. FreeRTOS中如何实现任务的动态创建与删除?26. 如何使用信号量实现互斥?27. FreeRTOS中如何调节任务的运行频率?28. 如何处理任务的异常?29. FreeRTOS中如何使用定时器实现定时任务?30. 如何进行系统性能优化?分享不易,关注我分享更多面试知识,嵌入式c++的同学可以看一下大佬总结的面经  c++/嵌入式面经专栏-牛客网 https://www.nowcoder.com/creation/manager/columnDetail/MJNwoM
点赞 评论 收藏
分享
金风科技 助理工程师 20K*14 硕士海归
点赞 评论 收藏
分享
4 4 评论
分享
牛客网
牛客企业服务