版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ForFuture_/article/details/79394112
Linux设备驱动基础编程
内核功能模块:进程调度,内存管理(mmu,分配进程内存),文件系统管理(如:支持的文件系统格式),设备驱动(硬件驱动由内核来统一管理),网络协议栈。
模块机制:
静态加载:把驱动模块编进内核,在内核启动时自动加载。
动态加载:把驱动模块编为.ko文件,在内核启动后,需要用时手动加载。
内核驱动头文件中所定义有关的宏:
#define __init __section(.init.text)
#define __initdata __section(.init.data)
#define __section(S) __attribute__ ((__section__(#S)))
//段".init*"其实就是表示只要初始化后不会再使用,内核可以把这段里的空间回收使用
#define __exitdata __section(.exit.data)
#define __exit __section(.exit.text)
//段".exit*"应是用于集中管理只有驱动模块卸载时才会触发调用的资源,防止被误调用
char __initdata buf[] = "hello world";//表示此字符数组在驱动初始化后可以回收空间
#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") //module_exit具体的宏定义没列出来
//linux内核2.4版本时,设备驱动模块的初始化函数名必须是"init_module",卸载函数名必须是"cleanup_module"。
//现在我们写驱动模块,初始化函数和卸载函数名可以随便命名,但其实内核里还是没变。
简单的事例代码(xxx.c)(xxx为具体的.c文件名):
#include <linux/module.h>
#include <linux/init.h>
//__init为了把test_init的函数代码放入统一的初始化段里,当内核把驱动初始化完后,自动释放此函数的代码指令空间
static int __init test_init(void)
{
printk("This is the test init function\n");//printk为内核默认的打印函数
return 0;//返回0表示成功,返回负数退出加载模块,返回正数,会有警告但还是会加载
}
//__exit为了指定此函数只在驱动卸载时使用,用完后自动释放
static void __exit test_exit(void)
{
printk("This is the test exit function\n");
}
module_init(test_init);//指定test_init为模块初始化函数
module_exit(test_exit);//指定test_exit为模块退出时执行的卸载函数
MODULE_LICENSE("GPL");//指定所支持的协议
MODULE_AUTHOR("作者");
MODULE_DESCRIPTION("描述");
MODULE_VERSION("版本");
编译驱动模块,需要调用内核源码目录里的Makefile。
在代码目录下创建一个Makefile文件来指定编译目标。
Makefile文件内容:
obj-m += xxx.o
KSRC := /目录路径/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`
编译完成后会生成xxx.ko文件,可以将其加载到驱动中(相当于运行该文件):
insmod xxx.ko //加载驱动模块
rmmod xxx //卸载驱动模块
查看驱动模块信息:
modinfo xxx.ko //查看模块的信息
cat /proc/modules //查看当前系统的动态加载模块(相当于lsmod)
如:xxx 1768 0 - Live 0xbf03c000
(模块名 使用的内存大小 正在被调用次数 - 有效 模块所在的内存地址)
ls /sys/module //查看系统里现有的驱动模块(包括动静态驱动模块)
因为printk的输出级别问题,如果不进行设置,我们是看不到输出的。
我们可以通过以下命令来查看驱动输出的消息:
cat /var/log/messages
tail /var/log/messages
dmesg | tail
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”);//指定输出级别为6
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(即不指定级别时使用此级别)
代码里用于调试输出的宏:
#ifdef DEBUG
#define TS_DEBUG(fmt,args...) do { printk(fmt, ##args); } while (0)
#else
#define TS_DEBUG(fmt,args...) do { } while (0)
#endif
用法:
TS_DEBUG("hello\n");
TS_DEBUG("%d, %d\n", num , num2);