版本 | 颁布日期 | 修订章节 |
---|---|---|
0.1 | 2015.08.13 | 撰写草稿 |
0.2 | 2015.12.07 | 整合字符型设备驱动 |
0.3 | 2015.12.07 | 整理文档,尚未验证驱动源码 |
0.4 | 2016.02.28 | 补充信息,整理结构关系 |
0.5 | 2016.03.13 | 验证驱动程序,正式发布 |
Linux一切皆是文件,串口、鼠标、键盘等,在内核中都体现为一个文件。如果要操作一个硬件,就通过操作其对应的文件。
1、 打开文件:不同类型文件,打开方法不一样。
2、 操作文件:不同文件读写方法不同。操作方法:读、写、定位。
3、 关闭文件。
1、 主要实现这个设备的操作方法;-------驱动编程的主要工作;
2、 把实现的文件操作方法向系统注册;------字符设备模型;
3、 给用户空间提供访问的接口 ----有dev下的设备文件;
Linux下是分层的,分为内核空间和用户空间
默认情况下:
内核空间和用户空间的比例是3:1,在Linux源码配置中可以修改
目的:保证系统安全,不允许应用程序直接访问硬件。
例:任何一个软件,具有直接控制网卡功能,只要把网卡参数设置错误,则整个系统都不能用网卡。
分层操作,如果应用软件出问题,不会影响系统
内核空间:3~4G,运行驱动程序
用户空间:0~3G,运行应用程序-----main这类程序
4、 驱动程序运行在内核空间,应用程序运行在应用空间,不能直接交流,需要驱动程序给应用程序提供访问接口;
5、 每一类设备驱动都有特定的编程框架(模型),学习驱动编写,重点是学习每一类设备的编程框架。每一种文集操作方法也是特定的编程框架;
6、 设备号。每一个设备都会有一个正整数表示的设备号。
设备号由主设备号和次设备号组成
主设备号:表示一种设备
次设备号:表示一种设备中的具体哪一个
例:4个串口驱动都是相同的,(寄存器空间不同,但操作方法、逻辑相同)
就是主设备号相同,次设备号不同
[root@ZX20150811 /dev]# ls -l | grep ttySAC
crw-rw---- 1 root root 204, 64 Aug 12 2015 ttySAC0
crw-rw---- 1 root root 204, 65 Aug 12 2015 ttySAC1
crw-rw---- 1 root root 204, 66 Aug 12 2015 ttySAC2
crw-rw---- 1 root root 204, 67 Aug 12 2015 ttySAC3
串口:ttySAC,有4个串口,主设备号为204
现在要使用串口0
打开/dev/ttySAC0
配置波特率、数据位、停止位、校验位,就可以对串口读写操作.
关闭串口
以字节为单位,进行顺序访问的设备。
例:串口、鼠标、键盘、按键、触摸屏、LED……
有个例外:LCD,可以随机访问,因为他是一块内存来实现显示。
实际工作中最常见的也是使用最多的,这类设备一般会在dev目录下有一个对应的设备文件。
以块为单位进行访问,(可以随机访问)的设备。
块:512字节
一次可以读写一块或多块。
例子:SD卡、U盘、硬盘、eMMC、Flash等存储类设备。
网络通信设备
例:网卡、WiFi(无线网卡)
这类设备不同于字符设备和块设备,在dev目录下没有对应的设备文件,操作方法也不一样。
标准C99结构体赋值方法
struct miscdevice
{
.minor=255,
.name=”zxng”,
.fops=&misc_fops,
}
新方法解释:
1、赋值可以选其中部分进行初始化;
2、赋值与顺序无关;
3、赋值的开头是“.”加结构体成员,并以“,”结束;
4、kill编译器默认不能认识
要使kill能够识别新标准,在 C/C++选项,头文件下面的选项框上加上-c99
标准C89结构体赋值方法
struct miscdevice {
255;
“zxng”;
&misc_fops;
}
赋值只能按顺序初始化结构体成员,并且不能跳过不需要赋值的选项
文件操作集合
file_operations *fops
这个结构是一个通用结构,不针对某一项设备,它考虑的是所有字符型设备可能需要的操作方法,对具体一个设备,只需要实现一部分接口就可以了。
对于LED灯而言可以操作的有
1、 控制亮灭,------可以使用write
2、 查询状态。------可以使用read
3、 操作文件第一步是打开文件 ------可以使用open
4、 操作文件最后一步是关闭文件 ------release
file_operations结构体所包含的函数指针:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
有两个函数很特殊,open和release,他可以不实现,系统使用默认的方式实现一个,找到驱动之后,其实什么也没做,直接返回
release:不需要应用程序调用对应的调用接口,当应用程序关闭时自动调用
常用函数
read、write、open、release、mmap、unlocked、ioctl、fasync
结构体前面加static,将本次使用的操作集合限制在这个驱动模块中
misc_fops:后面的操作集合都加前缀misc,表示是这个驱动所使用的函数
static const struct file_operations misc_fops = {
.open = misc_open,
.read = misc_read,
.write = misc_write,
.release = misc_release,
};
1、 驱动就是按模块的格式进行编写;
2、 模块不一定是驱动,但驱动(代码格式)就一定是模块;
3、 普通模块基础上添加上对应的驱动模型代码,就构成了驱动。
Linux字符型设备分为三类:
1、杂项设备
2、早期的经典标准字符驱动模型
3、Linux2.6版本新出的标准字符驱动模型
写一个应用程序,通过系统调用接口,来调用驱动程序,注意,这个应用程序是在用户空间上运行的。
应用程序通过设备号来查找驱动
在Linux虚拟机下
[root@phoenix works]# man 2 open
#include
#include
#include
#include
int open(const char *pathname, int flags);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
使用open函数的操作类型
flags
O_RDONLY (只读)
O_WRONLY (只写)
O_RDWR. (可读可写)
写读写函数,分别通过flags开控制,观察结果。
例:
fd = open(DEV_NAME, O_RDWR);
close(fd);
这个函数可以不写,程序关闭时会调用close函数
1、Linux系统调用,app程序在用户空间上的调用
通过输入命令:man 2 write 查询得到
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
输入参数解释:
fd :文件描述符号,传入是open的返回值,标识一个在进程中打开的文件
buf :数据源指针
count :要写入的数据字节数量或要读出的字节数量
返回值
成功 :write返回写入的字节数量,read返回读取字节数量。
失败 :-1,并且会设置全局错误代码变量errno;
2、对应到内核驱动文件操作方法接口
通过SI工程进入file_operations结构体中得到
static ssize_t misc_write (struct file *pfile, const char __user *buff, size_t count, loff_t *off)
static ssize_t misc_read (struct file *pfile, char __user *buff, size_t size, loff_t *off)
输入参数解释
pfile :struct file每调用一次open,都会创建这个结构
buff :用户空间传递下来的数据缓冲区指针
off :是文件读写指针(当前光标所在的位置)
3、验证参数对应关系
在驱动的read和write操作中,都会用到char __user *buff的指针,
实际上在驱动中不建议直接使用const char __user *buff参数指针,因为这个指针有可能是一个非法的指针,后果是在安装驱动的时候会导致系统异常,安装驱动会有一大串代码。
非法指针会导致内核异常,只需要把应用程序write修改一下,write(fd,(void *10),1);
当运行app时候,内核异常,同于会导致出错,需要进行参数检测,修正数量。
要想安全的使用用户空间传下来的数据,必须对指针进行合法性检测,合法再去访问。
内核提供的专用函数给我们访问。
在内核驱动代码不能直接使用用户空间的指针来存取数据,需要通过专用的函数来完成数据的复制。
头文件
#include
从用户空间读取数据(应用程序传数据给内核)
copy_from_user 用在write接口
复制数据到用户空间(内核传数据给应用程序)
copy_to_user 用在read接口
这两个函数先检查指针的合法性,合法才进行复制操作
原型
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
输入参数解释:
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
to :内核空间的缓冲区指针
from :用户空间的缓冲区指针
n :要复制的字节数
返回值
成功 :返回0(没有成功复制的字节数量为0)。
失败 :返回没有成功复制的字节数量(正数)。
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
to :用户空间的缓冲区指针
from :内核空间的缓冲区指针
n :要复制的字节数
返回值
成功 :返回0(没有成功复制的字节数量为0)。
失败 :返回没有成功复制的字节数量(正数)。
虚拟地址映射
函数原型
static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
输入参数解释:
offset :要映射的物理起始地址
size :要映射的大小(例如:0x18)
返回值 :物理地址映射后得到的虚拟地址首地址
例:base_addr = (unsigned int *)ioremap (GPM4BASE, GPM4SIZE);
初始化先映射虚拟地址,通过虚拟地址来操作寄存器
关闭进程123
kill -9 123
虚拟地址释放
释放虚拟地址,映射取消
static inline void iounmap(volatile void __iomem *addr)
addr:就是使用ioremap虚拟出来的虚拟地址,
例:iounmap(base_addr);
在相反的方向使用,
如果在open使用ioremap,则要在release接口使用iounmap
完整流程,前面映射成功,后面操作失败
注册失败也要释放虚拟地址
这里编写的Makefile是针对在Linux源码外编译驱动模块的,Linux驱动的编译依赖于Linux源码,吧Linux驱动单独拿出来在外边编写程序,但当驱动编译成机器码时就需要依赖于Linux源码,由于Linux驱动是在内核空间进行操作的,所以对驱动对于系统的安全非常关键。因此在Linux源码上有可以写入作者的标识,只有标识相同的Linux内核系统以及依赖于相同标识的Linux源码编译出来的驱动才能够正常安装,也就是别的Linux驱动的机器码不能安装到自己的Linux系统上,只有拿到该驱动的源码,将源码依赖于自己Linux源码进行再次编译,才能够安装驱动。
在make menuconfig的配置中有自定义的标识
下面是一个常规的编译驱动模块的Makefile模板。
#需要编译的目标
obj-m += misc_zxled.o
#Linux源码
#编译基于X86所在目录
#KDIR := /lib/modules/$(shell uname -r)/build
#编译基于ARM所在目录,该目录对应Linux源码
KDIR := /works/linux-3.5/
all:
#编译驱动模块
make -C $(KDIR) M=$(PWD) modules
#如果需要的话,编译app程序
arm-linux-gcc app.c -o app
#删除编译产生的临时文件,前面加@则是在终端不打印输出
@rm -f *.o *.mod.o *.mod.c *.symvers *.markers *.unsigned *.order *~ *.bak
#复制驱动以及应用程序到相应的根文件系统目录,也可以手动将文件复制
cp *.ko app /work/root_nfs/home
clean:
make -C $(KDIR) M=`pwd` modules clean
rm -f app
[root@phoenix misc]# ls
app.c Makefile misc_zxled.c
[root@phoenix misc]# make
make -C /works/linux-3.5/ M=/mnt/hgfs/share/workspace/misc modules
make[1]: Entering directory `/works/linux-3.5'
CC [M] /mnt/hgfs/share/workspace/misc/misc_zxled.o
Building modules, stage 2.
MODPOST 1 modules
CC /mnt/hgfs/share/workspace/misc/misc_zxled.mod.o
LD [M] /mnt/hgfs/share/workspace/misc/misc_zxled.ko
make[1]: Leaving directory `/works/linux-3.5'
arm-linux-gcc app.c -o app
[root@phoenix misc]# ls
app app.c Makefile misc_zxled.c misc_zxled.ko
[root@phoenix misc]# make clean
make -C /works/linux-3.5/ M=`pwd` modules clean
make[1]: Entering directory `/works/linux-3.5'
LD /mnt/hgfs/share/workspace/misc/built-in.o
CC [M] /mnt/hgfs/share/workspace/misc/misc_zxled.o
Building modules, stage 2.
MODPOST 1 modules
CC /mnt/hgfs/share/workspace/misc/misc_zxled.mod.o
LD [M] /mnt/hgfs/share/workspace/misc/misc_zxled.ko
CLEAN /mnt/hgfs/share/workspace/misc/.tmp_versions
CLEAN /mnt/hgfs/share/workspace/misc/Module.symvers
make[1]: Leaving directory `/works/linux-3.5'
rm -f app
[root@phoenix misc]# ls
app.c Makefile misc_zxled.c
[root@phoenix misc]#