Linux驱动实践:带你一步一步编译内核驱动程序
作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++、嵌入式、Linux。
关注下方公众号,回复【书籍】,获取 Linux、嵌入式领域经典书籍;回复【PDF】,获取所有原创文章( PDF 格式)。
目录
别人的经验,我们的阶梯!
大家好,我是道哥。今天给大家分享一些笔记本里的一些存货: Linux 系统中的驱动和中断相关。
大概会用 6~7 篇的文章,由浅入深的为大家介绍Linux
中驱动程序的编写方法。
文章的顺序,也是我之前自己学习时的顺序。
以前的学习记录比较零散,现在只是把它们按照一定的顺序重新梳理一下。
这几篇文章,理论知识会少一些,更注重实际的操作。
我会把操作用引导的代码,全部上传到网盘上,在文末有下载说明。
只要根据文中介绍的步骤进行操作,就一定可以操作成功。
学习的困惑
记得以前我在开始学习驱动开发的时候,找来很多文章、资料来学习,但是总是觉得缺少了点全局视角。
就好像:我想看清一座山的全貌,但总是被困在一个、又一个山谷中一样。
主要的困惑有 3 点:
每一篇文章的介绍都是正确的,但是如果把很多文章放在一起看,就会发现怎么说的都不一样啊?
有些文章注重函数的介绍,但是缺乏一个全局的视角,从整体上来观察驱动程序的结构;
对于一个新手来说,能够边学习、边实践,这是最好的学习方式,但是很多文章不会注意这方面。虽然文章内容很漂亮,但是不知道怎么去实践、验证。
因此,这几篇文章我们就从最简单的驱动模块编译开始,然后介绍字符设备驱动程序。
在这部分,会以 GPIO 为例子,重点描述其中的关键节点。
最后再介绍在中断处理程序中,如何利用信号量、小任务、工作队列,把内核事件传递到应用层来处理。
作为第一个开篇文章,从最简单的内核编译开始。
实际操作一下:如何把一个最简单的驱动程序(hello
),按照 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
个文件:
源代码
Kconfig
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);
有两个小地方注意一下:
在内核中,打印函数是 printk,而不是 printf;
打印信息的级别有好几个,从 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 即可,然后在 按钮高亮的时候,按下回车键即可保存。
此时,在弹出的确认窗口中,选择 <exit>
,按下回车键即可:
此时,返回到 Device Drivers 的配置界面,在最下面的按钮中,选择让<exit>
高亮,然后一路退出即可。
创建 Makefile 文件
Makefile
文件是make
工具的脚本,首先创建它:
$ cd linux-4.15/drivers/hello
$ touch Makefile
其中的内容只有一行:
obj-$(CONFIG_HELLO) += hello.o
CONFIG_HELLO 可以看做一个变量,在编译的时候,这个变量的值可能是:y, n 或者 m。
在刚才的 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
驱动。
编译为驱动模块
编译为驱动模块,也有两种 操作方式:
编译所有的驱动模块
在执行 make ARCH=x86_64 menuconfig 指令的时候,把 hello 配置成 M;
然后在 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 函数的输出内容:
资料下载
在公众号【IOT物联网小镇】的后台回复关键字:1112,获取下列文件的网盘地址:
linux-4.15.tar.gz
hello文件夹压缩包
------ End ------
推荐阅读
【2】C语言指针-从底层原理到花式技巧,用图文和代码帮你讲解透彻
星标公众号,第一时间看文章!
Linux驱动实践:带你一步一步编译内核驱动程序的更多相关文章
- Linux驱动实践:如何编写【 GPIO 】设备的驱动程序?
作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...
- Linux驱动实践:中断处理函数如何【发送信号】给应用层?
作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...
- Linux驱动实践:你知道【字符设备驱动程序】的两种写法吗?
作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...
- Linux驱动实践:一起来梳理中断的前世今生(附代码)
作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...
- 【Linux驱动】TQ2440 DM9000E网卡驱动移植(Linux-2.6.30.4)
花了一天的时间研究了一下Linux-2.6.30.4版本号内核下关于TQ2440 DM9000E的网卡驱动移植.总结一下自己的收获. 事实上.在Linux-2.6.30.4版本号内核下有关于网卡驱动, ...
- Linux开源模块迁移概述暨交叉编译跨平台移植总结--从《嵌入式Linux驱动模板简洁和工程实践》
本文摘录<嵌入式Linux驱动模板简洁和工程实践>一本书"开发和调试技术". Linux强大的是,有那么多的开源项目可以使用.通常非常需要可以通过寻找相关的源模块被定义 ...
- Linux驱动之内核自带的S3C2440的LCD驱动分析
先来看一下应用程序是怎么操作屏幕的:Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出FrameBuffer这个设备来供用户 ...
- linux驱动开发重点关注内容--摘自《嵌入式Linux驱动模板精讲与项目实践》
本文摘自本人拙著 <嵌入式Linux驱动模板精讲与项目实践> 初步看起来Linux设备驱动开发涉及内容非常多,而须要实现驱动的设备千差万别.事实上做一段时间驱动之后回首看来主要就是下面几点 ...
- Linux驱动开发十六.input系统——3.系统自带的input驱动
前面两章我们通过input子系统构建了一个按键类型的输入设备的驱动,其实Linux的内核还提供了一套基于GPIO的按键驱动程序,和LED设备一样,我们只需要在编译内核的过程中进行配置然后在设备树中定义 ...
随机推荐
- 项目配置shiro原缓存注解失效
项目用springboot + shiro + ehcache @cacheable 注解不起作用原因 Shiro框架初始化比Spring框架的某些部件早,导致使用@Autowire注入Shiro框架 ...
- ApsNetCore打造一个“最安全”的api接口
Authentication,Authorization 如果公司交给你一个任务让你写一个api接口,那么我们应该如何设计这个api接口来保证这个接口是对外看起来"高大上",&qu ...
- 人力节省 50%,研发效能提升 40%,阿里 Serverless 架构落地实践
作者 | 万佳 嘉宾 | 杨皓然(不瞋) 导读:云的下一波浪潮是什么?杨皓然称"是 Serverless".作为一名阿里老兵,他早在 2010 年即加入阿里云,曾深度参与阿里云飞天 ...
- linux主机互信操作
一.主机互信原理两个主机之间ssh登录需要提示输入对方的密码,当频繁需要登录操作时,可以通过linux公钥和秘钥,建立双机信任关系.把你源主机的公钥文件内容追加到目的主机对应用户下的authorize ...
- SphereEx 公司成立,推动 Apache ShardingSphere 社区加速发展
近日,SphereEx 商业公司在中国红杉种子基金及初心资本助力下,已完成公司及团队组建.各大媒体平台及公众号已相继报道,并抢占新闻头条.作为以 Apache ShardingSphere 核心团队组 ...
- 超详细的Ribbon源码解析
Ribbon简介 什么是Ribbon? Ribbon是springcloud下的客户端负载均衡器,消费者在通过服务别名调用服务时,需要通过Ribbon做负载均衡获取实际的服务调用地址,然后通过http ...
- Oracle job的用法
1.Broken()过程更新一个已提交的工作的状态,典型地是用来把一个已破工作标记为未破工作. 这个过程有三个参数:job .broken与next_date. PROCEDURE Broken (j ...
- 小白自制Linux开发板 六. SPI TFT屏幕修改与移植
本文章参考:https://www.bilibili.com/read/cv9947785?spm_id_from=333.999.0.0 本篇通过SPI接口,使用ST7789V TFT焊接屏(13p ...
- [Git系列] 前言
Git 简介 Git 是一个重视速度的分布式版本控制和代码管理系统,最初是由 Linus Torvalds 为开发 Linux 内核而设计并开发的,是一款遵循二代 GUN 协议的免费软件.这一教程会向 ...
- [对对子队]会议记录4.18(Scrum Meeting9)
今天已完成的工作 何瑞 工作内容:修复了一些关卡1的bug 相关issue:搭建关卡1 相关签入:4.18签入1 4.18签入2 梁河览 工作内容:实现了音量控制,添加了BGM 相 ...