STM32_SD卡的SDIO通信_基础读写
本篇将使用CubeMX+Keil, 创建一个SD卡读写的工程。
目录
一、SD卡要点速读
二、SDIO要点速读
三、SD卡座接线原理图
四、CubeMX新建工程
五、CubeMX 生成 SD卡的SDIO通信部分
六、Keil 编辑工程代码
七、实验效果
实现效果,如下图:
一、SD卡 速读
SD卡,全称Secure Digital Memory Card(安全数码卡),是嵌入式设备上常用的一种存储介质。
1、尺寸大小 分类
按卡的大小分类,可以为3种:
- 标准SD卡 :体积较大,卡侧带写保护开关;常见于相机和摄像机中,用于存储高分辨率照片和视频;
- mini SD卡 :现在较少看到,已逐渐被microSD卡取代;
- Micro SD卡:旧称 TF卡,2004年更名为 Micro SD Card, 常用于扩展手机和平板电脑的存储空间。
每种卡形状大小不一,但功能一样:遵循相同的 SD卡协议、相同的命令集、相同的块大小(512)。只需确保SDIO引脚配置正确,并且遵循SD卡协议发送正确的命令,程序即可通用。
都是SD卡,但习惯上,标准SD叫SD卡,Micro SD叫TF卡。
目前,STM32开发板、Linux开发板 等,预留的卡座,一般是TF卡座,因为它占用空间最少。
2、卡的容量及标准 分类
在SD卡的表面丝印上,会有HC、XC等字样,表示它所使用的存储标准。
- SD: 早期的版本,基本停用,最高 2GB, 分区格式为 FAT12(FAT)、FAT16。
- SDHC:容量范围 2GB ~ 32GB, 分区格式为 FAT32。
- SDXC:容量范围 32GB ~ 2TB, 分区格式为 exFAT。
- SDUC:容量范围 2TB ~ 128TB, 分区格式为 exFAT。
3、SD卡的传输速度
SD卡的可变时钟频率:0~25MHz。当运行在25M+数据带宽4位时,最大理论传输速度是12.5MB/s。
而操作中,会明显低于理论速度,其受限于不同品牌的芯片优化、制造工芯、采用标准等。
SD卡是Flash存储,读写速度特点是:读快、写慢。
SD卡的最低写入速度,用Class等级来标识。
在表面丝印上,一般会有Class字样,它后面的数表示最低写入速度,单位是:MB/s。
或者,会用一个外面带半圆的数字表示。
- Class 2:2MB/s
- Class 4:4MB/s
- Class 6:6MB/s
- Class 10:10MB/s
附:常用的SD卡读写速率参考,非严谨值。
SD卡容量 | 文件系统 | 写入速度 | 读取速度 |
32G(SDHC) | FAT32 | 2MB/s | 8MB/s |
32G(SDHC) | exFAT | 3.5MB/s | 8.5MB/s |
64G(SDXC) | exFAT | 4MB/s | 8.5MB/s |
4、SD卡的使用寿命
一般是指:擦除的最大次数。
写入数据时需要先擦除扇区内容。读数据是不影响使用寿命的,写数据才会影响使用寿命。
因此,应避免频繁地对同一地址(扇区)进行写数据。如:使用程序每隔一秒保存一次数据到同一地址,这是不妥当的。
- TLC:1000~3000次
- MLC:3000 ~1万次
- SLC:可达10万次
擦写次数对使用寿命影响较小,而更容易直接“致死”的是:带电插拔,很容易坏卡,主要是静电原因!
二、SDIO要点速读
原理比较复杂,有兴趣的请自行csdn搜更详细的技术文档,或STM32的官方文档。
- SD卡的读写通信操作,可以用 SPI、SDIO,本示例使用SDIO。
- SDIO接口是在SD内存卡接口的基础上发展起来的;
- SDIO接口除了能读写SD内存卡,还能连接其它SDIO接口的设备;
- 常用的STM32F103C8,没有SDIO接口,F103系列R型号起,才带SDIO;
- STM32F4系列芯片,带更完善的SDIO主机接口,能与MMC卡、SD卡、SDI/0卡、EC-ATA设备进行通信;
- 三种总线模式:1-bit、4-bit、8-bit(不常用);
三、SD卡座接线原理图
STM32的SDIO外设与SD卡通信,通用接线如下图。
注:当使用弹簧式SD卡座,会有第9个脚(CD), 可不接。它用于判断SD卡是否插入,当插入SD卡时,此脚输出低电平。
四、CubeMX新建工程
建议复制一个已带UART1、printf的工程,这样更省时。
如果没有,可参考以下步骤。
1、新建一个普通的工程
新手可参考如下图解,老司机请直接跳过。
【STM32+CubeMX】 新建工程_STM32F407
2、为工程添加UART1通信、printf输出
用于把SD卡的测试信息,(通过USB转TTl),输出到串口助手观察。
如果,你已知晓如何通过printf输出信息,自行添加,跳过即可。
USART1 DMA发送、DMA空闲中断 接收不定长数据
UART1 快速实现移植、通信 ( bsp_UART.c 、bsp_UART.h)
五、CubeMX 配置 SD卡的SDIO 初始化
通过 CubeMX配置SDIO, 极度简单。
本节为方便测试,只使用普通的读写方式,后续篇章再添加DMA、FATFS等方式。
1、使能SDIO
- Mode:选择SD的四线模式,即 SD 4 bits Wide bus.
- 参数部分:F4系列不用修改配置,默认即可。F103系列,需把时钟分频系数修改为 6,即SDIOCLK Clock divide factor这一项,由默认0改为6, 不然会通信失败。
2、时钟设置
进入时钟树配置页面。
这时可能会弹出一个询问窗:是否自动配置所需时钟?
选择:NO ,手动修改即可。
如果Yes,它将针对已使能的SDIO进行必须值的配置,而系统时钟值,会被修改为其它值。不推荐。
F4系列,如果板上是25M的晶振,用如下参数值;要是8M的晶振,修改晶振、分频两处为8即可。
重点:箭头所指的Q值,它用于控制USB 、SDIO和随机数生成器的时钟。
这个时钟,必须是 48M !
好了,已完成配置。
重新生成工程,即可!
六、Keil 编辑工程代码
1、打开keil 工程,先重新编译一次。
- 正常情况,编译是0 Error的。
- 如果有Error, 应该是新建工程时,路径、名称有中文了,重新开建工程,用英文即可。
2、重要修改:SD卡的初始化,使用 1-bit 模式
CubeMX生成的SDIO初始化代码,有一个bug,需要手动修改,操作如下:
- 编译后,右击 main.c 文件中函数 MX_SDIO_SD_Init(),
- 在弹出菜单中:Go To Definition Of ...; 将跳转到SD卡初始化函数;
跳转到 sdio.c文件的 MX_SDIO_SD_Init()函数内部后,
把下图位置中的 4B,改为 1B ;
它下面还有一个4B,不用修改,只修改刚才那个即可。不要改错位置了!
重点:每次重新生成后,都要手动修改一次。如果不修改,初始化过程会导致程序卡死。
3、编写 读写测试 代码
SD卡的基础读写函数比较简单,常用的函数共4个。
1、获取SD卡信息
HAL_SD_CardInfoTypeDef pCardInfo = {0}; // SD卡信息结构体
HAL_SD_GetCardInfo(&hsd, &pCardInfo); // 获取 SD 卡的信息2、读数据
HAL_SD_ReadBlocks(&hsd, aOldData, 7, 2, 3000); // SD卡的句柄、数据、块地址、块数量、超时ms3、写数据
HAL_SD_WriteBlocks(&hsd, aTestData, 7, 2, 3000) // SD卡的句柄、数据、块地址、块数量、超时ms4、擦除数据
HAL_SD_Erase(&hsd, 7, 8) // SD卡的句柄、块起始地址、块结束地址
在 main函数内的 /* USER CODE BEGIN 2 */ 注释下方,编写以下代码(可复制):
示例代码里已附详细注释,比较容易理解,流程是:
- 获取SD卡信息
- 读取测试块的原数据
- 写入测试
- 擦除测试
- 写回原数据
/***************** SD卡读写通信测试 *****************//* 1、获取卡信息,打印到串口助手 *//* 2、读测试:读出测试位置原数据,保存在 aOldData[] *//* 3、写测试:在测试的块上,写入指定数据 *//* 读出刚才写入的块数据,打印到串口助手观察 *//* 4、擦除测试:擦除指定块上的数据 *//* 读出刚才擦除块的数据,打印到串口助手观察 *//* 5、写回原数据到指定位置 *//* 读出刚才写入的块数据,打印到串口助手观察 */ #define SD_TEST_SIZE 1024 // 测试数据的字节数,刚好是2个块大小:2x512static uint8_t aOldData[SD_TEST_SIZE] = {0}; // 用于存放旧数据,先读出来,测试完了,再把旧数据写回去static uint8_t aTestData[SD_TEST_SIZE] = {0}; // 临时缓存,用来存放测试数据HAL_SD_CardInfoTypeDef pCardInfo = {0}; // SD卡信息结构体uint8_t status = HAL_SD_GetCardState(&hsd); // SD卡状态标志值if (status == HAL_SD_CARD_TRANSFER){/* 1、获取卡信息,打印到串口助手 */HAL_SD_GetCardInfo(&hsd, &pCardInfo); // 获取 SD 卡的信息printf("\r1、获取SD卡信息 ... \r\n");printf("卡类型:%d \r\n", pCardInfo.CardType); // 类型返回:0-SDSC、1-SDHC/SDXC、3-SECUREDprintf("卡版本:%d \r\n", pCardInfo.CardVersion); // 版本返回:0-CARD_V1、1-CARD_V2printf("块数量:%d \r\n", pCardInfo.BlockNbr); // 可用的块数量printf("块大小:%d \r\n", pCardInfo.BlockSize); // 每个块的大小; 单位:字节printf("卡容量:%lluG \r\n", ((unsigned long long)pCardInfo.BlockSize * pCardInfo.BlockNbr) / 1024 / 1024 / 1024); // 计算卡的容量HAL_Delay(1000); // 重要:稍作延时再开始读写测试; 避免有些仿真器烧录期间的多次复位,短暂运行了程序,导致下列读写数据不完整。 /* 2、读测试:读出测试位置原数据,保存在 aOldData[] */printf("\r2、读取测试块的原数据 ... \r\n");memset(aOldData, 0, SD_TEST_SIZE); // 清0数组的数据if (HAL_SD_ReadBlocks(&hsd, aOldData, 7, 2, 3000) == HAL_OK) // 读SD卡数据块; 参数:SD结构体、数据地址、块起始地址、读的块数量、超时时间;{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束for (uint32_t i = 0; i < SD_TEST_SIZE; i++) // 打印 原数据printf("%X ", aOldData[i]);printf("\r\n");}else{printf("SD卡 读测试 失败!\n"); }/* 3-1、写测试:在测试的块上写入数据 */printf("\r3、SD卡 写入测试 ...\r\n");memset(aTestData, 0x8, SD_TEST_SIZE); // 为数组准备要写入的测试数据:整个数组填充指定值if (HAL_SD_WriteBlocks(&hsd, aTestData, 7, 2, 3000) == HAL_OK) // 向SD卡写入数据块; 参数:SD结构体、数据地址、块起始地址、写入的块数量、超时时间;{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束printf("对指定块写入结束! \r写入的数据是:\n");for (uint32_t i = 0; i < SD_TEST_SIZE; i++) // 打印 写入的数据printf("%X ", aTestData[i]);printf("\r\n");}else{printf("SD卡 写测试 失败!\n");}/* 3-2、读出刚才写测试的块内数据 */printf("\r现在块内的数据是:\r\n");memset(aTestData, 0, SD_TEST_SIZE); // 清0数组的数据if (HAL_SD_ReadBlocks(&hsd, aTestData, 7, 2, 3000) == HAL_OK) // 读SD卡数据块; 参数:SD结构体、数据地址、块起始地址、读的块数量、超时时间;{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束for (uint32_t i = 0; i < SD_TEST_SIZE; i++) // 打印 写入后块内现在数据printf("%X ", aTestData[i]);printf("\r\n");}else{printf("SD卡 读测试 失败!\n");}/* 4-1、擦除测试:擦除指定块上的数据 */printf("\r4、擦除块测试 ...\r\n");if (HAL_SD_Erase(&hsd, 7, 8) == HAL_OK) // 擦除SD卡上的数据; 参数:SD结构体、块的起始地址、块的结束地址{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束printf("擦除 成功! \r\n");}else{printf("擦除 失败! \r\n");}/* 4-2、读取,擦除后指定块上的数据 */printf("擦除后,现在块内的数据是:\r\n");memset(aTestData, 0, SD_TEST_SIZE); // 清0数组的数据if (HAL_SD_ReadBlocks(&hsd, aTestData, 7, 2, 3000) == HAL_OK) // 读SD卡数据块; 参数:SD结构体、数据地址、块起始地址、读的块数量、超时时间;{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束for (uint32_t i = 0; i < SD_TEST_SIZE; i++) // 打印 块内现在的数据printf("%X ", aTestData[i]);printf("\r\n");}else{printf("SD卡 读测试 失败!\n");}/* 5-1、写回测试块上的原数据 */printf("\r5、写回原数据 ...\r\n");//memset(aOldData, 1, SD_TEST_SIZE);if (HAL_SD_WriteBlocks(&hsd, aOldData, 7, 2, 3000) == HAL_OK) // 向SD卡写入数据块; 参数:SD结构体、数据地址、块起始地址、写入的块数量、超时时间;{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束printf("写入结束! \n");}else{printf("SD卡 写回原数据 失败!\n");} /* 5-2、读取,写入后的数据 */printf("现在块内的数据是: \r\n");memset(aTestData, 0, SD_TEST_SIZE); // 清0数组的数据if (HAL_SD_ReadBlocks(&hsd, aTestData, 7, 2, 3000) == HAL_OK) // 读SD卡数据块; 参数:SD结构体、数据地址、块起始地址、读的块数量、超时时间;{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束for (uint32_t i = 0; i < SD_TEST_SIZE; i++) // 打印 块内现在的数据printf("%X ", aTestData[i]);printf("\r\n\r\n");}else{printf("SD卡 读测试 失败! \r\n");}printf("SD卡 读写测试结束!\r\n");}
完成后,位置如下图:
七、实验效果
程序运行后,串口助手输出如下:
如有错漏 ,望指正~~~!
相关文章:

STM32_SD卡的SDIO通信_基础读写
本篇将使用CubeMXKeil, 创建一个SD卡读写的工程。 目录 一、SD卡要点速读 二、SDIO要点速读 三、SD卡座接线原理图 四、CubeMX新建工程 五、CubeMX 生成 SD卡的SDIO通信部分 六、Keil 编辑工程代码 七、实验效果 实现效果,如下图: 一、SD卡 速读…...
【Docker】私有Docker仓库的搭建
一、准备工作 确保您的系统已安装Docker。如果没有安装,请参考Docker官方文档进行安装。 准备一个用于存储仓库数据的目录,例如/registry_data/。 二、拉取官方registry镜像 首先,我们需要从Docker Hub拉取官方的registry镜像。执行以下命…...

linux 管道符、重定向与环境变量
1. 输入输出重定向 在linux工作必须掌握的命令一文中,我们已经掌握了几乎所有基础常用的Linux命令,那么接下来的任务就是把多个命令适当的组合到一起,使其协同工作,会更高效的处理数据,做到这一点就必须搞清楚命令的输…...

Ansible fetch模块详解:轻松从远程主机抓取文件
在自动化运维的过程中,我们经常需要从远程主机下载文件到本地,以便进行分析或备份。Ansible的fetch模块正是为了满足这一需求而设计的,它可以帮助我们轻松地从远程主机获取文件,并将其保存到本地指定的位置。在这篇文章中…...

wireshark工具简介
目录 1 wireshark介绍 2 wireshark抓包流程 2.1 选择网卡 2.2 停止抓包 2.3 保存数据 3 wireshark过滤器设置 3.1 显示过滤器的设置 3.2 抓包过滤器 4 wireshark的封包列表与封包详情 4.1 封包列表 4.2 封包详情 参考文献 1 wireshark介绍 wireshark是非常流行的网络…...
51单片机——按键控制LED流水灯
引言 在电子制作和嵌入式系统学习中,51 单片机是一个经典且入门级的选择。按键控制 LED 流水灯是 51 单片机的一个基础应用,通过这个实例,我们可以深入了解单片机的输入输出控制原理。 51 单片机简介 51 单片机是对所有兼容 Intel 8051 指…...

【opencv】第9章 直方图与匹配
第9章 直方图与匹配 9.1 图像直方图概述 直方图广泛运用于很多计算机视觉运用当中,通过标记帧与帧之间显著的边 缘和颜色的统计变化,来检测视频中场景的变化。在每个兴趣点设置一个有相近 特征的直方图所构成“标签”,用以确定图像中的兴趣点。边缘、色…...

HTML5 Web Worker 的使用与实践
引言 在现代 Web 开发中,用户体验是至关重要的。如果页面在执行复杂计算或处理大量数据时变得卡顿或无响应,用户很可能会流失。HTML5 引入了 Web Worker,它允许我们在后台运行 JavaScript 代码,从而避免阻塞主线程,保…...

MVCC底层原理实现
MVCC的实现原理 了解实现原理之前,先理解下面几个组件的内容 1、 当前读和快照读 先普及一下什么是当前读和快照读。 当前读:读取数据的最新版本,并对数据进行加锁。 例如:insert、update、delete、select for update、 sele…...

基于ESP32-IDF驱动GPIO输出控制LED
基于ESP32-IDF驱动GPIO输出控制LED 文章目录 基于ESP32-IDF驱动GPIO输出控制LED一、点亮LED3.1 LED电路3.2 配置GPIO函数gpio_config()原型和头文件3.3 设置GPIO引脚电平状态函数gpio_set_level()原型和头文件3.4 代码实现并编译烧录 一、点亮LED 3.1 LED电路 可以看到&#x…...

【优选算法】9----长度最小的子数组
----------------------------------------begin-------------------------------------- 铁子们,前面的双指针算法篇就算告一段落啦~ 接下来是我们的滑动窗口篇,不过有一说一,算法题就跟数学题一样,只要掌握方法,多做…...

LabVIEW太阳能照明监控系统
在公共照明领域,传统的电力照明系统存在高能耗和维护不便等问题。利用LabVIEW开发太阳能照明监控系统,通过智能控制和实时监测,提高能源利用效率,降低维护成本,实现照明系统的可持续发展。 项目背景 随着能源危机…...
MongoDB中单对象大小超16M的存储方案
在 MongoDB 中,单个文档的大小限制为 16MB。如果某个对象(文档)的大小超过 16MB,可以通过以下几种方案解决: 1. 使用 GridFS 适用场景:需要存储大文件(如图像、视频、文档等)。 原…...

三维激光扫描-用智能检测系统提升效率
当下,企业对生产效率和质量控制的要求越来越高。传统的检测方法往往难以满足高精度、快速响应的需求。三维激光扫描技术结合智能检测系统,为工业检测带来了革命性的变革。 传统检测方法的局限性 传统检测方法主要依赖于人工测量和机械检测工具…...
css遇到的一些问题
1.vw单位,在PC端vw单位是包含右侧滚轮的宽度,而在移动端不会包含滚轮的长度,在PC端运用vw单位进行居中对齐,会比实际偏左盒子偏右一点,因为内容区域并不包含滚轮。 2.运用媒体查询进行响应式布局式,媒体查询…...

【langgraph】ubuntu安装:langgraph:未找到命令
langgraph 在ubuntu24.04 参考:langgraph运行:报错: (05_ep_dev) root@k8s-master-pfsrv:/home/zhangbin/perfwork/01_ai/05_ep_dev/expert# langgraph dev langgraph:未找到命令查看langraph的安装情况 pip show langgraph...

mysql 学习2 MYSQL数据模型,mysql内部可以创建多个数据库,一个数据库中有多个表;表是真正放数据的地方,关系型数据库 。
在第一章中安装 ,启动mysql80 服务后,连接上了mysql,那么就要 使用 SQL语句来 操作mysql数据库了。那么在学习 SQL语言操作 mysql 数据库 之前,要对于 mysql数据模型有一个了解。 MYSQL数据模型 在下图中 客户端 将 SQL语言&…...
小识JVM堆内存管理的优化机制TLAB
JVM(Java虚拟机)在堆内存分配空间时,TLAB(Thread Local Allocation Buffer,线程本地分配缓存区)是一种重要的内存管理优化技术。以下是对TLAB的详细解释: 一、TLAB的定义 TLAB是JVM堆内存管理…...

ToDesk云电脑、顺网云、网易云、易腾云、极云普惠云横测对比:探寻电竞最佳拍档
一、云电脑:电竞新宠崛起 在电竞游戏不断发展的今天,硬件性能成为了决定游戏体验的关键因素。为了追求极致的游戏画面与流畅度,玩家们往往需要投入大量资金购置高性能电脑。然而,云电脑技术的出现,为玩家们提供了一种…...

学习ASP.NET Core的身份认证(基于JwtBearer的身份认证10)
基于Cookie传递token的主要思路是通过用户身份验证后,将生成的token保存到Response.Cookies返回客户端,后续客户端访问服务接口时会自动携带Cookie到服务端以便验证身份。之前一直搞不清楚的是服务端程序如何从Cookie读取token进行认证(一般都…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...