Linux:驱动之最简单的空模块源码分析

  • 驱动之最简单的空模块源码分析

目前尚不是最终版本,还望有心人自己学习的时候,把自己整合的知识点相关的答案也好问题也好,或者实践过程中的一些操作截图,再或者其他的一些想要分享材料发给笔者邮箱:uestc_ganlin@163.com,我们一起完善这篇博客!笔者写这篇博客的时候已经工作第四个年头了,目前是在整理之前有过的学习资料,仅作为笔记,供同志们参考!短时间内可能不会去全部完善。

 

  • 最简单的模块源码

Makefile

# ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
# KERN_VER = $(shell uname -r)
# KERN_DIR = /lib/modules/$(KERN_VER)/build	

# 开发板的linux内核的源码树目录
KERN_DIR = /root/qt/kernel

obj-m	+= module_test.o

all:
	make -C $(KERN_DIR) M=`pwd` modules 
#	arm-none-linux-gnueabi-gcc app.c -o app
	
cp:
	cp *.ko /root/removal/rootfs/root/driver_test
#	cp app /root/removal/rootfs/root/driver_test

.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean

module_test.c

// 为了module_init,module_exit相关的,加入下面头文件
#include <linux/module.h>
// 为了__init,__exit相关的,加入下面头文件
#include <linux/init.h>		

// 模块安装函数
static int __init chrdev_init(void)
{	
	printk(KERN_INFO "chrdev_init helloworld init\n");
	//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("aston");	
// 描述模块的介绍信息			
MODULE_DESCRIPTION("module test");
// 描述模块的别名信息	
MODULE_ALIAS("alias xxx");			
  • 常用的模块操作命令

lsmod (list module,将模块列表显示),功能是打印出当前内核中已经安装的模块列表。

modinfo(module information,模块信息),功能是打印出一个内核模块的自带信息,用法是modinfo xxx.ko;

rmmod(remove module,卸载模块),功能是从当前内核中卸载一个已经安装了的模块,用法是rmmod xxx(注意卸载模块时只需要输入模块名即可,不能加.ko后缀,但有时候好像也可以加);

剩下的后面再说,暂时用不到,如modprobe、depmod等,这里暂时记为待补充。

 

  • 模块的安装

insmod命令与module_init宏:模块源代码中用module_init宏声明了一个函数(在我们这个例子里是chrdev_init函数),作用就是让chrdev_init这个函数和insmod命令绑定起来,也就是说当我们insmod module_test.ko时,insmod命令内部实际执行的操作就是帮我们调用chrdev_init函数。

照此分析,那insmod时就应该能看到chrdev_init中使用printk打印出来的一个chrdev_init字符串,但是实际没看到。原因是输出的信息被拦截了,要怎么才能看到呢?在ubuntu中使用dmesg命令就可以看到了。

模块安装时,insmod内部除了帮我们调用module_init宏所声明的函数外,实际还做了一些别的事(譬如lsmod命令执行后能看到多了一个模块,这也是因为insmod帮我们在内部做了记录),但是我们就不用管了。

 

  • 模块的卸载

rmmod命令与module_exit宏:对应模块的安装相同的理解。

 

  • 模块中常用的宏

MODULE_LICENSE:描述模块的许可证,一般声明为GPL许可证,而且最好不要少,否则可能会出现莫名其妙的错误(譬如一些明显存在的函数会找不到);

MODULE_AUTHOR:描述模块的作者;

MODULE_DESCRIPTION:描述模块的介绍信息;

MODULE_ALIAS:描述模块的别名之类的;

 

  • 模块的版本信息

使用modinfo可以查看模块的版本信息,内核zImage中也有一个确定的版本信息,insmod时模块的版本信息中vermagic必须和内核的相同,否则不能安装,报错信息为:insmod: ERROR: could not insert module module_test.ko: Invalid module format,模块的版本信息是为了保证模块和内核的兼容性,这是是一种安全措施,那么如何保证模块的vermagic和内核的vermagic一致?保证编译模块的内核源码树就是我们正在运行的这个内核在编译时用的内核源码树即可。说白了就是模块和内核要同出一门。

 

  • 函数修饰符

__init,本质上是个宏定义,在内核源代码中就有#define __init xxxx。这个__init的作用就是将被他修饰的函数放入.init.text段中去(本来默认情况下函数是被放入.text段中),整个内核中的所有的这类函数都会被链接器链接放入.init.text段中,所以所有的内核模块中被__init修饰的函数是被统一放在了一起的,内核启动时也会统一加载.init.text段中的这些模块安装函数,加载完后就会把这个段给释放掉以节省内存。

__exit,对应__init,不难理解。

 

  • printk函数详解

printk在内核源码中用来打印信息的函数,用法和我们了解的printf非常相似。

printk和printf最大的差别:printf是C库函数,是在应用层编程中使用的,不能在linux内核源代码中使用;printk是linux内核源代码中自己封装出来的一个打印函数,是内核源码中的一个普通函数,只能在内核源码范围内使用,不能在应用编程中使用。

printk相比printf来说还多了个:打印级别的设置。printk的打印级别是用来控制printk打印的这条信息是否在终端上显示的。应用程序中的调试信息选择性的打开或关闭输出,一般用条件编译来实现(DEBUG宏),但是在内核中,因为内核非常庞大,有时候整体调试内核时发现打印信息非常的多和乱,所以才有了打印级别这个概念。

操作系统的命令行中也有一个打印信息级别属性,值为0-7。当前操作系统中执行printk的时候会去对比printk中的打印级别和我的命令行中设置的打印级别,小于命令行设置级别的信息会被放行打印出来,大于的就被拦截的。譬如我的ubuntu中的打印级别默认是4,那么printk中设置的级别比4小的就能打印出来,比4大的就不能打印出来。

ubuntu中这个printk的打印级别控制没法实践,ubuntu中不管你把级别怎么设置都不能直接打印出来,必须dmesg命令去查看。

 

  • 关于驱动中的头文件

驱动源代码中包含的头文件和原来应用编程程序中包含的头文件不是一回事。应用编程中包含的头文件是应用层的头文件,是应用程序的编译器带来的(譬如gcc的头文件路径在 /usr/include下,这些东西是和操作系统无关的)。驱动源码属于内核源码的一部分,驱动源码中的头文件其实就是内核源代码目录下的include目录下的头文件。

 

  • 驱动编译之Makefile分析

KERN_DIR,变量的值就是我们用来编译这个模块的内核源码树的目录;

obj-m += module_test.o,这一行就表示我们要将module_test.c文件编译成一个模块; 

make -C $(KERN_DIR) M=`pwd` modules,这个命令用来实际编译模块,工作原理就是:利用make -C进入到我们指定的内核源码树目录下,然后在源码目录树下借用内核源码中定义的模块编译规则去编译这个模块,编译完成后把生成的文件还拷贝到当前目录下,完成编译;

make clean ,用来清除编译痕迹;

总结:模块中的的Makefile非常简单,本身并不能完成模块的编译,而是通过make -C进入到内核源码树下借用内核源码的体系来完成模块的编译和链接的。这个Makefile本身是非常模式化的,往往后面两部分是永远不用修改的,只有前两者要修改,就是这里的KERN_DIR和obj-m,分别用来指定你需要的内核源码树和编译后生成的模块文件名。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值