内核功能模块: 进程调度, 内存管理(mmu, 分配进程内存), 文件系统管理(如支持的文件系统格式), 设备驱动(硬件驱动好后由内核来统一管理), 网络协议栈
1). 模块机制
静态加载, 把驱动模块编进内核, 在内核启动时自动加载
动态加载, 把驱动模块编为ko, 在内核启动后,需要用时加载
2). 编写内核驱动
#include <linux/module.h>
#include <linux/init.h>
static int __init test_init(void)
{
return 0; //返回0表示成功, 返加负数退出加载模块, 如返1,2,3等正数,会有警告但还是会加载
}
//__init 把test_init的函数代码放入统一的初始化段里,当内核把驱动初始化完后, 释放此函数的代码指令空间
static void __exit test_exit(void)
{
....
}
//__exit 指定此函数只在驱动卸载时使用, 用完后释放
module_init(test_init); //指定test_init为模块初始化函数
module_exit(test_exit); //指定test_exit为模块退出时执行的卸载函数
MODULE_LICENSE("GPL"); //指定所支持的协议
MODULE_AUTHOR("作者");
MODULE_DESCRIPTION("描述");
MODULE_VERSION("版本");
3). 所用的宏定义:
#define __init __section(.init.text)
#define __initdata __section(.init.data)
#define __section(S) __attribute__ ((__section__(#S)))
char __initdata buf[] = "hello world"; //表示此字符数组在驱动初始化后可以回收空间
// 段".init*"其实就是表示只要初始化后不会再使用,内核可以把这段里的空间回收使用。
#define __exitdata __section(.exit.data)
#define __exit __section(.exit.text)
// 段".exit*"应是用于集中管理只有驱动模块卸载时才会触发调用的资源,防止被误调用。
//linux内核2.4版本时,设备驱动模块的初始化函数名必须是"init_module", 卸载函数名必须是"cleanup_module".
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
module_init这宏其实就是把我们的初始化函数名多加个别名("init_module")
module_exit宏用于把卸载函数多加个别名("cleanup_module")
// 现在我们写驱动模块,初始化函数和卸载函数名可以随便命名,其实内核里还是没变。
4). 编译驱动模块,需要调用内核源码目录里的Makefile, 并在驱动源码目录下创建一个Makefile文件指定编译目标:
obj-m += test.o
KSRC := /disk3/myown/h3/orangepi_sdk/source/linux-3.4.112/
export ARCH := arm
export CROSS_COMPILE := arm-linux-gnueabihf-
all:
make -C $(KSRC) modules M=`pwd`
.PHONY : clean
clean:
make -C $(KSRC) modules clean M=`pwd`
编译好后:
insmod test.ko //加载驱动模块
rmmod test //卸载驱动模块
5). 查看驱动模块信息:
modinfo test.ko 查看模块的信息
cat /proc/modules 查看当前系统的动态加载模块 相当于lsmod
test 1768 0 - Live 0xbf03c000
模块名, 使用的内存大小, 正在被调用次数, 有效 , 模块所在的内存地址
ls /sys/module 查看系统里现有的驱动模块,包括动静态驱动模块
6). 查看驱动输出的消息
cat /var/log/messages
tail /var/log/messages
dmesg | tail
7). printk的输出级别控制
#include <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 condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
默认的级别为13: "<d>"
#define KERN_DEFAULT "<d>"
使用 : printk(KERN_INFO"内容"); // printk("<6>kskdlfj");
查看当前内核的输出级别 cat /proc/sys/kernel/printk
7 7 1 7
7:console_loglevel
7:default_message_loglevel
1:minimum_console_loglevel
7:default_console_loglevel
当printk函数使用的级别小于当前console_loglevel级别时, 则可以输出, 否则不输出
修改级别输出 echo "8 4" > /proc/sys/kernel/printk
//输出级别只要小于8就可以输出, 默认级别为4(即不指定级别时使用此级别)
8). 代码里用于调试输出的宏:
#ifdef DEBUG
#define TS_DEBUG(fmt,args...) do { printk(fmt, ##args); } while (0)
#else
#define TS_DEBUG(fmt,args...) do { } while (0)
#endif
usage:
TS_DEBUG("hello\n");
TS_DEBUG("%d, %d\n", num , num2);