打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
驱动程序设计
  *第5阶段(驱动程序设计)
    *第1天
    5-1-1(Linux驱动程序介绍)
    字符设备:字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现open, close,read和write 系统调用。
    块设备:Linux则允许块设备传送任意数目的字节。因此, 块和字符设备的区别仅仅是驱动的与内核的接口不同。
   网络接口:任何网络事务都通过一个接口来进行, 一个接口通常是一个硬件设备(eth0), 但是它也可以是一个纯粹的软件设备, 比如回环接口(lo)。一个网络接口负责发送和接收数据报文。
 
 
Linux用户程序通过设备文件(又名:设备节点)来使用驱动程序操作字符设备和块设备

    5-1-2(字符设备驱动)
主次设备号:字符设备通过字符设备文件来存取。
设备号作用:主设备号用来标识与设备文件相连的驱动程序。次编号被驱动程序用来辨别操作的
是哪个设备。
                                 ** 主设备号用来反映设备类型**
                                **次设备号用来区分同类型的设备**
分配主设备号:Linux内核如何给设备分配主设备号?
                        可以采用静态申请,动态分配两种方法

静态申请,方法:
1、根据Documentation/devices.txt,确定一个没有使用的主设备号
2、使用register_chrdev_region 函数注册设备号

int register_chrdev_region(dev_t from, unsignedcount, const char *name)
动态分配,方法
使用alloc_chrdev_region 分配设备号
int alloc_chrdev_region(dev_t *dev, unsignedbaseminor, unsigned count,const char *name)
注销设备号:void unregister_chrdev_region(dev_t from,unsigned count)
创建设备文件,2种方法
1. 使用mknod 命令手工创建
2. 自动创建
手工创建,mknod 用法:
              mknod filename type major minor
重要结构:
   在Linux字符设备驱动程序设计中,有3种非常重要的数据结构:
    Struct file,Struct inode,Struct file_operations
    Struct File:代表一个打开的文件
重要成员:loff_t f_pos /*文件读写位置*/
                 struct file_operations *f_op
    Struct Inode:用来记录文件的物理上的信息。
重要成员:dev_t i_rdev:设备号
    Struct file_operations:一个函数指针的集合,定义能在设备上进行的操作。结构中的成员指向驱
动中的函数, 这些函数实现一个特别的操作, 对于不支持的操作保留为NULL。
设备注册:
                字符设备的注册可分为如下3个步骤:
                1. 分配cdev
                2. 初始化cdev
                3. 添加cdev
设备注册(分配):struct cdev *cdev_alloc(void)
设备注册(初始化):void cdev_init(struct cdev *cdev, const struct file_operations *fops)
设备注册(添加):int cdev_add(struct cdev *p, dev_t dev, unsigned count)
设备操作:
       1、int (*open)(struct inode *, struct file *)在设备文件上的第一个操作,并不要求驱动程序一定要实现这个方法。如果该项为NULL,设备的打开操作永远成功。
       2、void (*release)(struct inode *, struct file *) 当设备文件被关闭时调用这个操作。与open相
仿,release也可以没有。
       3、ssize_t (*read) (struct file *, char __user *, size_t, loff_t *)从设备中读取数据。
       4、ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)向设备发送数据。
       5、 unsigned int (*poll) (struct file *, struct poll_table_struct *)对应select系统调用
       6、 int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long)控制设备
       7、int (*mmap) (struct file *, struct vm_area_struct *)将设备映射到进程虚拟地址空间中。
       8、off_t (*llseek) (struct file *, loff_t, int)修改文件的当前读写位置,并将新位置作为返回值。

 
 Open方法:
Open方法是驱动程序用来为以后的操作完成初始化准备工作的。在大部分驱动程序
中,open完成如下工作:初始化设备。
                                     标明次设备号。
Release方法:
Release方法的作用正好与open相反。这个设备方法有时也称为close,它应该:关闭设备。
读和写:内核提供了专门的函数用于访问用户空间的指针,例如:
 int copy_from_user(void *to, const void __user *from, int n)
 int copy_to_user(void __user *to, const void *from, int n)
设备注消:int cdev_del(struct cdev *p)

 
5-1-3(驱动调试技术)
打印调试:应用程序中合适的点调用printf。当调试内核代码的时候,可以用printk完成类似任务。

   
5-1-4(简单字符设备驱动实例分析)
5-1-5(并发控制)
并发与竞态:       并发:多个执行单元同时被执行。
                         竞态:并发的执行单元对共享资源(硬件资源和软件上的全局变量等)的访问导致的竞争状态
处理并发的常用技术:加锁或者互斥,即确保在任何时间只有一个执行单元可以操作共享资源。在Linux内核中主要通过semaphore机制和spin_lock机制实现。
信号量:
1、定义与初始化的工作可由如下宏一步完成:
     DECLARE_MUTEX(name):定义一个信号量name,并初始化它的值为1。
     DECLARE_MUTEX_LOCKED(name):定义一个信号量name,但把它的初始值设置为0,即锁在创建时就处在已锁状态。
2、获取信号量
      void down(struct semaphore * sem)
      int down_interruptible(struct semaphore * sem)
      down_killable(struct semaphore *sem)
3、释放信号量
     void up(struct semaphore * sem)    sem加1
自旋锁:
        自旋锁最多只能被一个可执行单元持有。
1、spin_lock_init(x)该宏用于初始化自旋锁x,自旋锁在使用前必须先初始化。
2、spin_lock(lock)获取自旋锁lock,如果成功,立即获得锁,并马上返回,否则它将一直自旋在那里,直到该自旋锁的保持者释放
3、spin_trylock(lock)试图获取自旋锁lock,如果能立即获得锁,并返回真,否则立即返回假。它不会一直等待被释放。
4、spin_unlock(lock)释放自旋锁lock,它与spin_trylock或spin_lock配对使用。
信号量PK自旋锁:
   信号量可能允许有多个持有者,而自旋锁在任何时候只能允许一个持有者。
   信号量适合于保持时间较长的情况;而自旋锁适合于保持时间非常短的情况。

    *第2天 linux高级字符设备驱动程序
    5-2-1(Ioctl设备控制)
功能:大部分驱动除了需要具备读写设备的能力外,还需要具备对硬件控制的能力。例如,要求设备报告错误信息,改变波特率,这些操作常常通过ioctl方法来实现。
用户使用方法:使用ioctl 系统调用来控制设备,原型如下:
                                      int ioctl(int fd,unsigned long cmd,...)
驱动ioctl方法:int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
如何实现Ioctl方法?步骤:
                        1. 定义命令
                        2. 实现命令
定义命令:
           ioctl 命令编码被划分为几个位段,include/asm/ioctl.h中定义了这些位字段:类型(幻数),序号,传送方向,参数的大小。
Ioctl函数的实现包括如下3个技术环节:
                        1. 返回值:Ioctl函数的实现通常是根据命令执行的一个switch语句
                        2. 参数使用:如果是一个整数,可以直接使用,如果是指针,我们必须确保这个用户地址是有效的
                        3. 命令操作:

    5-2-2(内核等待队列)
 等待队列: 在Linux驱动程序设计中,可以使用等待队列实现进程的阻塞,等待队列可看作保存进程的容器,在阻塞进程时,将进程放入等待队列,当唤醒进程时,从等待等列中取出进程。
1、定义并初始化等待队列:DECLARE_WAIT_QUEUE_HEAD(my_queue)
2、有条件睡眠:wait_event(queue,condition)
                    wait_event_interruptible(queue,condition)
                    int wait_event_killable(wait_queue_t queue, condition)
3、从等待队列中唤醒进程:wake_up(wait_queue_t *q)
                                    wake_up_interruptible(wait_queue_t *q)

    5-2-3(阻塞型字符设备驱动)
阻塞方式:在阻塞型驱动程序中,Read实现方式如下:如果进程调用read,但设备没有数据或
数据不足,进程阻塞。当新数据到达后,唤醒被阻塞进程。
                在阻塞型驱动程序中,Write实现方式如下:如果进程调用了write,但设备没有足够的空
间供其写入数据,进程阻塞。当设备中的数据被读走后,缓冲区中空出部分空间,则唤醒进程。
非阻塞方式:阻塞方式是文件读写操作的默认方式,但应用程序员可通过使用O_NONBLOCK
标志来人为的设置读写操作为非阻塞方式。
                如果设置了O_NONBLOCK标志,read和write的行为是不同的。如果进程在没
有数据就绪时调用了read,或者在缓冲区没有空间时调用了write,系统只是简单地返回-EAGAIN,而不会阻塞进程。

    5-2-4(poll设备方法)
     Select系统调用(功能):Select系统调用用于多路监控,当没有一个文件满足要求时,select将阻塞调用进程。
    int select(int maxfd, fd_set *readfds, fd_set *writefds, fe_set *exceptfds, const struct timeval *timeout)
Select系统调用(使用方法):
1. 将要监控的文件添加到文件描述符集
2. 调用Select开始监控
3. 判断文件是否发生变化
Poll方法:
应用程序常常使用select系统调用,它可能会阻塞进程。这个调用由驱动的poll 方法实现,原型为:
unsigned int (*poll)(struct file *filp,poll_table *wait)
Poll设备方法负责完成:
1. 使用poll_wait将等待队列添加到poll_table中。
2. 返回描述设备是否可读或可写的掩码。

    5-2-5(自动创建设备文件)
创建设备文件的方法:
      利用udev(mdev)来实现设备文件的自动创建很简单,在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。
   例:struct class *myclass = class_create(THIS_MODULE,“my_device_driver”);
        device_create(myclass, NULL, MKDEV(major_num, 0), NULL,“my_device”);
        当驱动被加载时,udev( mdev )就会自动在/dev下创建my_device设备文件。

    *第3天 LINUX硬件设备访问
    5-3-1(mmap设备方法)
mmap系统调用(功能):void* mmap ( void * addr , size_t len , int prot , int flags ,int fd , off_t offset )
       内存映射函数mmap, 负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。
解除映射:int munmap(void *start,size_t length)
虚拟内存区域:虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。
mmap设备操作:映射一个设备是指把用户空间的一段地址关联到设备内存上。当程序读写这段用户空间的地址时,它实际上是在访问设备。
mmap设备方法所需要做的就是建立虚拟地址到物理地址的页表。
                          int (*mmap) (struct file *, struct vm_area_struct *)
mmap如何完成页表的建立?
方法有二:
1、使用remap_pfn_range一次建立所有页表;
2、使用nopage VMA方法每次建立一个页表。

    5-3-2(硬件访问)
寄存器与内存的区别在哪里呢?
      寄存器和RAM 的主要不同在于寄存器操作有副作用(side effect 或边际效果):读取某个地址时可能导致该地址内容发生变化。
IO端口:当一个寄存器或内存位于IO空间时,称其为IO端口。
IO内存:当一个寄存器或内存位于内存空间时,称其为IO内存。
操作I/O端口:对I/O端口的操作需按如下步骤完成:
1. 申请
2. 访问
3. 释放
映射I/O内存:在访问I/O内存之前, 必须进行物理地址到虚拟地址的映射,ioremap 函数具有此功能:
void *ioremap(unsigned long phys_addr, unsigned long size)
I/O内存不再需要使用时应当释放,步骤如下:
1. void iounmap(void * addr)
2. void release_mem_region(unsigned longstart, unsigned long len)
5-3-3 混杂设备
      在Linux系统中,存在一类字符设备,它们共享一个主设备号(10),但次设备号不同,我们称这类设备为混杂设备(miscdevice)。
设备描述:struct miscdevice
设备注册:int misc_register(struct miscdevice * misc)

    5-3-4(LED驱动程序)

    *第4天
    5-4-1(总线)
        Sysfs文件系统:sysfs 被看成是与proc同类别的文件系统。sysfs 把连接在系统上的设备和总线组织成分级的文件,使其从用户空间可以访问到。
       Sysfs 被加载在/sys/ 目录下,子目录包括如下。
       Block:在系统中发现的每个块设备在该。
       Bus:在内核中注册的每条总线在该目录下对应一个子目录, 如:ide pci scsi usb pcmcia
      其中每个总线目录内又包含两个子目录:devices 和drivers ,devices目录包含了在整个系统中发现的属于该总线类型的设备,drivers目录包含了注册到该总线的所有驱动。
     Class:将设备按照功能进行的分类,如/sys/class/net目录下包含了所有网络接口。
    Devices:包含系统所有的设备。
    Kernel:内核中的配置参数
    Module:系统中所有模块的信息
    Firmware:系统中的固件
    Fs: 描述系统中的文件系统
    Power:系统中电源选项
Kobject 实现了基本的面向对象管理机制,是构成Linux2.6设备模型的核心结构。
热插拔事件:在Linux系统中,当系统配置发生变化时,如:添加kset到系统;移动kobject, 一个通知会
从内核空间发送到用户空间,这就是热插拔事件。

    5-4-2(设备)
    设备模型元素:总线、驱动、设备
   总线:总线是处理器和设备之间的通道,在设备模型中, 所有的设备都通过总线相连, 甚至是
   内部的虚拟“platform”总线。在Linux 设备模型中, 总线由bus_type 结构表示。
  总线的注册使用:bus_register(struct bus_type * bus)
  总线的删除使用:void bus_unregister(struct bus_type *bus)
  总线方法:int (*match)(struct device * dev, struct device_driver * drv)
                  当一个新设备或者驱动被添加到这个总线时,该方法被调用。
  总线方法:int (*uevent)(struct device *dev, char **envp, int num_envp,char *buffer, int buffer_size)
                  在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量。
 总线属性:总线属性由结构bus_attribute 描述
创建属性:int bus_create_file(struct bus_type *bus,struct bus_attribute *attr)
删除属性:void bus_remove_file(struct bus_type*bus, struct bus_attribute *attr)
设备描述:Linux 系统中的每个设备由一个struct device 描述
设备注册:int device_register(struct device *dev)
注销设备:void device_unregister(struct device *dev)
                 **一条总线也是个设备,也必须按设备注册**
设备属性:设备属性由struct device_attribute 描述
创建属性:int device_create_file(struct device *device, struct device_attribute * entry)
删除属性:void device_remove_file(struct device *dev, struct device_attribute * attr)

    5-4-3(驱动)
    驱动描述:驱动程序由struct device_driver 描述。
    注册驱动:int driver_register(struct device_driver *drv)
    注销驱动:void driver_unregister(struct device_driver *drv)
    驱动属性:驱动的属性使用struct driver_attribute 来描述
    创建属性: int driver_create_file(struct device_driver * drv,struct driver_attribute * attr)
    删除属性:void driver_remove_file(struct device_driver * drv, struct driver_attribute * attr)

    5-4-4(platform驱动)
Platform总线:Platform总线是linux2.6内核加入的一种虚拟总线。
由两部分组成:platform_device和platform_driver。
Platform总线设备本身的资源注册进内核,由内核统一管理,提高了程序的可移植性。
通过platform机制开发底层设备驱动的流程:定义platform_device
                                                                 注册platform_device
                                                                 定义platform_driver
                                                                 注册platform_driver
平台设备描述:平台设备使用Struct Platform_device来描述
分配使用:struct platform_device*platform_device_alloc(const char *name, int id)
 注册平台设备:int platform_device_add(struct platform_device *pdev)
设备资源:平台设备资源使用struct resource来描述
获取资源:struct resource *platform_get_resource(struct platform_device*dev, unsigned int type, unsigned int num)
平台驱动描述:平台驱动使用struct platform_driver 描述
平台驱动注册使用函数:int platform_driver_register(struct platform_driver *)

    5-4-5(中断处理程序)
为什么需要中断?
1.外设的处理速度一般慢于CPU
2.CPU不能一直等待外部事件所以设备必须有一种方法来通知CPU它的工作进度,这种方法就是中断。
中断实现,在Linux驱动程序中,为设备实现一个中断包含两个步骤:
1.向内核注册中断
2.实现中断处理函数
中断注册:
request_irq用于实现中断的注册功能:
int request_irq(unsigned int irq,void (*handler)(int, void*, structpt_regs *),unsigned long flags,
const char *devname,void *dev_id)
快速/慢速中断:这两种类型的中断处理程序的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。
共享中断:共享中断就是将不同的设备挂到同一个中断信号线上。Linux对共享的支持主要是为PCI 设备服务。
共享中断也是通过request_irq函数来注册的,但有三个特别之处:
1.申请共享中断时,必须在flags参数中指定IRQF_SHARED位
2. dev_id参数必须是唯一的。
3.共享中断的处理程序中,不能使用disable_irq(unsigned int irq)。
中断处理程序:特别之处在于中断处理程序是在中断上下文中运行的,它的行为受到某些限制:
1.不能向用户空间发送或接受数据
2.不能使用可能引起阻塞的函数
3.不能使用可能引起调度的函数
释放中断:void free_irq(unsigned int irq, void *dev_id)


    5-4-6(按键驱动)

    *第5天
    5-5-1(网卡驱动程序设计)
    5-5-2(CS8900A网卡驱动程序分析)

    5-5-3(输入子系统)
    设备描述:在Linux内核中,input设备用input_dev结构体描述,使用input子系统实现输入设备驱动的时候,驱动的核心工作是向系统报告按键、触摸屏、键盘、鼠标等输入事件(event,通过input_event结构体描述),不再需要关心文件操作接口,因为input子系统已经完成了文件操作接口。
    注册输入设备的函数为:int input_register_device(struct input_dev *dev)
   注销输入设备的函数为:void input_unregister_device(struct input_dev *dev)
   驱动实现-事件支持:设备驱动通过set_bit()告诉input子系统它支持哪些事件,如下所示:
                                  set_bit(EV_KEY, button_dev.evbit)

    5-5-4(触摸屏驱动程序)
    *第6天
    5-6-1(PCI总线).avi
    5-6-2(PCI驱动程序设计).avi
    5-6-3(串口驱动程序).avi
    *第6阶段(深入专题与项目实战)
    H.264远程视频监控系统
    MP3嵌入式播放器
    USB驱动程序专题
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
15.4.?直接内存存取-Linux设备驱动第三版(中文版)
Linux USB驱动框架分析
Linux块设备驱动程序原理
Linux pci驱动分析
Linux操作系统网络驱动程序编写 - Ursa
Linux下USB内核之学习笔记
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服