系统移植一

使用设备是fs4412开发板

一、系统移植

  • 系统移植是将一个操作系统或软件从一个硬件平台或处理器架构转移到另一个平台的过程。系统移植的主要目标是使软件在新的硬件环境下能够正常运行。
  • 在系统移植过程中,主要的改动集中在硬件相关的底层部分以及操作系统的核心模块。
二、主要步骤
1.嵌入式环境搭建
  1. 在嵌入式开发中,通常使用交叉编译工具将源代码编译成目标平台(例如ARM架构)的可执行代码。
  2. 示例
sudo apt-get install gcc-arm-none-eabi
该工具将帮助你将代码编译为ARM架构指令集。
  1. TFTP协议
  • TFTP是一种简单的文件传输协议,用于嵌入式系统中通过网络传输文件。它通常用于传输启动文件和固件。
  • 安装TFTP工具
sudo apt-get install tftpd-hpa tftp-hpa
  • 配置TFTP服务器: 编辑配置文件
sudo vi /etc/default/tftpd-hpa

确保如下配置

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpboot"  # 创建该目录并设置权限:chmod 777 /tftpboot
# 这是TFTP服务器的根目录。所有通过TFTP传输的文件将被限制在这个目录中。
# 我设置的是/home/ubuntu/tftpboot
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-c -s -l"

  • 重启TFTP服务器
sudo service tftpd-hpa restart
  • 测试TFTP服务器
    • tftp 127.0.0.1连接TFTP服务器
    • 使用get filename下载文件
    • 使用put filename上传文件
    • 使用quit退出
      在这里插入图片描述
  1. NFS协议(Network File System)
  • NFS允许网络上的文件系统像本地文件一样被访问,常用于嵌入式系统开发中通过网络加载根文件系统。
    1. 安装NFS服务器
    sudo apt-get install nfs-kernel-server
    
    1. 配置NFS导出的目录: 编辑/etc/exports文件,在底部添加要导出的目录,NFS 服务器可以指定哪些目录可以被客户端访问,以及每个目录的访问权限和规则。
    sudo vi /etc/exports
    
    我设置的是
    /home/lsf/source/rootfs *(rw,sync,no_root_squash,no_subtree_check)
    //所有客户端
    
    1. 重启NFS服务器
    sudo service nfs-kernel-server restart
    
    1. 测试:在另一个终端上,作为客户端进行测试
cd /mnt/ 
mkdir testdir  # 创建挂载点
sudo mount -t nfs 127.0.0.1:/home/ubuntu/rootfs ./testdir
//是将本地NFS服务器上导出的目录 /home/ubuntu/rootfs 挂载到本地的 ./testdir 目录。

断开连接

sudo umount ./testdir

在这里插入图片描述

2. 三大件移植
  • 在嵌入式系统中,移植主要涉及三大件:Bootloader、操作系统、文件系统。以下是常见的三大件的对比和流程:
  1. Bootloader
    Bootloader是系统启动的第一阶段,负责加载操作系统内核。在PC中,BIOS扮演这个角色,而在嵌入式系统中,常用u-boot作为bootloader。

    • PC: BIOS启动 -> 加载Windows操作系统 -> 加载文件系统
    • Android: Recovery模式 -> 加载Android操作系统 -> 文件系统
    • 嵌入式系统: u-boot -> 加载Linux操作系统 -> 文件系统
  2. 操作系统(OS)
    嵌入式系统的操作系统通常为Linux。在移植过程中,需要针对目标平台的处理器架构、内存管理和外设驱动进行修改和适配。典型的流程包括:

  • 修改内核源代码以支持目标硬件。
  • 编译内核并通过Bootloader加载到内存中执行。
  1. 文件系统(FS)
    文件系统负责管理系统中的文件和目录。在嵌入式系统中,文件系统通常存储在NAND Flash、SD卡等非易失性存储设备上。常见的嵌入式文件系统包括ext4、jffs2、squashfs等。
  • 文件系统移植:根据目标平台的存储设备,选择合适的文件系统,并通过NFS或TFTP加载。
  1. 嵌入式系统整体移植流程
  • 使用交叉编译工具生成可执行的Bootloader和操作系统。
  • 配置TFTP或NFS用于传输和挂载根文件系统。
  • 将Bootloader(如u-boot)移植到目标平台,并通过它加载Linux内核。
  • 通过NFS、TFTP或其他方式加载并挂载文件系统。

bootloader

  • Bootloader 是系统启动加载器的统称,用于加载操作系统(OS)。它在嵌入式系统中起到了关键作用,尤其是在系统损坏时,可以通过 Bootloader 进行系统修复或重新刷机。
  • 作用:
    • 系统启动:Bootloader 在开机后执行部分硬件初始化,准备加载操作系统。
    • 系统修复:当系统损坏时,可以通过 Bootloader 进入交互模式,进行系统的修复或重新刷机。
    • 双系统管理:支持双系统的设备可以通过 Bootloader 选择要启动的系统。
    • 硬件初始化:在加载操作系统之前,Bootloader 需要初始化关键硬件组件,如内存、CPU 时钟、外设等,以确保操作系统的正常启动。

工作过程

  • Bootloader 通常分为两个阶段
    • 第一阶段(汇编级别)
      • 启动与硬件初始化:在系统加电后,Bootloader 负责执行最初的硬件初始化,如设置 CPU 时钟、初始化内存控制器、配置堆栈指针等。
      • 跳转到 C 语言代码:完成最基本的硬件设置后,控制权会跳转到用 C 语言编写的第二阶段 Bootloader。
    • 第二阶段(C 语言阶段)
      • 进一步的硬件初始化:在 C 语言代码中,Bootloader 完成更多复杂的硬件初始化,如网络、串口、存储设备等外设的配置。
      • 引导过程:根据设定的启动命令和环境变量,Bootloader 加载操作系统内核和设备树,并将控制权交给操作系统。
U-Boot 概述
  • U-Boot(Universal Bootloader)是一款广泛用于嵌入式系统中的开源 Bootloader,支持多种 CPU 架构,如 ARM、PowerPC、x86、MIPS 等。它体积小、功能强大,广泛用于嵌入式系统启动和调试。
  • U-Boot 与其他 Bootloader 的对比:
    • BIOS(通常用于 PC):大约 300MB,功能强大,带有图形用户界面。
    • Recovery 模式(用于 Android):大约 3.5MB,功能一般,界面较为简单。
    • U-Boot:大约 200KB,功能足够,提供字符界面,非常适合嵌入式设备使用。
使用U-boot

U-Boot 提供了丰富的环境变量和命令,用于配置和控制启动过程。下面是 U-Boot 的常用功能和命令介绍:

  1. U-Boot 环境变量

    • U-Boot 通过环境变量存储启动时的配置信息,用户可以查看、修改和保存这些变量。
    baudrate=115200:串口通信的波特率设置。
    bootargs:Linux 内核的启动参数,指定文件系统的加载方式、控制台设置、IP 配置等。
    bootcmd:定义了 U-Boot 启动后要执行的命令序列,用于加载操作系统。
    ipaddr:开发板的 IP 地址。
    serverip:TFTP 服务器的 IP 地址。
    
  2. 设置环境变量

setenv 变量名 值

例如,设置 Linux 内核启动参数:

setenv bootargs root=/dev/nfs nfsroot=192.168.2.64:/home/ubuntu/source/rootfs rw console=ttySAC2,115200 init=linuxrc ip=192.168.2.245
//这条命令告诉 Linux 使用 NFS 作为根文件系统,并通过 UART2 输出控制台信息。
//ttySAC2 表示控制台将输出到串口2。
//bootargs 是一个特殊的环境变量,表示内核启动时的参数。
//root=/dev/nfs:指定根文件系统的路径。表示根文件系统位于一个 NFS(Network File System)服务器上,而不是在本地存储设备上.这意味着在系统启动时,它将通过网络挂载一个远程文件系统作为根文件系统。
//nfsroot 参数用于指定 NFS 服务器的IP地址和路径。
//rw:指定根文件系统为可读写(read-write)。这意味着启动后的系统能够对根文件系统进行写操作。
//init=linuxrc 指定启动时内核将运行的初始程序。
//ip=192.168.2.245:这个参数用于指定启动时设备的 IP 地址。
  1. 保存环境变量: 将修改后的环境变量保存到 Flash,这样环境变量在下次启动时依然有效。
saveenv
  1. U-Boot 命令集
printenv
setenv 变量名 值
saveenv
bootm//启动操作系统内核的命令,支持传递设备树文件和文件系统。
help//查看所有可用的 U-Boot 命令及其简要说明。
ping serverIP //测试网络连接(Ping 服务器 IP)
reset://重启开发板,可以通过该命令在 Bootloader 中进行软重启。
tftp 41000000 uImage
使用 TFTP 下载文件: U-Boot 支持通过 TFTP 协议下载文件,例如下载操作系统内核镜像 uImage 到 RAM 中的地址 41000000

bootm 41000000 - 42000000
启动内核(bootm 命令): bootm 命令用于启动操作系统。通常需要提供操作系统内核的地址、设备树文件(如果有)的地址。其中 41000000 是内核镜像的加载地址,42000000 是设备树的加载地址,- 表示没有提供文件系统镜像的地址。
U-Boot 启动过程的配置
  1. Bootargs 环境变量
    bootargs 是 U-Boot 传递给 Linux 内核的启动参数,定义了如何加载文件系统、IP 地址、控制台设置等。例如:
setenv bootargs root=/dev/nfs nfsroot=192.168.2.64:/home/ubuntu/source/rootfs rw console=ttySAC2,115200 init=linuxrc ip=192.168.2.245
  1. Bootcmd 环境变量
    bootcmd 定义了 U-Boot 启动后的操作步骤。
setenv bootcmd tftp 41000000 uImage;tftp 42000000 exynos4412-fs4412.dtb;bootm 41000000 - 42000000
//以下为解释
tftp 41000000 uImage:通过 TFTP 下载内核镜像到地址 41000000
tftp 42000000 exynos4412-fs4412.dtb:通过 TFTP 下载设备树文件到地址 42000000。
bootm 41000000 - 42000000:启动内核,并使用设备树文件。
  1. 常见的U-boot环境变量
  • bootdelay=1:设置启动延迟时间为 1 秒。
  • ethaddr:以太网 MAC 地址。
  • ipaddr:开发板的 IP 地址。
  • serverip:TFTP 服务器的 IP 地址。
  • netmask:子网掩码,通常为 255.255.255.0。
  • serverip:TFTP 服务器的 IP 地址,用于下载内核和其他启动文件。
  • fileaddr 和 filesize:这些变量用于指定下载文件的地址和大小。TFTP 下载完成后,U-Boot 会自动设置这些变量。
  • gatewayip:网关地址,通常在跨网络连接时使用。嵌入式系统中一般不需要配置网关。
  • stdin、stdout、stderr:定义标准输入、输出和错误的设备。通常这些值设置为 serial,表示通过串口进行输入输出操作。
  • Environment size:环境变量的大小,通常会有一个上限值(例如 16KB),并且会显示当前已使用的大小。
启动流程解读
  1. U-boot阶段
    • 启动 U-Boot,在开发板上上电启动后,U-Boot 启动并首先完成部分硬件的初始化。
    • 环境变量设置:根据设置的 bootargs 和 bootcmd,U-Boot 知道从哪里加载内核和文件系统
  2. TFTP 文件下载
    • 下载 uImage(内核镜像)
    • 下载设备树文件(.dtb 文件)
  3. 启动内核
    • 下载完成后,U-Boot 使用 bootm 命令启动内核,并传递设备树文件地址
  4. 加载根文件系统
    • 根据 bootargs 环境变量中的设置,Linux 内核知道通过 NFS 加载根文件系统
  5. 系统启动完成
  6. 注意事项
    • TFTP 和 NFS 的协同工作:
      • 在 U-Boot 中,TFTP 常用于加载内核镜像(uImage)和设备树文件(.dtb 文件),因为这类文件的体积较小,适合通过网络快速传输。
      • NFS 则用于加载较大的根文件系统,NFS 服务器能够提供一个远程文件系统,供 Linux 内核使用。这种方式在嵌入式开发中非常常见,因为不需要将文件系统写入 Flash,方便调试和更新。

u-boot的编译和移植

  • 从芯片原厂或开源社区获取,例如从官方获取 u-boot-2013.01 源码。
  • 在 Linux 环境下,使用以下命令解压 U-Boot 源代码
    tar -xvf u-boot-2013.01.tar.bz2
    
    • 解压完成后,将得到 u-boot-2013-xxx 目录,该目录为顶级目录,后续的所有操作(如编译、移植)都基于此目录进行。
U-Boot 目录结构说明
  • U-Boot 的源码目录结构非常清晰,分为 平台相关 和 平台无关 的部分。
平台相关的代码

这一部分包含与具体的硬件平台相关的代码,主要包括不同的 CPU 架构和开发板的实现代码。由于不同 CPU 和开发板的硬件资源和配置不同,U-Boot 针对每个平台都有对应的实现。

  • arch/arm/cpu/armv7
    • 这里是与 ARMv7 架构相关的代码,因为 fs4412 是基于 ARM Cortex-A9 的处理器,属于 ARMv7 架构。在这个目录中,你会看到与 ARMv7 架构的处理器相关的代码。
    • 包含处理器的初始化、时钟设置、缓存管理、启动等。
  • board/samsung/origen
    • 不同的开发板有各自的板级代码。由于 fs4412 是基于三星的芯片,因此对应的开发板代码在 board/samsung/origen 目录中。这个目录下的代码专门处理与 fs4412 硬件平台相关的初始化代码,如内存控制器、外设的初始化等。
  • include/configs/origen.h
    • 这个文件包含了与板子相关的配置宏。它定义了 fs4412 的硬件参数,如 DRAM 大小、串口波特率、启动设备等。
    • 通过修改这个文件,你可以调整板子启动时的一些行为,如 U-Boot 如何找到内核、如何配置网络等。
2.平台无关代码

这一部分代码与具体的硬件平台无关,提供了 U-Boot 的核心功能,如驱动程序、库、文件系统、网络协议等。无论你使用的是哪个平台,这些功能都是通用的。

  • api:与 API 调用相关的代码,U-Boot 提供了一些标准接口供开发者调用。
  • config.mk:U-Boot 编译时所使用的配置文件,定义了编译选项和目标文件等信息。
  • drivers:驱动程序目录,包含 U-Boot 所支持的各种硬件设备驱动,如串口、网卡、存储控制器等。
  • include:头文件目录,包含了 U-Boot 核心代码和硬件平台的头文件。
  • lib:与 U-Boot 核心库相关的代码,提供了标准的功能库。
  • fs:文件系统代码,支持各种嵌入式文件系统(如 FAT、EXT4、UBIFS 等),用于加载启动时的文件系统。
  • net:网络协议相关的代码,支持多种网络功能和协议,如 TFTP、DHCP、HTTP 等。
  • tools:编译和开发过程中所用的工具,包含一些 U-Boot 自身使用的工具程序。
  • dts:包含设备树(Device Tree Source)文件,设备树用于描述系统中的硬件配置。虽然设备树描述的是硬件信息,但设备树本身是一个通用机制,通常不依赖于特定的平台。
  • common/Makefile、rules.mk 和 config.mk:这些文件是 U-Boot 的编译系统文件,定义了如何编译 U-Boot 源代码。它们定义了各种目标和规则,以便在不同平台上编译 U-Boot。
  • 等待
u-boot的编译
  • U-Boot 的编译必须在 顶层目录 下进行,所有操作都基于这个目录进行。以下是完整的编译过程,包括配置、编译和清理步骤。
  1. 配置 U-Boot,指定板子
    在编译 U-Boot 之前,首先要为你的目标硬件板子(开发板)进行配置。这一步会根据板子生成相应的配置文件。
make <board_name>_config ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
//如果你当前系统是 x86_64,默认的 ARCH 是 x86 或 x86_64。
//ARCH=arm 表示你正在为 ARM架构 编译U-Boot。

//CROSS_COMPILE=arm-none-linux-gnueabi-  用于指定交叉编译器的前缀。
//交叉编译器是指在一种平台上编译运行于另一种平台的代码的编译器。
//构建系统将使用这个工具链来编译生成适合ARM架构的可执行文件和镜像。

针对Origen开发板的配置命令

make origen_config ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
  1. 编译 U-Boot
    在完成配置后,开始进行编译。编译的命令如下:
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-

这个命令会根据之前配置的板子生成 U-Boot 可执行文件。编译过程会创建多个中间文件(如 .o 文件),并在顶层目录生成最终的 U-Boot 二进制文件。

  1. 编译结果
    编译完成后,在 U-Boot 顶层目录下会生成以下文件
  • u-boot.bin:U-Boot 可执行文件,可以烧录到开发板的 Flash 或通过 TFTP 加载运行。
  • u-boot.img(可选):有些平台可能生成 u-boot.img,用于更加复杂的启动场景。
  • tools/mkimage:这是一个重要的工具,用于将内核、设备树、文件系统等打包成 U-Boot 可识别的格式。在后续操作中会用到这个工具来生成引导映像。
  1. 清理编译
    • make clean
      • 该命令会清除编译过程中生成的所有中间文件(如 .o 文件)。但它保留了配置文件(即第1步的板子配置),因此你不需要重新配置板子。
      • 适用于你已经配置好环境,之后只想重新编译的情况。
    • make distclean
      • 该命令会清除所有生成的文件,包括中间文件和配置文件。也就是说,执行 distclean 后,你需要重新配置板子才能再次编译。
      • 适用于你需要彻底清理整个项目环境的情况。
u-boot移植

目的:将支持 Origen 开发板的 U-Boot 源码移植到 fs4412 开发板,使其可以正确运行并支持该硬件平台。

移植步骤
  1. 拷贝 Origen 开发板的代码到 fs4412
    U-Boot 的板子相关代码通常位于 board/samsung 目录下,包含硬件初始化等与板子密切相关的代码。为了让 fs4412 开发板能够正确运行 U-Boot,需要基于现有的 Origen 代码进行修改。
    执行以下命令将 Origen 板子的代码复制到 fs4412 板子
cp -a board/samsung/origen board/samsung/fs4412
//将 board/samsung/origen 目录及其内容递归地复制到 board/samsung/fs4412 目录中。

将 origen.c 文件重命名为 fs4412.c,因为 fs4412 和 origen 板子的硬件配置不同,需要在代码中进行相应的调整:

mv board/samsung/fs4412/origen.c board/samsung/fs4412/fs4412.c

编辑 board/samsung/fs4412/Makefile,将其中的 origen.o 修改为 fs4412.o,以便编译时生成正确的对象文件

vi board/samsung/fs4412/Makefile
//将obj-y += origen.o改为obj-y += fs4412.o
  1. 拷贝配置文件并修改
    U-Boot 的配置文件通常位于 include/configs 目录下,每个开发板都有自己的配置文件,里面定义了诸如内存大小、串口设置、时钟配置等信息。我们需要复制 Origen 的配置文件,并为 fs4412 进行相应的修改。
    将 Origen 的配置文件复制为 fs4412 的配置文件
cp include/configs/origen.h include/configs/fs4412.h

接下来,需要打开 include/configs/fs4412.h,根据 fs4412 的硬件配置进行调整。常见的调整项包括:
- 内存配置(如 DRAM 大小)
- 串口配置
- 启动设备(如从 NAND、MMC 启动等)

  1. 指定fs4412板子的相关信息
  • 在 U-Boot 中,boards.cfg 文件用于管理所有支持的开发板。这个文件定义了每个开发板的基本信息,如板子的名字、CPU 类型、供应商等。为了让编译系统识别 fs4412 板子,需要在 boards.cfg 中添加 fs4412 的相关信息。
  • 编辑 boards.cfg,可以复制一行 Origen 板子的配置,然后修改其中的名字、CPU 和其他相关信息。
vi boards.cfg//打开并编辑 boards.cfg
//找到 Origen 的配置行
//复制该行并修改为 fs4412 的配置

在这里插入图片描述

  1. 编译U-boot
    配置完成后,就可以编译 U-Boot 了。
    1. 配置板子
      • 首先使用 make 配置命令,根据 boards.cfg 文件中的信息为 fs4412 板子生成相关配置文件。
    make fs4412_config ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
    
    1. 编译 U-Boot
    make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
    
  2. 清理编译(如果需要清理编译过程中生成的文件)
    • 清理中间文件(保留配置文件)
      • make clean
    • 彻底清理(该命令会清除所有生成的文件,包括配置文件。)
      • make distclean
  3. 将 U-Boot 编译生成的 u-boot.bin 烧录到开发板上。
U-Boot 代码的启动过程

U-Boot 的启动分为两个阶段:汇编阶段和C 语言阶段。每个阶段负责特定的初始化任务,最终进入交互模式等待用户命令或加载操作系统。

  1. 汇编阶段:基本硬件初始化
  • 汇编阶段的主要任务是完成 CPU 和基本硬件的初始化,然后跳转到 C 语言代码。这个阶段的代码位于 arch/arm/cpu/armv7/start.S 文件中,汇编代码是执行启动的最初步骤。
  • 汇编启动流程
    • 入口点:当开发板上电启动时,程序执行的入口是 start.S 文件中的 reset 标签。
    b reset
    
    • reset 标签:
      该标签定义了程序启动的核心初始化部分,主要负责 CPU 和关键硬件的初始化。
      在这里插入图片描述
    • cpu_init_cp15:
      此函数主要用于初始化 CPU 内部的一些配置,包括 MMU(内存管理单元)、RAM、禁用 CPU 缓存等。这个初始化对于 ARM 平台的正常运行至关重要。
    • cpu_init_crit:
      该函数负责初始化开发板相关的硬件。在这里可能会调用板子特定的低级初始化代码。例如,可能调用位于 board/samsung/origen/lowlevel_init.S 的 lowlevel_init 函数。
      虽然可以自定义这个初始化函数以支持不同的硬件,但通常不推荐直接修改该部分代码,最好通过 U-Boot 提供的其他接口进行硬件初始化。
    • b _main
      当汇编部分的初始化完成后,跳转到 C 语言部分进行更多的初始化工作。这个跳转是通过 b _main 完成的,_main 是 arch/arm/lib/crt0.S 中定义的入口点。
  1. C 语言阶段:系统初始化与交互模式
    当 U-Boot 从汇编阶段进入 C 语言阶段后,开始进行更高级的硬件初始化工作,包括调用板子相关的函数、进行自拷贝操作等,最终进入 U-Boot 的交互模式。
    C 语言启动流程
    • _main 函数
      • 位于 arch/arm/lib/crt0.S 文件中,_main 函数是 C 语言阶段的入口点。
      • 主要任务是设置堆栈、初始化板子特定的硬件,并最终启动交互模式。
    • board_init_f:
      • 该函数位于 arch/arm/lib/board.c 文件中,主要是初始化与板子相关的硬件资源。
      • 在这个过程中,会调用板子特定的初始化函数。例如,如果你的板子是 fs4412,系统会调用位于 board/samsung/fs4412/fs4412.c 中的函数。你可以在这些文件中进行板子级别的初始化。
      • 在 board_init_f 函数执行时,系统还没有完全从 Flash 自拷贝到 RAM,因此它的作用是设置好基本的外设,确保后续可以从 RAM 中运行。
    • 自拷贝
      • U-Boot 的自拷贝机制是在启动时将自身从 Flash 拷贝到 RAM 中。这是因为 RAM 的访问速度比 Flash 更快,运行效率更高。
      • 自拷贝操作通常在 C 语言阶段完成,完成后程序会跳转到 RAM 中继续执行。
    • board_init_r
      • 完成自拷贝后,程序会跳转到 board_init_r,这个函数位于 arch/arm/lib/board.c 中。它负责进一步的初始化工作,完成剩余的硬件初始化,并最终进入 U-Boot 的交互模式。
void board_init_r(gd_t *id, ulong dest_addr)
{
    // 其他初始化操作...
    for (;;)
    {
        main_loop();  // 进入 U-Boot 的命令行交互模式
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值