分类: 嵌入式
2015-03-06 09:37:48
Linux设备驱动程序(第三版)—随书笔记
第二章 建立和运行模块(上)
作者:VianoWu
时间:2014.6.25整理
关键词:
1.module_init( ) module_exit( ) insmod
2.控制台优先等级(与代码调试信息) syslog kern.log messages debug
源码:linux 2.6.39 modutils
一、module_init() 和 module_exit()
原书示例:
#include
#include
MODULE_LICENSE(“Dual BSD/GPL”);
Static int hello_init(void)
{
printk(“KERN_ALERT” hello,world\n);
return 0;
}
Static void hello_exit(void)
{
printk(“KERN_ALERT”Goodbye,cruel
world\n);
}
Module_init(hello_init);
Module_exit(hello_exit);
*知识扩展:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1. module_init( ) 与 init_module( )的关系
\linux-2.6.39\include\linux\init.h
第294行
/* Each module must use one module_init(). */
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
由以上源码可知:module_init( )这个宏的参数是作为init_module( )函数的别名。即如果不是采用默认的init_module( )函数声明的初始化函数,可以通过module_init( )这个宏来代替。
module_exit( )与exit_module( )同理。
2.module_init( )的具体实现
(1)若module_init()编译进内核:
\linux-2.6.39\include\linux\Init.h
第258行
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
第212行
#define __initcall(fn) device_initcall(fn)
第207行
#define device_initcall(fn) __define_initcall("6",fn,6) //编译进内核的定义
第188行
/*
* A "pure" initcall has no dependencies on anything else, and purely
* initializes variables that couldn't be statically initialized.
*
* This only exists for built-in code, not for modules.
*/
第167行
/* initcalls are now grouped by functionality into separate
* subsections. Ordering inside the subsections is determined
* by link order.
* For backwards compatibility, initcall() puts the call in
* the device init subsection.
*
* The `id' arg to __define_initcall() is needed so that multiple initcalls
* can point at the same handler without causing duplicate-symbol build errors.
*/
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
结合我之前文章《深入linux设备驱动程序内核机制--随笔--第一章 内核模块(上)》http://blog.chinaunix.net/uid-26975993-id-4296257.html中,“三、EXPORT_SYMBOL的内核实现”的知识扩展部分“1.__CRC_SYMBOL(sym,sec)”
中的“(1)关于“#”stringification”可得:
__define_initcall(6,XXX,6)等价于 __initcallXXX6 __used __attribute__ ((__section__(“.initcall6.init”))) = XXX;
即如果模块编译进入内核中,模块的初始化函数名最终放置在一个“.initcall6.init”节中,由初始化函数调用。
关于linux启动调用模块初始化的整个过程:
Linux\Init\Main.c
第456行asmlinkage void __init start_kernel(void) -> 第624行 rest_init();
第347行static noinline void __init_refok rest_init(void) -> 第357行kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
第776行 static int __init kernel_init(void * unused) -> 第801行 do_basic_setup();
第710行 static void __init do_basic_setup(void) -> 第718行 do_initcalls();
第695行 static void __init do_initcalls(void) -> 第699行 for (fn = __early_initcall_end; fn < __initcall_end; fn++)
Linux-2.6.39\include\asm-generic\vmlinux.lds.h:
第627行
#define INITCALLS \
*(.initcallearly.init) \
VMLINUX_SYMBOL(__early_initcall_end) = .; \
*(.initcall0.init) \
*(.initcall0s.init) \
*(.initcall1.init) \
*(.initcall1s.init) \
*(.initcall2.init) \
*(.initcall2s.init) \
*(.initcall3.init) \
*(.initcall3s.init) \
*(.initcall4.init) \
*(.initcall4s.init) \
*(.initcall5.init) \
*(.initcall5s.init) \
*(.initcallrootfs.init) \
*(.initcall6.init) \
*(.initcall6s.init) \
*(.initcall7.init) \
*(.initcall7s.init)
其中:VMLINUX_SYMBOL 在第56行 #define VMLINUX_SYMBOL(sym)sym
第646行
define INIT_CALLS \
MLINUX_SYMBOL(__initcall_start) = .; \
NITCALLS \
MLINUX_SYMBOL(__initcall_end) = .;
可得:__initcall_start/__initcall_end 都采用了当前地址值。
(2)若模块采用insmod方式:
Modutils :
Insmod的实现:http://blog.csdn.net/wuhui_gdnt/article/details/5316531
3.module_exit( )的实现
\linux-2.6.39\include\linux\Init.h
第268行
/**
* module_exit() - driver exit entry point
* @x: function to be run when driver is removed
*
* module_exit() will wrap the driver clean-up code
* with cleanup_module() when used with rmmod when
* the driver is a module. If the driver is statically
* compiled into the kernel, module_exit() has no effect.
* There can only be one per module.
*/
#define module_exit(x) __exitcall(x);
如果驱动编译进内核,则module_exit( )无作用。否则将由rmmod调用cleanup_module( )函数执行相关动作。
第214行
#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = fn
第300行
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) \
static inline exitcall_t __exittest(void) \
{ return exitfn; } \
void cleanup_module(void) __attribute__((alias(#exitfn)));
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1.printk
printk函数在linux内核中定义并且对模块可用;在insmod加载模块之后,模块被连接到内核并且可存取内核的公用符号。字符串KERN_ALERT是消息的优先级。
*知识扩展:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1.KERN_ALERT 消息优先级
(1)消息优先级
\linux-2.6.39\include\linux\printk.h
第7行
#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 */调试信息
(2)控制台优先等级配置
\linux-2.6.39\include\linux\printk.h
第27行
#define console_loglevel (console_printk[0])
#define default_message_loglevel (console_printk[1])
#define minimum_console_loglevel (console_printk[2])
#define default_console_loglevel (console_printk[3])
\linux-2.6.39\kernel\printk.c
第58行
/* We show everything that is MORE important than this.. */
#define MINIMUM_CONSOLE_LOGLEVEL 1 /* Minimum loglevel we let people use */
#define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */
第64行
int console_printk[4] = {
DEFAULT_CONSOLE_LOGLEVEL, /* console_loglevel */
DEFAULT_MESSAGE_LOGLEVEL, /* default_message_loglevel */
MINIMUM_CONSOLE_LOGLEVEL, /* minimum_console_loglevel */
DEFAULT_CONSOLE_LOGLEVEL, /* default_console_loglevel */
};
在控制台输入以下命令:
#vim /proc/sys/kernel/printk
4 //console_loglevel 控制台日志等级:打印优先等级高于该值的消息至控制台
4 //default_message_loglevel 默认的消息优先级
1 //minimum_console_loglevel 控制台日志等级的最小值(最高优先级)
7 //defaut_console_loglevel 控制台日志的默认等级
在proc/sys/kernel/目录下:
printk_delay : printk消息之间的延迟毫秒数
printk_ratelimit: printk消息之间的最小时间间隔
printgk_ratelimit_burst: 消息数量
(3)代码优化
在调试代码的时候,可以通过以下命令修改消息等级,查看警告信息。
#echo 5 > /proc/sys/kernel/prinitk //将控制台等级调低,可以打印出警告信息。
在 /var/log 目录下的四个文件:Syslog kern.log messages debug可以分别得到相应的输出信息。其中,syslog和kern.log可以查看到所有的打印信息。Messages记录测试代码的4、5、6级别的打印信息,debug记录了测试代码的7级别打印信息。
命令行输入dmesg (一个显示内核缓冲区系统控制信息的工具),打印内核开机信息和测试代码的所有打印信息。
本次测试源码:
Printkk_test.c
16
#include
17
#include
18
#include
19
20 static int printk_level_init(void)
21 {
22 printk("printk level init\n");
23 printk(KERN_EMERG"0 LEVEL\n");
24 printk(KERN_ALERT"1 LEVEL\n");
25 printk(KERN_CRIT"2 LEVEL\n");
26 printk(KERN_ERR"3 LEVEL\n");
27 printk(KERN_WARNING"4 LEVEL\n");
28 printk(KERN_NOTICE"5 LEVEL\n");
29 printk(KERN_INFO"6 LEVEL\n");
30 printk(KERN_DEBUG"7 LEVEL\n");
31 return 0;
32 }
33
34 static void printk_level_exit(void)
35 {
36 printk("printk level exit\n");
37 printk(KERN_EMERG"0 LEVEL\n");
38 printk(KERN_ALERT"1 LEVEL\n");
39 printk(KERN_CRIT"2 LEVEL\n");
40 printk(KERN_ERR"3 LEVEL\n");
41 printk(KERN_WARNING"4 LEVEL\n");
42 printk(KERN_NOTICE"5 LEVEL\n");
43 printk(KERN_INFO"6 LEVEL\n");
44 printk(KERN_DEBUG"7 LEVEL\n");
45 }
46
47 module_init(printk_level_init);
48 module_exit(printk_level_exit);
49
50 MODULE_LICENSE("GPL");
51 MODULE_AUTHOR("VIANOWU");
Makefile:
1ifneq ($(KERNELRELEASE),)
2
3 obj-m := printk_test.o
4
5 else
6
7 #KDIR := /home/mier/05-Linux_Source/Linux_Kernel/linux-2.6.39 //第一路径
8 #KDIR := /usr/src/linux-headers-2.6.32-21 //第二路径
9 KDIR := /lib/modules/2.6.32-21-generic/build //第三路径
10
11 all:
12 make -C $(KDIR) M=$(PWD) modules
13 clean:
14 rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order
15
16 endif
在命令行输入cat /var/log/syslog |tail 和cat /var/log/syslog |tail查看Syslog和kern.log输出所有的打印信息。得到相同的结果,如下所示:
Jun 25 09:41:10 comtop-desktop kernel: [174188.360876] printk level init
Jun 25 09:41:10 comtop-desktop kernel: [174188.360887] 0 LEVEL
Jun 25 09:41:10 comtop-desktop kernel: [174188.360919] 1 LEVEL
Jun 25 09:41:10 comtop-desktop kernel: [174188.360931] 2 LEVEL
Jun 25 09:41:10 comtop-desktop kernel: [174188.360939] 3 LEVEL
Jun 25 09:41:10 comtop-desktop kernel: [174188.360948] 4 LEVEL
Jun 25 09:41:10 comtop-desktop kernel: [174188.360956] 5 LEVEL
Jun 25 09:41:10 comtop-desktop kernel: [174188.360964] 6 LEVEL
Jun 25 09:41:10 comtop-desktop kernel: [174188.360972] 7 LEVEL
在命令行输入cat /var/log/messages |tail查看messages所记录的打印信息为:
Jun 25 09:41:10 comtop-desktop kernel: [174188.360876] printk level init
Jun 25 09:41:10 comtop-desktop kernel: [174188.360948] 4 LEVEL
Jun 25 09:41:10 comtop-desktop kernel: [174188.360956] 5 LEVEL
Jun 25 09:41:10 comtop-desktop kernel: [174188.360964] 6 LEVEL
在命令行输入 cat /var/log/debug |tail 查看debug记录信息:
Jun 25 09:41:10 comtop-desktop kernel: [174188.360972] 7 LEVEL
(4)测试代码编译常见问题分析
(4.1)测试代码环境:
#cat /proc/version
Linux version 2.6.32-21-generic (buildd@rothera) (gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5) ) #32-Ubuntu SMP Fri Apr 16 08:10:02 UTC 2010
(4.2)测试编译源码选择:
KDIR := /home/mier/05-Linux_Source/Linux_Kernel/linux-2.6.39 //第一路径
KDIR := /usr/src/linux-headers-2.6.32-21 //第二路径
KDIR := /lib/modules/2.6.32-21-generic/build //第三路径
(4.3)执行现象:
(4.3.1)路径一:
# make
make -C /home/mier/05-Linux_Source/Linux_Kernel/linux-2.6.39 M=/test/printk_level modules
make[1]: Entering directory `/home/mier/05-Linux_Source/Linux_Kernel/linux-2.6.39'
CC [M] /test/printk_level/printk_test.o
Building modules, stage 2.
MODPOST 1 modules
CC /test/printk_level/printk_test.mod.o
LD [M] /test/printk_level/printk_test.ko
make[1]: Leaving directory `/home/mier/05-Linux_Source/Linux_Kernel/linux-2.6.39
# insmod printk_test.ko
insmod: error inserting 'printk_test.ko': -1 Invalid module format
错误原因分析:
模块版本与内核版本不一致;
#modinfo printk_test.ko
filename: printk_test.ko
author: VIANOWU
license: GPL
depends:
vermagic: 2.6.39 mod_unload 686
(4.3.2)路径二:
#make
make -C /usr/src/linux-headers-2.6.32-21 M=/test/printk_level modules
make[1]: Entering directory `/usr/src/linux-headers-2.6.32-21'
WARNING: Symbol version dump /usr/src/linux-headers-2.6.32-21/Module.symvers
is missing; modules will have no dependencies and modversions.
CC [M] /test/printk_level/printk_test.o
In file included from include/linux/gfp.h:4,
from include/linux/kmod.h:22,
from include/linux/module.h:13,
from /test/printk_level/printk_test.c:17:
include/linux/mmzone.h:18:26: error: linux/bounds.h: No such file or directory
include/linux/mmzone.h:258:5: warning: "MAX_NR_ZONES" is not defined
include/linux/mmzone.h:260:7: warning: "MAX_NR_ZONES" is not defined
include/linux/mmzone.h:262:7: warning: "MAX_NR_ZONES" is not defined
In file included from include/linux/gfp.h:4,
from include/linux/kmod.h:22,
from include/linux/module.h:13,
from /test/printk_level/printk_test.c:17:
include/linux/mmzone.h:300: error: ‘MAX_NR_ZONES’ undeclared here (not in a function)错误
错误原因分析:
缺少/lib/modules/’uname -r’/bulid/中的modules.symvers
(4.3.3)路径三:
# make
make -C /lib/modules/2.6.32-21-generic/build M=/test/printk_level modules
make[1]: Entering directory `/usr/src/linux-headers-2.6.32-21-generic'
CC [M] /test/printk_level/printk_test.o
Building modules, stage 2.
MODPOST 1 modules
CC /test/printk_level/printk_test.mod.o
LD [M] /test/printk_level/printk_test.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.32-21-generic'
#insmod printk_test.ko
# rmmod printk_test.ko
(4.4)几个目录
/boot/ config-2.6.32-21-generic 这个运行系统的内核配置.Config
/lib/modules/2.6.32-21-generic/build 内核模块相关链接libc
(5)关于内核调试办法将独立整理。(包括syslog与syslogd、klogd dmesg /var/log目录下的文件)
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<