• Linux驱动:手把手教hello world驱动配置、编译进内核或为模块


    《关注、星标嵌入式客栈,精彩及时送达

    [导读] 回想自己刚刚学写Linux 驱动时,觉得很难,简直无从下手。现在写公众号,也常遇到一些朋友对于写一个Linux驱动不知道这个驱动究竟如何编译、如何装载、如何测试,本文就如何编译进内核或者模块来聊一聊我的一些体会。

    大家周末好,最近来了很多新朋友,感谢小伙伴们关注小号,让我们一起进步,一起成长!如果喜欢小号请星标,星标的作用将会使本号最新文章在列表中置顶,这样就不会错过推送的文章了。

    基本概念

    要学习Linux驱动开发,个人体会是要建立清晰的概念,对于概念尽量理解清楚其内涵以及作用。

    驱动模型

    先来看看驱动处在Linux系统中的什么位置:

    • 驱动运行于内核空间中

    • 驱动程序像是一个黑盒子:

      • 对上层用户空间提供操作设备的接口,并隐藏设备的实现细节

      • 对下层的具体硬件设备进行控制、管理,通过用户空间的访问对具体的硬件设备操作。

    这里有两个重要概念需要理解,啥是内核空间?啥又是用户空间?现代宏操作系统(相对于微内核而言)通常将虚拟内存分割隔离成内核空间和用户空间两个部分。或许会问为啥要这样处理呢?这样隔离分割可提供内存保护和硬件保护,以防止恶意或错误的软件行为。再进一步说说就好理解了,用户空间需要访问内核空间需要访问内核空间以及底层设备要怎么做呢?利用操作系统提供的操作系统调用(OS API)进而访问内核空间,这些操作接口是内核经过精心设计且提供了完备的保护机制,故而对内核空间是安全的。

    内核职责

    对于Linux内核而言,其主要功能职责有这么几个:

    • 进程管理:进程管理子系统负责进程的管理(包括创建、销毁以及调度)控制对CPU的进程访问。调度程序执行一项调度策略,以确保进程可以公平地访问CPU,同时确保内核按时执行必要的硬件操作。

    • 内存管理:允许多个进程安全地共享机器的主内存系统。内存管理器主要使用虚拟内存管理机制,该虚拟内存允许Linux支持使用的内存量超过系统可用内存的进程。使用文件系统将未使用的内存换出到非易失性存储器(如硬盘,eMMC等),然后在需要时交换回来。

    • 文件系统:几乎所有的对象(所有的设备都是文件对象)都可以看成文件,底层文件系统涵盖了真实的文件系统还有虚拟文件系统,形成一个文件系统框架。

    • 设备管理:利用设备驱动框架,对硬件设备控制管理,并通过虚拟文件系统对用户空间应用程序提供统一的系统操作接口集。

    • 网络功能:实现了常见的网络协议栈,以及对底层网络设备的控制。

    如想对内核有个整体认识,可以阅读这篇文章,点击跳转阅读:

    Linux 内核架构分析

    驱动类型

    回到驱动这个概念,Linux驱动已基本明了在整个系统中处于什么位置。Linux根据设备特点,将设备驱动程序大致分为下面三大类:

    • 字符设备驱动:设备的访问特点可以如字节流一样访问,对用户提供统一的文件访问接口集:open/close/read/write等,具体有哪些呢,比如串口。

    • 块设备驱动:块设备上基本可以容纳文件系统,例如常见的存储设备。

    • 网络设备驱动:这个就比较好理解了,常见对外的网络接口,比如以太网设备驱动等。

    驱动的编译方式

    刚刚接触Linux设备驱动开发的朋友,往往对这一步会不知道如何下手,这里来描述一下两种方式。这是本文想重点想分享的内容。

    Linux内核代码主要采用了Kconfig/Makefile进行配置以及构建管理。

    • Kconfig:主要用于配置以及依赖裁剪管理

    • Makefile:编译规则,这个无需多说。

    代码及配置

    以<<Linux设备驱动程序>>中hello word代码进行说明。

    #include <linux/module.h>
    #include <linux/init.h>
    MODULE_LICENSE("Dual BSD/GPL");
    
    static int hello_init(void)
    {
     printk(KERN_ALERT "Hello word");
     return 0;
    }
    static void hello_exit(void)
    {
     printk(KERN_ALERT "Goodbye,Hello word");
    }
    
    /* register the init and exit routine of the module */
    module_init( hello_init );
    module_exit( hello_exit );
    

    1.在./driver下创建目录,比如创建为hello文件夹

    2.在该文件夹创建hello.c将上述代码编辑进这个文件保存,具体怎么操作就不详述了。

    3.在该文件夹下创建Kconfig,Makefile两个文件

    Kconfig内容:

    config HAVE_HELLO
     tristate "hello driver"  
     help
       This hello driver is just to shaow how to develop driver process.
    
       This driver can also be built as a module. If so, the module
       will be called .
     default y 
    
    #endmenu
    

    表示如果使能了CONFIG_HAVE_HELLO在内核裁剪配置文件中,将显示hello driver菜单,默认编译进内核:

    • y: 编译进内核

    • m:编译为模块.ko文件

    • n:表示不编译,未使能。

    Makefile内容:

    obj-$(CONFIG_HAVE_HELLO) += hello.o 
    

    表示CONFIG_HAVE_HELLO使能时,编译规则指定的文件为hello.c

    4.编辑driver顶层的Kconfig,Makefile文件

    Kconfig文件

    menu "Device Drivers"
    source "drivers/amba/Kconfig"
    ......
    
    source "drivers/hello/Kconfig"
    endmenu
    

    在endmenu前添加hello文件夹的配置文件解析:source "drivers/hello/Kconfig"

    如此一来,配置系统就会按照这个配置去解析hello文件夹下的Kconfig

    Makefile文件:

    #
    # Makefile for the Linux kernel device drivers.
    #
    # 15 Sep 2000, Christoph Hellwig <hch@infradead.org>
    # Rewritten to use lists instead of if-statements.
    #
    
    obj-y    += irqchip/
    obj-y    += bus/
    
    .......
    
    obj-$(CONFIG_HAVE_HELLO) += hello/
    

    在原Makefile后添加obj-$(CONFIG_HAVE_HELLO) += hello/,这句话的作用是当CONFIG_HAVE_HELLO使能后,在哪里去找源文件。在结合hello文件下模块Makefile就形成了层次式Makefile

    接下来看看如何编译。

    编译

    首先调用make distclean,清除原来的配置。

    然后调用make ARCH=arm xxx_defconfig:

    configuration written to .config表示这步操作成功,如果失败检查Kconfig配置是否出错。

    ARCH=arm 表示使用的是ARM平台的目标机,如nanopi2_linux_defconfig 表示配置文件,该文件存在于:

    然后来看看hello是否生效,调用make ARCH=arm menuconfig

    进入Device Drivers子目录,移动光标到最底部,就看到添加的hello driver配置菜单了。

    那么这里如前文所说我们有这么几种选择:

    • 编译进内核,敲y,显示<*>表示编译进内核映象

    • 编译为模块,敲M表示编译为模块,最后生成hello.ko,可以使用insmod命令装载,这里比较容易查到具体如何怎么操作,就不赘述了

    • 不编译,敲N表示不使能。

    选择好,保存配置,剩下就是编译了:

    编译进内核

    编译为模块

    最后在./driver/hello文件下生成hello.ko就成了,剩下就是动态加载调试了。

    总结一下

    通过上文阅读,应能掌握如何配置、编译进内核或者模块的基本操作,对于刚入手学习Linux驱动开发属于必掌握的基本操作,由于书籍中少有这种实战介绍,所以这里总结分享一下这些基础操作。当然对于熟悉了内核驱动开发而言,完全可以直接编辑xxx_defconfig,如如这里可以这么做:

    CONFIG_HELLO_DRIVER=m
    

    这就修改为=y表示编译进内核,=m表示编译为模块,=n表示不使能.

    本文辛苦原创总结,如果觉得有价值也请帮忙点赞/在看/转发支持,不胜感激!

    END

    往期精彩推荐,点击即可阅读

    ▲Linux内核中I2C总线及设备长啥样? 

    Linux 内核架构分析

    手把手教系列之IIR数字滤波器设计实现

    为节约时间,文章同步自公众号(首发) 如需学习资料,请用微信关注文中公众号二维码,后台发送"领取",可免费获取海量学习资料,涵盖单片机技术,信号处理,人工智能,嵌入式linux,C/C++编程,数据结构与算法
  • 相关阅读:
    16位汇编第三讲 分段存储管理思想
    16位汇编语言第二讲系统调用原理,以及各个寄存器详解
    /bin/sh 与 /bin/bash 的区别
    Linux中cat、more、less、tail、head命令的区别
    Linux之特殊权限(SUID/SGID/SBIT)
    HTML页面参数的传递与获取
    Ajax的跨域请求——JSONP的使用
    IDEA新建maven项目
    IDEA新建Web项目
    权限管理基础——原理与解决方案
  • 原文地址:https://www.cnblogs.com/embInn/p/14038119.html
Copyright © 2020-2023  润新知