【Linux】Linux设备驱动开发详解:基于最新的Linux 4.0内核_linux 4.0 设备驱动-程序员宅基地

技术标签: Linux  驱动  

1 Linux设备驱动概述及开发环境构建

1.1 设备驱动的作用

  • 驱使硬件设备行动

1.2 无操作系统时的设备驱动

  • 典型架构:一个无限循环中夹杂着对设备中断的检测或者对设备的轮询
    无标题.png

1.3 有操作系统时的设备驱动

  • 并发 、内存管理

    无标题.png

1.4 Linux 设备驱动

1.4.1 设备的分类及特点

● 字符设备。
● 块设备。
● 网络设备。

1.4.2 Linux 设备驱动与整个软硬件系统的关系

捕获.PNG

1.4.3 Linux 设备驱动的重点、难点

● 编写 Linux 设备驱动要求工程师有非常好的硬件基础,懂得 SRAM、 Flash、 SDRAM、磁盘的读写方式,UART、 I2C、 USB 等设备的接口以及轮询、中断、 DMA 的原理,PCI 总线的工作方式以及 CPU 的内存管理单元(MMU)等。
● 编写 Linux 设备驱动要求工程师有非常好的 C 语言基础,能灵活地运用 C 语言的结构体、指针、函数指针及内存动态申请和释放等。
● 编写 Linux 设备驱动要求工程师有一定的 Linux 内核基础,虽然并不要求工程师对内核各个部分有深入的研究,但至少要明白驱动与内核的接口。尤其是对于块设备、网络设备、 Flash 设备、串口设备等复杂设备,内核定义的驱动体系结构本身就非常复杂。
● 编写 Linux 设备驱动要求工程师有非常好的多任务并发控制和同步的基础,因为在驱动中会大量使用自旋锁、互斥、信号量、等待队列等并发与同步机制。

2 驱动设计的硬件基础

2.1 处理器

2.1.1 通用处理器

2.1.2 数字信号处理器

捕获.PNG

2.2 存储器

捕获.PNG

捕获.PNG

2.3 接口与总线

串口 、 I2C I 2 C 、SPI 、USB、以太网 、PCI 和 PCI-E 、SD 和 SDIO

捕获.PNG

捕获.PNG

2.4 CPLD 和 FPGA

2.5 原理图分析

  • 符号 、网络 、描述

2.6 硬件时序分析

  • 时序分析的意思是让芯片之间的访问满足芯片数据手册中时序图信号有效的先后顺序、采样建立时间(Setup Time)和保持时间(Hold Time)的要求

2.7 芯片数据手册阅读方法

2.8 仪器仪表使用

  • 万用表 、示波器 、逻辑分析仪

3 Linux 内核及内核编程

3.1 Linux 内核的发展与演变

  • 表 3.1 Linux 操作系统版本的历史及特点
版 本 时 间 特 点
Linux 0.1 1991 年 10 月 最初的原型
Linux 1.0 1994 年 3 月 包含了 386 的官方支持,仅支持单 CPU 系统
Linux 1.2 1995 年 3 月 第一个包含多平台(Alpha、 Sparc、 MIPS 等)支持的官方版本
Linux 2.0 1996 年 6 月 包含很多新的平台支持,最重要的是,它是第一个支持 SMP(对称多处理器)体系的内核版本
Linux 2.2 1999 年 1 月 极大提升 SMP 系统上 Linux 的性能,并支持更多的硬件
Linux 2.4 2001 年 1 月 进一步提升了 SMP 系统的扩展性,同时也集成了很多用于支持桌面系统的特性: USB、 PC 卡(PCMCIA)的支持,内置的即插即用等
Linux 2.6.0 ~ 2.6.39 2003 年 12 月~2011 年 5 月 无论是对于企业服务器还是对于嵌入式系统, Linux 2.6 都是一个巨大的进步。对高端机器来说,新特性针对的是性能改进、可扩展性、吞吐率,以及对 SMP 机器 NUMA 的支持。对于嵌入式领域,添加了新的体系结构和处理器类型。包括对那些没有硬件控制的内存管理方案的无MMU 系统的支持。同样,为了满足桌面用户群的需要,添加了一整套新的音频和多媒体驱动程序
Linux 3.0 ~ 3.19、Linux 4.0-rc1 至今 2011 年 7 月至今 性能优化等 开发热点聚焦于虚拟化、新文件系统、 Android、新体系结构支持以及

3.2 内核组件

捕获.PNG

1. 进程调度

捕获.PNG

2. 内存管理

捕获.PNG

3. 虚拟文件系统

捕获.PNG

4. 网络接口

捕获.PNG

5. 进程间通信

  • 进程间通信支持进程之间的通信, Linux 支持进程间的多种通信机制,包含信号量、共享内存、消息队列、管道、 UNIX 域套接字等,这些机制可协助多个进程、多资源的互斥访问、进程间的同步和消息传递。在实际的 Linux 应用中,人们更多地趋向于使用 UNIX 域套接字,而不是 System V IPC 中的消息队列等机制。 Android 内核则新增了 Binder 进程间通信方式。

4 内核模块

4.1 模块简介

insmod ./hello.ko
rmmod hello

lsmod
/proc/modules
/sys/module

4.2 模块结构

4.2.1 加载函数

static int __init hello_init(void)
{
    ...

    return 0;
}

module_init(hello_init);

4.2.2 卸载函数

static void __exit hello_exit(void)
{
    ...
}

module_exit(hello_exit);

4.2.3 许可声明

MODULE_AUTHOR("lin");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("A simple param Module");
MODULE_ALIAS("a simplest module");
  • 模块参数module_param(var, int, S_IRUGO);
  • 导出符号EXPORT_SYMBOL_GPL(func); (proc/kallsyms)

5 文件系统与设备文件

捕获.PNG

捕获.PNG

6 字符设备驱动

6.1 驱动结构

6.1.1 cdev结构体

捕获.PNG

//生成dev
MKDEV(int major, int minor);    //major:0-19 minor:20-31
//获取设备号
MAJOR(dev_t dev)
MINOR(dev_t dev)
//cdev操作
void cdev_init(struct cdev *, struct file_operations *);
struct cdev* cdev_alloc(void);
void cdev_put(struct cdev *);
int  cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);

6.1.2 设备号分配

int register_chrdev_region(dev_t from, unsigned count, const char *name);
int    alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

int unregister_chrdev_region(dev_t from, unsigned count);

6.1.3 file_operations结构体

捕获.PNG

7 设备驱动中的并发控制

7.1 并发与竞态

  • 临界区:访问共享资源的代码段
  • 互斥:中断屏蔽、原子操作、自旋锁、信号量、互斥体

7.2 编译乱序和执行乱序

  • 表 隔离指令
指令名 功能描述
DMB 数据存储器隔离。DMB 指令保证: 仅当所有在它前面的存储器访问操作都执行完毕后,才提交(commit)在它后面的存储器访问操作。
DSB 数据同步隔离。比 DMB 严格: 仅当所有在它前面的存储器访问操作都执行完毕后,才执行在它后面的指令(亦即任何指令都要等待存储器访 问操作——译者注)
ISB 指令同步隔离。最严格:它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令。

7.3 中断屏蔽

local_irq_disable() local_irq_enable() //与自旋锁联合使用
local_irq_save(flags) local_irq_restore(flags)
local_bh_disable() local_bh_enable()

7.4 原子操作

7.4.1 整型原子操作

  • 设置

    void atomic_set(atomic_t *v, int i);
    atomic_t ATOMIC_INIT(int i);
  • 获取

    int atomic_read(atomic_t *v);
  • 加减

    void atomic_add(int i, atomic_t *v);
    void atomic_sub(int i, atomic_t *v);
    
    void atomic_inc(atomic_t *v);
    void atomic_dec(atomic_t *v);
  • 操作后测试(为0返回true,非0返回false)

    int atomic_inc_and_test(atomic_t *v);
    int atomic_dec_and_test(atomic_t *v);
    int atomic_sub_and_test(int i, atomic_t *v);
  • 操作后返回新值

    int atomic_add_return(int i, atomic_t *v);
    int atomic_sub_return(int i, atomic_t *v);
    
    int atomic_inc_return(atomic_t *v);
    int atomic_dec_return(atomic_t *v);

7.4.2 位原子操作

捕获.PNG

7.5 自旋锁

7.5.1 自旋锁

spinlock_t lock;
spin_lock_init(lock);
spin_lock(lock);
spin_trylock(lock);
spin_unlock(lock);


spin_lock_irq(lock); spin_unlock_irq(lock);
spin_lock__irqsave(lock); spin_unlock_irqrestore(lock);
spin_lock_bh(lock); spin_unlock_bh(lock);

无标题.png

7.5.2 读写锁

无标题.png

7.5.3 顺序锁

  • 读执行单元不会被写执行单元阻塞;但写执行单元进行写操作时,其他写执行单元就会自旋。

无标题.png

7.5.4 读-复制-更新

  • RCU: Read-Copy-Update

    捕获.PNG

    无标题.png

    7.6 信号量

    无标题.png

    7.7 互斥体

    无标题.png

    7.8 完成量

    无标题.png

8 阻塞I/O和非阻塞I/O

8.1 阻塞I/O和非阻塞I/O

fd= open("/dev/ttyS1", O_RDWR | O_NONBLOCK);
fcntl(fd, F_SETFL, O_NONBLOCK);

8.1.1 等待队列

//定义
wait_queue_head_t queue_head;
//初始化
init_waitqueue_head(&queue_head);
//定义及初始化
DECLARE_WAIT_QUEUE_HEAD(name)
//队列等待元素
DECLARE_WAITQUEUE(name, tsk)
//操作
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
//等待事件
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
//唤醒队列
void wake_up(wait_queue_head_t *q);
void wake_up_interruptible(wait_queue_head_t *q);
//睡眠
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
static ssize_t xxx_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
{
    ...
    DECLARE_WAITQUEUE(wait, current);
    add_wait_queue(&xxx_wait, &wait);

    /*等待设备缓冲区可写*/
    do {
        avail = device_writable();
        if (avail < 0) {
            if (file->f_flags & O_NONBLOCK) {
                ret = -EAGAIN;
                goto out;
            }
            __set_current_state(TASK_INTERRUPTIBLE);
            schedule();
            if (signal_pending(current)) {
                ret = -ERESTARTSYS;
                goto out;
            }
        }
    } while (avail < 0);

    device_write();
out:
    remove_wait_queue(&xxx_wait, &wait);
    set_current_state(TASK_RUNNING);

    reutrn ret;
}

捕获.PNG

8.1.2 支持等待队列的globalfifo

无标题.png

8.2 轮询操作

8.2.1 轮询的概念与作用

9.2.3 信号的释放

  1. 异步通知结构体

    struct xxx_dev{
        struct cdev cdev;
        ...
        struct fasync_struct *async_queue;
    }
    1. xxx_fasync
    static int xxx_fasync(int fd, struct file *filp, int mode)
    {
        struct xxx_dev *dev=file->private_data;
        return fasync_helper(fd, filp, mode, &dev->async_queue);
    }
    1. 释放读信号
    //xxx_write
    if(dev->async_queue)
      kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
    1. 从异步通知列表删除filp
    //xxx_release
    xxx_fasync(-1, filp, 0);

9.4 Linux异步I/O

9.4.1 AIO

struct aiocb {
 
  int aio_fildes;               // File Descriptor
  int aio_lio_opcode;           // Valid only for lio_listio (r/w/nop)
  volatile void *aio_buf;       // Data Buffer
  size_t aio_nbytes;            // Number of Bytes in Data Buffer
  struct sigevent aio_sigevent; // Notification Structure
 
  /* Internal fields */
  ...
 
};
API 函数 说明
aio_read int aio_read( struct aiocb *aiocbp ); 请求异步读操作
aio_error int aio_error( struct aiocb *aiocbp ); 检查异步请求的状态
aio_return ssize_t aio_return( struct aiocb *aiocbp ); 获得完成的异步请求的返回状态
aio_write int aio_write( struct aiocb *aiocbp ); 请求异步写操作
aio_suspend int aio_suspend( const struct aiocb *const cblist[], int n, const struct timespec *timeout ); 挂起调用进程,直到一个或多个异步请求已经完成(或失败)
aio_cancel int aio_cancel( int fd, struct aiocb *aiocbp ); 取消异步 I/O 请求
lio_listio int lio_listio( int mode, struct aiocb *list[], int nent, struct sigevent *sig ); 发起一系列 I/O 操作

9.4.2 内核AIO与libaio

10 中断与时钟

10.1 中断与定时器

11 内存与I/O访问

17 I2C、SPI、USB驱动架构类比

无标题.png

18 ARM Linux设备树

18.1 ARM设备树起源

  • 可描述的信息:
    • CPU的数量和类别
    • 内存基地址和大小
    • 总线和桥
    • 外设连接
    • 中断控制器和中断使用情况
    • GPIO控制器和GPIO使用情况
    • 时钟控制器和时钟使用情况

18.2 设备树的组成和结构

18.2.1 DTS、DTC和DTB

  1. .dts:device tree source

    1.1 Soc共用部分:.dtsi (/include/ “s3c24440.dtsi”)

    1.2 模板

    /* root节点 */
    / {
        node1 {
            a-string-property = "A string";
            a-string-list-property = "first string", "second string";
            a-byte-data-property = [0x01 0x23 0x34 0x56];
            child-node1 {
                first-child-property;
                second-child-property = <1>;
                a-string-property = "Hello, world";
            };
            child-node2 {
            };
        };
        node2 {
            an-empty-property;
            a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
            child-node1 {
            };
        };
    };
  2. .dtc:device tree compiler

  3. .dtb:Device Tree Blob

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/limHappipen/article/details/79821307

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签