├── 1.png
├── 嵌入式软件开发思维导图.png
├── books
├── Shell命令行操作.pdf
├── Linux初学者入门优秀教程.pdf
├── Arm64 指令集快速查找表(ARMv8 A64 Quick Reference).pdf
└── README.md
├── 面试题与面经
├── 嵌入式资料整合第二辑.pdf
├── ARM嵌入式系统基础教程.PDF
├── Linux BSP工程师面试常问问题汇集..pdf
├── vivo C++ 嵌入式面经.txt
├── README.md
├── 操作系统面试题.md
├── Linux面试题2.md
├── Linux面试题1.md
└── 计算机网络原理面试题.md
├── 03-驱动开发与外设编程
├── embedded_peripheral_examples.zip
└── README.md
├── LICENSE.md
├── 嵌入式图形 Qt 开发
└── README.md
├── 09-2025_AI_on_MCU
└── README.md
├── 07-Debug_Optimization
└── README.md
├── 05-EmbeddedLinux
└── README.md
├── 04-实时操作系统
└── README.md
├── 02-嵌入式系统基础知识
└── README.md
├── 01-C语言基础与进阶
└── Readme.md
├── 06-NetworkIot
└── README.md
└── 08-项目实战与工具链
└── README.md
/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0voice/EmbeddedSoftwareLearn/HEAD/1.png
--------------------------------------------------------------------------------
/嵌入式软件开发思维导图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0voice/EmbeddedSoftwareLearn/HEAD/嵌入式软件开发思维导图.png
--------------------------------------------------------------------------------
/books/Shell命令行操作.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0voice/EmbeddedSoftwareLearn/HEAD/books/Shell命令行操作.pdf
--------------------------------------------------------------------------------
/面试题与面经/嵌入式资料整合第二辑.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0voice/EmbeddedSoftwareLearn/HEAD/面试题与面经/嵌入式资料整合第二辑.pdf
--------------------------------------------------------------------------------
/books/Linux初学者入门优秀教程.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0voice/EmbeddedSoftwareLearn/HEAD/books/Linux初学者入门优秀教程.pdf
--------------------------------------------------------------------------------
/面试题与面经/ARM嵌入式系统基础教程.PDF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0voice/EmbeddedSoftwareLearn/HEAD/面试题与面经/ARM嵌入式系统基础教程.PDF
--------------------------------------------------------------------------------
/面试题与面经/Linux BSP工程师面试常问问题汇集..pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0voice/EmbeddedSoftwareLearn/HEAD/面试题与面经/Linux BSP工程师面试常问问题汇集..pdf
--------------------------------------------------------------------------------
/03-驱动开发与外设编程/embedded_peripheral_examples.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0voice/EmbeddedSoftwareLearn/HEAD/03-驱动开发与外设编程/embedded_peripheral_examples.zip
--------------------------------------------------------------------------------
/books/Arm64 指令集快速查找表(ARMv8 A64 Quick Reference).pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0voice/EmbeddedSoftwareLearn/HEAD/books/Arm64 指令集快速查找表(ARMv8 A64 Quick Reference).pdf
--------------------------------------------------------------------------------
/面试题与面经/vivo C++ 嵌入式面经.txt:
--------------------------------------------------------------------------------
1 | vivo C++ 嵌入式面经
2 |
3 | -------------------------------------------------------------
4 | 一面技术面:
5 |
6 | 1 通过const成员函数实现非const成员函数
7 | 2 虚函数、纯虚函数、虚函数表
8 | 3单例模式、观察者模式
9 | 4 链表找环、小于n的质数、二叉树前序遍历(递归+非递归)(第三道题仅口述,没让写)
10 | 5 项目
11 | 6 人生规划
12 | 7 C\C++\JAVA\图像处理\嵌入式,想做哪几个方向(不知道为啥问这个问题)
13 |
--------------------------------------------------------------------------------
/面试题与面经/README.md:
--------------------------------------------------------------------------------
1 | # 更多资源尽在百度网盘:[点击这里下载](https://pan.baidu.com/s/1ZNIpG7bVxzgVxD_ySkxZ0g?pwd=tex9)提取码: tex9
2 |
3 | 
4 |
5 |
6 | ---
7 |
8 | 或扫描二维码
9 |
10 | ---
11 | 6.19 补充
12 | 通过网盘分享的文件:电子书汇总.rar
13 | 链接: https://pan.baidu.com/s/1ZNIpG7bVxzgVxD_ySkxZ0g?pwd=tex9 提取码: tex9
14 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # License
2 |
3 | ## 📖 Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
4 |
5 | 本项目基于**知识共享署名-非商业性使用-相同方式共享 4.0 国际协议**发布。您可以自由地:
6 |
7 | - ✅ **共享** — 在任何媒介以任何形式复制、发行本项目内容;
8 | - ✅ **演绎** — 修改、转换或以本项目为基础进行创作。
9 |
10 | 但必须遵守以下条件:
11 |
12 | ### 🔸 署名(Attribution BY)
13 |
14 | 您必须明确署名,给出适当的作者归属(例如原始项目地址),提供本许可协议的链接,并说明是否对原内容作了修改。
15 |
16 | ### 🔸 非商业性使用(NonCommercial NC)
17 |
18 | 您不得将本项目内容用于任何**商业目的**。包括但不限于:收费培训、书籍出版、平台售卖、视频付费课程等。
19 |
20 | ### 🔸 相同方式共享(ShareAlike SA)
21 |
22 | 如果您对内容进行了修改、加工或创作了衍生作品,您必须以与本许可协议相同的方式进行共享,即**CC BY-NC-SA 4.0**。
23 |
24 | ---
25 |
26 | ## 🛑 免责声明
27 |
28 | 本项目资源整理自公开网络,仅用于学习交流之目的。项目内容版权归原始作者所有,若涉及侵权问题,请通过 Issue 或邮件联系,我们将在第一时间处理。
29 |
30 | ---
31 |
32 | 📎 协议原文与说明:https://creativecommons.org/licenses/by-nc-sa/4.0/
33 |
34 |
35 |
--------------------------------------------------------------------------------
/books/README.md:
--------------------------------------------------------------------------------
1 | **推荐读物:**
2 |
3 | 《C和指针第二版》
4 |
5 | 《LinuxC编程一站式学习》
6 |
7 | 《STC89C52数据手册》
8 |
9 | 《STM32不完全手册库函数版》
10 |
11 | 《cortexM3权威指南》
12 |
13 | 《STM32F10中文参考手册》
14 |
15 | 《FreeRTOS源码详解与应用开发 基于STM32》
16 |
17 | 《跟我一起写Makefile》
18 |
19 | 《UNIX环境高级编程》
20 |
21 | 《深入理解LINUX内核_第3版》
22 |
23 | 《UNIX环境高级编程》
24 |
25 | 《TCPIP详解卷》
26 |
27 | #### 因仓库容量限制,更多电子书资源在百度网盘:[点击这里下载](https://pan.baidu.com/s/1ZNIpG7bVxzgVxD_ySkxZ0g?pwd=tex9)提取码: tex9
28 | ---
29 |
30 | 或扫描二维码
31 |
32 | ---
33 | 6.19 补充
34 | 通过网盘分享的文件:电子书汇总.rar
35 | 链接: https://pan.baidu.com/s/1ZNIpG7bVxzgVxD_ySkxZ0g?pwd=tex9 提取码: tex9
36 |
37 |
38 | > 资源全面无套路,只求star一下本项目,让更多需要的人可以学习!
39 | > 如果资源失效请第一时间 issue 或通过邮箱联系我!
40 |
41 | 
42 |
43 | 
44 |
45 | 
46 |
47 | 
48 |
49 | 
50 |
51 | 
52 |
53 | 
54 |
55 |
56 |
--------------------------------------------------------------------------------
/03-驱动开发与外设编程/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # 🟠 第三层:驱动开发与外设编程
4 |
5 | 嵌入式驱动开发是连接硬件与上层应用的关键层,掌握寄存器操作、外设驱动编写及工具链使用是嵌入式工程师的核心技能。
6 | 以下从底层原理到实践应用进行深度扩展:
7 |
8 | ## 🔹 寄存器级开发
9 | #### 📌 地址映射与寄存器偏移
10 | - **总线架构**:
11 | - AHB/APB总线:STM32通过AHB(高级高性能总线)连接高速外设,APB(高级外设总线)连接低速外设。
12 | - 示例:GPIOA位于AHB1总线,基地址0x40020000;USART1位于APB2总线,基地址0x40011000。
13 | - **寄存器偏移**:
14 | - 每个外设包含多个寄存器,通过基地址+偏移量访问。
15 | - 示例:GPIOA_MODER(模式寄存器)偏移0x00,GPIOA_ODR(输出数据寄存器)偏移0x14。
16 |
17 | #### 📌 位操作技巧
18 | - **原子操作宏**:
19 | ```c
20 | #define SET_BIT(REG, BIT) ((REG) |= (BIT))
21 | #define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT))
22 | #define READ_BIT(REG, BIT) ((REG) & (BIT))
23 | #define TOGGLE_BIT(REG, BIT) ((REG) ^= (BIT))
24 | ```
25 | - **多位置位/清零**:
26 | ```c
27 | // 同时设置PA5、PA6为输出(MODER[13:12]=01, MODER[11:10]=01)
28 | GPIOA_MODER = (GPIOA_MODER & ~(0xF << 10)) | (0x5 << 10);
29 | ```
30 |
31 |
32 | ### 🔹 通用外设驱动
33 | #### 📌 GPIO(通用输入输出)
34 | - **模式配置**:
35 | - 输入模式:浮空输入、上拉输入、下拉输入、模拟输入。
36 | - 输出模式:推挽输出、开漏输出(需外部上拉)。
37 | - 复用模式:用于SPI、I2C等外设功能。
38 | - **中断配置步骤**:
39 | 1. 配置GPIO为输入模式。
40 | 2. 配置SYSCFG_EXTICR寄存器选择中断源。
41 | 3. 配置EXTI_IMR(中断屏蔽)、EXTI_RTSR(上升沿触发)/FTSR(下降沿触发)。
42 | 4. 在NVIC中使能并设置中断优先级。
43 | ```c
44 | // 示例:配置PA0为上升沿触发中断
45 | SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0; // 选择PA0
46 | EXTI->IMR |= EXTI_IMR_IM0; // 使能中断线0
47 | EXTI->RTSR |= EXTI_RTSR_TR0; // 上升沿触发
48 | HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0); // 设置中断优先级
49 | HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能NVIC中断
50 | ```
51 |
52 | #### 📌 UART/USART
53 | - **波特率计算**:
54 | - 公式:`波特率 = 系统时钟 / (16 * USARTDIV)`
55 | - 示例:系统时钟72MHz,波特率115200,则USARTDIV = 72000000 / (16 * 115200) ≈ 39.0625。
56 | - **中断接收实现**:
57 | ```c
58 | // 接收完成回调函数
59 | void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
60 | if (huart->Instance == USART1) {
61 | // 处理接收到的数据
62 | process_data(rx_buffer, rx_length);
63 | // 重新开启接收中断
64 | HAL_UART_Receive_IT(&huart1, rx_buffer, 1);
65 | }
66 | }
67 | ```
68 |
69 | #### 📌 SPI(串行外设接口)
70 | - **模式配置**:
71 | - 时钟极性(CPOL):0(空闲时SCLK为低)或1(空闲时SCLK为高)。
72 | - 时钟相位(CPHA):0(第一个边沿采样)或1(第二个边沿采样)。
73 | - 数据位宽:8位或16位。
74 | - **主从模式区别**:
75 | - 主模式:控制SCK时钟,负责发起通信。
76 | - 从模式:接收SCK时钟,响应主设备请求。
77 |
78 | #### 📌 I2C(集成电路间总线)
79 | - **寻址方式**:
80 | - 7位地址:0x00~0x7F,其中0x00为广播地址。
81 | - 10位地址:扩展寻址,用于特殊设备。
82 | - **多主竞争解决**:
83 | - 通过SDA线的电平检测实现总线仲裁,先检测到SDA线被拉低的主设备退出竞争。
84 |
85 | #### 📌 ADC(模拟-to-数字转换器)
86 | - **采样时间配置**:
87 | - 采样时间越长,转换结果越精确,但转换速度越慢。
88 | - 示例:STM32F4的ADC采样时间可配置为3、15、28、56、84、112、144、480周期。
89 | - **多通道扫描模式**:
90 | ```c
91 | // 配置ADC1扫描模式,采样通道0、1、2
92 | hadc1.Instance = ADC1;
93 | hadc1.Init.ScanConvMode = ENABLE;
94 | hadc1.Init.ContinuousConvMode = DISABLE;
95 | hadc1.Init.NbrOfConversion = 3; // 3个转换通道
96 |
97 | sConfig.Channel = ADC_CHANNEL_0;
98 | sConfig.Rank = 1;
99 | HAL_ADC_ConfigChannel(&hadc1, &sConfig);
100 |
101 | sConfig.Channel = ADC_CHANNEL_1;
102 | sConfig.Rank = 2;
103 | HAL_ADC_ConfigChannel(&hadc1, &sConfig);
104 |
105 | sConfig.Channel = ADC_CHANNEL_2;
106 | sConfig.Rank = 3;
107 | HAL_ADC_ConfigChannel(&hadc1, &sConfig);
108 | ```
109 |
110 |
111 | ### 🔹 复杂外设支持
112 | #### 📌 DMA 控制器
113 | - **通道选择**:
114 | - 每个DMA控制器包含多个通道,不同外设对应不同通道。
115 | - 示例:USART1_RX对应DMA2通道5,USART1_TX对应DMA2通道4。
116 | - **双缓冲区模式**:
117 | - 适合大数据量传输,一个缓冲区用于当前传输,另一个准备下一次传输。
118 | ```c
119 | // 配置DMA双缓冲区模式
120 | hdma_adc.Instance = DMA2_Stream0;
121 | hdma_adc.Init.BufferSize = 2; // 双缓冲区
122 | hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;
123 | hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE;
124 | hdma_adc.Init.MemInc = DMA_MINC_ENABLE;
125 | // ...其他配置
126 | ```
127 |
128 | #### 📌 看门狗(Watchdog)
129 | - **独立看门狗(IWDG)**:
130 | - 由专用低速时钟(LSI,约32kHz)驱动,即使主时钟故障仍能工作。
131 | - 喂狗时间范围:典型值10ms~16s。
132 | - **窗口看门狗(WWDG)**:
133 | - 喂狗时间必须在窗口范围内(上限值~下限值),防止程序在异常状态下喂狗。
134 |
135 | #### 📌 CAN(控制器局域网)
136 | - **位时序配置**:
137 | - 由同步段(SYNC_SEG)、传播时间段(PROP_SEG)、相位缓冲段1(PHASE_SEG1)和相位缓冲段2(PHASE_SEG2)组成。
138 | - 示例:波特率500kbps,系统时钟42MHz,位时序配置为:
139 | ```c
140 | sFilterConfig.FilterBank = 0;
141 | sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
142 | sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
143 | sFilterConfig.FilterIdHigh = 0x0000;
144 | sFilterConfig.FilterIdLow = 0x0000;
145 | sFilterConfig.FilterMaskIdHigh = 0x0000;
146 | sFilterConfig.FilterMaskIdLow = 0x0000;
147 | sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
148 | sFilterConfig.FilterActivation = ENABLE;
149 | HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
150 | ```
151 |
152 |
153 | ### 🔹 开发库 & 工具链
154 | #### 📌 STM32 HAL(硬件抽象层)
155 | - **HAL库架构**:
156 | - 核心层:提供外设初始化、控制和状态检查函数。
157 | - 回调函数:通过弱函数(weak)实现,用户可重写。
158 | - 示例:
159 | ```c
160 | // HAL_UART_Transmit()函数原型
161 | HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
162 |
163 | // 重写回调函数
164 | void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
165 | if (huart->Instance == USART1) {
166 | // 发送完成后的处理
167 | }
168 | }
169 | ```
170 |
171 | #### 📌 STM32 LL(低层驱动)
172 | - **优势**:
173 | - 代码体积更小,执行效率更高。
174 | - 更接近寄存器操作,适合性能敏感场景。
175 | - **与HAL对比**:
176 | | **特性** | **HAL** | **LL** |
177 | |----------------|--------------------------|--------------------------|
178 | | 抽象程度 | 高 | 低 |
179 | | 代码体积 | 大 | 小 |
180 | | 执行效率 | 低 | 高 |
181 | | 学习难度 | 低 | 高 |
182 |
183 | #### 📌 STM32CubeMX
184 | - **时钟树配置**:
185 | - 基于PLL(锁相环)生成系统时钟,需合理配置倍频系数和分频系数。
186 | - 示例:配置系统时钟为180MHz:
187 | ```
188 | HSE (8MHz) → PLLM=8 → VCO输入=1MHz → PLLN=360 → VCO输出=360MHz → PLLP=2 → 系统时钟=180MHz
189 | ```
190 | - **中间件集成**:
191 | - 支持FreeRTOS、LWIP、USB、File System等中间件一键配置。
192 |
193 |
194 | ### 🔹 实战技巧与常见问题
195 | #### 1. **外设初始化流程**
196 | 1. 使能外设时钟。
197 | 2. 配置GPIO复用功能(如需要)。
198 | 3. 配置外设参数(如波特率、采样时间)。
199 | 4. 使能外设。
200 |
201 | #### 2. **中断处理优化**
202 | - 中断服务函数(ISR)应尽量简短,避免耗时操作。
203 | - 关键数据传递使用原子操作或关中断保护。
204 | ```c
205 | // 示例:使用原子操作传递数据
206 | volatile uint32_t g_flag __attribute__((aligned(4)));
207 |
208 | void EXTI0_IRQHandler(void) {
209 | __disable_irq();
210 | g_flag = 1; // 原子写操作
211 | __enable_irq();
212 | HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
213 | }
214 | ```
215 |
216 | #### 3. **调试技巧**
217 | - **寄存器查看**:
218 | ```c
219 | // 查看GPIOA_MODER寄存器值
220 | uint32_t moder_value = GPIOA->MODER;
221 | printf("GPIOA_MODER = 0x%08X\n", moder_value);
222 | ```
223 | - **示波器检测**:
224 | - 检测SPI/I2C总线波形,验证通信时序。
225 | - 检测PWM波形,验证占空比和频率。
226 |
227 |
228 | ### 六、面试高频问题
229 | 1. **HAL与LL库的选择标准**:
230 | - 快速开发选HAL,性能敏感场景选LL;需平衡开发效率与代码体积。
231 |
232 | 2. **I2C通信中ACK/NACK的作用**:
233 | - ACK(应答):接收方正确接收到数据,发送低电平。
234 | - NACK(非应答):接收方无法继续接收,发送高电平。
235 |
236 | 3. **ADC采样时间对精度的影响**:
237 | - 采样时间越长,对信号的积分效果越好,抗干扰能力越强,精度越高。
238 |
239 | 4. **DMA与CPU直接传输的优缺点**:
240 | - 优点:释放CPU资源,实现高速数据传输。
241 | - 缺点:配置复杂,占用总线带宽。
242 |
--------------------------------------------------------------------------------
/嵌入式图形 Qt 开发/README.md:
--------------------------------------------------------------------------------
1 | # 嵌入式平台 Qt 开发知识体系
2 |
3 | ## 一、Qt 嵌入式开发基础认知
4 | ### (一)Qt 框架适配嵌入式的核心价值
5 | 1. **跨平台兼容性**
6 | 支持多类嵌入式系统(Linux、RTOS如FreeRTOS/RT-Thread、QNX、VxWorks等),一套代码可部署至ARM、RISC - V、x86等架构硬件,降低多平台适配成本。
7 | 2. **轻量级与可裁剪性**
8 | 通过`qmake`/CMake配置,可按需裁剪Qt模块(如关闭`QtNetwork`减少网络模块依赖),适配资源受限的嵌入式设备(如MCU级硬件)。
9 | 3. **高效图形渲染能力**
10 | - **传统方案**:`Qt Widgets`基于`QPainter`提供基础控件(按钮、标签等),满足简单GUI需求。
11 | - **现代方案**:`Qt Quick/QML`采用声明式语法,结合`Scene Graph`实现硬件加速渲染,适配高动态交互场景(如车载中控、智能仪表)。
12 |
13 | ### (二)嵌入式开发典型场景
14 | 覆盖工业控制(人机界面HMI)、医疗设备(便携式诊断仪界面)、汽车电子(车载信息娱乐系统IVI)、智能家居(智能家电控制面板)、手持终端(工业PDA、POS机)等领域,成为嵌入式GUI开发首选框架。
15 |
16 |
17 | ## 二、环境搭建与工具链
18 | ### (一)开发环境搭建
19 | 1. **宿主环境**
20 | 主流选择Linux(如Ubuntu)作为开发主机,便于交叉编译工具链配置;Windows可通过WSL2或虚拟机模拟Linux环境。
21 | 2. **Qt 安装与配置**
22 | - 下载[Qt Online Installer](https://www.qt.io/download),选择对应版本(建议LTS版,如Qt 6.6),按需安装`Qt Widgets`、`Qt Quick`、`Qt for Device Creation`等组件。
23 | - 配置交叉编译工具链(如ARM的`arm - linux - gnueabihf - gcc`、RISC - V的`riscv64 - linux - gnu - gcc`),在Qt Creator中关联工具链(`工具>选项>设备>编译器/ kits`)。
24 |
25 | ### (二)交叉编译流程
26 | 以ARM嵌入式平台为例:
27 | ```bash
28 | # 配置Qt交叉编译
29 | ./configure -prefix /opt/qt5 -opensource -confirm -license \
30 | -platform linux - gcc -device linux - arm - gnueabihf - g++ \
31 | -device - option CROSS_COMPILE = /path/to/arm - linux - gnueabihf - \
32 | -sysroot /path/to/sysroot - opengl es2 - eglfs
33 |
34 | # 编译与安装
35 | make - j$(nproc)
36 | make install
37 | ```
38 | 编译后,生成适配目标平台的Qt库,用于嵌入式应用开发。
39 |
40 |
41 | ## 三、核心机制与基础开发
42 | ### (一)信号与槽机制
43 | 1. **异步事件驱动**
44 | 实现界面交互(如按钮`clicked`信号触发LED控制槽函数)、硬件事件响应(串口数据接收信号触发解析逻辑),解耦嵌入式系统中UI、外设、业务逻辑。
45 | 2. **跨线程/跨模块通信**
46 | 支持线程间安全通信(如传感器采集线程发送`dataReady`信号,UI线程更新显示),或不同模块(硬件驱动与应用逻辑)间解耦。
47 |
48 | 示例(控制嵌入式LED):
49 | ```cpp
50 | class LedControl : public QObject {
51 | Q_OBJECT
52 | public slots:
53 | void toggleLed() { /* 操作GPIO控制LED */ }
54 | };
55 |
56 | // 主窗口绑定
57 | QPushButton *btn = new QPushButton("Toggle LED");
58 | LedControl *led = new LedControl();
59 | connect(btn, &QPushButton::clicked, led, &LedControl::toggleLed);
60 | ```
61 |
62 | ### (二)嵌入式控件开发与适配
63 | 1. **基础控件优化**
64 | - `QPushButton`:适配触摸交互,设置`setFlat(true)`简化样式,通过`setStyleSheet`自定义按压反馈(如改变背景色)。
65 | - `QLabel`:显示传感器数据、状态图标,支持`setPixmap`加载硬件加速的SVG/PNG图像,减少内存占用。
66 | - `QSlider`:调节设备参数(音量、亮度),通过`valueChanged`信号实时同步硬件状态。
67 | 2. **自定义控件**
68 | 针对嵌入式外设(如旋钮、仪表盘),继承`QWidget`/`QQuickItem`开发自定义控件,复用Qt绘图系统(`QPainter`/`QSGNode`)实现硬件状态可视化。
69 |
70 |
71 | ## 四、嵌入式功能开发模块
72 | ### (一)定时器(QTimer)
73 | 1. **硬件轮询**
74 | 定时读取传感器数据(如每100ms读取温湿度传感器),通过`timeout`信号触发采集逻辑,适配嵌入式低功耗需求(减少无效轮询)。
75 | 2. **动画与状态刷新**
76 | 配合QML实现LED呼吸灯、UI状态轮询(如网络连接状态),或在`Qt Widgets`中驱动自定义动画(进度条、波形图刷新)。
77 |
78 | 示例(传感器数据采集):
79 | ```cpp
80 | QTimer *sensorTimer = new QTimer(this);
81 | sensorTimer->setInterval(100);
82 | connect(sensorTimer, &QTimer::timeout, this, &SensorWidget::readSensorData);
83 | sensorTimer->start();
84 | ```
85 |
86 | ### (二)文本与文件操作
87 | 1. **配置文件读写**
88 | 利用`QSettings`读写嵌入式设备配置(如串口波特率、LED亮度),支持INI、JSON格式,存储路径可指定为`/etc`等系统分区。
89 | 2. **日志系统**
90 | 通过`qInstallMessageHandler`自定义日志输出,将调试信息写入串口、本地文件或远程服务器,便于嵌入式设备离线调试。
91 |
92 | 示例(配置文件读写):
93 | ```cpp
94 | QSettings settings("/etc/app_config.ini", QSettings::IniFormat);
95 | settings.setValue("serial/baudrate", 115200);
96 | int baudrate = settings.value("serial/baudrate").toInt();
97 | ```
98 |
99 | ### (三)绘图与数据可视化
100 | 1. **QPainter 绘图**
101 | 用于自定义控件(如仪表盘、波形图),直接操作硬件加速的绘图上下文,适配嵌入式GPU渲染(如ARM Mali、全志VPU)。
102 | 2. **QChart 图表**
103 | 展示传感器历史数据(温度曲线、压力变化),需优化内存(限制数据点缓存数量),通过`QChartView`嵌入界面,支持触控交互(缩放、平移)。
104 |
105 | 示例(简单波形绘制):
106 | ```cpp
107 | void CustomPlot::paintEvent(QPaintEvent *event) {
108 | QPainter painter(this);
109 | painter.drawLine(0, height()/2, width(), height()/2); // 基线
110 | // 绘制传感器数据点...
111 | }
112 | ```
113 |
114 | ### (四)多线程开发
115 | 1. **硬件交互线程**
116 | 独立线程处理耗时操作(串口数据解析、传感器采集),避免阻塞UI线程,保证嵌入式界面响应流畅。
117 | 2. **线程同步**
118 | 通过`QMutex`、`QWaitCondition`保证多线程访问硬件资源(GPIO、I2C、SPI)的安全性,防止竞争条件。
119 | 3. **轻量化线程池**
120 | 优先使用`QRunnable` + `QThreadPool`实现任务池(如批量传感器数据处理),减少线程创建销毁开销,适配嵌入式资源限制。
121 |
122 | 示例(串口数据采集线程):
123 | ```cpp
124 | class SerialWorker : public QObject, public QRunnable {
125 | Q_OBJECT
126 | public:
127 | void run() override { /* 串口数据读取与解析逻辑 */ }
128 | signals:
129 | void dataReady(QByteArray data);
130 | };
131 |
132 | // 主线程调用
133 | QThreadPool::globalInstance()->start(new SerialWorker());
134 | ```
135 |
136 |
137 | ## 五、嵌入式外设交互开发
138 | ### (一)多媒体应用开发
139 | 1. **音频播放**
140 | 使用`QMediaPlayer` + `QAudioOutput`播放提示音、语音播报,依赖嵌入式平台音频驱动(如ALSA、ASoC),需在系统中配置音频设备。
141 | 2. **视频渲染**
142 | 通过`QVideoWidget`(`Qt Widgets`)或QML `Video`组件播放摄像头画面、本地视频,结合硬件解码(如全志VPU、NXP i.MX VPU)降低CPU负载,需在Qt配置中启用对应编解码器。
143 |
144 | 示例(简单音频播放):
145 | ```cpp
146 | QMediaPlayer *player = new QMediaPlayer(this);
147 | player->setMedia(QUrl::fromLocalFile("/usr/share/sounds/alert.wav"));
148 | player->setAudioOutput(new QAudioOutput(this));
149 | player->play();
150 | ```
151 |
152 | ### (二)硬件控制(LED、按键等)
153 | 1. **LED控制**
154 | - **sysfs方式**:操作`/sys/class/leds`路径下的LED节点(亮度、触发模式),封装`QLedControl`类实现控制。
155 | - **直接硬件操作**:调用嵌入式平台SDK接口(如`stm32_gpio_set`、`sunxi_gpio_set_value`),直接控制GPIO电平。
156 |
157 | 示例(sysfs控制LED):
158 | ```cpp
159 | class QLedControl : public QObject {
160 | Q_OBJECT
161 | public:
162 | void setBrightness(int value) {
163 | QFile file("/sys/class/leds/led0/brightness");
164 | if (file.open(QIODevice::WriteOnly)) {
165 | file.write(QByteArray::number(value));
166 | file.close();
167 | }
168 | }
169 | };
170 | ```
171 |
172 | 2. **按键交互**
173 | - **输入子系统**:通过`QSocketNotifier`监听`/dev/input/eventX`设备节点,捕获按键事件(按下、松开、长按)。
174 | - **触摸模拟**:在带触摸屏的嵌入式设备中,利用Qt触摸事件模拟按键交互,简化硬件设计。
175 |
176 | 示例(监听输入事件):
177 | ```cpp
178 | QSocketNotifier *notifier = new QSocketNotifier(keyEventFd, QSocketNotifier::Read, this);
179 | connect(notifier, &QSocketNotifier::activated, this, &KeyWidget::onKeyEvent);
180 | ```
181 |
182 | ### (三)串口通信(Serial)
183 | 1. **基础配置与数据收发**
184 | 使用`QSerialPort`配置串口参数(波特率、数据位、校验位等),适配嵌入式平台UART外设,注意运行时设备权限(需`sudo`或配置`udev`规则)。
185 | 2. **协议解析与多线程处理**
186 | 在独立线程中处理串口数据接收、协议解析(Modbus、自定义二进制协议),通过信号与槽同步到UI线程,避免阻塞界面。
187 |
188 | 示例(串口通信):
189 | ```cpp
190 | QSerialPort *serial = new QSerialPort(this);
191 | serial->setPortName("/dev/ttyS0");
192 | serial->setBaudRate(115200);
193 | if (serial->open(QIODevice::ReadWrite)) {
194 | connect(serial, &QSerialPort::readyRead, this, &SerialWidget::onSerialDataReceived);
195 | }
196 | ```
197 |
198 |
199 | ## 六、进阶优化与平台适配
200 | ### (一)性能优化策略
201 | 1. **资源裁剪**
202 | 通过`qmake`/CMake关闭不必要的Qt模块(如`QT -= network`),减小应用体积;使用`strip`工具去除调试符号,进一步压缩可执行文件。
203 | 2. **渲染优化**
204 | - 优先采用QML硬件加速渲染,避免复杂`QWidget`层级嵌套。
205 | - 配置`QSG_RHI_BACKEND`指定嵌入式平台GPU渲染后端(如`vulkan`、`opengl`、`metal`),利用硬件加速提升图形性能。
206 | 3. **内存管理**
207 | - 嵌入式场景禁用Qt调试内存分配器(定义`QT_NO_DEBUG`宏),减少内存开销。
208 | - 使用`QScopedPointer`、`QSharedPointer`等智能指针管理硬件资源,防止内存泄漏。
209 |
210 | ### (二)多平台适配与BSP集成
211 | 1. **设备树与硬件抽象**
212 | 在嵌入式Linux中,通过设备树配置Qt依赖的硬件资源(帧缓冲、GPU节点、外设引脚),确保Qt EGLFS/Wayland后端正确识别硬件。
213 | 2. **BSP定制与编译**
214 | 基于嵌入式平台BSP(如Yocto Project、Buildroot)编译Qt库,启用平台特定优化(NEON指令集加速、硬件编解码器支持),并集成到系统镜像。
215 |
216 | ### (三)调试与部署
217 | 1. **远程调试**
218 | 利用Qt Creator的远程调试功能,通过GDB Server连接嵌入式设备,实时调试程序、查看变量与调用栈,定位硬件交互、逻辑错误。
219 | 2. **应用部署**
220 | - 使用`linuxdeployqt`工具打包Qt应用及依赖库,生成独立可执行包,适配不同嵌入式系统。
221 | - 通过Yocto Project、Buildroot将Qt应用集成到系统镜像,实现出厂预装。
222 |
223 |
224 | ## 七、生态与学习资源
225 | ### 官方资源
226 | - [Qt 嵌入式开发文档](https://doc.qt.io/qt - for - embedded - linux/index.html):涵盖框架架构、平台适配、性能优化等内容。
227 | - [Qt for Device Creation](https://www.qt.io/product/qt - for - device - creation):专为嵌入式设计的商业解决方案,提供工具链、部署管理支持。
228 |
--------------------------------------------------------------------------------
/面试题与面经/操作系统面试题.md:
--------------------------------------------------------------------------------
1 | # 1、什么是操作系统?
2 | 操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机系统的内核与基石;
3 | 操作系统本质上是运行在计算机上的软件程序 ;
4 | 操作系统为用户提供一个与系统交互的操作界面 ;
5 | 操作系统分内核与外壳(可以把外壳理解成围绕着内核的应用程序,而内核就是能操作硬件的程序)。
6 | ```
7 | 内核负责管理系统的进程、内存、设备驱动程序、文件和网络系统等等,决定着系统的性能和稳定性。
8 | 是连接应用程序和硬件的桥梁。 内核就是操作系统背后黑盒的核心。
9 | ```
10 | 
11 |
12 | # 2、什么是系统调用?
13 | 根据进程访问资源的特点,可以把进程在系统上的运行分为两个级别:
14 | 用户态(user mode) : 用户态运行的进程或可以直接读取用户程序的数据。
15 | 系统态(kernel mode):可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。
16 |
17 | 说了用户态和系统态之后,那么什么是系统调用呢?
18 | 运行的应用程序基本都是运行在用户态,如果调用操作系统提供的系统态级别的子功能咋办呢?那就需要系统调用了!
19 | 也就是说在运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
20 | 这些系统调用按功能大致可分为如下几类:
21 | * 设备管理。完成设备的请求或释放,以及设备启动等功能。
22 | * 文件管理。完成文件的读、写、创建及删除等功能。
23 | * 进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。
24 | * 进程通信。完成进程之间的消息传递或信号传递等功能。
25 | * 内存管理。完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
26 |
27 | # 3、进程和线程的区别?
28 | 线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
29 |
30 | # 4、进程有哪几种状态?
31 | 创建状态(new) :进程正在被创建,尚未到就绪状态。
32 | 就绪状态(ready) :进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。
33 | 运行状态(running) :进程正在处理器上上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。
34 | 阻塞状态(waiting) :又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
35 | 结束状态(terminated) :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。
36 |
37 | 
38 |
39 | # 5、进程间的通信方式
40 | 管道/匿名管道(Pipes) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
41 | 有名管道(Names Pipes) : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in first out)。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
42 | 信号(Signal) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
43 | 消息队列(Message Queuing) :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。
44 | 信号量(Semaphores) :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
45 | 共享内存(Shared memory) :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
46 | 套接字(Sockets) : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
47 |
48 | # 6、线程间的同步的方式
49 | 线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。操作系统一般有下面三种线程同步的方式:
50 | 互斥量(Mutex):采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
51 | 信号量(Semphares) :它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量
52 | 事件(Event) :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作
53 |
54 | # 7、进程的调度算法
55 | 先到先服务(FCFS)调度算法 : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
56 | 短作业优先(SJF)的调度算法 : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
57 | 时间片轮转调度算法 : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
58 | 多级反馈队列调度算法 :前面介绍的几种进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。
59 | 优先级调度 : 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
60 |
61 | # 8、操作系统的内存管理主要是做什么?
62 | 操作系统的内存管理主要负责内存的分配与回收(malloc 函数:申请内存,free 函数:释放内存),另外地址转换也就是将逻辑地址转换成相应的物理地址等功能也是操作系统内存管理做的事情。
63 |
64 | # 9、常见的几种内存管理机制
65 | 简单分为连续分配管理方式和非连续分配管理方式这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间,常见的如 块式管理 。同样地,非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,常见的如页式管理 和 段式管理。
66 | 块式管理 : 远古时代的计算机操系统的内存管理方式。将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,称之为碎片。
67 | 页式管理 :把主存分为大小相等且固定的一页一页的形式,页较小,相对相比于块式管理的划分力度更大,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址。
68 | 段式管理 : 页式管理虽然提高了内存利用率,但是页式管理其中的页实际并无任何实际意义。 段式管理把主存分为一段段的,每一段的空间又要比一页的空间小很多 。但是,最重要的是段是有实际意义的,每个段定义了一组逻辑信息,例如,有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。 段式管理通过段表对应逻辑地址和物理地址。
69 |
70 | # 10、快表和多级页表
71 | 在分页内存管理中,很重要的两点是:
72 | 虚拟地址到物理地址的转换要快。
73 | 解决虚拟地址空间大,页表也会很大的问题。
74 | ### 快表
75 | 为了解决虚拟地址到物理地址的转换速度,操作系统在 页表方案 基础之上引入了 快表 来加速虚拟地址到物理地址的转换。可以把块表理解为一种特殊的高速缓冲存储器(Cache),其中的内容是页表的一部分或者全部内容。作为页表的 Cache,它的作用与页表相似,但是提高了访问速率。由于采用页表做地址转换,读写内存数据时 CPU 要访问两次主存。有了快表,有时只要访问一次高速缓冲存储器,一次主存,这样可加速查找并提高指令执行速度。
76 | 使用快表之后的地址转换流程是这样的:
77 | 根据虚拟地址中的页号查快表;
78 | 如果该页在快表中,直接从快表中读取相应的物理地址;
79 | 如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中;
80 | 当快表填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。
81 | 看完了之后会发现快表和平时经常在开发的系统使用的缓存(比如 Redis)很像,的确是这样的,操作系统中的很多思想、很多经典的算法,都可以在日常开发使用的各种工具或者框架中找到它们的影子。
82 | ### 多级页表
83 | 引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间,特别是那些根本就不需要的页表就不需要保留在内存中。多级页表属于时间换空间的典型场景。
84 |
85 | # 11、分页机制和分段机制的共同点和区别
86 | ### 共同点:
87 | 分页机制和分段机制都是为了提高内存利用率,较少内存碎片。
88 | 页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的。
89 | ### 区别:
90 | 页的大小是固定的,由操作系统决定;而段的大小不固定,取决于当前运行的程序。
91 | 分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要。
92 |
93 | # 12、逻辑(虚拟)地址和物理地址
94 | 逻辑地址由操作系统决定。物理地址指的是真实物理内存中地址,更具体一点来说就是内存地址寄存器中的地址。物理地址是内存单元真正的地址。
95 |
96 | # 13、CPU 寻址了解吗?为什么需要虚拟地址空间?
97 | 现代处理器使用的是一种称为 虚拟寻址(Virtual Addressing) 的寻址方式。使用虚拟寻址,CPU 需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。 实际上完成虚拟地址转换为物理地址转换的硬件是 CPU 中含有一个被称为 内存管理单元(Memory Management Unit, MMU) 的硬件。
98 |
99 | 
100 |
101 | 为什么要有虚拟地址空间呢?
102 | 没有虚拟地址空间的时候,程序都是直接访问和操作的都是物理内存 。但是这样有什么问题呢?
103 | 用户程序可以访问任意内存,寻址内存的每个字节,这样就很容易(有意或者无意)破坏操作系统,造成操作系统崩溃。
104 | 想要同时运行多个程序特别困难,比如想同时运行一个微信和一个 QQ 音乐都不行。为什么呢?举个简单的例子:微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就造成了微信这个程序就会崩溃。
105 | 总结来说:如果直接把物理地址暴露出来的话会带来严重问题,比如可能对操作系统造成伤害以及给同时运行多个程序造成困难。
106 | 通过虚拟地址访问内存有以下优势:
107 | 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
108 | 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为 4 KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
109 | 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。
110 |
111 | # 14、什么是虚拟内存(Virtual Memory)?
112 | 虚拟内存是计算机系统内存管理的一种技术,可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是“使用硬盘空间来扩展内存“的技术。虚拟内存的重要意义是它定义了一个连续的虚拟地址空间,并且 把内存扩展到硬盘空间。
113 |
114 | # 15、局部性原理
115 | 局部性原理表现在以下两个方面:
116 | 时间局部性 :如果程序中的某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。产生时间局部性的典型原因,是由于在程序中存在着大量的循环操作。
117 | 空间局部性 :一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内,这是因为指令通常是顺序存放、顺序执行的,数据也一般是以向量、数组、表等形式簇聚存储的。
118 | 时间局部性是通过将近来使用的指令和数据保存到高速缓存存储器中,并使用高速缓存的层次结构实现。空间局部性通常是使用较大的高速缓存,并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上就是建立了 “内存一外存”的两级存储器的结构,利用局部性原理实现髙速缓存。
119 |
120 | # 16、虚拟存储器
121 | 基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其他部分留在外存,就可以启动程序执行。由于外存往往比内存大很多,所以运行的软件的内存大小实际上是可以比计算机系统实际的内存大小大的。在程序执行过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换到外存上,从而腾出空间存放将要调入内存的信息。这样,计算机好像为用户提供了一个比实际内存大的多的存储器——虚拟存储器。
122 | 实际上,虚拟内存同样是一种时间换空间的策略,用 CPU 的计算时间,页的调入调出花费的时间,换来了一个虚拟的更大的空间来支持程序的运行。程序世界几乎不是时间换空间就是空间换时间。
123 |
124 | # 17、虚拟内存的技术实现
125 | 虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。 虚拟内存的实现有以下三种方式:
126 | 请求分页存储管理 :建立在分页管理之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。请求分页存储管理系统中,在作业开始运行之前,仅装入当前要执行的部分段即可运行。假如在作业运行的过程中发现要访问的页面不在内存,则由处理器通知操作系统按照对应的页面置换算法将相应的页面调入到主存,同时操作系统也可以将暂时不用的页面置换到外存中。
127 | 请求分段存储管理 :建立在分段存储管理之上,增加了请求调段功能、分段置换功能。请求分段储存管理方式就如同请求分页储存管理方式一样,在作业开始运行之前,仅装入当前要执行的部分段即可运行;在执行过程中,可使用请求调入中断动态装入要访问但又不在内存的程序段;当内存空间已满,而又需要装入新的段时,根据置换功能适当调出某个段,以便腾出空间而装入新的段。
128 | 请求段页式存储管理
129 |
130 | 不管是上面那种实现方式,一般都需要:
131 | 一定容量的内存和外存:在载入程序的时候,只需要将程序的一部分装入内存,而将其他部分留在外存,然后程序就可以执行了;
132 | 缺页中断:如果需执行的指令或访问的数据尚未在内存(称为缺页或缺段),则由处理器通知操作系统将相应的页面或段调入到内存,然后继续执行程序;
133 | 虚拟地址空间 :逻辑地址到物理地址的变换。
134 |
135 | # 18、页面置换算法
136 | 地址映射过程中,若在页面中发现所要访问的页面不在内存中,则发生缺页中断 。
137 | 缺页中断 就是要访问的页不在主存,需要操作系统将其调入主存后再进行访问。 在这个时候,被内存映射的文件实际上成了一个分页交换文件。
138 | 当发生缺页中断时,如果当前内存中并没有空闲的页面,操作系统就必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。用来选择淘汰哪一页的规则叫做页面置换算法,可以把页面置换算法看成是淘汰页面的规则。
139 | OPT 页面置换算法(最佳页面置换算法) :最佳(Optimal, OPT)置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若千页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。一般作为衡量其他置换算法的方法。
140 | FIFO(First In First Out) 页面置换算法(先进先出页面置换算法) : 总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。
141 | LRU (Least Currently Used)页面置换算法(最近最久未使用页面置换算法) :LRU算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。
142 | LFU (Least Frequently Used)页面置换算法(最少使用页面置换算法) : 该置换算法选择在之前时期使用最少的页面作为淘汰页。
143 |
--------------------------------------------------------------------------------
/09-2025_AI_on_MCU/README.md:
--------------------------------------------------------------------------------
1 | # 第九层:2025 新趋势
2 |
3 | ## ✅ AI on MCU / Edge AI
4 |
5 | ### 🔹 TinyML / TensorFlow Lite Micro
6 |
7 | #### 1. **概念与优势**
8 | - **TinyML**:将机器学习模型部署到资源受限的微控制器(MCU)上,实现边缘智能。
9 | - **优势**:
10 | - **低延迟**:本地处理数据,无需云端交互。
11 | - **低功耗**:适合电池供电的物联网设备。
12 | - **隐私保护**:敏感数据无需上传。
13 | - **离线运行**:在网络中断时仍能工作。
14 |
15 | #### 2. **开发流程**
16 | 1. **模型训练**:
17 | 使用TensorFlow/Keras等工具在PC上训练模型。
18 | ```python
19 | # 简单MNIST模型示例
20 | model = tf.keras.Sequential([
21 | tf.keras.layers.Flatten(input_shape=(28, 28)),
22 | tf.keras.layers.Dense(128, activation='relu'),
23 | tf.keras.layers.Dense(10, activation='softmax')
24 | ])
25 | model.compile(optimizer='adam',
26 | loss='sparse_categorical_crossentropy',
27 | metrics=['accuracy'])
28 | model.fit(x_train, y_train, epochs=5)
29 | ```
30 |
31 | 2. **模型量化**:
32 | 将浮点模型转换为整数模型,减少内存占用和计算量。
33 | ```python
34 | converter = tf.lite.TFLiteConverter.from_keras_model(model)
35 | converter.optimizations = [tf.lite.Optimize.DEFAULT]
36 | tflite_model = converter.convert()
37 | ```
38 |
39 | 3. **模型部署**:
40 | 将量化后的模型转换为C数组,集成到MCU项目中。
41 | ```bash
42 | xxd -i model.tflite > model_data.cc
43 | ```
44 |
45 | 4. **MCU推理**:
46 | 使用TensorFlow Lite Micro框架在MCU上运行模型。
47 | ```c
48 | // 初始化解释器
49 | tflite::MicroErrorReporter micro_error_reporter;
50 | const tflite::ErrorReporter* error_reporter = µ_error_reporter;
51 |
52 | const tflite::MicroOpResolver& op_resolver = MicroOpsResolver();
53 | const tflite::SimpleTensorAllocator tensor_allocator(tensor_arena, kTensorArenaSize);
54 |
55 | tflite::MicroInterpreter interpreter(model_data, model_data_len, op_resolver,
56 | tensor_allocator, error_reporter);
57 |
58 | // 运行推理
59 | TfLiteStatus invoke_status = interpreter.Invoke();
60 | if (invoke_status != kTfLiteOk) {
61 | error_reporter->Report("Invoke failed\n");
62 | }
63 | ```
64 |
65 | #### 3. **性能指标**
66 | | **模型** | **参数量** | **激活内存** | **准确率** | **推理时间(STM32H7)** |
67 | |----------------|------------|--------------|------------|-------------------------|
68 | | MobileNetV1 | 4.2M | 16MB | 70.6% | 800ms |
69 | | TinyMLNet | 0.02M | 0.2MB | 68.2% | 5ms |
70 | | EfficientNet-Lite0 | 4M | 12MB | 75.0% | 600ms |
71 |
72 |
73 | ### 🔹 STM32 AI 开发套件
74 |
75 | #### 1. **硬件平台**
76 | - **STM32H7系列**:高性能MCU,支持DSP和FPU,适合运行复杂AI模型。
77 | - **STM32L4+系列**:低功耗MCU,集成AI加速器,适合电池供电设备。
78 | - **X-CUBE-AI扩展包**:提供模型转换工具和优化库。
79 |
80 | #### 2. **开发工具链**
81 | 1. **STM32CubeMX**:配置硬件和生成初始化代码。
82 | 2. **STM32Cube.AI**:将TensorFlow/PyTorch模型转换为STM32优化代码。
83 | ```bash
84 | # 使用x-cube-ai命令行工具转换模型
85 | stm32ai generate -m model.h5 -o stm32ai_output
86 | ```
87 | 3. **STM32CubeIDE**:集成开发环境,调试和优化AI应用。
88 |
89 | #### 3. **性能优化**
90 | - **硬件加速**:利用STM32的DSP、FPU和专用AI加速器(如STM32H7的Chrom-ART加速器)。
91 | - **模型优化**:使用STM32Cube.AI的量化工具将模型压缩至8位或更少。
92 | - **内存管理**:优化模型和中间数据的内存布局,减少RAM占用。
93 |
94 |
95 | ### 🔹 模型量化与部署
96 |
97 | #### 1. **量化技术**
98 | - **权重量化**:将浮点权重转换为整数(通常8位或更少)。
99 | - **激活量化**:运行时将输入/输出数据转换为整数。
100 | - **混合精度**:对关键层使用更高精度,平衡准确率和性能。
101 |
102 | #### 2. **部署挑战与解决方案**
103 | | **挑战** | **解决方案** |
104 | |------------------------|----------------------------------------------|
105 | | 内存受限 | 使用内存映射技术,模型分段加载 |
106 | | 计算能力有限 | 优化算子实现,利用硬件加速指令 |
107 | | 功耗敏感 | 采用低功耗模式,推理过程中动态调整频率 |
108 | | 模型更新 | 设计OTA机制,支持模型动态更新 |
109 |
110 |
111 | ### 🔹 AI + 外设驱动融合案例
112 |
113 | #### 1. **智能传感器处理**
114 | - **场景**:基于加速度计数据的活动识别。
115 | - **实现**:
116 | ```c
117 | // 从加速度计读取数据
118 | void read_accelerometer_data(float *data, size_t length) {
119 | // 读取加速度计原始数据
120 | int16_t raw_data[3];
121 | accelerometer_read(raw_data);
122 |
123 | // 转换为浮点数并归一化
124 | for (int i = 0; i < 3; i++) {
125 | data[i] = (float)raw_data[i] / 32768.0f;
126 | }
127 | }
128 |
129 | // 运行AI模型进行活动识别
130 | activity_t recognize_activity(float *sensor_data) {
131 | // 准备模型输入
132 | TfLiteTensor* input = interpreter->input(0);
133 | memcpy(input->data.f, sensor_data, input->bytes);
134 |
135 | // 执行推理
136 | if (interpreter->Invoke() != kTfLiteOk) {
137 | return ACTIVITY_UNKNOWN;
138 | }
139 |
140 | // 获取输出结果
141 | TfLiteTensor* output = interpreter->output(0);
142 | int activity_index = argmax(output->data.f, output->dims->data[0]);
143 |
144 | return (activity_t)activity_index;
145 | }
146 | ```
147 |
148 | #### 2. **预测性维护**
149 | - **场景**:基于振动传感器的电机故障预测。
150 | - **实现**:
151 | 1. 采集振动数据并进行FFT变换。
152 | 2. 使用AI模型分析频谱特征,识别潜在故障。
153 | 3. 通过BLE将结果发送至云端。
154 |
155 |
156 | ## ✅ 安全性
157 |
158 | ### 🔹 安全启动(Secure Boot)
159 |
160 | #### 1. **原理与流程**
161 | 1. **硬件信任根**:
162 | - 设备内置不可更改的私钥(存储在OTP中)。
163 | - 用于验证第一个加载的软件组件(通常是Bootloader)。
164 |
165 | 2. **验证流程**:
166 | ```
167 | ROM → 验证Bootloader签名 → 验证应用固件签名 → 启动应用
168 | ```
169 |
170 | #### 2. **STM32实现**
171 | - **选项字节配置**:
172 | ```c
173 | // 启用读保护(RDP)
174 | HAL_FLASH_OB_Unlock();
175 | FLASH_OBProgramInitTypeDef obInit;
176 | obInit.OptionType = OPTIONBYTE_RDP;
177 | obInit.RDPLevel = OB_RDP_LEVEL_1; // 禁用调试接口
178 | HAL_FLASHEx_OBProgram(&obInit);
179 | HAL_FLASH_OB_Lock();
180 | ```
181 |
182 | - **签名验证**:
183 | ```c
184 | // 验证固件签名
185 | bool verify_firmware_signature(const uint8_t *firmware, size_t size, const uint8_t *signature) {
186 | // 从OTP读取公钥
187 | const uint8_t *public_key = get_public_key_from_otp();
188 |
189 | // 使用ECDSA验证签名
190 | return ecdsa_verify(public_key, firmware, size, signature);
191 | }
192 | ```
193 |
194 |
195 | ### 🔹 TPM 安全芯片接入
196 |
197 | #### 1. **TPM 2.0 概述**
198 | - **功能**:
199 | - 安全存储密钥
200 | - 硬件级加密
201 | - 平台身份验证
202 | - 远程证明
203 |
204 | #### 2. **STM32与TPM集成**
205 | - **硬件连接**:
206 | STM32通过I2C/SPI与TPM芯片(如Infineon OPTIGA™ TPM SLB 9670)通信。
207 |
208 | - **软件实现**:
209 | ```c
210 | // TPM初始化
211 | tpm_error_t tpm_init(void) {
212 | // 初始化I2C接口
213 | i2c_init(TPM_I2C_ADDRESS);
214 |
215 | // 发送TPM启动命令
216 | uint8_t startup_cmd[10] = {0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x44};
217 | uint8_t response[20];
218 |
219 | if (i2c_write(TPM_I2C_ADDRESS, startup_cmd, 10) != 0) {
220 | return TPM_ERROR_COMMUNICATION;
221 | }
222 |
223 | // 读取响应
224 | if (i2c_read(TPM_I2C_ADDRESS, response, 20) != 0) {
225 | return TPM_ERROR_COMMUNICATION;
226 | }
227 |
228 | // 验证响应
229 | if (response[6] == 0x00 && response[7] == 0x00) {
230 | return TPM_SUCCESS;
231 | } else {
232 | return TPM_ERROR_INITIALIZATION;
233 | }
234 | }
235 |
236 | // 生成密钥
237 | tpm_error_t tpm_generate_key(uint8_t *key_handle, uint8_t *public_key) {
238 | // 发送生成密钥命令
239 | // ...
240 |
241 | // 处理响应
242 | // ...
243 |
244 | return TPM_SUCCESS;
245 | }
246 | ```
247 |
248 | #### 3. **应用场景**
249 | - **安全启动增强**:使用TPM验证固件完整性。
250 | - **安全通信**:TPM生成和存储TLS密钥,保护通信数据。
251 | - **设备身份认证**:基于TPM的唯一密钥实现设备身份识别。 `
252 |
253 |
254 | ## 🚀 实战案例
255 |
256 | ### 1. **工业设备预测性维护**
257 | - **需求**:基于振动传感器数据预测设备故障。
258 | - **实现**:
259 | - 使用STM32H7采集振动数据。
260 | - 部署TinyML模型进行实时分析。
261 | - 通过TLS加密将结果发送至云端。
262 | - 使用TPM确保数据完整性和设备身份安全。
263 |
264 | ### 2. **智能家居安全监控**
265 | - **需求**:基于摄像头的人体检测与异常行为识别。
266 | - **实现**:
267 | - 使用STM32MP1微处理器运行轻量级CNN模型。
268 | - 仅在检测到异常时唤醒系统并发送警报。
269 | - 通过安全启动确保固件未被篡改。
270 | - 使用TPM存储用户认证密钥。
271 |
272 |
273 | ## 🔗 参考资源
274 |
275 | 1. **AI on MCU**:
276 | - [TensorFlow Lite Micro](https://www.tensorflow.org/lite/microcontrollers)
277 | - [STM32Cube.AI](https://www.st.com/en/embedded-software/x-cube-ai.html)
278 | - [Edge Impulse](https://www.edgeimpulse.com/)
279 |
280 | 2. **安全性**:
281 | - [PSA Certified](https://www.psacertified.org/)
282 | - [mbed TLS](https://tls.mbed.org/)
283 | - [TPM 2.0 Specification](https://trustedcomputinggroup.org/resource/tpm-library-specification/)
284 |
285 | 3. **实战案例**:
286 | - [STMicroelectronics AI Demo](https://www.st.com/en/evaluation-tools/stm32ai-discovery.html)
287 | - [ESP32 TinyML Examples](https://github.com/tensorflow/tflite-micro-arduino-examples)
288 |
289 |
290 | AI与安全是2025年嵌入式领域的两大核心趋势。通过将AI算法部署到边缘设备,可实现实时智能决策,同时降低网络带宽和云端计算成本。而安全性则是保障设备和数据可信的基础,从安全启动到加密通信,再到TPM硬件级保护,构建多层次安全防护体系。在实际项目中,需根据具体需求选择合适的AI模型和安全方案,平衡性能、功耗和安全性。
291 |
--------------------------------------------------------------------------------
/07-Debug_Optimization/README.md:
--------------------------------------------------------------------------------
1 |
2 | # ⚡ 第七层:调试与性能优化
3 |
4 | ---
5 |
6 | ## ✅ 常用调试工具
7 |
8 | ### 🔹 JTAG / SWD 接口
9 |
10 | - **JTAG**(Joint Test Action Group)标准调试接口,支持多设备级联。
11 | - **SWD**(Serial Wire Debug)是 ARM Cortex 系列的简化调试协议,仅使用两根线(SWDIO, SWCLK),适用于资源受限设备。
12 |
13 | **JTAG 与 SWD 接口对比**
14 | | 特性 | JTAG | SWD |
15 | |------------|----------------------------------------------|--------------------------------|
16 | | 引脚数 | 5 线(TMS、TCK、TDI、TDO、TRST) | 2 线(SWDIO、SWCLK) |
17 | | 速度 | 中低速(典型 1-10MHz) | 高速(可达 50MHz 以上) |
18 | | 占用资源 | 高(需多个 GPIO) | 低(仅 2 个 GPIO) |
19 | | 级联能力 | 支持多设备(通过 TAP 控制器) | 不支持级联 |
20 | | 适用场景 | 复杂芯片调试(如 FPGA) | 嵌入式 MCU(如 STM32) |
21 |
22 | - SWD 调试配置示例(STM32CubeMX):
23 | ```c
24 | // 使能SWD接口(禁用JTAG以释放GPIO)
25 | __HAL_RCC_GPIOA_CLK_ENABLE();
26 | GPIO_InitTypeDef GPIO_InitStruct = {0};
27 | GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14; // SWDIO, SWCLK
28 | GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
29 | GPIO_InitStruct.Pull = GPIO_NOPULL;
30 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
31 | GPIO_InitStruct.Alternate = GPIO_AF0_SWJ;
32 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
33 | __HAL_AFIO_REMAP_SWJ_NOJTAG(); // 禁用JTAG,保留SWD
34 | ```
35 |
36 |
37 |
38 | ### 🔹 GDB + OpenOCD 调试
39 |
40 | - **GDB**:GNU 调试器,支持断点、单步、查看变量等操作。
41 | - **OpenOCD**:Open On-Chip Debugger,用于连接 GDB 和硬件调试接口(如 ST-Link)。
42 |
43 | 关键命令详解:
44 | ```bash
45 | # 1. 启动OpenOCD(连接ST-Link与目标MCU)
46 | openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
47 |
48 | # 2. 启动GDB并加载ELF文件
49 | arm-none-eabi-gdb path/to/firmware.elf
50 |
51 | # 3. 连接到OpenOCD服务器
52 | (gdb) target remote :3333 # 默认端口3333
53 |
54 | # 4. 下载程序到Flash
55 | (gdb) load
56 |
57 | # 5. 复位并暂停CPU
58 | (gdb) monitor reset halt
59 |
60 | # 6. 设置断点
61 | (gdb) break main # 在main()函数入口设置断点
62 | (gdb) break MyFunction # 在自定义函数设置断点
63 | (gdb) break file.c:123 # 在文件file.c的第123行设置断点
64 |
65 | # 7. 执行控制
66 | (gdb) continue # 继续执行
67 | (gdb) next # 单步执行(不进入函数)
68 | (gdb) step # 单步执行(进入函数)
69 | (gdb) finish # 运行到当前函数结束
70 |
71 | # 8. 查看变量
72 | (gdb) print myVariable # 打印变量值
73 | (gdb) p &myArray[0] # 打印数组地址
74 | (gdb) x/10i $pc # 查看当前执行的10条汇编指令
75 |
76 | # 9. 查看寄存器
77 | (gdb) info registers # 查看所有寄存器
78 | (gdb) p $r0 # 查看特定寄存器(如R0)
79 | ```
80 |
81 | ### 🔹 逻辑分析仪 / 示波器
82 |
83 | - **逻辑分析仪**:用于捕捉数字信号波形,分析通信协议(如 I2C, SPI)。
84 | - 逻辑分析仪典型场景:
85 | - SPI 通信时序分析(验证 CPOL/CPHA 设置)。
86 | - I2C 总线竞争检测(查看 ACK/NACK 信号)。
87 | - UART 波特率校准(测量位宽计算实际波特率)。
88 |
89 | - **示波器**:查看模拟信号、电压、电流变化。对调试电源问题、干扰、PWM波形等极为重要。
90 | - 示波器关键参数:
91 | - 带宽:至少为信号最高频率的 3-5 倍(如测量 1MHz PWM 需 5MHz 带宽)。
92 | - 采样率:至少为信号最高频率的 10 倍(如 1MHz 信号需 10MSa/s 采样率)。
93 |
94 | #### 调试 PWM 信号示例:
95 | ```c
96 | // 配置TIM3输出PWM(频率1kHz,占空比50%)
97 | TIM_HandleTypeDef htim3;
98 | htim3.Instance = TIM3;
99 | htim3.Init.Prescaler = 72 - 1; // 72MHz / 72 = 1MHz
100 | htim3.Init.Period = 1000 - 1; // 1MHz / 1000 = 1kHz
101 | htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
102 | HAL_TIM_PWM_Init(&htim3);
103 |
104 | TIM_OC_InitTypeDef sConfigOC;
105 | sConfigOC.OCMode = TIM_OCMODE_PWM1;
106 | sConfigOC.Pulse = 500; // 占空比50%
107 | sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
108 | HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
109 | HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
110 | ```
111 | 使用示波器测量:频率应为 1kHz,高电平时间 500μs(占空比 50%)。
112 |
113 | ### 🔹 printf / 串口调试
114 |
115 | - 常用 `printf()` 输出信息到串口查看程序执行流程。
116 | - 可与 RTT(Real Time Transfer)配合实现非阻塞调试输出。
117 |
118 | ### 🔹 断点调试
119 |
120 | - 在 IDE(如 STM32CubeIDE)中设置断点暂停程序运行,查看寄存器、内存、变量。
121 | - 适合调试初始化流程、外设配置错误等问题。
122 |
123 | ---
124 |
125 | ## ✅ 性能与功耗优化
126 |
127 | ### 🔹 FreeRTOS Trace 与分析工具
128 |
129 | - 使用 FreeRTOS+Trace 工具(Percepio)记录任务切换、上下文切换、CPU 占用率。
130 | - 可通过 `vTraceEnable()` 开启追踪。
131 | - 跟踪点(Trace Point):
132 | 在关键代码位置插入记录函数(如任务切换、中断处理)。
133 |
134 | ```c
135 | // 自定义跟踪点示例
136 | #define TRACE_TASK_SWITCH() do { \
137 | uint32_t current_task = (uint32_t)pxCurrentTCB; \
138 | uint32_t timestamp = xTaskGetTickCount(); \
139 | vTraceStoreEvent(EVENT_TASK_SWITCH, timestamp, current_task); \
140 | } while(0)
141 | ```
142 | - 数据存储:
143 | - 环形缓冲区:存储跟踪事件,避免内存溢出。
144 | - 示例配置:
145 | ```c
146 | #define configUSE_TRACE_FACILITY 1 // 启用跟踪功能
147 | #define configUSE_STATS_FORMATTING_FUNCTIONS 1 // 启用统计功能
148 | #define TRACE_BUFFER_SIZE 1024 // 跟踪缓冲区大小(事件数)
149 | ```
150 |
151 |
152 | ### 🔹 SystemView 分析工具
153 |
154 | - SEGGER 提供的实时系统分析工具。
155 | - 与 FreeRTOS 集成,通过 SWO 接口获取任务执行时间、事件追踪等信息。
156 | - 关键指标解读:
157 | - 任务执行时间:各任务 CPU 占用百分比。
158 | - 上下文切换频率:过高表示任务调度不合理。
159 | - 中断响应时间:从中断触发到 ISR 执行的时间差。
160 |
161 | ### 🔹 STM32CubeMonitor
162 |
163 | - ST 官方提供的可视化变量监控与数据图示工具。
164 | - 可用于实时观察寄存器值、ADC 曲线、温度、电压等参数。
165 |
166 | ### 🔹 低功耗模式优化
167 |
168 | #### Cortex-M 支持三种主要低功耗模式:
169 |
170 | | 模式 | 唤醒时间 | 功耗 | 保留内容 |
171 | |----------|-----------|----------|--------------------------------|
172 | | Sleep | 数 μs | 几 mA | CPU 寄存器、SRAM 内容 |
173 | | Stop | 几十 μs | 几 μA | SRAM 内容、部分寄存器 |
174 | | Standby | 几 ms | 几十 nA | 仅备份寄存器(如 RTC) |
175 |
176 | #### 优化技巧:
177 |
178 | - 外设时钟管理:
179 | ```c
180 | // 禁用未使用的外设时钟
181 | __HAL_RCC_GPIOA_CLK_DISABLE(); // 禁用GPIOA时钟
182 | __HAL_RCC_SPI1_CLK_DISABLE(); // 禁用SPI1时钟
183 |
184 | // 仅在需要时启用外设
185 | void vReadSensor(void) {
186 | __HAL_RCC_I2C1_CLK_ENABLE(); // 启用I2C时钟
187 | // 读取传感器数据
188 | __HAL_RCC_I2C1_CLK_DISABLE(); // 读取完成后禁用时钟
189 | }
190 | ```
191 |
192 | - RTC 唤醒配置:
193 | ```c
194 | // 配置RTC闹钟唤醒(每10秒唤醒一次)
195 | RTC_TimeTypeDef sTime = {0};
196 | RTC_DateTypeDef sDate = {0};
197 | RTC_AlarmTypeDef sAlarm = {0};
198 |
199 | sTime.Hours = 0;
200 | sTime.Minutes = 0;
201 | sTime.Seconds = 0;
202 | HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
203 |
204 | sDate.WeekDay = RTC_WEEKDAY_MONDAY;
205 | sDate.Month = RTC_MONTH_JANUARY;
206 | sDate.Date = 1;
207 | sDate.Year = 0;
208 | HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
209 |
210 | sAlarm.AlarmTime = sTime;
211 | sAlarm.Alarm = RTC_ALARM_A;
212 | sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY | RTC_ALARMMASK_HOURS | RTC_ALARMMASK_MINUTES;
213 | sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
214 | HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
215 |
216 | // 进入Standby模式
217 | HAL_PWR_EnterSTANDBYMode();
218 | ```
219 |
220 | ### 调试与优化实战案例
221 | 1. 内存泄漏检测
222 | - 静态检测工具:
223 | - CppCheck:检查内存分配与释放是否匹配。
224 | - Valgrind(需模拟器环境):检测动态内存问题。
225 | - 自定义内存管理钩子:
226 | ```c
227 | // 记录内存分配/释放情况
228 | void *pvPortMalloc( size_t xWantedSize ) {
229 | void *pvReturn = NULL;
230 | vTaskSuspendAll();
231 | {
232 | // 记录分配信息(如分配地址、大小、时间)
233 | pvReturn = prvHeapAllocateMemory( xWantedSize );
234 | vRecordMemoryAllocation(pvReturn, xWantedSize);
235 | }
236 | xTaskResumeAll();
237 | return pvReturn;
238 | }
239 | ```
240 | 2. 中断风暴处理
241 | - 问题现象:CPU 占用率 100%,系统无响应。
242 | - 排查步骤:
243 | - 使用调试器暂停 CPU,查看当前执行的代码(通常是某个 ISR)。
244 | - 检查中断触发条件(如 GPIO 引脚是否抖动)。
245 | - 添加中断计数统计:
246 | - 解决方案:
247 | - 添加软件消抖:
248 | ```c
249 | static uint32_t ulLastInterruptTime = 0;
250 | #define DEBOUNCE_TIME 50 // 50ms
251 |
252 | void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
253 | uint32_t ulCurrentTime = xTaskGetTickCount();
254 | if (ulCurrentTime - ulLastInterruptTime > DEBOUNCE_TIME) {
255 | // 处理有效中断
256 | vProcessButtonPress();
257 | ulLastInterruptTime = ulCurrentTime;
258 | }
259 | }
260 | ```
261 |
262 | ### 面试高频问题
263 | 1. JTAG 与 SWD 的优缺点:
264 |
265 | - JTAG:兼容性强,支持多设备级联,但占用引脚多;SWD:引脚少,速度快,适合嵌入式设备。
266 |
267 | 2. 如何优化 RTOS 系统的 CPU 使用率:
268 | - 减少空闲任务 CPU 占用(通过configIDLE_SHOULD_YIELD配置)。
269 | - 优化中断处理时间,避免长中断服务程序。
270 | - 使用低功耗模式,在空闲时进入 Sleep/Stop 状态。
271 |
272 | 3. 调试时发现程序跑飞,如何定位问题:
273 |
274 | - 设置看门狗定时器,捕获异常复位。
275 | - 使用硬件断点,检查关键函数是否被正确调用。
276 | - 添加断言(assert),验证关键条件。
277 |
278 | 4. 如何测量代码执行时间:
279 | - 使用高精度定时器(如 STM32 的 DWT_CYCCNT)。
280 | - SystemView 等工具通过 SWO 接口获取精确时间。
281 |
282 | ### 学习资源推荐
283 | 1. 调试工具文档:
284 | - [GDB 官方文档](https://sourceware.org/gdb/documentation/)
285 | - [OpenOCD 用户手册](https://link.wtturl.cn/?target=http%3A%2F%2Fopenocd.org%2Fdoc%2Fpdf%2Fopenocd.pdf&scene=im&aid=497858&lang=zh)
286 |
287 | 2. 性能分析教程:
288 | - [FreeRTOS Trace 可视化指南](https://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_Trace/trace_introduction.html)
289 | - [SEGGER SystemView 应用笔记](https://link.wtturl.cn/?target=https%3A%2F%2Fwww.segger.com%2Fproducts%2Fdevelopment-tools%2Fsystemview%2F&scene=im&aid=497858&lang=zh)
290 |
291 | 3. 低功耗设计指南:
292 | - [STM32 低功耗应用手册](https://link.wtturl.cn/?target=https%3A%2F%2Fwww.st.com%2Fresource%2Fen%2Fapplication_note%2Fdm00071990-stm32-microcontroller-lowpower-modes-stmicroelectronics.pdf&scene=im&aid=497858&lang=zh)
293 | - [Cortex-M 低功耗技术白皮书](https://developer.arm.com/documentation/100166/latest/)
294 |
295 | 4. 实践项目:
296 | - 在 STM32 上实现功耗测量(使用外部电流表或内部 ADC 监测 VDD 电流)。
297 | - 使用 SystemView 分析 FreeRTOS 任务调度行为。
298 |
--------------------------------------------------------------------------------
/面试题与面经/Linux面试题2.md:
--------------------------------------------------------------------------------
1 | ## 问题一:
2 |
3 | 绝对路径用什么符号表示?当前目录、上层目录用什么表示?主目录用什么表示? 切换目录用什么命令?
4 |
5 | ### 答案:
6 | 绝对路径: 如/etc/init.d
7 | 当前目录和上层目录: ./ ../
8 | 主目录: ~/
9 | 切换目录: cd
10 |
11 | ## 问题二:
12 |
13 | 怎么查看当前进程?怎么执行退出?怎么查看当前路径?
14 |
15 | ### 答案:
16 | 查看当前进程: ps
17 | 执行退出: exit
18 | 查看当前路径: pwd
19 |
20 | ## 问题三:
21 |
22 | 怎么清屏?怎么退出当前命令?怎么执行睡眠?怎么查看当前用户 id?查看指定帮助用什么命令?
23 |
24 | ### 答案:
25 | 清屏: clear
26 | 退出当前命令: ctrl+c 彻底退出
27 | 执行睡眠 : ctrl+z 挂起当前进程fg 恢复后台
28 | 查看当前用户 id: ”id“:查看显示目前登陆账户的 uid 和 gid 及所属分组及用户名
29 | 查看指定帮助: 如 man adduser 这个很全 而且有例子; adduser --help 这个告诉你一些常用参数; info adduesr;
30 |
31 | ## 问题四:
32 |
33 | Ls 命令执行什么功能? 可以带哪些参数,有什么区别?
34 |
35 | ### 答案:
36 | ls 执行的功能: 列出指定目录中的目录,以及文件
37 | 哪些参数以及区别: a 所有文件l 详细信息,包括大小字节数,可读可写可执行的权限等
38 |
39 | ## 问题五:
40 |
41 | 建立软链接(快捷方式),以及硬链接的命令。
42 |
43 | ### 答案:
44 | 软链接: ln -s slink source
45 | 硬链接: ln link source
46 |
47 | ## 问题六:
48 |
49 | 目录创建用什么命令?创建文件用什么命令?复制文件用什么命令?
50 |
51 | ### 答案:
52 | 创建目录: mkdir
53 | 创建文件:典型的如 touch,vi 也可以创建文件,其实只要向一个不存在的文件输出,都会创建文件
54 | 复制文件: cp 7. 文件权限修改用什么命令?格式是怎么样的?
55 | 文件权限修改: chmod
56 | 格式如下:
57 | ```
58 | chmodu+xfile给file的属主增加执行权限 chmod 751 file 给 file 的属主分配读、写、执行(7)的权限,给 file 的所在组分配读、执行(5)的权限,给其他用户分配执行(1)的权限
59 | chmodu=rwx,g=rx,o=xfile上例的另一种形式 chmod =r file 为所有用户分配读权限
60 | chmod444file同上例 chmod a-wx,a+r file同上例
61 | $ chmod -R u+r directory 递归地给 directory 目录下所有文件和子目录的属主分配读的权限
62 | ```
63 |
64 | ## 问题七:
65 |
66 | 使用哪一个命令可以查看自己文件系统的磁盘空间配额呢?
67 |
68 |
69 | ### 答案:
70 |
71 | 使用命令repquota 能够显示出一个文件系统的配额信息
72 |
73 |
74 | ## 问题八:
75 |
76 | 查看文件内容有哪些命令可以使用?
77 |
78 | ### 答案:
79 | vi 文件名 #编辑方式查看,可修改
80 | cat 文件名 #显示全部文件内容
81 | more 文件名 #分页显示文件内容
82 | less 文件名 #与 more 相似,更好的是可以往前翻页
83 | tail 文件名 #仅查看尾部,还可以指定行数
84 | head 文件名 #仅查看头部,还可以指定行数
85 |
86 | ## 问题九:
87 |
88 | 随意写文件命令?怎么向屏幕输出带空格的字符串,比如”hello world”?
89 |
90 | ### 答案:
91 |
92 | 写文件命令:vi
93 |
94 | 向屏幕输出带空格的字符串:`echo hello world`
95 |
96 |
97 |
98 | ## 问题十:
99 |
100 | 终端是哪个文件夹下的哪个文件?黑洞文件是哪个文件夹下的哪个命令?
101 |
102 | ### 答案:
103 | 终端 /dev/tty
104 | 黑洞文件 /dev/null
105 |
106 | ## 问题十一:
107 |
108 | 移动文件用哪个命令?改名用哪个命令?
109 |
110 | ### 答案:
111 | ```
112 | mv mv
113 | ```
114 |
115 | ## 问题十二:
116 |
117 | 复制文件用哪个命令?如果需要连同文件夹一块复制呢?如果需要有提示功能呢?
118 |
119 | ### 答案:
120 | ```
121 | cp cp -r ????
122 | ```
123 |
124 | ## 问题十三:
125 |
126 | 删除文件用哪个命令?如果需要连目录及目录下文件一块删除呢?删除空文件夹用什么命令?
127 |
128 | ### 答案:
129 | rm rm -r rmdir
130 |
131 | ## 问题十四:
132 |
133 | Linux 下命令有哪几种可使用的通配符?分别代表什么含义?
134 |
135 | ### 答案:
136 |
137 | “?”可替代单个字符。
138 | “*”可替代任意多个字符。
139 | 方括号“[charset]”可替代 charset 集中的任何单个字符,如[a-z],[abABC]
140 |
141 |
142 |
143 | ## 问题十五:
144 |
145 | 用什么命令对一个文件的内容进行统计?(行号、单词数、字节数)
146 |
147 | ### 答案:
148 |
149 | wc 命令 - c 统计字节数 - l 统计行数 - w 统计字数。
150 |
151 | ## 问题十六:
152 |
153 | Grep 命令有什么用? 如何忽略大小写? 如何查找不含该串的行?
154 |
155 | ### 答案:
156 | 是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。
157 | grep [stringSTRING] filename grep [^string] filename
158 |
159 | ## 问题十七:
160 |
161 | Linux 中进程有哪几种状态?在 ps 显示出来的信息中,分别用什么符号表示的?
162 |
163 | ### 答案:
164 | (1)、不可中断状态:进程处于睡眠状态,但是此刻进程是不可中断的。不可中断, 指进程不响应异步信号。
165 | (2)、暂停状态/跟踪状态:向进程发送一个 SIGSTOP 信号,它就会因响应该信号 而进入 TASK_STOPPED 状态;当进程正在被跟踪时,它处于 TASK_TRACED 这个特殊的状态。“正在被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作。
166 | (3)、就绪状态:在 run_queue 队列里的状态
167 | (4)、运行状态:在 run_queue 队列里的状态
168 | (5)、可中断睡眠状态:处于这个状态的进程因为等待某某事件的发生(比如等待 socket 连接、等待信号量),而被挂起
169 | (6)、zombie 状态(僵尸):父亲没有通过 wait 系列的系统调用会顺便将子进程的尸体(task_struct)也释放掉
170 | (7)、退出状态
171 |
172 | D 不可中断 Uninterruptible(usually IO)
173 | R 正在运行,或在队列中的进程
174 | S 处于休眠状态
175 | T 停止或被追踪
176 | Z 僵尸进程
177 | W 进入内存交换(从内核 2.6 开始无效)
178 | X 死掉的进程
179 |
180 |
181 |
182 | ## 问题十八:
183 |
184 | 怎么使一个命令在后台运行?
185 |
186 | ### 答案:
187 | 一般都是使用 & 在命令结尾来让程序自动运行。(命令后可以不追加空格)
188 |
189 |
190 | ## 问题十九:
191 |
192 | 利用 ps 怎么显示所有的进程? 怎么利用 ps 查看指定进程的信息?
193 |
194 | ### 答案:
195 | ps -ef (system v 输出)
196 |
197 | ps -aux bsd 格式输出
198 |
199 | ps -ef | grep pid
200 |
201 | ## 问题二十:
202 |
203 | 哪个命令专门用来查看后台任务?
204 |
205 |
206 | ### 答案:
207 |
208 | `job -l`
209 |
210 |
211 | ## 问题二十一:
212 |
213 | 把后台任务调到前台执行使用什么命令?把停下的后台任务在后台执行起来用什么命令?
214 |
215 | ### 答案:
216 | 把后台任务调到前台执行 fg
217 |
218 | 把停下的后台任务在后台执行起来 bg
219 |
220 |
221 |
222 | ## 问题二十二:
223 |
224 | 终止进程用什么命令? 带什么参数?
225 |
226 |
227 | ### 答案:
228 |
229 | kill [-s <信息名称或编号>][程序] 或 kill [-l <信息编号>]
230 |
231 | kill-9 pid
232 |
233 |
234 |
235 | ## 问题二十三:
236 |
237 | 怎么查看系统支持的所有信号?
238 |
239 |
240 | ### 答案:
241 |
242 | `kill -l`
243 |
244 | ## 问题二十四:
245 |
246 | 搜索文件用什么命令? 格式是怎么样的?
247 |
248 |
249 | ### 答案:
250 |
251 | find <指定目录> <指定条件> <指定动作>
252 |
253 | whereis 加参数与文件名
254 |
255 | locate 只加文件名
256 |
257 | find 直接搜索磁盘,较慢。
258 |
259 | find / -name "string*"
260 |
261 |
262 |
263 | ## 问题二十五:
264 |
265 | 查看当前谁在使用该主机用什么命令? 查找自己所在的终端信息用什么命令?
266 |
267 | ### 答案:
268 | 查找自己所在的终端信息:who am i
269 | 查看当前谁在使用该主机:who
270 |
271 |
272 |
273 | ## 问题二十六:
274 |
275 | 使用什么命令查看用过的命令列表?
276 |
277 |
278 | ### 答案:
279 |
280 | `history`
281 |
282 |
283 | ## 问题二十七:
284 |
285 | 使用什么命令查看磁盘使用空间? 空闲空间呢?
286 |
287 |
288 | ### 答案:
289 |
290 | df -hl
291 | 文件系统 容量 已用 可用 已用% 挂载点
292 | Filesystem Size Used Avail Use% Mounted on /dev/hda2 45G 19G 24G 44% /
293 | /dev/hda1 494M 19M 450M 4% /boot
294 |
295 | ## 问题二十八:
296 |
297 | 使用什么命令查看网络是否连通?
298 |
299 | ### 答案:
300 |
301 | `netstat`
302 |
303 | ## 问题二十九:
304 |
305 | 使用什么命令查看 ip 地址及接口信息?
306 |
307 |
308 | ### 答案:
309 |
310 | ifconfig
311 |
312 | ## 问题三十:
313 |
314 | 查看各类环境变量用什么命令?
315 |
316 |
317 | ### 答案:
318 |
319 | 查看所有 env
320 | 查看某个,如 home: env $HOME
321 |
322 | ## 问题三十一:
323 |
324 | 通过什么命令指定命令提示符?
325 |
326 |
327 | ### 答案:
328 |
329 | \u:显示当前用户账号
330 |
331 | \h:显示当前主机名
332 |
333 | \W:只显示当前路径最后一个目录
334 |
335 | \w:显示当前绝对路径(当前用户目录会以~代替)
336 |
337 | $PWD:显示当前全路径
338 |
339 | $:显示命令行’$'或者’#'符号
340 |
341 | \#:下达的第几个命令
342 |
343 | \d:代表日期,格式为week day month date,例如:"MonAug1"
344 |
345 | \t:显示时间为24小时格式,如:HH:MM:SS
346 |
347 | \T:显示时间为12小时格式
348 |
349 | \A:显示时间为24小时格式:HH:MM
350 |
351 | \v:BASH的版本信息 如export PS1=’[\u@\h\w\#]$‘
352 |
353 |
354 |
355 | ## 问题三十二:
356 |
357 | 查找命令的可执行文件是去哪查找的? 怎么对其进行设置及添加?
358 |
359 |
360 | ### 答案:
361 |
362 | whereis [-bfmsu][-B <目录>...][-M <目录>...][-S <目录>...][文件...]
363 |
364 | 补充说明:whereis 指令会在特定目录中查找符合条件的文件。这些文件的烈性应属于原始代码,二进制文件,或是帮助文件。
365 |
366 | -b 只查找二进制文件。
367 | -B<目录> 只在设置的目录下查找二进制文件。 -f 不显示文件名前的路径名称。
368 | -m 只查找说明文件。
369 | -M<目录> 只在设置的目录下查找说明文件。 -s 只查找原始代码文件。
370 | -S<目录> 只在设置的目录下查找原始代码文件。 -u 查找不包含指定类型的文件。
371 | which 指令会在 PATH 变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果。
372 | -n 指定文件名长度,指定的长度必须大于或等于所有文件中最长的文件名。
373 | -p 与-n 参数相同,但此处的包括了文件的路径。 -w 指定输出时栏位的宽度。
374 | -V 显示版本信息
375 |
376 |
377 |
378 | ## 问题三十三:
379 |
380 | 通过什么命令查找执行命令?
381 |
382 | ### 答案:
383 | which 只能查可执行文件
384 |
385 | whereis 只能查二进制文件、说明文档,源文件等
386 |
387 |
388 | ## 问题三十四:
389 |
390 | 怎么对命令进行取别名?
391 |
392 | ### 答案:
393 | alias la='ls -a'
394 |
395 | ## 问题三十五:
396 |
397 | du 和 df 的定义,以及区别?
398 |
399 | ### 答案:
400 |
401 | du 显示目录或文件的大小
402 | df 显示每个<文件>所在的文件系统的信息,默认是显示所有文件系统。
403 | (文件系统分配其中的一些磁盘块用来记录它自身的一些数据,如 i 节点,磁盘分布图,间接块,超级块等。这些数据对大多数用户级的程序来说是不可见的,通常称为 Meta Data。) du 命令是用户级的程序,它不考虑 Meta Data,而 df 命令则查看文件系统的磁盘分配图并考虑 Meta Data。
404 | df 命令获得真正的文件系统数据,而 du 命令只查看文件系统的部分情况。
405 |
406 | ## 问题三十六:
407 |
408 | awk 详解。
409 |
410 | ### 答案:
411 |
412 | awk '{pattern + action}' {filenames}
413 | #cat /etc/passwd |awk -F ':' '{print 1"\t"7}' //-F 的意思是以':'分隔 root /bin/bash
414 | daemon /bin/sh 搜索/etc/passwd 有 root 关键字的所有行
415 |
416 | #awk -F: '/root/' /etc/passwd root:x:0:0:root:/root:/bin/bash
417 |
418 |
419 |
420 | ## 问题三十七:
421 |
422 | 当你需要给命令绑定一个宏或者按键的时候,应该怎么做呢?
423 |
424 |
425 | ### 答案:
426 |
427 | 可以使用bind命令,bind可以很方便地在shell中实现宏或按键的绑定。
428 |
429 | 在进行按键绑定的时候,我们需要先获取到绑定按键对应的字符序列。
430 |
431 | 比如获取F12的字符序列获取方法如下:先按下Ctrl+V,然后按下F12 .我们就可以得到F12的字符序列 ^[[24~。
432 |
433 | 接着使用bind进行绑定。
434 |
435 | [root@localhost ~]# bind ‘”\e[24~":"date"'
436 |
437 | 注意:相同的按键在不同的终端或终端模拟器下可能会产生不同的字符序列。
438 |
439 | 【附】也可以使用showkey -a命令查看按键对应的字符序列。
440 |
441 |
442 |
443 | ## 问题三十八:
444 |
445 | 如果一个linux新手想要知道当前系统支持的所有命令的列表,他需要怎么做?
446 |
447 |
448 | ###
449 | ### 答案:
450 |
451 | 使用命令compgen -c,可以打印出所有支持的命令列表。
452 |
453 | [root@localhost ~]$ compgen -c
454 |
455 | l.
456 |
457 | ll
458 |
459 | ls
460 |
461 | which
462 |
463 | if
464 |
465 | then
466 |
467 | else
468 |
469 | elif
470 |
471 | fi
472 |
473 | case
474 |
475 | esac
476 |
477 | for
478 |
479 | select
480 |
481 | while
482 |
483 | until
484 |
485 | do
486 |
487 | done
488 |
489 | …
490 |
491 |
492 |
493 | ## 问题三十九:
494 |
495 | 如果你的助手想要打印出当前的目录栈,你会建议他怎么做?
496 |
497 |
498 | ### 答案:
499 |
500 | 使用Linux 命令dirs可以将当前的目录栈打印出来。
501 |
502 | [root@localhost ~]# dirs
503 |
504 | /usr/share/X11
505 |
506 | 【附】:目录栈通过pushd popd 来操作。
507 |
508 |
509 |
510 | ## 问题四十:
511 |
512 | 你的系统目前有许多正在运行的任务,在不重启机器的条件下,有什么方法可以把所有正在运行的进程移除呢?
513 |
514 |
515 | ### 答案:
516 |
517 | 使用linux命令 ’disown -r ’可以将所有正在运行的进程移除。
518 |
519 |
520 |
521 | ## 问题四十一:
522 |
523 | bash shell 中的hash 命令有什么作用?
524 |
525 |
526 | ### 答案:
527 |
528 | linux命令’hash’管理着一个内置的哈希表,记录了已执行过的命令的完整路径, 用该命令可以打印出你所使用过的命令以及执行的次数。
529 |
530 | [root@localhost ~]# hash
531 |
532 | hits command
533 |
534 | 2 /bin/ls
535 |
536 | 2 /bin/su
537 |
538 |
539 |
540 | ## 问题四十二:
541 |
542 | 哪一个bash内置命令能够进行数学运算。
543 |
544 |
545 | ### 答案:
546 |
547 | bash shell 的内置命令let 可以进行整型数的数学运算。
548 |
549 | #! /bin/bash
550 | …
551 | …
552 | let c=a+b
553 | …
554 | …
555 |
556 |
557 |
558 | ## 问题四十三:
559 |
560 | 怎样一页一页地查看一个大文件的内容呢?
561 |
562 |
563 | ### 答案:
564 |
565 | 通过管道将命令”cat file_name.txt” 和 ’more’ 连接在一起可以实现这个需要.
566 |
567 | [root@localhost ~]# cat file_name.txt | more
568 |
569 |
570 |
571 | ## 问题四十四:
572 |
573 | 数据字典属于哪一个用户的?
574 |
575 |
576 | ### 答案:
577 |
578 | 数据字典是属于’SYS’用户的,用户‘SYS’ 和 ’SYSEM’是由系统默认自动创建的
579 |
580 |
581 |
582 | ## 问题四十五:
583 |
584 | 怎样查看一个linux命令的概要与用法?假设你在/bin目录中偶然看到一个你从没见过的的命令,怎样才能知道它的作用和用法呢?
585 |
586 |
587 | ### 答案:
588 |
589 | 使用命令whatis 可以先出显示出这个命令的用法简要,比如,你可以使用whatis zcat 去查看‘zcat’的介绍以及使用简要。
590 |
591 | [root@localhost ~]# whatis zcat
592 |
593 | zcat [gzip] (1) – compress or expand files
594 |
--------------------------------------------------------------------------------
/05-EmbeddedLinux/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # 🟢 第五层:嵌入式 Linux 开发基础
4 |
5 | 嵌入式 Linux 是物联网、智能设备、工业控制等领域的核心技术之一。本层重点掌握从 Bootloader 到驱动的开发过程,理解 Linux 系统构成及其移植方法。
6 |
7 | ---
8 |
9 | ## 🔹 嵌入式 Linux 系统概览
10 |
11 | ### 📌 嵌入式 Linux 特点
12 |
13 | - 可裁剪、可定制、模块化强
14 | - 支持多种架构(ARM、MIPS、RISC-V 等)
15 | - 社区支持强大(开源内核、驱动丰富)
16 |
17 | ### 📌 系统组成
18 |
19 | ```text
20 | [Bootloader] → [Kernel] → [Root File System] → [User Application]
21 | ```
22 |
23 | - **Bootloader**:负责上电后硬件初始化、加载内核(如 U-Boot)
24 | - **Kernel**:Linux 内核,管理硬件与系统资源
25 | - **RootFS**:根文件系统,包含用户空间程序
26 | - **应用层**:运行用户程序、脚本、服务等
27 |
28 | ---
29 |
30 | ## 🔹 启动流程详解
31 |
32 | ### 📌 通用启动流程
33 |
34 | ```text
35 | Power On →
36 | BootROM →
37 | Bootloader (SPL/U-Boot) →
38 | Load & Decompress Kernel →
39 | Kernel 初始化 →
40 | 挂载 RootFS →
41 | 启动 init →
42 | Shell / App
43 | ```
44 |
45 | ### 📌 U-Boot(主流 Bootloader)
46 |
47 | - 二阶段:SPL(初始化内存)+ U-Boot 本体
48 | - 功能:串口输出、TFTP 下载、引导内核、环境变量配置等
49 | - 命令示例:
50 | ```bash
51 | setenv bootargs console=ttyS0 root=/dev/mmcblk0p2
52 | tftp 0x80008000 zImage
53 | bootz 0x80008000 - 0x83000000
54 | ```
55 |
56 | ---
57 |
58 | ## 🔹 设备树(Device Tree)
59 |
60 | ### 📌 基本概念
61 |
62 | - 描述硬件资源的结构化信息
63 | - 独立于内核源码,提高可移植性
64 | - 文件类型:`.dts`(源文件)、`.dtsi`(包含文件)、`.dtb`(二进制)
65 |
66 | ### 📌 示例结构
67 |
68 | ```dts
69 | uart1: serial@40011000 {
70 | compatible = "vendor,uart";
71 | reg = <0x40011000 0x400>;
72 | interrupts = <5>;
73 | status = "okay";
74 | };
75 | ```
76 |
77 | ### 📌 编译设备树
78 |
79 | ```bash
80 | make ARCH=arm CROSS_COMPILE=arm-linux- dtbs
81 | ```
82 |
83 | ---
84 |
85 | ## 常用 Linux 命令与开发工具
86 |
87 | ### 文件与目录管理
88 | | 命令 | 功能说明 |
89 | |-------------------|------------------------------|
90 | | `ls -l` | 列出文件详细信息 |
91 | | `cd /path` | 进入目录 |
92 | | `cp source dest` | 拷贝文件/目录 |
93 | | `mv old new` | 移动/重命名文件 |
94 | | `rm -rf dir` | 删除文件或目录 |
95 | | `mkdir name` | 创建新目录 |
96 | | `find` / `grep` | 搜索文件/内容 |
97 |
98 | ### 权限与用户管理
99 | | 命令 | 功能说明 |
100 | |--------------------|-----------------------------|
101 | | `chmod 755 file` | 修改权限(rwx) |
102 | | `chown user:group` | 更改文件拥有者 |
103 | | `sudo` | 以管理员身份执行命令 |
104 | | `whoami` / `id` | 查看当前用户信息 |
105 |
106 | ### 进程管理
107 | | 命令 | 功能说明 |
108 | |------------------|-----------------------------|
109 | | `ps` / `top` | 查看运行进程 |
110 | | `kill PID` | 杀死某个进程 |
111 | | `htop` | 进阶图形化进程管理工具 |
112 | | `nice`, `renice` | 修改进程优先级 |
113 |
114 | ### 网络调试
115 | | 命令 | 功能说明 |
116 | |------------------------|----------------------------|
117 | | `ping` | 测试网络连通性 |
118 | | `ifconfig` / `ip` | 配置 IP、MAC |
119 | | `netstat -anp` | 查看网络连接状态 |
120 | | `scp`, `rsync` | 文件远程复制 |
121 | | `ssh user@host` | SSH 登录远程系统 |
122 |
123 | ### 设备与文件系统
124 | | 命令 | 功能说明 |
125 | |----------------------|-----------------------------|
126 | | `mount` / `umount` | 挂载 / 卸载设备 |
127 | | `df -h` | 查看磁盘空间使用情况 |
128 | | `lsblk`, `blkid` | 查看块设备信息 |
129 | | `dmesg | tail` | 查看内核设备日志 |
130 |
131 | ### 软件包管理(针对开发板所用 Linux)
132 | | 工具 | 说明 |
133 | |--------------------|---------------------------------|
134 | | `apt`, `opkg`, `yum` | 安装 / 卸载软件包 |
135 | | `dpkg -i pkg.deb` | 安装本地 deb 包 |
136 |
137 | ### Shell 脚本与自动化
138 | - `#!/bin/sh` 或 `#!/bin/bash`:脚本头部声明
139 | - 脚本权限设置:`chmod +x script.sh`
140 | - 示例:
141 |
142 | ```sh
143 | #!/bin/bash
144 | for i in {1..5}
145 | do
146 | echo "Test $i"
147 | done
148 | ```
149 |
150 | ### 交叉编译相关命令(Makefile 环境)
151 |
152 | | 命令/工具 | 说明 |
153 | | --------------- | -------------- |
154 | | `make` | 使用 Makefile 构建 |
155 | | `arm-linux-gcc` | 使用交叉编译器编译 |
156 | | `file a.out` | 查看可执行文件平台架构 |
157 |
158 | ---
159 |
160 | ## 🔹 Linux 驱动开发模型
161 |
162 | ### 📌 驱动分层模型
163 |
164 | ```text
165 | [硬件设备] ←→ [总线] ←→ [Device] ←→ [Driver] ←→ [内核]
166 | ```
167 |
168 | - **总线(bus)**:如 platform、i2c、spi 总线
169 | - **设备(device)**:描述具体外设
170 | - **驱动(driver)**:实现对设备的控制逻辑
171 |
172 | ### 📌 字符设备驱动框架
173 |
174 | ```c
175 | struct file_operations fops = {
176 | .open = my_open,
177 | .read = my_read,
178 | .write = my_write,
179 | .release = my_release,
180 | };
181 |
182 | int major = register_chrdev(0, "mydev", &fops);
183 | ```
184 |
185 | ### 📌 平台驱动开发流程
186 |
187 | 1. 定义 `platform_device`
188 | 2. 编写并注册 `platform_driver`
189 | 3. 通过 `of_match_table` 匹配设备树节点
190 | 4. 实现 `probe/remove` 等接口
191 |
192 | ---
193 |
194 | ## 🔹 根文件系统构建
195 |
196 | ### 📌 常见文件系统类型
197 |
198 | - ext3/ext4:标准 Linux 文件系统
199 | - squashfs:只读压缩文件系统,适合嵌入式
200 | - initramfs:内存文件系统
201 |
202 | ### 📌 文件系统布局(典型)
203 |
204 | ```
205 | /
206 | ├── bin/ → 常用命令
207 | ├── sbin/ → 系统工具
208 | ├── etc/ → 配置文件
209 | ├── dev/ → 设备节点
210 | ├── lib/ → 库文件
211 | ├── proc/ → 内核虚拟文件系统
212 | ├── sys/ → 设备/驱动信息
213 | ├── usr/ → 用户软件
214 | ├── tmp/ → 临时目录
215 | └── home/ → 用户主目录
216 | ```
217 |
218 | ### 📌 构建方式
219 |
220 | - BusyBox + 自制文件结构
221 | - Buildroot:快速构建定制系统
222 | - Yocto:更灵活、工业级构建方案
223 |
224 | ---
225 |
226 | ## 🔹 工具链与调试手段
227 |
228 | ### 📌 交叉编译工具链
229 |
230 | - gcc-arm-linux-gnueabi
231 | - arm-none-eabi-gcc
232 | - 使用环境变量指定:
233 | ```bash
234 | export CROSS_COMPILE=arm-linux-
235 | ```
236 |
237 | ### 📌 GDB 调试
238 |
239 | - GDB Server + Remote Debug
240 | ```bash
241 | gdb-multiarch vmlinux
242 | target remote :1234
243 | ```
244 |
245 | ### 📌 常用调试工具
246 |
247 | | 工具 | 用途 |
248 | |-------------|----------------------------|
249 | | GDB | 程序级调试 |
250 | | strace | 跟踪系统调用 |
251 | | dmesg | 内核日志查看 |
252 | | ldd | 查看依赖的库文件 |
253 | | top / htop | 查看系统资源使用情况 |
254 | | lsmod/insmod| 加载/查看内核模块 |
255 |
256 | ---
257 |
258 | ## 🔹 常见开发平台
259 |
260 | | 平台 | 特点 |
261 | |-------------|------------------------------|
262 | | Raspberry Pi | 社区活跃,支持 Linux 全栈 |
263 | | Allwinner / Rockchip | 国产主控,适配良好 |
264 | | BeagleBone | 支持 PRU、实时协处理器 |
265 | | STM32MP1 | 支持 Linux + Cortex-M 协同 |
266 |
267 | ---
268 |
269 | ### 🔹 嵌入式系统安全基础
270 | 1. 威胁模型分析
271 | - 物理攻击:
272 | - 探针访问调试接口(JTAG/SWD)读取 Flash 内容。
273 | - 电压 / 时钟干扰导致程序异常(故障注入攻击)。
274 | - 网络攻击:
275 | - 中间人攻击(MITM)篡改通信数据。
276 | - 恶意固件注入(利用未加密 OTA 通道)。
277 | - 软件攻击:
278 | - 缓冲区溢出执行恶意代码。
279 | - 逆向工程获取算法逻辑(如加密密钥)。
280 |
281 | 2. 安全设计原则
282 | - 最小权限原则:
283 |
284 | 每个组件仅拥有完成任务所需的最小权限(如 MPU 配置)。
285 |
286 | - 防御纵深:
287 |
288 | 多层次安全机制(如安全启动 + 通信加密 + 运行时防护)。
289 |
290 | - 故障安全:
291 |
292 | 系统在异常情况下自动进入安全状态(如看门狗复位)。
293 |
294 | ---
295 |
296 | ### 🔹 安全启动(Secure Boot)
297 |
298 | > 保证启动时加载的固件是可信的
299 |
300 | 1. 基本原理
301 | ```plaintext
302 | BootROM → 加载并验证一级Bootloader → 加载并验证二级Bootloader → 加载并验证应用固件
303 | ```
304 | - 信任链传递:
305 |
306 | 每个阶段只信任经过上一阶段验证的代码。
307 |
308 | 2. 数字签名验证流程
309 | ```c
310 | // 简化的签名验证伪代码
311 | bool VerifyFirmwareSignature(uint8_t *firmware, uint32_t size, uint8_t *signature) {
312 | // 1. 从OTP读取可信根公钥
313 | const uint8_t *trusted_public_key = GetTrustedPublicKey();
314 |
315 | // 2. 计算固件哈希值
316 | uint8_t calculated_hash[32];
317 | SHA256(firmware, size, calculated_hash);
318 |
319 | // 3. 使用公钥解密签名获取原始哈希
320 | uint8_t decrypted_hash[32];
321 | RSA_PKCS1_Verify(trusted_public_key, signature, decrypted_hash);
322 |
323 | // 4. 比较哈希值
324 | return (memcmp(calculated_hash, decrypted_hash, 32) == 0);
325 | }
326 | ```
327 | 3. STM32 Secure Boot 实现
328 | - 选项字节配置:
329 | ```c
330 | // 启用读保护(RDP)
331 | HAL_FLASH_OB_Unlock();
332 | FLASH_OBProgramInitTypeDef obInit = {0};
333 | obInit.OptionType = OPTIONBYTE_RDP;
334 | obInit.RDPLevel = OB_RDP_LEVEL_1; // 禁用调试接口
335 | HAL_FLASHEx_OBProgram(&obInit);
336 | HAL_FLASH_OB_Lock();
337 | ```
338 | - TrustZone 配置(适用于 STM32L5 等支持型号):
339 | ```c
340 | // 配置安全/非安全区域
341 | MPU_Region_InitTypeDef MPU_InitStruct = {0};
342 |
343 | // 配置SRAM为安全区域
344 | MPU_InitStruct.Number = MPU_REGION_0;
345 | MPU_InitStruct.BaseAddress = 0x20000000;
346 | MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
347 | MPU_InitStruct.SubRegionDisable = 0x00;
348 | MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
349 | MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
350 | MPU_InitStruct.DisableExec = DISABLE;
351 | MPU_InitStruct.IsShareable = ENABLE;
352 | MPU_InitStruct.IsCacheable = DISABLE;
353 | MPU_InitStruct.IsBufferable = DISABLE;
354 | HAL_MPU_ConfigRegion(&MPU_InitStruct);
355 | ```
356 |
357 | ---
358 |
359 | ### 🔹 固件加密与防逆向
360 |
361 | 1. **AES 加密固件**,防止泄露源码逻辑
362 |
363 | - 加密流程:
364 | - 开发阶段:使用工具链(如 GCC 插件)加密固件。
365 | - 部署阶段:Bootloader 解密后加载到 RAM 执行。
366 | - 密钥管理:
367 | - 主密钥存储在 OTP(一次性可编程)区域。
368 | - 会话密钥通过主密钥派生(如 AES-KDF)
369 |
370 | 2. Flash 读保护(RDP)
371 |
372 | | RDP 级别 | 保护效果 | 可逆性 |
373 | |------------|------------------------------------------|-------------------------|
374 | | Level 0 | 无保护(默认) | 是 |
375 | | Level 1 | 禁止调试接口,Flash 只能运行不能读取 | 降级会擦除所有 Flash |
376 | | Level 2 | 永久禁止调试接口和 Flash 读取 | 不可逆 |
377 |
378 | 3. 代码混淆技术
379 | - 控制流平坦化:
380 |
381 | 将线性代码转换为基于状态机的结构,增加逆向难度。
382 |
383 | - 指令替换:
384 |
385 | 用等效指令序列替换关键操作(如a+b替换为a-(-b))。
386 |
387 |
388 | ---
389 |
390 | ### 🔹 权限隔离与防护
391 |
392 | 1. MPU(内存保护单元)配置
393 | ```c
394 | // 配置MPU保护关键数据区
395 | void ConfigureMPU(void) {
396 | // 使能MPU
397 | HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
398 |
399 | // 配置区域0保护关键代码区
400 | MPU_Region_InitTypeDef MPU_InitStruct = {0};
401 | MPU_InitStruct.Number = MPU_REGION_0;
402 | MPU_InitStruct.BaseAddress = 0x08000000; // Flash起始地址
403 | MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
404 | MPU_InitStruct.SubRegionDisable = 0x00;
405 | MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
406 | MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW_URO; // 特权可读写,用户只读
407 | MPU_InitStruct.DisableExec = DISABLE;
408 | MPU_InitStruct.IsShareable = DISABLE;
409 | MPU_InitStruct.IsCacheable = DISABLE;
410 | MPU_InitStruct.IsBufferable = DISABLE;
411 | HAL_MPU_ConfigRegion(&MPU_InitStruct);
412 | }
413 | ```
414 | 2. TrustZone 安全域隔离
415 | - 安全资产分类:
416 |
417 | | 类别 | 示例 | 存储位置 |
418 | |------------|------------------------------|------------------|
419 | | 密钥 | TLS 私钥、加密密钥 | 安全 SRAM |
420 | | 敏感算法 | 密码验证、加密函数 | 安全代码区 |
421 | | 安全服务 | OTA 签名验证、证书管理 | 安全任务 |
422 |
423 | - 安全 / 非安全通信:
424 | ```c
425 | // 从非安全代码调用安全服务
426 | __attribute__((section(".nonsecure_call")))
427 | uint32_t SecureService_Call(uint32_t service_id, uint32_t param1, uint32_t param2) {
428 | // 通过SVC指令切换到安全模式
429 | __asm("SVC #0");
430 | // 返回值通过R0传递
431 | }
432 | ```
433 |
434 | ---
435 |
436 | ### 🔹 Bootloader 开发建议
437 |
438 | - 通用功能:下载、校验、重启、回滚
439 | - 支持双分区升级(Slot A / Slot B)
440 | - 防止电量中断、写失败后的砖机风险
441 | - 可设置升级标志位(Upgrade Flag)
442 |
443 | ---
444 |
445 | ## 🔚 小结
446 |
447 | 嵌入式 Linux 是从单片机迈向高性能系统开发的核心门槛,掌握其启动流程、设备树结构与驱动框架是后续学习内核裁剪、系统移植与 IoT 平台开发的基础。
448 |
--------------------------------------------------------------------------------
/04-实时操作系统/README.md:
--------------------------------------------------------------------------------
1 |
2 | # 🟣 第四层:实时操作系统(RTOS)
3 |
4 | 本模块介绍嵌入式 RTOS(如 FreeRTOS)的基础知识、任务调度机制、资源管理方式以及在实际项目中的使用模式。
5 |
6 | ---
7 |
8 | ## 🔹 RTOS 基础概念
9 |
10 | ### 什么是 RTOS?
11 | **RTOS**(Real-Time Operating System)是用于嵌入式设备中的轻量级操作系统,能提供任务调度、时间管理、资源管理等功能。
12 |
13 | **特点:**
14 | - 确定性(Determinism):
15 | - 任务执行时间可预测,如中断响应时间 ≤100μs。
16 | - 对比:通用操作系统(如 Linux)强调吞吐量,不保证实时性。
17 |
18 | - 可抢占内核(Preemptive Kernel):
19 | - 高优先级任务可立即抢占低优先级任务。
20 | - 示例:飞行控制系统中,传感器数据采集任务优先级高于显示任务。
21 |
22 |
23 | ### 常见 RTOS
24 | - FreeRTOS(开源、广泛使用)
25 | - RT-Thread(国产开源,图形化支持强)
26 | - CMSIS-RTOS(ARM 标准接口)
27 | - Zephyr(Linux 基金会支持,适合物联网)
28 |
29 | **RTOS vs 裸机系统**
30 | | 特性 | 裸机系统 | RTOS(实时操作系统) |
31 | |--------------|-----------------------------|---------------------------------|
32 | | 任务管理 | 单任务 / 前后台系统 | 多任务并发,支持任务优先级 |
33 | | 资源分配 | 手动管理 | 自动调度和资源管理 |
34 | | 实时响应 | 依赖主循环结构 | 确定性调度,响应更稳定 |
35 | | 开发难度 | 低(适合简单系统) | 高(需理解调度机制、堆栈管理) |
36 |
37 | **主流 RTOS 对比**
38 | | RTOS | 开源 | 应用领域 | 特点 |
39 | |------------|----------|--------------------------|----------------------------------------------|
40 | | FreeRTOS | ✅ | 工业控制、消费电子 | 轻量级、广泛支持、文档完善 |
41 | | RT-Thread | ✅ | 物联网、智能家居 | 国产、组件丰富(如文件系统、GUI) |
42 | | μC/OS | ⚠️ 商用需授权 | 航空航天、医疗设备 | 支持安全认证(如 DO-178C)、稳定可靠 |
43 | | VxWorks | ❌ | 国防、通信、航天 | 商业闭源、高可靠性、实时性能强 |
44 |
45 |
46 | ---
47 |
48 | ## 🔹 任务管理
49 |
50 | ### 任务创建与内存布局
51 | ```c
52 | // 创建任务示例
53 | void vTaskFunction(void *pvParameters) {
54 | for (;;) {
55 | // 任务代码
56 | vTaskDelay(pdMS_TO_TICKS(100)); // 释放CPU
57 | }
58 | }
59 |
60 | // 任务创建
61 | xTaskCreate(vTaskFunction, "Task1", 256, NULL, 2, NULL);
62 | ```
63 | - 栈空间分配:
64 | - 每个任务独立栈空间,需避免溢出(通过configCHECK_FOR_STACK_OVERFLOW检测)。
65 | - 计算方法:任务局部变量大小 + 函数调用深度 × 最大寄存器保存数。
66 |
67 | ### 任务状态转换
68 | ```plaintext
69 | 调度器选择 超时/事件发生
70 | 就绪 ───────────→ 运行 ←─────────── 阻塞
71 | ↑ │ │
72 | │ └─── 调用vTaskDelay │
73 | │ │
74 | └─────── 调用vTaskSuspend ┘
75 | 或挂起API
76 | ```
77 |
78 |
79 | ### 任务优先级与调度算法
80 | - 抢占式调度:
81 | - 基于任务优先级,高优先级任务可立即抢占当前运行任务。
82 | - 实现:FreeRTOS 通过pxCurrentTCB指针指向当前任务控制块(TCB)。
83 |
84 | - 时间片轮转:
85 | - 同优先级任务按时间片轮流执行(由configTICK_RATE_HZ决定)。
86 | - 示例:两个优先级相同的任务各执行 10ms。
87 |
88 | ---
89 |
90 | ## 🔹 时间管理
91 |
92 | ### 任务延时实现
93 | ```c
94 | // 相对延时(从调用开始计算)
95 | vTaskDelay(pdMS_TO_TICKS(100));
96 |
97 | // 绝对延时(固定周期执行)
98 | TickType_t xLastWakeTime = xTaskGetTickCount();
99 | const TickType_t xFrequency = pdMS_TO_TICKS(100);
100 | for (;;) {
101 | vTaskDelayUntil(&xLastWakeTime, xFrequency);
102 | // 周期性任务代码
103 | }
104 | ```
105 |
106 | ### 软件定时器
107 | - 单次触发:执行一次后停止。
108 | - 周期触发:按固定周期重复执行。
109 | ```c
110 | // 创建并启动定时器
111 | TimerHandle_t xTimer = xTimerCreate(
112 | "Timer", // 定时器名称
113 | pdMS_TO_TICKS(1000), // 周期1秒
114 | pdTRUE, // 周期模式
115 | (void *)0, // 定时器ID
116 | vTimerCallback // 回调函数
117 | );
118 | xTimerStart(xTimer, 0);
119 |
120 | // 定时器回调函数
121 | void vTimerCallback(TimerHandle_t xTimer) {
122 | // 定时任务代码
123 | }
124 | ```
125 |
126 | ---
127 |
128 | ## 🔹 线程间通信
129 |
130 | ### 队列(Queue)
131 | - 特性:
132 | - 线程安全的 FIFO 缓冲区,支持阻塞读写。
133 | - 最大长度和消息大小在创建时指定。
134 |
135 | ```c
136 | // 创建队列
137 | QueueHandle_t xQueue = xQueueCreate(5, sizeof(int)); // 5个int元素
138 |
139 | // 发送消息(阻塞100ms)
140 | int value = 100;
141 | xQueueSend(xQueue, &value, pdMS_TO_TICKS(100));
142 |
143 | // 接收消息(永久等待)
144 | int received_value;
145 | xQueueReceive(xQueue, &received_value, portMAX_DELAY);
146 | ```
147 |
148 | ### 信号量(Semaphore)
149 | #### 二值信号量:
150 | - 用于任务同步(如中断与任务通信)。
151 | ```c
152 | // 创建二值信号量
153 | SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();
154 |
155 | // 任务中获取信号量
156 | if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {
157 | // 获得信号量,执行临界区代码
158 | }
159 |
160 | // 中断中释放信号量
161 | BaseType_t xHigherPriorityTaskWoken = pdFALSE;
162 | xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
163 | portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
164 | ```
165 |
166 | #### 计数信号量(共享资源数量)
167 | - 核心概念
168 | - 资源计数器:初始值为可用资源数量,用于控制对有限资源的访问。
169 | - 操作规则:
170 | - xSemaphoreTake():获取信号量时计数器减 1,若计数器为 0 则阻塞。
171 | - xSemaphoreGive():释放信号量时计数器加 1,唤醒等待任务。
172 | - 典型应用场景
173 | - 多资源管理:如打印机池(假设有 3 台打印机)
174 | ```c
175 | // 创建计数信号量(初始值=3,最大值=3)
176 | SemaphoreHandle_t xPrinterSemaphore = xSemaphoreCreateCounting(3, 3);
177 |
178 | // 任务中请求打印机
179 | if (xSemaphoreTake(xPrinterSemaphore, portMAX_DELAY) == pdTRUE) {
180 | // 获得打印机,执行打印任务
181 | vPrintTask();
182 | // 释放打印机
183 | xSemaphoreGive(xPrinterSemaphore);
184 | }
185 | ```
186 | - 生产者——消费者缓冲区:用信号量跟踪缓冲区空 / 满状态。
187 | #### 互斥信号量(用于资源保护)
188 | - 核心特性
189 | - 二值信号量的特例:初始值为 1,表示资源可用。
190 | - 优先级继承:解决优先级反转问题(低优先级任务持有锁时临时提升其优先级)。
191 | - 优先级反转示例
192 | ```c
193 | // 创建互斥锁
194 | SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
195 |
196 | // 高优先级任务H
197 | void vTaskHigh(void *pvParameters) {
198 | for (;;) {
199 | xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁
200 | // 临界区代码
201 | xSemaphoreGive(xMutex); // 释放锁
202 | }
203 | }
204 |
205 | // 低优先级任务L
206 | void vTaskLow(void *pvParameters) {
207 | for (;;) {
208 | xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁
209 | // 执行长时间操作(此时被中优先级任务M抢占)
210 | xSemaphoreGive(xMutex); // 释放锁
211 | }
212 | }
213 | ```
214 | - 问题:任务 L 持有锁时被任务 M 抢占,导致任务 H 无法执行(优先级反转)。
215 | - 解决:启用优先级继承后,任务 L 持有锁时临时提升至任务 H 的优先级,避免被 M 抢占。
216 |
217 | ### 消息队列(Message Queue)
218 | #### 与普通队列的区别
219 | - 结构化数据传递:支持传递复杂数据类型(如结构体)。
220 | - 指针传递优化:可传递数据指针而非数据本身,减少内存拷贝。
221 |
222 | #### 使用示例
223 | ```c
224 | // 定义消息结构体
225 | typedef struct {
226 | uint8_t command;
227 | uint32_t data;
228 | void (*callback)(void);
229 | } Message_t;
230 |
231 | // 创建消息队列(最多5个消息)
232 | QueueHandle_t xMessageQueue = xQueueCreate(5, sizeof(Message_t));
233 |
234 | // 发送消息
235 | Message_t xMessage = {
236 | .command = 0x01,
237 | .data = 100,
238 | .callback = vProcessCallback
239 | };
240 | xQueueSend(xMessageQueue, &xMessage, portMAX_DELAY);
241 |
242 | // 接收消息
243 | Message_t xReceivedMessage;
244 | if (xQueueReceive(xMessageQueue, &xReceivedMessage, portMAX_DELAY) == pdTRUE) {
245 | // 处理消息
246 | vProcessMessage(&xReceivedMessage);
247 | }
248 | ```
249 | #### 消息队列 vs 普通队列
250 | | 特性 | 普通队列 | 消息队列 |
251 | |--------------|----------------------------------|--------------------------------------------|
252 | | 数据类型 | 固定大小字节块 | 支持结构体、指针等复杂数据类型 |
253 | | 适用场景 | 简单数据传输(如 ADC 值) | 复杂命令传递(如协议解析、任务通信) |
254 | | 内存效率 | 每次传输都需拷贝数据 | 可传递指针,减少内存拷贝,效率更高 |
255 |
256 |
257 | ### 事件组(Event Group)
258 | - 类似标志位,可用于多任务同步
259 | ```c
260 | // 创建事件组
261 | EventGroupHandle_t xEventGroup = xEventGroupCreate();
262 |
263 | // 任务1:设置事件位0
264 | xEventGroupSetBits(xEventGroup, 0x01);
265 |
266 | // 任务2:等待事件位0和1都置位
267 | EventBits_t uxBits = xEventGroupWaitBits(
268 | xEventGroup, // 事件组句柄
269 | 0x03, // 等待位0和1
270 | pdTRUE, // 等待后清除位
271 | pdTRUE, // 等待所有位
272 | portMAX_DELAY // 永久等待
273 | );
274 | ```
275 |
276 | ---
277 |
278 | ## 🔹 资源管理
279 |
280 | ### 内存管理方式
281 | #### 静态分配(推荐)
282 | ```c
283 | // 使用静态内存创建任务
284 | StaticTask_t xTaskBuffer;
285 | StackType_t xStack[256];
286 |
287 | xTaskCreateStatic(
288 | vTaskFunction, // 任务函数
289 | "Task1", // 任务名称
290 | 256, // 栈大小
291 | NULL, // 参数
292 | 2, // 优先级
293 | xStack, // 静态栈
294 | &xTaskBuffer // 静态任务控制块
295 | );
296 | ```
297 | #### 动态分配(需要注意碎片与失败处理)
298 | - 原因:频繁分配 / 释放不同大小的内存块,导致空闲内存分散。
299 | - 示例:
300 | ```c
301 | // 可能导致碎片的错误模式
302 | void vTask(void *pvParameters) {
303 | for (;;) {
304 | char *pcBuffer = (char *)pvPortMalloc(100);
305 | // 使用缓冲区...
306 | vPortFree(pcBuffer); // 释放后可能产生碎片
307 | vTaskDelay(pdMS_TO_TICKS(10));
308 | }
309 | }
310 | ```
311 |
312 | #### 安全使用动态内存的原则
313 | - 预分配固定大小块:
314 | ```c
315 | // 预先分配对象池
316 | static uint8_t xObjectPool[10][100]; // 10个100字节的对象
317 | static BaseType_t xObjectAvailable[10] = {1}; // 标记可用状态
318 |
319 | uint8_t *pvGetObject(void) {
320 | for (int i = 0; i < 10; i++) {
321 | if (xObjectAvailable[i]) {
322 | xObjectAvailable[i] = 0;
323 | return &xObjectPool[i][0];
324 | }
325 | }
326 | return NULL;
327 | }
328 | ```
329 | - 检查分配结果:
330 | ```c
331 | void *pvBuffer = pvPortMalloc(100);
332 | if (pvBuffer == NULL) {
333 | // 内存分配失败处理
334 | vHandleMemoryError();
335 | }
336 | ```
337 |
338 |
339 | ### 临界区保护
340 | - 关中断:
341 | ```c
342 | void vCriticalFunction(void) {
343 | taskENTER_CRITICAL();
344 | // 临界区代码(禁止中断)
345 | taskEXIT_CRITICAL();
346 | }
347 | ```
348 | - 互斥锁:
349 | ```c
350 | // 创建互斥锁
351 | SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
352 |
353 | // 获取锁
354 | xSemaphoreTake(xMutex, portMAX_DELAY);
355 | // 临界区代码
356 | xSemaphoreGive(xMutex); // 释放锁
357 | ```
358 |
359 | ---
360 |
361 | ## 🔹 FreeRTOS 配置与移植
362 |
363 | ### 配置项(FreeRTOSConfig.h)
364 | | 参数 | 描述 | 示例值 |
365 | |-------------------------------|-----------------------------------|--------------------|
366 | | `configUSE_PREEMPTION` | 是否使用抢占式调度 | `1`(启用) |
367 | | `configTICK_RATE_HZ` | 系统滴答频率(Hz) | `1000`(1ms) |
368 | | `configMAX_PRIORITIES` | 最大任务优先级数 | `5 ~ 32` |
369 | | `configMINIMAL_STACK_SIZE` | 最小任务栈大小(以字为单位) | `128`(STM32) |
370 | | `configSUPPORT_DYNAMIC_ALLOCATION` | 是否支持动态内存分配 | `1`(支持) |
371 |
372 |
373 | ### 移植步骤
374 | 1. 提供 SysTick 定时器实现
375 | 2. 提供上下文切换代码(汇编)
376 | 3. 编写启动任务入口函数 `vTaskStartScheduler()`
377 |
378 | ### 移植关键点
379 | - 上下文切换实现(汇编):
380 | ```assembly
381 | ; Cortex-M3/M4 上下文切换示例(PendSV处理函数)
382 | PendSV_Handler:
383 | CPSID I ; 关中断
384 | MRS R0, PSP ; 获取进程栈指针
385 | CBZ R0, PendSV_NoSave ; 首次调用直接切换
386 |
387 | ; 保存寄存器到当前任务栈
388 | SUBS R0, R0, #0x20 ; 调整栈指针
389 | STM R0, {R4-R11} ; 保存R4-R11
390 | LDR R1, =pxCurrentTCB ; 获取当前任务指针
391 | LDR R1, [R1] ; 加载任务控制块地址
392 | STR R0, [R1] ; 保存新的栈指针
393 |
394 | PendSV_NoSave:
395 | LDR R0, =pxCurrentTCB ; 获取当前任务指针
396 | LDR R1, [R0] ; 加载当前任务控制块
397 | LDR R0, [R1, #4] ; 加载下一个任务控制块
398 | STR R0, [R0] ; 更新当前任务指针
399 | LDR R0, [R0] ; 加载新任务栈指针
400 | LDM R0, {R4-R11} ; 恢复寄存器
401 | MSR PSP, R0 ; 更新进程栈指针
402 | ORR LR, LR, #0x04 ; 设置返回标志
403 | CPSIE I ; 开中断
404 | BX LR ; 返回
405 | ```
406 | ---
407 |
408 | ## 🔹 RTOS 调试与性能分析
409 |
410 | ### 调试工具与技术
411 | - 任务状态查看:
412 | ```c
413 | // 获取任务运行时信息
414 | void vTaskList(char *pcWriteBuffer);
415 |
416 | // 示例输出:
417 | // TaskName State Priority Stack Num
418 | // Task1 Running 2 128 1
419 | // Task2 Blocked 1 256 2
420 | ```
421 |
422 | ### 性能指标分析
423 | - CPU 使用率:
424 | ```c
425 | // 计算CPU使用率(需配置configGENERATE_RUN_TIME_STATS=1)
426 | uint32_t ulHighFrequencyTimerTicks;
427 | vTaskGetRunTimeStats(&ulHighFrequencyTimerTicks);
428 | ```
429 | - 任务堆栈深度:
430 | ```c
431 | // 检查任务栈剩余空间
432 | UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
433 | ```
434 |
435 | ---
436 |
437 | ## 面试高频问题
438 |
439 | #### RTOS 中任务与线程的区别:
440 | - 任务是 RTOS 调度的基本单位,线程是操作系统调度的基本单位;RTOS 任务通常更轻量级。
441 |
442 | #### 信号量与互斥锁的区别:
443 | - 信号量可用于同步和资源计数,互斥锁专用于资源保护,支持优先级继承避免死锁。
444 |
445 | #### 如何避免 RTOS 中的死锁:
446 | - 按相同顺序获取锁,使用带超时的锁获取函数,避免嵌套锁。
447 |
448 | #### FreeRTOS 任务优先级设置原则:
449 | - 关键任务(如传感器采样)设高优先级,非关键任务(如显示更新)设低优先级。
450 |
451 | ---
452 | ## 🔹 实践应用场景
453 |
454 | - 多任务协同:传感器数据采集 + 通信模块处理
455 | - 响应式控制:定时器 + 外部中断 + 优先级控制
456 | - 任务调度机制优化(任务嵌套/抢占/时间片轮转)
457 |
--------------------------------------------------------------------------------
/02-嵌入式系统基础知识/README.md:
--------------------------------------------------------------------------------
1 | # 第二层:嵌入式系统基础知识
2 |
3 | ---
4 |
5 | ## 🔹 嵌入式系统概览
6 |
7 | ### 📌 嵌入式系统定义与特点
8 |
9 | **定义**:
10 | - 专用性:针对特定任务优化,如汽车 ABS 防抱死系统仅负责刹车控制。
11 | - 嵌入性:隐藏于设备内部,用户通常意识不到其存在(如微波炉中的控制系统)。
12 | - 计算系统:包含硬件(处理器、传感器)和软件(固件、驱动)。
13 |
14 | **特点**:
15 | - 资源受限(低功耗、内存小)
16 | - 实时性要求高
17 | - 高可靠性与稳定性
18 | - 通常运行裸机或 RTOS
19 |
20 | ---
21 |
22 | ### 📌 系统构成(MCU、存储器、传感器、外设)
23 |
24 | | 模块 | 功能说明 |
25 | |------------|--------------------------------------|
26 | | MCU | 核心控制器,如 ARM Cortex-M |
27 | | Flash/SRAM | 存储程序代码与运行数据 |
28 | | 外设 | GPIO、UART、SPI、I2C、ADC、PWM 等 |
29 | | 传感器 | 温湿度、光照、姿态等 |
30 | | 通信模块 | WiFi、BLE、LoRa、CAN、NB-IoT 等 |
31 | | 电源管理 | 电池、LDO、DC-DC,支持低功耗模式 |
32 |
33 | ```
34 | +--------------------------+
35 | | MCU |
36 | | +----------------------+ |
37 | | | Flash / RAM | |
38 | | | GPIO / UART / ADC | |
39 | | +----------------------+ |
40 | +-----------|--------------+
41 | |
42 | +------+------+
43 | | 外设 / 传感器 |
44 | +-------------+
45 | ```
46 |
47 | 嵌入式系统的硬件构成是一个有机整体,各模块协同工作实现特定功能。以下从核心组件到通信架构进行深度解析:
48 |
49 | ---
50 | ### MCU(微控制器):嵌入式系统的心脏
51 | #### 1. **核心功能**
52 | - **运算与控制**:执行程序指令,处理传感器数据,控制外设。
53 | - **典型架构**:
54 | - **ARM Cortex-M**:主流低功耗MCU(如STM32、Nordic nRF系列)。
55 | - **RISC-V**:开源架构,灵活定制(如SiFive、平头哥玄铁系列)。
56 | - **8051/AVR**:传统8位MCU,低成本(如Arduino Uno基于ATmega328P)。
57 |
58 | #### 2. **关键参数**
59 | - **主频**:决定运算速度(如STM32F103主频72MHz,STM32H7主频480MHz)。
60 | - **内核位数**:8位(适合简单控制)、32位(主流)、64位(高性能应用)。
61 | - **片上外设**:集成ADC、DAC、PWM等功能模块,减少外部芯片依赖。
62 |
63 |
64 | ### 存储器:程序与数据的载体
65 | #### 1. **Flash存储器**
66 | - **功能**:存储程序代码(固件),掉电不丢失。
67 | - **分类**:
68 | - **NOR Flash**:读取速度快,支持XIP(就地执行),适合代码存储。
69 | - **NAND Flash**:容量大、成本低,适合存储大量数据(如SSD、SD卡)。
70 | - **典型应用**:
71 | - MCU内置Flash(如STM32F407含1MB Flash)。
72 | - 外部SPI Flash(如W25Q系列,用于存储文件系统或配置参数)。
73 |
74 | #### 2. **SRAM(静态随机存取存储器)**
75 | - **功能**:运行时数据存储(如变量、堆栈)。
76 | - **特点**:速度快(纳秒级访问),但成本高、容量小。
77 | - **容量配置**:
78 | - 小型MCU:几KB~几十KB(如Arduino Uno含2KB SRAM)。
79 | - 高性能MCU:数百KB~MB级(如STM32H7含1MB SRAM)。
80 |
81 | #### 3. **其他存储类型**
82 | - **EEPROM**:可擦写可编程只读存储器,适合存储少量关键参数(如设备ID)。
83 | - **FRAM**:铁电随机存储器,读写速度快、寿命长(10^12次擦写),用于数据记录。
84 |
85 |
86 | ### 外设接口:与外部世界的桥梁
87 | #### 1. **GPIO(通用输入输出)**
88 | - **功能**:数字信号输入/输出(如控制LED、读取按键状态)。
89 | - **特性**:
90 | - 可配置上拉/下拉电阻。
91 | - 支持中断触发(如外部按键按下时唤醒MCU)。
92 |
93 | #### 2. **通信接口**
94 | | **接口** | **特点** | **典型应用** |
95 | |----------|--------------------------|----------------------------------|
96 | | **UART** | 全双工,异步,2线(TX/RX) | 调试信息输出、与模块通信(如GPS) |
97 | | **SPI** | 全双工,同步,4线(SCK/MOSI/MISO/CS) | 高速数据传输(如OLED屏幕) |
98 | | **I2C** | 半双工,同步,2线(SCL/SDA) | 多设备通信(如连接多个传感器) |
99 | | **CAN** | 差分信号,抗干扰强,多主模式 | 汽车电子(如ECU间通信) |
100 |
101 | #### 3. **模拟接口**
102 | - **ADC(模拟-to-数字转换器)**:
103 | - 将模拟信号(如电压、电流)转换为数字值。
104 | - 精度通常为10~16位(如STM32的12位ADC,分辨率4096级)。
105 | - **DAC(数字-to-模拟转换器)**:
106 | - 将数字值转换为模拟电压输出(如音频信号生成)。
107 |
108 | #### 4. **定时控制**
109 | - **PWM(脉冲宽度调制)**:
110 | - 通过占空比控制输出电压平均值,用于电机调速、LED调光。
111 | - 频率范围:几Hz~MHz(如舵机控制需50Hz PWM)。
112 |
113 |
114 | ### 传感器:感知物理世界的窗口
115 | #### 1. **常见类型**
116 | - **环境传感器**:
117 | - **温湿度**:DHT22、SHT30(精度±0.3°C)。
118 | - **光照**:BH1750(测量范围1~65535 lux)。
119 | - **气压**:BMP280(海拔测量误差±1m)。
120 | - **运动传感器**:
121 | - **加速度计**:ADXL345(检测倾斜、振动)。
122 | - **陀螺仪**:MPU6050(测量角速度,用于姿态解算)。
123 | - **其他**:
124 | - **气体传感器**:MQ-2(检测烟雾、液化气)。
125 | - **红外传感器**:HC-SR501(人体感应)。
126 |
127 | #### 2. **接口方式**
128 | - **数字接口**:I2C(如SHT30)、SPI(如ADXL345)。
129 | - **模拟接口**:输出电压值,需通过MCU的ADC转换(如模拟光照传感器)。
130 |
131 |
132 | ### 通信模块:连接万物的纽带
133 | #### 1. **短距离通信**
134 | - **WiFi**:
135 | - 标准:802.11b/g/n(如ESP8266、ESP32)。
136 | - 应用:智能家居(如智能插座)、数据上传至云端。
137 | - **BLE(蓝牙低功耗)**:
138 | - 传输距离:10~100m,功耗极低(如Nordic nRF52系列)。
139 | - 应用:可穿戴设备(如心率带)、Beacon定位。
140 |
141 | #### 2. **中长距离通信**
142 | - **LoRa**:
143 | - 扩频技术,传输距离5~15km,低功耗(如Semtech SX1278)。
144 | - 应用:物联网广域覆盖(如智能抄表)。
145 | - **NB-IoT**:
146 | - 蜂窝网络,覆盖全国,功耗极低(如Quectel BC660K)。
147 | - 应用:远程监控(如井盖状态监测)。
148 |
149 | #### 3. **工业总线**
150 | - **CAN总线**:
151 | - 传输速率:500kbps~1Mbps,抗干扰强。
152 | - 应用:汽车电子(如车身控制模块)、工业自动化。
153 | - **Modbus**:
154 | - 主从协议,支持RS-232/RS-485,广泛用于工业设备通信。
155 |
156 |
157 | ### 电源管理:续航与稳定性的保障
158 | #### 1. **电源转换**
159 | - **LDO(低压差线性稳压器)**:
160 | - 优点:电路简单,输出纹波小(如AMS1117-3.3)。
161 | - 缺点:效率低(输入输出压差越大,效率越低)。
162 | - **DC-DC转换器**:
163 | - 升压/降压,效率高(可达90%以上,如TPS62160)。
164 | - 适合电池供电设备(如充电宝)。
165 |
166 | #### 2. **低功耗设计**
167 | - **休眠模式**:
168 | - MCU进入休眠,关闭非必要外设,仅保留唤醒源(如RTC时钟)。
169 | - 典型功耗:STM32L4系列休眠电流低至0.5μA。
170 | - **动态电压调整**:
171 | - 根据工作负载动态降低MCU电压(如ARM Cortex-M4的FlexPowerControl)。
172 |
173 | ---
174 | ### 系统集成与典型架构
175 | #### 1. **最小系统**
176 | - MCU + 晶振 + 复位电路 + 电源。
177 | - 示例:STM32最小系统板(核心板)。
178 |
179 | #### 2. **扩展架构**
180 | ```
181 | ┌─────────────────────────────────────────┐
182 | │ 应用层 │
183 | │ (用户逻辑:如温湿度采集、数据处理) │
184 | ├─────────────────────────────────────────┤
185 | │ 驱动层 │
186 | │ (传感器驱动、通信协议栈、外设控制) │
187 | ├─────────────────────────────────────────┤
188 | │ 硬件层 │
189 | │ MCU ── 存储器 ── 外设 ── 传感器 ── 通信 │
190 | └─────────────────────────────────────────┘
191 | ```
192 |
193 | #### 3. **低功耗设计案例**
194 | - **智能手环**:
195 | - 平时MCU处于休眠,加速度计检测运动状态。
196 | - 定时唤醒GPS模块采集位置数据,通过BLE上传手机。
197 |
198 |
199 | ### 开发与调试工具
200 | #### 1. **硬件工具**
201 | - **开发板**:STM32 Nucleo、Arduino、ESP32 DevKit。
202 | - **调试器**:ST-Link、J-Link(用于程序下载和调试)。
203 | - **逻辑分析仪**:Saleae Logic(分析通信协议波形)。
204 |
205 | #### 2. **软件工具**
206 | - **IDE**:Keil MDK、STM32CubeIDE、Arduino IDE。
207 | - **驱动配置**:STM32CubeMX(自动生成初始化代码)。
208 | - **调试工具**:OpenOCD(开源调试协议)、GDB(调试器)。
209 |
210 | ---
211 | ### 面试高频问题
212 | 1. **SPI与I2C的区别**:
213 | - SPI:高速(可达数十Mbps),4线,点对点;I2C:低速(标准100kbps),2线,支持多设备。
214 |
215 | 2. **如何选择合适的通信协议**:
216 | - 短距离高速:SPI;多设备低速:I2C;长距离抗干扰:CAN;广域低功耗:LoRa/NB-IoT。
217 |
218 | 3. **低功耗设计的关键策略**:
219 | - 休眠模式、动态电压调整、关闭非必要外设、使用低功耗通信协议(如BLE)。
220 |
221 | ---
222 |
223 | ## 🔹 架构与启动流程
224 |
225 | ### 📌 Cortex-M 内核结构
226 |
227 | - 32 位精简指令集(Thumb 指令集)
228 | - 内建 NVIC(中断控制器)
229 | - 支持两种堆栈:MSP(主栈)和 PSP(进程栈)
230 | - 寄存器组:R0-R15、LR、PC、xPSR
231 |
232 | ---
233 |
234 | ### 📌 启动文件 Startup.s
235 |
236 | - 用汇编语言书写的启动文件,完成向量表定义、初始化堆栈、调用 `main()`。
237 |
238 | ```asm
239 | Reset_Handler:
240 | LDR R0, =_estack ; 设置栈顶地址
241 | MOV SP, R0
242 | BL SystemInit ; 时钟初始化
243 | BL main ; 跳转到主函数
244 | ```
245 |
246 | ---
247 |
248 | ### 📌 启动流程简要
249 |
250 | 1. MCU 上电 → 执行 `Reset_Handler`
251 | 2. 设置 SP(栈顶)
252 | 3. 初始化 `.data` 和 `.bss` 段
253 | 4. 调用 `SystemInit()`(通常配置系统时钟)
254 | 5. 跳转执行用户 `main()` 函数
255 |
256 | ---
257 |
258 | ## 🔹 编译器与链接器
259 |
260 | ### 嵌入式工具链详解
261 |
262 | #### 1. **Keil MDK(Microcontroller Development Kit)**
263 | - **特点**:
264 | - 商业软件,支持ARM Cortex-M/R/A全系列处理器。
265 | - 集成μVision IDE、ARM编译器、调试器,界面友好。
266 | - 针对STM32等芯片提供Device Family Pack(DFP),简化外设配置。
267 | - **应用场景**:
268 | - 企业级产品开发(如医疗设备、工业控制)。
269 | - 需高效调试功能(如硬件断点、实时变量监控)。
270 |
271 | #### 2. **IAR EWARM(Embedded Workbench for ARM)**
272 | - **特点**:
273 | - 编译效率高,生成代码体积比GCC小10%-20%。
274 | - 调试器支持高级功能(如指令级调试、功耗分析)。
275 | - 跨平台支持(Windows、Linux、Mac)。
276 | - **应用场景**:
277 | - 对代码体积敏感的场景(如MCU Flash空间有限)。
278 | - 汽车电子(符合ISO 26262功能安全标准)。
279 |
280 | #### 3. **GCC (arm-none-eabi)**
281 | - **特点**:
282 | - 开源免费,基于GNU工具链(GCC、GDB、Binutils)。
283 | - 跨平台支持,适合Linux开发者。
284 | - 可通过命令行(CLI)集成到自动化构建流程(如Makefile、CMake)。
285 | - **典型工具**:
286 | - `arm-none-eabi-gcc`:编译器。
287 | - `arm-none-eabi-ld`:链接器。
288 | - `arm-none-eabi-objcopy`:格式转换工具(如生成.bin/.hex文件)。
289 |
290 |
291 | ### 链接脚本(.ld)深入解析
292 | #### 1. **核心作用**
293 | - **内存分区**:定义Flash、RAM等存储器区域的起始地址和大小。
294 | - **段分配**:将代码段(.text)、数据段(.data)、BSS段(.bss)等映射到指定内存区域。
295 | - **地址对齐**:确保关键数据(如中断向量表)位于特定地址。
296 |
297 | #### 2. **MEMORY 区块解析**
298 | ```ld
299 | MEMORY
300 | {
301 | FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K // 只读,可执行
302 | RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K // 可读可写可执行
303 | }
304 | ```
305 | - **属性说明**:
306 | - `r`:可读,`w`:可写,`x`:可执行。
307 | - `ORIGIN`:起始地址,`LENGTH`:大小。
308 |
309 | #### 3. **SECTIONS 区块解析**
310 | ```ld
311 | SECTIONS
312 | {
313 | .text : { *(.text) } > FLASH // 代码段放入Flash
314 | .data : { *(.data) } > RAM AT > FLASH // 已初始化数据运行时在RAM,加载时在Flash
315 | .bss : { *(.bss) } > RAM // 未初始化数据放入RAM
316 | .stack : { . = . + 0x1000; } > RAM // 栈空间(1KB)
317 | .heap : { . = . + 0x2000; } > RAM AT > RAM // 堆空间(2KB)
318 | }
319 | ```
320 | - **关键段说明**:
321 | - `.text`:存储程序代码(如函数体)。
322 | - `.data`:存储已初始化的全局变量(如`int a = 10;`)。
323 | - `.bss`:存储未初始化的全局变量(如`int b;`),运行前自动清零。
324 | - `.stack`:栈空间,用于局部变量和函数调用。
325 | - `.heap`:堆空间,用于动态内存分配(如`malloc`)。
326 |
327 | #### 4. **特殊用法**
328 | - **自定义段**:
329 | ```ld
330 | .mysection : { KEEP(*(.mysection)) } > RAM // 保留特定段,不被链接器优化
331 | ```
332 | - **指定中断向量表位置**:
333 | ```ld
334 | .isr_vector : {
335 | . = ALIGN(4);
336 | KEEP(*(.isr_vector)) // 中断向量表必须位于Flash起始地址
337 | . = ALIGN(4);
338 | } > FLASH
339 | ```
340 |
341 |
342 | ### STM32存储器布局详解
343 | #### 1. **物理内存映射(以STM32F4为例)**
344 | ```
345 | 地址范围 大小 描述
346 | 0x00000000-0x1FFFFFFF 512MB 代码区(可映射到Flash/SRAM/系统内存)
347 | 0x20000000-0x2001FFFF 128KB SRAM(运行时数据)
348 | 0x40000000-0x5FFFFFFF 512MB 外设寄存器(APB/AHB总线)
349 | 0xE0000000-0xE00FFFFF 1MB 系统控制空间(NVIC、SysTick等)
350 | ```
351 |
352 | #### 2. **Flash区域详解**
353 | - **起始地址**:0x08000000(实际代码从此处开始)。
354 | - **典型布局**:
355 | ```
356 | 0x08000000-0x08000100 中断向量表
357 | 0x08000100-0x08080000 程序代码(.text)
358 | 0x08080000-0x08088000 已初始化数据(.data加载时位置)
359 | ```
360 |
361 | #### 3. **RAM区域详解**
362 | - **起始地址**:0x20000000。
363 | - **典型布局**:
364 | ```
365 | 0x20000000-0x20000100 已初始化数据(.data运行时位置)
366 | 0x20000100-0x20000200 未初始化数据(.bss)
367 | 0x20000200-0x20001200 栈空间(向下增长)
368 | 0x20001200-0x20010000 堆空间(向上增长)
369 | ```
370 |
371 | #### 4. **外设映射区(0x40000000起)**
372 | - **GPIO控制器**:0x40020000-0x40025000(如GPIOA基址0x40020000)。
373 | - **USART1**:0x40011000-0x40011400。
374 | - **定时器(TIM2)**:0x40000000-0x40000400。
375 | - **访问示例**:
376 | ```c
377 | #define GPIOA_BASE 0x40020000
378 | #define GPIOA_MODER (*(volatile uint32_t*)(GPIOA_BASE + 0x00)) // 模式寄存器
379 |
380 | GPIOA_MODER |= 0x01; // PA0设为输出模式
381 | ```
382 |
383 |
384 | ### 编译与链接流程
385 | #### 1. **编译阶段**
386 | ```
387 | 源代码(.c/.cpp) → 预处理器 → 编译器 → 汇编代码(.s) → 汇编器 → 目标文件(.o)
388 | ```
389 | - **关键步骤**:
390 | - 预处理器处理`#include`、`#define`等指令。
391 | - 编译器将代码转换为汇编语言。
392 | - 汇编器生成机器码(目标文件)。
393 |
394 | #### 2. **链接阶段**
395 | ```
396 | 多个目标文件(.o) + 库文件(.a/.lib) → 链接器 → 可执行文件(.elf) → 格式转换器 → 固件文件(.bin/.hex)
397 | ```
398 | - **链接器工作**:
399 | 1. 合并所有目标文件的段(如.text、.data)。
400 | 2. 解析符号引用(如函数调用、全局变量)。
401 | 3. 根据链接脚本分配地址。
402 | 4. 生成最终可执行文件。
403 |
404 | #### 3. **烧录阶段**
405 | - 工具:ST-Link、J-Link、OpenOCD等。
406 | - 流程:将.bin/.hex文件写入MCU的Flash起始地址(如0x08000000)。
407 |
408 |
409 | ### 常见问题与调试技巧
410 | #### 1. **链接错误**
411 | - **符号未定义**:
412 | - 原因:调用未实现的函数或使用未定义的变量。
413 | - 解决:检查函数名拼写,确保目标文件包含该符号。
414 | - **内存溢出**:
415 | - 原因:代码或数据量超过Flash/RAM大小。
416 | - 解决:优化代码,减少全局变量,或更换更大容量的MCU。
417 |
418 | #### 2. **调试工具**
419 | - **反汇编工具**:
420 | ```bash
421 | arm-none-eabi-objdump -d main.elf # 生成反汇编代码
422 | ```
423 | - **查看内存分布**:
424 | ```bash
425 | arm-none-eabi-size main.elf # 显示各段大小
426 | ```
427 | 输出示例:
428 | ```
429 | text data bss dec hex filename
430 | 1234 56 789 2079 81F main.elf
431 | ```
432 |
433 | #### 3. **链接脚本调试技巧**
434 | - 添加自定义段:
435 | ```ld
436 | .debug_info : { *(.debug_info) } > RAM // 将调试信息放入RAM
437 | ```
438 | - 使用`KEEP`防止符号被优化:
439 | ```ld
440 | .vectors : { KEEP(*(.vectors)) } > FLASH // 保留中断向量表
441 | ```
442 |
443 |
444 | ### 面试高频问题
445 | 1. **.data和.bss的区别**:
446 | - `.data`存储已初始化的全局变量,占用Flash和RAM;
447 | - `.bss`存储未初始化的全局变量,仅占用RAM(运行前自动清零)。
448 |
449 | 2. **如何减小代码体积**:
450 | - 使用IAR等优化能力更强的编译器。
451 | - 移除无用代码(如未使用的函数)。
452 | - 压缩常量数据(如图片、字体)。
453 |
454 | 3. **链接脚本中AT关键字的作用**:
455 | - `AT`指定段的加载地址,如`.data > RAM AT > FLASH`表示:
456 | - 运行时在RAM(0x20000000),但加载时从Flash(0x08080000)复制到RAM。
457 |
458 | ---
459 |
460 | ### 芯片数据手册阅读方法
461 |
462 | #### 为什么重要?
463 |
464 | 芯片数据手册(Datasheet)和参考手册(Reference Manual)是开发嵌入式系统时的核心参考材料,了解外设功能、寄存器地址、时钟结构、中断号、引脚复用等关键信息。
465 |
466 | #### 典型结构(以 STM32 为例):
467 |
468 | | 部分 | 说明 |
469 | | --------------------------- | -------------------------------- |
470 | | Features | 简要功能描述,例如内核型号、Flash/RAM 容量、外设数量等 |
471 | | Block Diagram | 芯片整体结构图 |
472 | | Pinout / Alternate Function | 引脚定义和复用功能说明 |
473 | | Electrical Characteristics | 电源、电压、电流、温度范围等参数 |
474 | | Memory Map | 内存地址映射(Flash、SRAM、外设地址区间) |
475 | | Peripherals | 每个外设的寄存器结构与配置方法 |
476 |
477 | #### 阅读技巧:
478 |
479 | * **先看 Block Diagram 和内存映射图**,了解系统架构。
480 | * **按功能模块查阅**:比如用 UART,就查看 USART 章节。
481 | * **关注寄存器描述表格**:查看寄存器地址、每个位的含义、读写属性(R/W)和复位值。
482 | * **善用搜索关键词**:如 `RCC_APB2ENR`,快速定位外设时钟控制相关信息。
483 |
484 | ---
485 |
486 | ### 向量表的定义与重定向
487 |
488 | #### 什么是向量表?
489 |
490 | 向量表是处理器在启动时用来获取异常/中断服务函数入口地址的数组,通常放在 Flash 起始地址(如 `0x0800 0000`)或 RAM 中。
491 |
492 | 每个项为一个 **函数指针**,比如:
493 |
494 | ```c
495 | typedef void(*ISR_Handler)(void);
496 | const ISR_Handler vector_table[] __attribute__((section(".isr_vector"))) = {
497 | (ISR_Handler)&_estack, // 初始栈顶指针
498 | Reset_Handler, // Reset
499 | NMI_Handler, // NMI
500 | HardFault_Handler, // HardFault
501 | ...
502 | };
503 | ```
504 |
505 | #### 重定向方法(常用于 Bootloader 或自定义中断):
506 |
507 | **1. 修改中断处理函数指针:**
508 |
509 | ```c
510 | __attribute__((section(".vector_table")))
511 | void (*my_vector_table[])(void) = {
512 | ... // 自定义中断处理函数
513 | };
514 | ```
515 |
516 | **2. 使用 `SCB->VTOR`(Vector Table Offset Register)更改向量表地址:**
517 |
518 | ```c
519 | #include "core_cm4.h"
520 | SCB->VTOR = (uint32_t)my_vector_table;
521 | ```
522 |
523 | > 注意:新表地址必须对齐 0x100(最低 8 位为 0)
524 |
525 | #### 应用场景:
526 |
527 | * Bootloader 跳转到 App 时的向量表切换
528 | * 定制中断处理逻辑
529 | * 启动阶段将向量表从 Flash 重定向到 RAM(以支持运行时修改)
530 |
531 | ---
532 |
533 | ### ROM 启动 vs RAM 启动的差异
534 |
535 | #### 启动方式的定义:
536 |
537 | 启动方式决定系统复位后,**从哪块内存的地址开始执行程序**,通常与 Boot Mode 管脚或 Boot 配置位有关。
538 |
539 | #### ROM 启动(Flash 启动):
540 |
541 | * CPU 从 Flash 起始地址(如 `0x0800 0000`)加载向量表与指令
542 | * 适用于生产烧录版本
543 | * 启动速度快,代码执行稳定
544 |
545 | #### RAM 启动:
546 |
547 | * CPU 从 SRAM 地址(如 `0x2000 0000`)启动
548 | * 适用于调试、自定义 Bootloader 或通过 JTAG 加载代码场景
549 | * 启动前通常需要拷贝一段程序到 RAM(由 Boot ROM、调试器或引导代码完成)
550 |
551 | #### 使用差异:
552 |
553 | | 项目 | ROM 启动 | RAM 启动 |
554 | | ----- | -------------- | --------------------- |
555 | | 程序位置 | 编译链接到 Flash 区域 | 编译链接到 RAM 区域 |
556 | | 向量表位置 | 默认在 Flash | 需手动配置向量表并设置 SCB->VTOR |
557 | | 应用场景 | 量产版本、正常运行 | Bootloader、自举加载、调试 |
558 |
559 | #### 链接脚本修改示例(GCC):
560 |
561 | * ROM 启动:
562 |
563 | ```ld
564 | FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
565 | ```
566 |
567 | * RAM 启动:
568 |
569 | ```ld
570 | RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
571 | ```
572 |
573 |
--------------------------------------------------------------------------------
/01-C语言基础与进阶/Readme.md:
--------------------------------------------------------------------------------
1 | ## 🔵 第一层:C/C++ 语言基础与进阶(必修)
2 |
3 | ### ✅ 变量 / 数据类型 / 关键字 / 常量
4 |
5 | #### 📌 变量(Variable)
6 | - 用于在程序中存储数据的具名内存区域。
7 | - 声明格式:`类型 变量名 [= 初始值];`
8 | ```c
9 | int count = 10;
10 | float temperature;
11 | char c = 'A';
12 | ```
13 | - 局部变量:函数内声明,仅在函数内部可用。
14 | - 全局变量:函数外声明,整个文件或项目中可见(根据作用域)。
15 |
16 | #### 📌 数据类型(Data Types)
17 | - **整型**:`int`, `short`, `long`, `long long`, `unsigned`
18 | - **浮点型**:`float`, `double`
19 | - **字符型**:`char`
20 | - **派生类型**:指针、数组、结构体等
21 | ```c
22 | unsigned int u = 100;
23 | long long big_number = 12345678900LL;
24 | ```
25 |
26 | #### 📌 关键字(Keywords)
27 | 常用 C 关键字解释如下:
28 | | 关键字 | 说明 |
29 | |------------|------------------------------|
30 | | `const` | 定义只读变量 |
31 | | `volatile` | 防止编译器优化,常用于寄存器 |
32 | | `static` | 变量作用域或函数仅在本文件可见 |
33 | | `extern` | 声明外部变量/函数 |
34 | | `typedef` | 为数据类型取别名 |
35 | ```c
36 | static int counter = 0; // 内部链接
37 | extern int g_value; // 声明外部变量
38 | volatile uint32_t *reg = (uint32_t *)0x40021000; // 用于寄存器访问
39 | ```
40 |
41 | #### 📌 常量(Constant)
42 | - **字面常量**:如 `10`, `3.14`, `'a'`, `"abc"`
43 | - **符号常量**:用 `#define` 或 `const` 定义
44 | ```c
45 | #define PI 3.14159
46 | const int MAX_SIZE = 100;
47 | ```
48 |
49 | ---
50 |
51 | ### ✅ 栈 和 堆(内存管理)
52 | #### 栈 (stack):自动分配内存,函数退出即释放。
53 | 1. 核心特性
54 | - 自动分配与释放:由编译器自动管理,函数调用时分配栈帧,函数返回时自动释放。
55 | - 后进先出(LIFO):类似一摞盘子,最后放入的最先取出。
56 | - 高速访问:栈内存访问效率高(通常通过寄存器直接操作)。
57 | - 空间有限:栈空间通常较小(如 Linux 默认 8MB),过大的局部变量可能导致栈溢出。
58 |
59 | 2. 存储内容
60 | - 局部变量:函数内部定义的变量。
61 | - 返回地址:函数执行完毕后返回的位置。
62 | - 函数参数:调用函数时传递的参数。
63 | - 寄存器值:保存调用前的寄存器状态,以便恢复。
64 |
65 | 3. 工作原理
66 | - 栈指针(ESP):指向当前栈顶的内存地址。
67 | - 栈帧(Stack Frame):每个函数调用在栈上分配的独立空间,包含局部变量和参数。
68 | - 示例代码:
69 | ```c
70 | void func(int a, int b) {
71 | int sum = a + b; // sum存储在栈上
72 | // ...
73 | } // 函数返回时,sum和参数a、b自动释放
74 | ```
75 | 4. 优缺点
76 | - 优点:无需手动管理内存,速度快,不会内存泄漏。
77 | - 缺点:生命周期固定(函数结束即释放),空间有限。
78 |
79 | #### 堆 (heap):使用 `malloc` / `free` 手动分配和释放
80 | 1. 核心特性
81 |
82 | - 手动分配与释放:使用malloc/calloc/realloc分配,free释放。
83 | - 动态生命周期:内存块的生命周期由程序员控制,可跨函数使用。
84 | - 碎片化问题:频繁分配和释放可能导致内存碎片,降低空间利用率。
85 | - 慢速访问:需通过指针间接访问,效率低于栈。
86 |
87 | 2. 存储内容
88 | - 动态分配的对象:如malloc返回的内存块。
89 | - 大型数据结构:如数组、链表、树等需要动态调整大小的结构。
90 | - 跨函数数据:需要在函数调用结束后继续存在的数据。
91 |
92 | 3. 工作原理
93 | - 内存管理器:操作系统提供的堆管理器负责分配和回收内存。
94 | - 空闲链表:堆管理器维护空闲内存块列表,分配时查找合适大小的块。
95 | - 示例代码:
96 | ```c
97 | void createArray() {
98 | int* arr = (int*)malloc(10 * sizeof(int)); // 从堆分配内存
99 | if (arr != NULL) {
100 | arr[0] = 100; // 使用堆内存
101 | // ...
102 | }
103 | free(arr); // 手动释放内存
104 | }
105 | ```
106 | 4. 常见函数
107 | - malloc(size_t size):分配指定字节的内存,不初始化。
108 | - calloc(size_t num, size_t size):分配内存并初始化为 0。
109 | - realloc(void* ptr, size_t new_size):调整已分配内存的大小。
110 | - free(void* ptr):释放内存,必须与malloc配对使用。
111 |
112 | **栈 vs 堆的对比**
113 | | 特性 | 栈(Stack) | 堆(Heap) |
114 | |--------------|--------------------------------------|---------------------------------------------|
115 | | 分配方式 | 自动(由编译器管理) | 手动(如 `malloc`/`free` 或 `new`/`delete`)|
116 | | 生命周期 | 函数调用期间自动创建和销毁 | 程序员控制,需手动释放 |
117 | | 内存空间 | 连续、有限(通常几 MB) | 不连续、较大(受限于物理内存) |
118 | | 访问速度 | 快(通过寄存器快速访问) | 慢(通过指针间接访问) |
119 | | 内存碎片 | 不存在(先进后出结构) | 可能产生(频繁分配和释放) |
120 | | 使用场景 | 局部变量、函数调用栈帧 | 动态数据结构、跨函数共享数据 |
121 |
122 | ---
123 |
124 | ### 指针
125 | #### 指针的基本概念
126 |
127 | **指针的作用:** 可以通过指针间接访问内存
128 |
129 | - 内存编号是从0开始记录的,一般用十六进制数字表示
130 | - 可以利用指针变量保存地址
131 |
132 |
133 |
134 | #### 指针变量的定义和使用
135 |
136 | 指针变量定义语法: `数据类型 * 变量名;`
137 |
138 | **示例:**
139 |
140 | ```cpp
141 | int main() {
142 |
143 | //1、指针的定义
144 | int a = 10; //定义整型变量a
145 |
146 | //指针定义语法: 数据类型 * 变量名 ;
147 | int * p;
148 |
149 | //指针变量赋值
150 | p = &a; //指针指向变量a的地址
151 | cout << &a << endl; //打印数据a的地址
152 | cout << p << endl; //打印指针变量p
153 |
154 | //2、指针的使用
155 | //通过*操作指针变量指向的内存
156 | cout << "*p = " << *p << endl;
157 |
158 | system("pause");
159 |
160 | return 0;
161 | }
162 | ```
163 |
164 | 指针变量和普通变量的区别
165 |
166 | - 普通变量存放的是数据,指针变量存放的是地址
167 | - 指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用
168 |
169 | 总结1: 我们可以通过 & 符号 获取变量的地址
170 |
171 | 总结2:利用指针可以记录地址
172 |
173 | 总结3:对指针变量解引用,可以操作指针指向的内存
174 |
175 |
176 |
177 | #### 指针所占内存空间
178 |
179 | 提问:指针也是种数据类型,那么这种数据类型占用多少内存空间?
180 |
181 | **示例:**
182 |
183 | ```cpp
184 | int main() {
185 |
186 | int a = 10;
187 |
188 | int * p;
189 | p = &a; //指针指向数据a的地址
190 |
191 | cout << *p << endl; //* 解引用
192 | cout << sizeof(p) << endl;
193 | cout << sizeof(char *) << endl;
194 | cout << sizeof(float *) << endl;
195 | cout << sizeof(double *) << endl;
196 |
197 | system("pause");
198 |
199 | return 0;
200 | }
201 | ```
202 |
203 | 总结:所有指针类型在32位操作系统下是4个字节,64位操作系统为8个字节
204 |
205 | #### 空指针和野指针
206 |
207 | **空指针**:指针变量指向内存中编号为0的空间
208 |
209 | **用途:** 初始化指针变量
210 |
211 | **注意:** 空指针指向的内存是不可以访问的
212 |
213 |
214 | **示例1:空指针**
215 |
216 | ```cpp
217 | int main() {
218 |
219 | //指针变量p指向内存地址编号为0的空间
220 | int * p = NULL;
221 |
222 | //访问空指针报错
223 | //内存编号0 ~255为系统占用内存,不允许用户访问
224 | cout << *p << endl;
225 |
226 | system("pause");
227 |
228 | return 0;
229 | }
230 | ```
231 |
232 |
233 |
234 | **野指针**:指针变量指向非法的内存空间
235 |
236 | **示例2:野指针**
237 |
238 | ```cpp
239 | int main() {
240 |
241 | //指针变量p指向内存地址编号为0x1100的空间
242 | int * p = (int *)0x1100;
243 |
244 | //访问野指针报错
245 | cout << *p << endl;
246 |
247 | system("pause");
248 |
249 | return 0;
250 | }
251 | ```
252 |
253 | 总结:空指针和野指针都不是我们申请的空间,因此不要访问。
254 |
255 |
256 | #### const修饰指针
257 |
258 |
259 | const修饰指针有三种情况
260 |
261 | 1. const修饰指针 --- 常量指针
262 | 2. const修饰常量 --- 指针常量
263 |
264 | 1. const既修饰指针,又修饰常量
265 |
266 |
267 | **示例:**
268 |
269 | ```cpp
270 | int main() {
271 |
272 | int a = 10;
273 | int b = 10;
274 |
275 | //const修饰的是指针,指针指向可以改,指针指向的值不可以更改
276 | const int * p1 = &a;
277 | p1 = &b; //正确
278 | //*p1 = 100; 报错
279 |
280 |
281 | //const修饰的是常量,指针指向不可以改,指针指向的值可以更改
282 | int * const p2 = &a;
283 | //p2 = &b; //错误
284 | *p2 = 100; //正确
285 |
286 | //const既修饰指针又修饰常量
287 | const int * const p3 = &a;
288 | //p3 = &b; //错误
289 | //*p3 = 100; //错误
290 |
291 | system("pause");
292 |
293 | return 0;
294 | }
295 | ```
296 |
297 | 技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量
298 |
299 |
300 |
301 | #### 指针和数组
302 | 核心概念
303 | - 数组本质:连续存储多个指针变量。
304 | - 用途:常用于处理多个字符串或动态分配的内存块。
305 |
306 | **作用:** 利用指针访问数组中元素
307 |
308 | **示例:**
309 |
310 | ```cpp
311 | int main() {
312 |
313 | int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
314 |
315 | int * p = arr; //指向数组的指针
316 |
317 | cout << "第一个元素: " << arr[0] << endl;
318 | cout << "指针访问第一个元素: " << *p << endl;
319 |
320 | for (int i = 0; i < 10; i++)
321 | {
322 | //利用指针遍历数组
323 | cout << *p << endl;
324 | p++;
325 | }
326 |
327 | system("pause");
328 |
329 | return 0;
330 | }
331 | ```
332 |
333 |
334 |
335 | #### 指针和函数
336 |
337 | **作用:** 利用指针作函数参数,可以修改实参的值(和前边形参相反)
338 |
339 | **示例:**
340 |
341 | ```cpp
342 | //值传递
343 | void swap1(int a ,int b)
344 | {
345 | int temp = a;
346 | a = b;
347 | b = temp;
348 | }
349 | //地址传递
350 | void swap2(int * p1, int *p2)
351 | {
352 | int temp = *p1;
353 | *p1 = *p2;
354 | *p2 = temp;
355 | }
356 |
357 | int main() {
358 |
359 | int a = 10;
360 | int b = 20;
361 | swap1(a, b); // 值传递不会改变实参
362 |
363 | swap2(&a, &b); //地址传递会改变实参
364 |
365 | cout << "a = " << a << endl;
366 |
367 | cout << "b = " << b << endl;
368 |
369 | system("pause");
370 |
371 | return 0;
372 | }
373 | ```
374 |
375 | 总结:如果不想修改实参,就用值传递,如果想修改实参,就用地址传递
376 |
377 |
378 |
379 | #### 指针、数组、函数
380 | - **函数指针声明**:`int (*fp)(int)` 表示指向返回 `int` 的函数的指针
381 | - **函数指针数组**:用于策略模式或注册多个处理函数
382 |
383 | **案例描述:** 封装一个函数,利用冒泡排序,实现对整型数组的升序排序
384 |
385 | 例如数组:int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
386 |
387 |
388 |
389 | **示例:**
390 |
391 | ```cpp
392 | //冒泡排序函数
393 | void bubbleSort(int * arr, int len) //int * arr 也可以写为int arr[]
394 | {
395 | for (int i = 0; i < len - 1; i++)
396 | {
397 | for (int j = 0; j < len - 1 - i; j++)
398 | {
399 | if (arr[j] > arr[j + 1])
400 | {
401 | int temp = arr[j];
402 | arr[j] = arr[j + 1];
403 | arr[j + 1] = temp;
404 | }
405 | }
406 | }
407 | }
408 |
409 | //打印数组函数
410 | void printArray(int arr[], int len)
411 | {
412 | for (int i = 0; i < len; i++)
413 | {
414 | cout << arr[i] << endl;
415 | }
416 | }
417 |
418 | int main() {
419 |
420 | int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
421 | int len = sizeof(arr) / sizeof(int);
422 |
423 | bubbleSort(arr, len);
424 |
425 | printArray(arr, len);
426 |
427 | system("pause");
428 |
429 | return 0;
430 | }
431 | ```
432 |
433 |
434 | ### ✅ 表达式、语句、运算符
435 | - 表达式:`a + b`, `x++`, `p[i]`
436 | - 运算符:`+`, `-`, `*`, `/`, `%`, `&&`, `||`, `!`, `==`, `!=`
437 | - 语句:控制流程结构 `if`, `for`, `while`, `switch`
438 |
439 | ### ✅ 数组 / 字符串
440 | #### 数组(Array)
441 | - 数组是一组相同类型数据的有序集合,在内存中连续存储。
442 | - 一维数组定义:`类型 数组名[大小];`
443 |
444 | ```c
445 | int arr[5] = {1, 2, 3, 4, 5};
446 | char str[10] = "Hello";
447 | ```
448 |
449 | - 数组索引从 0 开始,访问方式如:arr[2] 表示第三个元素。
450 | - 多维数组:int matrix[3][4]; 表示 3 行 4 列矩阵。
451 | #### 字符串(String)
452 | - 字符串是以空字符 '\0' 结尾的字符数组。
453 | - 定义方式:
454 | ```c
455 | char str1[] = "Hello"; // 自动添加 '\0'
456 | char str2[6] = {'H','e','l','l','o','\0'}; // 手动指定
457 | ```
458 | - 常用字符串函数
459 | ```
460 | // 需包含头文件
461 | strlen(str); // 计算长度(不含 '\0')
462 | strcpy(dest, str); // 拷贝
463 | strcmp(a, b); // 比较字符串
464 | strcat(a, b); // 将 b 拼接到 a 后面
465 | ```
466 | ```c
467 | #include
468 |
469 | char msg[20];
470 | strcpy(msg, "Hi"); // msg: "Hi"
471 | strcat(msg, " there"); // msg: "Hi there"
472 | ```
473 |
474 | ### ✅ 结构体 / 共用体 / 枚举 / 位域
475 | #### 结构体
476 | **定义:**
477 |
478 | 结构体是将多个不同类型的数据组合在一起的复合数据类型,用于表示实体的多个属性。
479 | **使用场景:**
480 | - 表示传感器、外设状态、网络包头等复杂数据
481 |
482 | - 多变量统一传参,提升代码组织性
483 |
484 | 语法:
485 | ```c
486 |
487 | struct StructName {
488 | int id;
489 | float value;
490 | char name[20];
491 | };
492 | ```
493 | 示例:
494 | ```c
495 |
496 | struct SensorData {
497 | int id;
498 | float temperature;
499 | char location[20];
500 | };
501 |
502 | struct SensorData s1 = {1, 36.5, "room_1"};
503 | printf("Sensor: %d, Temp: %.1f\n", s1.id, s1.temperature);
504 | ```
505 |
506 | #### 共用体
507 | **定义:**
508 |
509 | 共用体中的所有成员共享同一块内存,任何时刻只能使用其中一个成员。
510 | **使用场景:**
511 | - 节省内存:如嵌入式协议帧解析
512 |
513 | - 多种数据格式的重解释
514 |
515 | 语法:
516 | ```c
517 |
518 | union UnionName {
519 | int i;
520 | float f;
521 | char c;
522 | };
523 | ```
524 | 示例:
525 | ```c
526 |
527 | union Data {
528 | int i;
529 | float f;
530 | };
531 |
532 | union Data d;
533 | d.i = 10;
534 | printf("i = %d\n", d.i);
535 | d.f = 3.14;
536 | printf("f = %.2f\n", d.f); // 修改 f 会破坏 i
537 | ```
538 | 注意事项:
539 | - 占用内存大小为最大成员的大小
540 |
541 | - 修改一个成员后,其他成员的值不可预测
542 |
543 | #### 枚举(Enumeration)
544 | **定义:**
545 |
546 | 枚举是一种用户自定义的数据类型,用于定义一组命名的整数常量。
547 |
548 | **使用场景:**
549 | - 定义状态机状态
550 |
551 | - 表示设备运行模式、错误码
552 |
553 | 语法:
554 | ```c
555 |
556 | enum Color { RED, GREEN, BLUE };
557 | enum Color color = GREEN;
558 | ```
559 | 示例:
560 | ```c
561 |
562 | enum State {
563 | STATE_IDLE,
564 | STATE_ACTIVE,
565 | STATE_ERROR = 100, // 可手动赋值
566 | STATE_SLEEP
567 | };
568 |
569 | enum State current = STATE_ACTIVE;
570 | printf("State: %d\n", current); // 输出 1
571 | ```
572 | 特性:
573 | - 默认从 0 开始递增
574 |
575 | - 可强制设定起始值
576 |
577 | #### 位域(Bit Field)
578 | **定义:**
579 |
580 | 位域用于结构体中,定义每个字段占用的比特位数,实现更细粒度的内存控制。
581 |
582 | **使用场景:**
583 | - 配置寄存器映射
584 |
585 | - 网络协议比特位字段解析
586 |
587 | - 内存空间紧张场景
588 |
589 | 语法:
590 | ```c
591 |
592 | struct Flags {
593 | unsigned int ready : 1;
594 | unsigned int error : 1;
595 | unsigned int mode : 2;
596 | };
597 | ```
598 | 示例:
599 | ```c
600 |
601 | struct Flags f;
602 | f.ready = 1;
603 | f.error = 0;
604 | f.mode = 3; // 占2位,最大为11(二进制)即3
605 |
606 | printf("ready = %d, mode = %d\n", f.ready, f.mode);
607 | ```
608 |
609 | 注意事项:
610 | - 位域不能取地址(&f.ready 不合法)
611 |
612 | - 字段数值不能超过位数范围(2^n - 1)
613 |
614 | - 与具体编译器实现密切相关(跨平台需小心)
615 |
616 | ### ✅ 位操作
617 | - 嵌入式开发中用于设置寄存器位、控制硬件
618 | ```c
619 | #define LED_PIN (1 << 2)
620 | PORT |= LED_PIN; // 置位
621 | PORT &= ~LED_PIN; // 清零
622 | PORT ^= LED_PIN; // 翻转
623 | ```
624 |
625 | ### ✅ 关键语义 & 修饰符
626 | #### `const`(只读限定符)
627 | ```c
628 | const int a = 10;
629 | void print(const char* msg); // msg 不可修改
630 | ```
631 |
632 | #### `volatile`(防止优化)
633 | ```c
634 | volatile int *reg = (int *)0x40021000; // 硬件寄存器访问
635 | ```
636 |
637 | #### `static`(静态变量/内部链接)
638 | ```c
639 | static int count = 0; // 静态变量,函数调用间保留值
640 | ```
641 |
642 | #### `extern`(外部变量声明)
643 | ```c
644 | extern int global_var;
645 | ```
646 |
647 | #### `register`(提示变量存放寄存器)
648 | ```c
649 | register int speed;
650 | ```
651 |
652 | #### `auto`(默认局部变量)
653 | ```c
654 | auto int a = 10; // 一般可省略 auto
655 | ```
656 |
657 | ### ✅ 内存存储类型与生命周期
658 |
659 | | 存储类型 | 生命周期 | 作用域 | 关键字 |
660 | |------------|-----------------|-------------------|--------------|
661 | | 栈(stack) | 函数调用期间 | 局部变量 | auto |
662 | | 静态区 | 程序全程 | 局部/全局 | static |
663 | | 堆(heap) | 手动管理 | 全局 | malloc/free |
664 | | 寄存器 | 函数调用期间 | 局部 | register |
665 |
666 | ### ✅ 编译与调试基础
667 |
668 | #### C 编译四阶段(以 GCC 为例)
669 | ```bash
670 | gcc -E main.c -o main.i # 预处理
671 | gcc -S main.c -o main.s # 编译为汇编
672 | gcc -c main.c -o main.o # 汇编为目标文件
673 | gcc main.o -o main # 链接生成可执行文件
674 | ```
675 |
676 | #### Makefile 示例
677 | ```makefile
678 | CC = gcc
679 | TARGET = app
680 | OBJS = main.o utils.o
681 |
682 | $(TARGET): $(OBJS)
683 | $(CC) -o $@ $^
684 |
685 | %.o: %.c
686 | $(CC) -c $< -o $@
687 |
688 | clean:
689 | rm -f *.o $(TARGET)
690 | ```
691 |
692 | #### GCC 编译参数
693 | | 参数 | 含义 |
694 | |------|------|
695 | | `-Wall` | 开启所有警告 |
696 | | `-g` | 含调试信息 |
697 | | `-O2` | 优化等级 |
698 | | `-I` | 头文件路径 |
699 | | `-L`/`-l` | 库路径和链接 |
700 | | `-D` | 宏定义 |
701 |
702 | #### GDB 基础调试
703 | ```bash
704 | gdb ./main
705 | (gdb) break main
706 | (gdb) run
707 | (gdb) next / step
708 | (gdb) print var
709 | (gdb) continue
710 | ```
711 |
712 | #### 内联汇编
713 | ```c
714 | int result;
715 | __asm__ __volatile__ (
716 | "movl $5, %%eax;"
717 | "movl $3, %%ebx;"
718 | "addl %%ebx, %%eax;"
719 | "movl %%eax, %0;"
720 | : "=r"(result)
721 | :
722 | : "%eax", "%ebx"
723 | );
724 | printf("result = %d\n", result); // 输出 8
725 | ```
726 |
727 | ---
728 |
729 | ## 排序算法
730 | ### 冒泡排序(Bubble Sort)
731 |
732 | #### 原理:
733 |
734 | 相邻元素两两比较,把最大的“冒”到最后。
735 |
736 | #### 时间复杂度:
737 |
738 | * 最坏/平均:O(n²)
739 | * 最好:O(n)(加优化判断)
740 |
741 | #### 适用场景:
742 |
743 | 数据量小、逻辑简单、嵌入式环境友好
744 |
745 | #### 示例代码:
746 |
747 | ```c
748 | void bubble_sort(int arr[], int n) {
749 | for (int i = 0; i < n - 1; ++i) {
750 | int swapped = 0;
751 | for (int j = 0; j < n - i - 1; ++j) {
752 | if (arr[j] > arr[j+1]) {
753 | int t = arr[j]; arr[j] = arr[j+1]; arr[j+1] = t;
754 | swapped = 1;
755 | }
756 | }
757 | if (!swapped) break; // 优化:已排序
758 | }
759 | }
760 | ```
761 |
762 | ---
763 |
764 | ### 选择排序(Selection Sort)
765 |
766 | #### 原理:
767 |
768 | 每轮从未排序区间中选择最小值放到前面。
769 |
770 | #### 时间复杂度:
771 |
772 | * 所有情况:O(n²)
773 |
774 | #### 适用场景:
775 |
776 | 嵌入式设备中内存访问代价高,交换少
777 |
778 | #### 示例代码:
779 |
780 | ```c
781 | void selection_sort(int arr[], int n) {
782 | for (int i = 0; i < n - 1; ++i) {
783 | int min = i;
784 | for (int j = i + 1; j < n; ++j) {
785 | if (arr[j] < arr[min])
786 | min = j;
787 | }
788 | if (min != i) {
789 | int t = arr[i]; arr[i] = arr[min]; arr[min] = t;
790 | }
791 | }
792 | }
793 | ```
794 |
795 | ---
796 |
797 | ### 插入排序(Insertion Sort)
798 |
799 | #### 原理:
800 |
801 | 每次将一个元素插入到已排序部分的合适位置。
802 |
803 | #### 时间复杂度:
804 |
805 | * 最坏/平均:O(n²)
806 | * 最好:O(n)
807 |
808 | #### 适用场景:
809 |
810 | 数据量小、数据基本有序时表现好
811 |
812 | #### 示例代码:
813 |
814 | ```c
815 | void insertion_sort(int arr[], int n) {
816 | for (int i = 1; i < n; ++i) {
817 | int key = arr[i], j = i - 1;
818 | while (j >= 0 && arr[j] > key) {
819 | arr[j+1] = arr[j];
820 | j--;
821 | }
822 | arr[j+1] = key;
823 | }
824 | }
825 | ```
826 |
827 | ---
828 |
829 | ### 快速排序(Quick Sort)
830 |
831 | #### 原理:
832 |
833 | 分治法。选定基准,左边小于它,右边大于它。
834 |
835 | #### 时间复杂度:
836 |
837 | * 最坏:O(n²)
838 | * 平均:O(n log n)
839 |
840 | #### 适用场景:
841 |
842 | 高性能需求、数据量较大(慎用递归栈)
843 |
844 | #### 示例代码:
845 |
846 | ```c
847 | int partition(int arr[], int low, int high) {
848 | int pivot = arr[high], i = low - 1;
849 | for (int j = low; j < high; ++j) {
850 | if (arr[j] < pivot) {
851 | ++i;
852 | int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
853 | }
854 | }
855 | int t = arr[i+1]; arr[i+1] = arr[high]; arr[high] = t;
856 | return i + 1;
857 | }
858 |
859 | void quick_sort(int arr[], int low, int high) {
860 | if (low < high) {
861 | int p = partition(arr, low, high);
862 | quick_sort(arr, low, p - 1);
863 | quick_sort(arr, p + 1, high);
864 | }
865 | }
866 | ```
867 |
868 | ---
869 |
870 | ### 归并排序(Merge Sort)
871 |
872 | #### 原理:
873 |
874 | 分治法。将数组分成两半排序后合并。
875 |
876 | #### 时间复杂度:
877 |
878 | * 所有情况:O(n log n)
879 |
880 | #### 适用场景:
881 |
882 | 追求稳定排序、高精度处理、实时传感器数据等(需额外内存)
883 |
884 | #### 示例代码:
885 |
886 | ```c
887 | void merge(int arr[], int l, int m, int r) {
888 | int n1 = m-l+1, n2 = r-m;
889 | int L[n1], R[n2];
890 |
891 | for (int i = 0; i < n1; ++i) L[i] = arr[l+i];
892 | for (int j = 0; j < n2; ++j) R[j] = arr[m+1+j];
893 |
894 | int i = 0, j = 0, k = l;
895 | while (i < n1 && j < n2)
896 | arr[k++] = (L[i] <= R[j]) ? L[i++] : R[j++];
897 |
898 | while (i < n1) arr[k++] = L[i++];
899 | while (j < n2) arr[k++] = R[j++];
900 | }
901 |
902 | void merge_sort(int arr[], int l, int r) {
903 | if (l < r) {
904 | int m = (l + r) / 2;
905 | merge_sort(arr, l, m);
906 | merge_sort(arr, m+1, r);
907 | merge(arr, l, m, r);
908 | }
909 | }
910 | ```
911 |
912 | ---
913 |
914 | ### 堆排序(Heap Sort)
915 |
916 | #### 原理:
917 |
918 | 利用堆结构(大根堆),反复取出最大元素构造有序序列。
919 |
920 | #### 时间复杂度:
921 |
922 | * 所有情况:O(n log n)
923 |
924 | #### 适用场景:
925 |
926 | 嵌入式中对最值处理(最大温度等)、优先级调度
927 |
928 | #### 示例代码:
929 |
930 | ```c
931 | void heapify(int arr[], int n, int i) {
932 | int largest = i, l = 2*i + 1, r = 2*i + 2;
933 | if (l < n && arr[l] > arr[largest]) largest = l;
934 | if (r < n && arr[r] > arr[largest]) largest = r;
935 | if (largest != i) {
936 | int t = arr[i]; arr[i] = arr[largest]; arr[largest] = t;
937 | heapify(arr, n, largest);
938 | }
939 | }
940 |
941 | void heap_sort(int arr[], int n) {
942 | for (int i = n/2 - 1; i >= 0; i--) heapify(arr, n, i);
943 | for (int i = n-1; i > 0; i--) {
944 | int t = arr[0]; arr[0] = arr[i]; arr[i] = t;
945 | heapify(arr, i, 0);
946 | }
947 | }
948 | ```
949 |
950 | ---
951 |
952 | ## 📌 总结对比表
953 |
954 | | 排序算法 | 时间复杂度(平均) | 空间复杂度 | 稳定性 | 嵌入式适用性 |
955 | | ---- | ---------- | -------- | --- | -------- |
956 | | 冒泡排序 | O(n²) | O(1) | ✅ | ✅(小数据) |
957 | | 选择排序 | O(n²) | O(1) | ❌ | ✅ |
958 | | 插入排序 | O(n²) | O(1) | ✅ | ✅(近似有序) |
959 | | 快速排序 | O(n log n) | O(log n) | ❌ | ⚠️(递归栈) |
960 | | 归并排序 | O(n log n) | O(n) | ✅ | ⚠️(额外内存) |
961 | | 堆排序 | O(n log n) | O(1) | ❌ | ✅ |
962 |
963 |
--------------------------------------------------------------------------------
/06-NetworkIot/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # 🟣 第六层:网络通信与物联网协议(Network & IoT)
4 |
5 | 本模块聚焦于嵌入式系统中的通信机制和物联网协议栈,涵盖串口通信、无线模块、MQTT 等协议到云平台对接,适用于 IoT 产品开发全流程。
6 |
7 | ---
8 |
9 | ## 串口通信与Socket通信
10 |
11 | ### 串口通信
12 | #### 1. 什么是串口通信?
13 |
14 | **串口通信**是一种历史悠久且广泛使用的**点对点、串行、异步**通信方式。它指的是数据在通信线上**一位一位地**顺序传输。最常见的串口标准是 **RS-232**,但在嵌入式系统中,更多使用 **TTL 电平的 UART** (Universal Asynchronous Receiver/Transmitter) 接口。
15 |
16 | 它的特点是**简单、直接**,常用于设备间的短距离连接,比如单片机与电脑、两个单片机之间、或者连接一些外部模块(GPS、蓝牙模块、传感器等)。
17 |
18 | #### 2. 核心特点
19 |
20 | * **串行传输:** 数据一位接一位地在线上传输,而不是并行传输多位。这减少了传输线数量,降低了成本,但在相同频率下速度比并行慢。
21 | * **异步通信:** 发送方和接收方不需要共用同一个时钟信号。双方通过约定好的**波特率**(每秒传输的位数)来同步数据。每个数据帧通常包含起始位、数据位、校验位和停止位。
22 | * **点对点:** 通常只连接两个设备进行通信。
23 | * **硬件接口:** 涉及到物理引脚连接,如 RX (接收数据)、TX (发送数据)、GND (地线) 等。
24 | * **传输距离:** 相对较短,RS-232 理论上是 15 米,但通过 RS-485 等差分信号标准可以达到更远距离。
25 |
26 | #### 3. 工作原理
27 |
28 | 在异步串口通信中,数据以**帧**的形式传输:
29 |
30 | 1. **空闲状态:** 数据线通常保持在高电平。
31 | 2. **起始位:** 发送方发送一个低电平信号,表示一个数据帧的开始。
32 | 3. **数据位:** 紧接着是 5 到 9 位的数据,通常是 8 位。先发低位。
33 | 4. **校验位(可选):** 用于检测数据传输中是否出错(奇校验或偶校验)。
34 | 5. **停止位:** 发送一个高电平信号,表示一个数据帧的结束。可以是一位、一位半或两位。
35 | 6. **空闲状态:** 信号线回到高电平,等待下一个数据帧。
36 |
37 | #### 4. 常见应用
38 |
39 | * **嵌入式设备调试和数据传输:** 单片机与上位机(PC)之间传输调试信息、配置参数、传感器数据等。
40 | * **工业控制:** 连接 PLC、仪器仪表、HMI (人机界面) 等。
41 | * **GPS、蓝牙、WiFi 模块:** 许多这些模块通过串口与主控器进行数据交互。
42 | * **传统外设连接:** 早期鼠标、调制解调器等。
43 |
44 | #### 5. 优点与缺点
45 |
46 | * **优点:** 简单、成本低、实现容易,对于资源有限的设备非常适用。
47 | * **缺点:** 传输速度相对较慢(特别是与以太网相比)、传输距离有限、点对点通信不适合多设备组网。
48 |
49 | ### Socket网络通信
50 | #### 1. 什么是 Socket 通信?
51 |
52 | **Socket (套接字)** 是**网络通信的抽象层**,是**应用层与传输层之间**的接口,它使得应用程序能够通过网络进行数据传输。你可以把 Socket 理解为**连接网络两端进程的“插座”**。通过 Socket,两个独立的进程(可以在同一台机器上,也可以在不同机器上)可以在网络上相互发送和接收数据。
53 |
54 | Socket 通信是构建大多数网络应用(如网页浏览、文件传输、即时通讯、网络游戏)的基础。它通常基于 **TCP/IP 协议栈**。
55 |
56 | #### 2. 核心特点
57 |
58 | * **网络化:** 基于网络协议(如 TCP/IP),可以在全球范围内进行通信。
59 | * **全双工:** 通常情况下,两端可以同时发送和接收数据。
60 | * **面向连接或无连接:**
61 | * **TCP Socket (流套接字):** **面向连接、可靠、基于字节流**。在数据传输前需要建立连接(三次握手),数据传输有顺序、无丢失、无重复保证。
62 | * **UDP Socket (数据报套接字):** **无连接、不可靠、基于数据报**。无需建立连接即可发送数据,数据可能丢失、乱序、重复。
63 | * **IP 地址和端口号:** Socket 通信通过 **IP 地址** 确定目标主机,通过 **端口号** 确定目标主机上的具体应用程序进程。
64 | * **客户端/服务器模式:** 大多数 Socket 通信遵循客户端/服务器模式。服务器程序在特定端口监听连接请求,客户端程序连接到服务器的特定端口发送请求。
65 |
66 | #### 3. 工作原理 (以 TCP Socket 为例)
67 |
68 | 1. **服务器端:**
69 | * **`socket()`:** 创建一个 Socket。
70 | * **`bind()`:** 将 Socket 绑定到一个本地 IP 地址和端口号上。
71 | * **`listen()`:** 使 Socket 进入监听状态,等待客户端连接。
72 | * **`accept()`:** 阻塞等待客户端连接。当有客户端连接时,接受连接并返回一个新的 Socket,用于与该客户端通信。
73 | * **`recv()/send()`:** 通过接受的 Socket 与客户端收发数据。
74 | * **`close()`:** 关闭 Socket。
75 | 2. **客户端:**
76 | * **`socket()`:** 创建一个 Socket。
77 | * **`connect()`:** 连接到服务器的 IP 地址和端口号。
78 | * **`send()/recv()`:** 与服务器收发数据。
79 | * **`close()`:** 关闭 Socket。
80 |
81 | #### 4. 常见应用
82 |
83 | * **Web 服务器:** HTTP 协议就是基于 TCP Socket 实现的。
84 | * **文件传输:** FTP (文件传输协议) 也是基于 TCP Socket。
85 | * **即时通讯:** QQ、微信等聊天软件底层通常使用 Socket 进行通信。
86 | * **网络游戏:** 大多数在线游戏使用 Socket 进行数据交互。
87 | * **物联网云平台通信:** 许多物联网设备(如智能家居网关、工业控制器)通过 Socket 连接到云平台进行数据上传和指令接收(如 MQTT 协议底层就运行在 TCP Socket 上)。
88 |
89 | #### 5. 优点与缺点
90 |
91 | * **优点:** 跨网络通信能力强、支持复杂的网络应用、可靠性高(TCP)、适合大数据量传输。
92 | * **缺点:** 相对复杂,需要理解网络协议栈;资源开销相对较大(TCP 连接维护开销)。
93 |
94 |
95 | ### 串口通信 vs Socket 通信:对比总结
96 |
97 | | 特性 | 串口通信 (UART/RS-232) | Socket 通信 (TCP/UDP) |
98 | | :----------- | :------------------------------------- | :------------------------------------------------ |
99 | | **通信范围** | **局域性** (通常是设备内部或短距离有线连接) | **广域性** (可通过网络在不同设备、服务器之间通信) |
100 | | **传输方式** | **物理层/数据链路层** (硬件直接控制) | **网络层/传输层之上** (通过操作系统提供的接口) |
101 | | **协议栈** | 通常无复杂协议栈,仅定义物理电平和数据帧格式 | 基于 **TCP/IP 协议栈** (IP, TCP, UDP等) |
102 | | **连接对象** | **物理设备** (点对点) | **网络进程** (通过 IP 地址和端口号识别) |
103 | | **抽象程度** | **低** (直接操作硬件寄存器或库) | **高** (抽象为文件描述符,通过系统调用操作) |
104 | | **复杂性** | **相对简单** | **相对复杂** (需理解网络编程和协议) |
105 | | **应用场景** | 调试、嵌入式设备间通信、简单传感器连接 | 互联网应用、物联网云连接、复杂网络数据传输 |
106 |
107 |
108 | ---
109 |
110 | ## 无线通信协议
111 |
112 | ### 1. Wi-Fi
113 | **Wi-Fi** 是一种基于 IEEE 802.11 标准的无线局域网技术,广泛应用于家庭、办公室和公共场所,为设备提供高速、可靠的无线网络连接。在嵌入式领域,Wi-Fi 模块使得设备能够直接连接互联网或作为局部热点。
114 |
115 | #### **ESP32 支持 STA / AP 模式,常用于联网或热点传输**
116 |
117 | * **STA (Station) 模式:** 在这种模式下,ESP32 模块就像我们家里的手机或电脑一样,作为一个**客户端**连接到现有的 Wi-Fi 路由器(AP,Access Point),从而接入互联网。这让嵌入式设备能够上传数据到云端、接收远程指令,实现物联网功能。
118 | * **AP (Access Point) 模式:** 在这种模式下,ESP32 模块会**创建自己的 Wi-Fi 网络**,充当一个**热点**。其他设备(如手机、电脑)可以连接到这个热点,与 ESP32 进行点对点通信。这常用于设备配网(让手机连接 ESP32 设置其连接家里的 Wi-Fi)、设备调试、或者构建一个不依赖外部路由器的本地控制网络。
119 | * **STA + AP 共存模式:** ESP32 还可以同时运行 STA 和 AP 模式。例如,它既可以连接到家里的路由器联网,同时又提供一个临时的热点供手机连接进行本地控制。
120 |
121 | #### **Wi-Fi 应用场景**
122 |
123 | * **SmartConfig:** 这是一种**智能配网技术**,允许用户通过手机 App 快速配置 ESP32 连接到家里的 Wi-Fi 网络,而无需在设备上输入复杂的 Wi-Fi 密码。手机 App 将 Wi-Fi 凭证通过特定方式(如广播或多播)发送,ESP32 侦听到并解析这些凭证完成配网。
124 | * **ESP-NOW:** 这是乐鑫(Espressif)推出的一种**无连接**的 Wi-Fi 通信协议。它允许 ESP32 设备在**没有路由器的情况下**,直接与其他 ESP32 设备进行短距离、快速、低功耗的数据交换。非常适合需要设备间直接通信,且对网络基础设施依赖度低的场景,如智能家居中的灯控、传感器网络等。
125 | * **HTTP Server 应用:** ESP32 可以搭建一个**轻量级的 HTTP 服务器**。当 ESP32 处于 AP 模式时,其他设备连接到它,就可以通过浏览器访问 ESP32 提供的网页界面,进行设备配置、状态监控或远程控制。当 ESP32 处于 STA 模式时,也可以作为 HTTP 客户端与远程服务器进行数据交互(如上传传感器数据到服务器,或从服务器获取控制指令)。
126 |
127 | ### 2. BLE(蓝牙低功耗)
128 | **BLE (Bluetooth Low Energy)** 是蓝牙技术联盟在蓝牙 4.0 版本中推出的一种**超低功耗无线技术**。它专为物联网设备设计,特点是功耗极低、成本低廉,适用于电池供电、数据传输量小且不频繁的场景。
129 |
130 | #### **GATT 协议模型:服务、特征值、通知机制**
131 |
132 | BLE 的核心是 **GATT (Generic Attribute Profile) 协议**,它定义了数据组织和交互的方式:
133 |
134 | * **Service (服务):** 服务是**相关数据的集合**。例如,一个心率监测器可能提供一个“心率服务”,一个温度传感器可能提供一个“环境传感服务”。每个服务都有一个唯一的 **UUID (Universally Unique Identifier)**。
135 | * **Characteristic (特征值):** 特征值是**服务中的具体数据项**。它是设备可读写的最小数据单元。例如,在“心率服务”中,可能有一个“心率测量值”的特征值;在“环境传感服务”中,可能有一个“温度”特征值。每个特征值也拥有唯一的 UUID。特征值有不同的属性,如可读 (Read)、可写 (Write)、可通知 (Notify) 等。
136 | * **Descriptor (描述符):** 描述符是对特征值的进一步描述,例如单位、范围等。
137 | * **通知机制 (Notification):** 订阅方(通常是手机或网关)可以订阅特征值。当设备端(如传感器)的某个特征值发生变化时,它会自动将最新的值**推送**给所有订阅了该特征值的客户端,而无需客户端主动轮询。这大大降低了功耗。
138 |
139 | #### **使用 nRF52、ESP32 等平台进行广播、连接和数据交互**
140 |
141 | * **广播 (Advertising):** BLE 设备可以通过广播发送少量数据包,而无需建立连接。这常用于设备发现、发送传感器快照数据、或作为信标(Beacon)。
142 | * **连接 (Connection):** 当需要进行更频繁或双向的数据交换时,BLE 设备会建立一个连接。一个设备作为**主机 (Master)**,另一个作为**从机 (Slave)**。连接建立后,双方可以根据 GATT 协议模型进行数据交互。
143 | * **数据交互:** 通过读写特征值,或者通过订阅通知/指示 (Indication) 来实现数据的双向传输。例如,手机 App 读取温度传感器的温度特征值,或者订阅其温度变化的通知。
144 | * **平台选择:**
145 | * **Nordic nRF52 系列:** 在低功耗 BLE 领域非常流行,功耗表现和 RF 性能优异,SDK 和开发工具链成熟。
146 | * **ESP32 系列:** 除了 Wi-Fi,ESP32 也集成了强大的 BLE 功能,可以同时支持 Wi-Fi 和 BLE,在物联网应用中具有成本和集成度优势。
147 |
148 | ### 3. LoRa / ZigBee
149 | 这两种协议都属于低功耗广域网(LPWAN)或低功耗短距离无线网络,主要用于传感器网络和远程数据采集。
150 |
151 | #### **LoRa (Long Range) - 长距离通信:适用于室外传感器**
152 |
153 | * **特点:** LoRa 是一种**物理层**的调制技术,它允许数据在极低的功耗下实现**超长距离(数公里到数十公里)**的传输,通常用于室外环境。LoRaWAN 是基于 LoRa 物理层之上的一套完整网络协议,定义了设备、网关和网络服务器之间的通信规则。
154 | * **优势:** 功耗极低、传输距离远、抗干扰能力强。
155 | * **劣势:** 传输速率低,不适合传输大量数据。
156 | * **应用:** 智慧农业(土壤温湿度监测)、智慧城市(停车位监测、垃圾桶液位监测)、工业监测(偏远区域设备状态监控)等。
157 | * **使用 Semtech SX1278 / LoRa 模块通信:** Semtech 是 LoRa 技术的核心提供商,其 SX127x 系列芯片是 LoRa 模块中常用的收发器。在嵌入式开发中,通常使用集成这些芯片的 LoRa 模块(如基于 SX1278 的模块)通过 SPI 或 UART 接口与主控器(如 STM32、ESP32)通信,进行数据的发送和接收。需要关注 LoRaWAN 协议栈的实现。
158 |
159 | #### **ZigBee - 短距离、低功耗、网状网络:适用于室内传感器**
160 |
161 | * **特点:** ZigBee 是一种基于 IEEE 802.15.4 标准的**低功耗、短距离、自组织、网状网络**协议。它适合在室内或有限区域内构建大量的节点网络。
162 | * **优势:**
163 | * **自组织、自愈合的网状网络:** 节点之间可以相互转发数据,扩大通信范围,即使部分节点故障,网络也能自我修复。
164 | * **超低功耗:** 适合电池供电的传感器,电池寿命可达数年。
165 | * **设备容量大:** 一个 ZigBee 网络可以支持大量的设备。
166 | * **劣势:** 传输距离相对有限(几十到几百米)、传输速率低。
167 | * **应用:** 智能家居(灯控、门锁、窗帘)、工业无线传感器网络、医疗监测等。
168 | * **使用 ZigBee 模块通信:** 通常使用集成了 ZigBee 协议栈的模块(如 TI 的 CC2530 系列、NXP 的 JN5169 系列)。这些模块通常通过串口(UART)与主控器通信。开发者需要了解 ZigBee 网络的**组网方式(协调器、路由器、终端设备)、地址分配、数据传输(点对点、广播)和通信帧格式解析**等。例如,发送或接收一个数据帧时,需要构建或解析其头部信息(目标地址、源地址、命令类型等)和有效载荷。
169 |
170 | ---
171 |
172 | ## 物联网协议栈
173 |
174 | #### MQTT 协议详解
175 |
176 | **MQTT (Message Queuing Telemetry Transport)** 是一种**轻量级、发布/订阅模式**的消息传输协议。它专为**资源受限的设备**(如物联网 IoT 设备)以及**低带宽、高延迟或不稳定网络**环境而设计。因其高效、可靠地传输少量数据的能力,MQTT 在物联网领域得到了广泛应用。
177 |
178 |
179 | #### 1. 核心概念与架构
180 |
181 | MQTT 协议由以下三个主要组件构成:
182 |
183 | * **发布者 (Publisher)**:产生并发送消息的客户端设备或应用程序。发布者只负责将消息发送到**主题(Topic)**,不关心谁会接收消息。
184 | * **订阅者 (Subscriber)**:接收消息的客户端设备或应用程序。订阅者通过**订阅一个或多个主题**来表明对哪些消息感兴趣。
185 | * **消息代理/服务器 (Broker)**:MQTT 协议的核心。它负责接收发布者发送的消息,并根据消息的**主题**将消息路由到所有订阅了该主题的客户端。Broker 还处理客户端的连接、断开、订阅、取消订阅等请求,并管理会话状态。
186 |
187 | **工作流程概览:**
188 |
189 | * 客户端(发布者或订阅者)与 MQTT Broker 建立 **TCP 连接**。
190 | * 发布者将消息发送给 Broker,消息包含一个**主题**和一个**有效载荷(Payload)**。
191 | * Broker 接收到消息后,根据消息的**主题**,将其转发给所有订阅了该主题的订阅者。
192 | * 订阅者接收到 Broker 转发的消息。
193 |
194 | #### 2. 发布/订阅 (Publish/Subscribe) 模式
195 |
196 | 这是 MQTT 与传统请求/响应(Request/Response,如 HTTP)模式最显著的区别,也是其优势所在:
197 |
198 | * **解耦性:** 发布者和订阅者之间无需直接知道对方的存在,它们只与 Broker 通信。这使得系统更加灵活、可伸缩、易于维护。
199 | * **异步通信:** 消息是异步发送和接收的,发布者无需等待订阅者的响应。这对于实时性要求不高但需要持续传输数据的场景非常有效。
200 | * **一对多通信:** 一个发布者发送的消息可以同时被多个订阅者接收,非常适合广播和通知场景。
201 | * **主题 (Topic):**
202 | * MQTT 使用主题来分类和路由消息。主题是层级的字符串,例如 `home/livingroom/temperature` 或 `factory/line1/machine/status`。
203 | * 主题支持**通配符**:
204 | * `+`:**单层通配符**,表示一个层级。例如 `home/+/temperature` 可以匹配 `home/livingroom/temperature` 和 `home/bedroom/temperature`。
205 | * `#`:**多层通配符**,表示零或多层。例如 `home/#` 可以匹配 `home/livingroom/temperature`、`home/bedroom/light` 以及 `home` 下的所有子主题。
206 |
207 | #### 3. 服务质量等级 (Quality of Service - QoS)
208 |
209 | MQTT 提供三种 QoS 等级,以满足不同场景下对消息可靠性的要求,实现发布者与 Broker、Broker 与订阅者之间的消息传递保证:
210 |
211 | * **QoS 0: At Most Once (最多一次)**
212 | * 消息发送后即“即发即弃”,不保证消息一定能到达,也不会重试。
213 | * **优点:** 传输速度最快,开销最小。
214 | * **适用场景:** 对实时性要求高、偶尔丢失消息可以接受的场景,如环境传感器数据、非关键性日志等。
215 |
216 | * **QoS 1: At Least Once (至少一次)**
217 | * 消息至少会被送达一次,但可能会重复送达。
218 | * Broker 在收到消息后会发送确认包(PUBACK)。如果发布者在规定时间内未收到 PUBACK,会重发消息。
219 | * **优点:** 保证消息不丢失,适用于重要但不介意重复的消息。
220 | * **适用场景:** 命令控制、重要警报,但上层应用需要处理消息重复。
221 |
222 | * **QoS 2: Exactly Once (只交付一次)**
223 | * 消息只会被送达一次,保证不丢失也不重复。这是最高级的 QoS。
224 | * 通过四次握手(PUBLISH, PUBREC, PUBREL, PUBCOMP)实现。
225 | * **优点:** 最高可靠性,保证消息的唯一性和完整性。
226 | * **适用场景:** 金融交易、计费数据、关键控制命令等对可靠性有严格要求的场景。
227 | * **缺点:** 传输开销最大,效率最低。
228 |
229 | #### 4. 会话管理与持久会话 (Session Management & Persistent Sessions)
230 |
231 | * **Clean Session (清洁会话):**
232 | * 当客户端连接 Broker 时,可以设置 `Clean Session` 标志为 `true` 或 `false`。
233 | * **`Clean Session = true` (非持久会话/瞬时会话):** 客户端每次连接都是一个新的会话。断开连接后,Broker 会清除该客户端的所有订阅和未发送的消息。当客户端重新连接时,会话是全新的,需要重新订阅。
234 | * **`Clean Session = false` (持久会话):** 客户端断开连接后,Broker 会保存其会话状态,包括订阅列表、QoS 1 和 QoS 2 未发送/未确认的消息。当客户端以相同的 `Client ID` 重新连接时,会话会恢复,Broker 会发送离线期间的消息。
235 | * **应用:** 持久会话对于网络不稳定、设备可能频繁掉线的物联网场景非常有用,可以确保设备即使离线也能收到重要的离线消息。
236 |
237 | #### 5. 遗嘱消息 (Last Will and Testament - LWT)
238 |
239 | * 客户端在连接 Broker 时,可以注册一条“遗嘱消息”(Will Message)。
240 | * 如果客户端在非正常断开连接(如设备故障、网络中断)而没有主动发送 DISCONNECT 报文,Broker 就会自动发布这条预先注册的遗嘱消息到指定的主题。
241 | * **作用:** 允许其他客户端(通过订阅该遗嘱主题)感知到某个设备意外离线,从而可以采取相应措施(如状态更新、报警)。
242 |
243 | #### 6. 安全性 (Security)
244 |
245 | MQTT 本身运行在 TCP/IP 协议之上,其安全性主要通过以下层级来保障:
246 |
247 | * **传输层安全 (TLS/SSL):** 这是最常见的安全方式。MQTT 可以通过 TCP/TLS(即 MQTT over SSL/TLS)进行加密通信,确保数据在传输过程中的机密性和完整性,防止窃听和篡改。
248 | * **应用层认证与授权:**
249 | * **用户名/密码认证:** 客户端在连接 Broker 时提供用户名和密码进行身份验证。
250 | * **客户端证书认证:** 更高级别的认证方式,通过 X.509 客户端证书进行双向身份验证。
251 | * **ACL (Access Control List) 授权:** Broker 根据配置的 ACL 规则,限制客户端只能发布或订阅特定的主题,防止未经授权的访问和操作。
252 | * **MQTT 5.0 的增强安全特性:** MQTT 5.0 引入了更多安全功能,如增强认证机制、会话过期、用户属性等,进一步提升了协议的安全性。
253 |
254 | #### 7. MQTT 协议包结构 (Control Packet Structure)
255 |
256 | MQTT 控制报文由三部分组成:
257 |
258 | * **固定报头 (Fixed Header)**:所有 MQTT 控制报文都包含,占 1-5 个字节。包含:
259 | * **报文类型 (Message Type)**:4 位,表示报文的类型(如 CONNECT, PUBLISH, SUBSCRIBE 等)。
260 | * **标志位 (Flags)**:4 位,根据报文类型有不同含义(如 QoS 等级、DUP 标志等)。
261 | * **剩余长度 (Remaining Length)**:可变长度编码,表示可变报头和有效载荷的总长度。
262 | * **可变报头 (Variable Header)**:部分报文包含,根据报文类型不同而不同。例如,PUBLISH 报文的可变报头包含主题名和报文标识符。
263 | * **有效载荷 (Payload)**:部分报文包含,承载实际数据。例如,PUBLISH 报文的有效载荷就是实际要传输的应用数据。
264 |
265 | 由于其紧凑的报文结构和二进制有效载荷,MQTT 在数据传输效率上远优于 HTTP 等协议,尤其适用于数据量小、通信频繁的物联网场景。
266 |
267 | #### 8. MQTT vs HTTP (在 IoT 领域)
268 |
269 | | 特性 | MQTT | HTTP |
270 | | :----------- | :------------------------------------------- | :---------------------------------------------- |
271 | | **通信模式** | **发布/订阅 (Pub/Sub)**,异步,双向 | **请求/响应 (Request/Response)**,同步,单向 |
272 | | **连接状态** | **长连接 (Stateful)**,连接建立后可发送多条消息 | **短连接 (Stateless)**,每次请求通常建立新连接 |
273 | | **消息开销** | **轻量级**,最小报文头 2 字节,效率高 | **相对重**,报文头较大,多为文本格式 |
274 | | **实时性** | **推送 (Push)**,消息实时送达 | **轮询 (Pull)**,客户端定时请求获取更新,非实时 |
275 | | **功耗** | 低(长连接减少频繁建连开销) | 高(频繁建连、断连) |
276 | | **适用场景** | 资源受限设备、低带宽、频繁数据传输、消息推送 | Web 应用、大数据传输、文件下载、一次性请求 |
277 |
278 | **总结:** MQTT 因其轻量、高效、发布/订阅模式和灵活的 QoS,成为物联网设备间通信的理想选择。它很好地解决了 HTTP 在资源受限和网络不稳定环境下的痛点。
279 |
280 | ---
281 |
282 | ### HTTP / HTTPS
283 |
284 | #### HTTP 请求方法(Methods)
285 |
286 | * **GET**:请求指定资源。常用于获取数据。
287 | * **POST**:提交数据到服务器,如表单、上传。
288 | * **PUT**:上传数据,通常是更新资源。
289 | * **DELETE**:删除指定资源。
290 | * **HEAD**:与 GET 类似,但不返回响应体。
291 | * **OPTIONS**:返回服务器支持的请求方法。
292 | * **TRACE**:诊断请求响应路径,回显请求报文。
293 | * **CONNECT**:用于建立隧道(如 HTTPS 代理)。
294 |
295 | #### HTTP 状态码
296 |
297 | | 分类 | 范围 | 含义 |
298 | | --- | -------- | -------- |
299 | | 1xx | 100\~199 | 接收中,继续处理 |
300 | | 2xx | 200\~299 | 请求成功 |
301 | | 3xx | 300\~399 | 重定向或更多操作 |
302 | | 4xx | 400\~499 | 客户端错误 |
303 | | 5xx | 500\~599 | 服务器错误 |
304 |
305 | **部分状态码示例:**
306 |
307 | * 200 OK
308 | * 201 Created
309 | * 204 No Content
310 | * 301 Moved Permanently
311 | * 302 Found
312 | * 304 Not Modified
313 | * 400 Bad Request
314 | * 401 Unauthorized
315 | * 403 Forbidden
316 | * 404 Not Found
317 | * 500 Internal Server Error
318 | * 502 Bad Gateway
319 |
320 | #### HTTP 长连接与短连接
321 |
322 | * **HTTP/1.0** 默认使用短连接(每次请求后断开)
323 | * **HTTP/1.1** 默认使用长连接(`Connection: keep-alive`)
324 |
325 | #### HTTP 请求报文格式
326 |
327 | ```
328 | GET /hello.htm HTTP/1.1
329 | Host: www.example.com
330 | User-Agent: Mozilla/5.0
331 | Accept: */*
332 | Connection: Keep-Alive
333 | ... 其他头部字段
334 | ```
335 |
336 | #### HTTP 响应报文格式
337 |
338 | ```
339 | HTTP/1.1 200 OK
340 | Content-Type: text/html
341 | Content-Length: 158
342 | Server: Apache
343 | Date: Sun, 14 Jun 2025 10:00:00 GMT
344 |
345 | ...
346 | ```
347 |
348 | ### HTTPS 通信过程
349 |
350 | HTTPS = HTTP + TLS/SSL 加密
351 |
352 | #### 通信过程:
353 |
354 | 1. 客户端发起 HTTPS 请求(握手开始)
355 | 2. 服务器返回证书(含公钥)
356 | 3. 客户端验证证书合法性
357 | 4. 客户端生成随机对称密钥(用公钥加密传给服务器)
358 | 5. 双方使用对称密钥开始加密通信
359 |
360 | #### 对称加密(加解密使用同一密钥)
361 |
362 | * **DES**(56 位)
363 | * **AES**(128/192/256 位)
364 |
365 | #### 非对称加密(加密/解密使用公钥/私钥)
366 |
367 | * **RSA**:支持加密、签名
368 | * **DSA**:只支持签名(效率高,但不用于加解密)
369 |
370 | #### 哈希算法(不可逆)
371 |
372 | * **MD5**(128 位)
373 | * **SHA-1**(160 位)
374 | * **SHA-256**(256 位)
375 |
376 |
377 | ### CoAP / LwM2M
378 | - 适合低功耗终端的简化协议,UDP 传输,可压缩
379 | - 用于 NB-IoT、LwIP 等网络栈中
380 |
381 | ---
382 |
383 | ### 🔹 安全通信实践
384 | 1. TLS 握手优化
385 | - 预共享密钥(PSK)模式:
386 | 减少证书验证开销,适合资源受限设备。
387 | ```c
388 | // mbed TLS配置PSK
389 | mbedtls_ssl_config_set_psk(&ssl_conf,
390 | psk, // 预共享密钥
391 | psk_length,
392 | identity, // 身份标识
393 | strlen(identity));
394 | ```
395 | 2. 证书管理方案
396 | - 证书存储:
397 | - 根证书存储在安全 Flash 区域。
398 | - 设备证书通过安全通道动态更新。
399 | - 证书验证:
400 | ```c
401 | // 验证服务器证书链
402 | int verify_cert(void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags) {
403 | // 检查证书有效期
404 | if (mbedtls_x509_crt_check_validity(crt, time(NULL)) != 0) {
405 | return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
406 | }
407 |
408 | // 检查证书颁发者
409 | if (!mbedtls_x509_crt_verify(crt, trusted_certs, NULL, NULL, flags, NULL, NULL)) {
410 | return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
411 | }
412 |
413 | return 0;
414 | }
415 | ```
416 |
417 |
418 | ---
419 |
420 | ### 🔹 安全测试
421 |
422 | 1. 固件逆向分析
423 | - 工具链:
424 | - Ghidra:反编译二进制文件,生成 C 语言伪代码。
425 | - IDA Pro:专业逆向工程工具,支持 ARM 架构。
426 | - 防御措施:
427 | - 固件加密:使用 AES-256 加密整个固件。
428 | - 反调试机制:检测调试接口是否被连接。
429 | ```c
430 | // 检测SWD/JTAG调试接口
431 | bool IsDebuggerAttached(void) {
432 | // 读取DBGMCU_IDCODE寄存器
433 | uint32_t idcode = DBGMCU->IDCODE;
434 | // 检查调试使能位
435 | return ((DBGMCU->CR & (DBGMCU_CR_DBG_SLEEP | DBGMCU_CR_DBG_STOP | DBGMCU_CR_DBG_STANDBY)) != 0);
436 | }
437 | ```
438 |
439 | 2. 侧信道攻击防护
440 | - 电源分析攻击:
441 | 通过测量设备功耗分析加密密钥。
442 | - 防护措施:
443 | 常量时间实现:避免条件分支依赖密钥值。
444 | ```c
445 | // 常量时间比较(防止时序攻击)
446 | bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
447 | uint8_t result = 0;
448 | for (size_t i = 0; i < len; i++) {
449 | result |= a[i] ^ b[i];
450 | }
451 | return (result == 0);
452 | }
453 | ```
454 |
455 | ---
456 |
457 | ## TCP/IP 协议栈基础与嵌入式实现
458 |
459 | #### TCP/IP 协议栈分层结构(四层模型)
460 |
461 | | 层级 | 协议/组件 | 功能说明 |
462 | |----------------|----------------------------|----------------------------------|
463 | | 应用层 | HTTP, MQTT, CoAP, DNS | 面向用户的协议 |
464 | | 传输层 | TCP, UDP | 数据传输可靠性与端口管理 |
465 | | 网络层 | IP, ICMP, ARP | 地址与路由 |
466 | | 链路层 | Ethernet, Wi-Fi, BLE | 硬件通信和数据帧传输 |
467 |
468 |
469 | #### TCP 与 UDP 区别
470 |
471 | | 特性 | TCP | UDP |
472 | |----------------|----------------------------|----------------------------|
473 | | 是否连接 | 是(面向连接) | 否(无连接) |
474 | | 是否可靠 | 是(有重传、确认) | 否(可能丢包) |
475 | | 适用场景 | Web、文件传输、SSH | 视频流、语音、广播 |
476 | | 开销 | 较大(握手、窗口等) | 较小(直接发送) |
477 |
478 |
479 | #### 嵌入式 TCP/IP 协议栈组件
480 |
481 | - **LwIP(Lightweight IP)**
482 | - 开源轻量级 TCP/IP 协议栈
483 | - 支持 TCP/UDP/IP/DNS/DHCP 等
484 | - 常用于 STM32、ESP32、RT-Thread 中
485 | - **uIP(micro IP)**
486 | - 更轻量,适合资源极小的 MCU
487 | - **FreeRTOS+TCP**
488 | - 与 FreeRTOS 配套的 TCP/IP 协议栈
489 | - **Nut/Net、CycloneTCP**:其他常用协议栈
490 |
491 |
492 | #### 嵌入式 TCP/IP 通信流程(以 LwIP 为例)
493 |
494 | 1. **初始化网络接口**:配置 IP、MAC、网关
495 | 2. **创建 socket 套接字**:TCP 或 UDP
496 | 3. **建立连接 / 绑定端口**
497 | 4. **接收/发送数据**:`recv()`, `send()`
498 | 5. **关闭连接**:`close()`
499 |
500 |
501 |
502 | #### 常用 API 示例(LwIP BSD socket)
503 |
504 | ```c
505 | // TCP 客户端示例
506 | int sock = socket(AF_INET, SOCK_STREAM, 0);
507 | struct sockaddr_in server;
508 | server.sin_family = AF_INET;
509 | server.sin_port = htons(12345);
510 | server.sin_addr.s_addr = inet_addr("192.168.1.10");
511 |
512 | connect(sock, (struct sockaddr*)&server, sizeof(server));
513 | send(sock, "Hello", strlen("Hello"), 0);
514 | recv(sock, buffer, sizeof(buffer), 0);
515 | close(sock);
516 | ```
517 |
518 | #### DHCP / DNS / ICMP 说明
519 |
520 | * **DHCP**:动态分配 IP(LwIP 可配置)
521 | * **DNS**:域名解析,调用 `gethostbyname()` 等
522 | * **ICMP**:如 `ping` 实现通信测试
523 |
524 | #### 推荐阅读资料
525 |
526 | * [LwIP 官方文档](https://savannah.nongnu.org/projects/lwip/)
527 | * [FreeRTOS+TCP 文档](https://freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_TCP/index.html)
528 | * [TCP/IP Illustrated (Vol 1)](https://book.douban.com/subject/1088054/)
529 |
530 | ---
531 |
532 |
533 |
534 | ## 云平台接入 & OTA 实现
535 |
536 | ### 云平台对接
537 | - 主流平台:阿里云 IoT、腾讯连连、OneNet、ThingsBoard
538 | - 认证方式:三元组 / MQTT 密钥 / TLS 证书
539 |
540 | ### OTA 升级机制
541 | > 支持远程更新嵌入式系统的固件版本
542 | 1. 双分区升级架构
543 |
544 | ```plaintext
545 | Flash布局:
546 | +-------------------+ 0x08000000
547 | | Bootloader |
548 | +-------------------+ 0x08010000
549 | | Application Slot A|
550 | +-------------------+ 0x08040000
551 | | Application Slot B|
552 | +-------------------+ 0x08070000
553 | | Configuration Area|
554 | +-------------------+
555 | ```
556 | 2. 升级状态机实现
557 |
558 | ```c
559 | typedef enum {
560 | OTA_IDLE, // 空闲状态
561 | OTA_CHECKING, // 检查更新
562 | OTA_DOWNLOADING, // 下载中
563 | OTA_VERIFYING, // 校验中
564 | OTA_READY, // 准备重启
565 | OTA_UPGRADING, // 升级中
566 | OTA_FAILED // 升级失败
567 | } OTA_State_t;
568 |
569 | // OTA状态机处理函数
570 | void OTA_Process(void) {
571 | switch (ota_state) {
572 | case OTA_IDLE:
573 | if (check_update_flag) {
574 | ota_state = OTA_CHECKING;
575 | vCheckForUpdate();
576 | }
577 | break;
578 |
579 | case OTA_DOWNLOADING:
580 | if (download_complete) {
581 | ota_state = OTA_VERIFYING;
582 | vVerifyFirmware();
583 | } else if (download_error) {
584 | ota_state = OTA_FAILED;
585 | vHandleError(DOWNLOAD_ERROR);
586 | }
587 | break;
588 |
589 | // 其他状态处理...
590 | }
591 | }
592 | ```
593 | 3. 失败回滚机制
594 |
595 | ```c
596 | // 启动时验证应用完整性
597 | bool ValidateApplication(uint32_t start_address) {
598 | // 检查向量表签名
599 | uint32_t *vector_table = (uint32_t *)start_address;
600 | if (vector_table[0] == 0xFFFFFFFF) { // 检查栈顶指针是否有效
601 | return false;
602 | }
603 |
604 | // 计算应用哈希并验证
605 | uint8_t calculated_hash[32];
606 | SHA256((uint8_t *)start_address, APPLICATION_SIZE, calculated_hash);
607 |
608 | // 从配置区获取预期哈希
609 | uint8_t *expected_hash = GetExpectedHash();
610 | return (memcmp(calculated_hash, expected_hash, 32) == 0);
611 | }
612 |
613 | // 主程序
614 | int main(void) {
615 | // 初始化硬件
616 | HAL_Init();
617 | SystemClock_Config();
618 |
619 | // 检查主应用是否有效
620 | if (ValidateApplication(APPLICATION_SLOT_A_ADDRESS)) {
621 | // 跳转到主应用
622 | JumpToApplication(APPLICATION_SLOT_A_ADDRESS);
623 | } else if (ValidateApplication(APPLICATION_SLOT_B_ADDRESS)) {
624 | // 主应用无效,尝试从备份应用启动
625 | JumpToApplication(APPLICATION_SLOT_B_ADDRESS);
626 | } else {
627 | // 两个应用都无效,进入恢复模式
628 | EnterRecoveryMode();
629 | }
630 | }
631 | ```
632 | #### ✅ OTA 流程核心步骤
633 |
634 | 1. 检查版本更新(HTTP/MQTT 下载 manifest)
635 | 2. 下载固件(二进制)
636 | 3. 存储到备份区(Backup Slot)
637 | 4. 校验 CRC/Hash / 签名
638 | 5. 设置 Bootloader 标志位并重启
639 | 6. Bootloader 引导进入新固件
640 | 7. 若失败则回滚(Fail-safe 机制)
641 |
642 | #### ✅ 常用升级协议
643 |
644 | - HTTP / HTTPS
645 | - MQTT + Base64 二进制块传输
646 | - CoAP(轻量级)
647 |
648 | ---
649 |
650 | ## ✅ 推荐学习顺序
651 |
652 | 1. 学习 UART 通信与基本网络 socket 原理
653 | 2. 掌握 Wi-Fi / BLE 开发流程(推荐 ESP32/nRF52)
654 | 3. 理解 MQTT 协议与平台接入逻辑
655 | 4. 实践 OTA 升级流程,构建远程维护能力
656 |
657 | ---
658 |
659 | ## 📌 常见问题 FAQ
660 |
661 | | 问题 | 解答 |
662 | |------|------|
663 | | MQTT 断线如何重连? | 设置心跳机制与 reconnect 回调逻辑 |
664 | | OTA 更新失败怎么办? | 回退机制 + 双镜像分区设计 |
665 | | CoAP 和 MQTT 有何区别? | CoAP 基于 UDP,适合低功耗设备;MQTT 基于 TCP,稳定性好 |
666 |
--------------------------------------------------------------------------------
/08-项目实战与工具链/README.md:
--------------------------------------------------------------------------------
1 | # 📦 第八层:项目实战与工具链
2 |
3 | ## ✅ 工程管理
4 |
5 | ### 🔹 Git 版本控制
6 |
7 | #### 1. **Git 分支策略**
8 | - **主干分支(main/master)**:
9 | 永远代表可发布的稳定版本,仅接受通过CI/CD验证的代码。
10 | - **开发分支(develop)**:
11 | 集成所有新功能的开发,是日常开发的基础分支。
12 | - **特性分支(feature/*)**:
13 | 从develop分支创建,用于开发单个新功能或修复问题,完成后合并回develop。
14 | - **发布分支(release/*)**:
15 | 从develop分支创建,用于准备发布版本,进行最后的测试和Bug修复。
16 | - **热修复分支(hotfix/*)**:
17 | 从main分支创建,用于紧急修复生产环境问题,修复后合并回main和develop。
18 |
19 | #### 2. **提交规范**
20 | 采用Conventional Commits规范:
21 | ```
22 | <类型>[可选范围]: <描述>
23 |
24 | [可选正文]
25 |
26 | [可选脚注]
27 | ```
28 | - **常见类型**:
29 | - `feat`:新功能
30 | - `fix`:修复Bug
31 | - `docs`:文档更新
32 | - `style`:代码格式调整(不影响功能)
33 | - `refactor`:代码重构
34 | - `test`:添加或修改测试
35 | - `chore`:构建或辅助工具的变动
36 |
37 | #### 3. **标签管理**
38 | 使用语义化版本(SemVer)打标签:
39 | ```bash
40 | # 创建标签
41 | git tag v1.0.0
42 |
43 | # 推送标签到远程
44 | git push origin v1.0.0
45 |
46 | # 查看所有标签
47 | git tag -l
48 | ```
49 |
50 |
51 | ### 🔹 Makefile、CMake 构建工具
52 |
53 | #### 1. **Makefile 基础**
54 | - **简单示例**:
55 | ```makefile
56 | CC = arm-none-eabi-gcc
57 | CFLAGS = -Wall -O2 -mcpu=cortex-m4 -mthumb
58 | LDFLAGS = -Tstm32f4.ld
59 |
60 | SRCS = $(wildcard *.c)
61 | OBJS = $(SRCS:.c=.o)
62 | TARGET = firmware.elf
63 |
64 | all: $(TARGET)
65 |
66 | $(TARGET): $(OBJS)
67 | $(CC) $(LDFLAGS) $(OBJS) -o $@
68 |
69 | %.o: %.c
70 | $(CC) $(CFLAGS) -c $< -o $@
71 |
72 | clean:
73 | rm -f $(OBJS) $(TARGET)
74 | ```
75 |
76 | #### 2. **CMake 高级应用**
77 | - **跨平台配置**:
78 | ```cmake
79 | cmake_minimum_required(VERSION 3.10)
80 | project(EmbeddedProject C)
81 |
82 | # 设置交叉编译工具链
83 | set(CMAKE_SYSTEM_NAME Generic)
84 | set(CMAKE_C_COMPILER arm-none-eabi-gcc)
85 | set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
86 | set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
87 | set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
88 |
89 | # 添加编译选项
90 | add_compile_options(
91 | -mcpu=cortex-m4
92 | -mthumb
93 | -mfloat-abi=hard
94 | -mfpu=fpv4-sp-d16
95 | -Wall
96 | -Wextra
97 | -Os
98 | )
99 |
100 | # 添加链接选项
101 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -T${CMAKE_SOURCE_DIR}/STM32F407VGTx_FLASH.ld")
102 |
103 | # 添加源文件
104 | file(GLOB_RECURSE SOURCES "src/*.c" "drivers/*.c")
105 |
106 | # 添加可执行文件
107 | add_executable(${PROJECT_NAME}.elf ${SOURCES})
108 |
109 | # 添加目标文件
110 | add_custom_target(${PROJECT_NAME}.bin
111 | COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
112 | DEPENDS ${PROJECT_NAME}.elf
113 | )
114 | ```
115 |
116 |
117 | ### 🔹 Jenkins/GitHub Actions CI 流水线
118 |
119 | #### 1. **GitHub Actions 配置**
120 | - **编译与测试工作流**:
121 | ```yaml
122 | name: Build and Test
123 |
124 | on:
125 | push:
126 | branches: [ main, develop ]
127 | pull_request:
128 | branches: [ main, develop ]
129 |
130 | jobs:
131 | build:
132 | runs-on: ubuntu-latest
133 |
134 | steps:
135 | - name: Checkout code
136 | uses: actions/checkout@v3
137 |
138 | - name: Set up Python
139 | uses: actions/setup-python@v4
140 | with:
141 | python-version: 3.9
142 |
143 | - name: Install dependencies
144 | run: |
145 | sudo apt-get update
146 | sudo apt-get install -y gcc-arm-none-eabi cmake ninja-build
147 |
148 | - name: Configure CMake
149 | run: cmake -B build -G Ninja
150 |
151 | - name: Build
152 | run: cmake --build build
153 |
154 | - name: Run tests
155 | run: |
156 | cd build
157 | ctest --output-on-failure
158 | ```
159 |
160 | #### 2. **Jenkins 集成**
161 | - **构建脚本示例**:
162 | ```groovy
163 | pipeline {
164 | agent any
165 |
166 | stages {
167 | stage('Checkout') {
168 | steps {
169 | checkout scm
170 | }
171 | }
172 |
173 | stage('Build') {
174 | steps {
175 | sh 'make clean all'
176 | }
177 | }
178 |
179 | stage('Test') {
180 | steps {
181 | sh 'make test'
182 | }
183 | }
184 |
185 | stage('Code Coverage') {
186 | steps {
187 | sh 'make coverage'
188 | }
189 | post {
190 | always {
191 | junit 'build/test-results/*.xml'
192 | publishCoverage adapters: [coberturaAdapter('build/coverage/coverage.xml')]
193 | }
194 | }
195 | }
196 |
197 | stage('Deploy') {
198 | when {
199 | branch 'main'
200 | }
201 | steps {
202 | sh 'make deploy'
203 | }
204 | }
205 | }
206 | }
207 | ```
208 |
209 |
210 | ## ✅ 项目实践
211 |
212 | ### 🔹 嵌入式应用框架设计
213 |
214 | #### 1. **分层架构**
215 | ```
216 | +----------------------+
217 | | 应用层 |
218 | | (业务逻辑、算法) |
219 | +----------------------+
220 | | 服务层 |
221 | | (任务管理、事件) |
222 | +----------------------+
223 | | 驱动层 |
224 | | (硬件抽象、BSP) |
225 | +----------------------+
226 | | 硬件层 |
227 | | (MCU、外设) |
228 | +----------------------+
229 | ```
230 |
231 | #### 2. **组件化设计**
232 | - **核心组件**:
233 | - 任务管理器:负责任务创建、调度和通信。
234 | - 事件系统:处理异步事件和回调。
235 | - 配置管理:加载和保存系统配置。
236 | - 日志系统:分级日志记录和输出。
237 |
238 | #### 3. **代码结构示例**
239 | ```
240 | project/
241 | ├── app/ # 应用层
242 | │ ├── main.c # 主程序入口
243 | │ ├── modules/ # 功能模块
244 | │ │ ├── sensor/ # 传感器处理
245 | │ │ ├── comm/ # 通信处理
246 | │ │ └── control/ # 控制逻辑
247 | │ └── config/ # 配置文件
248 | ├── services/ # 服务层
249 | │ ├── task_mgr/ # 任务管理器
250 | │ ├── event/ # 事件系统
251 | │ └── utils/ # 工具函数
252 | ├── drivers/ # 驱动层
253 | │ ├── bsp/ # 板级支持包
254 | │ ├── hal/ # 硬件抽象层
255 | │ └── periph/ # 外设驱动
256 | └── build/ # 构建系统
257 | ├── cmake/ # CMake配置
258 | └── Makefile # Makefile
259 | ```
260 |
261 |
262 | ### 🔹 通用 BSP 构建
263 |
264 | #### 1. **设计原则**
265 | - **硬件无关性**:上层代码不直接访问硬件寄存器。
266 | - **可移植性**:相同功能代码可在不同硬件平台复用。
267 | - **配置化**:通过配置文件而非修改代码适配不同硬件。
268 |
269 | #### 2. **BSP 实现示例**
270 | ```c
271 | // bsp_led.h
272 | #ifndef BSP_LED_H
273 | #define BSP_LED_H
274 |
275 | #include
276 |
277 | typedef enum {
278 | LED_RED,
279 | LED_GREEN,
280 | LED_BLUE
281 | } led_t;
282 |
283 | typedef enum {
284 | LED_OFF,
285 | LED_ON,
286 | LED_TOGGLE
287 | } led_state_t;
288 |
289 | // 初始化LED
290 | void bsp_led_init(void);
291 |
292 | // 设置LED状态
293 | void bsp_led_set(led_t led, led_state_t state);
294 |
295 | #endif
296 |
297 | // bsp_led.c (STM32实现)
298 | #include "bsp_led.h"
299 | #include "stm32f4xx_hal.h"
300 |
301 | // LED GPIO定义
302 | #define LED_RED_PIN GPIO_PIN_14
303 | #define LED_RED_PORT GPIOG
304 | #define LED_GREEN_PIN GPIO_PIN_13
305 | #define LED_GREEN_PORT GPIOG
306 | #define LED_BLUE_PIN GPIO_PIN_15
307 | #define LED_BLUE_PORT GPIOG
308 |
309 | void bsp_led_init(void) {
310 | GPIO_InitTypeDef GPIO_InitStruct = {0};
311 |
312 | // 使能GPIO时钟
313 | __HAL_RCC_GPIOG_CLK_ENABLE();
314 |
315 | // 配置GPIO引脚
316 | GPIO_InitStruct.Pin = LED_RED_PIN | LED_GREEN_PIN | LED_BLUE_PIN;
317 | GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
318 | GPIO_InitStruct.Pull = GPIO_NOPULL;
319 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
320 | HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
321 |
322 | // 默认关闭所有LED
323 | HAL_GPIO_WritePin(LED_RED_PORT, LED_RED_PIN, GPIO_PIN_RESET);
324 | HAL_GPIO_WritePin(LED_GREEN_PORT, LED_GREEN_PIN, GPIO_PIN_RESET);
325 | HAL_GPIO_WritePin(LED_BLUE_PORT, LED_BLUE_PIN, GPIO_PIN_RESET);
326 | }
327 |
328 | void bsp_led_set(led_t led, led_state_t state) {
329 | GPIO_TypeDef *port;
330 | uint16_t pin;
331 |
332 | // 根据LED类型选择GPIO
333 | switch (led) {
334 | case LED_RED:
335 | port = LED_RED_PORT;
336 | pin = LED_RED_PIN;
337 | break;
338 | case LED_GREEN:
339 | port = LED_GREEN_PORT;
340 | pin = LED_GREEN_PIN;
341 | break;
342 | case LED_BLUE:
343 | port = LED_BLUE_PORT;
344 | pin = LED_BLUE_PIN;
345 | break;
346 | default:
347 | return;
348 | }
349 |
350 | // 设置LED状态
351 | switch (state) {
352 | case LED_OFF:
353 | HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET);
354 | break;
355 | case LED_ON:
356 | HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET);
357 | break;
358 | case LED_TOGGLE:
359 | HAL_GPIO_TogglePin(port, pin);
360 | break;
361 | }
362 | }
363 | ```
364 |
365 |
366 | ### 🔹 模块化驱动结构
367 |
368 | #### 1. **驱动分层**
369 | - **硬件层**:直接操作寄存器的低级驱动。
370 | - **抽象层**:提供统一接口的高级驱动。
371 | - **适配层**:连接抽象层和硬件层的中间层。
372 |
373 | #### 2. **SPI驱动示例**
374 | ```c
375 | // spi_interface.h (抽象接口)
376 | #ifndef SPI_INTERFACE_H
377 | #define SPI_INTERFACE_H
378 |
379 | #include
380 |
381 | typedef struct {
382 | // 初始化SPI
383 | void (*init)(uint32_t baudrate);
384 |
385 | // 发送数据
386 | void (*send)(const uint8_t *data, uint32_t length);
387 |
388 | // 接收数据
389 | void (*receive)(uint8_t *data, uint32_t length);
390 |
391 | // 发送并接收数据
392 | void (*transfer)(const uint8_t *tx_data, uint8_t *rx_data, uint32_t length);
393 | } spi_interface_t;
394 |
395 | // 获取SPI接口实例
396 | const spi_interface_t* spi_get_interface(void);
397 |
398 | #endif
399 |
400 | // spi_stm32.c (STM32实现)
401 | #include "spi_interface.h"
402 | #include "stm32f4xx_hal.h"
403 |
404 | static SPI_HandleTypeDef hspi1;
405 |
406 | static void spi_init(uint32_t baudrate) {
407 | // 配置SPI参数
408 | hspi1.Instance = SPI1;
409 | hspi1.Init.Mode = SPI_MODE_MASTER;
410 | hspi1.Init.Direction = SPI_DIRECTION_2LINES;
411 | hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
412 | hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
413 | hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
414 | hspi1.Init.NSS = SPI_NSS_SOFT;
415 |
416 | // 根据波特率计算分频系数
417 | uint32_t prescaler = SPI_BAUDRATEPRESCALER_2;
418 | if (baudrate < 1000000) prescaler = SPI_BAUDRATEPRESCALER_128;
419 | else if (baudrate < 2000000) prescaler = SPI_BAUDRATEPRESCALER_64;
420 | else if (baudrate < 4000000) prescaler = SPI_BAUDRATEPRESCALER_32;
421 | else if (baudrate < 8000000) prescaler = SPI_BAUDRATEPRESCALER_16;
422 | else if (baudrate < 16000000) prescaler = SPI_BAUDRATEPRESCALER_8;
423 | else if (baudrate < 32000000) prescaler = SPI_BAUDRATEPRESCALER_4;
424 |
425 | hspi1.Init.BaudRatePrescaler = prescaler;
426 | hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
427 | hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
428 | hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
429 | hspi1.Init.CRCPolynomial = 10;
430 |
431 | // 初始化SPI
432 | HAL_SPI_Init(&hspi1);
433 | }
434 |
435 | static void spi_send(const uint8_t *data, uint32_t length) {
436 | HAL_SPI_Transmit(&hspi1, (uint8_t*)data, length, 1000);
437 | }
438 |
439 | static void spi_receive(uint8_t *data, uint32_t length) {
440 | HAL_SPI_Receive(&hspi1, data, length, 1000);
441 | }
442 |
443 | static void spi_transfer(const uint8_t *tx_data, uint8_t *rx_data, uint32_t length) {
444 | HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)tx_data, rx_data, length, 1000);
445 | }
446 |
447 | // SPI接口实现
448 | static const spi_interface_t spi_impl = {
449 | .init = spi_init,
450 | .send = spi_send,
451 | .receive = spi_receive,
452 | .transfer = spi_transfer
453 | };
454 |
455 | // 获取SPI接口实例
456 | const spi_interface_t* spi_get_interface(void) {
457 | return &spi_impl;
458 | }
459 | ```
460 |
461 |
462 | ### 🔹 OTA 升级方案设计
463 |
464 | #### 1. **双分区架构**
465 | ```
466 | Flash布局:
467 | +-------------------+ 0x08000000
468 | | Bootloader | (80KB)
469 | +-------------------+ 0x08014000
470 | | Application A | (448KB)
471 | +-------------------+ 0x08084000
472 | | Application B | (448KB)
473 | +-------------------+ 0x08104000
474 | | Configuration | (16KB)
475 | +-------------------+
476 | ```
477 |
478 | #### 2. **OTA状态机**
479 | ```c
480 | typedef enum {
481 | OTA_IDLE, // 空闲状态
482 | OTA_CHECKING, // 检查更新
483 | OTA_DOWNLOADING, // 下载中
484 | OTA_DOWNLOAD_PAUSED, // 下载暂停
485 | OTA_VERIFYING, // 校验中
486 | OTA_READY, // 准备重启
487 | OTA_UPGRADING, // 升级中
488 | OTA_FAILED // 升级失败
489 | } ota_state_t;
490 |
491 | typedef struct {
492 | ota_state_t state;
493 | uint32_t total_size;
494 | uint32_t downloaded_size;
495 | uint8_t progress;
496 | char error_msg[64];
497 | uint8_t firmware_hash[32];
498 | } ota_context_t;
499 | ```
500 |
501 | #### 3. **OTA流程**
502 | 1. **检查更新**:
503 | ```c
504 | bool ota_check_update(void) {
505 | // 从服务器获取版本信息
506 | http_response_t response = http_get(UPDATE_SERVER_URL "/version");
507 | if (response.status != 200) {
508 | return false;
509 | }
510 |
511 | // 解析服务器版本
512 | uint32_t server_version = parse_version(response.body);
513 | uint32_t current_version = get_current_version();
514 |
515 | // 比较版本
516 | return (server_version > current_version);
517 | }
518 | ```
519 |
520 | 2. **下载固件**:
521 | ```c
522 | void ota_download_firmware(void) {
523 | // 打开固件下载URL
524 | http_client_t client = http_open(UPDATE_SERVER_URL "/firmware.bin");
525 | if (!client) {
526 | ota_set_state(OTA_FAILED, "Failed to open URL");
527 | return;
528 | }
529 |
530 | // 获取文件大小
531 | uint32_t file_size = http_get_content_length(client);
532 | ota_set_total_size(file_size);
533 |
534 | // 开始下载
535 | uint8_t buffer[512];
536 | uint32_t bytes_received = 0;
537 | uint32_t bytes_written = 0;
538 |
539 | while ((bytes_received = http_read(client, buffer, 512)) > 0) {
540 | // 写入到备份区
541 | if (!flash_write(APPLICATION_B_ADDRESS + bytes_written, buffer, bytes_received)) {
542 | ota_set_state(OTA_FAILED, "Flash write failed");
543 | http_close(client);
544 | return;
545 | }
546 |
547 | bytes_written += bytes_received;
548 | ota_update_progress(bytes_written * 100 / file_size);
549 |
550 | // 检查是否需要暂停
551 | if (ota_should_pause()) {
552 | http_close(client);
553 | ota_set_state(OTA_DOWNLOAD_PAUSED, "Download paused");
554 | return;
555 | }
556 | }
557 |
558 | http_close(client);
559 | ota_set_state(OTA_VERIFYING, "Verifying firmware");
560 | }
561 | ```
562 |
563 | 3. **验证与应用**:
564 | ```c
565 | bool ota_verify_firmware(void) {
566 | // 计算下载固件的哈希值
567 | uint8_t calculated_hash[32];
568 | calculate_firmware_hash(APPLICATION_B_ADDRESS, APPLICATION_SIZE, calculated_hash);
569 |
570 | // 与服务器提供的哈希值比较
571 | if (memcmp(calculated_hash, ota_get_expected_hash(), 32) != 0) {
572 | return false;
573 | }
574 |
575 | // 验证向量表
576 | uint32_t *vector_table = (uint32_t*)APPLICATION_B_ADDRESS;
577 | if (vector_table[0] == 0 || vector_table[1] == 0) {
578 | return false;
579 | }
580 |
581 | return true;
582 | }
583 |
584 | void ota_apply_update(void) {
585 | // 设置升级标志
586 | set_update_flag(1);
587 |
588 | // 保存新固件版本
589 | save_new_version(get_server_version());
590 |
591 | // 重启系统
592 | NVIC_SystemReset();
593 | }
594 | ```
595 |
596 | # 开发工具链安装指南
597 | ## 1. **IDE推荐**
598 |
599 | ### VS Code + PlatformIO
600 |
601 | **官网链接**:
602 | - [VS Code](https://code.visualstudio.com/)
603 | - [PlatformIO](https://platformio.org/)
604 |
605 | **安装步骤**:
606 | 1. 下载并安装 [VS Code](https://code.visualstudio.com/Download)
607 | 2. 打开VS Code,点击左侧扩展图标(或按 `Ctrl+Shift+X`)
608 | 3. 搜索并安装 **PlatformIO IDE** 扩展
609 | 4. 安装完成后,重启VS Code
610 | 5. PlatformIO会自动安装所需的工具链和依赖
611 |
612 | **验证安装**:
613 | 打开VS Code,点击左下角的 **PlatformIO Home** 图标,若能正常打开则安装成功。
614 |
615 |
616 | ### STM32CubeIDE
617 |
618 | **官网链接**:
619 | - [STM32CubeIDE](https://www.st.com/en/development-tools/stm32cubeide.html)
620 |
621 | **安装步骤**:
622 | 1. 访问官网,点击 **Get Software** 下载对应操作系统的安装包
623 | 2. 运行安装程序,按照向导完成安装
624 | 3. 安装过程中会自动下载并配置STM32CubeMX
625 |
626 | **验证安装**:
627 | 启动STM32CubeIDE,创建一个新的STM32项目,若能正常编译则安装成功。
628 |
629 |
630 | ### CLion
631 |
632 | **官网链接**:
633 | - [CLion](https://www.jetbrains.com/clion/)
634 |
635 | **安装步骤**:
636 | 1. 下载并安装 [CLion](https://www.jetbrains.com/clion/download/)
637 | 2. 安装CMake和MinGW(Windows用户需要):
638 | - CMake:从 [官网](https://cmake.org/download/) 下载并安装
639 | - MinGW:推荐使用 [MSYS2](https://www.msys2.org/) 安装
640 |
641 | **验证安装**:
642 | 启动CLion,创建一个新的C/C++项目,选择CMake工具链,若能正常编译则安装成功。
643 |
644 |
645 | ## 2. **调试工具**
646 |
647 | ### OpenOCD
648 |
649 | **官网链接**:
650 | - [OpenOCD](http://openocd.org/)
651 |
652 | **安装步骤**:
653 | - **Windows**:
654 | 1. 从 [GNU MCU Eclipse](https://github.com/gnu-mcu-eclipse/openocd/releases) 下载预编译二进制包
655 | 2. 解压到指定目录(如 `C:\openocd`)
656 | 3. 将 `bin` 目录添加到系统环境变量
657 |
658 | - **Linux**:
659 | ```bash
660 | sudo apt-get install openocd # Ubuntu/Debian
661 | sudo yum install openocd # CentOS/RHEL
662 | ```
663 |
664 | - **macOS**:
665 | ```bash
666 | brew install open-ocd
667 | ```
668 |
669 | **验证安装**:
670 | 在终端中运行 `openocd --version`,若显示版本信息则安装成功。
671 |
672 |
673 | ### GDB
674 |
675 | **官网链接**:
676 | - [GDB](https://www.gnu.org/software/gdb/)
677 | - [ARM GCC Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm)
678 |
679 | **安装步骤**:
680 | 1. 下载并安装 [ARM GCC Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads)
681 | 2. 将 `bin` 目录添加到系统环境变量
682 |
683 | **验证安装**:
684 | 在终端中运行 `arm-none-eabi-gdb --version`,若显示版本信息则安装成功。
685 |
686 |
687 | ### ST-Link/V2
688 |
689 | **官网链接**:
690 | - [ST-Link](https://www.st.com/en/development-tools/st-link-v2.html)
691 |
692 | **安装步骤**:
693 | - **Windows**:
694 | 1. 从 [ST官网](https://www.st.com/en/development-tools/stsw-link004.html) 下载并安装ST-Link驱动
695 | 2. 安装完成后,将ST-Link/V2调试器连接到电脑
696 |
697 | - **Linux**:
698 | ```bash
699 | sudo apt-get install stlink-tools # Ubuntu/Debian
700 | ```
701 |
702 | **验证安装**:
703 | 在终端中运行 `st-info --version`,若显示版本信息则安装成功。
704 |
705 |
706 | ## 3. **静态代码分析**
707 |
708 | ### CppCheck
709 |
710 | **官网链接**:
711 | - [CppCheck](https://cppcheck.sourceforge.io/)
712 |
713 | **安装步骤**:
714 | - **Windows**:
715 | 1. 从 [官网](https://cppcheck.sourceforge.io/) 下载安装包
716 | 2. 运行安装程序,按照向导完成安装
717 |
718 | - **Linux**:
719 | ```bash
720 | sudo apt-get install cppcheck # Ubuntu/Debian
721 | sudo yum install cppcheck # CentOS/RHEL
722 | ```
723 |
724 | - **macOS**:
725 | ```bash
726 | brew install cppcheck
727 | ```
728 |
729 | **验证安装**:
730 | 在终端中运行 `cppcheck --version`,若显示版本信息则安装成功。
731 |
732 |
733 | ### Clang-Tidy
734 |
735 | **官网链接**:
736 | - [Clang-Tidy](https://clang.llvm.org/extra/clang-tidy/)
737 |
738 | **安装步骤**:
739 | - **Windows**:
740 | 1. 安装 [LLVM](https://releases.llvm.org/download.html)
741 | 2. Clang-Tidy会随LLVM一起安装
742 |
743 | - **Linux**:
744 | ```bash
745 | sudo apt-get install clang-tidy # Ubuntu/Debian
746 | ```
747 |
748 | - **macOS**:
749 | ```bash
750 | brew install llvm
751 | ```
752 |
753 | **验证安装**:
754 | 在终端中运行 `clang-tidy --version`,若显示版本信息则安装成功。
755 |
756 |
757 | ### SonarQube
758 |
759 | **官网链接**:
760 | - [SonarQube](https://www.sonarqube.org/)
761 |
762 | **安装步骤**:
763 | 1. 下载并安装 [Docker](https://www.docker.com/get-started)
764 | 2. 运行SonarQube容器:
765 | ```bash
766 | docker run -d --name sonarqube -p 9000:9000 sonarqube
767 | ```
768 | 3. 访问 [http://localhost:9000](http://localhost:9000),使用默认账号(admin/admin)登录
769 |
770 | **验证安装**:
771 | 在浏览器中打开 [http://localhost:9000](http://localhost:9000),若能看到SonarQube界面则安装成功。
772 |
773 |
774 | ## 4. **单元测试**
775 |
776 | ### Unity
777 |
778 | **官网链接**:
779 | - [Unity](https://github.com/ThrowTheSwitch/Unity)
780 |
781 | **安装步骤**:
782 | 1. 从GitHub下载Unity源码:
783 | ```bash
784 | git clone https://github.com/ThrowTheSwitch/Unity.git
785 | ```
786 | 2. 将 `src` 目录添加到项目的头文件搜索路径
787 |
788 | **验证安装**:
789 | 创建一个简单的测试文件,包含Unity头文件,若能正常编译则安装成功。
790 |
791 |
792 | ### CMock
793 |
794 | **官网链接**:
795 | - [CMock](https://github.com/ThrowTheSwitch/CMock)
796 |
797 | **安装步骤**:
798 | 1. 从GitHub下载CMock源码:
799 | ```bash
800 | git clone https://github.com/ThrowTheSwitch/CMock.git
801 | ```
802 | 2. 将 `src` 目录添加到项目的头文件搜索路径
803 |
804 | **验证安装**:
805 | 创建一个简单的测试文件,包含CMock头文件,若能正常编译则安装成功。
806 |
807 |
808 | ### Google Test
809 |
810 | **官网链接**:
811 | - [Google Test](https://github.com/google/googletest)
812 |
813 | **安装步骤**:
814 | 1. 从GitHub下载Google Test源码:
815 | ```bash
816 | git clone https://github.com/google/googletest.git
817 | ```
818 | 2. 使用CMake构建并安装:
819 | ```bash
820 | cd googletest
821 | mkdir build
822 | cd build
823 | cmake ..
824 | make
825 | sudo make install
826 | ```
827 |
828 | **验证安装**:
829 | 创建一个简单的测试文件,包含Google Test头文件,若能正常编译则安装成功。
830 |
831 |
832 | ## 📚 资源汇总
833 |
834 | | **工具** | **官网链接** | **安装指南** |
835 | |------------------|---------------------------------------------|-------------------------------------------|
836 | | VS Code | https://code.visualstudio.com/ | 直接下载安装包 |
837 | | PlatformIO | https://platformio.org/ | VS Code扩展市场安装 |
838 | | STM32CubeIDE | https://www.st.com/en/development-tools/stm32cubeide.html | 官网下载安装包 |
839 | | CLion | https://www.jetbrains.com/clion/ | 官网下载安装包 |
840 | | OpenOCD | http://openocd.org/ | 包管理器或预编译二进制包 |
841 | | GDB | https://www.gnu.org/software/gdb/ | 随ARM GCC Toolchain安装 |
842 | | ST-Link/V2 | https://www.st.com/en/development-tools/st-link-v2.html | 官网下载驱动 |
843 | | CppCheck | https://cppcheck.sourceforge.io/ | 包管理器或安装包 |
844 | | Clang-Tidy | https://clang.llvm.org/extra/clang-tidy/ | 随LLVM安装 |
845 | | SonarQube | https://www.sonarqube.org/ | Docker容器或独立安装 |
846 | | Unity | https://github.com/ThrowTheSwitch/Unity | 从GitHub下载源码 |
847 | | CMock | https://github.com/ThrowTheSwitch/CMock | 从GitHub下载源码 |
848 | | Google Test | https://github.com/google/googletest | CMake构建并安装 |
849 |
--------------------------------------------------------------------------------
/面试题与面经/Linux面试题1.md:
--------------------------------------------------------------------------------
1 | # 1、什么是Linux?
2 |
3 | Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和Unix的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的Unix工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。
4 |
5 | # 2、Unix和Linux有什么区别?
6 |
7 | Linux和Unix都是功能强大的操作系统,都是应用广泛的服务器操作系统,有很多相似之处,甚至有一部分人错误地认为Unix和Linux操作系统是一样的,然而,事实并非如此,以下是两者的区别。
8 |
9 | 1、开源性
10 | Linux是一款开源操作系统,不需要付费,即可使用;Unix是一款对源码实行知识产权保护的传统商业软件,使用需要付费授权使用。
11 |
12 | 2、跨平台性
13 | Linux操作系统具有良好的跨平台性能,可运行在多种硬件平台上;Unix操作系统跨平台性能较弱,大多需与硬件配套使用。
14 |
15 | 3、可视化界面
16 | Linux除了进行命令行操作,还有窗体管理系统;Unix只是命令行下的系统。
17 |
18 | 4、硬件环境
19 | Linux操作系统对硬件的要求较低,安装方法更易掌握;Unix对硬件要求比较苛刻,安装难度较大。
20 |
21 | 用户群体
22 | Linux的用户群体很广泛,个人和企业均可使用;Unix的用户群体比较窄,多是安全性要求高的大型企业使用,如银行、电信部门等,或者Unix硬件厂商使用,如Sun等。
23 | 相比于Unix操作系统,Linux操作系统更受广大计算机爱好者的喜爱,主要原因是Linux操作系统具有Unix操作系统的全部功能,并且能够在普通PC计算机上实现全部的Unix特性,开源免费的特性,更容易普及使用!
24 |
25 | # 3、什么是 Linux 内核?
26 |
27 | Linux 系统的核心是内核。内核控制着计算机系统上的所有硬件和软件,在必要时分配硬件,并根据需要执行软件。
28 |
29 | 1、系统内存管理
30 | 2、应用程序管理
31 | 3、硬件设备管理
32 | 4、文件系统管理
33 |
34 | # 4、Linux的基本组件是什么?
35 |
36 | 就像任何其他典型的操作系统一样,Linux拥有所有这些组件:内核,shell和GUI,系统实用程序和应用程序。Linux比其他操作系统更具优势的是每个方面都附带其他功能,所有代码都可以免费下载。
37 |
38 | # 5、Linux 的体系结构?
39 |
40 | 从大的方面讲,Linux 体系结构可以分为两块:
41 |
42 | 
43 |
44 | * 用户空间(User Space) :用户空间又包括用户的应用程序(User Applications)、C 库(C Library) 。
45 | * 内核空间(Kernel Space) :内核空间又包括系统调用接口(System Call Interface)、内核(Kernel)、平台架构相关的代码(Architecture-Dependent Kernel Code)
46 |
47 | # 6、BASH和DOS之间的基本区别是什么?
48 |
49 | BASH和DOS控制台之间的主要区别在于3个方面:
50 |
51 | * BASH命令区分大小写,而DOS命令则不区分;
52 | * 在BASH下,/ character是目录分隔符,\作为转义字符。在DOS下,/用作命令参数分隔符,\是目录分隔符
53 | * DOS遵循命名文件中的约定,即8个字符的文件名后跟一个点,扩展名为3个字符。BASH没有遵循这样的惯例。
54 |
55 | # 7、Linux 开机启动过程?
56 |
57 | >了解即可。
58 |
59 | 1、主机加电自检,加载 BIOS 硬件信息。
60 |
61 | 2、读取 MBR 的引导文件(GRUB、LILO)。
62 |
63 | 3、引导 Linux 内核。
64 |
65 | 4、运行第一个进程 init (进程号永远为 1 )。
66 |
67 | 5、进入相应的运行级别。
68 |
69 | 6、运行终端,输入用户名和密码。
70 |
71 | # 8、Linux系统缺省的运行级别?
72 |
73 | * 关机。
74 | * 单机用户模式。
75 | * 字符界面的多用户模式(不支持网络)。
76 | * 字符界面的多用户模式。
77 | * 未分配使用。
78 | * 图形界面的多用户模式。
79 | * 重启。
80 |
81 | # 9、Linux 使用的进程间通信方式?
82 |
83 | * 1、管道(pipe)、流管道(s_pipe)、有名管道(FIFO)。
84 | * 2、信号(signal) 。
85 | * 3、消息队列。
86 | * 4、共享内存。
87 | * 5、信号量。
88 | * 6、套接字(socket) 。
89 |
90 | # 10、Linux 有哪些系统日志文件?
91 |
92 | 比较重要的是` /var/log/messages` 日志文件。
93 |
94 | > 该日志文件是许多进程日志文件的汇总,从该文件可以看出任何入侵企图或成功的入侵。
95 | 另外,如果胖友的系统里有 ELK 日志集中收集,它也会被收集进去。
96 |
97 | # 11、Linux系统安装多个桌面环境有帮助吗?
98 |
99 | 通常,一个桌面环境,如KDE或Gnome,足以在没有问题的情况下运行。尽管系统允许从一个环境切换到另一个环境,但这对用户来说都是优先考虑的问题。有些程序在一个环境中工作而在另一个环境中无法工作,因此它也可以被视为选择使用哪个环境的一个因素。
100 |
101 | # 12、什么是交换空间?
102 |
103 | 交换空间是Linux使用的一定空间,用于临时保存一些并发运行的程序。当RAM没有足够的内存来容纳正在执行的所有程序时,就会发生这种情况。
104 |
105 | # 13、什么是root帐户?
106 |
107 | root帐户就像一个系统管理员帐户,允许你完全控制系统。你可以在此处创建和维护用户帐户,为每个帐户分配不同的权限。每次安装Linux时都是默认帐户。
108 |
109 | # 14、什么是LILO?
110 |
111 | LILO是Linux的引导加载程序。它主要用于将Linux操作系统加载到主内存中,以便它可以开始运行。
112 |
113 | # 15、什么是BASH?
114 |
115 | BASH是Bourne Again SHell的缩写。它由Steve Bourne编写,作为原始Bourne Shell(由/ bin / sh表示)的替代品。它结合了原始版本的Bourne Shell的所有功能,以及其他功能,使其更容易使用。从那以后,它已被改编为运行Linux的大多数系统的默认shell。
116 |
117 | # 16、什么是CLI?
118 |
119 | 命令行界面(英语**:command-line interface**,缩写]:CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。也有人称之为字符用户界面(CUI)。
120 |
121 | 通常认为,命令行界面(CLI)没有图形用户界面(GUI)那么方便用户操作。因为,命令行界面的软件通常需要用户记忆操作的命令,但是,由于其本身的特点,命令行界面要较图形用户界面节约计算机系统的资源。在熟记命令的前提下,使用命令行界面往往要较使用图形用户界面的操作速度要快。所以,图形用户界面的操作系统中,都保留着可选的命令行界面。
122 |
123 | # 17、什么是GUI?
124 |
125 | 图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面。
126 |
127 | 图形用户界面是一种人与计算机通信的界面显示格式,允许用户使用鼠标等输入设备操纵屏幕上的图标或菜单选项,以选择命令、调用文件、启动程序或执行其它一些日常任务。与通过键盘输入文本或字符命令来完成例行任务的字符界面相比,图形用户界面有许多优点。
128 |
129 | # 18、开源的优势是什么?
130 |
131 | 开源允许你将软件(包括源代码)免费分发给任何感兴趣的人。然后,人们可以添加功能,甚至可以调试和更正源代码中的错误。它们甚至可以让它运行得更好,然后再次自由地重新分配这些增强的源代码。这最终使社区中的每个人受益。
132 |
133 | # 19、简单 Linux 文件系统?
134 |
135 | 在 Linux 操作系统中,所有被操作系统管理的资源,例如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文件或是目录都被看作是一个文件。
136 |
137 | 也就是说在 Linux 系统中有一个重要的概念:一切都是文件。其实这是 Unix 哲学的一个体现,而 Linux 是重写 Unix 而来,所以这个概念也就传承了下来。在 Unix 系统中,把一切资源都看作是文件,包括硬件设备。UNIX系统把每个硬件都看成是一个文件,通常称为设备文件,这样用户就可以用读写文件的方式实现对硬件的访问。
138 |
139 | Linux 支持 5 种文件类型,如下图所示:
140 |
141 | 
142 |
143 |
144 | # 20、Linux 的目录结构是怎样的?
145 |
146 | > 这个问题,一般不会问。更多是实际使用时,需要知道。
147 |
148 | Linux 文件系统的结构层次鲜明,就像一棵倒立的树,最顶层是其根目录:
149 |
150 | 
151 |
152 | 常见目录说明:
153 |
154 | * /bin: 存放二进制可执行文件(ls,cat,mkdir等),常用命令一般都在这里;
155 | * /etc: 存放系统管理和配置文件;
156 | * /home: 存放所有用户文件的根目录,是用户主目录的基点,比如用户user的主目录就是/home/user,可以用~user表示;
157 | * /usr: 用于存放系统应用程序;
158 | * /opt: 额外安装的可选应用程序包所放置的位置。一般情况下,我们可以把tomcat等都安装到这里;
159 | * /proc: 虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息;
160 | * /root: 超级用户(系统管理员)的主目录(特权阶级o);
161 | * /sbin: 存放二进制可执行文件,只有root才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程序。如ifconfig等;
162 | * /dev: 用于存放设备文件;
163 | * /mnt: 系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统;
164 | * /boot: 存放用于系统引导时使用的各种文件;
165 | * /lib: 存放着和系统运行相关的库文件 ;
166 | * /tmp: 用于存放各种临时文件,是公用的临时文件存储点;
167 | * /var: 用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启动日志等。)等;
168 | * /lost+found: 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows下叫什么.chk)就在这里。
169 |
170 | # 21、什么是 inode ?
171 | > 一般来说,面试不会问 inode 。但是 inode 是一个重要概念,是理解 Unix/Linux 文件系统和硬盘储存的基础。
172 |
173 | 理解inode,要从文件储存说起。
174 |
175 | 文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。
176 |
177 | 操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。"块"的大小,最常见的是4KB,即连续八个 sector组成一个 block。
178 |
179 | 文件数据都储存在"块"中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为"索引节点"。
180 |
181 | 每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。
182 |
183 | ### 简述 Linux 文件系统通过 i 节点把文件的逻辑结构和物理结构转换的工作过程?
184 |
185 | > 如果看的一脸懵逼,也没关系。一般来说,面试官不太会问这个题目。
186 |
187 | Linux 通过 inode 节点表将文件的逻辑结构和物理结构进行转换。
188 |
189 | * inode 节点是一个 64 字节长的表,表中包含了文件的相关信息,其中有文件的大小、文件所有者、文件的存取许可方式以及文件的类型等重要信息。在 inode 节点表中最重要的内容是磁盘地址表。在磁盘地址表中有 13 个块号,文件将以块号在磁盘地址表中出现的顺序依次读取相应的块。
190 | * Linux 文件系统通过把 inode 节点和文件名进行连接,当需要读取该文件时,文件系统在当前目录表中查找该文件名对应的项,由此得到该文件相对应的 inode 节点号,通过该 inode 节点的磁盘地址表把分散存放的文件物理块连接成文件的逻辑结构。
191 |
192 | # 22、什么是硬链接和软链接?
193 |
194 | 1)硬链接
195 |
196 | 由于 Linux 下的文件是通过索引节点(inode)来识别文件,硬链接可以认为是一个指针,指向文件索引节点的指针,系统并不为它重新分配 inode 。每添加一个一个硬链接,文件的链接数就加 1 。
197 |
198 | * 不足:1)不可以在不同文件系统的文件间建立链接;2)只有超级用户才可以为目录创建硬链接。
199 |
200 | 2)软链接
201 |
202 | 软链接克服了硬链接的不足,没有任何文件系统的限制,任何用户可以创建指向目录的符号链接。因而现在更为广泛使用,它具有更大的灵活性,甚至可以跨越不同机器、不同网络对文件进行链接。
203 |
204 | * 不足:因为链接文件包含有原文件的路径信息,所以当原文件从一个目录下移到其他目录中,再访问链接文件,系统就找不到了,而硬链接就没有这个缺陷,你想怎么移就怎么移;还有它要系统分配额外的空间用于建立新的索引节点和保存原文件的路径。
205 |
206 | 实际场景下,基本是使用软链接。总结区别如下:
207 |
208 | * 硬链接不可以跨分区,软件链可以跨分区。
209 | * 硬链接指向一个 inode 节点,而软链接则是创建一个新的 inode 节点。
210 | * 删除硬链接文件,不会删除原文件,删除软链接文件,会把原文件删除。
211 |
212 |
213 | # 23、RAID 是什么?
214 |
215 | RAID 全称为独立磁盘冗余阵列(Redundant Array of Independent Disks),基本思想就是把多个相对便宜的硬盘组合起来,成为一个硬盘阵列组,使性能达到甚至超过一个价格昂贵、 容量巨大的硬盘。RAID 通常被用在服务器电脑上,使用完全相同的硬盘组成一个逻辑扇区,因此操作系统只会把它当做一个硬盘。
216 |
217 | RAID 分为不同的等级,各个不同的等级均在数据可靠性及读写性能上做了不同的权衡。在实际应用中,可以依据自己的实际需求选择不同的 RAID 方案。
218 |
219 | # 24、一台 Linux 系统初始化环境后需要做一些什么安全工作?
220 |
221 | * 1、添加普通用户登陆,禁止 root 用户登陆,更改 SSH 端口号。
222 |
223 | > 修改 SSH 端口不一定绝对哈。当然,如果要暴露在外网,建议改下。
224 |
225 | * 2、服务器使用密钥登陆,禁止密码登陆。
226 |
227 | * 3、开启防火墙,关闭 SElinux ,根据业务需求设置相应的防火墙规则。
228 |
229 | * 4、装 fail2ban 这种防止 SSH 暴力破击的软件。
230 |
231 | * 5、设置只允许公司办公网出口 IP 能登陆服务器(看公司实际需要)
232 |
233 | > 也可以安装 VPN 等软件,只允许连接 VPN 到服务器上。
234 |
235 | * 6、修改历史命令记录的条数为 10 条。
236 |
237 | * 7、只允许有需要的服务器可以访问外网,其它全部禁止。
238 |
239 | * 8、做好软件层面的防护。
240 | * 8.1 设置 nginx_waf 模块防止 SQL 注入。
241 | * 8.2 把 Web 服务使用 www 用户启动,更改网站目录的所有者和所属组为 www 。
242 |
243 |
244 | # 25、什么叫 CC 攻击?什么叫 DDOS 攻击?
245 |
246 | CC 攻击,主要是用来攻击页面的,模拟多个用户不停的对你的页面进行访问,从而使你的系统资源消耗殆尽。
247 |
248 | DDOS 攻击,中文名叫分布式拒绝服务攻击,指借助服务器技术将多个计算机联合起来作为攻击平台,来对一个或多个目标发动 DDOS 攻击。
249 |
250 | > 攻击,即是通过大量合法的请求占用大量网络资源,以达到瘫痪网络的目的。
251 |
252 | ### 怎么预防 CC 攻击和 DDOS 攻击?
253 |
254 | 防 CC、DDOS 攻击,这些只能是用硬件防火墙做流量清洗,将攻击流量引入黑洞。
255 |
256 | > 流量清洗这一块,主要是买 ISP 服务商的防攻击的服务就可以,机房一般有空余流量,我们一般是买服务,毕竟攻击不会是持续长时间。
257 |
258 | # 26、什么是网站数据库注入?
259 |
260 | 由于程序员的水平及经验参差不齐,大部分程序员在编写代码的时候,没有对用户输入数据的合法性进行判断。
261 |
262 | 应用程序存在安全隐患。用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据,这就是所谓的 SQL 注入。
263 |
264 | SQL注入,是从正常的 WWW 端口访问,而且表面看起来跟一般的 Web 页面访问没什么区别,如果管理员没查看日志的习惯,可能被入侵很长时间都不会发觉。
265 |
266 | ### 如何过滤与预防?
267 |
268 | 数据库网页端注入这种,可以考虑使用 nginx_waf 做过滤与预防。
269 |
270 | # 27、Shell 脚本是什么?
271 |
272 | 一个 Shell 脚本是一个文本文件,包含一个或多个命令。作为系统管理员,我们经常需要使用多个命令来完成一项任务,我们可以添加这些所有命令在一个文本文件(Shell 脚本)来完成这些日常工作任务。
273 |
274 | # 28、可以在 Shell 脚本中使用哪些类型的变量?
275 |
276 | 在 Shell 脚本,我们可以使用两种类型的变量:
277 |
278 | 系统定义变量
279 |
280 | > 系统变量是由系统系统自己创建的。这些变量通常由大写字母组成,可以通过 set 命令查看。
281 |
282 | 用户定义变量
283 |
284 | > 用户变量由系统用户来生成和定义,变量的值可以通过命令 "echo $<变量名>" 查看。
285 |
286 |
287 | # 29、Shell 脚本中 if 语法如何嵌套?
288 |
289 | ```c
290 | if [ 条件 ]
291 | then
292 | 命令1
293 | 命令2
294 | …..
295 | else
296 | if [ 条件 ]
297 | then
298 | 命令1
299 | 命令2
300 | ….
301 | else
302 | 命令1
303 | 命令2
304 | …..
305 | fi
306 | fi
307 | ```
308 |
309 | # 30、Shell 脚本中 case 语句的语法?
310 |
311 | ```c
312 | case 变量 in
313 | 值1)
314 | 命令1
315 | 命令2
316 | …..
317 | 最后命令
318 | !!
319 | 值2)
320 | 命令1
321 | 命令2
322 | ……
323 | 最后命令
324 | ;;
325 | esac
326 | ```
327 |
328 | # 31、Shell 脚本中 for 循环语法?
329 |
330 | ```c
331 | for 变量 in 循环列表
332 | do
333 | 命令1
334 | 命令2
335 | ….
336 | 最后命令
337 | done
338 | ```
339 |
340 | # 32、Shell 脚本中 while 循环语法?
341 |
342 | 如同 for 循环,while 循环只要条件成立就重复它的命令块。
343 | 不同于 for循环,while 循环会不断迭代,直到它的条件不为真。
344 |
345 | 基础语法:
346 | ```c
347 | while [ 条件 ]
348 | do
349 | 命令…
350 | done
351 | ```
352 |
353 | # 33、如何使脚本可执行?
354 |
355 | 使用 chmod 命令来使脚本可执行。例子如下:`chmod a+x myscript.sh`
356 |
357 | # 34、在 Shell 脚本如何定义函数呢?
358 |
359 | 函数是拥有名字的代码块。当我们定义代码块,我们就可以在我们的脚本调用函数名字,该块就会被执行。示例如下所示:
360 |
361 | ```c
362 | $ diskusage () { df -h ; }
363 | 译注:下面是我给的shell函数语法,原文没有
364 | [ function ] 函数名 [()]
365 | {
366 | 命令;
367 | [return int;]
368 | }
369 | ```
370 |
371 | # 35、判断一文件是不是字符设备文件,如果是将其拷贝到 /dev 目录下?
372 |
373 | ```c
374 | #!/bin/bash
375 | read -p "Input file name: " FILENAME
376 | if [ -c "$FILENAME" ];then
377 | cp $FILENAME /dev
378 | fi
379 | ```
380 |
381 | # 36、添加一个新组为 class1 ,然后添加属于这个组的 30 个用户,用户名的形式为 stdxx ,其中 xx 从 01 到 30 ?
382 |
383 | ```c
384 | #!/bin/bash
385 | groupadd class1
386 | for((i=1;i<31;i++))
387 | do
388 | if [ $i -le 10 ];then
389 | useradd -g class1 std0$i
390 | else
391 | useradd -g class1 std$i
392 | fi
393 | done
394 |
395 | ```
396 |
397 | # 37、写一个 sed 命令,修改 /tmp/input.txt 文件的内容?
398 |
399 | 要求:
400 |
401 | * 删除所有空行。
402 | * 一行中,如果包含 “11111”,则在 “11111” 前面插入 “AAA”,在 “11111” 后面插入 “BBB” 。比如:将内容为 0000111112222 的一行改为 0000AAA11111BBB2222 。
403 |
404 | ```
405 | [root@~]## cat -n /tmp/input.txt
406 | 1 000011111222
407 | 2
408 | 3 000011111222222
409 | 4 11111000000222
410 | 5
411 | 6
412 | 7 111111111111122222222222
413 | 8 2211111111
414 | 9 112222222
415 | 10 1122
416 | 11
417 |
418 | ## 删除所有空行命令
419 | [root@~]## sed '/^$/d' /tmp/input.txt
420 | 000011111222
421 | 000011111222222
422 | 11111000000222
423 | 111111111111122222222222
424 | 2211111111
425 | 112222222
426 | 1122
427 |
428 | ## 插入指定的字符
429 | [root@~]## sed 's#\(11111\)#AAA\1BBB#g' /tmp/input.txt
430 | 0000AAA11111BBB222
431 | 0000AAA11111BBB222222
432 | AAA11111BBB000000222
433 | AAA11111BBBAAA11111BBB11122222222222
434 | 22AAA11111BBB111
435 | 112222222
436 | 1122
437 | ```
438 |
439 | # 38、用户进程间通信主要哪几种方式?
440 |
441 | (1)管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖 先的进程之间进行通信。
442 |
443 | (2)命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的 功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名 管道通过命令 mkfifo 或系统调用 mkfifo 来创建。
444 |
445 | (3)信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进 程间通信外,进程还可以发送信号给进程本身;linux 除了支持 Unix 早期信号语义函数 sigal 外,还支持语义符合 Posix.1 标准的信号函数 sigaction(实际上,该函数是基于 BSD 的,BSD 为了实现可靠信号机制,又能够统一对外接口,用 sigaction 函数重新实现了 signal 函数)。
446 |
447 | (4)消息(Message)队列:消息队列是消息的链接表,包括 Posix 消息队列 system V 消息队 列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消 息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等 缺
448 |
449 | (5)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用 IPC 形式。是针对其他 通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间 的同步及互斥。
450 |
451 | (6)信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
452 |
453 | (7)套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是 由 Unix 系统的 BSD 分支开发出来的,但现在一般可以移植到其它类 Unix 系统上:Linux 和 System V 的变种都支持套接字。
454 |
455 | # 39、通过伙伴系统申请内核内存的函数有哪些?
456 |
457 | 在物理页面管理上实现了基于区的伙伴系统(zone based buddy system)。对不同区的内存 使用单独的伙伴系统(buddy system)管理,而且独立地监控空闲页。
458 | 相应接口`alloc_pages(gfp_mask, order)`,`_ _get_free_pages(gfp_mask, order)`等。
459 |
460 |
461 | # 40、Linux 虚拟文件系统的关键数据结构有哪些?(至少写出四个)
462 |
463 | * struct super_block
464 | * struct inode
465 | * struct fil
466 | * struct dentry
467 |
468 |
469 | # 41、对文件或设备的操作函数保存在那个数据结构中?
470 |
471 | struct file_operations
472 |
473 | # 42、Linux 中的文件包括哪些?
474 |
475 | * 执行文件
476 | * 普通文件
477 | * 目录文件
478 | * 链接文件和设备文件
479 | * 管道文件
480 |
481 | # 43、创建进程的系统调用有那些?
482 |
483 | * clone()
484 | * fork()
485 | * fork()
486 |
487 | 系统调用服务例程:
488 |
489 | * sys_clone
490 | * sys_fork
491 | * sys_vfork
492 |
493 | # 44、调用 schedule()进行进程切换的方式有几种?
494 |
495 | 1.系统调用 do_fork();
496 |
497 | 2.定时中断 do_timer();
498 |
499 | 3.唤醒进程 wake_up_process
500 |
501 | 4.改变进程的调度策略 setscheduler();
502 |
503 | 5.系统调用礼让 sys_sched_yield();
504 |
505 | # 45、Linux 调度程序是根据进程的动态优先级还是静态优先级来调度进程的?
506 |
507 | Liunx 调度程序是根据根据进程的动态优先级来调度进程的,但是动态优先级又是根据静态 优先级根据算法计算出来的,两者是两个相关联的值。因为高优先级的进程总是比低优先级
508 |
509 | 的进程先被调度,为防止多个高优先级的进程占用 CPU 资源,导致其他进程不能占有 CPU, 所以引用动态优先级概念
510 |
511 | # 46、进程调度的核心数据结构是哪个?
512 |
513 | struct runqueue
514 |
515 | # 47、如何加载、卸载一个模块?
516 |
517 | * insmod 加载
518 | * rmmod 卸载
519 |
520 | # 48、模块和应用程序分别运行在什么空间?
521 |
522 | 模块运行在内核空间,应用程序运行在用户空间
523 |
524 | # 49、Linux 中的浮点运算由应用程序实现还是内核实现?
525 |
526 | 应用程序实现,Linux 中的浮点运算是利用数学库函数实现的,库函数能够被应用程序链接后 调用,不能被内核链接调用。这些运算是在应用程序中运行的,然后再把结果反馈给系统。 Linux 内核如果一定要进行浮点运算,需要在建立内核时选上 math-emu,使用软件模拟计算 浮点运算,据说这样做的代价有两个:用户在安装驱动时需要重建内核,可能会影响到其他的 应用程序,使得这些应用程序在做浮点运算的时候也使用 math-emu,大大的降低了效率。
527 |
528 | # 50、模块程序能否使用可链接的库函数?
529 |
530 | 模块程序运行在内核空间,不能链接库函数。
531 |
532 | # 51、TLB 中缓存的是什么内容?
533 |
534 | TLB,页表缓存,当线性地址被第一次转换成物理地址的时候,将线性地址和物理地址的对应 放到 TLB 中,用于下次访问这个线性地址时,加快转换速度。
535 |
536 | # 52、Linux 中有哪几种设备?
537 |
538 | 字符设备和块设备。网卡是例外,他不直接与设备文件对应,mknod 系统调用用来创建设备 文件。
539 |
540 | # 53、字符设备驱动程序的关键数据结构是哪个?
541 |
542 | 字符设备描述符 struct cdev,cdev_alloc()用于动态的分配 cdev 描述符,cdev_add()用于注 册一个 cdev 描述符,cdev 包含一个 struct kobject 类型的数据结构它是核心的数据结构
543 |
544 | # 54、设备驱动程序包括哪些功能函数?
545 |
546 | * open()
547 | * read()
548 | * write()
549 | * llseek()
550 | * realse()
551 |
552 | # 55、如何唯一标识一个设备?
553 |
554 | Linux 使用一个设备编号来唯一的标示一个设备,设备编号分为:主设备号和次设备号,一般主 设备号标示设备对应的驱动程序,次设备号对应设备文件指向的设备,在内核中使用 dev_t 来 表示设备编号,一般它是 32 位长度,其中 12 位用于表示主设备号,20 位用于表示次设备号, 利用 MKDEV(int major,int minor);用于生成一个 dev_t 类型的对象。
555 |
556 | # 56、Linux 通过什么方式实现系统调用?
557 |
558 | 靠软件中断实现的,首先,用户程序为系统调用设置参数,其中一个编号是系统调用编号,参数 设置完成后,程序执行系统调用指令,x86 上的软中断是有 int 产生的,这个指令会导致一个异 常,产生一个事件,这个事件会导致处理器跳转到内核态并跳转到一个新的地址。并开始处 理那里的异常处理程序,此时的异常处理就是系统调用程序。
559 |
560 | # 57、Linux 软中断和工作队列的作用是什么?
561 |
562 | Linux 中的软中断和工作队列是中断处理。
563 |
564 | 1.软中断一般是“可延迟函数”的总称,它不能睡眠,不能阻塞,它处于中断上下文,不能进城切 换,软中断不能被自己打断,只能被硬件中断打断(上半部),可以并发的运行在多个 CPU 上。 所以软中断必须设计成可重入的函数,因此也需要自旋锁来保护其数据结构。
565 |
566 | 2.工作队列中的函数处在进程上下文中,它可以睡眠,也能被阻塞,能够在不同的进程间切 换。已完成不同的工作。 可延迟函数和工作队列都不能访问用户的进程空间,可延时函数在执行时不可能有任何正在 运行的进程,工作队列的函数有内核进程执行,他不能访问用户空间地址
567 |
568 | # 58、Linux开机启动过程?
569 |
570 | 1)主机加电自检,加载BOLS硬件信息
571 |
572 | 2)读取MBR的引导文件(grub,lilo)
573 |
574 | 3)引导linux内核
575 |
576 | 4)运行第一个进程init(进程号永远为1)
577 |
578 | 5)进入相应的运行级别
579 |
580 | 6)运行终端,输入用户名和密码
581 |
582 | # 59、Linux系统缺省的运行级别
583 |
584 | 0.关机
585 |
586 | 1.单机用户模式
587 |
588 | 2.字符界面的多用户模式(不支持网络)
589 |
590 | 3.字符界面的多用户模式
591 |
592 | 4.未分配使用
593 |
594 | 5.图形界面的多用户模式
595 |
596 | 6.重启
597 |
598 | # 60、Linux系统是由那些部分组成?
599 |
600 | Linux系统内核,shell,文件系统和应用程序四部分组成
601 |
602 | # 61、硬链接和软链接有什么区别?
603 |
604 | 1)硬链接不可以跨分区,软件链可以跨分区
605 | 2)硬链接指向一个i节点,而软链接则是创建一个新的i节点
606 | 3)删除硬链接文件,不会删除原文件,删除软链接文件,会把原文件删除
607 |
608 |
609 | # 62、如何规划一台Linux主机,步骤是怎样?
610 |
611 | 1、确定机器是做什么用的,比如是做 WEB 、DB、还是游戏服务器。
612 |
613 | > 不同的用途,机器的配置会有所不同。
614 |
615 | 2、确定好之后,就要定系统需要怎么安装,默认安装哪些系统、分区怎么做。
616 |
617 | 3、需要优化系统的哪些参数,需要创建哪些用户等等的。
618 |
619 | # 63、查看系统当前进程连接数?
620 |
621 | ```
622 | netstat -an | grep ESTABLISHED | wc -l
623 | ```
624 |
625 | # 64、如何在/usr目录下找出大小超过10MB的文件?
626 |
627 | ```
628 | # find /usr -size +10M
629 | ```
630 |
631 | # 65、添加一条到192.168.3.0/24的路由,网关为192.168.1.254?
632 |
633 | ```
634 | route add -net 192.168.3.0/24 netmask 255.255.255.0 gw 192.168.1.254
635 | ```
636 |
637 | # 66、如何在/var目录下找出90天之内未被访问过的文件?
638 |
639 | ```
640 | find /var \! -atime -90
641 | ```
642 |
643 | # 67、如何在/home目录下找出120天之前被修改过的文件?
644 |
645 | ```
646 | find /home -mtime +120
647 | ```
648 |
649 | # 68、在整个目录树下查找文件“core”,如发现则无需提示直接删除它们。
650 |
651 | ```
652 | find / -name core -exec rm {} \;
653 | ```
654 |
655 | # 69、有一普通用户想在每周日凌晨零点零分定期备份/user/backup到/tmp目录下,该用户应如何做?
656 |
657 | ```
658 | crontab -e
659 | 0 0 * * 7 /bin/cp /user/backup /tmp
660 | ```
661 |
662 | # 70、每周一下午三点将/tmp/logs目录下面的后缀为*.log的所有文件rsync同步到备份服务器192.168.1.100中同样的目录下面,crontab配置项该如何写?
663 |
664 | ```
665 | 00 15 * * 1 rsync -avzP /tmp/logs/*.log root@192.168.1.100:/tmp/logs
666 | ```
667 |
668 | # 71、找到/tmp/目录下面的所有名称以"_s1.jpg"结尾的普通文件,如果其修改日期在一天内,则将其打包到/tmp/back.tar.gz文件中
669 |
670 | ```
671 | find /tmp -type f -name ".*_sj.jpg" -mtime 1|xarges tar zxf /tmp/back.tar.gz
672 | ```
673 |
674 | # 72、配置mysql服务器的时候,配置了auto_increment_increment=3,请问这里的3意味着什么?
675 |
676 | ```
677 | auto_increment是用于主键自动增长的,从3开始增长,3表示自增的起始值
678 | ```
679 |
680 | # 73、详细说明keepalived的故障切换工作原理
681 |
682 | 这种故障切换是通过VRRP协议来实现的,主节点会按一定的时间间隔发送心跳信息的广播包,告诉备节点自己的存活状态信息,当主节点发生故障时,备节点在一段时间内就收到广播包,从而判断主节点出现故障,因此会调用自身的接管程序来接管主节点的IP资源及服务,当主节点恢复时,备节点会主动释放资源,恢复到接管前的状态,从而来实现主备故障切换
683 |
684 | # 74、什么是系统调用?
685 | 根据进程访问资源的特点,可以把进程在系统上的运行分为两个级别:
686 | 用户态(user mode) : 用户态运行的进程或可以直接读取用户程序的数据。
687 | 系统态(kernel mode):可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。
688 |
689 | 说了用户态和系统态之后,那么什么是系统调用呢?
690 | 运行的应用程序基本都是运行在用户态,如果调用操作系统提供的系统态级别的子功能咋办呢?那就需要系统调用了!
691 | 也就是说在运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
692 | 这些系统调用按功能大致可分为如下几类:
693 | * 设备管理。完成设备的请求或释放,以及设备启动等功能。
694 | * 文件管理。完成文件的读、写、创建及删除等功能。
695 | * 进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。
696 | * 进程通信。完成进程之间的消息传递或信号传递等功能。
697 | * 内存管理。完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
698 |
699 | # 75、进程和线程的区别?
700 | 线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
701 |
702 | # 76、进程有哪几种状态?
703 | 创建状态(new) :进程正在被创建,尚未到就绪状态。
704 | 就绪状态(ready) :进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。
705 | 运行状态(running) :进程正在处理器上上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。
706 | 阻塞状态(waiting) :又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
707 | 结束状态(terminated) :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。
708 |
709 | 
710 |
711 | # 77、进程间的通信方式
712 | 管道/匿名管道(Pipes) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
713 | 有名管道(Names Pipes) : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in first out)。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
714 | 信号(Signal) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
715 | 消息队列(Message Queuing) :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。
716 | 信号量(Semaphores) :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
717 | 共享内存(Shared memory) :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
718 | 套接字(Sockets) : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
719 |
720 | # 78、线程间的同步的方式
721 | 线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。操作系统一般有下面三种线程同步的方式:
722 | 互斥量(Mutex):采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
723 | 信号量(Semphares) :它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量
724 | 事件(Event) :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作
725 |
726 | # 79、进程的调度算法
727 | 先到先服务(FCFS)调度算法 : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
728 | 短作业优先(SJF)的调度算法 : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
729 | 时间片轮转调度算法 : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
730 | 多级反馈队列调度算法 :前面介绍的几种进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。
731 | 优先级调度 : 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
732 |
733 | # 80、操作系统的内存管理主要是做什么?
734 | 操作系统的内存管理主要负责内存的分配与回收(malloc 函数:申请内存,free 函数:释放内存),另外地址转换也就是将逻辑地址转换成相应的物理地址等功能也是操作系统内存管理做的事情。
735 |
736 | # 81、常见的几种内存管理机制
737 | 简单分为连续分配管理方式和非连续分配管理方式这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间,常见的如 块式管理 。同样地,非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,常见的如页式管理 和 段式管理。
738 | 块式管理 : 远古时代的计算机操系统的内存管理方式。将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,称之为碎片。
739 | 页式管理 :把主存分为大小相等且固定的一页一页的形式,页较小,相对相比于块式管理的划分力度更大,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址。
740 | 段式管理 : 页式管理虽然提高了内存利用率,但是页式管理其中的页实际并无任何实际意义。 段式管理把主存分为一段段的,每一段的空间又要比一页的空间小很多 。但是,最重要的是段是有实际意义的,每个段定义了一组逻辑信息,例如,有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。 段式管理通过段表对应逻辑地址和物理地址。
741 |
742 | # 82、快表和多级页表
743 | 在分页内存管理中,很重要的两点是:
744 | 虚拟地址到物理地址的转换要快。
745 | 解决虚拟地址空间大,页表也会很大的问题。
746 | ### 快表
747 | 为了解决虚拟地址到物理地址的转换速度,操作系统在 页表方案 基础之上引入了 快表 来加速虚拟地址到物理地址的转换。可以把块表理解为一种特殊的高速缓冲存储器(Cache),其中的内容是页表的一部分或者全部内容。作为页表的 Cache,它的作用与页表相似,但是提高了访问速率。由于采用页表做地址转换,读写内存数据时 CPU 要访问两次主存。有了快表,有时只要访问一次高速缓冲存储器,一次主存,这样可加速查找并提高指令执行速度。
748 | 使用快表之后的地址转换流程是这样的:
749 | 根据虚拟地址中的页号查快表;
750 | 如果该页在快表中,直接从快表中读取相应的物理地址;
751 | 如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中;
752 | 当快表填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。
753 | 看完了之后会发现快表和平时经常在开发的系统使用的缓存(比如 Redis)很像,的确是这样的,操作系统中的很多思想、很多经典的算法,都可以在日常开发使用的各种工具或者框架中找到它们的影子。
754 | ### 多级页表
755 | 引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间,特别是那些根本就不需要的页表就不需要保留在内存中。多级页表属于时间换空间的典型场景。
756 |
757 | # 83、分页机制和分段机制的共同点和区别
758 | ### 共同点:
759 | 分页机制和分段机制都是为了提高内存利用率,较少内存碎片。
760 | 页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的。
761 | ### 区别:
762 | 页的大小是固定的,由操作系统决定;而段的大小不固定,取决于当前运行的程序。
763 | 分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要。
764 |
765 | # 84、逻辑(虚拟)地址和物理地址
766 | 逻辑地址由操作系统决定。物理地址指的是真实物理内存中地址,更具体一点来说就是内存地址寄存器中的地址。物理地址是内存单元真正的地址。
767 |
768 | # 85、CPU 寻址了解吗?为什么需要虚拟地址空间?
769 | 现代处理器使用的是一种称为 虚拟寻址(Virtual Addressing) 的寻址方式。使用虚拟寻址,CPU 需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。 实际上完成虚拟地址转换为物理地址转换的硬件是 CPU 中含有一个被称为 内存管理单元(Memory Management Unit, MMU) 的硬件。
770 |
771 | 
772 |
773 | 为什么要有虚拟地址空间呢?
774 | 没有虚拟地址空间的时候,程序都是直接访问和操作的都是物理内存 。但是这样有什么问题呢?
775 | 用户程序可以访问任意内存,寻址内存的每个字节,这样就很容易(有意或者无意)破坏操作系统,造成操作系统崩溃。
776 | 想要同时运行多个程序特别困难,比如想同时运行一个微信和一个 QQ 音乐都不行。为什么呢?举个简单的例子:微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就造成了微信这个程序就会崩溃。
777 | 总结来说:如果直接把物理地址暴露出来的话会带来严重问题,比如可能对操作系统造成伤害以及给同时运行多个程序造成困难。
778 | 通过虚拟地址访问内存有以下优势:
779 | 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
780 | 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为 4 KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
781 | 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。
782 |
783 | # 86、什么是虚拟内存(Virtual Memory)?
784 | 虚拟内存是计算机系统内存管理的一种技术,可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是“使用硬盘空间来扩展内存“的技术。虚拟内存的重要意义是它定义了一个连续的虚拟地址空间,并且 把内存扩展到硬盘空间。
785 |
786 | # 87、局部性原理
787 | 局部性原理表现在以下两个方面:
788 | 时间局部性 :如果程序中的某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。产生时间局部性的典型原因,是由于在程序中存在着大量的循环操作。
789 | 空间局部性 :一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内,这是因为指令通常是顺序存放、顺序执行的,数据也一般是以向量、数组、表等形式簇聚存储的。
790 | 时间局部性是通过将近来使用的指令和数据保存到高速缓存存储器中,并使用高速缓存的层次结构实现。空间局部性通常是使用较大的高速缓存,并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上就是建立了 “内存一外存”的两级存储器的结构,利用局部性原理实现髙速缓存。
791 |
792 | # 88、虚拟存储器
793 | 基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其他部分留在外存,就可以启动程序执行。由于外存往往比内存大很多,所以运行的软件的内存大小实际上是可以比计算机系统实际的内存大小大的。在程序执行过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换到外存上,从而腾出空间存放将要调入内存的信息。这样,计算机好像为用户提供了一个比实际内存大的多的存储器——虚拟存储器。
794 | 实际上,虚拟内存同样是一种时间换空间的策略,用 CPU 的计算时间,页的调入调出花费的时间,换来了一个虚拟的更大的空间来支持程序的运行。程序世界几乎不是时间换空间就是空间换时间。
795 |
796 | # 89、虚拟内存的技术实现
797 | 虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。 虚拟内存的实现有以下三种方式:
798 | 请求分页存储管理 :建立在分页管理之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。请求分页存储管理系统中,在作业开始运行之前,仅装入当前要执行的部分段即可运行。假如在作业运行的过程中发现要访问的页面不在内存,则由处理器通知操作系统按照对应的页面置换算法将相应的页面调入到主存,同时操作系统也可以将暂时不用的页面置换到外存中。
799 | 请求分段存储管理 :建立在分段存储管理之上,增加了请求调段功能、分段置换功能。请求分段储存管理方式就如同请求分页储存管理方式一样,在作业开始运行之前,仅装入当前要执行的部分段即可运行;在执行过程中,可使用请求调入中断动态装入要访问但又不在内存的程序段;当内存空间已满,而又需要装入新的段时,根据置换功能适当调出某个段,以便腾出空间而装入新的段。
800 | 请求段页式存储管理
801 |
802 | 不管是上面那种实现方式,一般都需要:
803 | 一定容量的内存和外存:在载入程序的时候,只需要将程序的一部分装入内存,而将其他部分留在外存,然后程序就可以执行了;
804 | 缺页中断:如果需执行的指令或访问的数据尚未在内存(称为缺页或缺段),则由处理器通知操作系统将相应的页面或段调入到内存,然后继续执行程序;
805 | 虚拟地址空间 :逻辑地址到物理地址的变换。
806 |
807 | # 90、页面置换算法
808 | 地址映射过程中,若在页面中发现所要访问的页面不在内存中,则发生缺页中断 。
809 | 缺页中断 就是要访问的页不在主存,需要操作系统将其调入主存后再进行访问。 在这个时候,被内存映射的文件实际上成了一个分页交换文件。
810 | 当发生缺页中断时,如果当前内存中并没有空闲的页面,操作系统就必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。用来选择淘汰哪一页的规则叫做页面置换算法,可以把页面置换算法看成是淘汰页面的规则。
811 | OPT 页面置换算法(最佳页面置换算法) :最佳(Optimal, OPT)置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若千页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。一般作为衡量其他置换算法的方法。
812 | FIFO(First In First Out) 页面置换算法(先进先出页面置换算法) : 总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。
813 | LRU (Least Currently Used)页面置换算法(最近最久未使用页面置换算法) :LRU算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。
814 | LFU (Least Frequently Used)页面置换算法(最少使用页面置换算法) : 该置换算法选择在之前时期使用最少的页面作为淘汰页。
815 |
--------------------------------------------------------------------------------
/面试题与面经/计算机网络原理面试题.md:
--------------------------------------------------------------------------------
1 | # 1.如何理解 URI?
2 | URI, 全称为(Uniform Resource Identifier), 也就是统一资源标识符,它的作用很简单,就是区分互联网上不同的资源。但是,它并不是我们常说的网址, 网址指的是URL, 实际上URI包含了URN和URL两个部分,由于 URL 过于普及,就默认将 URI 视为 URL 了。
3 | ### URI 的结构
4 | URI 真正最完整的结构是这样的
5 |
6 | 
7 |
8 | 好像跟平时见到的不太一样!先别急,来一一拆解。scheme 表示协议名,比如http, https, file等等。后面必须和://连在一起。user:passwd@ 表示登录主机时的用户信息,不过很不安全,不推荐使用,也不常用。host:port** 表示主机名和端口。path 表示请求路径,标记资源所在位置。query 表示查询参数,为key=val这种形式,多个键值对之间用&隔开。fragment 表示 URI 所定位的资源内的一个锚点**,浏览器可以根据这个锚点跳转到对应的位置。
9 | ```
10 | https://www.baidu.com/s?tn=54093922_6_hao_pg&ie=utf-8&wd=%E7%99%BE%E5%BA%A6
11 | ```
12 | 这个 URI 中
13 | * https 即scheme部分
14 | * www.baidu.com 为host:port部分(注意,http 和 https 的默认端口分别为80、443)
15 | * kenguba/upkpls/gisxr2 为path部分
16 | * word=1&name=kenguba 表示query部分
17 | * #UrkH4 就是锚点
18 | 
19 | ### URI 编码
20 | URI 只能使用 ASCII , ASCII 之外的字符是不支持显示的,而且还有一部分符号是界定符,如果不加以处理就会导致解析出错。因此,URI 引入了编码机制,将所有非 ASCII 码字符和界定符转为十六进制字节值,然后在前面加个%。
21 | ```
22 | encodeURI("https://www.yuque.com/kenguba/upkpls/gisxr2?name=一缕清风")
23 | "https://www.yuque.com/kenguba/upkpls/gisxr2?name=%E4%B8%80%E7%BC%95%E6%B8%85%E9%A3%8E"
24 | decodeURI("https://www.yuque.com/kenguba/upkpls/gisxr2?name=%E4%B8%80%E7%BC%95%E6%B8%85%E9%A3%8E")
25 |
26 | encodeURIComponent("https://www.yuque.com/kenguba/upkpls/gisxr2?name=一缕清风")
27 | "https://www.yuque.com/kenguba/upkpls/gisxr2?name=%E4%B8%80%E7%BC%95%E6%B8%85%E9%A3%8E"
28 | decodeURIComponent("https://www.yuque.com/kenguba/upkpls/gisxr2?name=%E4%B8%80%E7%BC%95%E6%B8%85%E9%A3%8E")
29 | ```
30 |
31 | # 2.解释一下HTTP的超文本传输协议
32 | HTTP 是超文本传输协议,也就是HyperText Transfer Protocol。它可以拆成三个部分:
33 | * 超文本
34 | * 传输
35 | * 协议
36 |
37 | 
38 |
39 | ### 协议
40 | 
41 |
42 | 生活中的协议,本质上与计算机中的协议是相同的,协议的特点:
43 | * 协 字,代表的意思是必须有两个以上的参与者。例如三方协议里的参与者有三个:你、公司、学校三个;租房协议里的参与者有两个:你和房东。
44 | * 议 字,代表的意思是对参与者的一种行为约定和规范。例如三方协议里规定试用期期限、毁约金等;租房协议里规定租期期限、每月租金金额、违约如何处理等。
45 | 针对 HTTP 协议,我们可以这么理解。HTTP 是一个用在计算机世界里的协议。它使用计算机能够理解的语言确立了一种计算机之间交流通信的规范(两个以上的参与者),以及相关的各种控制和错误处理方式(行为约定和规范)
46 | ### 传输
47 | 所谓的「传输」,很好理解,就是把一堆东西从 A 点搬到 B 点,或者从 B 点 搬到 A 点。别轻视了这个简单的动作,它至少包含两项重要的信息。HTTP 协议是一个双向协议。我们在上网冲浪时,浏览器是请求方 A ,百度网站就是应答方 B。双方约定用 HTTP 协议来通信,于是浏览器把请求数据发送给网站,网站再把一些数据返回给浏览器,最后由浏览器渲染在屏幕,就可以看到图片、视频了。
48 |
49 | 
50 |
51 | 数据虽然是在 A 和 B 之间传输,但允许中间有中转或接力。就好像第一排的同学想穿递纸条给最后一排的同学,那么传递的过程中就需要经过好多个同学(中间人),这样的传输方式就从「A < - > B」,变成了「A <-> N <-> M <-> B」。而在 HTTP 里,需要中间人遵从 HTTP 协议,只要不打扰基本的数据传输,就可以添加任意额外的东西。针对传输,我们可以进一步理解了 HTTP。HTTP 是一个在计算机世界里专门用来在两点之间传输数据的约定和规范。
52 |
53 | ### 超文本
54 | HTTP 传输的内容是「超文本」 我们先来理解「文本」,在互联网早期的时候只是简单的字符文字,但现在「文本」的涵义已经可以扩展为图片、视频、压缩包等,在 HTTP 眼里这些都算做「文本」。再来理解「超文本」,它就是超越了普通文本的文本,它是文字、图片、视频等的混合体最关键有超链接,能从一个超文本跳转到另外一个超文本。HTML 就是最常见的超文本了,它本身只是纯文字文件,但内部用很多标签定义了图片、视频等的链接,在经过浏览器的解释,呈现给我们的就是一个文字、有画面的网页了。OK,经过了对 HTTP 里这三个名词的详细解释,就可以给出比「超文本传输协议」这七个字更准确更有技术含量的答案:HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」
55 |
56 | ⚠️注意: HTTP 不是用于从互联网服务器传输超文本到本地浏览器的协议,也可以是服务器到服务器,所以采用两点之间的描述会更准确
57 |
58 | # 3.HTTP 的特点?HTTP 有哪些缺点?
59 |
60 | ### HTTP 的特点概括
61 | 灵活可扩展 主要体现在两个方面。
62 | 一个是语义上的自由,只规定了基本格式,比如空格分隔单词,换行分隔字段,其他的各个部分都没有严格的语法限制
63 | 另一个是传输形式的多样性,不仅仅可以传输文本,还能传输图片、视频等任意数据,非常方便。
64 | 可靠传输 HTTP 基于 TCP/IP,因此把这一特性继承了下来。这属于 TCP 的特性,不具体介绍了。
65 | 请求-应答 也就是一发一收、有来有回, 当然这个请求方和应答方不单单指客户端和服务器之间,如果某台服务器作为代理来连接后端的服务端,那么这台服务器也会扮演请求方的角色。
66 | 无状态 这里的状态是指通信过程的上下文信息,而每次 http 请求都是独立、无关的,默认不需要保留状态信息
67 | ### HTTP 缺点
68 | 无状态 所谓的优点和缺点还是要分场景来看的,对于 HTTP 而言,最具争议的地方在于它的无状态。
69 | 在需要长连接的场景中,需要保存大量的上下文信息,以免传输大量重复的信息,那么这时候无状态就是 http 的缺点了。
70 | 但与此同时,另外一些应用仅仅只是为了获取一些数据,不需要保存连接上下文信息,无状态反而减少了网络开销,成为了 http 的优点。
71 | 明文传输 即协议里的报文(主要指的是头部)不使用二进制数据,而是文本形式。这当然对于调试提供了便利,但同时也让 HTTP 的报文信息暴露给了外界,给攻击者也提供了便利。WIFI陷阱就是利用 HTTP 明文传输的缺点,诱导你连上热点,然后疯狂抓你所有的流量,从而拿到你的敏感信息。
72 | 队头阻塞问题 当 http 开启长连接时,共用一个 TCP 连接,同一时刻只能处理一个请求,那么当前请求耗时过长的情况下,其它的请求只能处于阻塞状态,也就是著名的队头阻塞问题。接下来会有一小节讨论这个问题。
73 |
74 | # 4.HTTP 报文结构是怎样的?
75 | 对于 TCP 而言,在传输的时候分为两个部分: **TCP头 **和 数据部分。而 HTTP 类似,也是 header + body 的结构,具体而言:
76 | ```
77 | 起始行 + 头部 + 空行 + 实体
78 | ```
79 | 由于 http 请求报文和响应报文是有一定区别,因此分开介绍
80 |
81 | 
82 |
83 | ### 起始行
84 | 对于请求报文来说,起始行类似下面这样:
85 | ```
86 | GET /home HTTP/1.1
87 | ```
88 | 也就是方法 + 路径 + http版本。对于响应报文来说,起始行一般张这个样:
89 | ```
90 | HTTP/1.1 200 OK
91 | ```
92 | 响应报文的起始行也叫做状态行。由 http版本、状态码和原因 三部分组成。⚠️注意:在起始行中,每两个部分之间用空格隔开,最后一个部分后面应该接一个换行,严格遵循 ABNF 语法规范
93 | ### 头部
94 | 展示一下请求头和响应头在报文中的位置:
95 |
96 | 
97 |
98 | 不管是请求头还是响应头,其中的字段是相当多的,而且牵扯到http非常多的特性,这里就不一一列举的,重点看看这些头部字段的格式:
99 | * 字段名不区分大小写
100 | * 字段名不允许出现空格,不可以出现下划线_
101 | * 字段名后面必须**紧接着 **:
102 | ### 空行
103 | 很重要,用来区分开头部和实体。问: 如果说在头部中间故意加一个空行会怎么样?那么空行后的内容全部被视为实体。
104 | ### 实体
105 | 就是具体的数据了,也就是 body 部分。请求报文对应请求体, 响应报文对应响应体。
106 |
107 |
108 | # 5.如何理解 HTTP 的请求方法?
109 |
110 | http/1.1 规定了以下请求方法(注意,都是大写):
111 | * GET 通常用来获取资源
112 | * HEAD 获取资源的元信息
113 | * POST 提交数据,即上传数据
114 | * PUT 修改数据
115 | * DELETE 删除资源(几乎用不到)
116 | * CONNECT 建立连接隧道,用于代理服务器
117 | * OPTIONS 列出可对资源实行的请求方法,预检请求,用来跨域请求
118 | * TRACE 追踪请求-响应的传输路径
119 |
120 | # 6.http 常见字段有哪些?
121 | * Host
122 | * Content-Length
123 | * Connection
124 | * Content-Encoding
125 | * Content-Type
126 |
127 |
128 | # 7.对于定长和不定长的数据,HTTP 是怎么传输的?
129 | ### 定长包体
130 | 对于定长包体而言,发送端在传输的时候一般会带上 Content-Length, 来指明包体的长度。我们用一个nodejs服务器来模拟一下:
131 | ```
132 | const http = require('http');
133 |
134 | const server = http.createServer();
135 |
136 | server.on('request', (req, res) => {
137 | if(req.url === '/') {
138 | res.setHeader('Content-Type', 'text/plain');
139 | res.setHeader('Content-Length', 10);
140 | res.write("helloworld");
141 | }
142 | })
143 |
144 | server.listen(8081, () => {
145 | console.log("成功启动");
146 | })
147 | ```
148 | 启动后访问: localhost:8081。浏览器中显示如下:
149 | ```
150 | helloworld
151 | ```
152 |
153 | 这是长度正确的情况,那不正确的情况是如何处理的呢?我们试着把这个长度设置的小一些:
154 | ```
155 | res.setHeader('Content-Length', 8);
156 | ```
157 |
158 | 重启服务,再次访问,现在浏览器中内容如下:
159 | ```
160 | hellowor
161 | ```
162 |
163 | 那后面的ld哪里去了呢?实际上在 http 的响应体中直接被截去了。然后试着将这个长度设置得大一些:
164 | ```
165 | res.setHeader('Content-Length', 12);
166 | ```
167 | 此时浏览器显示如下:
168 |
169 | 
170 |
171 | 直接无法显示了。可以看到 Content-Length 对于 http 传输过程起到了十分关键的作用,如果设置不当可以直接导致传输失败。
172 | ### 不定长包体
173 | 上述是针对于定长包体,那么对于不定长包体而言是如何传输的呢?这里就必须介绍另外一个 http 头部字段了:
174 | ```
175 | Transfer-Encoding: chunked
176 |
177 | // Transfer-Encoding: chunked
178 | // Transfer-Encoding: compress
179 | // Transfer-Encoding: deflate
180 | // Transfer-Encoding: gzip
181 | // Transfer-Encoding: identity
182 | // Several values can be listed, separated by a comma
183 | // Transfer-Encoding: gzip, chunked
184 | ```
185 |
186 | 表示分块传输数据,设置这个字段后会自动产生两个效果:
187 | * Content-Length 字段会被忽略
188 | * 基于长连接持续推送动态内容
189 | 我们依然以一个实际的例子来模拟分块传输,nodejs 程序如下:
190 | ```
191 | const http = require('http');
192 |
193 | const server = http.createServer();
194 |
195 | server.on('request', (req, res) => {
196 | if(req.url === '/') {
197 | res.setHeader('Content-Type', 'text/html; charset=utf8');
198 | res.setHeader('Content-Length', 10);
199 | res.setHeader('Transfer-Encoding', 'chunked');
200 | res.write("来啦
");
201 | setTimeout(() => {
202 | res.write("第一次传输
");
203 | }, 1000);
204 | setTimeout(() => {
205 | res.write("第二次传输");
206 | res.end()
207 | }, 2000);
208 | }
209 | })
210 |
211 | server.listen(8009, () => {
212 | console.log("成功启动");
213 | })
214 | ```
215 |
216 | 
217 |
218 | 用 telnet 抓到的响应如下:
219 |
220 | 
221 |
222 | 注意,Connection: keep-alive 及之前的为响应行和响应头,后面的内容为响应体,这两部分用换行符隔开。响应体的结构比较有意思,如下所示:
223 |
224 | ```
225 | chunk长度(16进制的数)
226 | 第一个chunk的内容
227 | chunk长度(16进制的数)
228 | 第二个chunk的内容
229 | ......
230 | 0
231 | ```
232 | 最后是留有有一个空行的,这一点请大家注意。以上便是 http 对于定长数据和不定长数据的传输方式。
233 |
234 | # 8.HTTP 如何处理大文件的传输?
235 |
236 | 对于几百 M 甚至上 G 的大文件来说,如果要一口气全部传输过来显然是不现实的,会有大量的等待时间,严重影响用户体验。因此,HTTP 针对这一场景,采取了范围请求的解决方案,允许客户端仅仅请求一个资源的一部分。
237 | ### 如何支持
238 | 当然,前提是服务器要支持范围请求,要支持这个功能,就必须加上这样一个响应头:
239 | ```
240 | $ curl -I https://www.yuque.com/
241 | HTTP/1.1 200 OK
242 | ...
243 | Accept-Ranges: bytes
244 | Content-Length: 146515
245 |
246 |
247 |
248 |
249 |
250 | $ curl -I http://download.dcloud.net.cn/HBuilder.9.0.2.macosx_64.dmg
251 | $ curl -H "Range: bytes=0-10" http://download.dcloud.net.cn/HBuilder.9.0.2.macosx_64.dmg -v
252 |
253 | //省略
254 | HTTP/1.1 200 OK
255 | ...
256 | Accept-Ranges: none
257 |
258 | //详细的
259 | HTTP/1.1 200 OK
260 | Server: Tengine
261 | Content-Type: application/octet-stream
262 | Content-Length: 233295878
263 | Connection: keep-alive
264 | Date: Mon, 26 Apr 2021 13:12:46 GMT
265 | x-oss-request-id: 6086BC4E66D721363972F4A8
266 | x-oss-cdn-auth: success
267 | Accept-Ranges: bytes
268 | ETag: "6D932737FD8C6058D6AE93BCC4C74AA7-45"
269 | Last-Modified: Tue, 06 Mar 2018 13:20:31 GMT
270 | x-oss-object-type: Multipart
271 | x-oss-hash-crc64ecma: 7369427768111114923
272 | x-oss-storage-class: Standard
273 | x-oss-server-time: 156
274 | Ali-Swift-Global-Savetime: 1617704046
275 | Via: cache15.l2cn1809[0,200-0,H], cache2.l2cn1809[1,0], cache7.cn682[39,39,200-0,M], cache2.cn682[44,0]
276 | Age: 778
277 | X-Cache: MISS TCP_MISS dirn:-2:-2
278 | X-Swift-SaveTime: Mon, 26 Apr 2021 13:25:44 GMT
279 | X-Swift-CacheTime: 3600
280 | Timing-Allow-Origin: *
281 | EagleId: af062a4216194435440612604e
282 | ```
283 |
284 | 假如在响应中存在Accept-Ranges首部(并且它的值不为 “none”),那么表示该服务器支持范围请求 在上面的响应中,Accept-Ranges: bytes 表示界定范围的单位是 bytes 。这里 Content-Length也是有效信息,因为它提供了要检索的图片的完整大小
285 |
286 | 如果站点未发送Accept-Ranges首部,那么它们有可能不支持范围请求。一些站点会明确将其值设置为 "none",以此来表明不支持。在这种情况下,某些应用的下载管理器会将暂停按钮禁用。
287 | ```
288 | curl -I https://www.youtube.com/watch?v=EwTZ2xpQwpA
289 |
290 | HTTP/1.1 200 OK
291 | ...
292 | Accept-Ranges: none
293 | ```
294 |
295 | ### Range 字段拆解
296 | 而对于客户端而言,它需要指定请求哪一部分,通过 Range 这个请求头字段确定,格式为bytes=x-y。接下来就来讨论一下这个 Range 的书写格式:
297 |
298 | 0-499 表示从开始到第 499 个字节。
299 |
300 | 500- 表示从第 500 字节到文件终点。
301 |
302 | -100 表示文件的最后100个字节。
303 |
304 | 服务器收到请求之后,首先验证范围是否合法,如果越界了那么返回416错误码,否则读取相应片段,返回206状态码。同时,服务器需要添加 Content-Range 字段,这个字段的格式根据请求头中Range字段的不同而有所差异。具体来说,请求单段数据和请求多段数据,响应头是不一样的。举个例子:
305 | ```
306 | // 单段数据
307 | curl http://i.imgur.com/z4d4kWk.jpg -i -H "Range: bytes=0-1023"
308 | Range: bytes=0-9
309 |
310 | // 多段数据
311 | curl http://www.example.com -i -H "Range: bytes=0-50, 100-150"
312 | Range: bytes=0-9, 30-39
313 | ```
314 | 接下来就分别来讨论着两种情况
315 | ### 单段数据
316 | 对于单段数据的请求,返回的响应如下:
317 | ```
318 | HTTP/1.1 206 Partial Content
319 | Content-Length: 10
320 | Accept-Ranges: bytes
321 | Content-Range: bytes 0-9/100
322 |
323 | i am xxxxx
324 | ```
325 |
326 | 值得注意的是Content-Range字段,0-9表示请求的返回,100表示资源的总大小,很好理解。
327 | ### 多段数据
328 | 接下来看看多段请求的情况。得到的响应会是下面这个形式:
329 | ```
330 | HTTP/1.1 206 Partial Content
331 | Content-Type: multipart/byteranges; boundary=00000010101
332 | Content-Length: 189
333 | Connection: keep-alive
334 | Accept-Ranges: bytes
335 |
336 |
337 | --00000010101
338 | Content-Type: text/plain
339 | Content-Range: bytes 0-9/96
340 |
341 | i am xxxxx
342 | --00000010101
343 | Content-Type: text/plain
344 | Content-Range: bytes 20-29/96
345 |
346 | eex jspy e
347 | --00000010101--
348 | ```
349 |
350 | 这个时候出现了一个非常关键的字段Content-Type: multipart/byteranges;boundary=00000010101,它代表了信息量是这样的:
351 |
352 | 请求一定是多段数据请求
353 |
354 | 响应体中的分隔符是 00000010101
355 |
356 | 因此,在响应体中各段数据之间会由这里指定的分隔符分开,而且在最后的分隔末尾添上--表示结束。以上就是 http 针对大文件传输所采用的手段。
357 |
358 | 与分块传输编码的对比
359 |
360 | Transfer-Encoding 首部允许分块编码,这在数据量很大,并且在请求未能完全处理完成之前无法知晓响应的体积大小的情况下非常有用。服务器会直接把数据发送给客户端而无需进行缓冲或确定响应的精确大小——后者会增加延迟。范围请求与分块传输是兼容的,可以单独或搭配使用
361 |
362 | # 9.HTTP 中如何处理表单数据的提交?
363 |
364 | 在 http 中,有两种主要的表单提交的方式,体现在两种不同的Content-Type取值:
365 | * application/x-www-form-urlencoded
366 | * multipart/form-data
367 | 由于表单提交一般是POST请求,很少考虑GET,因此这里我们将默认提交的数据放在请求体中
368 | ### application/x-www-form-urlencoded
369 | 对于application/x-www-form-urlencoded格式的表单内容,有以下特点:
370 |
371 | * 其中的数据会被编码成以&分隔的键值对
372 | * 字符以URL编码方式编码。如:
373 | ```
374 | // 转换过程: {a: 1, b: 2} -> a=1&b=2 -> 如下(最终形式)
375 | "a%3D1%26b%3D2"
376 | ```
377 |
378 | ### multipart/form-data
379 | 对于multipart/form-data而言:
380 |
381 | 请求头中的Content-Type字段会包含boundary,且boundary的值有浏览器默认指定。例:
382 | ```
383 | Content-Type: multipart/form-data;boundary=----WebkitFormBoundaryRRJKeWfHPGrS4LKe
384 | ```
385 |
386 | 数据会分为多个部分,每两个部分之间通过分隔符来分隔,每部分表述均有 HTTP 头部描述子包体,如Content-Type,在最后的分隔符会加上--表示结束。
387 |
388 | 相应的请求体是下面这样:
389 | ```
390 | Content-Disposition: form-data;name="data1";
391 | Content-Type: text/plain
392 | data1
393 | ----WebkitFormBoundaryRRJKeWfHPGrS4LKe
394 | Content-Disposition: form-data;name="data2";
395 | Content-Type: text/plain
396 | data2
397 | ----WebkitFormBoundaryRRJKeWfHPGrS4LKe--
398 | ```
399 |
400 | 值得一提的是,multipart/form-data 格式最大的特点在于:每一个表单元素都是独立的资源表述。另外,你可能在写业务的过程中,并没有注意到其中还有boundary的存在,如果你打开抓包工具,确实可以看到不同的表单元素被拆分开了,之所以在平时感觉不到,是以为浏览器和 HTTP 给你封装了这一系列操作。而且,在实际的场景中,对于图片等文件的上传,基本采用multipart/form-data而不用application/x-www-form-urlencoded,因为没有必要做 URL 编码,带来巨大耗时的同时也占用了更多的空间。
401 |
402 |
403 | # 10.如何理解 HTTP 代理?
404 |
405 | 我们知道在 HTTP 是基于请求-响应模型的协议,一般由客户端发请求,服务器来进行响应。当然,也有特殊情况,就是代理服务器的情况。引入代理之后,作为代理的服务器相当于一个中间人的角色,对于客户端而言,表现为服务器进行响应;而对于源服务器,表现为客户端发起请求,具有双重身份。那代理服务器到底是用来做什么的呢?
406 | ### 功能
407 | * 负载均衡 客户端的请求只会先到达代理服务器,后面到底有多少源服务器,IP 都是多少,客户端是不知道的。因此,这个代理服务器可以拿到这个请求之后,可以通过特定的算法分发给不同的源服务器,让各台源服务器的负载尽量平均。当然,这样的算法有很多,包括随机算法、轮询、一致性hash、LRU(最近最少使用)等等,不过这些算法并不是本文的重点,大家有兴趣自己可以研究一下。
408 | * 保障安全 利用心跳机制监控后台的服务器,一旦发现故障机就将其踢出集群。并且对于上下行的数据进行过滤,对非法 IP 限流,这些都是代理服务器的工作。
409 | * 缓存代理 将内容缓存到代理服务器,使得客户端可以直接从代理服务器获得而不用到源服务器那里
410 | ### 相关头部字段
411 | #### Via
412 | 代理服务器需要标明自己的身份,在 HTTP 传输中留下自己的痕迹,怎么办呢?通过Via字段来记录。举个例子,现在中间有两台代理服务器,在客户端发送请求后会经历这样一个过程:
413 | ```
414 | 客户端 -> 代理1 -> 代理2 -> 源服务器
415 | ```
416 | 在源服务器收到请求后,会在请求头拿到这个字段:
417 | ```
418 | Via: proxy_server1, proxy_server2
419 | ```
420 |
421 | 而源服务器响应时,最终在客户端会拿到这样的响应头:
422 | ```
423 | Via: proxy_server2, proxy_server1
424 | ```
425 |
426 | 可以看到,Via中代理的顺序即为在 HTTP 传输中报文传达的顺序
427 | ### X-Forwarded-For
428 | 字面意思就是为谁转发, 它记录的是请求方的IP地址(注意,和Via区分开,X-Forwarded-For记录的是请求方这一个IP)。
429 | ### X-Real-IP
430 | 是一种获取用户真实 IP 的字段,不管中间经过多少代理,这个字段始终记录最初的客户端的IP。相应的,还有X-Forwarded-Host和X-Forwarded-Proto,分别记录客户端(注意哦,不包括代理)的域名和协议名。
431 | ### X-Forwarded-For产生的问题
432 | 前面可以看到,X-Forwarded-For这个字段记录的是请求方的 IP,这意味着每经过一个不同的代理,这个字段的名字都要变,从客户端到代理1,这个字段是客户端的 IP,从代理1到代理2,这个字段就变为了代理1的 IP。但是这会产生两个问题:
433 | * 意味着代理必须解析 HTTP 请求头,然后修改,比直接转发数据性能下降。
434 | * 在 HTTPS 通信加密的过程中,原始报文是不允许修改的。
435 | 由此产生了代理协议,一般使用明文版本,只需要在 HTTP 请求行上面加上这样格式的文本即可:
436 | ```
437 | // PROXY + TCP4/TCP6 + 请求方地址 + 接收方地址 + 请求端口 + 接收端口
438 | PROXY TCP4 0.0.0.1 0.0.0.2 1111 2222
439 | GET / HTTP/1.1
440 | ```
441 | 这样就可以解决X-Forwarded-For带来的问题了
442 |
443 |
444 | # 11.说说 HTTP1.1 相比 HTTP1.0 提高了什么性能?
445 | ### HTTP1.1 相比 HTTP1.0 性能上的改进:
446 | * 使用 TCP 长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。
447 | * 支持 管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
448 | ### 但 HTTP1.1 还是有性能瓶颈:
449 | * 请求/响应头部(Header)未经压缩就发送,首部信息越多延迟越大。只能压缩 Body 的部分
450 | * 发送冗长的首部。每次互相发送相同的首部造成的浪费较多
451 | * 服务器是按请求的顺序响应的,如果服务器响应慢,会招致客户端一直请求不到数据,也就是队头阻塞;
452 | * 没有请求优先级控制
453 | * 请求只能从客户端开始,服务器只能被动响应
454 |
455 | # 12.那上面的 HTTP1.1 的性能瓶颈,HTTP2 做了什么优化?
456 |
457 | HTTP/2是Web的未来,demo演示!HTTP2 协议是 基于 HTTPS 的,所以 HTTP2 的安全性也是有保障的。那 HTTP2 相比 HTTP1.1 性能上的改进:
458 | ### 1. 头部压缩
459 | HTTP2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的分。这就是所谓的 HPACK 算法
460 | * 索引表
461 | * 霍夫曼编码
462 | 在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。
463 | ### 2. 二进制格式
464 | HTTP2 不再像 HTTP1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧和数据帧。
465 |
466 | 
467 |
468 | 这样虽然对人不友好,但是对计算机非常友好,因为计算机只懂二进制,那么收到报文后,无需再将明文的报文转成二进制,而是直接解析二进制报文,这增加了数据传输的效率
469 | 3. 数据流
470 | HTTP2 的数据包不是按顺序发送的,同一个连接里面连续的数据包,可能属于不同的回应。因此,必须要对数据包做标记,指出它属于哪个回应。每个请求或回应的所有数据包,称为一个数据流(Stream)。每个数据流都标记着一个独一无二的编号,其中规定客户端发出的数据流编号为奇数, 服务器发出的数据流编号为偶数 客户端还可以指定数据流的优先级。优先级高的请求,服务器就先响应该请求。
471 |
472 | 
473 |
474 | * 同域名下所有通信都在单个连接上完成。
475 | * 单个连接可以承载任意数量的双向数据流。
476 | * 数据流以消息的形式发送,而消息又由一个或多个帧组成,多个帧之间可以乱序发送,因为根据帧首部的流标识可以重新组装。
477 |
478 | 这一特性,使性能有了极大提升:
479 | * 同个域名只需要占用一个 TCP 连接,消除了因多个 TCP 连接而带来的延时和内存消耗。
480 | * 单个连接上可以并行交错的请求和响应,之间互不干扰。
481 | * 在HTTP2中,每个请求都可以带一个 31bit的优先值,0表示最高优先级, 数值越大优先级越低。有了这个优先值,客户端和服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。
482 | 4. 多路复用
483 | HTTP2 是可以在一个连接中并发多个请求或回应,而不用按照顺序一一对应。移除了 HTTP1.1 中的串行请求,不需要排队等待,也就不会再出现「队头阻塞」问题,降低了延迟,大幅度提高了连接的利用率。举例来说,在一个 TCP 连接里,服务器收到了客户端 A 和 B 的两个请求,如果发现 A 处理过程非常耗时,于是就回应 A 请求已经处理好的部分,接着回应 B 请求,完成后,再回应 A 请求剩下的部分。
484 | 
485 | 5. 服务器推送
486 | HTTP2 还在一定程度上改善了传统的「请求 - 应答」工作模式,服务不再是被动地响应,也可以主动向客户端发送消息。举例来说,在浏览器刚请求 HTML 的时候,就提前把可能会用到的 JS、CSS 文件等静态资源主动发给客户端,减少延时的等待,也就是服务器推送(Server Push,也叫 Cache Push)
487 |
488 |
489 | # 13.HTTP2 有哪些缺陷?HTTP3 做了哪些优化?
490 | HTTP2 主要的问题在于,多个 HTTP 请求在复用一个 TCP 连接,下层的 TCP 协议是不知道有多少个 HTTP 请求的。所以一旦发生了丢包现象,就会触发 TCP 的重传机制,这样在一个 TCP 连接中的所有的 HTTP 请求都必须等待这个丢了的包被重传回来。
491 |
492 | HTTP1.1 中的管道(pipeline) 传输中如果有一个请求阻塞了,那么队列后请求也统统被阻塞住了
493 |
494 | HTTP2 多请求复用一个TCP连接,一旦发生丢包,就会阻塞住所有的 HTTP 请求。
495 |
496 | 这都是基于 TCP 传输层的问题,所以 HTTP3 把 HTTP 下层的 TCP 协议改成了 UDP!UDP 发生是不管顺序,也不管丢包的,所以不会出现 HTTP1.1 的队头阻塞 和 HTTP2 的一个丢包全部重传问题
497 |
498 | 
499 |
500 | 大家都知道 UDP 是不可靠传输的,但基于 UDP 的 QUIC 协议 可以实现类似 TCP 的可靠性传输。
501 |
502 | QUIC 有自己的一套机制可以保证传输的可靠性的。当某个流发生丢包时,只会阻塞这个流,其他流不会受到影响。
503 |
504 | TL3 升级成了最新的1.3版本,头部压缩算法也升级成了 QPack
505 |
506 | HTTPS 要建立一个连接,要花费 6 次交互,先是建立三次握手,然后是 TLS/1.3 的三次握手。QUIC 直接把以往的 TCP 和 TLS/1.3 的 6 次交互 合并成了 3 次,减少了交互次数
507 |
508 | 
509 |
510 | 所以, QUIC 是一个在 UDP 之上的伪 TCP + TLS + HTTP2 的多路复用的协议。QUIC 是新协议,对于很多网络设备,根本不知道什么是 QUIC,只会当做 UDP,这样会出现新的问题。所以 HTTP3 现在普及的进度非常的缓慢,不知道未来 UDP 是否能够逆袭 TCP
511 |
512 | # 14.HTTP 与 HTTPS 有哪些区别?
513 | * HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
514 | * HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行SSL/TLS 的握手过程,才可进入加密报文传输。
515 | * HTTP 的端口号是 80,HTTPS 的端口号是 443。
516 | * HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的
517 |
518 |
519 | # 15.HTTPS 解决了 HTTP 的哪些问题?
520 | HTTP 由于是明文传输,所以安全上存在以下三个风险:
521 | * 窃听风险,比如通信链路上可以获取通信内容,用户号容易没。
522 | * 篡改风险,比如强制入垃圾广告,视觉污染,用户眼容易瞎。
523 | * 冒充风险,比如冒充淘宝网站,用户钱容易没。
524 |
525 | 
526 |
527 | HTTPS 在 HTTP 与 TCP 层之间加入了 SSL/TLS 协议,可以很好的解决了上述的风险:
528 | * 信息加密:交互信息无法被窃取
529 | * 校验机制:无法篡改通信内容,篡改了就不能正常显示
530 | * 身份证书:证明淘宝是真的淘宝网
531 | 可见,只要自身不做「恶」,SSL/TLS 协议是能保证通信是安全的
532 |
533 |
534 | # 16.HTTPS 是如何解决上面的三个风险的?
535 |
536 | 混合加密的方式实现信息的机密性,解决了窃听的风险。
537 | * 摘要算法的方式来实现完整性,它能够为数据生成独一无二的「指纹」,指纹用于校验数据的完整性,解决了篡改的风险。
538 | * 将服务器公钥放入到数字证书中,解决了冒充的风险
539 | ### 混合加密
540 | 通过混合加密的方式可以保证信息的机密性,解决了窃听的风险。
541 |
542 | 
543 |
544 | 混合加密 HTTPS 采用的是对称加密和非对称加密结合的「混合加密」方式:
545 | * 在通信建立前采用非对称加密的方式交换「会话秘钥」,后续就不再使用非对称加密。
546 | * 在通信过程中全部使用对称加密的「会话秘钥」的方式加密明文数据。
547 | 采用「混合加密」的方式的原因:
548 | * 对称加密只使用一个密钥,运算速度快,密钥必须保密,无法做到安全的密钥交换。
549 | * 非对称加密使用两个密钥:公钥和私钥,公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢。
550 | ### 摘要算法
551 | 摘要算法用来实现完整性,能够为数据生成独一无二的「指纹」,用于校验数据的完整性,解决了篡改的风险
552 |
553 | 
554 |
555 | 客户端在发送明文之前会通过摘要算法算出明文的「指纹」,发送的时候把「指纹 + 明文」一同 加密成密文后,发送给服务器,服务器解密后,用相同的摘要算法算出发送过来的明文,通过比较客户端携带的「指纹」和当前算出的「指纹」做比较,若「指纹」相同,说明数据是完整的。
556 | ### 数字证书
557 | 客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。这就存在些问题,如何保证公钥不被篡改和信任度?所以这里就需要借助第三方权威机构 CA(数字证书认证机构),将服务器公钥放在数字证书(由数字证书认证机构颁发)中,只要证书是可信的,公钥就是可信的。
558 |
559 | 
560 |
561 | 通过数字证书的方式保证服务器公钥的身份,解决冒充的风险
562 |
563 |
564 | # 17.HTTPS 是如何建立连接的?其间交互了什么?
565 | SSL/TLS 协议基本流程:
566 | 1.TCP 三次同步握手
567 | 2.客户端向服务器索要并验证服务器的公钥
568 | 3.双方协商生产「会话秘钥」
569 | 4.SSL 安全加密隧道协商完成
570 | 5.双方采用「会话秘钥」进行加密通信。
571 | 2,3步是 SSL/TLS 的建立过程,也就是握手阶段 SSL/TLS 的握手阶段涉及四次通信,可见下图:
572 |
573 | 
574 |
575 | SSL/TLS 协议建立的详细流程:
576 | ### 1. ClientHello
577 | 首先,由客户端向服务器发起加密通信请求,也就是 ClientHello 请求。在这一步,客户端主要向服务器发送以下信息:(1)客户端支持的 SSL/TLS 协议版本,如 TLS 1.2 版本。(2)客户端生产的随机数(Client Random),后面用于生产「会话秘钥」。(3)客户端支持的密码套件列表,如 RSA 加密算法。
578 | ### 2. SeverHello
579 | 服务器收到客户端请求后,向客户端发出响应,也就是 SeverHello。服务器回应的内容有如下内容:(1)确认 SSL/ TLS 协议版本,如果浏览器不支持,则关闭加密通信。(2)服务器生产的随机数(Server Random),后面用于生产「会话秘钥」。(3)确认的密码套件列表,如 RSA 加密算法。(4)服务器的数字证书。
580 | ### 3. 客户端回应
581 | 客户端收到服务器的回应之后,首先通过浏览器或者操作系统中的 CA 公钥,确认服务器的数字证书的真实性。如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息:
582 | (1)一个随机数(pre-master key)。该随机数会被服务器公钥加密。
583 | (2)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。
584 | (3)客户端握手结束通知,表示客户端的握手阶段已经结束。
585 | 这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。上面第一项的随机数是整个握手阶段的第三个随机数,这样服务器和客户端就同时有三个随机数,接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」
586 | ### 4. 服务器的最后回应
587 | 服务器收到客户端的第三个随机数(pre-master key)之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。然后,向客户端发生最后的信息:
588 | (1)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。
589 | (2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。
590 | 至此,整个 SSL/TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。
591 |
592 | # 18.UDP 和 TCP 的区别
593 |
594 | 
595 |
596 | # 19.TCP 三次握手和四次挥手
597 |
598 | TCP 三次握手和四次挥手也是面试题的热门考点,它们分别对应 TCP 的连接和释放过程。下面就来简单认识一下这两个过程
599 | ### TCP 三次握手
600 | 在了解具体的流程前,需要先认识几个概念
601 |
602 | 
603 |
604 | * SYN:它的全称是 Synchronize Sequence Numbers,同步序列编号。是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立 TCP 连接时,首先会发送的一个信号。客户端在接受到 SYN 消息时,就会在自己的段内生成一个随机值 X。
605 | * SYN-ACK:服务器收到 SYN 后,打开客户端连接,发送一个 SYN-ACK 作为答复。确认号设置为比接收到的序列号多一个,即 X + 1,服务器为数据包选择的序列号是另一个随机数 Y。
606 | * ACK:Acknowledge character, 确认字符,表示发来的数据已确认接收无误。最后,客户端将 ACK 发送给服务器。序列号被设置为所接收的确认值即 Y + 1。
607 |
608 | 
609 |
610 | 如果用现实生活来举例的话就是
611 | 小明 - 客户端 小红 - 服务端
612 | 小明给小红打电话,接通了后,小明说喂,能听到吗,这就相当于是连接建立。
613 | 小红给小明回应,能听到,你能听到我说的话吗,这就相当于是请求响应。
614 | 小明听到小红的回应后,好的,这相当于是连接确认。在这之后小明和小红就可以通话/交换信息了。
615 | ### TCP 四次挥手
616 | 在连接终止阶段使用四次挥手,连接的每一端都会独立的终止。下面来描述一下这个过程。
617 |
618 | 
619 |
620 | 首先,客户端应用程序决定要终止连接(这里服务端也可以选择断开连接)。这会使客户端将 FIN 发送到服务器,并进入 FIN_WAIT_1 状态。当客户端处于 FIN_WAIT_1 状态时,它会等待来自服务器的 ACK 响应。
621 | 然后第二步,当服务器收到 FIN 消息时,服务器会立刻向客户端发送 ACK 确认消息。
622 | 当客户端收到服务器发送的 ACK 响应后,客户端就进入 FIN_WAIT_2 状态,然后等待来自服务器的 FIN 消息
623 | 服务器发送 ACK 确认消息后,一段时间(可以进行关闭后)会发送 FIN 消息给客户端,告知客户端可以进行关闭。
624 | 当客户端收到从服务端发送的 FIN 消息时,客户端就会由 FIN_WAIT_2 状态变为 TIME_WAIT 状态。处于 TIME_WAIT 状态的客户端允许重新发送 ACK 到服务器为了防止信息丢失。客户端在 TIME_WAIT 状态下花费的时间取决于它的实现,在等待一段时间后,连接关闭,客户端上所有的资源(包括端口号和缓冲区数据)都被释放。
625 | 还是可以用上面那个通话的例子来进行描述
626 | 小明对小红说,我所有的东西都说完了,我要挂电话了。
627 | 小红说,收到,我这边还有一些东西没说。
628 | 经过若干秒后,小红也说完了,小红说,我说完了,现在可以挂断了
629 | 小明收到消息后,又等了若干时间后,挂断了电话。
630 |
631 |
632 | # 20.说说TCP传输的三次握手四次挥手策略
633 |
634 | ### 三次握手
635 | 为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。
636 | 用TCP协议把数据包送出去后,TCP不会对传送后的情况置之不理,它一定会向对方确认是否成功送达。握手过程中使用了TCP的标志:SYN和ACK
637 | * 发送端首先发送一个带SYN标志的数据包给对方。
638 | * 接收端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息。
639 | * 最后,发送端再回传一个带ACK标志的数据包,代表“握手”结束。
640 | ```
641 | 注意:若在握手过程中某个阶段莫名中断,TCP协议会再次以相同的顺序发送相同的数据包
642 | ```
643 | ### 四次挥手
644 | 断开一个TCP连接则需要四次挥手
645 | 第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可以接受数据
646 | 第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)
647 | 第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了
648 | 第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手
649 |
650 | # 21.什么是无状态协议,HTTP 是无状态协议吗,怎么解决
651 | 无状态协议(Stateless Protocol) 就是指浏览器对于事务的处理没有记忆能力。举个例子来说就是比如客户请求获得网页之后关闭浏览器,然后再次启动浏览器,登录该网站,但是服务器并不知道客户关闭了一次浏览器。
652 |
653 | HTTP 就是一种无状态的协议,他对用户的操作没有记忆能力。可能大多数用户不相信,他可能觉得每次输入用户名和密码登陆一个网站后,下次登陆就不再重新输入用户名和密码了。这其实不是 HTTP 做的事情,起作用的是一个叫做 小甜饼(Cookie) 的机制。它能够让浏览器具有记忆能力。
654 |
655 | 如果浏览器允许 cookie 的话,查看方式 chrome://settings/content/cookies
656 |
657 | 
658 |
659 | 也就说明记忆芯片通电了…… 当向服务端发送请求时,服务端会发送一个认证信息,服务器第一次接收到请求时,开辟了一块 Session 空间(创建了Session对象),同时生成一个 sessionId ,并通过响应头的 Set-Cookie:JSESSIONID=XXXXXXX 命令,向客户端发送要求设置 Cookie 的响应;客户端收到响应后,在本机客户端设置了一个 JSESSIONID=XXXXXXX 的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束;
660 |
661 | 
662 |
663 | 接下来客户端每次向同一个网站发送请求时,请求头都会带上该 Cookie信息(包含 sessionId ), 然后,服务器通过读取请求头中的 Cookie 信息,获取名称为 JSESSIONID 的值,得到此次请求的 sessionId。这样浏览器才具有了记忆能力。
664 |
665 | 
666 |
667 | 还有一种方式是使用 JWT 机制,它也是能够让浏览器具有记忆能力的一种机制。与 Cookie 不同,JWT 是保存在客户端的信息,它广泛的应用于单点登录的情况。JWT 具有两个特点
668 | * JWT 的 Cookie 信息存储在客户端,而不是服务端内存中。也就是说,JWT 直接本地进行验证就可以,验证完毕后,这个 Token 就会在 Session 中随请求一起发送到服务器,通过这种方式,可以节省服务器资源,并且 token 可以进行多次验证。
669 | * JWT 支持跨域认证,Cookies 只能用在单个节点的域或者它的子域中有效。如果它们尝试通过第三个节点访问,就会被禁止。使用 JWT 可以解决这个问题,使用 JWT 能够通过多个节点进行用户认证,也就是常说的跨域认证。
670 |
671 |
672 | # 22.OSI与TCP/IP各层的结构与功能,都有哪些协议?
673 |
674 | 
675 |
676 | ### 应用层
677 | 应用层(application-layer)的任务是通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程(进程:主机中正在运行的程序)间的通信和交互的规则。对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多,如域名系统DNS,支持万维网应用的 HTTP协议,支持电子邮件的 SMTP协议等等。把应用层交互的数据单元称为报文。
678 |
679 | #### 域名系统
680 | 域名系统(Domain Name System缩写 DNS,Domain Name被译为域名)是因特网的一项核心服务,它作为可以将域名和IP地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。(百度百科)例如:一个公司的 Web 网站可看作是它在网上的门户,而域名就相当于其门牌地址,通常域名都使用该公司的名称或简称。例如上面提到的微软公司的域名,类似的还有:IBM 公司的域名是 www.ibm.com、Oracle 公司的域名是 www.oracle.com、Cisco公司的域名是 www.cisco.com 等。
681 | #### HTTP协议
682 |
683 | 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW(万维网) 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。
684 |
685 |
686 | ### 运输层
687 | 运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务。应用进程利用该服务传送应用层报文。“通用的”是指并不针对某一个特定的网络应用,而是多种应用可以使用同一个运输层服务。由于一台主机可同时运行多个线程,因此运输层有复用和分用的功能。所谓复用就是指多个应用层进程可同时使用下面运输层的服务,分用和复用相反,是运输层把收到的信息分别交付上面应用层中的相应进程。
688 |
689 | 运输层主要使用以下两种协议:
690 | * 传输控制协议 TCP(Transmission Control Protocol)--提供面向连接的,可靠的数据传输服务。
691 | * 用户数据协议 UDP(User Datagram Protocol)--提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)。
692 |
693 | ### 网络层
694 | 在 计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。 在发送数据时,网络层把运输层产生的
695 |
696 | 报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 IP 协议,因此分组也叫 IP 数据报 ,简称 数据报。
697 |
698 | 这里要注意:不要把运输层的“用户数据报 UDP ”和网络层的“ IP 数据报”弄混。另外,无论是哪一层的数据单元,都可笼统地用“分组”来表示。
699 |
700 | 这里强调指出,网络层中的“网络”二字已经不是通常谈到的具体网络,而是指计算机网络体系结构模型中第三层的名称.
701 |
702 | 互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Intert Protocol)和许多路由选择协议,因此互联网的网络层也叫做网际层或IP层。
703 |
704 | ### 数据链路层
705 | 数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。 在两个相邻节点之间传送数据时,数据链路层将网络层交下来的 IP 数据报组装成帧,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。
706 |
707 | 在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。这样,数据链路层在收到一个帧后,就可从中提出数据部分,上交给网络层。 控制信息还使接收端能够检测到所收到的帧中有误差错。如果发现差错,数据链路层就简单地丢弃这个出了差错的帧,以避免继续在网络中传送下去白白浪费网络资源。如果需要改正数据在链路层传输时出现差错(这就是说,数据链路层不仅要检错,而且还要纠错),那么就要采用可靠性传输协议来纠正出现的差错。这种方法会使链路层的协议复杂些。
708 | ### 物理层
709 | 在物理层上所传送的数据单位是比特。 物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。 使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。
710 |
711 | 在互联网使用的各种协中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的TCP/IP并不一定单指TCP和IP这两个具体的协议,而往往表示互联网所使用的整个TCP/IP协议族。
712 | 
713 |
714 | # 23.TCP协议如何保证可靠传输
715 |
716 | 1.应用数据被分割成 TCP 认为最适合发送的数据块。
717 | 2.TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
718 | 3.校验和: TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
719 | 4.流量控制: TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)
720 | 5.拥塞控制: 当网络拥塞时,减少数据的发送。
721 | 6.ARQ协议: 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
722 | 7.超时重传: 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
723 | 8.TCP 的接收端会丢弃重复的数据。
724 |
725 | # 24.说说ARQ协议
726 |
727 | 自动重传请求(Automatic Repeat-reQuest,ARQ)是OSI模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ包括停止等待ARQ协议和连续ARQ协议。
728 |
729 | # 25.什么是滑动窗口和流量控制
730 |
731 | TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。
732 |
733 | # 26.什么是拥塞控制
734 |
735 | 在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
736 |
737 | 为了进行拥塞控制,TCP 发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。
738 |
739 | TCP的拥塞控制采用了四种算法,即 慢开始 、 拥塞避免 、快重传 和 快恢复。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。
740 |
741 | 慢开始: 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1,每经过一个传播轮次,cwnd加倍。
742 |
743 | 拥塞避免: 拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大,即每经过一个往返时间RTT就把发送放的cwnd加1.
744 |
745 | 快重传与快恢复: 在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。
746 |
747 | 当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。
748 |
749 | # 27.在浏览器中输入url地址 ->> 显示主页的过程?
750 |
751 | 总体来说分为以下几个过程:
752 | 1.DNS解析
753 | 2.TCP连接
754 | 3.发送HTTP请求
755 | 4.服务器处理请求并返回HTTP报文
756 | 5.浏览器解析渲染页面
757 | 6.连接结束
758 |
759 | 
760 |
761 |
762 | # 28.HTTP长连接,短连接
763 |
764 | 在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。
765 | 而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:
766 | ```
767 | Connection:keep-alive
768 | ```
769 | 在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
770 | HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
771 |
772 | # 29.Cookie的作用是什么?和Session有什么区别?
773 |
774 | Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
775 | Cookie 一般用来保存用户信息 比如
776 | ①在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;
777 | ②一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);
778 | ③登录一次网站后访问网站其他页面不需要重新登录。
779 | Session 的主要作用就是通过服务端记录用户的状态。 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
780 | Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。
781 | Cookie 存储在客户端中,而Session存储在服务器上,相对来说 Session 安全性更高。如果要在 Cookie 中存储一些敏感信息,不要直接写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。
782 |
783 | # 30.URI和URL的区别是什么?
784 |
785 | URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源。
786 |
787 | URL(Uniform Resource Location) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。
788 |
789 | URI的作用像身份证号一样,URL的作用更像家庭住址一样。URL是一种具体的URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。
790 |
791 | # 31.HTTP常见的状态码有哪些?
792 |
793 | * 1xx:指示信息--表示请求已接收,继续处理
794 | * 2xx:成功--表示请求已被成功接收、理解、接受
795 | * 3xx:重定向--要完成请求必须进行更进一步的操作
796 | * 4xx:客户端错误--请求有语法错误或请求无法实现
797 | * 5xx:服务器端错误--服务器未能实现合法的请求
798 | #### 常见的状态码:
799 | * 200:请求被正常处理
800 | * 204:请求被受理但没有资源可以返回
801 | * 206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源。
802 | * 301:永久性重定向
803 | * 302:临时重定向
804 | * 303:与302状态码有相似功能,只是它希望客户端在请求一个URI的时候,能通过GET方法重定向到另一个URI上
805 | * 304:发送附带条件的请求时,条件不满足时返回,与重定向无关
806 | * 307:临时重定向,与302类似,只是强制要求使用POST方法
807 | * 400:请求报文语法有误,服务器无法识别
808 | * 401:请求需要认证
809 | * 403:请求的对应资源禁止被访问
810 | * 404:服务器无法找到对应资源
811 | * 500:服务器内部错误
812 | * 503:服务器正忙
813 |
814 | # 32.说说常见的常见HTTP首部字段?
815 |
816 | 通用首部字段(请求报文与响应报文都会使用的首部字段)
817 | Date:创建报文时间Connection:连接的管理Cache-Control:缓存的控制Transfer-Encoding:报文主体的传输编码方式
818 | 请求首部字段(请求报文会使用的首部字段)
819 | Host:请求资源所在服务器Accept:可处理的媒体类型Accept-Charset:可接收的字符集Accept-Encoding:可接受的内容编码Accept-Language:可接受的自然语言
820 | 响应首部字段(响应报文会使用的首部字段)
821 | Accept-Ranges:可接受的字节范围Location:令客户端重新定向到的URIServer:HTTP服务器的安装信息
822 | 实体首部字段(请求报文与响应报文的的实体部分使用的首部字段)
823 | Allow:资源可支持的HTTP方法Content-Type:实体主类的类型Content-Encoding:实体主体适用的编码方式Content-Language:实体主体的自然语言Content-Length:实体主体的的字节数Content-Range:实体主体的位置范围,一般用于发出部分请求时使用
824 |
825 | # 33.HTTPS方式与web服务器通信的步骤?
826 |
827 | 1、客户使用HTTPS的URL访问web服务器,要求与web服务器建立SSL连接
828 | 2、web服务器收到客户端请求后,将网站的证书信息(证书中包含公钥)传送一份给客户端
829 | 3、客户端的浏览器与web服务器开始协商SSL连接的安全等级,也就是信息的加密等级
830 | 4、客户端的浏览器根据双方同意的安全等级,建立会话秘钥,然后利用网站的公钥将会话秘钥加密,并传送给网站
831 | 5、web服务器利用自己的私钥解密出会话秘钥
832 | 6、web服务器利用会话秘钥加密与客户端之间的通信
833 |
834 | # 34.HTTP请求报文与响应报文格式?
835 |
836 | 请求报文:
837 | a、请求行:包含请求方法、URI、HTTP版本信息
838 | b、请求首部字段
839 | c、请求内容实体
840 | 响应报文:
841 | a、状态行:包含HTTP版本、状态码、状态码的原因短语
842 | b、响应首部字段
843 | c、响应内容实体
844 |
845 | # 35.地址栏输入 URL 发生了什么?
846 |
847 | 输入 URL 后到响应,都经历了哪些过程。
848 | * 首先,需要在浏览器中的 URL 地址上,输入要访问的地址,如下
849 |
850 | 
851 |
852 | * 然后,浏览器会根据输入的 URL 地址,去查找域名是否被本地 DNS 缓存,不同浏览器对 DNS 的设置不同,如果浏览器缓存了访问的 URL 地址,那就直接返回 ip。如果没有缓存 URL 地址,浏览器就会发起系统调用来查询本机 hosts 文件是否有配置 ip 地址,如果找到,直接返回。如果找不到,就向网络中发起一个 DNS 查询。
853 | ```
854 | 首先来看一下 DNS 是啥,互联网中识别主机的方式有两种,通过主机名和 IP 地址。人们喜欢用名字的方式进行记忆,但是通信链路中的路由却喜欢定长、有层次结构的 IP 地址。所以就需要一种能够把主机名到 IP 地址的转换服务,这种服务就是由 DNS 提供的。DNS 的全称是 Domain Name System 域名系统。DNS 是一种由分层的 DNS 服务器实现的分布式数据库。DNS 运行在 UDP 上,使用 53 端口。
855 | ```
856 | 
857 |
858 | DNS 是一种分层数据库,它的主要层次结构如下
859 |
860 | 
861 |
862 | 一般域名服务器的层次结构主要是以上三种,除此之外,还有另一类重要的 DNS 服务器,它是 本地 DNS 服务器(local DNS server)。严格来说,本地 DNS 服务器并不属于上述层次结构,但是本地 DNS 服务器又是至关重要的。每个 ISP(Internet Service Provider) 比如居民区的 ISP 或者一个机构的 ISP 都有一台本地 DNS 服务器。当主机和 ISP 进行连接时,该 ISP 会提供一台主机的 IP 地址,该主机会具有一台或多台其本地 DNS 服务器的 IP地址。通过访问网络连接,用户能够容易的确定 DNS 服务器的 IP地址。当主机发出 DNS 请求后,该请求被发往本地 DNS 服务器,它起着代理的作用,并将该请求转发到 DNS 服务器层次系统中。
863 |
864 | 首先,查询请求会先找到本地 DNS 服务器来查询是否包含 IP 地址,如果本地 DNS 无法查询到目标 IP 地址,就会向根域名服务器发起一个 DNS 查询。
865 | ```
866 | 注意:DNS 涉及两种查询方式:一种是递归查询(Recursive query) ,一种是迭代查询(Iteration query)。《计算机网络:自顶向下方法》竟然没有给出递归查询和迭代查询的区别,找了一下网上的资料大概明白了下。
867 | 如果根域名服务器无法告知本地 DNS 服务器下一步需要访问哪个顶级域名服务器,就会使用递归查询;
868 | 如果根域名服务器能够告知 DNS 服务器下一步需要访问的顶级域名服务器,就会使用迭代查询。
869 | ```
870 | 在由根域名服务器 -> 顶级域名服务器 -> 权威 DNS 服务器后,由权威服务器告诉本地服务器目标 IP 地址,再有本地 DNS 服务器告诉用户需要访问的 IP 地址。
871 | * 第三步,浏览器需要和目标服务器建立 TCP 连接,需要经过三次握手的过程,具体的握手过程请参考上面的回答。
872 | * 在建立连接后,浏览器会向目标服务器发起 HTTP-GET 请求,包括其中的 URL,HTTP 1.1 后默认使用长连接,只需要一次握手即可多次传输数据。
873 | * 如果目标服务器只是一个简单的页面,就会直接返回。但是对于某些大型网站的站点,往往不会直接返回主机名所在的页面,而会直接重定向。返回的状态码就不是 200 ,而是 301,302 以 3 开头的重定向码,浏览器在获取了重定向响应后,在响应报文中 Location 项找到重定向地址,浏览器重新第一步访问即可。
874 | * 然后浏览器重新发送请求,携带新的 URL,返回状态码 200 OK,表示服务器可以响应请求,返回报文。
875 |
876 | # 36.HTTPS的工作原理
877 |
878 | 上面描述了一下 HTTP 的工作原理,下面来讲述一下 HTTPS 的工作原理。因为 HTTPS 不是一种新出现的协议,而是
879 |
880 | 
881 |
882 | 所以探讨 HTTPS 的握手过程,其实就是 SSL/TLS 的握手过程。
883 |
884 | TLS 旨在为 Internet 提供通信安全的加密协议。TLS 握手是启动和使用 TLS 加密的通信会话的过程。在 TLS 握手期间,Internet 中的通信双方会彼此交换信息,验证密码套件,交换会话密钥。
885 | 每当用户通过 HTTPS 导航到具体的网站并发送请求时,就会进行 TLS 握手。除此之外,每当其他任何通信使用HTTPS(包括 API 调用和在 HTTPS 上查询 DNS)时,也会发生 TLS 握手。
886 |
887 | TLS 具体的握手过程会根据所使用的密钥交换算法的类型和双方支持的密码套件而不同。以RSA 非对称加密来讨论这个过程。整个 TLS 通信流程图如下
888 |
889 | 
890 |
891 | * 在进行通信前,首先会进行 HTTP 的三次握手,握手完成后,再进行 TLS 的握手过程
892 | * ClientHello:客户端通过向服务器发送 hello 消息来发起握手过程。这个消息中会夹带着客户端支持的 TLS 版本号(TLS1.0 、TLS1.2、TLS1.3) 、客户端支持的密码套件、以及一串 客户端随机数。
893 | * ServerHello:在客户端发送 hello 消息后,服务器会发送一条消息,这条消息包含了服务器的 SSL 证书、服务器选择的密码套件和服务器生成的随机数。
894 | * 认证(Authentication):客户端的证书颁发机构会认证 SSL 证书,然后发送 Certificate 报文,报文中包含公开密钥证书。最后服务器发送 ServerHelloDone 作为 hello 请求的响应。第一部分握手阶段结束。
895 | * 加密阶段:在第一个阶段握手完成后,客户端会发送 ClientKeyExchange 作为响应,这个响应中包含了一种称为 The premaster secret 的密钥字符串,这个字符串就是使用上面公开密钥证书进行加密的字符串。随后客户端会发送 ChangeCipherSpec,告诉服务端使用私钥解密这个 premaster secret 的字符串,然后客户端发送 Finished 告诉服务端自己发送完成了。
896 | ```
897 | Session key 其实就是用公钥证书加密的公钥。
898 | ```
899 | * 实现了安全的非对称加密:然后,服务器再发送 ChangeCipherSpec 和 Finished 告诉客户端解密完成,至此实现了 RSA 的非对称加密。
900 |
--------------------------------------------------------------------------------