14 Linux设备驱动基础编程

版权声明:本文为博主原创文章,未经博主允许不得转载。 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);

猜你喜欢

转载自blog.csdn.net/ForFuture_/article/details/79394112
今日推荐