ARM & Linux 基础学习 / 配置交叉编译工具链 / 编译 Linux 应用和驱动 / 编译内核

这篇具有很好参考价值的文章主要介绍了ARM & Linux 基础学习 / 配置交叉编译工具链 / 编译 Linux 应用和驱动 / 编译内核。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

编辑整理 by Staok。

本文部分内容摘自 “100ask imx6ull” 开发板的配套资料(如 百问网的《嵌入式Linux应用开发完全手册》,在 百问网 imx6ull pro 开发板 页面 中的《2.1 100ASK_IMX6ULL_PRO:开发板资料》或《2.2 全系列Linux教程:在线视频与配套资料》里面可以下载到),还有参考 菜鸟教程、C语言中文网、红联的等等等等,比较广泛,侵删。进行了精髓提取,方便日后查阅。过于基础的内容不会在此提及。如有错误恭谢指出!

注:在 Github 上的原版文章日后可能会更新,在其它位置发的不会跟进。文章的 Gitee 仓库地址,Gitee 访问更流畅。

注意本文是基于 IMX6ULL 这个 SoC,即 A7 内核(ARM 各个内核介绍 【主线剧情 番外01】ARM 系列快速鸟瞰 - 欢迎来到 Staok - 瞰百易 (gitee.io)),本文所配置的交叉编译器也是对应的,即 ARMv7 32位,若是 i.mx8mm 这种基于 A53 内核的,就是对应 ARMv8 64位 的交叉编译器,要注意,不熟悉的仔细看一下~祝好

任何文章都有时效性,学习 linux 这种复杂系统,保持脑袋清醒和逻辑链清晰,耐下心来,共勉!

更全面的 Linux 应用 和 驱动编程,还见仓库 Github 仓库 或 Gitee 仓库 中,见里面相关的文件夹,东西真的很丰富~

Linux 驱动和应用的体验

Ubuntu 主机 的配置工作

  • 首先换源,参考前面 “换源 和 添加系统变量” 一节。

  • 配置 100ask Ubuntu 主机 的环境,执行:

    wget --no-check-certificate -O Configuring_ubuntu.sh https://weidongshan.coding.net/p/DevelopmentEnvConf/d/DevelopmentEnvConf/git/raw/master/Configuring_ubuntu.sh && sudo chmod +x Configuring_ubuntu.sh && sudo ./Configuring_ubuntu.sh

    这个会配置/安装一些基本应用如 NFS/TFTP 等,还建立 /home/book 目录,book 用户 等,具体看其 shell 程序。

  • 百问网的 imx6ull pro 开发板的 SDK包(包括 Linux、uboot、buildroot 等源码和工具链,这个需要 windows 电脑 和 虚拟机 ubuntu 各存一份,前者用来阅读,后者用来编译)两个下载途径:

    1. 本地拷贝法:百问网 imx6ull pro 开发板 页面,找到 100ask_imx6ull_pro_2020.02.29_v2.0(这个很大,网盘下载),里面有固件、SDK包、原理图(底板+核心板)、应用例程、工具软件等等。其中 SDK包(包括 Linux、uboot、buildroot 等源码和工具链)在 07_Bsp_sdk (系统源码,包含uboot kernel rootfs 工具链 测试代码等)) 里面,自行拷贝到虚拟机 ubuntu 里面并解压。但是这是本地拷贝的不是最新的,最新的可以 git 下载(注意很大),看下面 “在线下载&更新法”。

    2. 在线下载&更新法:参考 百问网的《嵌入式Linux应用开发完全手册》里面 第二篇 的 《8.2 使用repo获取内核及工具链等》 里面的 《8.2.2 在线下载》。

      1. 配置 Git 邮箱和用户名:git config --global user.email "user@100ask.com"git config --global user.name "100ask"

      2. 执行四条命令:

        git clone https://e.coding.net/codebug8/repo.git
        ​
        mkdir -p 100ask_imx6ull-sdk && cd 100ask_imx6ull-sdk
        ​
        ../repo/repo init -u https://gitee.com/weidongshan/manifests.git -b linux-sdk -m imx6ull/100ask_imx6ull_linux4.9.88_release.xml --no-repo-verify
        ​
        ../repo/repo sync -j4
      3. 今后可以直接在 100ask_imx6ull-sdk 目录下执行 ../repo/repo sync -c 进行同步更新最新代码!

  • 推荐在 windows 端 使用 Source Insight 来阅读 Linux 内核源码,详见 百问网的《嵌入式Linux应用开发完全手册》里面 第二篇 的《8.4 使用Source Insight阅读Linux内核源码》。

获取交叉编译工具链

这里提供三个获取方式。

用开发板厂家提供的 SDK 里的工具链

这里是 百问网的 imx6ull pro 开发板 的 SDK 中的工具链,在 /.../100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 里面,使用 Vim 工具编辑 ~/.bashrc 文件,在最后添加:

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/.../100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin

并在终端键入 source ~/.bashrc 使其生效。

然后在终端测试一下 arm-buildroot-linux-gnueabihf-gcc -v

ARM 官网下载 合适的工具链
  1. ARM GUN-A 官方编译器下载页面: GNU Toolchain | GNU-A Downloads – Arm Developer。下面几个连接是对各个编译器命名的说明,必看。

    • arm-linux-gnueabihf、aarch64-linux-gnu等ARM交叉编译GCC的区别_Namcodream521的博客-CSDN博客。

    • 转:ARM交叉编译工具链分类说明 arm-linux-gnueabi和arm-linux-gnueabihf 的区别_Beyoungbehappy的博客-CSDN博客。

    • arm交叉编译器gnueabi、none-eabi、arm-eabi、gnueabihf等的区别 - 涛少& - 博客园 (cnblogs.com)。

    • 带有 “bare-metal” 的为不支持操作系统的。

    • 总的来说:

      • 经过 Codesourcery 公司基于GCC优化,带有 none 标识的编译器。

        • ARM GUN-A 官方编译器下载页面 Downloads | GNU-A Downloads – Arm Developer。要下载的编译器要运行在 x86_x64 机器的虚拟机里面的ubuntu 18.04 里面,因此找到 x86_64 Linux hosted cross toolchains 下面的各个编译器版本。

        • AArch32 target with hard float (arm-none-linux-gnueabihf) —— 可用于交叉编译ARMv7 32位 目标系统中所有环节的代码,包括裸机程序、u-boot、Linux kernel、filesystem和App应用程序。

        • AArch64 GNU/Linux target (aarch64-none-linux-gnu) —— 可用于交叉编译ARMv8 64位目标中的裸机程序、u-boot、Linux kernel、filesystem和App应用程序。

      • 由 Linaro 公司基于GCC推出。

        • Linaro Releases 页面 Linaro Releases。

        • arm-linux-gnueabihf-gcc:可用于交叉编译ARMv7 32位 目标系统中所有环节的代码,包括裸机程序、u-boot、Linux kernel、filesystem和App应用程序。

        • aarch64-linux-gnu-gcc:可用于交叉编译ARMv8 64位目标中的裸机程序、u-boot、Linux kernel、filesystem和App应用程序。

  2. x86_64 Linux hosted cross compilers下面找到 AArch32 target with hard float (arm-none-linux-gnueabihf)(i.mx6ull 为 A7 内核,即为 32 位的 armv7 指令集),并下载;(AArch64 Linux hosted cross compilers下的编译器可以运行在 64位的 嵌入式板子 SoC 的 Linux 上);

  3. 使用 tar xvf 命令解压。

最后,添加环境变量。使用 Vim 工具编辑 ~/.bashrc 文件,在最后添加:

export ARCH=arm
export CROSS_COMPILE=arm-none-linux-gnueabihf-     # 添加名为 CROSS_COMPILE、ARCH 环境变量,写 makefile 用 make 工具编译的时候会用到
export PATH=$PATH:/<交叉编译器工具链的目录>/bin       # 交叉编译器工具链的 路径,可以直接在 shell 中 打编译器的名字来 执行编译器 bin 应用

并在终端键入 source ~/.bashrc 使其生效。

然后在终端测试一下 arm-none-linux-gnueabihf-gcc -v

使用交叉编译工具链编译程序产生 固件/应用 后,通过 “PC 与 嵌入式板 传输文件的方式汇总” 一节提供的方法,传给 嵌入式 linux 开发板,再执行,也许需要添加执行的权限:chmod +x <应用>

使用 Linaro GCC 编译器

p.s 这里作者没有试验,只是把说明放在这里。

正点原子的文章【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.14.3.1.2 小节里说到个别版本能编译通过但是不能运行,多换换版本试试。

到 Linaro Releases 下载适合的编译器,使用方法与上面类似。Linaro 的编译器对应的名字为 arm-linux-gnueabihf-

第一个应用

略,略略略~。(在 百问网 imx6ull pro 开发板 页面 中的《2.2 全系列Linux教程:在线视频与配套资料》里面的 \01_all_series_quickstart\04_嵌入式Linux应用开发基础知识\source 里面)

第一个驱动

注意:

  • 驱动程序用到 Linux 内核的 API,编译驱动程序之前要先编译内核。

  • 编译驱动时用的内核和嵌入式板子上运行的内核,要一致(不一致的话,不能正常安装 .ko 模块,强装会有意想不到的问题)。

  • 板子使用新编译出来的内核时,板子上原来的其他驱动也要更换为新编译出来的。

编译内核

不同的开发板对应不同的配置文件,配置文件位于内核源码 arch/arm/configs/ 目录。

在 Linux 源码目录里执行:

make mrproper
make xxx_imx6ull_defconfig
make zImage -j4
make dtbs

释义:

  • make mrproper 命令会删除所有的编译生成文件、内核配置文件(.config文件)和各种备份文件,所以几乎只在第一次执行内核编译前才用这条命令。make clean 命令则是用于删除大多数的编译生成文件,但是会保留内核的配置文件 .config,还有足够的编译支持来建立扩展模块。所以你若只想删除前一次编译过程的残留数据,只需执行 make clean 命令。总而言之,make mrproper 删除的范围比 make clean 大,实际上,make mrproper 在具体执行时第一步就是调用 make clean。

得到 内核文件 和 设备树文件 这两个文件:

arch/arm/boot/zImage
arch/arm/boot/dts/100ask_imx6ull-14x14.dtb
编译内核模块

在 Linux 源码目录里执行:

make ARCH=arm CROSS_COMPILE=<选择一个编译器,比如 Linaro 的 arm-linux-gnueabihf-> modules
sudo make ARCH=arm INSTALL_MOD_PATH=/home/book/nfs_rootfs modules_install # 编译出的模块 都装存到 /home/book/nfs_rootfs 下,自行更换

释义:

  • 第一条,如果设置好了 ARCH 和 CROSS_COMPILE 环境变量,直接键入 make modules 也可。

  • 第二条命令是把模块安装到 /home/book/nfs_rootfs 目录下备用 , 会得到 /home/book/nfs_rootfs/lib/modules 目录。

更新目标板

有很多种方式传输文件,详见 "PC 与 嵌入式板 传输文件的方式汇总" 章节。将 zImage 、100ask_imx6ull-14x14.dtb 和 内核模块的 lib 目录 这三者 分别放到嵌入式板子的 /boot 、 /boot 和 /lib 目录,比如使用方便的 nfs 文件系统;然后存储 sync,重启 reboot

编写、编译驱动

按照驱动程序的编写规则,写好驱动程序(hello_drv.c)和 对其进行编译的 Makefile 文件,以及 相应的 应用程序/测试程序(hello_drv_test.c)。

举例 Makefile 文件(这里面也同时将 测试程序 给编译了):

# 修改为 Linux 内核所在目录
KERN_DIR = /home/book/100ask_roc-rk3399-pc/linux-4.4
​
all:
    make -C $(KERN_DIR) M=`pwd` modules 
    $(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c # 这里就用到 环境变量 CROSS_COMPILE 了
​
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
    rm -f hello_drv_test
​
obj-m   += hello_drv.o

确保三个环境变量 ARCH、CROSS_COMPILE 和 PATH(交叉编译器的 /bin 目录)都以就绪。

  1. 执行 makemake all。产生 驱动程序的内核模块(hello_drv.ko)和 测试程序 ARM 端的二进制可执行文件,共两个文件,转移其到 嵌入式目标板子上。

  2. 在嵌入式 Linux 开发板上 安装驱动程序模块 insmod hello_drv.ko

  3. lsmod 命令下可以看到 hello_drv 模块;执行 cat /proc/devices 可以看到 对应的设备及其主设备号;执行 ls -l /dev/<设备名称> 可以看到此设备的主、此设备号等更多信息。

  4. 执行测试程序进行验证。

linux内核编译操作
make bzImage                    # 编译生成压缩的内核二进制文件
make vmlinux                    # 编译生成二进制内核文件
make modules                    # 编译生成内核模块
make modules_install            # 安装模块
make bzdisk|fdimage|isoimage    # 编译生成启动软盘镜像或者光盘镜像
make install                    # 安装内核文件
make all                        # 相当于vmlinux+modules+bzImage
make rpm                        # 构建内核rpm包
make foo/bar/foobar.ko          # 编译单个驱动
make header_install             # 安装内核头文件
make M=some/sub/dir             # 编译指定目录
make O=/path/to/some/dir        # 指定生成的文件放到该目录
make kernelversion              # 输出内核版本信息
make kernelrelease              # 输出内核发行标识
make rpm-pkg|deb-pkg|tar-pkg|targz-pkg|tarbz2-pkg   # 构建这种格式的内核包
make clean                      # 清除生成文件(保留.config和部分模块文件)
make mrproper                   # 清除全部文件(包括.config和备份文件)
make distclean                  # 在make mrproper上还清除编辑器其他的备份文件

学至此的一点启示

芯片厂家(大概)应该都会提供完整的 U-boot、 Linux 内核、芯片上硬件资源的驱动程序。

看韦东山的 imx6ull 板子的裸机开发源码,可以得知,启动文件 .s 文件需要看懂,都大同小异,然后官网会提供所有寄存器的 .h 文件及其结构体,然后每个外设似乎还会提供初始化、配置的代码(因为韦的源码里面,外设底层配置代码为英文注释的,99%的概率是官方提供的),这样就好了嘛,外设的底层驱动可以都扒官方例程。

构建系统简约步骤

这里只简约说明编译步骤,并非详细使用说明(以后的系列文章可能会有)。

每个部分单独手动简约步骤

以下工作进行前,先配置好环境变量和开发链工具等工作,详见 "准备交叉编译工具链" 章节。

1、编译 u-boot,配置文件位于 u-boot 源码的 configs/ 目录,生成 u-boot 启动镜像 u-boot-dtb.imx。在 Uboot 目录下执行:

make distclean
make mx6ull_14x14_evk_defconfig
make

2、编译内核,配置文件位于内核源码 arch/arm/configs/ 目录,生成 arch/arm/boot/zImage 内核文件 和 arch/arm/boot/dts/xxx_imx6ull-14x14.dtb 设备树文件。在 Linux 内核目录下执行:

Linux-4.9.88$ make mrproper
Linux-4.9.88$ make xxx_imx6ull_defconfig
Linux-4.9.88$ make zImage -j4
Linux-4.9.88$ make dtbs

3、编译内核模块,并把模块文件导入 /home/book/nfs_rootfs/lib/modules 目录。在 Linux 内核目录下执行:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
sudo make ARCH=arm INSTALL_MOD_PATH=/home/book/nfs_rootfs modules_install
使用 Buildroot 构建系统简约步骤

Linux 平台上有许多开源的嵌入式 linux 系统构建框架,这些框架极大的方便了开发者进行嵌入式系统的定制化构建,目前比较常见的有 OpenWrt, Buildroot, Yocto 等等。其中 Buildroot 功能强大,使用简单,而且采用了类似于 linux kernel 的配置和编译框架。

制作根文件系统方法比较:

  • Busybox。Busybox 本身包含了很了 Linux 命令,但是要编译其他程序的话需要手工下载、编译,如果它需要某些依赖库,你还需要手工下载、编译这些依赖库。如果想做一个极简的文件系统,可以使用 Busybox 手工制作。

  • Buildroot。它是一个自动化程序很高的系统,可以在里面配置、编译内核,配置编译 u-boot、配置编译根文件系统。在编译某些APP时,它会自动去下载源码、下载它的依赖库,自动编译这些程序。Buildroot 的语法跟一般的 Makefile 语法类似,很容易掌握。

  • Yocto。NXP、 ST 等公司的官方开发包是使用 Yocto,Yocto 语法复杂,容量大(10GB 以上),编译时间长。

Buildroot 是一组 Makefile 和补丁,可简化并自动化地为嵌入式系统构建完整的、可启动的 Linux 环境(包括 bootloader、 Linux 内核、包含各种 APP 的文件系统)。 Buildroot 运行于 Linux 平台,可以使用交叉编译工具为多个目标板构建嵌入式 Linux 平台。 Buildroot 可以自动构建所需的交叉编译工具链,创建根文件系统,编译 Linux 内核映像,并生成引导加载程序用于目标嵌入式系统,或者它可以执行这些步骤的任何独立组合。例如,可以单独使用已安装的交叉编译工具链,而 Buildroot 仅创建根文件系统。

学习更多关于 Buildroot 知识请参考这里。

扩展学习:

  • buildroot 下进入 menuconfig 包选择配置配置界面 make menuconfig

  • buildroot 下单独编译 u-boot make uboot-rebuild

  • buildroot 下进入内核 make menuconfig 配置选项界面 make linux-menuconfig

  • buildroot 下单独编译某个软件包 make <pkg>-rebuild

  • buildroot 下进入 busybox 配置界面 make busybox-menuconfig

  • buildroot 下生成系统 sdk,最后生成的目录在 output/images/ 目录下 make sdk

构建根文件系统:

在 Buildroot 目录下执行:

make clean
make xxx_imx6ull_defconfig
make all

漫长长长(2~6个小时,视电脑性能)的等待后编译完成。

可以配置多个不同的配置文件 xxx_imx6ull_defconfig,比如有的带 qt5 ,有的用于构建最精简的文件系统,有的用于另一块板子等待。

编译成功后文件输出路径为 output/images:

buildroot 20xx.xx
├── output
    ├── images
        ├── xxx_imx6ull-14x14.dtb             <--设备树文件
        ├── rootfs.ext2                       <--ext2 格式根文件系统
        ├── rootfs.ext4 -> rootfs.ext2        <--ext2 格式根文件系统
        ├── rootfs.tar
        ├── rootfs.tar.bz2                    <--打包并压缩的根文件系统,用于 NFSROOT 启动
        ├── sdcard.img                        <--完整的 SD 卡系统镜像
        ├── u-boot-dtb.imx                    <--u-boot 镜像
        └── zImage                            <--内核镜像

对应的文件更新到嵌入式板子的对应位置,或者使用 sdcard.img 或者 emmc.img 完整系统映像文件烧入 sd卡 或 emmc。文章来源地址https://www.toymoban.com/news/detail-759373.html

到了这里,关于ARM & Linux 基础学习 / 配置交叉编译工具链 / 编译 Linux 应用和驱动 / 编译内核的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 交叉编译工具链arm-linux-gnueabihf的安装-ubuntu 20.04

    http://t.csdn.cn/ZbjFX 建议直接在.bashrc文件作修改 ,修改方式相同 ( vi :视自己的编辑器而定) 因为我在修改profile文件后,环境变量生效,但是命令行的用户名等颜色高亮显示会消失;并且重启终端后,又需要再source一下profile。 但是将环境变量添加至.bashrc则不会出现这两个问题

    2024年02月11日
    浏览(49)
  • 下载较老版本或最新版本的ARM Linux gcc 交叉编译工具链

    如果开发的 ARM 平台比较的多,需要多个版本的 arm gcc 交叉编译工具链,那么如何获取较新版本的 arm gcc 交叉编译工具链呢? 速度较快的,也比较新的,就到 ARM 官方网站下载 下载地址: https://developer.arm.com/downloads/-/gnu-a GNU-A Downloads 最新的下载地址: https://developer.arm.com/do

    2024年02月14日
    浏览(49)
  • Linux-Arm环境下配置编译qt-everywhere及交叉编译环境

    最近在搞交叉编译的事,手上拿了个同事的香橙派玩交叉编译,现在来到了第一步,就是先在arm上配置qt的开发环境。当然了Qt没有直接提供qt on arm,而是需要自行在arm环境下编译一个qt环境出来,所以这里需要使用到qt提供的qt everywhere套件在板载系统上编译 移植Qt到ARM平台及

    2024年04月11日
    浏览(48)
  • ARM架构Linux操作系统上C语言版本的CycloneDDS中HelloWorld的编译过程(交叉编译工具链)

    ARM架构Linux操作系统上C语言版本的 CycloneDDS 中 HelloWorld 的编译过程与源码编译过程中的 roundtrip 示例编译是一样的,因为实在ARM架构上,所以需要将 idlc_generat e注释掉,自己手动添加源代码和头文件,之后在进行 cmake 和 make 操作

    2024年02月15日
    浏览(41)
  • ARM交叉编译工具链 gcc-arm-none-eabi

    1,交叉编译工具链简介 (1)命令规则 交叉编译工具链的命名规则为:arch [-vendor] [-os] [-(gnu)eabi] arch – 体系架构ÿ

    2024年03月25日
    浏览(56)
  • Linux——ARM交叉编译环境搭建

    可依据自己当前的环境进行选择下载,官网如下​​​ Downloads | GNU-A Downloads – Arm Developer 我这边选择如下,用于4412开发板的 gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf.tar.xz mkdir  /usr/local/arm tar -xvf gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf.tar.xz 在 vim /root/.bashrc 或 vim /etc/profi

    2024年02月21日
    浏览(50)
  • ARM_Linux的交叉开发以及交叉编译器

    目录 为什么要使用交叉开发 为什么要使用交叉编译 交叉编译器的安装 交叉编译器的使用 交叉开发是指在通用的电脑上吧程序编写,编译,调试好,再下载到嵌入式产品中去运行,对于一些简单的程序的话,直接在电脑上编译调试好即可,但是对于一些需要操作硬件的开发

    2024年01月23日
    浏览(48)
  • 【交叉编译环境】安装arm-linux交叉编译环境到虚拟机教程(简洁版本)

    就是看到了好些教程有些繁琐,我就写了一个 我这个解压安装的交叉编译环境是Linaro GCC的一个版本,可以用于在x86_64的主机上编译arm-linux-gnueabihf的目标代码 步骤来了 在你的Ubuntu系统中 创建一个目录 ,例如/usr/local/arm,然后将下载好的gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueab

    2024年02月03日
    浏览(39)
  • Ubuntu20.4配置arm交叉编译环境

    我是在虚拟机中配置的,如果你的嵌入式设备足够完成自己的编译,可以不考虑虚拟机的。 新安装的Ubuntu20.04系统请先执行以下代码 到aarch64下载对应的aarch64的base镜像。 之后执行如下命令,创建armsys文件夹,之后将刚下载的镜像拷贝到该文件夹下并且解压 安装一些必要的软

    2024年02月06日
    浏览(50)
  • 使用CMake交叉编译Arm Linux程序

    注意,工具链文件的指定一定要紧跟cmake命令之后,不能放到 … 后面构建arm架构cmake 工程里新建一个文件叫arm_linux_setup.cmake arm_linux_setup.cmake内容如下

    2024年02月03日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包