写点什么

Linux 设备驱动系列(二)——第一个设备驱动程序

  • 2024-04-23
    浙江
  • 本文字数:2148 字

    阅读完需:约 7 分钟

Linux设备驱动系列(二)——第一个设备驱动程序

关注微信公众号:Linux 内核拾遗

文章来源:https://mp.weixin.qq.com/s/aId6hhShCFaBfphyr9SLLw

1 内核模块信息介绍

一个 Linux 内核模块通常包含以下描述信息,这些描述信息可以使用"linux/module.h"中提供的宏来定义。

1.1 License

模块的许可证信息,表示该模块的使用和分发受到的许可限制。常见的许可证包括 GPL(GNU 通用公共许可证)、LGPL(GNU 宽通用公共许可证)、BSD 许可证等。

使用 MODULE_LICENSE 来声明:

MODULE_LICENSE("GPL");MODULE_LICENSE("GPL v2");MODULE_LICENSE("Dual BSD/GPL");
复制代码

1.2 Author

模块的作者或维护者信息,用于指明该模块的创作者或负责人。

使用 MODULE_AUTHOR 来声明,允许声明多次:

MODULE_AUTHOR("Author1");MODULE_AUTHOR("Author2");
复制代码

1.3 Module Description

简要描述该模块的功能、作用以及用途,使用户能够快速了解模块的主要功能和特点。

使用 MODULE_DESCRIPTION 来声明:

MODULE_DESCRIPTION("The first simple linux device driver");
复制代码

1.4 Module Version

模块的版本号,用于标识模块的不同版本。通常使用数字和可选的附加信息(如 alpha、beta、rc 等)来表示模块的版本信息。

使用 MODULE_VERSION 进行声明,通常使用的格式是:[<epoch>:]<version>[-extra-version]

MODULE_VERSION("2:1.0");
复制代码

2 内核模块编程入门

和普通的应用程序一样,内核模块也有相应的“main 函数”,它作为内核模块代码执行的起始点。此外,内核模块还有对应结束点,用于在内核模块退出的时候执行代码。

Linux 内核模块子系统提供了相应的起始点和结束点函数钩子:

  1. Init Function

  2. Exit Function

内核模块编写者通过实现模块自身的起始点和结束点函数,并在内核模块加载的时候注册到内核函数中,Linux 内核模块子系统将在加载(insmod)和卸载(rmmod)内核模块的时候调用相应的钩子函数实现,用于完成内核模块的初始化或者资源清理回收等工作。

需要注意的是,内核模块代码不能调用任何用户空间的代码库、API 或者系统调用,它只能引用 Linux 内核提供的头文件。

2.1 初始化/退出函数

一个最简单的内核模块初始化函数如下:

static int __init init_func(void){  return 0;}module_init(init_func);
void __exit exit_func(void){}module_exit(exit_func);
复制代码

其中 module_init 和 module_exit 宏分别用于将初始化函数 init_func 和退出函数 exit_func 注册到 Linux 内核模块子系统中。

2.2 打印函数

打印函数是 Linux 内核模块中最有效的调试手段和工具,类似于 C 语言中的 printf(),Linux 内核提供了内核空间版本的打印函数 printk()。


在使用上,两者最大的不同在于,开发者可以根据日志消息的重要性程度或者优先级,在 printk()中指定起日志等级,其中日志等级是通过宏来声明的。printk()支持的日志等级,从高到低如下所示:

  1. KERN_EMERGE:最紧急的消息,通常用在内核 crash 之前;

  2. KERN_ALERT:用于需要立即采取相应动作的场景;

  3. KERN_CRIT:通常与严重的硬件或者软件错误关联;

  4. KERN_ERR:用于报告错误条件,设备驱动通常用 KERN_ERR 来报告硬件设备运行过程中相关的错误;

  5. KERN_WARNING:用于问题告警的场景,但通常不会产生严重的问题;

  6. KERN_NOTICE:一些普遍但是值得注意的场景,大部分安全相关的问题都使用该日志等级;

  7. KERN_INFO:用于提供系统运行信息的消息,很多设备驱动程序使用该日志等级在启动阶段报告硬件的信息。

  8. KERN_DEBUG:用作开发调试目的的日志消息。


在比较新的版本中,Linux 内核提供了如下 API 来替代直接使用 printk():

  1. pr_info —— KERN_INFO;

  2. pr_cont:追加到与前一个日志消息同一行;

  3. pr_debug —— KERN_DEBUG;

  4. pr_err —— KERN_ERR;

  5. pr_warn —— KERN_WARNING。


示例:

printk(KERN_INFO "Welcome to my first simple linux device driver!");
复制代码


printk vs printf:

  1. printk()是内核级函数,能够打印多种预定义等级的日志,可以通过 dmesg 来获取其输出;

  2. printf()是用户空间函数,通常输出到一个文件描述符中,例如 stdout,其输出可以在 stdout 控制台获取。

3 一个完整的 Linux 设备驱动示例

下面给出了一个完整的 Linux 设备驱动程序源代码:


my_driver.c

#include<linux/kernel.h>#include<linux/init.h>#include<linux/module.h> static int __init init_func(void){    printk(KERN_INFO "Welcome to my first simple linux device driver\n");    printk(KERN_INFO "Kernel Module Inserted\n");    return 0;}static void __exit exit_func(void){    printk(KERN_INFO "Kernel Module Removed\n");} module_init(init_func);module_exit(exit_func); MODULE_LICENSE("GPL");MODULE_AUTHOR("FeiFei <feifei@gmail.com>");MODULE_DESCRIPTION("A simple linux kernel driver");MODULE_VERSION("2:1.0");
复制代码


Makefile

obj-m += my_driver.o KDIR = /lib/modules/$(shell uname -r)/build
all: make -C $(KDIR) M=$(shell pwd) modules clean: make -C $(KDIR) M=$(shell pwd) clean
复制代码


将这两个文件放到同一个目录下,运行"make"进行编译得到内核模块文件:my_driver.ko,最后可以通过 insmod/rmmod 来加载或者卸载我们编译好的内核模块,通过 dmesg 来查看内核模块的日志输出,也可以通过 modinfo my_driver.ko 来查看内核模块的详细信息。



关注微信公众号:Linux 内核拾遗

文章来源:https://mp.weixin.qq.com/s/aId6hhShCFaBfphyr9SLLw

发布于: 刚刚阅读数: 3
用户头像

聚沙成塔 2023-01-12 加入

分享Linux内核开发相关的编程语言、开发调试工具链、计算机组成及操作系统内核知识、Linux社区最新资讯等

评论

发布
暂无评论
Linux设备驱动系列(二)——第一个设备驱动程序_Linux内核_Linux内核拾遗_InfoQ写作社区