Linux是单内核的。单内核在百度百科中的说法就是把它从整体上作为一个单独的大过程来实现,同时也运行在一个单独的地址空间上,所有内核服务都在这样的一个大内核地址空间上运行。它的内部又能够被分为若干模块,内核之间的通信是微不足道的,因为大家都运行在内核态,并身处同一地址空间上,所以内核可以直接调用函数。
与单内核相对的是微内核。微内核的功能被划分为多个独立的过程,操作系统的核心部分是一个很小的内核,实现一些最基本的服务,如创建和删除进程、内存管理、中断管理等等。而文件系统、网络协议等其它部分都在微内核外的用户空间里运行。因此,微内核操作系统的扩展性要好,为了改善单内核的可扩展性、可维护性等,Linux操作系统使用了一种全新的内核模块机制。用户可以根据需要,在不需要对内核重新编译的情况下,模块能动态地装入内核或从内核移出。
优点:使用模块编程,可以提高Linux的灵活性;当需要对模块进行修改时,不用对整个内核编译而只需要对模块进行编程即可;模块可以不依赖于某个固定的硬件平台;当模块被加载入内核后,就相当于静态链接到了内核中,内核和模块在一个地址空间中运行。
首先,要写一个模块的config文件。一个模块是是否编译是由内核使用者决定的,所以要先会编写config文件。当我们在内核中使用内核编译命令make ARCH=arm menuconfig时,所有配置工具都是通过读取"arch/$(ARCH)Kconfig"文件来生成配置界面,这个文件就是所有配置的总入口,它会包含其他目录的Kconfig。假设我们要写一个字符设备的驱动模块,那么我就要在drivers/char目录下新建一个目录,表示我们的驱动模块,比如说叫new_module。那么我们驱动模块的Kconfig就在这个新目录new_module下新建,
menu "New_Moudle" #表示一个菜单的开始
config new_module #一个配置项 new_module
bool "open the new_module" #配置项是bool类型 y n,后面是显示的文本
#三态选项多一个m ,表示编译为模块
default n #默认为关
help #help注释
its a test
endmenu
只创建这个文件还不够,要将它加入到config执行菜单下面。按照上面的假设,现在 我们要在char目录下找到Kconfig文件,并将我们新创建模块的Kconfig文件添加进去。具体方法如下,
################
source "drivers/char/new_module/Kconfig"
################
这一句话的效果相当于C语言中的include,声明了新创建的Kconfig的路径。
当你选择y的时候,内核会创建一个宏CONFIG_NEW_MOUDLE,使用该宏可以控制makefile。
写完Kconfig文件后,我们开始编写自己的模块文件,
new_module.c
#include //这两个头文件通常要添加
#include
/* Init function called on module entry */
int my_module_init( void )
{
int i = 0;
for(i = 0;i < 20 ;i++){
printk(KERN_INFO "new_module_init called. Module is now loaded.\n");
}
return 0;
}
/* Cleanup function called on module exit */
void my_module_cleanup( void )
{
printk(KERN_INFO "new_module_cleanup called. Module is now unloaded.\n");
return;
}
/* Declare entry and exit functions */
module_init( my_module_init ); //模块加载函数
module_exit( my_module_cleanup );//模块卸载函数
/* Defines the license for this linux kernel module */
MODULE_LICENSE("GPL");
从2.4.10版本内核开始,模块必须通过MODULE_LICENSE宏声明此模块的许可证,否则在加载此模块时,会收到内核被污染 “kernel tainted” 的警告。从linux/module.h文件中可以看到,被内核接受的有意义的许可证有 “GPL”,“GPL v2”,“GPL and additional rights”,“Dual BSD/GPL”,“Dual MPL/GPL”,“Proprietary”。
同样,我们需要将新创建的文件添加入makefile的编译链中,当开启该配置,将模块编译入内核,否则,不编译该模块。
在char目录下面的Makefile文件中,添加新创建的目录
obj-$(CONFIG_NEW_MOUDLE) += nmodule/
在nmodule目录下面创建makefile文件,并且写入
obj-$(CONFIG_NEW_MOUDLE) += new_module.o
以上的操作会将模块静态编译入内核中,如果想要编译成ko文件,可以动态的加载卸载的话,可以参考下面的方法。
如果想要编译成ko文件可以动态加卸载,通常我们可以在根目录下的kmodule目录里创建一个新的目录,new_module,其下两个目录include和source分别存放头文件和源文件,
//hello.h
static int pp = 123;
//hello.c
#include
#include
#include "hello.h"
MODULE_LICENSE("GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello, world!%d\n",pp);
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT"Goodbye, world\n");
}
module_init(hello_init);
module_exit(hello_exit);
由于这一次的编写比之前的更加完备,makefile略显复杂
//Makefile
include $(ROOT_PATH)/kmodule/new_module/new_module.mk
CURDIR=$(shell pwd)
#模块名字,不要与模块中的.c文件同名
MODULE_NAME := new_module
obj-m := ${MODULE_NAME}.o
#user define objects
${MODULE_NAME}-objs += $(PLAT_OBJS)
INCS+= $(PDT_DIR_INC)
EXTRA_CFLAGS+= ${INCS}
all:
$(MAKE) -C $(LINUX_KERNEL_PATH) SUBDIRS=$(shell pwd) modules
clean:
@rm -f *.o *.ko .*cmd *mod.* .*.o.d $(${MODULE_NAME}-objs)
@rm -f $(PLAT_NEW_MODULE_SRC_PATH)/.*cmd $(PLAT_NEW_MODULE_SRC_PATH)/*mod*.* $(PLAT_NEW_MODULE_SRC_PATH)/.*.o.d
上面的文件是主文件,另外还有new_module.mk文件,声明目标文件和所需要的头文件目录。
PLAT_OBJS :=
PDT_DIR_INC :=
PDT_DIR_INC += -I$(ROOT_PATH)/kmodule/new_module/include
PLAT_NEW_MODULE_SRC_PATH := source
PLAT_OBJS += $(PLAT_NEW_MODULE_SRC_PATH)/hello.o
具体的模块编译规则是由本地工程 $(LINUX_KERNEL_PATH)路径下的规则制定,INC和EXTRA_CFLAGS就是与本地工程具体情况相关的变量。之前一样,需要把该文件夹下的makefile关联到上一级makefile中。