标签:
实验环境:
? ?
先没有操作看完了这章,因为涉及了好多额外内容,所以信息量还是比较大,所以先整体看一遍,第二遍则写代码再看一遍。
? ?
编写DEMO代码:
Hello World模块,随便建个目录ldd3-trainning,然后写个程序hello.c:
因为内核运行时是不依赖C库的,所以这里用了printk()
printk:处于内核的公用符号表中,待会到内核符号表那节再说吧
消息优先级:具有默认优先级的消息可能不会输出在控制台上,所以要显示地指定高优先级KERN_ALERT
static:因为这种函数在特定文件之外没有其他意义。因如果一个模块函数实在要对其他内核部分可见,则必须显式被导出。
? ?
编译模块:
如果想了解内核是如何构建的,阅读Documentation/kbuild目录下的文件。
----------------下面编写Makefile,一个Makefile可以管理多个实验代码,只要往里面添加就可以了
这里我拷贝随书代码的Makefile来解释:
? ?
obj-m表示 有一个模块hello.ko要从hello.o来构建,如果hello.o只由hello.c来生成则不需要写其他东西了,如果hello.o也是由file1.o file2.o生成,则要再写行:
module-objs := file1.o file2.o
? ?
好了,编译一下:
报错了,缺少个void
改正后继续编译
还是没有生成模块目标文件.ko,看错误信息,它的意思是没有hellop.c然后终止了,把makefile文件里的删了看看:
再次编译:
好了,成功生成!在构造模块之后,就是将模块装入内核。先运行一下在分析:
首先切换到超级用户下:
然后我们加载一下:
为什么没有显示hello world,根据这个答案它说控制台console和终端terminal是有区别的,代码里通过提高消息优先级为KERN_ALEART让消息输出到console上,但是当前系统console的信息不会输出到terminal上,所以
Edit /proc/sys/kernel/printk to set up the lowest priority to print in console。
书上说到调试技术那节会详细讲,这里就不要深究打印了,内核消息的传递机制将在第4章中详细讨论。用dmesg来查看console信息:
然后卸载模块rmmod hello:
或者:
可见,编写模块并不困难,难点在于理解设备并最大化其性能。
? ?
insmode:
modprobe: 也用来将模块装到内核中
lsmod:
? ?
核心模块和应用程序的对比:
内核:内核和C库文件是两回事,C库是供应用程序使用的。和内核相关的任何内容都在我们安装并配置好的内核源码树的头文件中声明,大多数内核相关头文件保存在include/linux和include/asm目录中:
上面其他子目录中保存的是和特定内核子系统相关的头文件。
核心模块和应用程序的差别:
而模块却是和内核说:"我在这儿,我初始化好了,我能做这些工作。"
但是模块的退出函数必须仔细撤销初始化函数所做的一切,否则,在系统重新引导之前某些东东就会残留在系统里;
而在一个内核错误即使不影响整个系统,也至少会杀死当前进程。(什么意思?不懂)
? ?
当前进程: 内核执行的大多数操作还是和某个特定的进程相关。 获得当前进程:访问定义在 include/asm-i386/current.h 中的全局项current
Jump To Definition可以看到task_struct定义在linux/sched.h,可以在window菜单下看到路径:
current指针指向当前正在运行的进程。在open等系统调用执行过程中,当前进程指的是调用这些系统调用的进程。我们来查看当前进程所执行文件的基本名称和PID看看,还在hello.c里面添加:
编译运行:
查看控制台:
看到了当前进程所执行的程序文件的基本名称base name: |
? ?
来看下如何在模块中使用函数指针和函数调用的,从而为运行中的内核增加新的功能。
我的理解是通过赋值内核的函数指针,从而把我们实现的函数体让内核通过函数指针实现函数调用:下图使用visio绘制,书上也有
? ?
内核符号表(cat /proc/kallsyms | grep hello):
公共内核符号表(所有的全局内核项目:函数和变量的地址):insmod使用公共内核符号表来解析模块中未定义的符号。
模块层叠技术:
模块符号的可见性:模块符号需要显式导出EXPORT SYMBOL(hello_symbol);
实验验证:
编译出错:
错误说元素错了,修改下:
编译还是报错:
难道符号只能是函数名或者变量名吗?于是我改成了:
编译后,依然报相同的错误,那我改成:
编译出错:
没辙了,不能瞎猜了,到stackoverflow上搜索EXPORT_SYMBOL,搜到一个问题:那我自己定义一个函数,然后导出看看:
编译出错提示:
好像说fun没有声明,好吧,把fun定义放到导出之前:
这次终于编译通过了:
看来只能导出我自己定义的函数啊!还必须在导出之前声明。等等,我加个static看看:
还是可以通过,看来是模块初始化函数和模块退出函数不可以导出啊!不对,我把导出放到后面试试:
好吧,成功了!初始化函数和模块退出函数也可以导出没有区别!不知道为什么文件里的全局变量不可以导出为符号?
我们可以在其他模块中声明外部函数:extern int fun(void); 来为调用它做准备。
加载模块,查看内核公共符号表:
把全局变量hello_symbol设置为static在编译了看看:
好了这次hello_symbol还是有的啊,不过这次注意到前面那个符号d是小写,凡是显式导出的就是大写,不显式导出的就是小写,总之都存在,我们再导出这个全局变量看看:
编译加载后看看:
看到了,d变成大写的D了,确实啊!
对了,书上说符号EXPORT_SYMBOL(hello_symbol);会被扩展为一个特殊变量的声明,而该变量将在模块可执行文件的特殊部分(一个"ELF段")中保存,在装载时,内核通过这个段来寻找模块导出的变量:readelf -a hello.ko | grep hello_symbol
然后取消导出看看:
看到确实被扩展成了一个特殊变量 __crc__hello_symbol,其他几个我就不认识了。
? ?
【预备知识】
内核环境
内核代码
头文件:
include/linux/module.h
include/linux/init.h
? ?
以下声明习惯放在源文件的最后:
MODULE_LICENSE("GPL");
MODULE_AUTHOR();
MODULE_DESCRIPTION();
MODULE_VERSION();
MODULE_ALIAS(); ch11
MODULE_DEVICE_TABLE(); ch12
? ?
【初始化和关闭】
初始化函数
__init
软件抽象:也是可注册的设备
模块装载器
设备类型
设施:一个设备的多个功能
注册:注册多个设施完成一个设备的注册
内核符号表
注册模块提供的设施
【清除函数】
【模块装载竞争】
【模块参数】
? ?
ldd3-2 构造和运行模块:Hello World模块笔记
标签:
原文地址:http://www.cnblogs.com/dcscodelife/p/5827785.html