zoukankan      html  css  js  c++  java
  • 简单入门Linux设备驱动之第二部分:第一个linux设备驱动程序

    声明:内容搬自阿三哥网站,只是翻译了一下。侵删。https://embetronicx.com/tutorials/linux/device-drivers/

    正文如下:

    这是“linux设备驱动系列”的教程。本系列的目的是提供简单实用的示例,使每个人都能以简单的方式理解这些概念。现在让我们即将学习“linux设备驱动第二部分-第一个linux设备驱动程序”。【hello world警告】在写驱动程序之前,我们先讲一些关于模块的基本信息。那我们快开始吧~~

    内容速览

    ·1 linux设备驱动第二部分-第一个linux设备驱动程序

    ·2 模块信息

      ·2.1 许可证(license)

      ·2.2 作者

      ·2.3 模块描述

      ·2.3 模块版本

    ·3 简易内核模块编程
      ·3.1 初始化(Init)函数
      ·3.2 退出(Exit)函数
      ·3.3 printk()
      ·3.4 printf()和printk()的不同
      ·3.5 简单的驱动程序
    ·4 编译我们的驱动程序
    ·5 加载与卸载设备驱动程序
      ·5.1 加载(loading)
      ·5.2 模块清单
      ·5.3 卸载(unloading)
      ·5.4 得到模块细节信息
     

    ·1 linux设备驱动第二部分-第一个linux设备驱动程序

     

    ·2 模块信息

      ·许可证

      ·作者

      ·模块简介

      ·模块版本

    这些信息被定义为宏(macro),位于 linux/module.h 中。

     

    ·2.1 许可证(license)

    GPL,也就是“GNU通用公共许可证”,是一种面向软件的开源许可证。。如果您的软件是按照GPL条款进行授权的,那么它是免费-“free”的。然而,这里的“free”不是指免费软件,它也可以是付费软件。根据GPL,free意味着自由。正如GPL的支持者自豪地宣称的那样:免费就是自由,而不是免费啤酒(free as in freedom, not free beer)。

     

    以下许可标识目前被接受为自由软件模块:

      “GPL”【[GNU 公共许可证 v2 及以上版本】

      “GPL v2” 【GNU 公共许可证 v2】

      “GPL and additional rights” 【GPL公共许可证v2 及更多】

      “Dual BSD/GPL” 【GPL公共许可证v2或BSD许可证

      “Dual MIT/GPL” 【GPL公共许可证v2或MIT许可证

      “Dual MPL/GPL” 【GPL公共许可证v2或Mozilla许可证

     

    以下是其他许可证标识:

      “Proprietary” 【非免费产品】

     

    还有一些双许可证的组件,但在linux上运行时,与之相关的只有GPL,所以这不是问题。比如,与GPL链接的LGPL许可证是GPL组合中的一种。

     

    许可证标识的存在是由于以下几点因素:

      1.用户想要审查他们的安装是否是免费的, modinfo 命令可以显示许可证信息。

      2.社区【linux社区】可以忽略来自私有模块的bug报告。

      3.供应商可以根据自己的需求标识他们的许可证。

    我们可以在我们的驱动程序中添加许可证,如下图。要完成这项任务,你需要include linux/module.h 头文件。

     

    MODULE_LICENSE("GPL");
    MODULE_LICENSE("GPL v2");
    MODULE_LICENSE("Dual BSD/GPL");

     

    注意:标识许可证并不是严格必需的,但是你的模块确实应该指定什么许可证应用在你的代码上。

     

    ·2.2 作者

    使用这个宏我们可以知道是谁写了这个驱动程序或者模块。所以想要知道作者姓名的用户可以使用 modinfo 显示这些信息。我们可以给我们的驱动程序(模块)添加作者姓名,如下图。为了完成这项任务,你需要include linux/module.h 头文件。

     

    MODULE_AUTHOR("Author");

     

    注意:使用“姓名<邮箱>”的格式或者“姓名”的格式, 如果由多个作者,就要使用多行MODULE_AUTHOR()语句来声明。

     

    ·2.3 模块描述

    使用这个宏,我们可以用来描述我们的模块或者驱动程序。这样用户就可以使用 modinfo 命令得到模块描述的信息。我们可以给我们的驱动程序(模块)添加模块描述,如下图。为了完成这项任务,你需要include linux/module.h 头文件。

    MODULE_DESCRIPTION("A sample driver");

    ·2.3 模块版本

    使用这个宏我们可以给出模块或者驱动程序的版本号。这样用户就可以使用 modinfo 命令得到模块的版本信息。

      版本格式:[<epoch>:]<version>[-<extra-version>]  【[ ]中的内容为可选,<>中的内容为必选】

    <epoch>:无符号整数,可以用来重新启动一个版本。如果没有给出,则默认为 0 。比如版本“2:1.1”是版本“1:2.0”之后的一个版本。

    <version> :此选项只能包含字母数字或者字符“.”。数字部分按照数字排序,字母部分按字母顺序排序(根据RPM或者DEB算法)。

    <extra-version>:跟<version>选项类似,但是可以插入本地定制,比如“rh3”或者“rusty1”。

    例子:

    MODULE_VERSION("2:1.0");
     
    ·3 简易内核模块编程
    到目前为止,我们知道了一些编写驱动程序最基本的知识。现在让我们开始编程吧。不论使用哪一种编程语言,我们要怎样开始写代码呢?有什么想法吗?好吧,在编程中,会有一个开始点和结束点。如果你使用C语言,那么开始点就是 main() 函数,不是吗?程序会从main()函数开始,然后执行main()函数中调用的内容。最后从main()函数的结束点退出。但是这里我们使用两个不同的函数来开始和结束。
      1.init函数
      2.exit函数
    内核模块需要的一套头文件和用户程序需要的头文件的是不一样的。并且一定要记住,模块的代码不能使用用户空间的库、API或者系统调用。
     
    ·3.1 初始化(Init)函数
     当驱动程序被加载到内核时,初始化函数是第一个执行的。举个栗子,当我们使用 insmod 命令加载驱动程序时,这个函数就会被执行。函数的语法如下图所示。
     
    static int __init hello_world_init(void) /* Constructor */
    {
        return 0;
    }
    module_init(hello_world_init);
    此函数应该使用 module_init()宏来注册自己。
     
    ·3.2 退出(Exit)函数
     当驱动程序从内核中卸载时,退出函数是最后一个执行的。举个栗子,当我们使用 rmmod 命令卸载驱动程序时,此函数就会被执行。函数的语法如下图所示。
     
    void __exit hello_world_exit(void)
    {
    
    }
    module_exit(hello_world_exit);
    此函数应该使用 module_exit()宏来注册自己。
     
    ·3.3 printk()
     在C语言编程中,我们怎么打印值或者任何量呢?没错,使用 printf()函数。printf()函数是用户空间函数。所以我们在模块中不能使用它。因此我们为内核创造了另一个函数--printk()。
    其中一个区别是,printk()函数允许您根据消息的严重性对消息进行分类,方法是将不同的日志级别或优先级与消息关联起来。你可以使用宏来指定日志等级。我现在来解释下这些宏。
    下面是printk()使用的一些宏。
    KERN_EMERG:用于紧急消息,通常是在crash前。
    KERN_ALERT:用于需要立即处理的场景。
    KERN_CRIT:用于临界状态,经常与严重的硬件或软件故障有关。
    KERN_ERR:用于报告错误状态。设备驱动程序经常使用KERN_ERR来报告硬件错误。
    KERN_WARNING:用于出问题时的警告,但是这个问题不会造成严重的系统问题。
    KERN_NOTICE:用于正常状态,但是值得注意。很多安全相关的状态会使用这个等级来报告信息。
    KERN_INFO:信息性消息。在这个级别上,许多驱动程序在启动时打印它们找到的硬件信息。
    KERN_DEBUG:用于调试的信息。
    例子:
     
    printk(KERN_INFO "Welcome To our simple driver");
    ·3.4 printf()和printk()的不同
    ·printk()时内核级别的函数可以根据定义的不同的日志等级打印信息。我们可以使用dmesg命令看到这些打印的消息。
    ·printf()永远向描述符为“STD_OUT”的文件打印消息。我们可以在STD_OUT控制台上看见这些打印出来的消息。
     
    ·3.5 简单的驱动程序
    这是我们简易设备驱动程序的完整代码(hello_world_module.c)。你可以从点击下面的链接下载这个项目。
          从github上下载代码。
     
    /***************************************************************************//**
    *  file       hello_world_module.c
    *
    *  details    Simple hello world driver
    *
    *  author     EmbeTronicX
    *
    * *******************************************************************************/
    #include<linux/kernel.h>
    #include<linux/init.h>
    #include<linux/module.h>
     
    /*
    ** Module Init function
    */
    static int __init hello_world_init(void)
    {
        printk(KERN_INFO "Welcome to EmbeTronicX
    ");
        printk(KERN_INFO "This is the Simple Module
    ");
        printk(KERN_INFO "Kernel Module Inserted Successfully...
    ");
        return 0;
    }
    
    /*
    ** Module Exit function
    */
    static void __exit hello_world_exit(void)
    {
        printk(KERN_INFO "Kernel Module Removed Successfully...
    ");
    }
     
    module_init(hello_world_init);
    module_exit(hello_world_exit);
     
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>");
    MODULE_DESCRIPTION("A simple hello world driver");
    MODULE_VERSION("2:1.0");
    ·4 编译我们的驱动程序
     一旦我们有了c 代码,就该是时候来编译它并且能得到模块文件hello_world_module.ko。为你的模块写一个Makefile是最直接的。
     
    obj-m += hello_world_module.o
    KDIR = /lib/modules/$(shell uname -r)/build
    
    all:
        make -C $(KDIR)  M=$(shell pwd) modules
    clean:
        make -C $(KDIR)  M=$(shell pwd) clean
    c代码(hello_world_module.c)和Makefile都准备好了,我们要做的就是唤醒make来build我们的第一个驱动程序(hello_world_module.ko)。
    在终端上,你需要输入 sudo make ,如下图。
     
    现在我们有了hello_world_module.ko。这就是我们要加载到内核的内核目标文件。
     
    ·5 加载与卸载设备驱动程序
     内核模块是一个很小的文件,它可以被加载到正在运行的内核,也可以被卸载。
     
    ·5.1 加载(loading)
     为了加载一个内核模块,我们要使用root权限并输入 insmod命令。
    举个栗子,我们的模块文件名字是hello_world_module.ko:
     
     sudo insmod hello_world_module.ko
     
     
    lsmod命令是用来查看已插入的模块。下面这张图里,已经出现了在初始化函数中的打印。使用 dmesg 命令查看内核日志打印。
     
    所以当我们加载模块时,它就会执行初始化函数。
     
    ·5.2 模块清单
     为了查看目前已加载的模块的模块清单,使用 lsmod命令。在上面的图中,你可以看到我已经用过 lsmod 命令了。
     
    ·5.3 卸载(unloading)
     为了卸载内核模块,使用root权限执行 rmmod 命令。
    以我们的情况为例,
     
    sudo rmmod hello_world_module.ko 或者 sudo rmmod hello_world_module
     
     
    所以当我们卸载模块时,它会执行退出函数。
     

    ·5.4 得到模块细节信息

     为了得到模块更多的信息(作者、支持的选项),我们可以使用 modinfo 命令。

    举个栗子,

    modinfo hello_world_module.ko

     

    ----------------分割线----------------

    现在我们知道了linux设备驱动程序从哪里开始。谢谢你的阅读。在我们下一小节的教程中,我们会看到怎么给linux设备驱动程序传递参数~~~

     
     
     
     
     
    -------------BE SEXY AND BRAINY---------
  • 相关阅读:
    Path Sum
    Linked List Cycle II
    Linked List Cycle
    Single Number i and ii
    Binary Tree Preorder Traversal and Binary Tree Postorder Traversal
    Max Points on a Line
    Evaluate Reverse Polish Notation
    【leetcode】98 验证二叉搜索树
    【vivo2020春招】 02 数位之积
    【vivo2020春招】03 vivo智能手机产能
  • 原文地址:https://www.cnblogs.com/yuxiaolan/p/14381431.html
Copyright © 2011-2022 走看看