本笔记基于MX6ULL为板载核心,使用正点原子alpha板。

eMMC

eMMCembedded MultiMediaCard)是一种嵌入式存储解决方案,广泛应用于智能手机、平板电脑、物联网设备、车载系统等嵌入式设备中。它结合了 NAND Flash 存储芯片控制器,并通过标准接口与主机(如处理器或 SoC)通信。成本低,但通信速率低于UFS,更低于SSD,主要应用在低端设备。

eMMC 的基本组成

eMMC 并不是单纯的存储芯片,而是一个完整的存储模块,主要由以下部分组成:

  • NAND Flash 存储芯片:用于实际数据存储。
  • Flash 控制器:管理 NAND Flash 的读写、坏块管理、磨损均衡(wear leveling)、错误校验(ECC)等。
  • 标准接口(MMC 接口):采用 JEDEC eMMC 标准,兼容性强,简化了主控设计。

使用方式和作用

  • 烧录方法:可通过 SD 卡、USB 工具

  • Linux 系统和应用程序通常会被烧录到 eMMC 存储中,并通过配置启动模式(如 eMMC 启动)来运行系统

  • 本质:eMMC = NAND Flash(存储) + 控制器(管理 Flash),相当于一个 “自带管理器的硬盘”

  • 存储内容

    • Bootloader(如 U-Boot)
    • Linux 内核(如 zImage
    • 根文件系统(如 rootfs.ext4

eMMC 和普通 NAND Flash 的区别?

  • eMMC:自带控制器,主控芯片直接通过标准接口读写(简单,适合嵌入式)。
  • 原始 NAND Flash:需要主控芯片实现坏块管理、ECC 校验等(复杂,需额外驱动)。

嵌入式设备的核心组件类比(以 i.MX6ULL 为例)

组件 电脑类比 嵌入式设备(如 i.MX6ULL) 作用
eMMC 硬盘(SSD/HDD) 嵌入式存储芯片(如 8GB eMMC) 存储系统(Linux内核、根文件系统)、应用程序、用户数据,非易失性(断电不丢失)。
RAM(如 DDR3) 内存条 动态内存(如 256MB/512MB DDR3) 临时存储运行时的程序和数据,易失性(断电丢失)。
主控芯片 CPU 处理器(如 NXP i.MX6ULL) 执行计算、控制外设(USB、GPIO、网络等),运行操作系统(如 Linux)。
Boot ROM BIOS/UEFI 芯片内置的 BootROM 初始化硬件,加载第一级 Bootloader(如 U-Boot)。

NFS(Network File System)服务

NFS(Network File System,网络文件系统)是一种分布式文件系统协议,由 Sun Microsystems 开发,允许计算机系统通过网络共享文件和目录。它广泛应用于 Linux/Unix 环境,使客户端可以像访问本地文件一样访问远程服务器上的文件。经过我的尝试,无法在WSL(1)上通过nfs挂载其他linux系统进行嵌入式开发。

通信流程

  1. 服务器端 启动 nfs-server 并导出(export)共享目录。
  2. 客户端 请求挂载远程目录,通过 RPC 协议与服务器交互。
  3. 数据传输:
    • 客户端读写文件时,NFS 协议将请求转发到服务器。
    • 服务器处理请求并返回数据,客户端无感知(透明访问)。

配置方法

除了不能再WSL上配置可以在虚拟机、或Linux系统下配置:

Ubuntu 使用如下命令安装 NFS 服务:

1
sudo apt-get install nfs-kernel-server rpcbind

使用如下命令打开 nfs 配置文件/etc/exports:

1
sudo vi /etc/exports

打开/etc/exports 以后在后面添加如下所示内容:

1
/home/user/linux/nfs *(rw,sync,no_root_squash)

重启 NFS 服务,使用命令如下:

1
sudo /etc/init.d/nfs-kernel-server restart

最后使用如下命令在嵌入端进行挂载:

1
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/user/nfs_rootfs /mnt

在开发过程中,在 Ubuntu 中编译好程序后放入/home/book/nfs_rootfs目录,开发板 mount nfs 后就可以直接通过/mnt 访问 Ubuntu 中的文件。

远程访问开发板

远程命令使用开发板的三种方式:

  1. 使用串口连接后通过MobaXterm通过指定串口和波特率(115200)后进行访问。

  2. windows端与开发板连接到同一个局域网后,通过MobaXterm的ssh指定IP和端口(22)后访问。

  3. 在linux端(ubuntu)无法使用MobaXterm,可以直接在终端中通过ssh进行访问,命令如下:

    1
    ssh root@192.168.X.XXX

文件传输的两种方法:

  1. 同一局域网下,使用Filezilla软件指定IP和端口号(22)后进行传输
  2. 相同的方式,使用WinSCP

GCC

交叉编译工具有OpenWrt,Buildroot,Linaro的,选择:

Linaro出品的交叉编译器:arm-linux-gnueabihf-gcc

编译流程

GCC 的编译过程分为 4 个阶段

gcc

(1)预处理(Preprocessing)

  • 处理 #include#define#ifdef 等宏指令。

  • 命令:

    1
    gcc -E hello.c -o hello.i  # 生成预处理文件

(2)编译(Compilation)

  • 将预处理后的代码转为汇编语言。

  • 命令:

    1
    gcc -S hello.i -o hello.s  # 生成汇编代码

(3)汇编(Assembly)

  • 将汇编代码转为机器码(目标文件 .o)。

  • 命令:

    1
    gcc -c hello.s -o hello.o  # 生成目标文件

(4)链接(Linking)

  • 合并目标文件和库文件(如 libc.so),生成可执行文件。

  • 命令:

    1
    gcc hello.o -o hello  # 生成可执行文件

简化命令:直接生成可执行文件

1
gcc hello.c -o hello

GCC 常用编译选项

基础选项

选项 说明
-o <file> 指定输出文件名(默认 a.out)。
-c 只编译不链接,生成 .o 目标文件。
-g 添加调试信息(供 GDB 使用)。
-Wall 启用所有警告(推荐始终使用)。
-O0/-O1/-O2/-O3 优化级别(-O0 不优化,-O3 最高优化)。

库和路径选项

选项 说明
-I<dir> 添加头文件搜索路径(如 -I/usr/local/include)。
-L<dir> 添加库文件搜索路径(如 -L/usr/local/lib)。
-l<library> 链接动态库(如 -lm 链接数学库 libm.so)。
-static 静态编译(将所有库打包进可执行文件)。

调试与优化

选项 说明
-D<macro> 定义宏(如 -DDEBUG 等价于 #define DEBUG)。
-fPIC 生成位置无关代码(用于动态库)。
-save-temps 保留临时文件(.i.s.o)。

操作文件I/O

在 Linux 系统中,文件 I/O(输入/输出)操作可以通过两种主要方式实现:标准 I/O(Standard I/O)系统 I/O(System I/O)。它们的主要区别在于接口层次、缓冲机制、性能以及适用场景。

1. 标准 I/O(Standard I/O)

标准 I/O 是由 C 标准库(libc) 提供的高级文件操作接口,封装了底层系统调用,提供缓冲机制,以提高 I/O 效率。

特点

  • 基于流(FILE*):使用 FILE* 结构体表示文件流。
  • 带缓冲(Buffered):
    • 全缓冲(Fully Buffered):当缓冲区满时才进行实际 I/O(适用于文件)。
    • 行缓冲(Line Buffered):遇到换行符 \n 或缓冲区满时刷新(适用于终端)。
    • 无缓冲(Unbuffered):立即写入(如 stderr)。
  • 跨平台兼容:由于是 C 标准库的一部分,代码可移植性较好。
  • 适用于频繁的小数据读写,减少系统调用次数,提高性能。

常用函数

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

FILE *fopen(const char *path, const char *mode); // 打开文件
int fclose(FILE *stream); // 关闭文件
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); // 读数据
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); // 写数据
int fseek(FILE *stream, long offset, int whence); // 移动文件指针
long ftell(FILE *stream); // 获取当前文件位置
char *fgets(char *s, int size, FILE *stream); // 读取一行
int fprintf(FILE *stream, const char *format, ...); // 格式化输出

示例

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main() {
FILE *fp = fopen("test.txt", "w");
if (!fp) {
perror("fopen failed");
return 1;
}
fprintf(fp, "Hello, Standard I/O!\n");
fclose(fp);
return 0;
}

2. 系统 I/O(System I/O / Unix I/O)

系统 I/O 是 Linux/Unix 提供的 底层文件操作接口,直接调用内核提供的系统调用(syscalls),不提供缓冲机制。

特点

  • 基于文件描述符(File Descriptor, fd):使用整数 int 表示文件(如 0=stdin, 1=stdout, 2=stderr)。
  • 无缓冲(Unbuffered):每次读写都会触发系统调用,效率较低但更可控。
  • 适用于高性能、低延迟场景(如网络编程、设备驱动)。
  • 更底层,灵活性更高,可以直接操作设备文件、管道、套接字等。

常用函数

1
2
3
4
5
6
7
8
9
10
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

int open(const char *pathname, int flags, mode_t mode); // 打开文件
int close(int fd); // 关闭文件
ssize_t read(int fd, void *buf, size_t count); // 读数据
ssize_t write(int fd, const void *buf, size_t count); // 写数据
off_t lseek(int fd, off_t offset, int whence); // 移动文件指针
int fcntl(int fd, int cmd, ...); // 控制文件描述符

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
int fd = open("test.txt", O_WRONLY | O_CREAT, 0644);
if (fd == -1) {
perror("open failed");
return 1;
}
write(fd, "Hello, System I/O!\n", 19);
close(fd);
return 0;
}

3. 主要区别对比

特性 标准 I/O(stdio) 系统 I/O(syscall)
接口级别 高级(C 标准库封装) 低级(直接系统调用)
缓冲机制 有缓冲(可配置) 无缓冲(直接读写)
性能 较高(减少系统调用) 较低(每次调用都进内核)
适用场景 普通文件、文本处理 设备文件、网络、底层开发
文件表示 FILE*(流) int(文件描述符)
移植性 高(跨平台) 低(依赖 Unix/Linux)
控制灵活性 较低(缓冲不可控) 高(直接控制)

4. 如何选择?

  • 使用标准 I/O(fopen/fread/fwrite):
    • 需要处理文本文件、格式化输入输出。
    • 需要缓冲机制提高性能(如频繁的小数据读写)。
    • 希望代码可移植到其他平台(Windows/macOS)。
  • 使用系统 I/O(open/read/write):
    • 需要直接操作设备文件(如 /dev 下的设备)。
    • 需要非阻塞 I/O 或文件锁(fcntl)。
    • 进行网络编程(套接字 socket 也是文件描述符)。
    • 需要精确控制 I/O 行为(如 O_DIRECT 绕过缓存)。

5. 混合使用

在某些情况下,可以混合使用两者:

  • 使用fileno()FILE*转换为文件描述符:

    1
    2
    FILE *fp = fopen("file.txt", "r");
    int fd = fileno(fp); // 获取底层文件描述符
  • 使用fdopen()将文件描述符转换为FILE*

    1
    2
    int fd = open("file.txt", O_RDONLY);
    FILE *fp = fdopen(fd, "r"); // 包装成标准 I/O 流

Others

修改DNS配置:

打开/etc/resolv.conf,添加修改nameserver 8.8.8.8

详细:IP地址与路由