1.用户空间和内核空间
用户空间:应用程序运行在用户空间,运行在处理器的最低级别,处理器控制着对硬件的直接访问以及内存的非授权访问。
内核空间:驱动模块运行于内核空间,运行在处理器的最高级别,可以进行所有的操作。
应用程序执行模式从用户模空间转到内核空间有两种方式:系统调用和中断。一个驱动程序也应该包含这两部分的功能。
2.内核并发
铭记:同一时刻,内核模块可能会有许多事情正在发生。
内核模块一定要注意保护并发,后面会讲到关于控制并发的内核API。
3.当前进程
struct task_struct 结构在include\linux\sched.h中定义,这个结构记录了当前进程的各种信息,结构体很庞大,这里就不给出代码了,有兴趣的朋友可以去看看
宏 current在arch/x86/asm/current.h中定义:
static __always_inline struct task_struct *get_current(void)
{
return read_pda(pcurrent);
}
#define current get_current()
而 read_pda函数在同目录下的pda.h中定义,也是一个宏,定义如下:
- #define pda_from_op(op, field) \
- ({ \
- typeof(_proxy_pda.field) ret__; \
- switch (sizeof(_proxy_pda.field)) { \
- case 2: \
- asm(op "w %%gs:%c1,%0" : \
- "=r" (ret__) : \
- "i" (pda_offset(field)), \
- "m" (_proxy_pda.field)); \
- break; \
- case 4: \
- asm(op "l %%gs:%c1,%0": \
- "=r" (ret__): \
- "i" (pda_offset(field)), \
- "m" (_proxy_pda.field)); \
- break; \
- case 8: \
- asm(op "q %%gs:%c1,%0": \
- "=r" (ret__) : \
- "i" (pda_offset(field)), \
- "m" (_proxy_pda.field)); \
- break; \
- default: \
- __bad_pda_field(); \
- } \
- ret__; \
- })
- #define read_pda(field) pda_from_op("mov", field)
看的出是一些内嵌汇编的操作,这里看的一头雾水,确实看不懂,希望知道的朋友告诉一声,不过我们现在知道通过current指针可以知道当前进程的一些信息,具体可以得到哪些东东可以看前面那个很大的struct task_struct结构体,书上举了个例子可以获得当前进行的程序基本名称(current->comm)和PID(current->pid)。
4.一些细节
*内核栈空间小,一般是页大小,所以内核模块要注意自动变量的分配。
*内核API中函数中某些有两个下划线前缀,要注意谨慎使用,比如__init表示在该函数执行完后内核会回收该函数的所有内存空间,后面还有些会在hello world程序中看到。
*内核模块中的符号(函数,变量)可以用EXPORT_SYMBOL()函数导出,这个符号信息会存在ELF中,装载模块时,内核可以从该ELF头去寻找模块导出的变量。
5.hello world 内核模块
好,基本的知识总结终于完了,现在马上让我们进入hello world内核模块的编写,哈哈,上程序:
- /*
- * $Id: hello.c,v 1.5 2004/10/26 03:32:21 corbet Exp
- */
- #include <linux/init.h> //包含moudle_init和module_exit的宏声明
- #include <linux/module.h> //包含一些声明,比如下面用到的MODULE_LICENSE等
- MODULE_LICENSE("Dual BSD/GPL");
- static int hello_init(void) //模块初始化函数,insmod时调用
- {
- printk(KERN_ALERT "Hello, world\n"); //打印函数,打印级别为KERN_ALERT
- return 0;
- }
- static void hello_exit(void) //模块退出函数,rmmod时调用
- {
- printk(KERN_ALERT "Goodbye, cruel world\n");
- }
- module_init(hello_init); //模块初始化函数声明
- module_exit(hello_exit); //模块退出时函数声明
其实程序很简单,在使用insmod hello.ko时在console打印Hello,world;在使用rmmod hello时打印Goodbye,crule world
5.1设置测试系统
在编译内核模块之前,我们需要构建内核源码树,而且我们编译模块使用的内核和最后模块运行在其中的目标内核最好保持一致,这样才能保证模块的正确插入。
5.2 Makefile编写
在看下面这个Makefile之前,最好看看内核Documentation/kbuild/modules.txt文件,它详细讲述了以下4方面:
In this document you will find information about:
- how to build external modules
- how to make your module use the kbuild infrastructur
- how kbuild will install a kernel
- how to install modules in a non-standard location
- # To build modules outside of the kernel tree, we run "make"
- # in the kernel source tree; the Makefile these then includes this
- # Makefile once again.
- # This conditional selects whether we are being included from the
- # kernel Makefile or not.
- ifeq ($(KERNELRELEASE),)
# Normal Makefile
- # Assume the source tree is where the running kernel was built
- # You should set KERNELDIR in the environment if it's elsewhere
- KERNELDIR ?= /lib/modules/$(shell uname -r)/build
- # The current directory is passed to sub-makes as argument
- PWD := $(shell pwd)
- modules:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- modules_install:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
- clean:
- rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
- .PHONY: modules modules_install clean
- else
- # called from kernel build system: just declare what our modules are
- # kbuild part of makefile
- obj-m := hello.o
- endif
上面的Makefile很方便的实现了和内核kbuild系统的连接,最好的理解方法可以把上面的文件拆成两个文件kbuild和Makefile来理解:
- FileName:kbuild
- obj-m:=hello.o
- FileName:Makefile
- KERNELDIR ?= /lib/modules/$(shell uname -r)/build
- PWD := $(shell pwd)
- modules:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- modules_install:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
- clean:
- rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
- .PHONY: modules modules_install clean
kbuild是给内核构建系统使用的,而Makefile就是编译成最终的modules需要的。
5.3 在mini2440开发板上运行hello world程序
我们可以构建在开发板上运行的hello world内核模块程序,这里不会讲解具体嵌入式linux开发环境的搭建,只需要知道内核版本是友善提供的2.6.29的内核版本。
我的开发环境是虚拟机RHE5,装好2.6.29源码,配置好交叉编译器,编译一个带nfs功能的内核,建立好文件系统,用nfs挂载,待整个系统运行后,在虚拟机用交叉编译器编译好helloworld内核模块,复制到文件系统目录,在SecureCRT串口工具执行insmod,结果如下:
[RealWit@MINI2440:/] #insmod hello.ko
Hello, world
[RealWit@MINI2440:/] #rmmod hello
Goodbye, cruel world
rmmod: module 'hello' not found
奇怪?怎么会有一句rmmod: module 'hello' not found出现,网上查了查,说是要把模块放到/lib/modules/2.6.29.4-RelWit/下,果然我试了下,就正确了,不过按道理所有内核模块编译出来的东东都应该放到该目录下,好吧,算完成了。
第一个内核模块helloworld完成了,万里长征还是第一步,下面我们会介绍最常见的字符设备驱动程序的设计框架,明天又该忙咯...
阅读(1682) | 评论(0) | 转发(0) |