Linux第一个驱动 Hello world

Linux第一个驱动 Hello world

一、什么是驱动?

​ 就如字面意思,驱动就是驱使一个东西动起来。如在单片机上跑一个程序,进行死循环。这个就是裸机程序。

​ 在Linux中驱动就是硬件与操作系统沟通的桥梁,应用程序的执行是依赖操作系统的,但是应用程序不能直接操作硬件。Linux的体系架构是的系统更加稳定可靠。
在这里插入图片描述

二、Linux驱动中的分类:

1.字符设备驱动

​ 如:触摸屏、LED灯、鼠标、对GPIO的控制。

2.块设备驱动

​ 如:TF卡、emmc等与存储相关的都属于块设备。

3.网络设备驱动

​ 如:WiFi、以太网

注:其中字符设备驱动是最重要的。

三、编写Hello world驱动

1.驱动分为四部分:

​ (1)头文件

​ (2)驱动模块的入口和出口

​ (3)声明信息

​ (4)功能实现

2.编写过程

​ 第一步:包含头文件

#include "linux/init.h"				//包含宏定义的头文件
#include "linux/module.h"			//包含初始化加载模块的头文件

​ 第二步:驱动模块的出口和入口

module_init();      				//入口函数 
module_exit();      				//出口函数

​ 第三步:声明模块拥有开源许可证

MODULE_LICENSE("GPL");

​ 第四步:功能的实现

static int hello_init(void)
{
    printk("hello world");
    return 0;
}

static void hello_exit(void)
{
    printk("goodbye world");
}

​ 内核加载时,打印“hello world” ,内核模块卸载时打印“goodbye world”。

​ 注意:内核打印函数是不能用printf,因为内核中没有办法使用C语言库。

四、完整的代码例程

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

static int hello_init(void)
{
    printk("hello world");
    return 0;
}

static void hello_exit(void)
{
    printk("goodbye world");
}

module_init(hello_init);      //入口函数 
module_exit(hello_exit);      //出口函数

MODULE_LICENSE("GPL");

五、编译驱动

​ 编译驱动分为两种,一种是把模块编译进内核,在内核启动是会自动加载;另一种是编译成模块,也就是.ko文件,需要自己手动加载

1.编译成模块

​ 第一步,编写Makefile

export CROSS_COMPILE=aarch64-linux-gnu-   //设置环境变量
obj-m += helloWorld.o   //把驱动编译成模块
KDIR:=	/home/forlinx/work/ls1028/OK1028-linux-fs/build/linux/linux/arm64/LS/output/ //编译生成的内核文件存在此目录
/*		/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/linux  	//内核源码路径,但是编译时所依赖的内核文件不在此目录    */
		
PWD:=$(shell pwd)		//获取当前路径
all:
	make -C $(KDIR) M=$(PWD) modules

​ 第二步,编译驱动

​ 编译驱动之前需要注意的问题:

​ (1)内核源码一定要先编译通过

​ (2)编译驱动模块用的内核源码一定要和开发板上运行的内核镜像是同一套

​ (3)Ubuntu的环境是否为ARM结构:

root@ubuntu:~/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel# export ARCH=arm64
root@ubuntu:~/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel# make menuconfig

在这里插入图片描述

遇到的错误以及解决办法

错误一、 Makefile文件生成的.o文件与.c文件名不一致问题:
root@ubuntu:~/work/WHT_work/hello_world# ls
hello_world.c  Makefile
root@ubuntu:~/work/WHT_work/hello_world# cat Makefile 
export CROSS_COMPILE=aarch64-linux-gnu-

obj-m += helloworld.o 		#可以看到这个helloworld.o与hello_world.c文件名字不一致

KDIR:=/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/linux

pwd:=$(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules

root@ubuntu:~/work/WHT_work/hello_world# make
make -C /home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/linux M=/home/forlinx/work/WHT_work/hello_world modules
make[1]: 进入目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
make[2]: *** 没有规则可制作目标“/home/forlinx/work/WHT_work/hello_world/helloworld.o”,由“__build” 需求。 停止。
Makefile:1652: recipe for target '/home/forlinx/work/WHT_work/hello_world' failed
make[1]: *** [/home/forlinx/work/WHT_work/hello_world] Error 2
make[1]: 离开目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
Makefile:10: recipe for target 'all' failed
make: *** [all] Error 2

解决办法:

​ 把.o文件名称和.c文件名称进行统一。

错误二、 没有 #include <asm/types.h> 文件
root@ubuntu:~/work/WHT_work/hello_world# make
make -C /home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/linux M=/home/forlinx/work/WHT_work/hello_world modules
make[1]: 进入目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
  CC [M]  /home/forlinx/work/WHT_work/hello_world/hello_world.o
In file included from ./include/linux/compiler.h:180:0,
                 from ./include/linux/init.h:5,
                 from /home/forlinx/work/WHT_work/hello_world/hello_world.c:1:
./include/uapi/linux/types.h:5:10: fatal error: asm/types.h: 没有那个文件或目录
 #include <asm/types.h>
          ^~~~~~~~~~~~~
compilation terminated.
scripts/Makefile.build:265: recipe for target '/home/forlinx/work/WHT_work/hello_world/hello_world.o' failed
make[2]: *** [/home/forlinx/work/WHT_work/hello_world/hello_world.o] Error 1
Makefile:1652: recipe for target '/home/forlinx/work/WHT_work/hello_world' failed
make[1]: *** [/home/forlinx/work/WHT_work/hello_world] Error 2
make[1]: 离开目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
Makefile:10: recipe for target 'all' failed
make: *** [all] Error 2

解决办法:

​ 出该问题的原因为内核源码中可能存在已编译的生成的文件和配置文件,执行 make mrproper 命令清除后内核可正常编译。

​ 需要注意的是在使用 make mrproper 时要在内核源码的目录下进行(路径:/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/linux)

​ 疑问:Makefile中mrproper和distclean,clean之间的区别是什么,之前使用过clean清除了编译过程中生成的.o文件,那他们的详细区别是什么呢?

三者的区别:
	clean		:	删除大多数生成的文件,但保留配置和足够的构建支持来构建外部模块
	mrproper	:	 删除所有生成的文件+配置+各种备份文件
	distclean	:	 mrproper +删除编辑器备份和补丁文件
错误三、没有 auto.conf 文件
root@ubuntu:~/work/WHT_work/hello_world# make
make -C /home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/linux M=/home/forlinx/work/WHT_work/hello_world modules
make[1]: 进入目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
Makefile:614: include/config/auto.conf: 没有那个文件或目录

  ERROR: Kernel configuration is invalid.
         include/generated/autoconf.h or include/config/auto.conf are missing.
         Run 'make oldconfig && make prepare' on kernel src to fix it.

Makefile:687: recipe for target 'include/config/auto.conf' failed
make[1]: *** [include/config/auto.conf] Error 1
make[1]: 离开目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
Makefile:10: recipe for target 'all' failed
make: *** [all] Error 2
解决办法:

​ 报错原因是因为没有.config文件,只要执行一次 make menuconfig生成一下文件,然后再去执行make oldconfig && make prepare即可。

错误四、出入口函数导致的错误
root@ubuntu:~/work/WHT_work/hello_world# make
make -C /home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/linux M=/home/forlinx/work/WHT_work/hello_world modules
make[1]: 进入目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
  CC [M]  /home/forlinx/work/WHT_work/hello_world/hello_world.o
In file included from /home/forlinx/work/WHT_work/hello_world/hello_world.c:2:0:
/home/forlinx/work/WHT_work/hello_world/hello_world.c: In function ‘__inittest’:
./include/linux/module.h:129:4: warning: ‘return’ with no value, in function returning non-void [-Wreturn-type]
  { return initfn; }     \
    ^
/home/forlinx/work/WHT_work/hello_world/hello_world.c:15:1: note: in expansion of macro ‘module_init’
 module_init();
 ^~~~~~~~~~~
./include/linux/module.h:128:42: note: declared here
  static inline initcall_t __maybe_unused __inittest(void)  \
                                          ^
/home/forlinx/work/WHT_work/hello_world/hello_world.c:15:1: note: in expansion of macro ‘module_init’
 module_init();
 ^~~~~~~~~~~
/home/forlinx/work/WHT_work/hello_world/hello_world.c: In function ‘__exittest’:
./include/linux/module.h:135:4: warning: ‘return’ with no value, in function returning non-void [-Wreturn-type]
  { return exitfn; }     \
    ^
/home/forlinx/work/WHT_work/hello_world/hello_world.c:16:1: note: in expansion of macro ‘module_exit’
 module_exit();
 ^~~~~~~~~~~
./include/linux/module.h:134:42: note: declared here
  static inline exitcall_t __maybe_unused __exittest(void)  \
                                          ^
/home/forlinx/work/WHT_work/hello_world/hello_world.c:16:1: note: in expansion of macro ‘module_exit’
 module_exit();
 ^~~~~~~~~~~
/home/forlinx/work/WHT_work/hello_world/hello_world.c: At top level:
./include/linux/module.h:130:6: error: ‘init_module’ aliased to undefined symbol ‘’
  int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));
      ^
/home/forlinx/work/WHT_work/hello_world/hello_world.c:15:1: note: in expansion of macro ‘module_init’
 module_init();
 ^~~~~~~~~~~
./include/linux/module.h:136:7: error: ‘cleanup_module’ aliased to undefined symbol ‘’
  void cleanup_module(void) __copy(exitfn) __attribute__((alias(#exitfn)));
       ^
/home/forlinx/work/WHT_work/hello_world/hello_world.c:16:1: note: in expansion of macro ‘module_exit’
 module_exit();
 ^~~~~~~~~~~
/home/forlinx/work/WHT_work/hello_world/hello_world.c:10:13: warning: ‘hello_exit’ defined but not used [-Wunused-function]
 static void hello_exit(void)
             ^~~~~~~~~~
/home/forlinx/work/WHT_work/hello_world/hello_world.c:4:12: warning: ‘hello_init’ defined but not used [-Wunused-function]
 static int hello_init(void)
            ^~~~~~~~~~
scripts/Makefile.build:265: recipe for target '/home/forlinx/work/WHT_work/hello_world/hello_world.o' failed
make[2]: *** [/home/forlinx/work/WHT_work/hello_world/hello_world.o] Error 1
Makefile:1652: recipe for target '/home/forlinx/work/WHT_work/hello_world' failed
make[1]: *** [/home/forlinx/work/WHT_work/hello_world] Error 2
make[1]: 离开目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
Makefile:12: recipe for target 'all' failed
make: *** [all] Error 2

解决办法:

​ 这个问题是在 module_init(hello_init); //入口函数 module_exit(hello_exit); //出口函数

这两个函数上,在Ubuntu中编译的源码中是没有添加括号里面的内容的,所以才会导致这个问题,添加进去就没有问题了。

错误五、未设置ARM结构导致的错误
root@ubuntu:~/work/WHT_work/hello_world# make
make -C /home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/linux M=/home/forlinx/work/WHT_work/hello_world modules
make[1]: 进入目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
  CC [M]  /home/forlinx/work/WHT_work/hello_world/hello_world.o
aarch64-linux-gnu-gcc: error: unrecognized argument in option ‘-mcmodel=kernel’
aarch64-linux-gnu-gcc: note: valid arguments to ‘-mcmodel=’ are: large small tiny
aarch64-linux-gnu-gcc: error: unrecognized command line option ‘-mno-sse’; did you mean ‘-fno-dse’?
aarch64-linux-gnu-gcc: error: unrecognized command line option ‘-mno-mmx’
aarch64-linux-gnu-gcc: error: unrecognized command line option ‘-mno-sse2’; did you mean ‘-fno-dse’?
aarch64-linux-gnu-gcc: error: unrecognized command line option ‘-mno-3dnow’; did you mean ‘-fno-doc’?
aarch64-linux-gnu-gcc: error: unrecognized command line option ‘-m64’
aarch64-linux-gnu-gcc: error: unrecognized command line option ‘-mno-red-zone’; did you mean ‘-fno-regmove’?
scripts/Makefile.build:265: recipe for target '/home/forlinx/work/WHT_work/hello_world/hello_world.o' failed
make[2]: *** [/home/forlinx/work/WHT_work/hello_world/hello_world.o] Error 1
Makefile:1652: recipe for target '/home/forlinx/work/WHT_work/hello_world' failed
make[1]: *** [/home/forlinx/work/WHT_work/hello_world] Error 2
make[1]: 离开目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
Makefile:12: recipe for target 'all' failed
make: *** [all] Error 2
解决办法:

​ 出现这个问题的原因是因为在编译时来回切换环境,导致一开始设置的ARM架构失效了,重新设置一下就好了

错误六 加载模块时产生的问题
root@forlinx:~# insmod hello_world.ko 
[ 2512.690509] hello_world: version magic '5.4.3 SMP mod_unload modversions aarch64' should be '5.4.3 SMP preempt mod_unload modversions aarch64'
insmod: ERROR: could not insert module hello_world.ko: Invalid module format
解决办法

​ 产生这个问题的原因是在Makefile中的KDIR引用的路径发生了错误,我是直接引用的源码路径,按常理来说是没有办法直接编译成功,实际情况是编译成功了,产生这个问题的原因是在之前学习编译内核时没有使用flex-builder命令编译,在内核源码目录用make进行编译的。

​ 而使用flex-builder编译出来的内核会保存在/home/forlinx/work/ls1028/OK1028-linux-fs/build/linux/linux/arm64/LS/output/ 这个路径下,所以把此位置的路径跟换掉就可以解决这个问题。

意外的错误、 编译1028内核出现错误
root@ubuntu:~/work/ls1028/OK1028-linux-fs/packages/linux/linux# flex-builder -c linux:custom -m ls1028ardb -a arm64
COMPONENT: linux:custom
MACHINE: ls1028ardb
DESTARCH: arm64
linux:custom
make: 进入目录“/home/forlinx/work/ls1028/OK1028-linux-fs”
make[1]: 进入目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux”
fetch-git-tree linux
fatal: 不是一个 git 仓库(或者任何父目录):.git
 Building linux with  
 KERNEL_CFG = ok1028_defconfig 
 Compiler = aarch64-linux-gnu-gcc (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0 
make[2]: 进入目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
make[3]: 进入目录“/home/forlinx/work/ls1028/OK1028-linux-fs/build/linux/linux/arm64/LS/output”
***
*** The source tree is not clean, please run 'make mrproper'
*** in /home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel
***
/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel/Makefile:512: recipe for target 'outputmakefile' failed
make[3]: *** [outputmakefile] Error 1
make[3]: 离开目录“/home/forlinx/work/ls1028/OK1028-linux-fs/build/linux/linux/arm64/LS/output”
Makefile:179: recipe for target 'sub-make' failed
make[2]: *** [sub-make] Error 2
make[2]: 离开目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
make[2]: 进入目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
make[3]: 进入目录“/home/forlinx/work/ls1028/OK1028-linux-fs/build/linux/linux/arm64/LS/output”
***
*** The source tree is not clean, please run 'make mrproper'
*** in /home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel
***
/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel/Makefile:512: recipe for target 'outputmakefile' failed
make[4]: *** [outputmakefile] Error 1
/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel/Makefile:678: recipe for target 'include/config/auto.conf.cmd' failed
make[3]: *** [include/config/auto.conf.cmd] Error 2
make[3]: 离开目录“/home/forlinx/work/ls1028/OK1028-linux-fs/build/linux/linux/arm64/LS/output”
Makefile:179: recipe for target 'sub-make' failed
make[2]: *** [sub-make] Error 2
make[2]: 离开目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/OK1028-linux-kernel”
Makefile:26: recipe for target 'build_kernel' failed
make[1]: *** [build_kernel] Error 2
make[1]: 离开目录“/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux”
Makefile:22: recipe for target 'linux' failed
make: *** [linux] Error 2
make: 离开目录“/home/forlinx/work/ls1028/OK1028-linux-fs”

解决办法:

​ 可以看到打印信息提示要使用 make mrproper ,出该问题的原因为内核源码中可能存在已编译的生成的文件和配置文件,执行 make mrproper 命令清除后内核可正常编译。

​ 需要注意的是在使用 make mrproper 时要在内核源码的目录下进行(路径:/home/forlinx/work/ls1028/OK1028-linux-fs/packages/linux/linux)

​ 疑问:Makefile中mrproper和distclean,clean之间的区别是什么,之前使用过clean清除了编译过程中生成的.o文件,那他们的详细区别是什么呢?

三者的区别:
	clean		:	删除大多数生成的文件,但保留配置和足够的构建支持来构建外部模块
	mrproper	:	 删除所有生成的文件+配置+各种备份文件
	distclean	:	 mrproper +删除编辑器备份和补丁文件

​ 最终解决完这些问题以后就可以顺利的编译成功了,也是很坎坷,都是出现在一些小的细节上。下次注意。。。。

​ 效果:

root@forlinx:~# rmmod hello_world.ko 
root@forlinx:~# insmod hello_world.ko 
root@forlinx:~# dmesg | grep hello     
[  172.948012] hello world
root@forlinx:~# dmesg | grep byb  
[  174.251172] byb byb

  • 20
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
LINUX设备驱动第三版_ 前言 第一章 设备驱动程序简介 设备驱动程序的作用 内核功能划分 设备和模块的分类 安全问题 版本编号 许可证条款 加入内核开发社团 本书概要 第二章 构造和运行模块 设置测试系统 Hello World模块 核心模块与应用程序的对比 编译和装载 内核符号表 预备知识 初始化和关闭 模块参数 在用户空间编写驱动程序 快速参考 第三章 字符设备驱动程序 scull的设计 主设备号和次设备号 一些重要的数据结构 字符设备的注册 open和release scull的内存使用 read和write 试试新设备 快速参考 第四章 调试技术 内核中的调试支持 通过打印调试 通过查询调试 通过监视调试 调试系统故障 调试器和相关工具 第五章 并发和竞态 scull的缺陷 并发及其管理 信号量和互斥体 completion 自旋锁 锁陷阱 除了锁之外的办法 快速参考 第六章 高级字符驱动程序操作 ioctl 阻塞型I/O poll和select 异步通知 定位设备 设备文件的访问控制 快速参考 第七章 时间、延迟及延缓操作 度量时间差 获取当前时间 延迟执行 内核定时器 tasklet 工作队列 快速参考 第八章 分配内存 kmalloc函数的内幕 后备高速缓存 get_free_page和相关函数 vmalloc及其辅助函数 per-CPU变量 获取大的缓冲区 快速参考 第九章 与硬件通信 I/O端口和I/O内存 使用I/O端口 I/O端口示例 使用I/O内存 快速参考 第十章 中断处理 准备并口 安装中断处理例程 实现中断处理例程 顶半部和底半部 中断共享 中断驱动的I/O 快速参考 第十一章 内核的数据类型 使用标准C语言类型 为数据项分配确定的空间大小 接口特定的类型 其他有关移植性的问题 链表 快速参考 第十二章 PCI驱动程序 PCI接口 ISA回顾 PC/104和PC/104+ 其他的PC总线 SBus NuBus 外部总线 快速参考 第十三章 USB驱动程序 USB设备基础 USB和Sysfs USB urb 编写USB驱动程序 不使用urb的USB传输 快速参考 第十四章 Linux设备模型 kobject、kset和子系统 低层sysfs操作 热插拔事件的产生 总线、设备和驱动程序 类 各环节的整合 热插拔 处理固件 快速索引 第十五章 内存映射和DMA Linux的内存管理 mmap设备操作 执行直接I/O访问 直接内存访问 快速参考 第十六章 块设备驱动程序 注册 块设备操作 请求处理 其他一些细节 快速参考 第十七章 网络驱动程序 snull设计 连接到内核 net_device结构细节 打开和关闭 数据包传输 数据包的接收 中断处理例程 不使用接收中断 链路状态的改变 套接字缓冲区 MAC 地址解析 定制 ioctl 命令 统计信息 组播 其他知识点详解 快速参考 第十八章 TTY驱动程序 小型TTY驱动程序 tty_driver函数指针 TTY线路设置 ioctls proc和sysfs对TTY设备的处理 tty_driver结构详解 tty_operations结构详解 tty_struct结构详解 快速参考 参考书目 9112405-1_o.jpg (85.53 KB, 下载次数: 50)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值