1.宏内核与微内核

内核分为四大类:单内核(宏内核);微内核;稠浊内核;外内核。

宏内核(Monolithickernel)是将内核从整体上作为一个大过程来实现,所有的内查究事都在一个地址空间运行,相互之间直接调用函数,大略高效。
Linux虽是宏内核,但已接管了微内核的部分精华。
Linux是模块化的、多线程的、内核本身可调度的系统,既接管了微内核的精华,又保留了宏内核的优点,无需通报,避免性能丢失。
微内核(Microkernel)功能被划分成独立的过程,过程间通过IPC进行通信,模块化程度高,一个做事失落效不会影响其余一个做事。

image

phpecmdefine走进linux 驱动开辟 之 内核模块 HTML

2.Linux体系架构

从两个层次上来考虑操作系统

用户空间:包含了用户的运用程序和C库GNU C Library (glibc)供应了连接内核的系统调用接口,还供应了在用户空间运用程序和内核之间进行转换的机制。
内核空间:包含了系统调用,内核,以及与平台架构干系的代码

image

划分缘故原由

当代CPU常日都实现了不同的事情模式以ARM为例:ARM实现了7种事情模式,不同模式下CPU可以实行的指令或者访问的寄存器不同:(1)用户模式 usr(2)系统模式 sys(3)管理模式 svc(4)快速中断 fiq(5)外部中断 irq(6)数据访问终止 abt(7)未定义指令非常;以X86为例:X86实现了4个不同级别的权限,Ring0—Ring3 ;Ring0下可以实行特权指令,可以访问IO设备;Ring3则有很多的限定。
为了保护内核的安全,把系统分成了2部分:用户空间和内核空间是程序实行的两种不同状态,我们可以通过“系统调用”和“硬件中断“来完成用户空间到内核空间的转移;

3.Linux的内核构造

Linux内核是整体式构造(宏内核),各个子系统联系紧密,作为一个大程序在内核空间运行。

系统调用接口(system call interface,SCI)供应了某些机制实行从用户空间到内核的函数调用。

image

1)Linux内核组成(子系统)

进程调度(SCHED):掌握多个进程对CPU的访问。
当须要选择下一个进程运行时,由调度程序选择最值得运行的进程。
可运行进程实际上是仅等待CPU资源的进程,如果某个进程在等待其它资源,则该进程是不可运行进程。
Linux利用了比较大略的基于优先级的进程调度算法选择新的进程。
内存管理(memory management,MM):许可多个进程安全的共享主内存区域。
Linux 的内存管理支持虚拟内存,即在打算机中运行的程序,其代码,数据,堆栈的总量可以超过实际内存的大小,操作系统只是把当前利用的程序块保留在内存中,别的的程序块则保留在磁盘中。
必要时,操作系统卖力在磁盘和内存间交流程序块。
内存管理从逻辑上分为硬件无关部分和硬件有关部分。
硬件无关部分供应了进程的映射和逻辑内存的对换;硬件干系的部分为内存管理硬件供应了虚拟接口。
一样平常而言,Linux的每个进程享有4GB的内存空间,03GB属于用户空间,34GB属于内核空间。
虚拟文件系统(Virtual File System,VFS):隐蔽了各种硬件的详细细节,为所有的设备供应了统一的接口,VFS供应了多达数十种不同的文件系统。
虚拟文件系统可以分为逻辑文件系统和设备驱动程序。
逻辑文件系统指Linux所支持的文件系统,如ext2,fat等,设备驱动程序指为每一种硬件掌握器所编写的设备驱动程序模块。

image网络接口(NET):供应了对各种网络标准的存取和各种网络硬件的支持。
网络接口可分为网络协议和网络驱动程序。
网络协议部分卖力实现每一种可能的网络传输协议。
网络设备驱动程序卖力与硬件设备通讯,每一种可能的硬件设备都有相应的设备驱动程序。

image进程间通讯(inter-process communication,IPC): 支持进程间各种通信机制。
共享内存管道旗子暗记量行列步队套接字

4.内核模块

Linux内核是模块化组成的,它许可内核在运行时动态地向个中插入或删除代码。

二、内核模块构造

1.头文件

内核模块头文件<linux/module.h>和<linux/init.h>是必不可少的 ,不同模块根据功能的差异,所须要的头文件也不相同 。

#include <linux/module.h> #include <linux/init.h> 2.模块初始化

模块的初始化卖力注册模块本身 ,只有已注册模块的各种方法才能够被运用程序利用并发挥各方法的实际功能。

模块并不是内核内部的代码,而是独立于内核之外,通过初始化,能够让内核之外的代码来替内核完本钱该当由内核完成的功能,模块初始化的功能相称于模块与内核之间衔接的桥梁,奉告内核已经准备好模块了。

内核模块初始化函数

//模块初始化函数一样平常都需声明为 static //__init 表示初始化函数仅仅在初始化期间利用,一旦初始化完毕,将开释初始化函数所占用的内存 static int __init module_init_func(void) { 初始化代码 } module_init(module_init_func); //module_init宏定义会在模块的目标代码中增加一个分外的代码段,用于解释该初始化函数所在的位置。
当利用 insmod 将模块加载进内核的时候,初始化函数的代码将会被实行。

3.模块退出

模块的退出相称于奉告内核“我要离开了,将不再为您做事了”。

内核模块退出函数

//模块退出函数没有返回值;//__exit 标记这段代码仅用于模块卸载;static void __exit module_exit_func(void){ //模块退出代码}module_exit(module_exit_func);//没有 module_exit 定义的模块无法被卸载

当利用 rmmod 卸载模块时,退出函数的代码将被实行。

把稳:如果模块被编译进内核,而不是动态加载,则__init的利用会在模块初始化完成后丢弃该函数并回收所占内存, _exit宏将忽略“清理扫尾”的函数。

4.模块容许证声明

Linux 内核是开源的,遵守 GPL 协议,以是哀求加载进内核的模块也最好遵照干系协议。

为模块指定遵守的协议用 MODULE_LINCENSE 来声明 :

MODULE_LICENSE(\"大众GPL\"大众); 内核能够识别的协议有“GPL”“GPL v2”“GPL and additional rights(GPL 及附加权利)”“Dual BSD/GPL(BSD/GPL 双重容许)”“Dual MPL/GPL(MPL/GPL 双重容许)”“Proprietary(私有)”

5.模块导出符号 【可选】

利用模块导出符号,方便其它模块依赖于该模块,并利用模块中的变量和函数等。

在Linux2.6的内核中,/proc/kallsyms文件对应着符号表,它记录了符号和符号对应的内存地址。
$ cat /proc/kallsyms ... ffffff80084039b8 t shash_digest_unaligned ffffff8008403a30 T crypto_shash_digest ffffff8008403ac0 t shash_async_final ffffff8008403af0 T shash_ahash_update ffffff8008403b50 t shash_async_update ffffff8008403b80 t crypto_exit_shash_ops_async ffffff8008403bb0 t crypto_shash_report ffffff8008403c18 t crypto_shash_show ffffff8008403c78 T crypto_alloc_shash ffffff8008403cc8 T crypto_register_shash ffffff8008403d00 T crypto_unregister_shash ffffff8008403d30 T crypto_register_shashes ffffff8008403df8 T crypto_unregister_shashes ffffff8008403e90 T shash_register_instance ffffff8008403ed0 T shash_free_instance ffffff8008403f08 T crypto_init_shash_spawn ffffff8008403f58 T shash_attr_alg ffffff8008403fb0 T shash_ahash_finup ffffff8008404068 t shash_async_finup ffffff80084040b0 T shash_ahash_digest ffffff80084041e0 t shash_async_digest ... 利用一下宏定义导出符号

EXPORT_SYMBOL(module_symbol);//或EXPORT_GPL_SYMBOL(module_symbol);

6.模块描述 [可选]

模块编写者还可以为所编写的模块增加一些其它描述信息,如模块作者、模块本身的描述或者模块版本等

MODULE_AUTHOR(\"大众Abing <Linux@zlgmcu.com>\"大众);MODULE_DESCRIPTION(\公众ZHIYUAN ecm1352 beep Driver\"大众);MODULE_VERSION(\"大众V1.00\公众);

模块描述以及容许证声明一样平常放在文件末端。

三、向Linux内核添加新内核模块

1.添加模块驱动文件

在linux/drivers/下新建目录hello,并且在hello/目录下新建hello.c、Makefile、Kconfig三个文件。

1)内核模块程序hello.c

/ hello world module /#include <linux/module.h>#include <linux/init.h>#include <linux/kernel.h>static int __init hello_init(void){ printk(KERN_INFO \公众Hello, I'm ready!\n\"大众); return 0;}static void __exit hello_exit(void){ printk(KERN_INFO \公众I'll be leaving, bye!\n\"大众);}module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE(\公众GPL\"大众);MODULE_AUTHOR(\"大众michael\"大众);MODULE_DESCRIPTION(\公众hello world module\公众);内核通过 printk() 输出的信息具有日志级别,日志级别是通过在 printk() 输出的字符串前加一个带尖括号的整数来掌握的,如 printk(“<6>Hello, world!/n”);。
内核中共供应了八种不同的日志级别

// 在 linux/kernel.h 中有相应的宏对应 #define KERN_EMERG \公众<0>\"大众 / system is unusable / #define KERN_ALERT \公众<1>\公众 / action must be taken immediately / #define KERN_CRIT \公众<2>\"大众 / critical conditions / #define KERN_ERR \"大众<3>\"大众 / error conditions / #define KERN_WARNING \"大众<4>\"大众 / warning conditions / #define KERN_NOTICE \"大众<5>\"大众 / normal but significant / #define KERN_INFO \"大众<6>\"大众 / informational / #define KERN_DEBUG \公众<7>\"大众 / debug-level messages / 2)Kconfig

menu \"大众HELLO TEST Driver \"大众comment \"大众HELLO TEST Driver Config\"大众 config HELLO tristate \"大众hello module test\公众 default m help This is the hello test driver. endmenu在menuconfig的“driver”菜单下添加“HELLO TEST Driver”子菜单,并加入“HELLO”配置选项,选项默认为m。
保存menuconfig后,会在kernel根目录下的.config文件中天生“CONFIG_HELLO=m”,在编译的时候会添加到临时环境变量中。

3)Makefile

obj-$(CONFIG_HELLO) += hello.o

可用于动态模块外部编译的写法

编译模块的内核配置必须与所运行内核的编译配置一样 。
ifneq ($(KERNELRELEASE),) obj-m += hello.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build # 定义内核路径 PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules # 表示在当前目录下编译 clean: rm -rf ..cmd .o .mod.c .ko .tmp_versions endif KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取实行此Makefile时,KERNELRELEASE没有被定义,以是make将读取实行else之后的内容。
当从内核源码目录返回时,KERNELRELEASE已被定义,kbuild也被启动去解析kbuild语法的语句,make将连续读取else之前的内容,天生的目标模块名。

2.修正上一级目录的Kconfig和Makefile

进入linux/drivers/

编辑Makefile,在后面添加一行:obj-$(CONFIG_HELLO) += hello/ 编辑Kconfig,在后面添加一行:

source \"大众drivers/hello/Kconfig\"大众注:某些内核版本须要同时在arch/arm/Kconfig中添加:source \公众drivers/hello/Kconfig\公众

3.make menuconfig配置和编译

实行:make menuconfig ARCH=arm进入配置菜单选择并进入:Device Drivers选项

image

进入 HELLO TEST Driver选项

image可以选择<m> <y> <n>,分别为编译成内核模块、编译进内核、不编译。

如果选择编译成动态模块<m>

编译内核过程中,会有如下输出:

LD drivers/hello/built-in.oCC [M] drivers/hello/hello.oCC drivers/hello/hello.mod.oLD [M] drivers/hello/hello.ko

如果选择编译进内核<y>

编译内核过程中,会有如下输出:

CC drivers/hello/hello.o LD drivers/hello/built-in.o 4.动态模块加载和卸载

加载模块利用 insmod 命令,卸载模块利用 rmmod 命令。

$ insmod hello.ko $ rmmod hello.ko #加载和卸载模块必须具有 root 权限 。
对付可接管参数的模块,在加载模块的时候为变量赋值即可,卸载模块无需参数。

$ insmod hello.ko num=8$ rmmod hello.ko

四、带参数的内核模块

模块参数必须利用 module_param 宏来声明,常日放在文件头部。

module_param 须要 3个参数:变量名称、类型以及用于 sysfs 入口的访问掩码。

static int num = 5; module_param(num, int, S_IRUGO); 内核模块支持的参数类型有: bool、 invbool、 charp、 int、 short、 long、 uint、 ushort和 ulong。
访问掩码的值在<linux/stat.h>定义, S_IRUGO 表示任何人都可以读取该参数,但不能修正。
支持传参的模块需包含 moduleparam.h 头文件。

能够吸收参数的模块范例

<linux/module.h>#include <linux/init.h>// moduleparam.h 文件已经包含在 module.h 文件中static int num = 3;static char whom = \公众master\公众;module_param(num, int, S_IRUGO);module_param(whom, charp, S_IRUGO);static int __init hello_init(void){ printk(KERN_INFO \"大众%s, I get %d\n\"大众, whom, num); //KERN_INFO 表示这条打印信息的级别 return 0;}static void __exit hello_exit(void){ printk(\"大众I'll be leaving, bye!\n\公众);}module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE(\"大众GPL\公众);MODULE_AUTHOR(\"大众luxiaodai\"大众);MODULE_DESCRIPTION(\"大众this is my first module\公众);更多linux免费视频资料获取 后台私信【架构】