Linux驱动开发1--内核入门之hello模块
3.1 创建项目驱动程序工作路径
[wudongxu@centos6_master ~]$ cd gitee/fl2440/
[wudongxu@centos6_master fl2440]$ ls
3rdparty bootloader crosstool LICENSE linux README.md x86_tools
[wudongxu@centos6_master fl2440]$ mkdir driver
[wudongxu@centos6_master fl2440]$ cd driver
3.2 编写hello模块C文件
[wudongxu@centos6_master driver]$ vim kernel_hello.c
kernel_hello.c ____________________________________________________________________________________________________________________
1 /*********************************************************************************
3 * All rights reserved.
4 *
5 * Filename: knernel_hello.c
6 * Description: This file
7 *
8 * Version: 1.0.0(03/19/2018)
10 * ChangeLog: 1, Release initial version on "03/19/2018 04:00:19 AM"
11 *
12 ********************************************************************************/
13 #include
//定义了一些相关的宏
14 #include
//定义了模块需要的
15 #include
16
//当使用insmod命令加载模块时,会调用该函数
17 static __init int hello_init(void)
18 {
19 printk(KERN_ALERT "hello,wudongxu IoT Studio!\n"); //打印hello,wudongxu IoT Studio
20 return 0;
21 }
22
//当使用rmmod命令卸载模块时,会调用该函数
23 static __exit void hello_exit(void)
24 {
25 printk(KERN_ALERT "Goodbye,I have found a good job!\n"); //打印Goodbye,I have found a good job
26 }
27
28 module_init(hello_init);
//声明模块加载函数,是内核模块的一个宏。也就是使insmod命令加载模块时,调用函数hello_init()。
29 module_exit(hello_exit);
// 声明模块卸载函数,是内核模块的一个宏。也就是使rmmod命令卸载模块时,调用函数hello_exit()。
30
32 MODULE_DESCRIPTION("Linux Kernel hello module (C) WuDongXu IoT Studio");
33 MODULE_LICENSE("Dual BSD/GPL"); /
/指定许可权为Dual BSD/GPL,该模块代码遵循BSD和GPL双重规范,用来告知内核, 该模块带有一个自由的许可证, 没有这样的说明, 在模块加载时内核会抱怨。
1,关于printk的说明
printk 函数在 Linux 内核中定义并且对模块可用,它与标准 C 库函数 printf 的行为相似。内核需要它自己的打印函数, 因为它靠自己运行, 没有 C 库的帮助. 模块能够调用 printk 是因为在 insmod加载了它之后, 模块被链接到内核并且可存取内核的公用符号. 字串 KERN_ALERT 是消息的优先级。
printk支持分级别打印调试,这些级别定义在linux-3.0/include/linux/printk.h文件中:
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
/* Use the default kernel loglevel */
#define KERN_DEFAULT "" Linux内核中printk()的语句是否打印到串口终端上,与u-boot里的bootargs参数中的 loglelve=7相关,只有低于loglevel级别的信息才会打印到控制终端上,否则不会在控制终端上输出。这时我们只能通过dmesg命令查看。 linux下的dmesg命令的可以查看linux内核所有的打印信息,它们记录在/var/log/messages系统日志文件中。linux内核中的打印信息很多,我们可以使用 dmesg -c命令清除之前的打印信息。
2, 关于 __init、module_init和module_exit 的说明
__init、module_init和module_exit这两个宏是定义在 linux-3.0/include/linux/init.h 中:
#define __init __section(.init.text) __cold notrace
#define __exit __section(.exit.text) __exitused __cold notrace
#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall("6",fn,6)
typedef int (*initcall_t)(void);
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" level ".init"))) = fn
static __init int hello_init(void) 宏就会被展开为 static __section(.init.text) __cold notrace int hello_init(void)
static __exit int hello_exit(void) 宏就会被展开为 __section(.exit.text) __exitused __cold notrace int hello_exit
这里的 __section 为gcc的链接选项,他表示把该函数链接到Linux内核映像文件的相应段中,这样hello_init将会被链接进.init.text段中,而hello_exit将会被链接进.exit.text段中。被链接进这两个段中的函数代码在调用完成之后,内核将会自动释放他们所占用的内存资源。因为这些函数只需要初始化或退出一次,所以hello_init()和hello_exit()函数最好在前面加上__init 和 __exit。
module_init(hello_init) 宏就会被展开为:
static int (*initcall_t)(void) __initcall_hello_init6 __used __attribute__((__section__(".initcall" "6"".init"))) = hello_init;
这段代码也就是定义了一个叫 __initcall_hello_init6的函数指针,他指向 hello_init 这个函数, gcc的链接选项 __attribute__ 和 __section__ 将该指针变量链接到linux内核映像的 .initcall 段中。linux系统在启动时,完成CPU和板级初始化之后,就会从该段中读入所有的模块初始化函数就执行。每一个Linux内核模块都需要使用module_init()和module_exit()宏来修饰,这样系统启动时才能自动调用并初始化他们
3.3. 在X86主机上测试内核模块
创建x86文件夹,在这里面编译并测试hello内核模块。
[wudongxu@centos6_master driver]$ mkdir x86
[wudongxu@centos6_master driver]$ cd x86
[wudongxu@centos6_master x86]$ ln -s ../kernel_hello.c
[wudongxu@centos6_master x86]$ vim Makefile
KERNAL_DIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
obj-m := kernel_hello.o
modules:
$(MAKE) -C $(KERNAL_DIR) M=$(PWD) modules
@make clear
clear:
@rm -f *.o *.cmd *.mod.c
@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
@rm -f .*ko.cmd .*.o.cmd .*.o.d
@rm -f *.unsigned
clean:
@rm -f hello.ko
[wudongxu@centos6_master x86]$ ls
kernel_hello.c Makefile
[wudongxu@ centos6_master x86]$ make
make -C /lib/modules/2.6.32-696.el6.x86_64/build
M=/home/wudongxu/gitee/fl2440/driver/x86 modules
make[1]: Entering directory `/usr/src/kernels/2.6.32-696.el6.x86_64' CC [M] /home/wudongxu/gitee/fl2440/driver/x86/kernel_hello.o
Building modules, stage 2. MODPOST 1 modules
CC /home/wudongxu/gitee/fl2440/driver/x86/kernel_hello.mod.o
LD [M] /home/wudongxu/gitee/fl2440/driver/x86/kernel_hello.ko.unsigned
NO SIGN [M] /home/wudongxu/gitee/fl2440/driver/x86/kernel_hello.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.32-696.el6.x86_64' make[1]: Entering directory `/home/wudongxu/gitee/fl2440/driver/x86' make[1]: Leaving directory
`/home/wudongxu/gitee/fl2440/driver/x86'
[wudongxu@centos6_master x86]$ ls
kernel_hello.c kernel_hello.ko Makefile
[guowenxue@centos6_master x86]$ ls
kernel_hello.c kernel_hello.ko Makefile
dmesg查看Linux内核的打印信息,dmesg -c将会清除之前Linux内核的打印信息
[wudongxu@centos6_master x86]$ sudo dmesg
[wudongxu@centos6_master x86]$ sudo dmesg -c
安装Linux内核模块,并查看内核的打印信息:
[wudongxu@centos6_master x86]$ sudo insmod kernel_hello.ko
[wudongxu@centos6_master x86]$ dmesg
Hello,WuDongXu IoT Studio!
lsmod命令查看当前linux内核安装了的内核模块
[wudongxu@centos6_master x86]$ sudo lsmod | grep kernel_hello
kernel_hello 891 0
rmmod将会删除linux内核安装了的内核模块
[wudongxu@centos6_master x86]$ sudo rmmod kernel_hello
[wudongxu@centos6_master x86]$ dmesg
Hello,
WuDongXu IoT Studio!
Goodbye, I have found a good job!
3.4 在ARM板上测试内核模块
[wudongxu@ centos6_master x86]$ cd ..
[wudongxu@centos6_master driver]$ vim Makefile
LINUX_SRC = ${shell pwd}/../linux/linux-3.0/
CROSS_COMPILE=/opt/xtools/arm920t/bin/arm-linuxINST_PATH=/tftp
PWD := $(shell pwd)
EXTRA_CFLAGS+=-DMODULE
obj-m += kernel_hello.o
modules:
@make -C $(LINUX_SRC) M=$(PWD) modules
@make clear
uninstall:
rm -f ${INST_PATH}/*.ko
install: uninstall
cp -af *.ko ${INST_PATH}
clear:
@rm -f *.o *.cmd *.mod.c
@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
@rm -f .*ko.cmd .*.o.cmd .*.o.d
clean: clear
@rm -f *.ko
关于Makefile文件的说明
1,LINUX_SRC 应该指定开发板所运行的Linux内核源码的路径,并且这个linux内核源码必须makemenuconfig,并且make过的,因为Linux内核的一个模块可能依赖另外一个模块,如果另外一个没有编译则会出现问题。所以Linux内核必须编译过,这样才能确认这种依赖关系;
2, 交叉编译器必须使用 CROSS_COMPILE 变量指定;
3, 如果编译Linux内核需要其它的一些编译选项,那可以使用 EXTRA_CFLAGS 参数来指定;
4, obj-m += kernel_hello.o 该行告诉Makefile要将 kernel_hello.c 源码编译生成内核模块文件kernel_hello.ko ;
5, @make -C $(LINUX_SRC) M=$(PWD) modules @ make是不打印这个命令本身 -C:把工作目录切换到-C后面指定的参数目录,M是Linux内核源码Makefile里面的一个变量,作用是回到当前目录继续读取Makefile。当使用make命令编译内核驱动模块时,将会进入到 KERNEL_SRC 指定的Linux内核源码中去编译,并在当前目录下生成很多临时文件以及驱动模块文件kernel_hello.ko;
6, clear 目标将编译linux内核过着产生的一些临时文件全部删掉;
[wudongxu@centos6_master driver]$ make
/home/wudongxu/gitee/fl2440/driver/../linux/linux-3.0/
make[1]: Entering directory `/home/wudongxu/gitee/fl2440/linux/linux-3.0' CC [M] /home/guowenxue/gitee/fl2440/driver/kernel_hello.o
Building modules, stage 2. MODPOST 1 modules
CC /home/guowenxue/gitee/fl2440/driver/kernel_hello.mod.o
LD [M] /home/guowenxue/gitee/fl2440/driver/kernel_hello.ko
make[1]: Leaving directory `/home/guowenxue/gitee/fl2440/linux/linux-3.0' make[1]: Entering directory `/home/guowenxue/gitee/fl2440/driver' make[1]: Leaving directory `/home/guowenxue/gitee/fl2440/driver'
[wudongxu@centos6_master driver]$ ls
kernel_hello.c kernel_hello.ko Makefile x86
开发板上测试:
使用之前的老内核测试刚编译的驱动模块:
~ >: tftp -gr kernel_hello.ko 192.168.0.5
kernel_hello.ko 100% |*******************************| 23746 0:00:00 ETA
~ >: ls
apps info mnt tmp
bin init proc usr
data
kernel_hello.ko root var
dev lib sbin
etc linuxrc sys
~ >: lsmod
~ >: insmod kernel_hello.ko
kernel_hello: Unknown symbol __aeabi_unwind_cpp_pr0 (err 0)
insmod: can't insert 'kernel_hello.ko': unknown symbol in module or invalid parameter
这个错误是因为
驱动模块与开发板现在正在运行的内核不一致,重新编译升级最新的Linux内核之后重新测试OK!
~ >: insmod kernel_hello.ko
Hello, WuDongXu IoT Studio!
~ >: lsmod
kernel_hello 541 0 - Live 0xbf000000
~ >: rmmod kernel_hello
Goodbye, I have found a good job!