精华内容
下载资源
问答
  • 一张图串行,并行,并发

    千次阅读 2019-05-12 22:12:01
    一次只能干一件事,挨个按顺序执行(串行): 一会儿吃口馒头,一会儿吃口菜(并发): 多条流水线同时工作(并行): 竞争问题: A和B线程都对同一个内存位置进行读写,就会容易出现竞争问题。 Thread...

    一次只能干一件事,挨个按顺序执行(串行):

     一会儿吃口馒头,一会儿吃口菜(并发):

     多条流水线同时工作(并行):

    竞争问题:

    A和B线程都对同一个内存位置进行读写,就会容易出现竞争问题。

    Thread1先读取42,Thread2也读取42。Thread1写入变成43,Thread2也写入变成43. 期望结果应该是44(42 +1 + 1).

     

    通过互斥锁达到数据同步读写的问题,但也会带来死锁问题:

    两个线程都在等待对方释放资源。

     

     

    展开全文
  • GCD串行并发队列扫盲

    2017-02-23 15:25:55
    今天一起来看看苹果的GCD队列相关知识,扫一扫盲区吧! 学习完本篇,您会对以下知识点更加理解: 队列、串行队列、并发队列、GCD全局队列、GCD主队列、创建串行队列、创建并发队列。 队列基础知识 在大学学习过队列...

    本篇一起来学习GCD队列相关知识及如何使用。一直以来都是看到过别人这么用,说实在的,还真没有学过文档,也没有深入研究过其所以然。今天一起来看看苹果的GCD队列相关知识,扫一扫盲区吧!

    学习完本篇,您会对以下知识点更加理解: 队列、串行队列、并发队列、GCD全局队列、GCD主队列、创建串行队列、创建并发队列。

    队列基础知识

    在大学学习过队列、栈数据结构吧?如果学习过,应该是非常容易理解的。不管是什么队列,一定是FIFO队列,即先进先出。

    所以,请大家记住了:不管是串行队列(SerialQueue)还是并发队列(ConcurrencyQueue),都是FIFO队列。也就意味着,任务一定是一个一个地,按照先进先出的顺序来执行。

    串行队列:在创建队列时,传参数DISPATCH_QUEUE_SERIAL表示创建串行队列。任务会一个一个地执行,只有前一个任务执行完成,才会继续执行下一个任务。串行执行并不是同步执行的意思,一定要注意区分

    并发队列:在创建队列时,传参数DISPATCH_QUEUE_CONCURRENT表示创建并发队列。并发队列会尽可能多地创建线程去执行任务。并发队列中的任务会按入队的顺序执行任务,但是哪个任务先完成是不确定的。

    队列类型

    苹果提供了以下队列:

    1. 全局队列:苹果预定义的全局并发队列,只能通过苹果提供的API来获取,可以设置优先级。

    2. 主队列:在应用启动的时候,就会自动创建与主线程关联的串行队列,我们也可能获取,不能手动创建。

    3. 手动创建串行队列

    4. 手动创建并发队列

    全局队列

    全局队列的第二个参数用于设置优先级,只有下面四个选项:

    
    
    /*
     #define DISPATCH_QUEUE_PRIORITY_HIGH 2
     #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
     #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
     #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
     */
    

    我们通常使用默认选项,所以很多时候看到的都是传0。下面我们来看看创建四个任务放到并发队列中异步地执行:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      NSLog(@"1");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      // 睡眠2秒
      sleep(2);
      NSLog(@"2");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      // 睡眠3秒
      sleep(1);
      NSLog(@"3");
    });

    打印出来的队列是通过dispatch_get_current_queue来获取当前正在执行任务的队列,也就是主队列了。不过这个API已经在6.0以后被标识为DEPRECATED(废弃)了。

    上面的代码中,我们在并发队列中添加了三个任务,其中任务1是直接执行,任务2是在异步执行过程中被睡眠2秒,任务3在异步执行过程中被睡眠1秒,结果任务3先于任务2执行完成。说明并发执行任务并不需要等待其他任务先执行完。对于这三个任务,是互不干扰的!

    当然,全局队列可以指定任务执行的优先级的,比如下面:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
      NSLog(@"4");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
      NSLog(@"3");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      NSLog(@"2");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
      NSLog(@"1");
    });

    任务四优先级最高,所以在并发队列中优先执行。

    提示:暂时忽略dispatch_async,后续文件会学习

    主队列

    主队列是应用程序启动时,由系统预先创建的,与主线程相关联的队列。我们只能通过系统API来获取主队列,不能手动创建它。下面我们来看看主队列这个串行队列的执行顺序如何:

    dispatch_async(dispatch_get_main_queue(), ^{
        sleep(2);
        NSLog(@"main 1");
    });
      
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"main 2");
    });
      
    dispatch_async(dispatch_get_main_queue(), ^{
        sleep(1);
        NSLog(@"main 3");
    });


    从打印结果可看到执行的顺序是按入队的顺序来执行的。虽然让任务1睡眠2秒再执行,其他任务也只能等待任务1完成,才能继承执行任务2,在任务2执行完成,才能执行任务3。

    从打印结果可以看到线程号是固定的,说明都在同一个线程中执行,而这个线程就是主线程。任务只能一个一个地执行。

    提示:暂时忽略dispatch_async,后续文件会学习

    创建串行队列

    通过dispatch_queue_create函数来创建队列,参数一是一个C语言的字符串,是队列的标签,也就是名称,通常是采用com..这样的格式。参数二是指定串行队列还是并发队列。

    • 创建串行队列传:DISPATCH_QUEUE_SERIAL(也就是NULL)

    • 创建并发队列传:DISPATCH_QUEUE_CONCURRENT

    dispatch_queue_t serialQueue = dispatch_queue_create("com.huangyibiao.serial-queue", 
    DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(serialQueue, ^{
      NSLog(@"s1");
    });
    dispatch_async(serialQueue, ^{
      sleep(2);
      NSLog(@"s2");
    });
    dispatch_async(serialQueue, ^{
      sleep(1);
      NSLog(@"s3");
    });

    从打印结果可以看出来,任务全在同一个线程中执行,但是并不是在主线程,而是在子线程执行。不过任务执行只有顺序地执行,任务没有执行完毕之前,下一个任务是不能开始的。

    创建并发队列

    通过dispatch_queue_create函数来创建队列,参数一是一个C语言的字符串,是队列的标签,也就是名称,通常是采用com..这样的格式。参数二是指定串行队列还是并发队列。

    • 创建串行队列传:DISPATCH_QUEUE_SERIAL(也就是NULL)

    • 创建并发队列传:DISPATCH_QUEUE_CONCURRENT

    一起来看看串行队列是否需要等待任务执行完成,下一个任务才能开始:

    dispatch_queue_t concurrencyQueue = dispatch_queue_create("com.huangyibiao.concurrency-queue",                            
    DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrencyQueue, ^{
      NSLog(@"s1");
    });
    dispatch_async(concurrencyQueue, ^{
      sleep(2);
      NSLog(@"s2");
    });
    dispatch_async(concurrencyQueue, ^{
      sleep(1);
      NSLog(@"s3");
    });


    从打印结果可以看出来,任务在三个子线程中执行,且互不干扰,不需要等待其他任务完成,就可以并发地分别去执行!

    展开全文
  • 但实际上这是SerDes中最容易实现的功能。更重要的是信号串行起来之后遇到的信号衰减、码间串扰、时钟同步等其他问题。 1.1 ISI 首先要理解的是高速串行信号经过信道之后不再是理想的0/1高低电平 下图为不同信道的...

    SerDes从名字上来听,主要功能就是是实现串行解串电路。
    但实际上这是SerDes中最容易实现的功能。更重要的是信号串行起来之后遇到的信号衰减、码间串扰、时钟同步等其他问题。

    1.1 ISI

    首先要理解的是高速串行信号经过信道之后不再是理想的0/1高低电平
    下图为不同信道的频域特性,从信号系统的角度看,可以看到传输介质是一个低通系统,并且在通带内信号的幅度响应也不平缓,不同的频率幅度响应不同会导致输入信号的高频分量被受到严重的衰减。下面称这种信道为带限信道。
    在这里插入图片描述
    假如输入是一个方波脉冲(脉宽为1UI),其上升沿、下降沿中最陡的部分波形是由其频谱最高频的部分构成的。但是经过带限信道之后,高频分量被滤掉,输出波形的上升沿、下降沿变缓,并且形成长长的拖尾,如下图所示。
    在这里插入图片描述
    而这个拖尾超出1UI的部分就会形成码间串扰。
    根据信号叠加理论,我们可以把一串0/1数据经过带限信道,看成多个脉冲卷积信道传递函数之后再叠加。
    在这里插入图片描述
    在这里插入图片描述
    下图是多个脉冲叠加的效果,可以看到输出波形的高低电平已经不再理想,如果信道更加恶化,电平的幅度继续减小,RX将无法判决出0/1逻辑。

    在这里插入图片描述
    以上是码间串扰产生的原因,另外我们还能引出cursor的概念。
    输出波形拖尾中每个UI的幅度就是某个cursor的数值,在均衡算法中通常会把电压幅度归一化到main-cursor上。
    (补充:为什么会有pre-cursor?因为实际传输介质都不是理想的因果系统)
    在这里插入图片描述

    1.2 NYQUIST FREQUENCY/NRZ/PAM4

    Nyquist频率这里直接给出结论,公式推导见通信原理教材。

    1. W 带宽的基带信道每秒最多能传输的符号数为2W 个。2W 为Nyquist 速
      率;
    2. Rs 码率的基带信号最少需要占用RS/2 的带宽。RS/2 被成为Nyquist 频率。
      在这里插入图片描述
      因为电气传输介质带宽的限制,目前业界SerDes在56Gbps到112Gbps普遍采用PAM4的调制格式来降低码间串扰对信号质量的影响。
      但是PAM4对于RX EPHY的设计带来很大的挑战,主要在于
    3. 信号眼高变小,判决门限变小
    4. 多电平信号使判决器设计复杂度增加
    5. 若采用高速ADC-based RX功耗难以降低
    6. 上下眼大小对电路的线性度要求高

    1.3 JITTER/EYE-DIAGRAM

    Jitter定义:short-term variation of signal with respect to its ideal position in time
    根据理想信号位置的定义不同,jitter有不同的测量方式
    在这里插入图片描述

    SerDes系统更多关注Jac or Jc,数字系统更关注Jcc

    Jitter的均值:skew
    RMS Jitter:jitter中的随机分量
    Peak-to-Peak Jitter:因为其中包含随机分量,所以要根据相应的BER等级计算得出
    在这里插入图片描述
    RJ主要来自于电路的噪声
    SJ or PJ主要来自于PLL SSC,或者PLL的feedthrough
    ISI就是码间串扰,主要来自于信道的带宽衰减
    DCD主要存在于半速率架构的TX中,因为时钟的占空比不是50%
    BUJ来自lane之间的串扰,串扰的传播通路主要有PDN和传输介质之间的电磁干扰
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    Jitter的测量:把眼图波形与阈值电压的交点位置和交点的数量记录下来绘制柱状图,可以得到两个类似于概率密度的函数。再根据概率密度函数可以得到浴盆曲线。

    只要给出相应的BER等级,可以根据概率反推出TJ大小

    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 里面有电路图,代码等。。。这次编程器程序采用了Keil C进行编写(临时学的),代码更容易看懂,也更容易修改。硬件上,电路图还是采用了原来那种直观的画法。
  • 线传感器网络中,需要一个EEPROM来存路由表,93C46系列算是比较老的器件了,速度也不快,但手头正好有几个,就先拿来试验一下,本以为很容易搞定的,却遇到了个奇怪的现象,搞了一阵才解决。在实验之前,查了些93C46...
  • 在学习linux内核驱动时,不论是linux相关的书籍,又或者是直接linux的源码,总是能在linux中看到各种各样的框架,linux内核极其庞杂,linux各种框架理解起来并不容易,如果直接硬着头皮死记硬背,意义也不大。...

    linux 串行通信接口驱动框架

    在学习linux内核驱动时,不论是看linux相关的书籍,又或者是直接看linux的源码,总是能在linux中看到各种各样的框架,linux内核极其庞杂,linux各种框架理解起来并不容易,如果直接硬着头皮死记硬背,意义也不大。

    博主学习东西一直秉持着追本溯源的态度,要弄清一个东西是怎么样的,如果能够了解它的发展,了解它为什么会变成这样,理解起来就非常简单了。抓住主干,沿着线头就可以将整个框架慢慢梳理清楚。

    从i2c开始

    在嵌入式中,不管是单片机还是单板机,i2c作为板级通信协议是非常受欢迎的,尤其是在传感器的使用领域,其主从结构加上对硬件资源的低要求,稳稳地占据着主导地位。

    我们就以i2c协议为例,聊一聊linux内核中串行通信接口框架。
    (注:在这篇文章只讨论大致框架,并不涉及具体细节,linux内核驱动部分的框架分得很细,无法全部覆盖,但求建立一个大体的概念)

    单片机中的i2c

    每个MCU基本上都会集成硬件i2c控制器,在单片机编程中,对于操作硬件i2c控制器,我们只需要操作一些相应的寄存器即可实现数据的收发。

    那如果没有硬件i2c控制器或者i2c控制器不够用呢?

    事情也不麻烦,我们可以使用两个gpio来软件模拟i2c协议,代码不过几十行,虽然i2c协议本身有一定的复杂性,但是如果仅仅是实现通信,在单片机上还是非常简单的。

    单片机中实现i2c程序

    我们不妨回想一下,在单片机中编写一个i2c驱动程序的流程:

    以sht31(这是一个常用的i2c接口的温湿度传感器)为例,刚入行的新手程序员可能这样写主机程序(伪代码):

    int sht31_read_temprature(){                 //读取温度值实现函数
        设置i2c写寄存器,发送i2c器件地址
        设置i2c写寄存器,发送i2c寄存器地址
        设置i2c读
        temperature = 读取目标器件发回的数据
        return temperature;
    }   
    
    int sht31_read_humidity(){                   //读取湿度值实现函数
        设置i2c写寄存器,发送i2c器件地址
        设置i2c写寄存器,发送i2c寄存器地址
        设置i2c读
        humidity = 读取目标器件发回的数据
        return humidity;
    }   
    ....

    程序优化

    每次读写函数都对硬件i2c的寄存器进行设置,很显然,这样的代码有很多重复的部分,我们可以将重复的读写部分提取出来作为公共函数,写成这样:

    array sht31_read_data(sht31数据寄存器地址){
        设置i2c写寄存器,发送i2c器件地址
        设置i2c写寄存器,发送i2c寄存器地址
        设置i2c读
        return 读取目标器件发回的数据;
    }

    所以,上例中的读温湿度就可以写成这样:

    array sht31_read_temprature(){
        return sht31_read_data(sht31温度数据寄存器地址);
    }
    array sht31_read_humidity(){
        return sht31_read_data(sht31湿度数据寄存器地址);
    }
    ...

    经过这一步优化,这个驱动程序就变成了两层:

    • i2c硬件操作部分,比如i2c与设备的读写,在同一平台上,硬件读写的寄存器操作都是一致的。
    • 设备的操作函数,不同的设备有不同的寄存器,对于存储设备而言就是存取数据,对于传感器而言就是读写传感器数据,需要读写设备时,直接调用第一步中的接口,传入不同的参数。

    可以明显看到的是,第一步中的i2c操作函数部分可以被抽象出来。

    这就是软件的分层

    如果你仔细看了上面的示例,基本上就理解了软件分层是什么概念,它其实就是不断地将公共的部分和重复代码提取出来,将其作为一个统一的模块,向外提供访问的接口。

    从宏观上来看程序就被分成了两部分,由于是调用与被调用的关系,所以层次结构可以更明显地体现他们之间的关系。

    分层的好处

    最直观地看过去,软件分层第一个好处就是节省代码空间,代码量更少,层次更清晰,对于代码的可读性和后期的维护都是非常大的好处。

    将相关的数据和操作封装成一个模块,对外提供接口,屏蔽实现细节,便于移植和协作,因为在移植和修改时,只需要修改当前模块的代码,保持对外接口不变即可,减少了移植和维护成本。

    举个例子:在上述的代码中,如果将sht31换成其他i2c设备,我只需要修改设备的操作函数部分,而i2c的读写部分可以复用.又或者同样的设备,切换成了spi协议通信,那么,设备的操作函数部分可以不用修改,只需要将i2c硬件读写换成spi硬件读写。

    程序的再次优化

    在程序的第一次优化中,i2c被抽象出两层:i2c硬件读写层和i2c的应用层(暂且这么命名吧),读写层只负责读写数据,而应用层则根据不同设备进行不同的读写操作,调用读写层接口。

    划分成读写层和驱动层之后,在空间上和程序复用性上已经走了一大步。

    但是在之后的开发中又发现一个问题:对于单个的厂商而言,生产的设备往往具有高度相似的寄存器操作方式,比如对于我们常用的sht3x(温湿度传感器)而言,这些系列的传感器设备之间的不同仅仅是测量范围、测量精度的不同,其他的寄存器配置其实是一样的,有经验的程序员就想到可以抽象出这样一个统一接口来针对所有这些同系列设备:

    sht3x_init(int sht3x_type,int measurement_range,int resolution){
        switch(sht3x_type){
            case sht31:
            set_measurement_range(sht31,measurement_range);
            set_resolution(sht31,resolution);
            break;
            case sht35:
            set_measurement_range(sht35,measurement_range);
            set_resolution(sht35,resolution);
            break;
            case ...
        }
    }  

    仅仅是在设置的时候设置不同的测量范围和精度,而其他的设置,数据读写部分都是完全相同的。

    对于这些同系列的设备,我们同样可以抽象出一个驱动层,以sht3x为例,同系列设备的操作变成这样:

    • i2c硬件读写层。
    • sht3x的公共操作函数,通过调用上层接口实现初始化、设置温度阈值等函数。
    • 具体设备的操作函数,对于sht31而言,通过传入sht31的参数,调用上层接口来读写sht31中的数据,需要传入的参数主要是i2c地址,设置精度等sht3x系列之间的差异化部分。

    这样,对于sht3x而言,用户在使用这一类设备的时候就只需要简单地调用诸如sht3x_init(u8 i2c_addr),sht3x_set_resolution(u8 resolution)这一类的函数即可完成设备的操作,通过传入不同的参数执行不同的操作。

    这里贴上一个图来加深理解:
    mcu_framwork.png

    到这里,对于一个单片机上的i2c设备而言,基本上已经有了比较好的层次结构:

    • i2c硬件读写层
    • 驱动层(主要是解决同系列设备驱动重复的寄存器操作问题)
    • 设备层(应用程序调用上一层接口实现具体的设备读写)

    将上述分层的1.2步集成到系统中,用户只需要调用相应接口直接就可以操作到设备,对用户来说简化了操作流程,对系统来说节省了程序空间。

    到这里,这一份i2c程序就已经比较完善了,当需要添加sht3x设备时,只需要在设备层添加即可。

    当需要添加其他i2c设备时,则需要提供驱动层和设备层的实现而复用硬件读写层。

    当需要将sht3x驱动程序移植到另一个平台时,只需要修改i2c硬件读写层,因为平台之间的i2c读写操作可能不一致,驱动层和设备层不需要修改。

    整个分层的思想就是复用。大大节省了调试时间,在debug的时候也能很方便地定位是哪一部分出了问题,同时可以将程序发布给其他用户,其他用户根据需要可以很方便地进行裁剪移植,避免浪费过多时间在同一件事情上。

    再看看linux中的i2c

    在单片机上可以碰到的问题,在linux系统上同样能碰到,单片机上运行的程序一般而言不会太庞大,驱动部分更是所占甚微,所以设备驱动程序的好坏并不会太过于影响程序的执行效率。

    但是在linux中,有时需要集成大量的驱动设备到系统中,同时内核代码是多人维护的模式,所以也必须采用驱动分层的模式来提高内存使用效率,降低程序耦合性。

    那么,在linux中是否也是像单片机中一样分为i2c硬件读写层、驱动层、设备层即可呢?

    其实大致的思想是一样的:将硬件读写抽象成一层,将同系列产品驱动抽象成一层,将具体设备的添加抽象成顶层,但是具体实现完全不一样。

    与单片机中程序不一样的是:linux中内核空间和用户空间是区分开来的,驱动程序将会被加载到内核中,提供接口给用户进行操作。

    从单片机切换到linux的第一种解决方案

    不难想到的解决方案是:分层模型不变,将上述分层中的设备层改为由驱动层直接在用户空间注册文件接口,用户程序通过用户文件对设备进行操作。

    于是,分层模型变成了这样:

    • i2c硬件读写层
    • 驱动层(主要是解决同系列设备驱动重复问题,同时在用户空间注册用户接口以供访问)
    • 应用层(相对于内核而言,等于单片机分层中的设备层,应用程序通过操作文件调用上一层接口实现具体的设备读写)

    问题就这样得到解决。

    用户在操作用户空间文件接口的时候,依次地通过文件接口传递目标设备的资源对设备进行初始化,各种设置,然后读写即可,对于sht3x而言,这些资源包括i2c地址、精度、阈值等等。

    但是,这样的做法的缺陷是:

    • 提高了驱动程序和应用开发的耦合性,同时驱动程序不具有独立性和安全性,程序的可移植性也很差。
    • 同时,此时的驱动层对应的是同系列设备,注册到用户空间的接口会更加抽象,用户需要对驱动程序进行二次开发.

    想一想,当用户需要使用sht31时,用户还得去阅读sht31的datasheet来查看并设置各种参数,这样驱动和用户程序不分离完全不符合高内聚低耦合的程序思想。

    最理想的状态自然是:

    • 驱动程序本身和驱动程序需要的资源由内核统一管理,这样才能提高驱动和管理资源的独立性,并且拥有较好的额可移植性和安全性,提供尽量简单的操作接口给用户空间。
    • 用户空间只需要直接使用驱动,而不需要对驱动程序进行二次开发。

    第二种解决方案

    既然驱动部分无法针对单一设备,而是针对诸如sht3x这一类设备,那我们为每个单一设备添加一份描述设备信息来提供资源(i2c地址等),比如sht31、sht35分别提供一份设备描述信息,而一个驱动程序可以对应多份设备描述信息。

    当需要使用某个具体设备比如sht31时,再将sht31的设备描述信息和sht3x的驱动程序结合起来,生成一个完整的sht31设备驱动程序,并在用户空间注册sht31文件接口,这样文件接口就可以针对具体的设备而实现具体的操作功能,用户可以通过简单的参数选择而直接使用。

    同时将设备描述信息同时注册到内核中,由内核统一管理,提高了驱动和资源管理的独立性和安全性,同时移植性能较好。

    这样,在分层模型中,我们将设备描述信息(设备资源)部分和驱动程序放置在同一层,驱动模型就变成了这样:

    1. i2c硬件读写层
    2. 驱动部分(提供系列设备的公共驱动部分)-------设备部分(提供单一设备的资源,驱动程序获取资源进行相应配置,与驱动层为一对多的关系),统称为驱动层
    3. 应用层(通过上层提供的接口对设备进行访问)

    这样的分层有个好处,在第二层驱动层中,直接实现了每个单一设备的程序,对应用层提供简便的访问接口,这样在用户层不用再进行复杂的设置,就可以直接对设备进行读写。

    它的分层是这样的:
    linux_framwork.png

    i2c硬件读写层

    linux i2c设备驱动程序中的硬件读写层由struct i2c_adapter来描述,它的内容是这样的:
    struct i2c_adapter {
    ...
    const struct i2c_algorithm algo; / the algorithm to access the bus /
    void
    algo_data;
    int nr;
    char name[48];
    ...
    };
    其中struct i2c_algorithm *algo结构体中master_xfer函数指针指向i2c的硬件读写函数,我们可以简单地认为一个struct i2c_adapter结构体描述一个硬件i2c控制器。在驱动编写的过程中,这一层的实现由系统提供。驱动编写者只需要调用i2c_get_adapter()接口来获取相应的adapter.

    驱动层(中间层,包含设备部分和驱动部分)

    linux总线机制

    在上面的分层讨论中可知:驱动层由驱动部分和设备部分组成,驱动部分由struct i2c_driver描述,而设备部分由struct i2c_device部分组成。

    这两部分虽然被我们分在同一层,但是这是相互独立的,当添加进一个device或者driver,会根据某些条件寻找匹配的driver或者device,那这一部分匹配谁来做呢?

    这就不得不提到linux中的总线机制,i2c总线担任了这个衔接的角色,诚然,i2c总线也属于总线的一种,i2c总线在系统启动时被注册到系统中,管理i2c设备。

    我们先来看看描述总线的结构体:

    struct bus_type {
        ....
        const char      *name;
        int (*match)(struct device *dev, struct device_driver *drv);
        int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
        int (*probe)(struct device *dev);
        int (*remove)(struct device *dev);
        void (*shutdown)(struct device *dev);
        int (*online)(struct device *dev);
        int (*offline)(struct device *dev);
        int (*suspend)(struct device *dev, pm_message_t state);
        int (*resume)(struct device *dev);
        struct subsys_private *p;
    };
    
    struct subsys_private {
        ...
        struct klist klist_devices;
        struct klist klist_drivers;
        unsigned int drivers_autoprobe:1;
        ...
    };

    这个结构体描述了linux中各种各样的sub bus,比如spi,i2c,platform bus,可以看到,这个结构体中有一系列的函数,在struct subsys_private结构体定义的指针p中,有struct klist klist_devices和struct klist klist_drivers这两项。

    当我们向i2c bus注册一个driver时,这个driver被添加到klist_drivers这个链表中,当向i2c bus注册一个device时,这个device被添加到klist_devices这个链表中。

    每有一个添加行为,都将调用总线的match函数,遍历两个链表,为新添加的device或者driver寻找对应的匹配,一旦匹配上,就调用probe函数,在probe函数中执行设备的初始化和创建用户操作接口。

    应用层

    在总线的(或者驱动部分的)probe函数被执行时,在/dev目录下创建对应的文件,例如/dev/sht3x,用户程序通过读写/dev/sht3x文件来操作设备。

    同时,也可以在/sys目录下生成相应的操作文件来操作设备,应用层直接面对用户,所以接口理应是简单易用的。

    驱动开发者的工作

    一般来说,如果只是开发i2c驱动,而不需要为新的芯片移植驱动的话,我们只需要在驱动层做相应的工作,并向应用层提供接口。i2c硬件读写层已经集成在系统中。

    抽象化仍在进行中

    上文中提到,当驱动开发者想要开发驱动时,在驱动层编写一个driver部分,添加到i2c总线中,同时编写一个device部分,添加到i2c总线中。

    driver部分主要包含了所有的操作接口,而device部分提供相应的资源。

    但是,随着设备的增长,同时由于device部分总是静态定义在文件中,导致这一部分占用的内核空间越来越大,而且,最主要的问题时,对于资源来说,大多都是一些重复的定义:比如时钟、定时器、引脚中断、i2c地址等同类型资源。

    按照一贯的风格,对于大量的重复定义,我们必须对其进行抽象化,于是linus一怒之下对其进行大刀阔斧的整改,于是设备树横空出世,是的,设备树就是针对各种总线(i2c bus,platform bus等等)的device资源部分进行整合。

    device_tree_fram.png

    将设备部分的静态描述转换成设备树的形式,由内核在加载时进行解析,这样节省了大量的空间。

    如果有兴趣可以看看博主的设备树解析篇:linux设备树解析--从dtb格式开始

    小结

    总的来说,分层即是一种抽象,将重复部分的代码不断地提取出来作为一个独立的模块,因为模块之间是调用与被调用的关系,所以看起来就是一种层次结构。

    高内聚,低耦合,这是程序设计界的六字箴言。

    不管是linux还是其他操作系统,又或者是其他应用程序,将程序模块化都是一种很好的编程习惯,linux内核的层次结构也是这种思想的产物。

    这篇文章只是简单地分析了linux分层机制的由来,从原理上理解为什么会有这样的层次结构,事实上,由于linux系统的复杂性,linux的驱动框架并非这么简单地分成三个部分,博主只是抽去了大部分细节,展现了最粗犷的框架。

    接下来,博主还会带你走进linux内核代码,剖析整个i2c框架的函数调用流程。

    好了,关于linux驱动框架的讨论就到此为止啦,如果朋友们对于这个有什么疑问或者发现有文章中有什么错误,欢迎留言

    原创博客,转载请注明出处!

    祝各位早日实现项目丛中过,bug不沾身.

    转载于:https://www.cnblogs.com/downey-blog/p/10491953.html

    展开全文
  • 作者:高速先生团队 串行/串行总线序列文章 讲差分线,信号的模态是一个绕不过去的话题。记得我在刚接触SI的时候,曾被这些概念弄得伤透了脑筋。差分,共模,奇模,偶模……这些概念经常把人绕的很晕。但是为了...
  • 多线程访问同一个资源进行读写操作,就很容易出一些问题(比如我们常见的读者写者,生产者消费者模型)所以我们会选择对他们设置信号量或者加锁,来限制同一个时刻只有一个线程对某个对象进行操作。多线程是一个蛮复杂...
  • 了网上非常多的运行代码,很多都是重复的再说一件事,可能对于java老鸟来说,理解java的多线程是非常容易的事情,但是对于我这样的菜鸟来说,这个实在有点难,可能是我太菜了,网上重复的陈述对于我理解这个问题一点帮助...
  • 如果数据比较多,很容易看串行。这时,可以为表格添加隔行换色并且鼠标指向行变色功能。   二 代码 table{ border:0;border-collapse:collapse;} /*设置表格整体样式*/ td{font:normal 12px/17px Arial;...
  • 在使用excel标注数据时遇到了个问题,咋标注过程中容易看串行,所以就想怎么样能在选择一个单元格的同时高亮这一行? 搜索到了如下结果,实践有效! 同时按Alt和F11,进入VB界面 在左上角的窗口中选择你要实现...
  • 实际实现起来并不是很困难,前提是你仔细过hmm的原理,然后很多实现就照着公式写出对应的代码,比如前向算法,后向算法,参数更新都是有明确的公式的,只需要对应写成代码,这里需要提到2点技巧。 1,所有概率...
  • 了解Java8新特性,这个就够了! 并行流与串行流 并行流:就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。 Java8中将并行进行...
  • 随着USB串行总线的流行,USB设备越来越多,价格也非常便宜。U盘我们用得多了,USB鼠标和键盘,我们也经常在用。所以,很多人认为,做一个USB的数据采集卡,一定没什么技术含量。其实不然,对于我们做应用科技的...
  • 并发编程可选择的方式有多进程、多线程和多协程。作者在另一本书《软件架构设计:大型网站技术架构与业务架构融合之道》中,曾...对于人脑的认知来说,“代码一行行串行”当然最容易理解。但在多线程下,多个线程的...
  • “ 串口是我们平时在...”01 串行通信首先串行通信是个啥。我们通常说的串行通信其实是一种想法,只是种想法。串行通信的思想是将数字信号通过一根数据线一个Bit一个Bit的发送出去,这种想法是相对于并行通信...
  • “ 串口是我们平时在...”01 串行通信首先串行通信是个啥。我们通常说的串行通信其实是一种想法,只是种想法。串行通信的思想是将数字信号通过一根数据线一个Bit一个Bit的发送出去,这种想法是相对于并行通信...
  • 学生信息管理系统

    热门讨论 2016-11-28 16:46:14
    先说说敲学生的感受吧,第一点比白利还又红皮书的代码都多而且还非常容易敲错代码和看串行,而且一串行就是一大片现在马上就让师傅验收了 让师傅验收之前先让自己同期的点一点看看那还又什么错误 就怎一点,本来很...
  • .NET4.0开始引入Task,它的出现大大简化了异步编程的复杂度,相较于传统的Thread和ThreadPool,Task更加容易控制和使用,下面就来看看它的具体用法。 1、一个简单的串行程序 串行程序大家肯定不陌生,说白了就是从上...
  • STC89C52RC串口收发例程,可直接拿来用,串口通信,例程简单,很容易看明白。 串行口为同步移位寄存器的输入输出方式。主要用于扩展并行输入或输出口。数据由RXD(P3.0)引脚输入或输出,同步移位脉冲由TXD(P3.1)...
  • 谁说并行就一定好了

    2016-05-01 12:07:53
    2.串行:管理简单,容易单项提升,容易扩展,不容易出问题。 3.cpu,就是不断增加制程,不能提高频率,现在也在往多核发展。 4.先scale UP,再scale out,先把单个发展最优,然后适度增加并行,但也不可太多并行,...
  • 代码也很紧凑,容易阅读。我们从读取文件的字符串行、避免读取写入同一文件、使用内存映射随机访问文件这三个文件处理中的典型案例来了解一下。文件处理场景大家都很熟悉,因此闲言少叙,直接代码。读取文件的字符...
  • 但Floyd的并行算法却很难搜到,github倒是有一些,但不容易运行成功,这里对这个算法的并行化进行详细的讲解,结合论文以及实际实现。 1.Floyd的串行算法 贴一下代码,理解请其他博客。 /***********在CPU中...
  • 背景:目前比如大型电商淘宝,京东的商品详情页和店铺详情页都有大量的信息,目前如果一个主接口串行的调用,则会出现性能不足的情况,因此可以考虑使用多线程的调用,看看具体的业务是否是cpu密集型 还是 io密集型...

空空如也

空空如也

1 2 3 4
收藏数 78
精华内容 31
关键字:

容易看串行