嵌入式基础知识大杂烩

1. u-boot 存放位置

u-boot(或称为Das U-Boot)是一种广泛应用于嵌入式系统的开源引导加载程序。它通常存放在嵌入式系统的非易失性存储器中,具体位置可以根据具体的嵌入式系统架构和设计来确定。

以下是几种常见的u-boot存放位置:

1. Flash存储器:

类似于单片机的情况,u-boot可以被烧写到系统的Flash存储器中。在嵌入式系统启动时,处理器会从Flash存储器读取u-boot的代码并执行

2. NOR Flash:

一些嵌入式系统使用NOR Flash作为主要的存储器,并将u-boot存放在Flash芯片的特定地址范围内。这种存储器通常可以直接在处理器中执行,而无需将其复制到其他位置。

3. NAND Flash:

另一种常见的存储器是NAND Flash,它也可以用于存放u-boot。与NOR Flash不同,NAND Flash的访问方式复杂一些,因此在启动过程中,一般会将u-boot从NAND Flash中复制到RAM中,并在RAM中执行

4. SD卡/TF卡:

在某些嵌入式系统中,u-boot可以存放在可移动存储介质上,例如SD卡或TF卡。当系统启动时,处理器会首先加载u-boot从存储卡上,并执行。

总结起来,u-boot一般存放在嵌入式系统的Flash存储器中,包括NOR Flash和NAND Flash,在一些系统中也可以存放在可移动存储介质上,例如SD卡或TF卡。

2. FreeRTOS 移植

在将FreeRTOS移植到STM32上之前,您需要进行以下配置:

1. 配置硬件:确保您的STM32芯片与所选择的FreeRTOS版本兼容,并且具备足够的内存资源。您可以参考芯片的数据手册和FreeRTOS的系统要求来确认这些信息。

2. 引入FreeRTOS源代码:将FreeRTOS源代码添加到您的项目中。您可以从FreeRTOS官方网站下载最新的稳定版本或者适用于STM32的移植版。

3. 配置FreeRTOSConfig.h文件:这个文件包含了FreeRTOS的核心配置参数。您需要根据STM32的硬件特性和应用需求进行相应配置。以下是一些可能需要配置的参数:

   - 定时器配置:根据STM32的定时器资源,配置用于FreeRTOS的系统节拍定时器
   - 内存管理器配置:根据可用的RAM大小和需要的内存管理策略,配置内存管理器选项。
   - 任务优先级配置:定义任务优先级的范围和默认优先级。
   - 栈配置:为每个任务定义适当大小的栈空间。
   - 优化配置:根据应用需求进行合适的编译优化设置。

4. 创建任务:在main.c(或其他文件)中创建需要运行的任务,通过调用FreeRTOS提供的API函数进行任务的创建、启动和管理。

5. 配置中断:根据您的应用需求,配置适当的中断处理程序,并使用FreeRTOS提供的接口函数来在中断中进行任务切换。

6. 配置时钟和系统节拍:根据STM32的时钟设置和FreeRTOS的节拍定时器配置,确保系统节拍的准确性和稳定性。

7. 编译和烧录:将修改后的代码编译为可执行文件,并将其烧录到STM32芯片中进行测试和调试。

请注意,以上步骤仅提供了一个大致的概述,实际移植过程可能会因STM32型号、FreeRTOS版本和应用需求而有所差异。建议您参考针对您具体芯片和FreeRTOS版本的移植指南或示例代码,以获得更详细和准确的配置信息和步骤。

3. Linux 同步通信和异步通信方式

在Linux中,有多种通信方式可用于进程间通信(IPC),其中一些是同步通信,而另一些是异步通信下面是一些常见的IPC方式以及它们的通信方式:

1. 管道(Pipe):管道是一种半双工通信方式,用于在父子进程或具有共同祖先的进程之间进行通信。它是通过内核缓冲区实现的,具有有限的容量管道是同步通信方式,发送方写入数据后,如果缓冲区已满,发送方会被阻塞等待接收方读取数据。

2. 命名管道(Named Pipe):命名管道也是一种半双工通信方式,允许没有亲缘关系的进程进行通信。不同于管道,命名管道使用文件系统路径作为标识符,并以文件的形式存在于文件系统中。命名管道是同步通信方式,发送方在写入数据时,如果缓冲区已满,则会被阻塞等待接收方读取数据。

3. 消息队列(Message Queue):消息队列允许独立的进程通过消息传递进行通信。它提供了一个存储消息的队列,并允许发送方发送消息到队列中,接收方从队列中读取消息。消息队列可以是同步或异步的,具体取决于发送方和接收方之间的操作

4. 信号量(Semaphore):信号量用于在多进程或多线程之间共享资源,并对资源的访问进行同步控制。信号量可以用于同步也可以用于异步通信,具体取决于进程或线程如何使用它们。

5. 共享内存(Shared Memory):共享内存允许多个进程共享同一块物理内存区域,以便进行快速高效的数据交换。共享内存本身是一个同步机制,但进程间的同步与互斥需要额外的同步机制,如信号量来实现。

6. Socket套接字(Socket):Socket是一种常见的网络编程接口,允许不同主机上的进程进行通信。Socket可以通过阻塞方式或非阻塞方式进行通信,因此可以实现同步或异步的通信方式

需要注意的是,上述通信方式并非专门属于同步或异步通信的,而是根据具体实现和使用方式来确定其通信方式。通信方式的选择应根据实际需求和场景来确定。

4. Uboot启动流程

U-Boot(Universal Bootloader)是一款常用的开源引导加载程序,常用于嵌入式系统的启动。下面是Linux U-Boot的一般启动流程:

1. 引导加载器加载:当嵌入式设备上电或复位后,处理器会跳转到预定义的引导加载器(Bootloader)地址,通常在Flash存储器的某个固定位置。这个引导加载器负责加载U-Boot镜像文件到内存中。

2. U-Boot初始化:一旦U-Boot镜像被加载到内存中,处理器会跳转到U-Boot的入口地址,开始执行U-Boot的代码。U-Boot首先会进行一系列的初始化工作,包括设置堆栈、初始化存储器控制器、配置时钟和外设等。(软件初始化)

3. 硬件初始化:接下来,U-Boot会根据板级支持包(Board Support Package,BSP)中的配置信息,通过调用相关函数对设备的硬件进行初始化如GPIO口、UART、以太网控制器等

4. 加载内核镜像:U-Boot会根据配置的启动方式(例如TFTP、NFS、MMC、SPI Flash等),从存储介质中加载Linux内核镜像(zImage或uImage)到内存中的指定地址。

5. 加载设备树(Device Tree):如果使用设备树来描述硬件信息,则U-Boot会加载设备树文件(dtb)到内存中指定的地址。

6. 传递启动参数:在加载完内核和设备树后,U-Boot会设置启动参数(如内核命令行参数、Device Tree Blob地址等),并将这些参数传递给内核。(直接修改PC寄存器的值为内核的入口地址,就可以让PC寄存器去执行内核的代码)

7. 跳转到内核:最后,U-Boot会通过函数调用或汇编指令将控制权转移到内核的入口地址,让内核接管系统的进一步启动过程。

需要注意的是,上述流程可能会因不同的硬件平台、U-Boot版本和配置方式而有所差异。具体的启动流程和配置方式应参考官方文档或相关开发者手册,以确保正确配置和操作。

5. STM32(单片机)启动流程

在STM32微控制器上电后,到达main函数执行之前,通常会经过以下几个主要的步骤:

1. 复位向量表(Reset Vector Table):当STM32微控制器上电或复位时,硬件会将处理器重置为初始状态。这时,处理器会跳转到预定义的复位向量表地址,即存储器的起始位置(通常是0x0000 0000),从而执行其中的复位处理程序。复位处理程序主要完成一些初始化工作,如设置堆栈指针、初始化全局变量等。

2. 设置堆栈和系统时钟:复位处理程序一般会设置栈指针(Stack Pointer)的初始值,并初始化系统时钟。通过设置合适的堆栈指针,可以确保在程序运行期间正确地管理函数调用和中断处理等操作。同时,始化系统时钟可以为后续的外设和功能提供正确的时钟源。(软件初始化)

3. 初始化外设和系统组件:在复位处理程序或启动文件中,会进行一系列的初始化工作,包括但不限于初始化中断控制器、配置时钟源和时钟分频器、初始化GPIO口、设置系统中断优先级等。这些步骤会确保各个外设和系统组件处于正确的状态,以便后续的应用程序正常运行。(硬件初始化)

4. 跳转到main函数:最后,经过上述的初始化过程后,处理器会跳转到main函数的入口地址开始执行应用程序。在main函数中,开发者可以编写自己的应用程序逻辑,实现所需的功能。

需要注意的是,上述步骤的具体实现可能因不同的微控制器型号和使用的开发工具链而有所差异。一般来说,STMicroelectronics提供了相应的启动文件和库函数,能够帮助开发者进行初始化和配置工作。在编写嵌入式代码时,建议参考对应的微控制器参考手册和相关文档,并按照官方提供的示例进行操作。

6. Linux 启动流程

  1. 上电:硬件上电
  2. Uboot:这里涉及到 uboot 的启动流程
  3. Linux内核启动:uboot 启动后会指引 Linux 内核启动
  4. 运行文件系统读入 boot 目录下的内核文件,或者说运行文件系统(boot下有什么文件?arch 目录等等)
  5. Init 进程:内核加载后运行的第一个程序,sbin/init;用来初始化系统环境,PID=1;
  6. 加载开机启动脚本:比如 /etc/init.d ;、etc/local.rc 等等
  7. 用户登录,进入shell

7. extern C

extern C 是一个用于 C++ 语言的关键字组合,用于声明具有 C 语言编译习惯和名称修饰规则的函数或变量。

在 C++ 中,函数和变量的名称修饰规则是不同于 C 语言的。C++ 会对函数或变量的名称进行修饰,以便支持函数重载、命名空间等特性。而 C 语言没有名称修饰这个概念。

使用 extern "C" 关键字将函数或变量声明为 C 链接,可以使得 C++ 编译器按照 C 语言的链接规则来处理这些声明。这样,就可以在 C++ 代码中与 C 语言的函数或变量进行兼容操作、调用或共享。

举个栗子:

#ifdef __cplusplus
extern "C" {
#endif

// C 风格的函数声明
void foo();

#ifdef __cplusplus
}
#endif

8. 给你一个裸机你怎么输出 printf

        在裸机环境下,没有操作系统提供的标准输入输出库,无法直接使用 printf 函数进行输出。然而,你可以手动编写一个简单的输出函数来模拟 printf 的功能。

        首先,需要了解裸机环境的底层硬件和外设接口。通常,裸机开发中会涉及寄存器访问、串口通信等底层操作。

下面是一个简单的示例,通过串口(UART)实现一个简化版的 printf 函数思路

操作寄存器,初始化串口,往串口数据寄存器里面写入数据。

// 定义串口地址
#define UART_BASE_ADDRESS 0x10000000

// 串口寄存器地址偏移量
#define UART_DATA_REG_OFFSET 0x00
#define UART_STATUS_REG_OFFSET 0x04

// 输出单个字符到串口
void uart_putc(char c) {
    // 等待串口就绪
    while (*(volatile unsigned int *)(UART_BASE_ADDRESS + UART_STATUS_REG_OFFSET) & 0x01);
    
    // 写入字符到数据寄存器
    *(volatile unsigned int *)(UART_BASE_ADDRESS + UART_DATA_REG_OFFSET) = c;
}

// 输出格式化字符串到串口
void my_printf(const char *format, ...) {
    // 根据参数解析格式化字符串并输出到串口
    // 这里只实现了 %c 和 %s 格式的输出
}

9. define 有什么缺陷,可以用什么替换 define

`#define` 是 C 和 C++ 中用于定义宏的预处理指令。虽然 `#define` 在一些情况下很方便,但也存在一些缺陷,包括:

1. 无类型检查:`#define` 定义的宏没有类型检查,编译器无法对其进行语法、语义和类型等错误检查。这可能导致宏的使用出现潜在的问题,并增加调试难度。

2. 可读性差:使用过度复杂的宏定义会导致代码可读性差,使得代码难以理解、维护和调试。宏定义通常是替换文本,不像函数一样具备结构和作用域

3. 副作用:宏定义可以对参数进行多次求值,这可能导致意外的副作用。例如,`#define SQUARE(x) (x * x)`,在将表达式作为参数时,可能会产生预期之外的结果。

为了替代 `#define`,可以使用以下方法:

1. 使用常量或枚举:对于需要定义常量或枚举类型的场景,可以使用常量或枚举来替代宏定义,这样可以具有类型检查和更好的可读性。

2. 使用内联函数:内联函数可以实现宏定义相同的效果,但会进行更严格的类型检查。内联函数能够保留函数的结构和作用域,同时具有类型安全性和可读性。

3. 使用 constexpr(在 C++11 中引入):constexpr 是用于声明可以在编译期间计算的常量表达式的关键字。它可以用于替代宏定义的常量,并且会进行编译时类型检查。

10. 为什么要有用户态和内核态,两者如何切换

        用户态和内核态是计算机系统中的两种不同的运行模式,用于区分用户程序和操作系统内核的执行权限和特权级别

        用户态是指用户程序在执行时所处的权限较低的状态。在用户态下,用户程序只能访问受限的资源和执行受限的操作。例如,用户程序不能直接访问底层硬件设备。

        内核态是指操作系统内核在执行时所处的权限较高的状态。在内核态下,操作系统具有对所有资源和操作的完全控制权,包括访问硬件设备、修改内存映射、管理进程等

        引入用户态和内核态的主要目的是实现系统的安全和可靠性。通过限制用户程序的权限,可以防止用户程序对系统造成损害或滥用系统资源。同时,操作系统在内核态下具有更高的权限和更广泛的功能,可以有效管理和保护系统资源。

        当用户程序需要执行特权操作时(例如访问受保护的资源或执行特权指令),必须通过系统调用的方式从用户态切换到内核态。系统调用是用户程序发起系统服务请求的一种机制,它将控制权转移到操作系统内核,并在内核态下执行相应的操作。完成后,操作系统再将控制权返回给用户程序,并切换回用户态。

        但总的来说,用户态和内核态之间的切换是通过特殊的机制和处理器指令来实现的,确保用户程序和操作系统能够安全地进行交互和协作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值