要编写linux驱动程序,首先要在自己的系统中配置并构造好内核树。
本人使用的是FC5,默认是不安装内核源代码的,可以上网下载一个,在源代码光盘4上也有内核源代码。
将光盘上的 kernel-2.6.15-1.2054_FC5.src.rpm 拷贝到 /usr/src 目录下。
# rpm -i kernel-2.6.15-1.2054_FC5.src.rpm
# cd /usr/src/redhat/SPECS
# ls
kernel-2.6.spec
# rpmbuild -bp --target i686 kernel-2.6.spec
# cd ..
# cd BUILD
# ls
kernel-2.6.15
# cd kernel-2.6.15
# ls
Config.mk linux-2.6.15.i686 vanilla xen xen-vanilla
# cd linux-2.6.15.i686
这里就是linux的内核树了。
接下来是编译内核,一定要编译内核,否则无法编译驱动程序。
# make
一个小时后内核就编译好了。
# cd /usr/src
# ln -s /usr/src/redhat/BUILD/kernel-2.6.15/linux-2.6.15.i686 linux
建立一个符号连接,这样就可以通过 /usr/src/linux 直接访问内核树了。
好了,现在可以开始编写内核模块了。
让我们从Hello World 开始吧。
/*hello.c*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
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 World/n" );
}
module_init(hello_init);
module_exit(hello_exit);
这个模块什么也不做,只是在加载时显示"Hello,World",卸载时显示"Goodbye World"。
这个模块定义了两个函数,其中一个在模块被装载到内核时调用hello_init(),而另一个则在模块被移出时调用hello_exit()。
module_init 和 module_exit 行使用了内核的特殊宏来表示上述两个函数所扮演的角色。另外一个宏 MODULE_LICENSE 用来告诉内核,该模块采用自由许可证,如果没有这样的声明,内核在装载该模块时会产生抱怨。函数printk()在 linux/kernel.h 中定义,功能和标准C库函数printf()类似。需要说明一点,写内核或内核模块不能用写应用程序时的系统调用或函数库,因为我们写的就是为应用程序提供系统调用的代码。内核有专用的函数库,如<linux/kernel.h>, <linux/fs.h>, <linux/sche.h>等。KERN_ALERT 定义了这条消息的优先级,我们需要在模块代码中显示地指定高优先级的原因在于:具有默认优先级的消息可能不会输出在控制台上。需要特别指出的是,printk 只能输出到文本控制台上,图形界面下的终端仿真器是不会看到printk 的输出的。
接下来是编写makefile:
TARGET = hello
KDIR = /usr/src/linux
PWD = $(shell pwd)
obj-m := $(TARGET).o
default:
make -C $(KDIR) M=$(PWD) modules
输入:
# make
好了模块已经编译好了,输入ls -a 可以看到hello.ko,现在就让我们来加载这个模块看看效果,按 ctrl+alt+F2 切换到文本界面,输入root 和密码,cd 到hello.ko 所在目录。
# insmod ./hello.ko
Hello World
# rmmod hello
Goodbye World
我们已经看到,编写一个模块并没有现象的那么困难——至少当模块不需要完成什么有价值的工作时。真正的困难在于理解设备并最大化其性能。
本人使用的是FC5,默认是不安装内核源代码的,可以上网下载一个,在源代码光盘4上也有内核源代码。
将光盘上的 kernel-2.6.15-1.2054_FC5.src.rpm 拷贝到 /usr/src 目录下。
# rpm -i kernel-2.6.15-1.2054_FC5.src.rpm
# cd /usr/src/redhat/SPECS
# ls
kernel-2.6.spec
# rpmbuild -bp --target i686 kernel-2.6.spec
# cd ..
# cd BUILD
# ls
kernel-2.6.15
# cd kernel-2.6.15
# ls
Config.mk linux-2.6.15.i686 vanilla xen xen-vanilla
# cd linux-2.6.15.i686
这里就是linux的内核树了。
接下来是编译内核,一定要编译内核,否则无法编译驱动程序。
# make
一个小时后内核就编译好了。
# cd /usr/src
# ln -s /usr/src/redhat/BUILD/kernel-2.6.15/linux-2.6.15.i686 linux
建立一个符号连接,这样就可以通过 /usr/src/linux 直接访问内核树了。
好了,现在可以开始编写内核模块了。
让我们从Hello World 开始吧。
/*hello.c*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
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 World/n" );
}
module_init(hello_init);
module_exit(hello_exit);
这个模块什么也不做,只是在加载时显示"Hello,World",卸载时显示"Goodbye World"。
这个模块定义了两个函数,其中一个在模块被装载到内核时调用hello_init(),而另一个则在模块被移出时调用hello_exit()。
module_init 和 module_exit 行使用了内核的特殊宏来表示上述两个函数所扮演的角色。另外一个宏 MODULE_LICENSE 用来告诉内核,该模块采用自由许可证,如果没有这样的声明,内核在装载该模块时会产生抱怨。函数printk()在 linux/kernel.h 中定义,功能和标准C库函数printf()类似。需要说明一点,写内核或内核模块不能用写应用程序时的系统调用或函数库,因为我们写的就是为应用程序提供系统调用的代码。内核有专用的函数库,如<linux/kernel.h>, <linux/fs.h>, <linux/sche.h>等。KERN_ALERT 定义了这条消息的优先级,我们需要在模块代码中显示地指定高优先级的原因在于:具有默认优先级的消息可能不会输出在控制台上。需要特别指出的是,printk 只能输出到文本控制台上,图形界面下的终端仿真器是不会看到printk 的输出的。
接下来是编写makefile:
TARGET = hello
KDIR = /usr/src/linux
PWD = $(shell pwd)
obj-m := $(TARGET).o
default:
make -C $(KDIR) M=$(PWD) modules
输入:
# make
好了模块已经编译好了,输入ls -a 可以看到hello.ko,现在就让我们来加载这个模块看看效果,按 ctrl+alt+F2 切换到文本界面,输入root 和密码,cd 到hello.ko 所在目录。
# insmod ./hello.ko
Hello World
# rmmod hello
Goodbye World
我们已经看到,编写一个模块并没有现象的那么困难——至少当模块不需要完成什么有价值的工作时。真正的困难在于理解设备并最大化其性能。