ARM 基础学习记录 / ARM 裸机编程

汇编程序调用 C 程序详情

在 C 程序和 ARM 汇编程序之间相互调用时必须遵守 ATPCS 规则,其是基于 ARM 指令集和 THUMB 指令集过程调用的规范,规定了调用函数如何传递参数,被调用函数如何获取参数,以何种方式传递函数返回值。

  1. 寄存器 R0~R15 在 ATPCS 规则的使用

    • 在函数中,通过寄存器 R0~R3 来传递参数,被调用的函数在返回前无需恢复寄存器 R0~R3 的内容。

    • 在函数中,通过寄存器 R4~R11 来保存局部变量。

    • 寄存器 R12 用作函数间 scratch 寄存器。

    • 寄存器 R13 用作栈指针,记作 SP ,在函数中寄存器 R13 不能用做其他用途,寄存器 SP 在进入函数时的值和退出函数时的值必须相等。

    • 寄存器 R14 用作链接寄存器,记作 LR ,它用于保存函数的返回地址,如果在函数中保存了返回地址,则 R14 可用作其它的用途。

    • 寄存器 R15 是程序计数器,记作 PC ,它不能用作其他用途。

  2. 汇编程序向 C 程序函数传递参数

    • 当参数小于等于 4 个时,使用寄存器 R0~R3 来进行参数传递。

    • 当参数大于 4 个时,前四个参数按照上面方法传递,剩余参数传送到栈中,入栈的顺序与参数顺序相反,即最后一个参数先入栈。

  3. C 程序函数返回结果给汇编程序

    • 结果为一个 32 位的整数时,通过寄存器 R0 返回。

    • 结果为一个 64 位整数时,通过 R0 和 R1 返回,依此类推。

    • 结果为一个浮点数时,通过浮点运算部件的寄存器 f0,d0 或 s0 返回。

    • 结果为一个复合的浮点数时,通过寄存器 f0-fN 或者 d0~dN 返回。

    • 对于位数更多的结果,通过调用内存来传递。

  4. 当 C 程序从一个函数跳转到另一个函数时,会先把源函数的 CPU 的寄存器和函数内的局部变量都入栈,当跳回时再出栈,这一过程的汇编代码是当 C 程序编译成汇编时被编译器自动添加。


imx6ull 裸机编程相关

这里是处理器启动流程等的介绍,属于科普环节,有个印象,会加深对于处理器如何运行的理解,非必要记住,而是为以后的操作说明每一个步骤都在做什么事情。此部分理解为主。

裸机映像文件合成详情

先说原理,看 imx6ull 芯片手册可知,芯片上电时内部的 boot ROM 固化的程序会通过外部引脚确定启动方式(USB\NAND\EMMC\SD等),将应用的二进制数据(app.bin)从存储区(NAND\EMMC\SD等)搬运到内存区(DDR2\3等),然后跳转到内存区的程序处开始执行程序。这个过程是这个芯片自动完成的,但是需要根据规定合成烧录到存储区的映像文件, 在编译得到应用的二进制文件 app.bin(这个就是比如 裸机应用固件 或 Linux 固件等)之后,再用 mkimage 工具(gcc-arm-linux-gnueabihf-6.2.1 编译器自带的)根据 imximage.cfg.cfgtmp 这个文件的信息,合成头部信息,再与 app.bin 组合生成 .imx 文件, .imx 的头部再添加 1KB 的数据(可以全为0,也可包含分区表等数据) 组合生成 .img 文件,具体如下:

  • .imx 文件 = 头部信息( IVT + Boot data + DCD) + app.bin -> 用于在烧写工具中烧写到 EMMC 中,烧写工具会自动将其烧写到 1KB 偏移处。

  • .img 文件 = 1k.bin + .imx 文件 = 1k.bin + 头部信息( IVT + Boot data + DCD) + app.bin -> 用于在烧写工具中烧写到 SD 中,烧写工具会将其烧写到 0 位置处(对与 SD 的烧写,此工具不会自动加 1KB 偏移...)。

头部信息包含了指示 boot ROM 程序要把 app.bin 数据搬运到内存的何处,其大小,以及包含了配置 DDR 的寄存器、引脚等数据等待,具体如下:

  • IVT:Image vector table,含 header(含 tag、length、version,这 3 项,length 表示 IVT 的大小)、entry(指示 app.bin 在内存中的位置,即程序数据被复制到内存哪里)、dcd(指示 DCD 数据 在内存中的位置)、boot_data(指示 Boot data 在内存中的位置)、self(指示 IVT 在内存中的位置)等,共占 32*8bit 大小,entry 为 app.bin 要在内存中的目的地址。

  • Boot data:start(映像文件在内存中的地址,为 IVT 在内存中的绝对地址减去 1024 偏移)、length(整个映像文件的长度,含 1k.bin)、plugin,共占 32*3bit 大小。

  • DCD:配 imx6ull 芯片的寄存器,如 DDR 的配置等,可自定,复杂,mkimage 根据 imximage.cfg.cfgtmp 这个文件的信息合成。

    其中,entry(指示 app.bin 在内存中的位置,即程序数据被复制到内存哪里)的地址在 Makefile 中调用 mkimage 工具时是可以指定的,在"重定位"章节会细说。

具体分布:

  • 头部数据和偏移区使用 mkimage 工具生成,官方都会提供的。

  • 最前面的灰色部分就是偏移数据区,对于EMMC/SD存储区设备是 1KB,对于 NAND 是256B,具体看手册。

最终生成的 .img 文件结构:

imx6ull 上电启动过程分析:

  1. boot Rom 会把 EMMC 或 SD 卡的前 4K 数据(涵盖了头部信息( IVT + Boot data + DCD)这些等)读入到芯片内部 RAM 运行。

  2. boot Rom 根据 DCD 进行初始化 DDR。

  3. boot Rom 根据 IVT,从 EMMC 或 SD 卡中将 app.bin 读到 DDR 的 0x80100000 地址(IVT 的 entry,如上图所示)。

  4. 跳转到 DDR 的 0x80100000 地址执行,即 CPU 开始从内存 0x80100000 地址开始执行机器码。

    以上步骤执行完之后的 DDR 内存图示:(这是反汇编 应用固件 产生的 机器码-汇编码 相互对应的内容)

重定位、启动和编译

各段数据重排序

每一个汇编成机器码的 .o 文件都会分为这几个数据段:

  • 代码段(.text):存放代码指令;

  • 只读数据段(.rodata):存放有初始值并且 const 修饰的全局类变量;

  • 数据段(.data):存放有初始值的全局类变量(有非零初始值的变量,如 char A = 'A';);

  • 零初始化段(.bss):存放没有初始值或初始值为0的全局类变量(如 int g_intA = 0;int g_intB;,这些存放在 .bss 段);

  • 注释段(.comment):存放注释。

在 Makefile 文件中,在链接步骤,通过 LD 工具,把各个 .o 文件的各个数据段,按照 imx6ull.lds 定义的顺序安放,即各段数据重排序,最后合成一个二进制文件 app.bin,其中的代码段(.text)、只读数据段(.rodata)和数据段(.data)等都来自于前面各个 .o 文件,每个段 的顺序按照 imx6ull.lds 安放。

链接脚本 imx6ull.lds 解析(一体式链接脚本格式):

SECTIONS {
    . = 0x80100000;                      //设定链接地址为0x80100000
​
    . = ALIGN(4);                        //将当前地址以4字节为标准对齐
    .text      :                         //创建段,其名称为 .text
    {                                    //.text包含的内容为所有链接文件的数据段
        *(.text)                         // *:表示所有文件
    }
​
    . = ALIGN(4);                        //将当前地址以4字节为标准对齐
    .rodata : { *(.rodata) }             //.rodata存放在.text之后,包含所有链接文件的只读数据段
​
    . = ALIGN(4);
    .data : { *(.data) }                 //.data存放在.rodata之后,包含所有链接文件的只读数据段
​
    . = ALIGN(4);
    __bss_start = .;                     //将当前地址的值存储为变量__bss_start
    .bss : { *(.bss) *(.COMMON) }        //.bss存放在.data段之后, 包含所有文件的bss段和注释段
    __bss_end = .;                       //将当前地址的值存储为变量__bss_end
}
​

可见 imx6ull.lds 文件给出 .bss 段的头、尾地址标识:__ bss_start__ bss_end

启动文件程序

以最简单的裸机点灯程序的启动文件 start.S 为例。仅为示例,过于简单,完整示例可看 下面 “ARM异常处理 & 启动文件的示例” 一节。

.text
.global  _start
_start:                 
    /* 设置栈地址 */
    ldr  sp,=0x80200000
    bl main
​
halt:
    b  halt
Makefile 文件解析

以最简单的裸机点灯程序的 makefile 为例。

PREFIX=arm-linux-gnueabihf-
CC=$(PREFIX)gcc
LD=$(PREFIX)ld
AR=$(PREFIX)ar
OBJCOPY=$(PREFIX)objcopy
OBJDUMP=$(PREFIX)objdump
​
led.img : start.S  led.c main.c
    $(CC) -nostdlib -g -c -o start.o start.S                 # 把启动文件 .s 和各个 .c 文件都汇编为机器码文件 .o
    $(CC) -nostdlib -g -c -o led.o led.c    
    $(CC) -nostdlib -g -c -o main.o main.c  
    
    $(LD) -T imx6ull.lds -g start.o led.o main.o -o led.elf  # 链接,按照 imx6ull.lds 定义的格式,各段数据重排序,把各个 .o 文件组成 .elf 文件
    
    $(OBJCOPY) -O binary -S led.elf  led.bin                 # .elf 转为 .bin 二进制文件,应用二进制文件
    $(OBJDUMP) -D -m arm  led.elf  > led.dis    
    mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d led.bin led.imx
                                                             # 使用 mkimage 生成 头部数据,并与 .bin 组合,产生 .imx 文件
    dd if=/dev/zero of=1k.bin bs=1024 count=1                # 创建一个 1KB 的空文件 1k.bin
    cat 1k.bin led.imx > led.img                             # 把 1k.bin 放在 .imx 前头,组合成 .img 文件
​
clean:
    rm -f led.dis  led.bin led.elf led.imx led.img *.o
​
清零 bss 段

在 启动文件 汇编程序中,根据 .bss 段的头、尾地址(__ bss_start__ bss_end)来对此区域清零,让 C 程序中未定义初始值或零初始值的变量在初始化时都为零值,而非随机值。

附程序:

clean_bss:
    ldr r1, =__bss_start    @ 将链接脚本变量__bss_start变量保存于r1
    ldr r2, =__bss_end      @ 将链接脚本变量__bss_end变量保存于r2
    mov r3, #0
clean:
    strb r3, [r1]           @ 将当前地址下的数据清零
    add r1, r1, #1          @ 将r1内存储的地址+1
    cmp r1, r2              @ 相等:清零操作结束;否则继续执行clean函数清零bss段
    bne clean
    
    mov pc, lr

并在进入主函数前调用 bl clean_bss /* 清零bss段 */

数据段再单独重定位

事出有因,想要把 .data 段的数据放到 片内内存中以加快访问速度,参考芯片手册得到片内RAM的地址为:0x900000 ~ 0x91FFFF,共128KB(当然不会很大,也就裸机下的编一编、学一学行,Linux 系统等的大型工程就不适合了),所以我们将 .data 段重定位后的地址设置为0x900000。

第一步:把链接脚本 imx6ull.lds 中的 .data : { *(.data) }换成下面的:

     data_load_addr = .;                    
     .data 0x900000 : AT(data_load_addr) 
     {
       data_start = . ;                  //addr = 0x900000
       *(.data)
       data_end = . ;                    //addr = 0x900000 + SIZEOF(.data)
     }

第二步:在启动文件中,复制 data 段数据到片内内存 data_start

 copy_data:
      /* 重定位data段 */
      ldr r1, =data_load_addr     /* data段的加载地址, 从链接脚本中得到, 0x8010xxxx */
      ldr r2, =data_start        /* data段重定位地址, 从链接脚本中得到, 0x900000 */
      ldr r3, =data_end          /* data段结束地址, 从链接脚本中得到,0x90xxxx */
 cpy:
      ldr r4, [r1]              /* 从r1读到r4 */
      str r4, [r2]              /* r4存放到r2 */
      add r1, r1, #4           /* r1+1 */
      add r2, r2, #4           /* r2+1 */
      cmp r2, r3               /* r2 r3比较 */
      bne cpy                  /* 如果不等则继续拷贝 */
​
      mov pc, lr               /* 跳转回调用copy_data函数之前的地址 */

并在进入主函数前调用 bl copy_data /* 复制 data 段数据到片内内存 data_start */

100ask imx6ull 的 《IMX6ULL裸机开发完全手册》中的 "第13篇 IMX6ULL裸机开发 - 9.4.3 总结:如何在C函数中使用链接脚本变量" 章节讲了如何在 C 程序中调用链接脚本中的表示地址的变量,从而可以在 C 程序中实现 "清零 bss 段"和"数据段搬运到片内内存",而不用在启动代码里完成这些操作。

100ask imx6ull 的 《IMX6ULL裸机开发完全手册》中的 "第13篇 IMX6ULL裸机开发 - 9.5 重定位全部代码" 章节讲了将全部应用的二进制数据搬到芯片的内部内存(128KB),并在其内运行,并且使用 C 程序实现 bss 段清零。其步骤是:第一步,修改链接脚本,段顶位置加上 . = 0x900000;,并加上头、尾的地址标识字符;第二步,在 C 程序中利用头、尾的地址标识字符将其间的数据搬运到芯片内部内存地址;第三步,修改启动文件汇编程序,跳转到内部内存的应用数据处执行。

修改应用在内存中的存放地址

IVT 中的 entry(指示 app.bin 在内存中的位置,即程序数据被复制到内存哪里)的地址在 Makefile 中调用 mkimage 工具时是可以指定,需要改相关联的几个地方如下:

假设应用的二进制数据(app.bin)原来是要存放在内存的 0x80100000 位置,现在要改为 app_address 处。

  1. Makefile 文件中修改 -e 选项后的地址 mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d relocate.bin relocate.imx

  2. 链接脚本 imx6ull.lds 中 SECTIONS { . = 0x80100000;... 此处改为 app_address 。

  3. 启动文件 start.S 内,要修改栈地址 sp,ldr sp,=0x80200000 此处根据 app_address 与 0x80100000 的偏移相应修改,对于小的裸机程序,可以至少比 app_address 大 0x00100000。

100ASK IMX6ULL Flashing Tool 工具使用

  • 通过 USB 运行裸机程序(不需要烧写,通过u-boot直接在内存中运行):

    板子设到 USB 启动,在 100ask_imx6ull_flashing_tool 工具中的“专业版”界面,打开 .imx 文件,直接点运行。

  • 通过 USB 烧写裸机程序:

    板子设到 USB 启动,在 Tool 中的“基础版”界面,若选 EMMC ,则用 .imx 文件,若选 SD ,则用 .img 文件。成功后,断电,切到 EMMC 或 SD 启动模式,再上电。

    或者在 win 上,用 win disk imager 工具,把 .img 文件写到 SD 卡。

  • 基础版界面详情:

按钮作用
烧写整个系统“选择设备”为EMMC时,把emmc.img烧到EMMC上; “选择设备”为SD/TF时,把sdcard.img烧到SD/TF卡上; “选择设备”为NAND时,把rootfs.ubi烧到Nand Flash上; 并且会烧写对应的U-Boot,请看下面的“更新Uboot”按钮说明。
更新内核把zImage上传到根文件系统的/boot目录 (对于Nand,是直接烧到内核分区)
更新设备树把100ask_imx6ull-14x14.dtb上传到根文件系统的/boot目录 (对于Nand,是直接烧到设备树分区)
更新Uboot对于IMX6ULL全功能版: ①“选择设备”为EMMC时,把u-boot-dtb.imx烧写到EMMC ②“选择设备”为SD/TF时,把u-boot-dtb.imx烧写到SD/TF卡 对于IMX6ULL mini nand版: ①“选择设备”为NAND时,把u-boot-dtb_nand.imx烧写到Nand Flash ②“选择设备”为SD/TF时,把u-boot-dtb_nandsd.imx烧写到SD/TF卡
烧写裸机把所选裸机文件,烧写到EMMC、SD/TF卡或Nand Flash
上传文件把所选用户文件,上传到根文件系统的/目录 对于imx6ull mini nand版,无法上传文件(只支持ext4文件系统,而它不是)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/131692.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

95. 费解的开关

题目 思路 因为最优解是每个灯只操作一次所以顺序无所谓只要确定了第一行后&#xff0c;下面都可以确定当前灯不亮就操作它下面的格子即可点亮它我觉得这种方法是唯一不会互相干扰的方法还是不太理解… 代码 #include <cstdio> #include <cmath> #include <c…

2023最新版本 从零基础入门C++与QT(学习笔记) -1- C++输入与输出

&#x1f38f;说在前面 &#x1f388;我预计是使用两个月的时间玩转C与QT &#x1f388;所以这是一篇学习笔记 &#x1f388;根据学习的效率可能提前完成学习,加油&#xff01;&#xff01;&#xff01; 输入(代码如下方代码块) &#x1f384;分析一下构成 &#x1f388;…

Rocksdb LSM Tree Compaction策略

RocksDB读写简介 直接画图说明。这张图取自Flink PMC大佬Stefan Richter在Flink Forward 2018演讲的PPT&#xff0c;笔者重画了一下。 RocksDB的写缓存&#xff08;即LSM树的最低一级&#xff09;名为memtable&#xff0c;对应HBase的MemStore&#xff1b;读缓存名为block cac…

CV计算机视觉每日开源代码Paper with code速览-2023.11.8

精华置顶 墙裂推荐&#xff01;小白如何1个月系统学习CV核心知识&#xff1a;链接 点击CV计算机视觉&#xff0c;关注更多CV干货 论文已打包&#xff0c;点击进入—>下载界面 点击加入—>CV计算机视觉交流群 1.【基础网络架构】&#xff08;WACV2024&#xff09;SBCFo…

Vue3-ref函数、reactive函数的响应式

Vue3-ref函数、reactive函数的响应式 在这之前&#xff0c;先讲Vue2的响应式处理 Vue2原本使用的是Object.defineProperty的响应式处理方式 methods方法中的this.name指的是vm.namereturn的name属性在通过this.name的间接调用时&#xff0c;通过了Object.defineProperty响应式…

火山引擎公共云·城市分享会:共享云经验,一起向未来

数智化时代的来临&#xff0c;不仅激发了行业对云计算的资源需求&#xff0c;也重构了云计算的技术架构及产品布局&#xff0c;给业务场景带来更多可能性&#xff0c;让云计算成为企业走向高效治理的一剂“良方”。随着业务的多样化、复杂化&#xff0c;企业应该如何借助云计算…

极兔面试:微服务爆炸,如何解决?Uber 是怎么解决2200个微服务爆炸的?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 谈谈你的DDD落地经验&#xff1f; 谈谈你对DDD的理解&#x…

【ubuntu 快速熟悉】

ubuntu 快速熟悉 2.ubuntu桌面管理器3.ubuntu常见文件夹说明4.ubuntu任务管理器4.1 gnome桌面的任务管理器4.2 实时监控GPU4.3 top 命令 5.ubuntu必备命令5.1 .deb文件5.2 查找命令5.2.1 find文件搜索5.2.2 which查找可执行文件的路径5.2.3 which的进阶&#xff0c;whereis5.2.…

Linux学习教程(第一章 简介)2

第一章 Linux简介 四、类UNIX系统是什么鬼? 《三、UNIX和Linux的区别》中讲到了 UNIX 系统的历史,UNIX 是操作系统的开山鼻祖,是操作系统的发源地,后来的 Windows 和 Linux 都参考了 UNIX。 有人说,这个世界上只有两种操作系统: UNIX 和类 UNIX 操作系统;其它操作系统…

解决:Git报错same change IDs

当使用git review的时候&#xff0c;review失败&#xff0c;报错multi commit …same change ids。。 错误&#xff1a; same Change-Id in multiple changes 意思是说&#xff0c;有多个commit记录的change ids是相同的&#xff0c;这change id概念出现在gerrit&#xff0…

数电发票接口服务商怎么选择?

自2023年11月1日起&#xff0c;除了港澳台、西藏外&#xff0c;全国范围内都开展了数电票开票试点&#xff0c;对于那些已经习惯使用传统税控开票接口的企业&#xff0c;如今在数电发票的试点下&#xff0c;原本的税控开票接口如同老去的侠客&#xff0c;曾经的荣光已经不再。在…

Zynq-Linux移植学习笔记之65- 国产ZYNQ在linux下usleep时间精度不准问题解决

1、背景介绍 采用复旦微的ZYNQ&#xff0c;跑linux操作系统&#xff0c;在应用程序中使用usleep进行延时时&#xff0c;发现存在10ms以下采用usleep试验都为10ms的情况 2、解决办法 使能设备树中的PS TTC设备&#xff0c;默认不是打开的 timere0024000 {compatible "s…

Android—幸运抽奖火箭发射倒计时(第六次作业)

Android—幸运抽奖&&点火发射&#xff08;第六次作业&#xff09; 创建项目 准备工作 修改按钮的颜色&#xff0c;如果不修改这行代码&#xff0c;那么后期给按钮添加background属性的时候&#xff0c;按钮并不会发生变化。 设置按钮的样式文件btn_press_blue.xml&am…

【原创分享】Mentor PADS将PCB封装直接添加到PCB的教程

一般&#xff0c;批量添加封装到PCB板上有以下方法&#xff1a; 第一步&#xff1a;点击菜单栏“ECO模式--添加元器件”如图&#xff0c;点击以后弹出如图界面。 1&#xff09;元件类型 PCB封装必须得添加完元件类型&#xff0c;才能通过ECO模式添加到PCB界面里面&#xff0c…

北斗卫星为油气行业发展注入新动力

北斗卫星为油气行业发展注入新动力 北斗卫星是中国自主研发的卫星导航系统&#xff0c;在全球范围内具有广泛应用。随着科技的进步和社会的发展&#xff0c;北斗卫星的智慧应用也逐渐在各行各业中崭露头角。特别是在油气行业&#xff0c;北斗卫星的智慧应用发挥了非常重要的作用…

elemetui 解决同个页面,同时使用多个el-table表格组件导致的数据错乱

1、背景 在一个页面中&#xff0c;使用了饿了么框架的3个el-table表格&#xff0c;3个表格平级&#xff0c;只不过是根据条件判断渲染哪个表格。本来以为使用v-if就可以隔离&#xff0c;没想到还是出现了问题&#xff0c;因为3个表格中有几列绑定的字段一模一样&#xff0c;导…

Python编程必备:itertools库功能全解析!

大家好&#xff0c;我是涛哥&#xff0c;今天为大家分享itertools库常用功能&#xff0c;并且为大家提供示例&#xff0c;全文5000字&#xff0c;大约阅读15分钟。 什么是 itertools&#xff1f; itertools是Python的一个内置模块&#xff0c;它提供了一系列用于迭代的函数&…

动态规划:918. 环形子数组的最大和

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《算法》 文章目录 前言一、题目解析二、解题思路解题思路状态表示状态转移方程初始化填表顺序返回值 三、代码实现总结 前言 本篇文章仅是作为小白的我的一些理解&#xff0c;&#xff0c;…

PyGWalker :数据分析中最优秀工具库!

假设你在 Jupyter Notebook 中有一堆数据需要分析和可视化。PyGWalker 就像一个神奇的工具&#xff0c;使这一切变得非常容易。它接受你的数据并将其转换成一种特殊的表格&#xff0c;你可以像使用 Tableau 一样与之交互。 你可以通过视觉方式探索数据&#xff0c;进行互动&am…

字符串的模式匹配(朴素模式匹配算法,KMP算法)

目录 1.朴素模式匹配算法1.定义2.算法实现3.代码实现 2.KMP算法1.优化思路2.next数组3.代码实现 3.求next数组4.KMP算法优化1.next数组的优化2.求nextval数组 1.朴素模式匹配算法 子串&#xff1a;主串的一部分&#xff0c;一定存在。 模式串&#xff1a;不一定能在主串中找到。…