news 2026/7/19 1:56:01

深入解析MMC/SD/SDIO主机控制器驱动:从初始化到数据传输实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入解析MMC/SD/SDIO主机控制器驱动:从初始化到数据传输实战

1. 项目概述:深入MMC/SD/SDIO主机控制器的驱动核心

在嵌入式系统开发中,存储和扩展接口的驱动是连接硬件与应用的关键桥梁。无论是运行在智能手表上的轻量级操作系统,还是处理海量数据的工业网关,都离不开稳定、高效的存储访问。MMC、SD以及SDIO,这三者构成了嵌入式领域最主流的存储与扩展接口标准。你可能每天都在使用基于这些标准的设备——从手机里的eMMC存储芯片,到相机中的SD卡,再到通过Wi-Fi SDIO模块联网的物联网设备——但其背后,主机控制器如何被精确地“唤醒”并投入工作,却是一套严谨而复杂的流程。

很多开发者初次接触这类驱动时,往往会感到困惑:数据手册上密密麻麻的寄存器描述、看似循环依赖的初始化步骤、以及各种超时和错误状态,让人望而却步。实际上,驱动一个MMC/SD/SDIO主机控制器,本质上是在与一个高度可配置的硬件状态机进行对话。你需要按照它规定的“语言”(寄存器配置)和“礼仪”(操作序列),一步步引导它从复位状态进入稳定工作状态,最终才能可靠地指挥存储卡或IO设备进行数据传输。

本文将以德州仪器(TI)OMAP/AM系列处理器中常见的MMCHS(MMC/SD/SDIO Host Controller)控制器为例,剥茧抽丝,详细解析从硬件上电到能够进行数据读写的完整初始化流程与基本编程模型。我不会仅仅罗列寄存器地址和数值,而是会结合我多年在嵌入式存储驱动调试中的实际经验,重点解释每一个步骤背后的设计意图、常见陷阱以及调试技巧。无论你是在为一块新的SoC移植驱动,还是试图优化现有驱动的稳定性和性能,相信这些从实践中总结出的细节都能为你提供直接的参考。

2. 控制器初始化流程的深度拆解

主机控制器的初始化,绝非简单地给几个寄存器赋值那么简单。它是一个环环相扣、次序严谨的过程,目的是在物理连接和电气特性上建立一个稳定、可靠的通信基础。任何步骤的遗漏或顺序错误,都可能导致后续所有操作失败。TI的文档将初始化概括为几个核心步骤,我们需要深入理解每一步的“为什么”。

2.1 第一步:时钟配置——为控制器注入“脉搏”

在接触任何外设控制器寄存器之前,首要任务是使能其时钟。这是嵌入式系统访问外设的铁律。对于MMCHS控制器,涉及两种时钟:

  1. 接口时钟(Interface Clock):用于控制器内部寄存器组的访问。没有它,CPU无法读写控制器的配置寄存器。
  2. 功能时钟(Functional Clock):用于控制器内部逻辑(如状态机、FIFO、DMA引擎)的运行,并最终经过分频产生输出给存储卡的通信时钟(mmc_clk)。

在TI的PRCM(Power, Reset, and Clock Management)模块中,通常通过设置PRCM.CM_ICLKEN1_COREPRCM.CM_FCLKEN1_CORE寄存器中对应的MMCHS位来实现。例如,对于MMCHS1控制器,使能其第24位。

实操心得:在调试初期,如果发现完全无法读写MMCHS的寄存器(读回来全是0或固定值),第一个要怀疑的就是接口时钟是否未使能。可以使用内存查看工具直接读取PRCM相关寄存器的值来确认。

2.2 第二步:软件复位——让控制器从“混沌”归零

使能时钟后,控制器可能处于一个不确定的状态。软件复位(Soft Reset)的作用是将控制器内部所有的状态机、FIFO缓冲区和大部分寄存器(除少数保持寄存器外)恢复到一个已知的、干净的初始状态。

流程非常明确:

  1. MMCHS_SYSCONFIG寄存器的SOFTRESET位写1。
  2. 轮询(Poll)MMCHS_SYSSTATUS寄存器的RESETDONE位,直到其变为1。

注意事项:复位过程需要一定时间,通常需要插入短暂的延时或进行有效的轮询等待。轮询时务必设置超时机制,避免因硬件故障导致驱动线程死锁。我曾遇到过因PCB板级问题导致复位永远无法完成的情况,超时机制能帮助快速定位是软件配置问题还是硬件故障。

2.3 第三步:硬件能力声明与配置

复位完成后,我们需要告诉控制器,它“应该”具备哪些能力,以及我们“希望”它如何工作。这主要涉及两个关键寄存器:

  • MMCHS_CAPA(Capabilities Register):这是一个只读或只写一次的寄存器,通常由BootROM或硬件电路固定,反映了控制器物理上支持的特性,如支持的电压(1.8V, 3.0V)、最大时钟频率、是否支持8位数据总线、是否支持DMA等。驱动需要读取此寄存器来了解硬件极限。
  • MMCHS_CUR_CAPA(Current Capabilities Register):这是一个可读写的寄存器,用于在硬件能力范围内,设置当前要使用的配置。例如,虽然控制器支持1.8V和3.0V,但在此处你可以选择只启用3.0V模式。

核心原理CAPA寄存器是硬件的“身份证”,CUR_CAPA是驱动给硬件下的“工作任务书”。在初始化阶段配置CUR_CAPA,是为了在后续总线电压切换、高速模式使能等操作时,控制器能依据此寄存器进行硬件级别的检查和协调,防止非法操作。

2.4 第四步:电源与唤醒模式定制

这部分配置决定了控制器在系统低功耗状态下的行为,对于电池供电的设备至关重要。

  • 空闲模式(Idle Mode):通过配置MMCHS_SYSCONFIG寄存器的SIDLEMODEAUTOIDLE字段,可以设定当CPU进入空闲状态时,控制器时钟是自动关闭、保持开启还是受智能时钟门控管理。
  • 唤醒配置(Wake-Up Configuration):对于SDIO设备(如Wi-Fi模块),其可以通过中断线(SDIO卡的第8个引脚)主动唤醒系统。配置流程如下:
    1. 如果需要唤醒功能,设置MMCHS_SYSCONFIGENAWAKEUP位。
    2. 设置MMCHS_HCTL寄存器的IWE位,使能SD卡中断线上的唤醒事件。
    3. 如果是SDIO卡,还需设置MMCHS_CIRQ_ENABLE位来使能卡中断。

避坑指南:唤醒配置的时机很重要。必须在SDIO卡完成初始化并进入工作状态后,再使能卡中断(CIRQ_ENABLE)。如果在卡识别阶段就使能,可能会收到无意义的中断信号,干扰初始化流程。我曾在调试一个Wi-Fi模块时,因过早使能中断导致系统不断被“假中断”唤醒,功耗异常。

2.5 第五步:总线配置与初始化时钟流

这是初始化阶段最具技巧性的部分,目标是建立主机与卡之间稳定的电气连接和时钟同步。

2.5.1 总线电压与模式设置首先,通过MMCHS_HCTL寄存器配置总线电压(SDVS)、电源模式(SDBP)和数据总线宽度(DTW)。

  1. 写入期望的电压值(如3.0V)。
  2. 轮询SDBP位,直到其变为1,表示总线电源已稳定上电。

    关键检查:这里有一个硬件安全机制。如果你设置的SDVS电压值不在MMCHS_CAPA寄存器声明的支持范围内,SDBP位将无法置1,写操作会被硬件静默忽略或恢复为0。这是防止软件错误配置损坏硬件或卡的第一道防线。

2.5.2 内部时钟使能与稳定

  1. 设置MMCHS_SYSCTL寄存器的ICE位为1,开启控制器的内部时钟。
  2. 轮询ICS位,直到其变为1,表示内部时钟已稳定。

2.5.3 初始化时钟流(INIT Sequence)在正式与卡通信之前,协议要求主机先提供至少74个(SD协议)或80个(MMC协议)时钟周期,且频率不能高于400kHz(通常设为100-400kHz),以便卡完成其内部上电初始化。这就是“初始化时钟流”。

  1. 将时钟分频器 (CLKD) 设置为一个较大的值,使输出时钟频率低于400kHz(例如,输入时钟96MHz,分频值240,得到400kHz)。
  2. 关键一步:将MMCHS_CON寄存器的INIT位置1。此模式会使控制器在发送任何命令时,自动在命令前插入至少80个时钟周期。
  3. 发送一个“哑命令”(Dummy Command),即向MMCHS_CMD寄存器写入0。由于INIT位被设置,控制器实际上不会在总线上发送CMD0,而是先输出一段初始化时钟流。
  4. 等待至少1ms(软件延时)。
  5. 清除INIT位,结束初始化序列。

2.5.4 时钟同步的“小把戏”TI的文档中提到一个细节:为了支持mmc_clk的同步,需要将控制MMC1_CLK引脚的Pad配置寄存器中的INPUTENABLE位置1。这相当于将时钟输出引脚也配置为输入,形成一个内部回环。这样做的目的是让控制器能采样到自己输出的时钟,用于内部时序同步,尤其是在高频模式下,能提高时钟信号的稳定性和精度。这是一个硬件设计相关的优化点,在其他厂商的控制器上不一定需要。

3. 卡识别与选择:与存储设备的“第一次握手”

初始化控制器后,下一步是识别连接到总线上的卡设备类型(MMC, SD v1.x, SD v2.x, SDIO)并为其分配相对地址(RCA)。这个过程是一系列标准命令的交互,流程图看起来复杂,但逻辑非常清晰。

3.1 识别流程的逻辑解析

流程本质上是利用不同卡类型对特定命令集的响应差异来进行“排除法”识别。

  1. 发送CMD0(GO_IDLE_STATE):这是复位所有卡到空闲状态的广播命令,没有响应。用于确保所有卡从一个已知状态开始。
  2. 发送CMD8(SEND_IF_COND):这是一个SD卡v2.0及以后版本才支持的命令。如果收到有效响应(RCA),说明总线上有SDv2+卡。如果命令超时(CTO),则可能是SDv1.x卡或MMC卡。
  3. 发送CMD5(IO_SEND_OP_COND):这是SDIO卡才响应的命令。如果收到有效响应,说明是SDIO卡(可能是纯IO或Combo卡)。如果超时,则可能是MMC卡或SD存储卡。
  4. 发送CMD55(APP_CMD) + ACMD41(APP_SEND_OP_COND)CMD55告诉卡下一个命令是应用特定命令。ACMD41用于查询SD卡(包括v1.x和v2.x)的操作条件。如果收到有效响应,说明是SD卡(根据CMD8的结果可进一步区分v1.x或v2.x)。
  5. 发送CMD1(SEND_OP_COND):这是MMC卡响应的命令。如果收到有效响应,说明是MMC卡。

核心技巧:这个识别序列必须严格按照顺序进行,因为命令具有特异性。例如,先发CMD8可以安全地区分SDv2+,而不会影响MMC卡。驱动代码中通常会用一个while循环配合状态标志来实现这个状态机。务必在每次命令发送后,检查MMCHS_STAT寄存器的命令完成(CC)和命令超时(CTO)位,并根据响应数据(MMCHS_RSPxx寄存器)解析卡的类型和能力(如支持的电压、是否支持高容量等)。

3.2 电压协商与卡激活

在通过ACMD41CMD1获得卡的OCR(Operating Conditions Register)响应后,主机需要与卡进行电压协商。

  1. 主机比较自身支持的电压范围(在MMCHS_CAPA中声明)与卡响应的电压范围。
  2. 选择一个双方都支持的电压(通常取交集内的最高电压,如3.0V)。
  3. 再次发送ACMD41CMD1,但这次参数中要包含协商好的电压值。这一步至关重要!第一次命令是查询,第二次是设置。卡在收到包含有效电压范围的命令后,才会开始内部初始化并进入就绪状态。需要轮询响应中的忙(busy)位,直到其变为0,表示卡已准备就绪。
  4. 卡就绪后,发送CMD2(ALL_SEND_CID)获取卡的唯一CID。
  5. 发送CMD3(SEND_RELATIVE_ADDR)为卡分配一个本地相对地址(RCA)。在单卡系统中,这个地址通常是固定的。
  6. 最后,发送CMD7(SELECT/DESELECT_CARD)带上RCA,来选中该卡进行后续的数据传输。一条总线上同一时刻只能有一张卡被选中。

4. 数据传输编程模型详解

卡被成功识别和选中后,就进入了数据读写阶段。MMCHS控制器支持三种主要的数据传输模式:带中断的DMA模式、轮询的DMA模式以及非DMA的轮询模式。选择哪种模式取决于系统对性能、CPU占用率和实时性的要求。

4.1 命令传输流程(发送命令的通用模板)

无论进行何种数据传输,发送命令的流程是通用的。它分为轮询和中断两种方式。

4.1.1 轮询方式发送命令

  1. 检查命令线空闲:读取MMCHS_PSTATE[0]CMDI)位,确保为0(命令线空闲)。
  2. 配置命令参数
    • 设置MMCHS_CON寄存器,配置命令特性(如是否为流传输、多块传输、是否启用超时检查)。
    • 如果有数据,配置MMCHS_BLK寄存器(块大小和块数量)。
    • 配置MMCHS_SYSCTL中的DTO(数据超时)字段。
    • 使能需要的事件中断(MMCHS_IE),如果使用轮询,可以不使能系统中断(MMCHS_ISE)。
  3. 写入命令参数和索引:将命令参数写入MMCHS_ARG,将命令索引和属性(如响应类型、是否带数据、是否启用CRC检查等)写入MMCHS_CMD寄存器。写入MMCHS_CMD寄存器即触发命令发送。
  4. 轮询等待完成:循环读取MMCHS_STAT寄存器,检查CC(命令完成)位是否置1。
  5. 处理结果
    • 如果CC置1,检查是否有错误位(CEB,CCRC,CERR等)。若无错误,且命令有响应,则从MMCHS_RSP10/32/54/76寄存器中读取响应数据。
    • 如果发生超时(CTO)或CRC错误(CCRC),通常需要执行软件复位命令线(设置MMCHS_SYSCTL[25] SRC位)后重试。

4.1.2 中断方式发送命令前3步与轮询方式相同。 4.使能系统中断:除了配置MMCHS_IE,还需配置MMCHS_ISE寄存器,使能相应事件向CPU产生中断信号。同时,在驱动中挂接好中断服务程序(ISR)。 5.触发并等待中断:写入MMCHS_CMD后,CPU可以执行其他任务。当命令完成或出错时,控制器触发中断。 6.在ISR中处理:在中断服务程序中,读取MMCHS_STAT寄存器判断事件类型,进行错误处理或读取响应数据。

经验之谈:对于初始化阶段的命令(如识别卡),使用轮询方式更简单直接。对于数据传输过程中的命令,尤其是在多任务操作系统中,使用中断方式可以显著降低CPU占用率。但中断方式对驱动程序的并发和重入安全性要求更高。

4.2 DMA模式数据传输(以带中断为例)

DMA模式是高性能数据传输的首选,它能将CPU从繁重的数据搬运工作中解放出来。

4.2.1 写传输流程(主机到卡)

  1. 检查数据线空闲:读取MMCHS_PSTATE[1]DATI)位,确保为0。
  2. 配置并启动DMA:根据要写入的数据内存地址、数据长度(需与MMCHS_BLK配置匹配),配置DMA控制器的源地址、目标地址(MMCHS数据寄存器)、传输长度等,并启动DMA通道。
  3. 发送写命令:按照命令传输流程,发送一个写命令(如CMD24写单块,CMD25写多块)。命令参数中需包含起始扇区地址。
  4. DMA与控制器协作:MMCHS控制器会从DMA通道获取数据,通过数据线发送给卡。每完成一个数据块(Block)的传输,控制器会产生一个块事件(BRR/BWR)或DMA请求。
  5. 等待传输完成中断:当所有数据块传输完毕,控制器会置位TC(Transfer Complete)标志并产生中断(如果已使能)。
  6. 停止传输(多块写):如果是多块写(CMD25),在传输完成后,需要发送CMD12(STOP_TRANSMISSION)命令来终止卡的写状态。可以配置Auto-CMD12让控制器自动发送。
  7. 错误处理:在传输过程中或完成后,检查MMCHS_STAT寄存器的数据错误位(DEB,DCRC,DTO)。如有错误,可能需要复位数据线(设置MMCHS_SYSCTL[26] SRD位)并重试或上报错误。

4.2.2 读传输流程(卡到主机)流程与写传输对称:

  1. 检查数据线空闲。
  2. 配置并启动DMA(方向为从MMCHS数据寄存器到内存)。
  3. 发送读命令(如CMD17读单块,CMD18读多块)。
  4. DMA将控制器从卡接收到的数据搬运至内存。
  5. 等待传输完成中断。
  6. 如果是多块读,发送CMD12停止传输。
  7. 错误处理。

性能调优关键点

  • 块大小(BLEN):设置为控制器和卡都支持的最大值(通常为512字节或1024字节),可以减少命令开销。
  • DMA突发长度:配置DMA控制器使用最大可能的突发长度(Burst Size),以提升总线利用效率。
  • 双缓冲区(Ping-Pong Buffer):对于持续流式读写(如视频录制),可以实现双缓冲区。当一个缓冲区通过DMA传输时,CPU可以处理另一个已满/已空的缓冲区,实现流水线操作。

4.3 非DMA模式(轮询PIO模式)

当系统不支持DMA,或传输数据量非常小时,可以使用轮询PIO模式。在这种模式下,CPU需要亲自读写MMCHS_DATA寄存器来搬运每一个数据字(4字节)。

基本流程

  1. 发送数据命令后,轮询MMCHS_STAT寄存器的BRR(Buffer Read Ready,用于读)或BWR(Buffer Write Ready,用于写)位。
  2. BRR置1时,从MMCHS_DATA寄存器连续读取(BLEN+3)/4次(因为BLEN是字节数,寄存器是32位宽),将数据存入内存缓冲区。
  3. BWR置1时,将内存缓冲区的数据连续写入MMCHS_DATA寄存器(BLEN+3)/4次。
  4. 重复步骤1-3,直到所有数据块传输完成。

注意事项:PIO模式会大量占用CPU资源,在高频率下可能导致系统响应迟缓。它通常仅用于调试、初始化或极小数据量传输。在实现时,务必在轮询循环中加入超时判断,防止因卡无响应导致死循环。

5. 高级功能与疑难问题排查

5.1 时钟频率的动态切换

在初始化阶段,时钟频率较低(如400kHz)。在卡识别完成后,为了获得更高的数据传输率,需要切换到更高的时钟频率(如25MHz, 50MHz)。切换流程必须遵循:

  1. 确保当前没有进行中的数据传输。
  2. MMCHS_SYSCTL[2]CEN)位清零,停止向卡提供时钟。
  3. 写入新的分频值到MMCHS_SYSCTL[15:6]CLKD)位域。
  4. CEN位置1,重新使能时钟输出。
  5. 轮询ICS位直到稳定。

避坑指南:切换频率前,必须确认卡支持目标频率(通过读取CSD寄存器中的TRAN_SPEED字段)。此外,切换到高速模式(如HS-SDR50)可能还需要先发送特定的切换命令(CMD6)给卡,待卡切换内部电路完成后,主机再切换时钟频率。顺序错误会导致通信失败。

5.2 SDIO特有的挂起与恢复流程

SDIO卡(如Wi-Fi、蓝牙模块)支持挂起(Suspend)和恢复(Resume)功能,以节省功耗。其核心是利用块间隙(Block Gap)和CMD52(IO_RW_DIRECT)命令。

  • 挂起:在数据传输中设置SBGR位,控制器会在下一个块间隙暂停。然后发送CMD52挂起命令给卡。需要保存当前的MMCHS_BLK寄存器值。
  • 恢复:恢复保存的MMCHS_BLK值,复位数据线(SRD),然后发送CMD52恢复命令。

5.3 常见问题排查实录

驱动开发中,大部分时间都在与各种异常情况作斗争。以下是一些典型问题及排查思路:

问题1:卡识别失败,CMD8/CMD5无响应或超时。

  • 检查硬件连接:测量CMD和DAT线的上拉电阻是否正常(通常为10k-50k欧姆)。用示波器观察初始化时钟流是否正常输出。
  • 检查电压:确认主机控制器配置的电压(SDVS)与卡所需的电压是否匹配。有些卡仅支持3.3V,在1.8V模式下无响应。
  • 检查时序:确保初始化时钟流的频率和周期数符合协议要求。时钟频率过高可能导致卡无法同步。
  • 检查命令格式:确认MMCHS_CMD寄存器的响应类型、CRC使能等位设置是否正确。对于CMD8,参数中是否包含了正确的检查模式(Check Pattern)。

问题2:数据传输过程中出现CRC错误(DCRC)。

  • 降低时钟频率:高频下信号完整性可能变差。尝试降低时钟频率看是否解决。
  • 检查PCB布线:SD/MMC总线对走线长度、阻抗匹配和串扰敏感。检查CMD、CLK与DAT线是否等长,是否有过孔或锐角。
  • 检查电源噪声:存储卡工作时电流可能突变,导致电源纹波增大。确保电源去耦电容(通常每个电源引脚一个0.1uF和一个1-10uF电容)靠近卡座放置。
  • 调整驱动强度:有些SoC允许调整IO口的驱动强度。过强的驱动可能引起过冲和振铃,过弱则可能无法识别。参考硬件手册进行调整。

问题3:多块写入后,卡数据损坏或无法读取。

  • 确认使用了停止命令:多块写操作(CMD25)后,必须发送CMD12或使能Auto-CMD12来正确终止卡的编程状态。缺少这一步,卡可能认为传输未结束,导致缓存中的数据未正确编程到NAND闪存中。
  • 检查DMA缓冲区对齐:确保DMA使用的内存缓冲区地址与缓存行(Cache Line)对齐,并做好缓存一致性维护(在DMA传输前后执行Cache CleanInvalidate操作)。缓冲区不对齐或缓存数据未同步是导致数据错误的常见原因。
  • 验证写入完成:在发送停止命令后,可以发送CMD13(SEND_STATUS)来查询卡的状态,确认卡是否繁忙(busy位)以及是否有写入错误。

问题4:中断模式下,系统偶尔卡死或数据丢失。

  • 中断服务程序(ISR)效率:确保ISR执行时间尽可能短,只做必要的状态读取、标志清除和事件触发,将数据处理等耗时任务交给底半部(如Tasklet、工作队列)或线程。
  • 中断竞争条件:清除控制器中断标志和使能中断的时机要仔细设计。通常应在ISR入口处读取并清除MMCHS_STAT寄存器(写1清零),在出口处再重新使能中断。避免在中断处理期间被同一中断再次打断。
  • DMA与中断同步:在DMA传输完成中断中,在访问DMA缓冲区数据前,务必确保DMA传输确实已经完成(检查DMA控制器状态寄存器),并处理好DMA描述符链的更新。

调试这类问题,逻辑分析仪或带有SD协议解码功能的示波器是 invaluable 的工具。它们可以直观地展示总线上的命令、响应和数据流,帮助你快速定位是软件命令序列错误,还是硬件信号质量问题。从最基础的时钟和命令响应看起,逐步深入到复杂的数据传输,是解决嵌入式存储驱动问题的有效方法论。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/19 1:55:59

手机短信技术全解析:从传统到OTT的演进与应用

1. 手机短消息的三种实现方式解析作为一名在通信行业摸爬滚打多年的老兵,今天想和大家聊聊手机短消息这个看似简单却暗藏玄机的功能。记得2003年我刚入行时,第一条短信的发送成功率还不到80%,而现在这个数字已经提升到了99.9%以上。这背后是三…

作者头像 李华
网站建设 2026/7/19 1:55:45

C#通过AT指令控制语音猫实现串口通信

1. 项目概述:C#通过AT指令操控语音猫的核心逻辑语音猫(Voice Modem)作为一种嵌入式通信设备,在电话自动应答、语音播报等场景中广泛应用。通过AT指令集控制这类设备,本质上是通过串口发送特定格式的文本命令。C#的Seri…

作者头像 李华
网站建设 2026/7/19 1:53:51

QT------布局layoutStretch失效问题

layoutStrech是设计某一控件在布局中的比例,比如在这里就设置了5,5对半分:但是layoutStrech在使用时经常性的失效:明明设计界面是没问题的运行就失效了明明设置的 5:5但是实际上就不是了,经过多方验证,这是由于设置lay…

作者头像 李华
网站建设 2026/7/19 1:50:43

MySQL 8.0.35 二进制包安装笔记

MySQL 8.0.35 二进制包安装笔记 — 192.168.195.141 环境信息项目值主机IP192.168.195.141操作系统Kylin Linux Advanced Server V10 (Halberd)内核4.19.90-89.11.v2401.ky10.x86_64glibc2.28内存2.8GMySQL版本8.0.35(二进制包,glibc2.17) 1.…

作者头像 李华
网站建设 2026/7/19 1:48:43

javascript练习

练习1:在数组里面添加一个方法,删除其中重复的元素这里用到了原型链,在Array的方法里面添加删除重复元素的Array.prototype.distinct function(){let array []for(value of this){if(array.indexOf(value)-1){array.push(value)}}return arr…

作者头像 李华