目录
5.1、设置bootcmd使开发板通过tftp下载自己建立的内核源码树编译得到的zImage
5.2、设置bootargs使开发板从nfs去挂载rootfs(内核配置记得打开使能nfs形式的rootfs)
7.1、register_chrdev详解(#include )
8.3、开始动手(这些结构体变量和函数我们可以直接在内核源码中cp修改)
我们的目录源码树为:/root/driver/kernel
一.开启驱动开发之路
E:\Linux\3.AppNet\4.process\2.CharDevBasic\4.1
(1)正常运行linux系统的开发板。要求开发板中的linux的zImage必须是自己编译的,不能是别人编译的。
(2)内核源码树,其实就是一个经过了配置编译之后的内核源码。
(3)nfs挂载的rootfs,主机ubuntu中必须搭建一个nfs服务器。
1.3、实践(参考十六.linux开发之Kernel移植——内核的配置和编译原理)
(1)copy原来提供的x210kernel.tar.bz2,找一个干净的目录(/root/driver),解压,并且配置编译。编译完成后得到了:1、内核源码树。2、编译ok的zImage
(参考十六,我们这里直接将事先编译配置好的内核复制到新的/root/driver目录下)
(2)fastboot将第1步中得到的zImage烧录到开发板中去启动(或者将zImage丢到tftp的共享目录,uboot启动时tftp下载启动),将来驱动编译好后,就可以在这个内核中去测试。因为这个zImage和内核源码树是一伙的,所以驱动安装时版本校验不会出错。
#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个 #KERN_VER = $(shell uname -r) #KERN_DIR = /lib/modules/$(KERN_VER)/build #KERN_DIR = /usr/src/linux-headers-3.13.0-32-generic
#开发板的linux内核的源码树目录 KERN_DIR = /root/driver/kernel
obj-m += module_test.o
all: make -C $(KERN_DIR) M=`pwd` modules
#一下指令将*.ko文件拷贝到nfs共享目录下 cp: cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
.PHONY: clean clean: make -C $(KERN_DIR) M=`pwd` modules clean
|
#include <linux/module.h> // module_init module_exit #include <linux/init.h> // __init __exit
// 模块安装函数 static int __init chrdev_init(void) { printk(KERN_INFO "chrdev_init helloworld init\n"); //KERN_INFO:为打印级别 //https://www.cnblogs.com/mylinux/p/4028787.html //printk("<7>" "chrdev_init helloworld init\n"); //printk("<7> chrdev_init helloworld init\n");
return 0; }
// 模块下载函数 static void __exit chrdev_exit(void) { printk(KERN_INFO "chrdev_exit helloworld exit\n"); }
module_init(chrdev_init); module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 MODULE_AUTHOR("aliya"); // 描述模块的作者 MODULE_DESCRIPTION("module one test");//描述模块的介绍信息 MODULE_ALIAS("aliya wwj"); // 描述模块的别名信息 |
二.最简单的模块源码分析1
2.1、常用的模块操作命令
(1)lsmod(listmodule,将模块列表显示),功能是打印出当前内核中已经安装的模块列表 |
(2)insmod(install module,安装模块),功能是向当前内核中去安装一个模块,用法是insmod xxx.ko |
(3)modinfo(module information,模块信息),功能是打印出一个内核模块的自带信息。,用法是modinfo xxx.ko |
(4)rmmod(remove module,卸载模块),功能是从当前内核中卸载一个已经安装了的模块,用法是rmmod xxx(注意卸载模块时只需要输入模块名即可,不能加.ko后缀) |
(1)先lsmod再insmod xxx安装后lsmod查看系统内模块记录。实践测试标明内核会将最新安装的模块放在lsmod显示的最前面。
(2)insmod与module_init宏。模块源代码中用module_init宏声明了一个函数(在我们这个例子里是chrdev_init函数),作用就是指定chrdev_init这个函数和insmod命令绑定起来,也就是说当我们insmod module_test.ko时,insmod命令内部实际执行的操作就是帮我们调用chrdev_init函数。
照此分析,那insmod时就应该能看到c hrdev_init中使用printk打印出来的一个chrdev_init字符串,但是实际没看到。原因是ubuntu中拦截了,要怎么才能看到呢?在ubuntu中使用dmesg命令就可以看到了。
(3)模块安装时insmod内部除了帮我们调用module_init宏所声明的函数外,实际还做了一些别的事(譬如lsmod能看到多了一个模块也是insmod帮我们在内部做了记录),但是我们就不用管了。
(3)insmod时模块的vermagic必须和内核的相同,否则不能安装,报错信息为:insmod: ERROR: could not insert module module_test.ko: Invalid module format
(4)模块的版本信息是为了保证模块和内核的兼容性,是一种安全措施
(5)如何保证模块的vermagic和内核的vermagic一致?当前插入的模块xxx.ko的版本信息(version magic)与正运行的kernel的版本信息一致。说白了就是模块和内核要同出一门。
当出现这种问题时,使用# dmesg | tail 打印信息,找原因
我出现的问题也是这个提示,但原因并不是版本问题,故使用dmesg | tail 也看不出什么。 版本问题可参考解决方法:http://blog.chinaunix.net/uid-20448327-id-172345.html
后来发现是makefile中将开发板的KERN_DIR目录没有屏蔽,导致这个问题。
屏蔽开发板的KERN_DIR后,从新insmod xxx安装,打印如下,使用rmmod xxx卸载后从新make安装即可。
三.最简单的模块源码分析2
3.1、模块卸载
3.2、模块中常用宏
(1)MODULE_LICENSE,模块的许可证。一般声明为GPL许可证,而且最好不要少,否则可能会出现莫名其妙的错误(譬如一些明显存在的函数提升找不到)。
(3)MODULE_DESCRIPTION://描述模块的介绍信息
(1)__init,本质上是个宏定义,在内核源代码中就有#define __init xxxx。
这个__init的作用就是将被他修饰的函数放入.init.text段中去(本来默认情况下函数是被放入.text段中)。
整个内核中的所有的这类 __init 函数都会被链接器链接放入.init.text段中,所以所有的内核模块的__init修饰的函数其实是被统一放在一起的。内核启动时统一会加载.init.text段中的这些模块安装函数,加载完后就会把这个段给释放掉以节省内存。
四.最简单的模块源码分析3
4.1、printk函数详解
(1)printk就是printf的孪生兄弟,一个用于windows,一个用于内核。
(2)printk相比printf来说还多了个:打印级别的设置。printk的打印级别是用来控制printk打印的这条信息是否在终端上显示的。应用程序中的调试信息要么全部打开要么全部关闭,一般用条件编译来实现(DEBUG宏),但是在内核中,因为内核非常庞大,打印信息非常多,有时候整体调试内核时打印信息要么太多找不到想要的,要么一个没有,没法调试。所以才有了打印级别这个概念。
(3)ubuntu中这个printk的打印级别控制没法实践,ubuntu中不管你把级别怎么设置都不能直接打印出来,必须dmesg命令去查看。
4.2、关于驱动模块中的头文件
(1)驱动源代码中包含的头文件和原来应用编程程序中包含的头文件不是一回事。应用编程中包含的头文件是应用层的头文件,是应用程序的编译器带来的(譬如gcc的头文件路径在 /usr/include下,这些东西是和操作系统无关的)。
驱动源码属于内核源码的一部分,驱动源码中的头文件其实就是内核源代码目录下的include目录下的头文件。
4.3、驱动编译的Makefile分析
#开发板的linux内核的源码树目录 KERN_DIR = /root/driver/kernel
#这一行就表示我们要将module_test.c文件编译成一个模块 obj-m += module_test.o
#这个命令用来编译实际的模块, make 执行的命令, `pwd`表示我们要把 pwd 当命令去执行, M=`pwd`表示我们 进入到当前这个目录下编译完成后还能回来( 记录当前目录) 。 modules 表示内核中的目标, 然后内核中的目标负责对 我们的程序进行编译 all: make -C $(KERN_DIR) M=`pwd` modules arm-linux-gcc app.c -o app
#一下指令将*.ko文件拷贝到nfs共享目录下 cp: cp *.ko /root/porting_x210/rootfs/rootfs/driver_test cp app /root/porting_x210/rootfs/rootfs/driver_test
.PHONY: clean clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf app |
(1)KERN_DIR,变量的值就是我们用来编译这个模块的内核源码树的目录
(2)obj-m += module_test.o,这一行就表示我们要将module_test.c文件编译成一个模块
(3)make -C $(KERN_DIR) M=`pwd` modules 这个命令用来实际编译模块,工作原理就是:利用make -C进入到我们指定的内核源码树目录下,然后在源码目录树下借用内核源码中定义的模块编译规则去编译这个模块,编译完成后把生成的文件还拷贝到当前目录下,完成编译。
总结:模块的makefile非常简单,本身并不能完成模块的编译,而是通过make -C进入到内核源码树下借用内核源码的体系来完成模块的编译链接的。这个Makefile本身是非常模式化的,3和4部分是永远不用动的,只有1和2需要动。1是内核源码树的目录,必须根据自己的编译环境输入对应目录!
五.用开发板来调试模块
E:\Linux\3.AppNet\4.process\2.CharDevBasic\4.1
5.1、设置bootcmd使开发板通过tftp下载自己建立的内核源码树编译得到的zImage
设置好TFTP下载方式及IP地址配对等,详细参考uboot笔记第二节
set bootcmd 'tftp 0x30008000 zImage;bootm 0x30008000'
5.2、设置bootargs使开发板从nfs去挂载rootfs(内核配置记得打开使能nfs形式的rootfs)
setenv bootargs root=/dev/nfs nfsroot=192.168.1.141:/root/porting_x210/rootfs/rootfs ip=192.168.1.10:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC2,115200 |
使用tftp烧录zImage后,显示如下,原因是在menuconfig没有开启对NFS的启动支持
之后从新make,再次重启开发板即可,打印出如下信息,且进入终端控制台。
上面图片中有一句话 Rreeint init memory:172K 这句话就是系统启动完成后, 释放掉了__init 指定的段。