草庐IT

Linux驱动实践:带你一步一步编译内核驱动程序

DPDK技术员 2023-09-29 原文

学习的困惑

记得以前我在开始学习驱动开发的时候,找来很多文章、资料来学习,但是总是觉得缺少了点全局视角。

就好像:我想看清一座山的全貌,但总是被困在一个、又一个山谷中一样。

主要的困惑有 3 点:

  1. 每一篇文章的介绍都是正确的,但是如果把很多文章放在一起看,就会发现怎么说的都不一样啊?

  2. 有些文章注重函数的介绍,但是缺乏一个全局的视角,从整体上来观察驱动程序的结构;

  3. 对于一个新手来说,能够边学习、边实践,这是最好的学习方式,但是很多文章不会注意这方面。虽然文章内容很漂亮,但是不知道怎么去实践、验证。

因此,这几篇文章我们就从最简单的驱动模块编译开始,然后介绍字符设备驱动程序。

在这部分,会以 GPIO 为例子,重点描述其中的关键节点。

最后再介绍在中断处理程序中,如何利用信号量、小任务、工作队列,把内核事件传递到应用层来处理。

作为第一个开篇文章,从最简单的内核编译开始。

实际操作一下:如何把一个最简单的驱动程序(hello),按照 2 种方式进行编译:

  1. 编译进内核;

  2. 编译为一个独立的驱动模块;

实践环境

为了便于测试,以下操作都是在 Ubuntu16.04 操作系统里完成的。

编译Linux驱动程序,肯定需要内核源码,这里选择的是 linux-4.15 版本,可以在官网下载。

文末有下载方式。

下载之后,把linux-4.15.tar.gz解压到Ubuntu中任意目录即可,例如:解压到~/tmp/目录下:

$ tar -zxvf linux-4.15.tar.gz -C ~/tmp/

编译进内核

创建驱动程序目录

linux 中的驱动,一般都放在 linux-4.15/drivers/ 目录下,因此在这个目录中创建一个hello文件夹。

$ mkdir linux-4.15/drivers/hello

对于一个驱动来说,最重要的就是3个文件:

  1. 源代码

  2. Kconfig

  3. Makefile

只要按照固定的格式来编写这3个文件,linux内核的编译脚本就可以确保把我们的驱动程序编译进去。

创建源文件

首先是源码,在hello文件夹中创建源文件 hello.c

$ cd linux-4.15/drivers/hello
$ touch hello.c

源文件hello.c的内容是:

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

// 当驱动被加载的时候,执行此函数
static int __init hello_init(void)
{
    printk(KERN_ALERT "welcome, hello"\n");
    return 0;
}

// 当驱动被卸载的时候,执行此函数
static void __exit hello_exit(void)
{
    printk(KERN_ALERT "bye, hello\n");
}

// 版权声明
MODULE_LICENSE("GPL");

// 以下两个函数属于 Linux 的驱动框架,只要把驱动两个函数地址注册进去即可。
module_init(hello_init);
module_exit(hello_exit);

有两个小地方注意一下:

  1. 在内核中,打印函数是 printk,而不是 printf;

  2. 打印信息的级别有好几个,从 DEBUG 到 EMERG,这里使用的是 KERN_ALERT,方便查看打印信息。

创建 Kconfig 文件

这个文件是用来对内核进行配置的,当执行 make menuconfig 指令的时候,这个文件就被解析。

先创建文件:

$ cd linux-4.15/drivers/hello
$ touch Kconfig

添加如下内容:

config HELLO
tristate "hello driver"
help
  just a simplest driver.
default y

第一行内容 config HELLO ,在执行配置的时候,将会生成一个变量 CONFIG_HELLO ,而这个变量,将会在编译的时候,被 Makefile 引用。

最后一行的 default y ,就表示把 CONFIG_HELLO 的值设置成 y,从而让这个驱动被编译到内核中。

现在,hello驱动中的KConfig配置文件已经准备好了,但是还需要这个配置文件登记到 Linux 内核的整体配置文件中。

也就是把它登记在 linux-4.15/drivers/Kconfig 文件的末尾:

source "drivers/hello/Kconfig"

endmenu   // 加在这一句的上面

现在,可以来执行下面指令,看一下具体的配置界面:

$ cd linux-4.15/
$ make distclean
$ make ARCH=x86_64 defconfig
$ make ARCH=x86_64 menuconfig

2条指令,是用来把默认的配置保存到当前目录下的 .config 配置文件,也就是把一个默认的配置文件复制过来,作为我们自己的配置文件。

以后再修改配置参数时,修改的内容就会存储在 .config 文件中,

3条指令,是用来配置内核的,可以进入 Device Drivers 菜单,然后在最底层看到我们的 hello driver 被标记成星号, 这表示被编译进内核。

按向下方向键,把高亮定位到 Device Drivers ---> ,然后敲回车键,进入到 Device Drivers 的配置界面。

按向下方向键,一直到最后一个条目,就可以看到我们的 hello 驱动了,如下:

可以看到 hello driver 前面显示的是型号 *,这表示:该驱动将会编译进内核。

我们可以按下空格键试一下,会在三种标记中切换:型号,M,空值。M 标记意思是编译成驱动模块。

我们这里选择星号(编译进内核),然后按下右方向键,最下方的几个按键的焦点移动到按钮上:

按下回车键,就会弹出保存对话框,选择默认保存文件 .config 即可,然后在<Ok>按钮高亮的时候,按下回车键即可保存。

此时,在弹出的确认窗口中,选择 <Exit>,按下回车键即可:

此时,返回到 Device Drivers 的配置界面,在最下面的按钮中,选择让 <Exit> 高亮,然后一路退出即可。

创建 Makefile 文件

Makefile 文件是make工具的脚本,首先创建它:

$ cd linux-4.15/drivers/hello
$ touch Makefile

其中的内容只有一行:

obj-$(CONFIG_HELLO) += hello.o
  1. CONFIG_HELLO 可以看做一个变量,在编译的时候,这个变量的值可能是:y, n 或者 m。

  2. 在刚才的 Kconfig 参数配置中,CONFIG_HELLO 被设置为 y,于是这句话就被翻译成:obj-y += hello,表示把 hello 驱动编译进内核。

现在,hello驱动程序的Makefile已经创建好了,我们还要让linux内核的编译框架知道这个文件才行。

在文件 linux-4.15/drivers/Makefile 中的末尾,添加如下内容:

obj-$(CONFIG_HELLO)    += hello/

编译

万事俱备,只欠编译!依次执行如下指令:

$ cd linux-4.15/
$ make -j4

make指令执行结束之后,编译得到的内核中(vmlinux)就包含了我们的hello驱动。

编译为驱动模块

编译为驱动模块,也有两种 操作方式:

编译所有的驱动模块

  1. 在执行 make ARCH=x86_64 menuconfig 指令的时候,把 hello 配置成 M;

  2. 然后在 linux-4.15 中执行编译模块指令:make -j4 modules。

编译成功之后,就可以得到文件: linux-4.15/drivers/hello/hello.ko。

这样的编译指令,是把所有的模块都编译了一次(在输出信息中,可以看到编译了很多模块)。

只编译 hello 这一个驱动模块

另外一种编译驱动模块的方式是:进入hello目录,只编译这一个驱动模块。

这种编译方法,就需要修改hello目录下的Makefile文件了,内容如下:

可以把 hello 目录下的所有文件删除,只保留源文件 hello.c,然后新建 Makefile 文件。

ifneq ($(KERNELRELEASE),)
        obj-m := hello.o
else
        KERNELDIR ?= /lib/modules/$(shell uname -r)/build
        PWD := $(shell pwd)
default:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
        $(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean
endif

然后,在hello文件夹中执行make指令,即可得到驱动模块  hello.ko 。

验证一下

加载驱动:

$ cd linux-4.15/drivers/hello
$ sudo insmod ./hello.ko

此时终端窗口是没有任何输出的,需要输入指令 dmesg | tail ,可以看到  hello_init 函数的输出内容:

卸载驱动:

$ sudo rmmod hello

再次输入 dmesg | tail ,可以看到 hello_exit 函数的输出内容:

原文链接:https://mp.weixin.qq.com/s/L8Y3Uw7Lida8N5YvK40BWg 


学习更多相关资料及视频
DPDK 学习资料、教学视频和学习路线图 :https://space.bilibili.com/1600631218
Dpdk/网络协议栈/ vpp /OvS/DDos/NFV/虚拟化/高性能专家 上课地址: https://ke.qq.com/course/5066203?flowToken=1043799
DPDK开发学习资料、教学视频和学习路线图分享有需要的可以自行添加学习交流q 君羊909332607备注(XMG) 获取
 

有关Linux驱动实践:带你一步一步编译内核驱动程序的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  3. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  4. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  5. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  6. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  7. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  8. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  9. ruby - Sinatra set cache_control to static files in public folder编译错误 - 2

    我不知道为什么,但是当我设置这个设置时它无法编译设置:static_cache_control,[:public,:max_age=>300]这是我得到的syntaxerror,unexpectedtASSOC,expecting']'(SyntaxError)set:static_cache_control,[:public,:max_age=>300]^我只想将“过期”header设置为css、javaascript和图像文件。谢谢。 最佳答案 我猜您使用的是Ruby1.8.7。Sinatra文档中显示的语法似乎是在Ruby1.

  10. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

随机推荐