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