linux内核编程(1)—内核模块helloworld
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
static int hello_init(void)
{
printk("Hello! This is the helloworld module!\n");
return 0;
}
static void hello_exit(void)
{
printk("Module exit! Bye Bye!\n");
return;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
1.1这几段代码就是一个模块函数代码了,其中有几个需要注意:
a) static是为了防止函数命名污染。
b) 在内核中的打印函数是printk,其中<0>表示该信息的等级,数字越小,级别越高。
c) 用module_init和module_exit宏来指定入口函数。
当在命令行安装该模块时,会自动调用hello_init这个函数,当卸载该函数时,对自动调用hello_exit函数。
2、 模块可选信息
模块有一些用来表示相关信息的宏:
MODULE_LICENSE 用来告诉内核遵循什么协议 GPL GPLv2等
MODULE_AUTHOR 作者
MODULE_DESCRIPTION 描述
MODULE_VERSION 版本
MODULE_ALIAS 别名
2、两种Makefile(2.6的内核)
第一种Makefile(推荐):
obj-m := helloworld.o
KERNELBUILD := /lib/modules/`uname -r`/build
all:
make -C $(KERNELBUILD) M=$(shell pwd) modules
clean:
rm -rf *.o *.ko *.mod.c .tmp_versions modules* Module*
第二种Makefile:
obj-m := helloworld.o
clean:
rm -rf *.o *.ko *.mod.c .tmp_versions modules* Module*
#make –C /lib/modules/`uname -r`/build M=$(shell pwd) modules
备注:2.6内核的kbuild子系统与2.4相比有本质的改变
其中:
a) KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量。
ifneq($(KERNELRELEASE),) 判断该变量是否为空。
在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,
所以make将读取执行else之后的内容。
b) KDIR := /lib/modules/2.6.35.6-45.fc14.i686/build 是给KDIR这个变量赋值,值为当前linux运行的内核源码。红色标记的只是我的Linux下的,可以直接
KDIR := /lib/modules/ $(shell uname -r) /build 这样写。
c) 当make的目标为all时,-C $(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD)表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。
d) 我们可以把上述的Makefile文件作为一个模板,只需要改动obj-m := hello.o这条语句就可以了:obj-m=XXX.o。
3、编译
#make
#insmod hello.ko
备注:屏幕上没有输出,是由console的日志记录级别和printk指定的级别决定的。可以用dmesg命令,看到输出。
#dmesg|tail
4、知识拓展(1)
C程序员都知道,要使用某个外部的函数,应当#include某个头文件,这个头文件包含了那个函数的原型。内核的头文件在/usr/src/linux/include/下,其中/usr/src/linux/include/asm是个符号链接,指向所用内核的具体体系结构目录,比方说我的系统是i386的,那么include/asm就指向include/asm-i386 。
内核编程不能链接libc库,即不能使用libc库中的函数,所以很麻烦。一些重要的函数如strcpy/strcmp/snprintf等,kernel为我们实现并导出(export)了,而我们需要#include相关的头文件,在/usr/src/linux/include/linux和/usr/src/linux/include/asm中,你需要自己寻找你所要使用的函数在哪个头文件中声明,并将其#include进来。
5、知识拓展(2)
printk可以当作printf函数来使用。其中一个不同点是,printk允许你按照相关的记录级或优先级将消息严格分类。通常你需要一个宏来指定记录等级。记录级宏的作用是扩展为一个字符串,这个字符串会在编译期间与相应的消息文本相连接,因此宏和字符串之间不需要逗号连接。如:printk(KERN_CRIT "I'm trashed; giving up on %p\n", ptr); 在头文件<linux/kernel.h>中共定义了八个可用的记录级,按其严重性倒序排列为:
1)KERN_EMERG:Used for emergency messages, usually those that precede a crash.
用于突发性事件的消息,通常在系统崩溃之前报告此类消息。
2)KERN_ALERT:A situation requiring immediate action.
在需要立即操作的情况下使用此消息。
3)KERN_CRIT:Critical conditions, often related to serious hardware or software .
用于临界条件下,通常遇到严重的硬软件错误时使用此消息。
4)KERN_ERR:Used to report error conditions; device drivers often use KERN_ERR to report hardware difficulties.
用于报告错误条件,设备驱动经常使用KERN_ERR报告硬件难题。
5)KERN_WARNING:Warnings about problematic situations that do not, in themselves, create serious problems with the system.
是关于问题状况的警告,一般这些状况不会引起系统的严重问题。
6)KERN_NOTICE :Situations that are normal, but still worthy of note. A number of security-related conditions are reported at this level.
该级别较为普通,但仍然值得注意,许多与安全性相关的情况会在这个级别被报告。
7)KERN_INFO:Informational messages. Many drivers print information about the hardware they find at startup time at this level.
信息消息,许多驱动程序在启动时刻用它来输出获得的硬件信息。
8)KERN_DEBUG :Used for debugging messages
用于调试。