操作系统
操作系统是运行在硬件层之上的,给应用程序提供运行环境的裸机程序。
目前操作系统有两种典型的内核实现模式:
1、单内核(宏内核):所有子模块代码编译到一个比较大的可执行文件(镜像文件)中。各子模块代码共用同一套运行资源,各模块间的交互直接通过函数调用来进行。如:linux
2、微内核:只将任务管理、内存管理、启动管理最其本的三个子模块编译到一个微型可执行文件中。其它子模块则各自编译成独立的后台服务程序,这些服务程序与微型内核以及APP间主要通过各种IPC进行通讯。如:windows
Linux是一个单内核,运行在单独的地址空间上。不过,Linux汲取了微内核的精化,具有微内核所所具有的模块化设计、抢占式内核、支持内核线程以及动态装载内核模块的能力。同时,Linux还避免了微内核的设计缺陷,linux让所有模块都运行在内核态,直接调用函数,无须像微内核那样采用消息传递。
内核模块
单独编译的一段内核代码,在需要的时候动态地加载到内核,从而动态地增加内核的功能,在不需要的时候,可以动态地卸载,从而减少内核,并节约一部分内存。而且无论是加载还是卸载都不需要重启整个系统。
模块化的好处:
1、模块本身不被编译到内核镜像里面,可以减小内核体积,节省内存资源。
2、可以在内核中添加或删除功能而不用重新编译内核,解决单内核扩展性差的缺点
3、加载和卸载不重启系统,单独编译,可以提高开发效率
如下为一个模块程序的模板,保存为mod.c
//头文件(必须)
#include <linux/init.h>//初始化头文件
#include <linux/module.h>//模块头文件
//加载函数(必须)
static int hello_init(void)//返回值是int类型,函数名自定义,参数是void
{
printk(KERN_ALERT "Hello World.\n");
return 0;
}
//卸载函数(必须)
static void hello_exit(void)//返回值是void类型,函数名自定义,参数是void
{
printk(KERN_ALERT "Goodbye World.\n");
}
//注册(必须)
module_init(hello_init);//对应加载函数
module_exit(hello_exit);//对应卸载函数
//license(必须)
MODULE_LICENSE("Dual BSD/GPL");
//作者与描述(可选)
MODULE_AUTHOR("Ono Zhang");
MODULE_DESCRIPTION("A simple Hello World Module");
与应用程序的区别:
#include <stdio.h>
int main(int argc, char *argv[])//参数也可以是void,如int main(void)
{
printf("Hello world.\n");
return 0;
}
1、模块的入口有2个,分别是module_init、module_exit里面对应的加载函数和卸载函数,而应用程序只有1个,main函数。
2、模块打印使用printk,应用程序打印使用printf。
3、模块入口函数的参数是void,应用程序参数可以是void或者2个参数。
编译
2种编译方式
1、编译进内核
把源码放到内核源码的drivers目录的某个目录下面,比如drivers/char目录下
修改目录下的Makefile,加一行 obj-$(CONFIG_MY_MOD) += mod.o,其中mod.o为mod.c对应的编译对象文件
修改Kconfig config MY_MOD tristate "My Module" help support my module device on develop board
进入顶层目录make menuconfig 选中Y Device Drivers->Character devices->My Module
make
这样镜像就包含了模块程序文件的代码
2、单独编译 *.ko
1)如果在内核源码目录下,同第一点修改,make menuconfig 选中M ,
进入顶层目录
make,进行编译内核镜像
make modules,进行编译模块文件
2)如果在外层目录,自己写Makefile,然后make
Makefile文件如下
obj-m = mod.o
KERNELDIR = /home/linux-kernel
PWD = $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) clean
其中KERNELDIR是内核源码目录所在的路径