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 ...
随机推荐
- MacBookPro磁盘空间不够
256G的SSD还是快被占满了,剩余12G,本来一切运行正常. 要往U盘里拷点资料,突然电脑就罢工了,cleanMyMac 显示磁盘容量剩余 1.8G. finder 罢工,无法重启,无法强退. 无法 ...
- python基础5 while循环
一.while循环: while 条件: 代码块 例: n=0 while n<10: print(n) n=n+1 #n自加1 ,满足n<10,继续循环 输出结果: 1 2 3 4 ...
- 学习Makefile
1> 编译一个文件2> 编译多个文件3> 编译多个目录下的文件4> inclue makefile5> 使用规则1>target:depend[tab]cmddep ...
- spring boot错误: 找不到或无法加载主类
一:当在eclipse启动spring boot项目时出现问题: springboot错误: 找不到或无法加载主类 解决办法: 1,通过cmd命令行,进入项目目录进行,mvn clean instal ...
- -webkit-box-orient: vertical; 在webpack上失效
-webkit-box-orient: vertical;在webpack上失效,可以使用以下方式解决 .ifc-header-content-comment { text-overflow: ell ...
- 怎么eclipse或MyEclipse中添加javaSe的源码
怎么eclipse或MyEclipse中添加javaSe的源码 有时在eclipse里我们调用java提供给我们的方法,我们有时需要查看java提供给我们的调用方法的源码或java提供给我们的核心类的 ...
- Windows 10安装Docker 步骤及顺序
最近在工作中,重新安装Docker时,遇到了一点坑,故将自己解决经验分享一下~ Hardware assisted virtualization and data execution protecti ...
- 在C#中GUID生成的四种格式
var uuid = Guid.NewGuid().ToString(); // 9af7f46a-ea52-4aa3-b8c3-9fd484c2af12 var uuidN = Guid.NewGu ...
- PHP菜鸟如何开始学习PHP语言
把我自己学习PHP的经验分享出来,既给想学习PHP的朋友提供一个思路,也算是整理一下自己的思路,好给后续的教程开个头吧~ 学习其实也是有方法的,举个例子:在您上学期间,班里一定有学霸,也有学渣,也有普 ...
- SRD_PreloaderCore
预加载 Preloader CoreVersion 1.10SumRndmDde This plugin requires the Game Upgrade plugin:http://sumrndm ...