驱动
- 1、驱动的概念
- 1.1、驱动的概念
- 1.2、驱动的定义与功能
- 1.3、驱动程序与应用程序的区别
- 1.4、应用/库/内核/驱动
- 2、Linux驱动
- 2.1、字符设备
- 2.2、块设备
- 2.3、网络设备
- 2.4、设备文件
- 3、应用程序如何通过设备文件找到设备驱动?
- 4、linux模块编程
- 使用模块的好处:
- 5、写一个简单的模块
- 5.1、模块加载函数
- 5.2、模块卸载函数:
- 5.3、Linux内核模块的编译方法有两种:
- 5.4、放入Linux内核源码中编译
- 5.5、Linux内核模块的使用:
- 注意事项:
1、驱动的概念
1.1、驱动的概念
1.2、驱动的定义与功能
- 计算机系统中存在着大量的设备,操作系统要求能够控制和管理这些硬件,而驱动就是帮助操作系统完成这个任务。
- 驱动相当于硬件的接口,它直接操作、控制着我们的硬件,操作系统通过驱动这个接口才能管理硬件。
1.3、驱动程序与应用程序的区别
- 驱动程序本身也是代码,但与应用程序不同,它不会主动去运行,而是被调用。这调用者就是应用程序。
- 驱动与应用是服务与被服务的关系。驱动是为应用服务的。因为应用程序很多时候需要用到硬件设备,但不能直接操作硬件设备,所以通过系统调用陷入内核调用驱动,从而操作硬件。
- 应用与驱动程序在系统中所处位置不同,决定了它们代码运行模式也不一样。
应用程序运行在用户空间(用户态)。
驱动代码运行于内核空间(内核态)。
1.4、应用/库/内核/驱动
- 应用程序调用函数库完成一系列功能,一部分库函数通 过系统调用由内核完成相应功能,例如:printf、fread函数 等等。
- 内核处理系统调用,内核在实现系统调用时会根据需要 调用设备驱动程序操作硬件。
- 设备驱动是硬件设备的直接控制者,它完成了内核和硬 件的通信任务。
- 库属于用户态,驱动属于内核态,所以驱动无法 使用标准C库里面的函数。如:eg.printf() / sprintf() / strlen()…
- 内核实现了大部分常用的函数,供驱动使用。有些函数名尽管相同,实现方式是不一样的。如:eg.printk() / sprintf() / strlen()…
2、Linux驱动
在linux世界里面,驱动可分为三 大类:
- 字符设备
- 块设备
- 网络设备
2.1、字符设备
- I/O传输过程中以字符为单位进行传输。
- 用户对字符设备发出读/写请求时,实际的硬件读/写操作一般紧接着发生。
2.2、块设备
- 块设备与字符相反,它的数据传输以块(内存缓冲)为单位传输。
- 用户对块设备读/写时,硬件上的读/写操作不会紧接着发生,即用户请求和硬件操作是异步的。
- 磁盘类、闪存类等设备都封装成块设备。
2.3、网络设备
网络设备是一类特殊的设备,它不像字符设备或块设备那
样通过对应的设备文件访问,也不能直接通过read或write 进行数据请求,而是通过socket接口函数进行访问。
2.4、设备文件
- 设备文件存放于/dev目录下,可以用ls -l或ll查看
- 每个设备文件都有其文件属性,属性包括:
设备类型(首字母 c=字符设备 b=块设备)
主/从设备号
- linux把设备抽象成文件,“一切设备皆文件”。所以对硬件的操作全部抽象成对文件的操作。
- 驱动是硬件的最直接操作者,设备文件是用户程序与设备驱动的一个接口,应用程序通过操作设备文件来调用设备驱动程序。
3、应用程序如何通过设备文件找到设备驱动?
- 主设备号:用于标识驱动程序,主设备号一样的设备文件将使用同一类驱动程序。(1-254)
- 从设备号:用于标识使用同一驱动程序的不同具体硬件。(0-255)
例如:6818开发板中的串口设备,主设备号标识串口这类设备,从设备号标识具体的某个串口。
cat /proc/devices命令可以查看当前系统中 主设备号的使用情况和其对应的硬件设备。
4、linux模块编程
linux内核整体结构非常庞大,包含的组件非常多。我 们怎样选择性的把需要的部分包含在内核中呢?
Linux内核抛弃把所有功能模块都编译到内核的做法,采用 了模块化的方法将各组件灵活添加和删减,并且驱动模块还可
以动态加载、删除。
使用模块的好处:
- 内核体积小:不需要的组件可以不编入内核
- 开发灵活:模块可以同普通软件一样,从内核中添加或删除
- 平台无关、节省内存…
5、写一个简单的模块
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
/*模块加载函数*/
int __init xxx_module_init (void) { ... }
/*模块卸载函数*/
void __exit xxx_module_exit (void) { ... }
/*声明模块加载函数宏*/
module_init(xxx_module_init);
/*声明模块卸载函数宏*/
module_exit(xxx_module_exit);
/*声明模块作者*/
MODULE_AUTHOR(“qfedu”);
/*模块许可证明,描述内核模块的许可权限*/
MODULE_LICENSE("GPL");
5.1、模块加载函数
- 完成相关资源申请、硬件初始化以及驱动的注册
- 若初始化成功返回0,失败返回错误值
- 模块加载函数必须以"module_init(函数名)"形式进行声明
5.2、模块卸载函数:
- 释放已申请资源、注销驱动
- 在模块卸载时被执行,不返回任何值
- 函数需要以"module_exit(函数名)"的形式进行声明
案例:加载或卸载模块时打印行号 和函数名
Makefil
obj-m := driver.o
KERNELDIR = /home/ltl/share/tools/kernel-3.4.39
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
rm -rf *.o *.mod.c *.order *.symvers
clean:
rm -rf *.ko
driver.c
#include <linux/module.h>
int __init jiazai_init(void)
{
printk(KERN_ERR "L%d:%s\n",__LINE__,__FUNCTION__);
return 0;
}
void __exit tuichu_exit(void)
{
printk(KERN_ERR "L%d:%s\n",__LINE__,__FUNCTION__);
}
module_init(jiazai_init);//加载时被执行
module_exit(tuichu_exit);//退出被调用
MODULE_LICENSE("GPL"); //同意GPL协议
5.3、Linux内核模块的编译方法有两种:
- 放入Linux内核源码中编译
- 采用独立的方法编译模块
5.4、放入Linux内核源码中编译
- 将写好的模块放入Linux内核任一目录下
- 修改相应目录下的Kconfig和Makefile文件
- 执行make modules
- 会在相同目录下生成与源文件同名的.ko文件
5.5、Linux内核模块的使用:
编译出.ko为后缀模块文件
smod 列举当前系统中的所有模块
insmod xxx.ko 加载指定模块到内核
rmmod xxx 卸载指定模块(不需要.ko后缀)
注意事项:
若卸载时出现以下提示:
rmmod:chdir(3.4.39): No such file or directory
在开发板根文件系统下建立以下目录:
/lib/modules/3.4.39(跟当前内核版本同名)