国嵌驱动笔记文档格式.docx
《国嵌驱动笔记文档格式.docx》由会员分享,可在线阅读,更多相关《国嵌驱动笔记文档格式.docx(16页珍藏版)》请在冰豆网上搜索。
Structinode记录文件的物理上的信息(设备号等),一个文件可以有多个file,但只有一个inode
Structfile_operation*f_op函数指针的集合
structfile_operationsmem_fops={
.owner=THIS_MODULE,
.llseek=mem_seek,
.read=mem_read
…….
}
读内核代码应用程序怎样访问驱动程序(read_write,c)
系统条用read找到vfs_read根据file结构中找到file_operations中的read
字符设备使用structcdev来描述
字符设备注册可分为如下3个部分:
1.分配cdevstructcdev*cdev_alloc(void)
2.初始化cdevcdev_init(structcedev*p,conststructfile_operations*fops)
3.添加cdevcdev_add(structcdev*p,dev_tdev,unsignedcount)dev设备号设备号的数目
字符设备的注销:
cdev_dev(structcdev*p)
设备操作
int(*open)(struct*inode,structfile*)
如果该项为NULL,设备打开永远成功
void(*release)(struct*inode,structfile*)
ssize_t(*read)(structfile*,char__user*buff,size_t,loff_t*)
ssize_t(*write)(structfile*,char__user*,size_t,loff_t*)
file是文件指针(来与内核),*buff是数据缓冲(用户空间),count传输的数据量(用户空间),offp访问位置(来与内核)
*buff是用户空间的,不能直接使用
intcopy_from_user(void*to,constvoid__user*from,intn)
intcopy_to_user(void__user*to,constvoid*to,intn)
loff_tllseek(struct*file,loff_toffset,intwhence)
open方法主要完成如下工作:
1.初始化设备2.标明此设备号
在open(structinode*inode,structfile*filp)函数可使用MINOR(inode->
i_rdev;
获取此设备号
filp->
private_data=dev;
将设备描述指针赋值给私有文件指针,区分出了那种设备
在read()函数使用structmem_dev*dev=filp->
private_data可根据私有文件指针指定找到具体的设备,read函数参数没有inode,无法获取此设备号。
kmalloc分配内存,返回地址,根据其返回的地址就可操作内存中的数据
copy_to_user(buf,(void*)(dev->
data+p),count)这里的(dev->
data+p)为什么要用(void*)强制转换呢?
copy_to_user是这么定义的intcopy_to_user(void__user*to,constvoid*to,intn),但还是不理解。
驱动程序调试分类:
打印调试,调试器调试(kgdb),查询调试(proc文件系统)
合理的使用printk可以全局的打开或关闭它们。
并发:
多个执行单元同时被执行
竟态:
并发的执行单元对共享资源(硬件资源或全局变量等)的共享访问
通过semaphore机制和spin_lock机制实现
获取信号量不成功该阻塞或者睡眠
1.定义信号量structsemaphoresem;
2.初始化信号量voidsema_init(structsemaphore*sem,intval)初始化信号量的初值为val
3.voidinit_MUTEX(structsemaphore*sem)初始化一个互斥锁,把sem的值设为1
4.voidinit_MUTEX_LOCKED(structsemaphore*sem)初始化一个互斥锁,把sem的值设为0
定义与初始化工作可由如下宏一步完成
DECLARE_MUTEX(name)定义一个信号量,并初始化为1
DECLARE_MUTEX_LOCK(name)定义一个信号量,并初始化为0,为已锁状态
5.获取信号量voiddown(structsemaphore*sem)可能会导致进程睡眠,故不能在中断上下文中使用,如果sem非负直接返回,否则挂起(TASK_UNINTERRUPTIBLE),不建议使用
6.voiddown_interrruptible(structsemaphore*sem)信号量不可用,置为TASK_INTERRUPTIBLE
7.voiddown_killable(structsemaphore*sem)信号量不可用,置为TASK_KILLABLE
8.voidup(structsemaphore*sem)释放信号量
自旋锁不会引起调用者的睡眠,线程会移植忙循环,移植等待下去
1.spin_lock_init(x)初始化自旋锁
2.spin_lock(lock)获取自旋锁,不成功自旋在那
3.spin_trylock(lock)不会一直等待
4.spin_unlock
信号量可以有多个持有者(1个互斥信号量),自旋锁只有一个持有者
信号量适合保持时间较长,自旋锁适合保持时间较短
Ioctl对硬件进行控制(改变波特率,报告错误信息)
用户使用方法:
intioctl(intfd,unsignedlongcmd,…)点表示可选参数
int(*ioctl)(structinode*inode,structfile*filp,unsignedintcmd,unsignedlongarg)
cmd用户空间传下来的,arg用户传下来的参数
ioctl命令实现方法:
1.定义命令2.实现命令
Documentation/ioctl-number.txt定义了使用的幻数
ioctl被划分为几个位段,include/asm/ioctl.h定义了这些字段:
1.类型(幻数):
8位宽,属于哪一类设备
2.序号:
表明设备命令的第几个
3.传送方向:
可能的值是_IOC_NONE,_IOC_READ,_IOC_WRITE是从应用程序的观点来看的
4.参数的大小(数据的类型)
内核提供下列宏来帮助定义命令
_IO(type,nr)没有参数传递
_IOR(type,nr,datatype)从驱动中读数据
_I0W(type,nr,datatype)从数据到驱动
_IOWR(type,nr,datatype)type和number成员作为参数被传递
Ioctl函数的实现1.返回值2.参数使用3.命令操作
通常是个switch语句,不支持的返回–EINVAL
使用ioctl中的参数:
整数可以直接使用,指针则使用前需进行正确的检查
参数检查
不需要检测的函数:
copy_from_user,copy_to_user,get_user,put_user
需要检测的函数:
__get_user,__put_user
intaccess_ok(inttype,constvoid*addr,unsingnedlongsize)
第一参数是VERIFY_READ或者VERIFY_WRITE,addr是要操作的用户内存的地址,size是操作的长度。
access_ok返回一个布尔值:
1.存取没问题0.失败,如果返回失败,
则ioctl应当返回-EFAULT.
等待队列:
实现进程的阻塞,保存进程的容器,阻塞时放入等待队列,唤醒时,取出进程
1.定义等待队列wait_queue_head_tmy_queue
2.初始化等待队列wait_waitqueue_head(&
my_queue)
3.定义并初始化等待队列DECLARE_WAIT_QUEUE_HEAD(my_queue)
有条件睡眠
wait_event(queue,condition)当condition为真,返回。
当condition为假,进入TASK_UNINTERRUTIBLE睡眠,并挂在queue指定的等待队列上
wait_event_interruptible(queue,condition)
wait_event_killable(queue,conditon)
无条件睡眠(老版本,不建议使用)sleep_on(wait_queue_head_t*q)interruptible_sleep_on(wait_queue_head_t*q)
等待队列中唤醒进程
Wake_up(wait_queue_t*q)唤醒等待队列中的所有进程都唤醒
Wake_up_interruptible(wait_queue_t*q)唤醒为TASK_INTERRUPTIBLE进程
阻塞方式是文件读写的默认方式,应用程序可以使用O_NONBLOCK标志非阻塞的
设置了O_NONBLOCK,系统只是简单的返回-EAGAIN
Select系统调用对应于poll
Select用于多路监控,如没有一个文件满足要求,select将阻塞进程
Intselect(intmaxfd,fd_set*reedfds,fd_set*writefds,fd_set*exceptfds,conststructtimeval*timeout)
maxfd:
文件描述符的范围,比检测的最大文件描述符大1
Readfds:
被读监控的文件描述符
Writefds:
被写监控的
Exceptfds:
被异常监控的
Timeout:
定时器
Timeout
1.为0时不管有没有文件满足要求,立即返回,无文件满足,返回0
2.为NULL时,select将阻塞进程,直到文件满足要求为止
3.为正整数的时候,等待的最长时间,即select在timeout时间内阻塞进程
Select返回值
1.正常返回满足要求的文件描述符个数
2.没有满足的返回0
3.select被某个信号打断,返回-1,errno为EINTR
Select系统调用:
1.将要监控的文件添加到文件描述符集
2.调用select开始监控
3.判断文件是否满足要求
VoidFD_SET(intfd,fd_set*fdset)将fd添加到fdset中
VoidFD_CLR(intfd,fd_set*fdset)在fdset中清楚fd
VoidFD_ZERO(fd_set*fdset)清空fdset
VoidFD_ISSET(intfd,fd_set*fdset)检测文件描述集中的需判断的fd发生变化
驱动通常由poll实现
Unsignedint(*poll)(structfile*filp,poll_table*wait)
负责完成:
1.使用poll_wait将等待队列添加到poll_table中
2.返回描述设备是否可读可写的掩码
位掩码:
POLLIN设备可读,POLLRDNORM数据可读,POLLOUT设备可写,POLLWRNORM数据可写
设备可读通常返回(POLLIN|POLLRDNORM)
设备可写通常返回(POLLOUT|POLLWRNORM)
Poll方法只是做一个登记,真正的阻塞发生在select.c中的do_select函数(分析了do_select函数)
自动创建设备文件
2.4内核使用devfs_register(devfs_handle_tdir,constchar*name,unsignedintflags,unsignedintmajorunsignedintminor,umode_tmode,void*ops,void*info)
Dir:
目录名,name:
文件名;
flags:
创建标志;
major:
主设备号;
minor此设备号;
mode:
创建模式;
ops:
操作函数集;
info:
通常为空
从2.6.13开始,devfs不复存在,udev成为替代
使用
1.class_create为设备创建一个class,
2.使用device_create创建对应的设备
例:
structclass*myclass=class_create(THIS_MODULE,“my_device_driver”);
Device_create(myclass,NULL,MKDEV(major_num,0),NULL,“my_device”)
void*mmap(void*addr,size_tlen,intprot,intflags,intfd,off_toffset)
负责把文件内容映射到进程的虚拟内存空间,通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。
addr:
映射的起始地址,通常为NULL,由系统指定
length:
映射文件的长度
prot:
映射区的保护方式PROT_EXEC:
映射区可被执行,PROT_READ:
映射区可被读取,PROT_WRITE:
映射区可被写入
映射区的特性MAP_SHARED:
写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享。
MAP_PRIVATE:
对映射区的写入操作会产生一个映射区的复制(copy-on-write),对此区域的修改不会写回源文件。
fd:
由open返回的文件描述符,代表要映射的文件
offset:
以文件开始处的偏移量,必须是分页大小的整数倍,通常为0,表示从文件头开始映射。
注意mmap不能映像原有文件的长度
intmunmap(void*start,size_tlength)
成功返回0,失败返回-1.start的取值一般是mmap返回的地址
虚拟内存区域:
是虚拟地址空间的一个同质区间,即有同样特性的连续地址范围。
一个进程的内存映像由以下几部分组成:
程序代码,数据,BSS和栈区域,以及内存映射区域
一个进程的内存区域可以通过查看/proc/pid/maps
每一行的域为:
start_endpermoffsetmajor:
minorinode
linux内核使用结构vm_area_struct来描述虚拟内存区域,其中主要成员如下:
unsignedlongvm_start虚拟内存区域起始地址
unsignedlongvm_end虚拟内存区域结束地址
unsignedlongvm_flags
映射一个设备是指把用户空间的一段地址关联到设备内存上。
mmap做了三件事:
1.找到用户空间地址(内核完成)2.找到设备的物理地址(原理图)3.关联(通过页式管理)
mmap设备方法需要做的就是建立虚拟地址到物理地址的页表。
int(*mmap)(structfile*,structvm_area_struct*)
建立页表有两种方法:
1.使用remap_pfn_range一次建立所有页表;
2.使用nopageVMA方法每次建立一个也表。
intremap_pfn_range(structvm_area_struct*vma,unsignedlongaddr,unsignedlongpfn,unsignedlongsize,pgprot_tprot)
vma:
虚拟内存区域指针virt_addr:
虚拟地址的起始值pfn:
要映射的物理地址所在的物理页祯号,可将物理地址>
>
PAGE_SHIFT得到(PAGE_SHIFT为12,相当于除以4KB)
size:
要映射的区域的大小prot:
VMA的保护属性
硬件访问
寄存器和内存的区别:
寄存器和RAM主要不同在于寄存器操作由副作用(side
effect或边际效果):
读取某刻地址可能导致该地址内容变化,读中断状态寄存器,便自动清零
内存与IO
在X86存在IO空间(串口并口等),在32为x86
IO空间是64K,内存空间是4G,ARM,PowerPC只有内存地址空间的
IO端口:
一个寄存器或内存位于IO空间时,称为IO端口
IO内存:
一个寄存器或内存位于内存空间时,称为IO内存
对IO端口的操作需要按如下步骤完成:
1,申请2,访问3,释放
申请:
structresource*request_region(unsignedlongfirst,unsignedlongn,
constchar*name)从first开始的n个端口,name设备名字
系统中端口的分配情况记录在/proc/ioports中
访问:
intboutbintwoutwintloutl
释放:
voidrelease_region(unsignedlongstart,unsignedlongn)
IO内存有4步:
1.申请2,映射3,访问4,释放
structresource*request_mem_region(unsignedlongstart,unsignedlong
len,char
*name)从start开始,长度为len字节的内存区。
成功,返回非NULL,否则返回NULL。
可在/proc/iomem中列出
在访问IO内存之前,必须进行物理地址到虚拟地址的转化,使用下面函数
void*ioremap(unsignedlongphys_addr,unsignedlongsize)
ioread8(void*addr)iowrite8(u8value,void*addr)老版本使用readbwriteb
1,voidiounmap(void*addr)2,voidrelease_mem_region(unsignedlong
start,unsignedlonglen)
混杂设备驱动:
共享一个主设备号10,成为混杂设备
Linux内核使用structmiscdevice描述混杂设备
structmiscdevice{
intminor;
constchar*name;
conststructfile_operations*fops;
structlist_headlist;
structdevice*parent;
structdevice*this_device;
使用misc_register函数来注册一个混杂设备驱动misc_register(structmiscdevice*misc)
使用上拉下拉避免悬浮
Linux总线设备驱动模型(2.6内核难点)
Sysfs文件系统(基于内存,展示内核数据结构属性,关系),与proc同类别的文件系统同类别的文件系统,sysfs把连接在系统上的设备和总线组织成分级的文件,使其从用户空间可以访问到
sysfs在/sys/目录下
block目录:
块设备信息bus:
总线(idepciscsisb
pcmcia)里边还有devices和drivers目录,devices目录下都是软链接
class目录:
按照功能进行分类(网络)
devices:
包含系统所有的设备kernel:
内核中的配置参数Module:
系统中所有模块信息
firmware:
系统中的固件fs:
描述系统中的文件系统power:
系统电源选项
Kobject实现了基本的面向对象管理机制,与sysfs文件系统紧密相连,在内核中注册的每个kobject对象对应sysfs文件系统中的一个目录(作用:
在sys下创建一个目录)类似于C++中的基类。
voidkobject_init(structkobject*kobj)初始化kobject结构
intkobject_add(strutkobject*kobj)将kobject对象注册到Linux系统
intkobject_init_and_add(stru