字符型设备驱动程序-first-printf以及点亮LED灯(三)
根据 字符型设备驱动程序-first-printf以及点亮LED灯(二) 学习 修改函数 中的printf 为 printk.
#include <linux/module.h> /* Needed by all modules */ #include <linux/kernel.h> /* Needed for KERN_DEBUG */ #include <linux/init.h> /* Needed for __init */ #include <linux/fs.h> int major =110; //第一步:首先编写三个原函数 int first_Led_Open( void ) { printk( KERN_DEBUG "first Led Open!\r\n"); return 0; } int first_Led_Write( void ) { printk( KERN_DEBUG "first Led Write!\r\n"); return 0; } int first_Led_Read( void ) { printk( KERN_DEBUG "first Led Read!\r\n"); return 0; } //第二步:告诉Linux内核上面三个函数的存在,定义一个 struct file_operations, 并且赋初值,就是上面的三个函数 static struct file_operations first_Led_Fops= { .owner = THIS_MODULE, .read = first_Led_Read, .write = first_Led_Write, .open = first_Led_Open, }; //编写驱动的入口函数 ,调用上面的 结构体,向内核注册上面三个函数的 存在 int __init first_Led_Init( void ) { printk( KERN_DEBUG "first Led Init!\r\n"); //调用 register_chrdev //参数1:major 表示主设备号 //参数2:name //参数3:要向内核注册的 file_operations 结构 类型 //我的理解是: //register_chrdev这个函数作用把 file_operations 结构 类型放到一个 字符设备数组中,位置是 major register_chrdev(major,"first_Led",&first_Led_Fops); return 0; } //第三步:谁去调用上面这个函数?用一个宏来修饰一下上面这个函数。 //我的理解:下面这个宏就是在给Linux系统上电初始化的时候 ,调用 会first_Led_Init函数。 //一个 Linux设备中 肯定有很多需要初始化的函数,那每次都要 修改初始化的代码才能要把初始化加进去,所以Linux就 //写了一个宏,在编译 的时候,把要初始化的函数编译进去(不知道先后顺序是怎么样的),2017年5月17日18:42:22,所长 module_init(first_Led_Init); //前三步的笔记: APP 在利用open("/dev/xxx")中的xxx包含一个字符设备属性,一个major,一个mior //内核根据APP 提供的 字符设备属性+major(主设备号)就能找到我们注册进去first_Led_Fops这个结构体包含的三个函数。 //APP 会根据 提供的是 字符设备属性 因此去 字符设备属性的数组中 查找 major 的位置 ,根据这个位置 first_Led_Fops //提供的 函数指针运行相应的函数。2017年5月17日18:44:10,所长。 //第四步:编写驱动的出口函数 ,调用上面的 结构体,向内核申请删除 上面三个函数的 存在,在内核中。 int __init first_Led_Exit( void ) { printk( KERN_DEBUG "first Led Exit!\r\n"); //调用 unregister_chrdev //参数1:major 表示主设备号 //参数2:name //我的理解是: //unregister_chrdev这个函数作用把 file_operations 结构 类型 从 字符设备数组中删除掉,数组位置是 major unregister_chrdev(major,"first_Led"); return 0; } //第五步:和入口函数一样 用宏来修饰 出口函数 module_exit(first_Led_Exit); //第五步总结:module_exit会定义一个结构体,会把first_Led_Exit赋值给这个结构体中的一员,当要卸载驱动程序的时候 //即运行出口函数的时候,Linux内核就会自动调用结构体中的 first_Led_Exit这个函数。。暂时不理解,2017年5月17日19:03:30,所长
驱动的 包含的 头文件 和 应用层包含的头文件 不是一样的。
GCC 编译的话 头文件路径 是 /usr/include
驱动源码属于内核源代码的一部分,因此头文件路径 应该是内核源代码的路径。 源代码下 include 下 的目录。
参考链接:http://www.cnblogs.com/fanzhidongyzby/p/3730131.html
重点 来了 : Makefile 的 编写,包括4部分内容,如下:
第一部分: KERN_DIR 和 KERN_VER 两个变量的定义。
KERN_DIR 表示 内核源码树的目录。
KERN_VER
第二部分: obj -m += module_First_Led.o
这句话的 意思:就是 把 对应 的 module_First_Led.c 添加到编译目标里面去。 -m 的 意思就是 把 这个 目标编译成 一个 模块(module),-y表示把module_First_Led.c编译,静态链接进目标文件去。
第三部分:all:
make -C $( KERN_DIR ) M='pwd' modules
这句话的 意思:这个命令实际编译模块。当在shell下使用 make,就会执行这句话。 make -C 表示进入某个目录下去编译,$( KERN_DIR )表示 一个变量,M=`pwd`
表示 Makefile用一个变量 M 记录当前的 目录,记住 pwd应该用 键盘数字1 左边 的那个符号 来包括``。modules 解释,这句 命令的的本质 就是 make modules ,其他 的都是参数 (路径等等),实际编译模块!
工作原理:make -C 进入到指定的源码目录下借用内核源码中定义的模块编译规则去编译这个模块,并把编译的文件 复制 到 当前目录下。
另一个解释:该命令是make modules命令的扩展,-C选项的作用是指将当前的工作目录转移到制定的 目录,即(KDIR)目录,程序到(shellpwd)当前目录查找模块源码,将其编译,生成.ko文件。
第四部分:.PHONY:clean
clean:
make -C $( KERN_DIR ) M='pwd' modules
.PHONY 伪目标 ,把 clean 设置为 伪目标。 clean 清除编译痕迹。。。。2017年5月19日17:20:04
Makefile 总结:
#2017年5月22日09:49:57,所长 #ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个 KERN_VER = $(shell uname -r) KERN_DIR = /lib/modules/$(KERN_VER)/build # 开发板的linux内核的源码树目录 # KERN_DIR = /root/driver/kernel obj-m += LinuxLedFirst.o all: make -C $(KERN_DIR) M=`pwd` modules .PHONY: clean clean: make -C $(KERN_DIR) M=`pwd` modules clean