驱动

  • 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、驱动的概念

linux vfs驱动_内核

1.2、驱动的定义与功能

  1. 计算机系统中存在着大量的设备,操作系统要求能够控制和管理这些硬件,而驱动就是帮助操作系统完成这个任务。
  2. 驱动相当于硬件的接口,它直接操作、控制着我们的硬件,操作系统通过驱动这个接口才能管理硬件。

1.3、驱动程序与应用程序的区别

  1. 驱动程序本身也是代码,但与应用程序不同,它不会主动去运行,而是被调用。这调用者就是应用程序。
  2. 驱动与应用是服务与被服务的关系。驱动是为应用服务的。因为应用程序很多时候需要用到硬件设备,但不能直接操作硬件设备,所以通过系统调用陷入内核调用驱动,从而操作硬件。
  3. 应用与驱动程序在系统中所处位置不同,决定了它们代码运行模式也不一样。
应用程序运行在用户空间(用户态)。
 驱动代码运行于内核空间(内核态)。

linux vfs驱动_操作系统_02

1.4、应用/库/内核/驱动

linux vfs驱动_操作系统_03

  1. 应用程序调用函数库完成一系列功能,一部分库函数通 过系统调用由内核完成相应功能,例如:printf、fread函数 等等。
  2. 内核处理系统调用,内核在实现系统调用时会根据需要 调用设备驱动程序操作硬件。
  3. 设备驱动是硬件设备的直接控制者,它完成了内核和硬 件的通信任务。
  • 库属于用户态,驱动属于内核态,所以驱动无法 使用标准C库里面的函数。如:eg.printf() / sprintf() / strlen()…
  • 内核实现了大部分常用的函数,供驱动使用。有些函数名尽管相同,实现方式是不一样的。如:eg.printk() / sprintf() / strlen()…

2、Linux驱动

在linux世界里面,驱动可分为三 大类:

  • 字符设备
  • 块设备
  • 网络设备

2.1、字符设备

  1. I/O传输过程中以字符为单位进行传输。
  2. 用户对字符设备发出读/写请求时,实际的硬件读/写操作一般紧接着发生。

2.2、块设备

  1. 块设备与字符相反,它的数据传输以块(内存缓冲)为单位传输。
  2. 用户对块设备读/写时,硬件上的读/写操作不会紧接着发生,即用户请求和硬件操作是异步的。
  3. 磁盘类、闪存类等设备都封装成块设备。

2.3、网络设备

网络设备是一类特殊的设备,它不像字符设备或块设备那
样通过对应的设备文件访问,也不能直接通过read或write 进行数据请求,而是通过socket接口函数进行访问。

2.4、设备文件

  • 设备文件存放于/dev目录下,可以用ls -l或ll查看
  • 每个设备文件都有其文件属性,属性包括:
设备类型(首字母 c=字符设备 b=块设备)
 主/从设备号

linux vfs驱动_内核_04

  • linux把设备抽象成文件,“一切设备皆文件”。所以对硬件的操作全部抽象成对文件的操作。
  • 驱动是硬件的最直接操作者,设备文件是用户程序与设备驱动的一个接口,应用程序通过操作设备文件来调用设备驱动程序。

3、应用程序如何通过设备文件找到设备驱动?

  1. 主设备号:用于标识驱动程序,主设备号一样的设备文件将使用同一类驱动程序。(1-254)
  2. 从设备号:用于标识使用同一驱动程序的不同具体硬件。(0-255)
例如:6818开发板中的串口设备,主设备号标识串口这类设备,从设备号标识具体的某个串口。
 
 cat /proc/devices命令可以查看当前系统中 主设备号的使用情况和其对应的硬件设备。

4、linux模块编程

linux内核整体结构非常庞大,包含的组件非常多。我 们怎样选择性的把需要的部分包含在内核中呢?
Linux内核抛弃把所有功能模块都编译到内核的做法,采用 了模块化的方法将各组件灵活添加和删减,并且驱动模块还可
以动态加载、删除。

使用模块的好处:

  1. 内核体积小:不需要的组件可以不编入内核
  2. 开发灵活:模块可以同普通软件一样,从内核中添加或删除
  3. 平台无关、节省内存…

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、模块加载函数

  1. 完成相关资源申请、硬件初始化以及驱动的注册
  2. 若初始化成功返回0,失败返回错误值
  3. 模块加载函数必须以"module_init(函数名)"形式进行声明

5.2、模块卸载函数:

  1. 释放已申请资源、注销驱动
  2. 在模块卸载时被执行,不返回任何值
  3. 函数需要以"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内核模块的编译方法有两种:

  1. 放入Linux内核源码中编译
  2. 采用独立的方法编译模块

5.4、放入Linux内核源码中编译

  1. 将写好的模块放入Linux内核任一目录下
  2. 修改相应目录下的Kconfig和Makefile文件
  3. 执行make modules
  4. 会在相同目录下生成与源文件同名的.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(跟当前内核版本同名)