Linux驱动
1 驱动分类
常规分类:字符设备、块设备、网络设备
字符设备:一种按字节来访问的设备,字符驱动负责驱动字符设备,这样的驱动通常实现open、close、read和write系统调用。如串口、LED、按键;
块设备:以块(一般为512字节)为最小传输单位的设备,块设备不能按字节处理数据。在Linux系统中运行块设备传输任意数目的字节。块设备与字符设备的区别是驱动与内核的接口不同。如硬盘、flash、SD卡。
网络设备:可以是一个硬件设备,如网卡;也可以是一个纯粹的软件设备,如回环接口(lo)。一个网络接口负责发送和接收数据报文。
总线分类:USB设备、PCI设备、平台总线设备
2 硬件访问
驱动程序要控制设备是通过设备内寄存器控制的。
硬件访问步骤:->地址映射->寄存器读写
在Linux系统中,无论是内核程序还是应用程序,都只能使用虚拟地址,而芯片手册中给出的寄存器地址或者RAM地址则是物理地址,无法直接使用。因此,读写寄存器的第1步就是将它的物理地址映射为虚拟地址。
地址映射包括动态映射和物理映射;
动态映射:在驱动程序中采用ioremap()函数将物理地址映射为虚拟地址:
函数原型: void *ioremap(physaddr,size)
physaddr:待映射的物理地址
size:映射的区域长度
返回值:映射后的虚拟地址
静态映射:根据用户事先指定的映射关系,在内核启动时,自动将物理地址映射为虚拟地址。
用户是通过map_desc结构体来指明物理地址与虚拟地址的映射关系。
struct map_desc{
unsigned long virtual; /* 映射后的虚拟地址 */
unsigned long pfn; /* 物理地址所在的页帧号 */
unsigned long length; /* 映射长度 */
unsigned int type; /* 映射的设备类型 */
};
pfn: 利用__phys_to_pfn(物理地址)可以计算出物理地址所在的物理页帧号
内核寄存器读写函数:
unsigned ioread8(void *addr0)
unsigned ioread16(void *addr0)
unsigned ioread32(void *addr0) unsigned readb(address)
unsigned readw(address)
unsigned readl(address) void iowrite8(u8 value,void *addr)
void iowrite16(u16 value,void *addr)
void iowrite32(u32 value,void *addr) void writeb(unsigned value,address)
void writew(unsigned value,address)
void writel(unsigned value,address)
3 字符设备文件
字符设备驱动程序是通过字符设备文件被用户调用。
通过字符设备文件,应用程序可以使用相应的字符设备驱动程序来控制字符设备。
应用程序首先通过文件名找到字符设备文件,假如要从设备中读出或者写入数据都是从字符设备文件展开的,字符设备文件是设备驱动程序和应用程序的一个媒介,应用程序对设备的操作是通过字符设备文件来完成的。
创建字符设备文件:
mknod命令
mknod /dev/文件名 c 主设备号 次设备号 //c表示char
例:mknod /dev/memdev0 c 253 0
字符设备文件与驱动程序通过主设备号建立起联系,字符设备文件对应一个主设备号,驱动程序对应一个主设备号,如果两个号相等说明两种之间是一一对应的关系。
当用户去操作设备的时候内核就会找到相应的驱动程序。
通过cat /proc/devices打印主设备号
次设备号0~255
ls /dev/memdev0 //查看
显示:/dev/memdev0
有了字符设备文件,也有了设备驱动程序,就要编写应用程序。就是通过字符设备文件访问设备驱动程序。
要访问硬件,其实就是访问硬件里的寄存器,假如定义一个数组,数组里头有5个整型的元素,每一个整型元素就可以模拟一个寄存器,要去操作硬件,最后可以变为操作数组,比如要把数据写入寄存器,实际上就变成往数组里头写入数据。通过驱动程序往数组里头写入数据。如果要从设备里头读出数据,通过驱动程序从数组里头读出数据。
4 字符设备驱动实例
驱动程序通常采用内核模块的程序结构来进行编码。因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块。
驱动程序文件包含:memdev.c、Makefile;应用程序文件包含:write_mem.c、read_mem.c;
1 通过命令make,编译驱动程序得到文件memdev.ko,并将其拷贝到nfs开发板挂载的目录,然后安装驱动程序
insmod memdev.ko 安装memdev.ko
lsmod 查看驱动
2 使用命令查看设备号:cat /proc/devices
3 创建字符设备文件:
mknod /dev/memdev0 c 253 0 //名字不能跟已有的重复
4 查看创建的设备字符文件
ls /dev/memdev0
显示: /dev/memdev0
5 编译应用程序write_mem.c
arm-linux-gcc write_mem.c -o write_mem
运行:./write_mem
报错:-/bin/sh: ./write_mem:not found (应用程序依赖的库找不到)
通过:arm-linux-readlef -d write_mem 查询应用程序所依赖的动态链接库
通过ls命令查看,开发板中没有这个库,所以报错;
解决办法:
1 直接将libc.so.6复制到开发板中
2 采样静态编译的方法
arm-linux-gcc -static write_mem.c -o write_mem
采用静态编译后运行:./write_mem
6 编译应用程序read_mem.c
arm-linux-gcc -static read_mem.c -o read_mem
运行:./read_mem
结果:dst is 2013
5 字符设备驱动程序模板
1 设备描述结构
在任何一种驱动模型中,设备都会用内核中的一种结构来描述。我们的符设备在内核中使用struct cdev来描述。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; //设备操作集
struct list_head list;
dev_t dev; //设备号
unsigned int count; //设备数
};
查看设备号:ls –l dev下都是设备文件
eg:crw-r----- 1 root root 10, 223 12月 15:00.10 uinput
10:主设备号 223:次设备号
主设备号反映设备类型,次设备号区分同类型设备
设备号操作:
Linux内核中使用dev_t类型来定义设备号,dev_t这种类型其实质为32位的unsigned int,其中高12位为主设备号,低20位为次设备号.
问1:如果知道主设备号,次设备号,怎么组合成dev_t类型
答:dev_t dev = MKDEV(主设备号,次设备号)
问2: 如何从dev_t中分解出主设备号?
答: 主设备号 = MAJOR(dev_t dev)
问3: 如何从dev_t中分解出次设备号?
答: 次设备号=MINOR(dev_t dev)
设备号分配:静态申请和动态分配
静态申请:
开发者自己选择一个数字作为主设备号,然后通过函数register_chrdev_region向内核申请使用。缺点:如果申请使用的设备号已经被内核中的其他驱动使用了,则申请失败。
动态分配:
使用alloc_chrdev_region由内核分配一个可用的主设备号。优点:因为内核知道哪些号已经被使用了,所以不会导致分配到已经被使用的号。
设备号注销
不论使用何种方法分配设备号,都应该在驱动退出时,使用unregister_chrdev_region函数释放这些设备号。
2 操作函数集
Struct file_operations是一个函数指针的集合,定义能在设备上进行的操作。结构中的函数指针指向驱动中的函数, 这些函数实现一个针对设备的操作, 对于不支持的操作则设置函数指针为 NULL。例如:
https://blog.csdn.net/littlelee111/article/details/10133759
struct file_operations dev_fops ={
.llseek = NULL,
.read = dev_read,
.write = dev_write,
.ioctl = dev_ioctl,
.open = dev_open,
.release = dev_release,
};
3 字符设备初始化
分配cdev:可以采用静态和动态两种办法
静态分配:
struct cdev mdev;
动态分配:
struct cdev *pdev = cdev_alloc();
初始化cdev:
structcdev的初始化使用cdev_init函数来完成。
cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数:
cdev: 待初始化的cdev结构
fops: 设备对应的操作函数集
注册cdev:
注册使用cdev_add函数来完成。
cdev_add(structcdev *p, dev_t dev, unsigned count)
参数:
p: 待添加到内核的符设备结构
dev: 设备号
count: 该类设备的设备个数
4 设备操作原型
int (*open)(struct inode *, struct file *) 打开设备,响应open系统
int (*release)(struct inode *, struct file *);关闭设备,响应close系统调用
loff_t (*llseek)(struct file *, loff_t, int);重定位读写指针,响应lseek系统调用
ssize_t (*read)(struct file *,char __user *,size_t,loff_t *)从设备读取数据,响应read系统调用
ssize_t(*write)(struct file*,const char __user*,size_t,loff_t*)向设备写入数据,响应write系统调用
struct file
在Linux系统中,每一个打开的文件,在内核中都会关联一个struct file,它由内核在打开文件时创建, 在文件关闭后释放。
重要成员:
loff_t f_pos /*文件读写指针*/
struct file_operations *f_op /*该文件所对应的操作*/
struct inode
每一个存在于文件系统里面的文件都会关联一个inode 结构,该结构主要用来记录文件物理上的信息。因此, 它和代表打开文件的file结构是不同的。一个文件没有被打开时不会关联file结构,但是却会关联一个inode 结构。
重要成员:
dev_t i_rdev:设备号
设备操作open
open设备方法是驱动程序用来为以后的操作完成初始化准备工作的。在大部分驱动程序中,open完成如下工作:
标明次设备号
启动设备
设备操作release
release方法的作用正好与open相反。这个设备方法有时也称为close,它应该:关闭设备。
设备操作read
read设备方法通常完成2件事情:
从设备中读取数据(属于硬件访问类操作)
将读取到的数据返回给应用程序
ssize_t (*read) (struct file *filp, char __user *buff, size_t count, loff_t *offp)
参数分析:
filp:与符设备文件关联的file结构指针, 由内核创建。
buff : 从设备读取到的数据,需要保存到的位置。由read系统调用提供该参数。
count: 请求传输的数据量,由read系统调用提供该参数。
offp: 文件的读写位置,由内核从file结构中取出后,传递进来
buff参数是来源于用户空间的指针,这类指针都不能被内核代码直接引用,必须使用专门的函数
int copy_from_user(void *to, const void __user *from, int n)
int copy_to_user(void __user *to, const void*from, intn)
设备操作write
write设备方法通常完成2件事情:
从应用程序提供的地址中取出数据
将数据写入设备(属于硬件访问类操作)
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)
5 驱动注销
当我们从内核中卸载驱动程序的时候,需要使用cdev_del函数来完成符设备的注销。
Linux驱动的更多相关文章
- Linux代码的重用与强行卸载Linux驱动
(一)Linux代码的重用 重用=静态重用(将要重用的代码放到其他的文件的头文件中声明)+动态重用(使用另外一个Linux驱动中的资源,例如函数.变量.宏等) 1.编译是由多个文件组成的Linux驱动 ...
- Linux驱动学习之常用的模块操作命令
1.常用的模块操作命令 (1)lsmod(list module,将模块列表显示),功能是打印出当前内核中已经安装的模块列表 (2)insmod(install module,安装模块),功能是向当前 ...
- Linux驱动学习之驱动开发准备工作
一.开启驱动开发之路 1.驱动开发的准备工作 (1)正常运行linux系统的开发板.要求开发板中的linux的zImage必须是自己编译的,不能是别人编译的.原因在于在安装模块的时候会进行安全性校验 ...
- Linux驱动学习之什么是驱动?
一.什么是驱动? 1: 驱动一词的字面意思 2: 物理上的驱动 3: 硬件中的驱动 4: linux内核驱动.软件层面上的驱动广义上是指:这一段代码操作了硬件去动,所以这一段代码就叫硬件的驱动程序. ...
- 嵌入式Linux驱动开发日记
嵌入式Linux驱动开发日记 主机硬件环境 开发机:虚拟机Ubuntu12.04 内存: 1G 硬盘:80GB 目标板硬件环境 CPU: SP5V210 (开发板:QT210) SDRAM: 512M ...
- linux驱动程序设计的硬件基础,王明学learn
linux驱动程序设计的硬件基础(一) 本章讲总结学习linux设备程序设计的硬件基础. 一.处理器 1.1通用处理器 通用处理器(GPP)并不针对特定的应用领域进行体系结构和指令集的优化,它们具有一 ...
- linux驱动初探之杂项设备(控制两个GPIO口)
关键字:linux驱动.杂项设备.GPIO 此驱动程序控制了外接的两个二极管,二极管是低电平有效. 上一篇博客中已经介绍了linux驱动程序的编写流程,这篇博客算是前一篇的提高篇,也是下一篇博客(JN ...
- linux驱动初探之字符驱动
关键字:字符驱动.动态生成设备节点.helloworld linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~ 此驱动程序基于linux3. ...
- Linux驱动之HelloWorld
最近看android的一些源码,里面有一些功能是用驱动实现的.于是就兴起看了一些驱动相关的东西,准备日后深入.这没有技术含量的水文,仅作为日后的备忘吧. 系统使用的是ubuntu 12.0.04,内核 ...
- linux 驱动学习笔记01--Linux 内核的编译
由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make confi ...
随机推荐
- [Day18]集合框架Collection、迭代器、增强for循环以及泛型
1.集合 1.1集合-本身是一个存储的容器 集合类的基本接口是Collection接口,这个接口有两个基本方法 (1)boolean add(E element) 用于向集合中添加元素,如果添加元素确 ...
- JDBC 查询
//查询""SMITH"的empno import java.sql.Connection; import java.sql.DriverManager; import ...
- 2019-04-25t16:19:49 转成正常的年月日
1.首先得到的值时2019-04-25t16:19:49 2.想转成2019-04-25 3. var d = new Date(2019-04-25t16:19:49); var yy = d.ge ...
- AWS deepracer
0.安装 坑很多,Ubuntu16.04上安python3,gazebo9,各种包,最后在python2下roslaunch,参见我爱豆的github: https://github.com/exit ...
- group by 用法解析
group by 用法解析 group by语法可以根据给定数据列的每个成员对查询结果进行分组统计,最终得到一个分组汇总表. SELECT子句中的列名必须为分组列或列函数.列函数对于GROUP BY子 ...
- ADB——keyevent命令
基本格式 adb shell input keyevent xxx # xxx为具体操作对应的数字 keycode 官方 KEYCODE 链接:戳这里 0 KEYCODE_UNKNOWN 未知按键 1 ...
- Spark SQL 编程初级实践
一.实验目的 (1) 通过实验掌握 Spark SQL 的基本编程方法: (2) 熟悉 RDD 到 DataFrame 的转化方法: (3) 熟悉利用 Spark ...
- element-ui+vue-treeselect校验
element-ui+vue-treeselect下拉框的校验 问题陈述: 在element-ui中有自带的表单验证,但是使用的vue-treeselect无法验证 vue-treeselect DE ...
- python安装画图模块pillow
步骤一: install pillow (注意导入是 import PIL ) 步骤二:如果pycharm中import选择不到,则需要在settings中导入下 ...
- apache24虚拟安装
1.进入Apache的conf目录 2.打开httpd.conf文件输入: 2.1:查找<IfModule alias_module> 2.2: 在 ScriptAlias ...