编辑
2024-03-29
学习记录
0
请注意,本文编写于 440 天前,最后修改于 240 天前,其中某些信息可能已经过时。

目录

相关概念
0.设计原则
1.编程语言
1.列表、数组、链表的区别:
2.define和const的区别
3.指针和引用的区别
4.[C/C++] const int 与 int const 的区别
5. volatile关键字:
6. inline关键字:
4. 有符号和无符号运算,强制转换为无符号。
5.C语言const、static、extern、volatile关键字
6.B树 B+树 红黑树定义区别
2.ARM架构
1.内存映射的原理
2.进程和线程的区别:
3.进程间的通信方式:
4.黑盒测试、白盒测试、灰色测试
5.TCP和UDP的区别:
OSI网络模型
3.三次握手、四次挥手流程
3.TCP通信为什么连接的时候是三次握手,关闭的时候却是四次握手?
4.进程之间的通信方式
5.堆和栈的区别
6. FreeRTOS和Linux的区别
7.FreeRTOS处理中断
8.Linux处理中断
9.freertos和linux怎么调度
10. ARM(Advanced RISC Machines)处理器分类
3单板相关
1.STM32启动方式:
2.STM32上电到main函数之前做了什么事?
3.IIC为什么要加上拉电阻,为什么使用开漏输出<br>上拉电阻原因
4. 进程的内存空间从低地址到高地址内存布局依次是:
5. 推挽输出和开漏输出的区别:
4.FreeRTOS

相关概念

0.设计原则

六大设计原则:

  1. 单一职责原则,理解:不同的类具备不同的职责,各司其职。做系统设计是,如果发现有一个类拥有了两种职责,那么就要问一个问题:可以将这个类分成两个类吗?如果真的有必要,那就分开,千万不要让一个类干的事情太多。总结:一个类只承担一个职责
  2. 开放封闭原则,理解:类、模块、函数,可以去扩展,但不要去修改。如果要修改代码,尽量用继承或组合的方式来扩展类的功能,而不是直接修改类的代码。当然,如果能保证对整个架构不会产生任何影响,那就没必要搞的那么复杂,直接改这个类吧。总结:对软件实体的改动,最好用扩展而非修改的方式。
  3. 里式替换原则,理解:父类可被子类替换,但反之不一定成立。也就是说,代码中可以将父类全部替换为子类,程序不会出现异常,但反过来就不一定了。总结:在继承类时,务必重写(override)父类中所有的方法,尤其需要注意父类的protected方法(它们往往是让你重写的),子类尽量不要暴露自己的public方法供外界调用。
  4. 最少知识原则,理解:尽量减少对象之间的交互,从而减小类之间的耦合。在做系统设计时,不要让一个类依赖于太多其他的类,需尽量减小依赖关系,否则死都不知道怎么死的。总结:一定要做到:低耦合、高内聚。
  5. 接口隔离原则,理解:不要对外暴露没有实际意义的接口。也就是说,尽量保证接口的实用性。当需要对外暴露接口时,需要再三斟酌,若没必要对外提供就删了吧,因为一旦提供了就意味着,将来要多做一件事情,何苦给自己找事做呢。总结:不要对外暴露没有实际意义的接口。
  6. 依赖倒置原则,理解:高层模块不应该依赖于底层模块,而应该依赖于抽象。抽象不应依赖于细节,细节应依赖于抽象。应该面向接口编程,不该面向实现类编程。面向实现类编程相当于就事论事,那是正向依赖;面向接口编程,相当于透过现象看本质,抓住事务的共性,那就是反向依赖,即依赖倒置。总结:面向接口编程,提取出事务的本质和共性。

23种设计模式:

  1. 创建型模式(5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  2. 结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  3. 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

1.编程语言

1.列表、数组、链表的区别:

  1. 列表可以存放不同的数据类型、数组和链表是存放相同数据类型的集合;
  2. 列表和数组内存连续,链表内存不连续;
  3. 列表不可以进行数学四则运算,数组可以进行数学四则运算;
  4. 数组和列表访问速度比链表快;
  5. 链表增加删除操作比数组和列表快;

2.define和const的区别

  1. define是预处理指令,用于创建符号常量。const是C和C++的关键字,用于创建具有常量值的变量,本质是只读变量。
  2. define在预处理阶段执行。const在编译阶段执行。
  3. define没有类型检查,仅进行文本替换。const有类型检查,可以与变量类型关联。

3.指针和引用的区别

  1. 指针:指针是一个变量,保存着内存地址。引用:引用是已存在变量的别名,没有自己的内存地址。
  2. 指针可以具有空值(NULL),引用不能为空,必须在初始化时指向一个有效的对象。
  3. 可以修改指针的指向,可以将指针重新赋值为另一个地址。一旦引用被初始化,它始终指向同一个对象,不可更改。
  4. 指针需要额外的内存空间来存储地址值。引用不需要额外的内存空间,因为它是对已存在变量的别名。

4.[C/C++] const int* 与 int const* 的区别

先确定一个规则:const默认与左边结合,左边没有东西则与右边结合。在这个规则下进行分析。 1.const int* a const与int结合,因此变量a是一个指向常量整型的指针。 2.int const * a const与int结合,因此变量a与1同。 3.int* const a const与*结合,因此变量a是一个指向整型的常量指针。 4.const int* const a 第1个const与int结合,第2个const与*结合,因此变量a是一个指向常量整型的常量指针。 5.int const * const a 第1个const与int结合,第2个const与*结合,因此变量a与上同。 6.int const * const * a 第1个const与int结合,第2个const与左边的*结合,而变量a前还有1个多出来的*,因此变量a是一个二级指针,即指向4/5中常量指针的指针。 7.int const * const * const a 第1个const与int结合,第2个const与左边的*结合,第3个const也与左边的*结合,因此变量a是一个常量二级指针,即指向4/5中常量指针的常量指针。 附注:1.常量整型不可改值。2.常量指针不能修改指针的指向。

5. volatile关键字:

  1. volatile 可以保证对特殊地址的稳定访问(变量地址);
  2. 到地址去读取,告诉编译器不要优化;
  3. 用处:a. 中断服务程序中修改的变量需要加 volatile;b. 全局变量;c. 多任务环境下各任务间共享的标志应该加 volatile;

6. inline关键字:

  1. inline 是C++引入的关键字
  2. inline 只适合涵数体内代码简单的涵数使用(switch while for)
  3. inline 是一种"用于实现的关键字"
  4. 内联是以代码膨胀(复制)为代价
  5. 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大,(不能有switch while)
  6. inline 只适合涵数体内代码简单的涵数使用

4. 有符号和无符号运算,强制转换为无符号。

5.C语言const、static、extern、volatile关键字

  1. const,只读常量,不可以修改和二次赋值,可以用在变量声明时对变量进行初始化,也可以修饰指针,有常量指针,指向的内容(*p)不能改变,还有指针常量,指针本身(p)不能改变;也可以修饰引用,即变量的别名;也可以修饰函数的形参,即传入的参数在函数内数值不能改变;还可以修饰函数返回值;
  2. static,加static修饰,为静态变量,存储在静态内存中,在整个程序执行过程中一直存在,不像局部变量,当所在的函数执行完后就自行销毁了,整个程序结束之后static变量才会被回收,这是修饰局部变量,当修饰全局变量和修饰函数的时候,会修改标识符的链接属性,从外部链接属性变为内部链接属性,即只能在当前源文件中访问不能在其他源文件中访问;也可在面向对象的编程中,修饰类内数据成员和成员函数,修饰类内成员后,对于类的每个对象,它是共有的,修饰成员函数也是对每个对象共有,所以不能有this指针。
  3. extern,修饰变量表示改变量在别的文件中已有声明了,如果在另一个文件中将这个变量声明为外部变量,那么这个变量的作用域将被扩展到另外一个文件中。
  4. volatile,易变的关键词,告诉编译系统所修饰的变量可能会被意想不到地改变。那么编译器就不会对代码进行优化,每次遇到这个变量就去内存中去读,用于中断服务程序中供其他程序检测的变量、用于多任务环境下,各任务间共享的标志、用于存储器映射的硬件寄存器的定于。

6.B树 B+树 红黑树定义区别

B树:

定义: B树是一种自平衡的多路搜索树,它可以有多个子节点,不同于二叉树的是,一个节点可以有超过两个的子节点。B树特别适合用于读写相对较大的数据块的存储系统,如磁盘。

数据结构: 一个B树的节点可能包含多个键(数据项)和子指针。节点中的键是有序的,并且每个键的左侧子树包含的键都比它小,右侧子树包含的键都比它大。B树通过重新分布键和指针,分裂和合并节点来维持平衡。

优点: 减少了磁盘I/O操作。保持了树的平衡。对于大型数据集的查找和顺序访问非常高效。

缺点: 节点分裂和合并的过程相对复杂。当数据经常插入和删除时,维护成本较高。

应用: 数据库索引。文件系统。

B+树:

定义: B+树是B树的变种,所有的值都在叶子节点上,并且叶子节点是通过指针连接的,这样就提供了对数据的顺序访问。内部节点(非叶子节点)只存储键值,并作为索引使用。

数据结构: 与B树类似,但B+树有两个不同点:一是非叶子节点不存储数据,仅用于索引;二是所有叶子节点之间都是相互链接的,这样就支持了快速的顺序遍历。

优点: 所有的查询都要查找到叶子节点,查询性能稳定。叶子节点形成了一个有序链表,便于全范围扫描。

缺点: 由于数据只存在于叶子节点,所以可能需要更多的I/O操作来达到叶子节点。

应用: 数据库索引(特别是范围查询和顺序访问)。

红黑树:

定义: 红黑树是一种自平衡的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是红色或黑色。通过对任何一条从根到叶子的路径上各个节点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出两倍,因而是近似平衡的。

数据结构: 每个节点包含颜色、键值、左右子节点以及指向父节点的指针。红黑树的约束包括:每个节点要么是红色,要么是黑色。根节点是黑色。所有叶子(NIL节点)是黑色。如果一个节点是红色的,则它的子节点必须是黑色的(反之不一定)。从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

优点: 保证最长路径不会超过最短路径的两倍,因而是平衡的。在实际应用中,插入、删除、查找操作都有很好的性能。

缺点: 算法实现相对复杂。在最坏情况下,可能需要多次颜色变更和树旋转。

应用: 关联数组。高级语言的数据结构库,如C++的STL中的map和set。

B树与B+树与红黑树的区别

  1. B树和B+树都是多路平衡查找树,而红黑树是二叉平衡查找树。
  2. B树中节点存储键和数据,而B+树的数据仅存储在叶子节点,内部节点只存键。
  3. B+树的叶子节点通过指针相连,便于全范围扫描,而B树不是。
  4. 红黑树的操作相对于B树和B+树来说更快,因为它是二叉的,但在处理大量数据时,由于B树和B+树减少了磁盘I/O,可能会更有效率。
  5. B树和B+树通常用于数据库和文件系统中,红黑树多用于内存中数据结构的实现。

2.ARM架构

1.内存映射的原理

  1. 将一块内存空间映射到不同的进程空间中

2.进程和线程的区别:

  1. 进程是一个具有独立功能的程序,是系统进行资源分配和调度的独立单位;
  2. 线程是进程中的一个执行任务,是处理器调度和分配的基本单位;
  3. 一个进程至少拥有一个线程,多个线程可以共享数据;

3.进程间的通信方式:

信号量、消息队列、共享内存、管道、套接字;

4.黑盒测试、白盒测试、灰色测试

  1. 黑盒测试是测试人员不了解正在测试的软件的内部结构和源代码。与用户界面进行交互,测试其功能。单元,集成,系统和验收;
  2. 白盒测试方法的目标是对软件的内部结构及其背后的逻辑进行分析。找一些边界情况进行测试;
  3. 灰盒测试介于两者中间;

5.TCP和UDP的区别:

  1. TCP和UDP是计算机网络中传输层的两个主要协议,主要有以下几个方面的区别:
  2. 连接特性方面:
    1. TCP是面向连接的协议。在传输数据之前,需要通过三次握手来建立连接,并在数据传输完成后通过四次挥手来释放连接。这种连接机制确保了数据传输的可靠性和顺序性;
    2. UDP则是无连接的协议。发送数据前不需要建立连接,直接发送数据包,这使得UDP在传输数据时更加灵活和高效;
  3. 可靠性方面:
    1. TCP使用校验和、重传控制、序号标识、滑动窗口和确认应答等机制来确保数据的无差错、不丢失、不重复且按序到达;
    2. UDP则提供尽最大努力交付的服务,不保证数据的可靠传输。在数据传输过程中,如果发生丢包或乱序,UDP不会进行重传或顺序控制;
  4. 效率与实时性:
    1. UDP具有较好的实时性和较高的工作效率,因为它不需要建立连接和进行复杂的控制操作;
    2. TCP虽然提供了可靠的数据传输,但由于其复杂的控制机制,相对UDP而言效率较低;
  5. 通信模式:
    1. TCP连接只能是点对点的,即一条TCP连接只能连接两个端点;
    2. UDP则支持一对一、一对多、多对一和多对多的交互通信模式,这使得UDP在广播和多播等场景中更具优势;
  6. 首部开销:
    1. TCP的首部较大,通常为20字节,这增加了每个数据包的开销;
    2. UDP的首部较小,只有8字节,减少了数据包的开销,提高了传输效率;

OSI网络模型

层级名称作用协议关键词
7应用层各类网络服务HTTP、FTP
6表示层数据编码、格式转换、加密LPP、NBSSP
5会话层维护会话SSL、TLS、DAP、LDAP
4传输层建立主机端到端的连接(应用间的通信)TCP、UDP端口号、TCP、UDP
3网络层路由选择,控制数据包在设备间的转发(主机间通信)**IP、ICMP、路由器、**RIP、IGMP、OSPFIP地址、路由器、ping通
2数据链路层将比特流封装成数据帧(数据帧、网卡间通信)ARP网卡、交换机、PPTP、L2TP、ATMPMAC地址、网卡
1物理层定义电平、传输介质、物理接口光纤、集线器、中继器等物理器件

3.三次握手、四次挥手流程

  1. 第一次握手:Client将标志位SYN(建立新连接)置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
  2. 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK(确认)都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
  3. 第三次握手:Client收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送给Server,Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
  4. 第一次挥手:客户端向服务器发起请求释放连接的TCP报文,置FIN为1。客户端进入终止等待-1阶段。
  5. 第二次挥手:服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,服务器端进入CLOSE-WAIT阶段,并向客户端发送一段TCP报文。客户端收到后进入种植等待-2阶段。
  6. 第三次挥手:服务器做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文。。此时服务器进入最后确认阶段。
  7. 第四次挥手:客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,于是进入时间等待阶段,并向服务器端发送一段报文。注意:第四次挥手后客户端不会立即进入closed阶段,而是等待2MSL再关闭。

3.TCP通信为什么连接的时候是三次握手,关闭的时候却是四次握手?

  1. 因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

4.进程之间的通信方式

  1. 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  2. 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  3. 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  4. 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
  5. 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  6. 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
  7. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

5.堆和栈的区别

  1. 程序内存布局场景下,堆与栈表示两种内存管理方式。

    1. 栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等;
    2. 堆由开发人员分配和释放, 若开发人员不释放,程序结束时由 OS 回收,分配方式类似于链表。
    3. 堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。
  2. 数据结构场景下,堆与栈表示两种常用的数据结构。

    1. 栈是一种线性结构,所以可以使用数组或链表(单向链表、双向链表或循环链表)作为底层数据结构,拥有“先进后出”的特性;
    2. 堆是一种常用的树形结构,是一种特殊的完全二叉树,当且仅当满足所有节点的值总是不大于或不小于其父节点的值的完全二叉树被称之为堆。堆的这一特性称之为堆序性。因此,在一个堆中,根节点是最大(或最小)节点。如果根节点最小,称之为小顶堆(或小根堆),如果根节点最大,称之为大顶堆(或大根堆)。堆的左右孩子没有大小的顺序。

6. FreeRTOS和Linux的区别

FreeRTOS和Linux是两个不同的嵌入式操作系统,它们在设计理念、架构和功能上存在一些异同。

  • 设计理念:FreeRTOS是一个实时操作系统(RTOS),专注于实时性和资源占用的最小化。它被设计用于低功耗、有限资源的嵌入式系统。而Linux是一个通用的操作系统,注重功能丰富性和多任务处理能力。它被广泛应用于服务器、PC和嵌入式系统。
  • 架构:FreeRTOS采用了基于优先级的抢占式调度算法,具有较低的内存占用和响应时间延迟。它通常以任务为单位进行调度,任务之间共享资源需要通过信号量、互斥锁等机制进行同步和互斥操作。而Linux采用了时间片轮转调度算法,支持多线程和多进程并发执行。它提供了丰富的进程间通信(IPC)机制,如管道、信号量、消息队列等。
  • 功能:FreeRTOS提供了基本的任务管理、内存管理、事件驱动等功能,并且可以根据具体需求进行定制和扩展。它适用于对实时性要求较高的应用场景,如工业控制、汽车电子等。而Linux提供了更为完善的文件系统、网络协议栈、设备驱动等功能,适用于需要复杂功能和大规模软件开发的应用领域。

总体而言,FreeRTOS和Linux在实时性能、资源占用和功能丰富度上存在差异。选择使用哪个操作系统取决于具体的应用需求和资源限制。

7.FreeRTOS处理中断

Cortex-M内核支持中断优先级配置,中断优先级数值越小,优先级越高,与FreeRTOS任务优先级相反,任务优先级数值越小,优先级越低

FreeRTOS中,实际上是没有中断的,中断还是由硬件产生。以STM32为例,中断请求的响应过程还是由STM32的中断服务函数完成,而FreeRTOS在其中的作用可以说成是唤醒某个阻塞或就绪任务。

也就是说,在硬件STM32的中断函数中,需要使用信号量或事件等完成中断与FreeRTOS系统任务的通信,实现通过中断函数来唤醒一次或多次FreeRTOS系统任务。

假设我们正在开发一个嵌入式系统,其中一个传感器通过中断方式通知系统有新的数据可用。我们将使用 FreeRTOS 来处理这个中断事件。在这个示例中,传感器中断触发 vSensorISR 中断服务例程,该例程通过释放信号量 xSemaphore 通知等待的任务有中断事件发生。任务函数 vTaskFunction 通过等待信号量的方式实现对中断事件的处理。这种方式保证了中断处理的实时性,同时避免了在中断服务例程中直接调用 FreeRTOS API。

8.Linux处理中断

玩过 MCU 的人都知道,中断服务程序的设计最好是快速完成任务并退出,因为此刻系统处于被中断中。但是在 ISR 中又有一些必须完成的事情,比如:清中断标志,读/写数据,寄存器操作等。

在 Linux 中,同样也是这个要求,希望尽快的完成 ISR。但事与愿违,有些 ISR 中任务繁重,会消耗很多时间,导致响应速度变差。Linux 中针对这种情况,将中断分为了两部分:

  1. 上半部(top half):收到一个中断,立即执行,有严格的时间限制,只做一些必要的工作,比如:应答,复位等。这些工作都是在所有中断被禁止的情况下完成的。

  2. 底半部(bottom half):能够被推迟到后面完成的任务会在底半部进行。在适合的时机,下半部会被开中断执行。

上半部立即执行,下半部放在工作队列或者内核线程去处理。

9.freertos和linux怎么调度

freertos: 每个任务都有一个任务控制块(TCB),当任务发生切换后,选择就绪任务列表里优先级最高的任务轮流执行。

任务在就绪状态下,每个优先级都有一个对应的列表,从就绪任务列表中高优先级列表往下找,找到第一个非空列表轮流执行里面的任务。

列表由列表头和列表项组成,列表头和列表项组成一个双向循环链表。

调度过程:在移植时,我们把系统时钟中断xPortSysTickHandler加入到了中断向量表,这个中断周期设置为1ms。这个中断是系统的核心,我们称作调度器,在这里会调用xTaskIncrementTick()把时间计数值加1,并检查有哪些延时任务到期了,将其从延时任务列表里移除并加入到就绪列表里。如果到期的任务优先级>=当前任务则开始一次任务切换。如果当前任务就绪态里有多个任务,也需要切换任务,优先级相同需要在一个系统时钟周期的时间片里轮流执行每个任务。另外在应用程序里也可以通过设置xYieldPending的值来通知调度器进行任务切换。

任务调度方式:

  1. 抢占式:最高优先级的任务一旦就绪,总能得到CPU的执行权;它抢占了低优先级的运行机会。在抢占式调度系统中,总是运行最高优先级的任务。如果有好几个最高优先级的任务,则可以时间轮转调度。
  2. 协作式:本质是任务运行一段时间放弃cpu运行权让其他任务运行,否则会一直占用cpu,其他任务无法执行。
  3. 时间片轮转:让相同优先级的几个任务轮流运行,每个任务运行一个时间片,任务在时间片运行完之后,操作系统自动切换到下一个任务运行;在任务运行的时间片中,也可以提前让出CPU运行权,把它交给下一个任务运行。如果高优先级任务不阻塞,低优先级任务无法运行。

Linux调度

Linux把任务主要分为两类:

  1. 实时任务:有很高的优先级需要尽快完成交付;
  2. 普通任务:没有一定的实时性要求,尽快完成交付即可。

当系统中存在实时任务时,调度程序会优先选择实时任务执行,普通任务将得不到执行的机会。同时不同类型的任务有不同的调度策略。

在 CPU 的角度看进程行为的话,可以分为两类:

  1. CPU 消耗型:此类进程就是一直占用 CPU 计算,CPU 利用率很高
  2. IO 消耗型:此类进程会涉及到 IO,需要和用户交互,比如键盘输入,占用 CPU 不是很高,只需要 CPU 的一部分计算,大多数时间是在等待 IO

CFS是 Completely Fair Scheduler 简称,即完全公平调度器。CFS 调度器和以往的调度器不同之处在于没有固定时间片的概念,而是公平分配 CPU 使用的时间。比如:2个优先级相同的任务在一个 CPU 上运行,那么每个任务都将会分配一半的 CPU 运行时间,这就是要实现的公平。

但现实中,必然是有的任务优先级高,有的任务优先级低。CFS 调度器引入权重 weight 的概念,用 weight 代表任务的优先级,各个任务按照 weight 的比例分配 CPU 的时间。比如:2个任务A和B,A的权重是1024,B的权重是2048,则A占 1024/(1024+2048) = 33.3% 的 CPU 时间,B占 2048/(1024+2048)=66.7% 的 CPU 时间。

10. ARM(Advanced RISC Machines)处理器分类

目前,ARM处理器(内核)分为5类:Cortex-A、Cortex-R、Cortex-M、Machine Learning、SecurCore。前3种我们大部分人都听说过,见下图:

image.png

A系列主要用来跑Linux

M系列就是本科玩过的单片机,可以跑FreeRTOS:

  1. STM32 F0xx系列(M0 48MHZ)
  2. STM32 Lxxx系列(M3 32MHZ)
  3. STM32 F1xx系列(M3 72MHZ)
  4. STM32 F2xx系列(M3 120MHZ)
  5. STM32 F3xx系列(M3 120MHZ)
  6. STM32 F4xx系列(M4 168MHZ)

3单板相关

1.STM32启动方式:

  1. 从Flash启动,将Flash地址0x0800 0000映射到0x00000000,这样启动以后就相当于从0x08000000开始的,这是我们最常用的模式;
  2. 从SRAM启动,将SRAM地址0x20000000映射到0x00000000,这样启动以后就相当于从0x20000000开始的,用于调试,笔者基本没用过
  3. 从系统存储器启动(System memory),将系统存储器地址x1FFFF000映射到0x00000000,这样启动以后就相当于从0x1FFFF000开始执行的,值得注意的是这个系统存储器里面存储的其实是STM32自带的Bootloader代码,这其实是一个官方的IAP,它提供了可以通过UART1接口将用户的代码下载到Flash中的功能,然后将boot0置0,复位单片机,便可以Flash启动。

2.STM32上电到main函数之前做了什么事?

  1. 初始化堆栈指针SP=_initial_sp,启动方式不同,指向地址不同。
  2. 初始化PC(通用寄存器指针)指针=Reset_Handler,若果是Flash启动就是0x0800 0000。
  3. SystemInit(配置系统时钟)
  4. __main(MDK自带的函数),初始化堆栈,.data .bss。
  5. 最终调用main 函数去到C 的世界

3.IIC为什么要加上拉电阻,为什么使用开漏输出
上拉电阻原因

  1. 当IIC总线在空闲状态,SDA和SCL需要处于高电平状态。
  2. 开漏输出无法输出高电平,使用上拉电阻可以完成高低电平之间的转换。
    开漏输出原因
  3. 假如使用推挽输出可能导致器件的烧毁
  4. 实现线与功能

4. 进程的内存空间从低地址到高地址内存布局依次是:

保留区 –> 文本段(包含程序和字符串常量)–>初始化的变量(.data,包含全局变量和静态变量)—>未初始化的变量(.bss,包含全局变量和静态变量)—>堆—>共享库或mmap—>栈–>命令函参数(main函数的参数)–>环境变量—>内核空间

5. 推挽输出和开漏输出的区别:

推挽输出的最大特点是可以真正能真正的输出高电平和低电平,在两种电平下都具有驱动能力。常说的与推挽输出相对的就是开漏输出,对于开漏输出和推挽输出的区别最普遍的说法就是开漏输出无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动。所谓的驱动能力,就是指输出电流的能力。

4.FreeRTOS

  1. 创建任务函数:xTaskCreate();
  2. 删除任务:vTaskDelete( NULL );传入NULL参数表示删除的是当前任务。
  3. 启动调度器任务开始执行:vTaskStartScheduler();
  4. 调度器启动之后修改任务优先级:vTaskPrioritySet();
  5. 查询任务的优先级:uxTaskPriorityGet()
  6. 任务挂起:vTaskSuspend()
  7. 处于挂起状态的任务唤醒:vTaskResume()或vTaskResumeFromISR()
  8. 任务的状态:运行态、阻塞态(正在等待某个事件)、挂起状态、就绪状态
  9. 延迟使用函数:vTaskDelay(250 / portTICK_RATE_MS)进入阻塞态250ms
  10. 空闲任务钩子函数:void vApplicationIdleHook( void )
  11. 打印字符串和整型值:vPrintStringAndNumber()
  12. 打印字符串:vPrintString( "hello world\r\n" );
  13. FreeRTOS 中所有的通信与同步机制都是基于队列实现的。
  14. 创建队列:xQueueCreate()返回一个 xQueueHandle 句柄以便于对其创建的队列进行引用。
  15. 队列数据赋值:xQueueSendToBack()或xQueueSend()将数据发送到队列尾,xQueueSendToFront()将数据发送到队列首部。在中断中使用xQueueSendToFrontFromISR()与xQueueSendToBackFromISR()。
  16. 读取队列中的值:xQueueReceive()用于从队列中接收(读取)数据单元。接收到的单元同时会从队列中删除。xQueuePeek()也是从从队列中接收数据单元,不同的是并不从队列中删出接收到的单元。在中断中使用xQueueReceiveFromISR()。
  17. 查询队列中当前有效数据单元的个数:uxQueueMessagesWaiting()在中断服务中使用其中断安全版本 uxQueueMessagesWaitingFromISR()。
  18. 马上切换至其他任务:taskYIELD();
  19. 如果队列存储的数据单元尺寸较大,那最好是利用队列来传递数据的指针而不是对数据本身在队列上一字节一字节地拷贝进或拷贝出。
  20. 只有以”FromISR”或”FROM_ISR”结束的 API 函数或宏才可以在中断服务例程中
  21. 创建二值信号量:vSemaphoreCreateBinary()
  22. 带阻塞时间的读取队列(信号量):xSemaphoreTake()。不能在中断服务例程中调用。
  23. 中断中放置令牌:xSemaphoreGiveFromISR()
  24. __asm{ int 0x82 } 这条语句产生中断
  25. 创建一个计数信号量:xSemaphoreCreateCounting()
  26. taskENTER_CRITICAL()与taskEXIT_CRITICAL()之间称为临界区,临界区执行的内容不会切换到其他任务。
  27. 挂起调度器:vTaskSuspendAll()可以停止上下文切换而不用关中断。
  28. 唤醒调度器:xTaskResumeAll()
  29. 创建互斥量:xSemaphoreCreateMutex()
  30. 创建内存:pvPortMalloc()
  31. 释放内存:vPortFree()
  32. 查询指定任务的运行历史中,其栈空间还差多少就要溢出:uxTaskGetStackHighWaterMark()
  33. 空闲任务钩子函数作用:执行低优先级,后台或需要不停处理的功能代码。测试处系统处理裕量。将处理器配置到低功耗模式。

本文作者:zzw

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 License 许可协议。转载请注明出处!