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驱动的更多相关文章

  1. Linux代码的重用与强行卸载Linux驱动

    (一)Linux代码的重用 重用=静态重用(将要重用的代码放到其他的文件的头文件中声明)+动态重用(使用另外一个Linux驱动中的资源,例如函数.变量.宏等) 1.编译是由多个文件组成的Linux驱动 ...

  2. Linux驱动学习之常用的模块操作命令

    1.常用的模块操作命令 (1)lsmod(list module,将模块列表显示),功能是打印出当前内核中已经安装的模块列表 (2)insmod(install module,安装模块),功能是向当前 ...

  3. Linux驱动学习之驱动开发准备工作

    一.开启驱动开发之路 1.驱动开发的准备工作 (1)正常运行linux系统的开发板.要求开发板中的linux的zImage必须是自己编译的,不能是别人编译的.原因在于在安装模块的时候会进行安全性校验 ...

  4. Linux驱动学习之什么是驱动?

    一.什么是驱动? 1: 驱动一词的字面意思 2: 物理上的驱动 3: 硬件中的驱动 4: linux内核驱动.软件层面上的驱动广义上是指:这一段代码操作了硬件去动,所以这一段代码就叫硬件的驱动程序. ...

  5. 嵌入式Linux驱动开发日记

    嵌入式Linux驱动开发日记 主机硬件环境 开发机:虚拟机Ubuntu12.04 内存: 1G 硬盘:80GB 目标板硬件环境 CPU: SP5V210 (开发板:QT210) SDRAM: 512M ...

  6. linux驱动程序设计的硬件基础,王明学learn

    linux驱动程序设计的硬件基础(一) 本章讲总结学习linux设备程序设计的硬件基础. 一.处理器 1.1通用处理器 通用处理器(GPP)并不针对特定的应用领域进行体系结构和指令集的优化,它们具有一 ...

  7. linux驱动初探之杂项设备(控制两个GPIO口)

    关键字:linux驱动.杂项设备.GPIO 此驱动程序控制了外接的两个二极管,二极管是低电平有效. 上一篇博客中已经介绍了linux驱动程序的编写流程,这篇博客算是前一篇的提高篇,也是下一篇博客(JN ...

  8. linux驱动初探之字符驱动

    关键字:字符驱动.动态生成设备节点.helloworld linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~ 此驱动程序基于linux3. ...

  9. Linux驱动之HelloWorld

    最近看android的一些源码,里面有一些功能是用驱动实现的.于是就兴起看了一些驱动相关的东西,准备日后深入.这没有技术含量的水文,仅作为日后的备忘吧. 系统使用的是ubuntu 12.0.04,内核 ...

  10. linux 驱动学习笔记01--Linux 内核的编译

    由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make confi ...

随机推荐

  1. MacBookPro磁盘空间不够

    256G的SSD还是快被占满了,剩余12G,本来一切运行正常. 要往U盘里拷点资料,突然电脑就罢工了,cleanMyMac 显示磁盘容量剩余 1.8G. finder 罢工,无法重启,无法强退. 无法 ...

  2. python基础5 while循环

    一.while循环: while  条件: 代码块 例: n=0 while n<10: print(n) n=n+1   #n自加1 ,满足n<10,继续循环 输出结果: 1 2 3 4 ...

  3. 学习Makefile

    1> 编译一个文件2> 编译多个文件3> 编译多个目录下的文件4> inclue makefile5> 使用规则1>target:depend[tab]cmddep ...

  4. spring boot错误: 找不到或无法加载主类

    一:当在eclipse启动spring boot项目时出现问题: springboot错误: 找不到或无法加载主类 解决办法: 1,通过cmd命令行,进入项目目录进行,mvn clean instal ...

  5. -webkit-box-orient: vertical; 在webpack上失效

    -webkit-box-orient: vertical;在webpack上失效,可以使用以下方式解决 .ifc-header-content-comment { text-overflow: ell ...

  6. 怎么eclipse或MyEclipse中添加javaSe的源码

    怎么eclipse或MyEclipse中添加javaSe的源码 有时在eclipse里我们调用java提供给我们的方法,我们有时需要查看java提供给我们的调用方法的源码或java提供给我们的核心类的 ...

  7. Windows 10安装Docker 步骤及顺序

    最近在工作中,重新安装Docker时,遇到了一点坑,故将自己解决经验分享一下~ Hardware assisted virtualization and data execution protecti ...

  8. 在C#中GUID生成的四种格式

    var uuid = Guid.NewGuid().ToString(); // 9af7f46a-ea52-4aa3-b8c3-9fd484c2af12 var uuidN = Guid.NewGu ...

  9. PHP菜鸟如何开始学习PHP语言

    把我自己学习PHP的经验分享出来,既给想学习PHP的朋友提供一个思路,也算是整理一下自己的思路,好给后续的教程开个头吧~ 学习其实也是有方法的,举个例子:在您上学期间,班里一定有学霸,也有学渣,也有普 ...

  10. SRD_PreloaderCore

    预加载 Preloader CoreVersion 1.10SumRndmDde This plugin requires the Game Upgrade plugin:http://sumrndm ...