内核编程入门,就以最为简单的hello.c为例。
环境:Redhat 9.0,内核版本2.4.20-8。
虽然现在2.6.x的内核很早就就发布了,但是毕竟很多公司还在使用2.4.x的内核。作为新手,从2.4.x的内核入手是可行的。原因有如下几条:
(1)2.4.x比较成熟。可能你遇到的绝大多数问题,网上都有解决方案。在这个过程中,你可以节省大量的时间,同时还可以对比网上的解决方案,加深认识,总结解决问题的方法,调整自己的学习方法和思路。
(2)事物的发展总不可能是一蹴而就的。了解发展的历程,对深入理解问题有很大的好处。所以在2.4.x的内核的基础上学习2.6.x的内核,就能够体会到2.6.x的内核在哪些方面要出色,或者为什么要采取这种改进技术。相信理论清晰了,即时2.6.x的内核也会容易上手。
下面总结了第一个内核程序hello.c的学习过程。
(一)第一阶段:尽量简单
/*
* hello.c
*/
执行,出现错误一:
#
#
hello.o: 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 UTS_RELEASE "2.4.20 "
#define LINUX_VERSION_CODE 132116
#define KERNEL_VERSION(a,b,c) (((a) < < 16) + ((b) < < 8) + (c))
(2)内核源代码版本/usr/src/linux-2.4.20-8/include/linux/version.h
/usr/src/linux-2.4.20-8/include/linux
# cat version.h
#include <linux/rhconfig.h>
#if defined(__module__smp)
#define UTS_RELEASE "2.4.20-8smp "
#elif defined(__module__BOOT)
#define UTS_RELEASE "2.4.20-8BOOT "
#elif defined(__module__bigmem)
#define UTS_RELEASE "2.4.20-8bigmem "
#else
#define UTS_RELEASE "2.4.20-8 "
#endif
#define LINUX_VERSION_CODE 132116
#define KERNEL_VERSION(a,b,c) (((a) < < 16) + ((b) < < 8) + (c))
可以采取修改编译器版本号与内核源代码版本号一致的办法来解决这个问题,即修改/usr/include/linux/version.h中 #define UTS_RELEASE "2.4.20 "
为 #define UTS_RELEASE "2.4.20-8 "
执行,出现错误二:
# gcc -c hello.c
# insmod hello.o
Warning: loading hello.o will taint the kernel: no license
See http://www.tux.org/lkml/#export-tainted for information about tainted modules
Module hello loaded, with warnings
# tail -n 1 /var/log/messages
Jan 30 12:02:08 lqm kernel: Hello
也就是说出现了no license的警告。GNU的软件需要有GPL,所以修改源代码如下:
/*
* hello.c
*/
#define MODULE
#include <linux/module.h>
MODULE_LICENSE( "GPL ");
int init_module(void)
{
printk( "Hello World!\n ");
return 0;
}
void cleanup_module(void)
{
printk( "Goodbye!\n ");
}
这时没有错误了。写了一个脚本,测试流程自动化:
执行结果如下:
#
Instal module - hello.o
Jan 30 13:31:29 lqm kernel: Hello World!
hello 748 0 (unused)
Module hello has instaled
Remove module - hello
Jan 30 13:31:30 lqm kernel: Goodbye!
Module hello has removed
(二)第二阶段:完善,深入一点
/*
* hello.c
*/
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux/kernel.h> /*printk*/
#include <linux/module.h>
MODULE_LICENSE( "GPL ");
static int init_module(void)
{
printk( "Hello, world!\n ");
return 0;
}
static void cleanup_module(void)
{
printk( "Goodbye!\n ");
}
写Makefile文件如下:
修改执行脚本run:
#!/bin/bash
# The first step
make && make install && echo -e "Instal module - hello.o\n "
sleep 1
tail -n 1 /var/log/messages
lsmod | grep "hello " && echo -e "Module hello has instaled\n "
# The second step
make uninstall && echo -e "Remove module - hello\n "
sleep 1
tail -n 1 /var/log/messages
lsmod | grep "hello " || echo "Module hello has removed "
# The last step
make clean
执行结果如下:
# ./run
gcc -D__KERNEL__ -DMODULE -O -Wall -I/usr/src/linux-2.4.20-8/include/ -c hello.c
hello.c:18: warning: `init_module ' defined but not used
hello.c:25: warning: `cleanup_module ' defined but not used
insmod hello.o
Instal module - hello.o
Jan 31 13:40:23 lqm kernel: Hello,
hello 728 0 (unused)
Module hello has instaled
rmmod hello
Remove module - hello
Jan 31 13:40:24 lqm kernel:
Module hello has removed
rm -f *.o
(三)第三阶段:总结
1、一个内核模块至少应该包括两个函数:
(1)init_module:模块插入内核时调用
(2)cleanup_module:模块移除时调用
这个简单的程序就是只实现了这两个函数,而且只做了打印信息的工作,没有使用价值。典型情况下,init_module为内核中的某些东西注册一个句柄,相当于模块初始化的工作。cleanup_module则是撤销模块前期的处理工作,使模块得以安全卸载。
2、insmod实现动态加载模块。在当前OS上,动态加载模块以测试硬件等,避免了繁琐的工作。但是,在这种情况下,会出现版本不匹配的情况。另外,要分清楚内核源代码路径和编译器路径的不同,知道在编译时该指定那个路径。第二阶段开始出现过几个错误都是因为默认的路径是编译器路径,而不是内核源代码路径。体会内核模块化带来的好处!
3、应用Make工具来管理项目。即使小,也要训练。在2.4内核和2.6内核下,Makefile的编写会有所不同。只是语法形式的不同,先深入掌握一种,另一种注意一下应该可以避免犯错误。
环境:Redhat 9.0,内核版本2.4.20-8。
虽然现在2.6.x的内核很早就就发布了,但是毕竟很多公司还在使用2.4.x的内核。作为新手,从2.4.x的内核入手是可行的。原因有如下几条:
(1)2.4.x比较成熟。可能你遇到的绝大多数问题,网上都有解决方案。在这个过程中,你可以节省大量的时间,同时还可以对比网上的解决方案,加深认识,总结解决问题的方法,调整自己的学习方法和思路。
(2)事物的发展总不可能是一蹴而就的。了解发展的历程,对深入理解问题有很大的好处。所以在2.4.x的内核的基础上学习2.6.x的内核,就能够体会到2.6.x的内核在哪些方面要出色,或者为什么要采取这种改进技术。相信理论清晰了,即时2.6.x的内核也会容易上手。
下面总结了第一个内核程序hello.c的学习过程。
(一)第一阶段:尽量简单
/*
* hello.c
*/
#define MODULE
#include <linux/module.h>
int init_module(void)
{
printk( "Hello World!\n ");
return 0;
}
void cleanup_module(void)
{
printk( "Goodbye!\n ");
}
执行,出现错误一:
#
gcc -c hello.c
#
insmod hello.o
hello.o: 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 UTS_RELEASE "2.4.20 "
#define LINUX_VERSION_CODE 132116
#define KERNEL_VERSION(a,b,c) (((a) < < 16) + ((b) < < 8) + (c))
(2)内核源代码版本/usr/src/linux-2.4.20-8/include/linux/version.h
/usr/src/linux-2.4.20-8/include/linux
# cat version.h
#include <linux/rhconfig.h>
#if defined(__module__smp)
#define UTS_RELEASE "2.4.20-8smp "
#elif defined(__module__BOOT)
#define UTS_RELEASE "2.4.20-8BOOT "
#elif defined(__module__bigmem)
#define UTS_RELEASE "2.4.20-8bigmem "
#else
#define UTS_RELEASE "2.4.20-8 "
#endif
#define LINUX_VERSION_CODE 132116
#define KERNEL_VERSION(a,b,c) (((a) < < 16) + ((b) < < 8) + (c))
可以采取修改编译器版本号与内核源代码版本号一致的办法来解决这个问题,即修改/usr/include/linux/version.h中 #define UTS_RELEASE "2.4.20 "
为 #define UTS_RELEASE "2.4.20-8 "
执行,出现错误二:
# gcc -c hello.c
# insmod hello.o
Warning: loading hello.o will taint the kernel: no license
See http://www.tux.org/lkml/#export-tainted for information about tainted modules
Module hello loaded, with warnings
# tail -n 1 /var/log/messages
Jan 30 12:02:08 lqm kernel: Hello
也就是说出现了no license的警告。GNU的软件需要有GPL,所以修改源代码如下:
/*
* hello.c
*/
#define MODULE
#include <linux/module.h>
MODULE_LICENSE( "GPL ");
int init_module(void)
{
printk( "Hello World!\n ");
return 0;
}
void cleanup_module(void)
{
printk( "Goodbye!\n ");
}
这时没有错误了。写了一个脚本,测试流程自动化:
#!/bin/bash
gcc -c hello.c
sleep 1
insmod hello.o && echo -e "Instal module - hello.o\n "
sleep 1
tail -n 1 /var/log/messages
lsmod | grep "hello " && echo -e "Module hello has instaled\n "
rmmod hello && echo -e "Remove module - hello\n "
sleep 1
tail -n 1 /var/log/messages
lsmod | grep "hello " || echo "Module hello has removed "
执行结果如下:
#
./run
Instal module - hello.o
Jan 30 13:31:29 lqm kernel: Hello World!
hello 748 0 (unused)
Module hello has instaled
Remove module - hello
Jan 30 13:31:30 lqm kernel: Goodbye!
Module hello has removed
(二)第二阶段:完善,深入一点
/*
* hello.c
*/
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux/kernel.h> /*printk*/
#include <linux/module.h>
MODULE_LICENSE( "GPL ");
static int init_module(void)
{
printk( "Hello, world!\n ");
return 0;
}
static void cleanup_module(void)
{
printk( "Goodbye!\n ");
}
写Makefile文件如下:
# Kernel Programming
# Shanghai University, lhzeng
# The path of kernel source code
INCLUDEDIR = /usr/src/linux-2.4.20-8/include/
# Compiler
CC = gcc
# Options
CFLAGS = -D__KERNEL__ -DMODULE -O -Wall -I$(INCLUDEDIR)
# Target
OBJS = hello.o
all: $(OBJS)
$(OBJS): hello.c /usr/include/linux/version.h
$(CC) $(CFLAGS) -c $ <
install:
insmod $(OBJS)
uninstall:
rmmod hello
.PHONY: clean
clean:
rm -f *.o
写Makefile时应该注意,不要用空格来代替 <TAB> 。否则会出现错误:missing separator. Stop.
修改执行脚本run:
#!/bin/bash
# The first step
make && make install && echo -e "Instal module - hello.o\n "
sleep 1
tail -n 1 /var/log/messages
lsmod | grep "hello " && echo -e "Module hello has instaled\n "
# The second step
make uninstall && echo -e "Remove module - hello\n "
sleep 1
tail -n 1 /var/log/messages
lsmod | grep "hello " || echo "Module hello has removed "
# The last step
make clean
执行结果如下:
# ./run
gcc -D__KERNEL__ -DMODULE -O -Wall -I/usr/src/linux-2.4.20-8/include/ -c hello.c
hello.c:18: warning: `init_module ' defined but not used
hello.c:25: warning: `cleanup_module ' defined but not used
insmod hello.o
Instal module - hello.o
Jan 31 13:40:23 lqm kernel: Hello,
hello 728 0 (unused)
Module hello has instaled
rmmod hello
Remove module - hello
Jan 31 13:40:24 lqm kernel:
Module hello has removed
rm -f *.o
(三)第三阶段:总结
1、一个内核模块至少应该包括两个函数:
(1)init_module:模块插入内核时调用
(2)cleanup_module:模块移除时调用
这个简单的程序就是只实现了这两个函数,而且只做了打印信息的工作,没有使用价值。典型情况下,init_module为内核中的某些东西注册一个句柄,相当于模块初始化的工作。cleanup_module则是撤销模块前期的处理工作,使模块得以安全卸载。
2、insmod实现动态加载模块。在当前OS上,动态加载模块以测试硬件等,避免了繁琐的工作。但是,在这种情况下,会出现版本不匹配的情况。另外,要分清楚内核源代码路径和编译器路径的不同,知道在编译时该指定那个路径。第二阶段开始出现过几个错误都是因为默认的路径是编译器路径,而不是内核源代码路径。体会内核模块化带来的好处!
3、应用Make工具来管理项目。即使小,也要训练。在2.4内核和2.6内核下,Makefile的编写会有所不同。只是语法形式的不同,先深入掌握一种,另一种注意一下应该可以避免犯错误。