分类: LINUX
2008-05-03 14:00:48
|
|
[root@HUNK kernel]# insmod ddc.ko howmany=3 whom="hunk"
[root@HUNK kernel]#
Message from at Wed May 7 18:15:19 2008 ...
HUNK kernel: (0) Hello, hunk !
Message from at Wed May 7 18:15:19 2008 ...
HUNK kernel: (1) Hello, hunk !
Message from at Wed May 7 18:15:19 2008 ...
HUNK kernel: (2) Hello, hunk !
[root@HUNK kernel]# rmmod ddc
[root@HUNK kernel]#
Message from at Wed May 7 18:15:27 2008 ...
HUNK kernel: Goodbye,I Love Linux !
但实际上,遇到了好多问题:
1、我的系统是 2.4 升级到 2.6 的,编译后加载出现insmod:error inserting hello.o:-1 invalid module format...
原因参考下文:
你的模块代码一定要为每个它要连接的内核版本重新编译 -- 至少, 在缺乏 modversions 时, 这里不涉及因为它们更多的是给内核发布制作者, 而不是开发者. 模块是紧密结合到一个特殊内核版本的数据结构和函数原型上的; 模块见到的接口可能一个内核版本与另一个有很大差别. 当然, 在开发中的内核更加是这样.
内核不只是认为一个给定模块是针对一个正确的内核版本建立的. 建立过程的其中一步是对一个当前内核树中的文件(称为 vermagic.o)连接你的模块; 这个东东含有相当多的有关要为其建立模块的内核的信息, 包括目标内核版本, 编译器版本, 以及许多重要配置变量的设置. 当尝试加载一个模块, 这些信息被检查与运行内核的兼容性. 如果不匹配, 模块不会加载; 代之的是你见到如下内容:
# insmod hello.ko Error inserting './hello.ko': -1 Invalid module format
看一下系统日志文件(/var/log/message 或者任何你的系统被配置来用的)将发现导致模块无法加载特定的问题.
如果你需要编译一个模块给一个特定的内核版本, 你将需要使用这个特定版本的建立系统和源码树.
内核接口在各个发行之间常常变化. 如果你编写一个模块想用来在多个内核版本上工作(特别地是如果它必须跨大的发行版本), 你可能只能使用宏定义和 #ifdef 来使你的代码正确建立. 本书的这个版本只关心内核的一个主要版本, 因此不会在我们的例子代码中经常见到版本检查. 但是这种需要确实有时会有. 在这样情况下, 你要利用在 linux/version.h 中发现的定义. 这个头文件, 自动包含在 linux/module.h, 定义了下面的宏定义:
这个宏定义扩展成字符串, 描述了这个内核树的版本. 例如, "2.6.10".
这个宏定义扩展成内核版本的二进制形式, 版本号发行号的每个部分用一个字节表示. 例如, 2.6.10 的编码是 132618 ( 就是, 0x02060a ). []有了这个信息, 你可以(几乎是)容易地决定你在处理的内核版本.
这个宏定义用来建立一个整型版本编码, 从组成一个版本号的单个数字. 例如, KERNEL_VERSION(2.6.10) 扩展成 132618. 这个宏定义非常有用, 当你需要比较当前版本和一个已知的检查点.
大部分的基于内核版本的依赖性可以使用预处理器条件解决, 通过利用 KERNEL_VERSION 和 LINUX_VERSION_VODE. 版本依赖不应当, 但是, 用繁多的 #ifdef 条件来搞乱驱动的代码; 处理不兼容的最好的方式是把它们限制到特定的头文件. 作为一个通用的原则, 明显版本(或者平台)依赖的代码应当隐藏在一个低级的宏定义或者函数后面. 高层的代码就可以只调用这些函数, 而不必关心低层的细节. 这样书写的代码易读并且更健壮.
2、./hello:kernel –module version mismatch
hello.o was compiled for kernel version 2.4.20
while this kernel is version 2.4.20-8
原因:模块和内核版本不匹配,即编译内核的编译器与现在编译模块的编译器版本不一致。
解决方法:1> 将/usr/include/linux/version.h 文件中的#define… “2.4.20”修改成#define… “2.4.20-8”(),再重新编译!
3、出现Warning: loading hello-3.o will taint the kernel: no license
把代码的认证设置成GPL就可以了。这个认证机制都是在linux/module.h里面定义的。参考一下下面的代码就可以了。
/* You can use strings, like this:
*/
MODULE_LICENSE("GPL"); // Get rid of taint message by declaring code as GPL.
/* Or with defines, like this:
*/
4、输出信息不能显示在控制台,而是直接将把内核消息追加到/val/log/messages 中
最简单的方法是把printk( “<1> Hello, World!\n”) ; 里面的 <1> 改成 <0>,原因参考下文:
printk
我们在前面几章中使用 printk 函数, 简单地假设它如同 printf 一样使用. 现在到时候介绍一些不同的地方了.
一个不同是 printk 允许你根据消息的严重程度对其分类, 通过附加不同的记录级别或者优先级在消息上. 你常常用一个宏定义来指示记录级别. 例如, KERN_INFO, 我们之前曾在一些打印语句的前面看到过, 是消息记录级别的一种可能值. 记录宏定义扩展成一个字串, 在编译时与消息文本连接在一起; 这就是为什么下面的在优先级和格式串之间没有逗号的原因. 这里有 2 个 printk 命令的例子, 一个调试消息, 一个紧急消息:
printk(KERN_DEBUG "Here I am: %s:%i\n", __FILE__, __LINE__); printk(KERN_CRIT "I'm trashed; giving up on %p\n", ptr);
有 8 种可能的记录字串, 在头文件
有问题的情况的警告, 这些情况自己不会引起系统的严重问题.
正常情况, 但是仍然值得注意. 在这个级别一些安全相关的情况会报告.
信息型消息. 在这个级别, 很多驱动在启动时打印它们发现的硬件的信息.
用作调试消息.
每个字串( 在宏定义扩展里 )代表一个在角括号中的整数. 整数的范围从 0 到 7, 越小的数表示越大的优先级.
一条没有指定优先级的 printk 语句缺省是 DEFAULT_MESSAGE_LOGLEVEL, 在 kernel/printk.c 里指定作为一个整数. 在 2.6.10 内核中, DEFAULT_MESSAGE_LOGLEVEL 是 KERN_WARNING, 但是在过去已知是改变的.
基于记录级别, 内核可能打印消息到当前控制台, 可能是一个文本模式终端, 串口, 或者是一台并口打印机. 如果优先级小于整型值 console_loglevel, 消息被递交给控制台, 一次一行( 除非提供一个新行结尾, 否则什么都不发送 ). 如果 klogd 和 syslogd 都在系统中运行, 内核消息被追加到 /var/log/messages (或者另外根据你的 syslogd 配置处理), 独立于 console_loglevel. 如果 klogd 没有运行, 你只有读 /proc/kmsg ( 用 dmsg 命令最易做到 )将消息取到用户空间. 当使用 klogd 时, 你应当记住, 它不会保存连续的同样的行; 它只保留第一个这样的行, 随后是, 它收到的重复行数.
变量 console_loglevel 初始化成 DEFAULT_CONSOLE_LOGLEVEL, 并且可通过 sys_syslog 系统调用修改. 一种修改它的方法是在调用 klogd 时指定 -c 开关, 在 klogd 的 manpage 里有指定. 注意要改变当前值, 你必须先杀掉 klogd, 接着使用 -c 选项重启它. 另外, 你可写一个程序来改变控制台记录级别. 你会发现这样一个程序的版本在由 O' Reilly 提供的 FTP 站点上的 miscprogs/setlevel.c. 新的级别指定未一个整数, 在 1 和 8 之前, 包含 1 和 8. 如果它设为 1, 只有 0 级消息( KERN_EMERG )到达控制台; /proc/sys/kernel/printk
现在应当清楚了为什么 hello.c 例子使用 KERN_ALERT 标志; 它们是要确保消息会出现在控制台上.
总结了我们在本章接触到的内核函数, 变量, 宏定义, 和 /proc 文件. 它的用意是作为一个参考. 每一项列都在相关头文件的后面, 如果有. 从这里开始, 在几乎每章的结尾会有类似一节, 总结一章中介绍的新符号. 本节中的项通常以在本章中出现的顺序排列:
初始化函数应当声明成静态的, 因为它们不会在特定文件之外可见; 没有硬性规定这个, 然而, 因为没有函数能输出给内核其他部分, 除非明确请求. 声明中的 __init 标志可能看起来有点怪; 它是一个给内核的暗示, 给定的函数只是在初始化使用. 模块加载者在模块加载后会丢掉这个初始化函数, 使它的内存可做其他用途. 一个类似的标签 (__initdata) 给只在初始化时用的数据. 使用 __init 和 __initdata 是可选的, 但是它带来的麻烦是值得的. 只是要确认不要用在那些在初始化完成后还使用的函数(或者数据结构)上. 你可能还会遇到 __devinit 和 __devinitdata 在内核源码里; 这些只在内核没有配置支持 hotplug 设备时转换成 __init 和 _initdata. 我们会在 14 章谈论 hotplug 支持.
使用 moudle_init 是强制的. 这个宏定义增加了特别的段到模块目标代码中, 表明在哪里找到模块的初始化函数. 没有这个定义, 你的初始化函数不会被调用.
模块可以注册许多的不同设施, 包括不同类型的设备, 文件系统, 加密转换, 以及更多. 对每一个设施, 有一个特定的内核函数来完成这个注册. 传给内核注册函数的参数常常是一些数据结构的指针, 描述新设施以及要注册的新设施的名子. 数据结构常常包含模块函数指针, 模块中的函数就是这样被调用的.
用户空间工具, 加载模块到运行中的内核以及去除它们.
指定模块的初始化和清理函数的宏定义.
函数( __init 和 __exit )和数据 (__initdata 和 __exitdata)的标记, 只用在模块初始化或者清理时间. 为初始化所标识的项可能会在初始化完成后丢弃; 退出的项可能被丢弃如果内核没有配置模块卸载. 这些标记通过使相关的目标在可执行文件的特定的 ELF 节里被替换来工作.
最重要的头文件中的一个. 这个文件包含很多驱动使用的内核 API 的定义, 包括睡眠函数和许多变量声明.
当前进程.
进程 ID 和 当前进程的命令名.
一个 makefile 符号, 内核建立系统用来决定当前目录下的哪个模块应当被建立.
/sys/module 是一个 sysfs 目录层次, 包含当前加载模块的信息. /proc/moudles 是旧式的, 那种信息的单个文件版本. 其中的条目包含了模块名, 每个模块占用的内存数量, 以及使用计数. 另外的字串追加到每行的末尾来指定标志, 对这个模块当前是活动的.
来自内核源码目录的目标文件, 描述一个模块为之建立的环境.
必需的头文件. 它必须在一个模块源码中包含.
头文件, 包含在建立的内核版本信息.
整型宏定义, 对 #ifdef 版本依赖有用.
宏定义, 用来输出一个符号给内核. 第 2 种形式输出没有版本信息, 第 3 种限制输出给 GPL 许可的模块.
放置文档在目标文件的模块中.
宏定义, 声明一个模块的初始化和清理函数.
宏定义, 创建模块参数, 可以被用户在模块加载时调整( 或者在启动时间, 对于内嵌代码). 类型可以是 bool, charp, int, invbool, short, ushort, uint, ulong, 或者 intarray.
内核代码的 printf 类似物.