Linux mmap系统调用
文章目录
- 前言
- 一、mmap()函数简介
- 二、代码演示
- 2.1 mmap使用场景
- 2.2 私有匿名映射
- 2.3 私有文件映射
- 2.4 共享匿名映射
- 2.5 共享文件映射
- 参考
前言
NAMEmmap, munmap - map or unmap files or devices into memorySYNOPSIS#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);int munmap(void *addr, size_t length);
mmap函数用于将文件或设备映射到内存中。
mmap函数是一种内存映射文件的方法,它可以将一个文件或设备映射到进程的地址空间中,使得进程可以像访问内存一样访问文件或设备。
一、mmap()函数简介
mmap()函数在调用进程的虚拟地址空间中创建一个新的映射:
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
RETURN VALUEOn success, mmap() returns a pointer to the mapped area.
通过这种方式,文件内容可以通过指针直接访问addr,就像访问普通的内存数组一样,这极大地提高了文件操作的效率和直观性。
以下是关于其行为的一些关键点:
(1)参数addr指定了新映射的起始地址。如果addr为NULL,内核会选择一个(页对齐的)地址来创建映射;这是创建新映射的最便携方法。如果addr不为NULL,内核将其作为放置映射的提示;对于Linux系统,内核会选择一个靠近的页边界(但始终大于或等于/proc/sys/vm/mmap_min_addr指定的值)并尝试在那里创建映射。如果该地址已经存在其他映射,内核会选择一个新的地址,可能与提示相关或不相关。新映射的地址将作为调用的结果返回。
(2)参数length指定映射的长度,必须大于0。
(3)对于文件映射(与匿名映射相对应,参见MAP_ANONYMOUS),映射的内容使用文件描述符fd引用的文件(或其他对象)中的从偏移量offset开始的length字节进行初始化。offset必须是sysconf(_SC_PAGE_SIZE)返回的页面大小的倍数。
总结一下,mmap()函数在调用进程的虚拟地址空间中创建一个新的映射,内核根据提供的地址或提示选择一个合适的地址来确定映射的起始位置。对于文件映射,映射的内容从文件中的指定偏移量处开始进行初始化。
在mmap()调用返回后,文件描述符fd可以立即关闭而不会使映射失效。
参数prot描述了映射的期望内存保护方式(不能与文件的打开模式冲突)。它可以是PROT_NONE,或者是以下标志位的按位或:
PROT_EXEC:页面可执行。
PROT_READ:页面可读取。
PROT_WRITE:页面可写入。
PROT_NONE:页面不可访问。
参数flags确定对映射的更新是否对其他映射同一区域的进程可见,以及是否将更新传递到底层文件。这个行为是通过在flags中包含以下值中的一个来确定的:
(1)MAP_SHARED:共享映射。对映射的更新对其他映射同一区域的进程可见,并且(对于基于文件的映射而言)会传递到底层文件。(要精确控制何时将更新传递到底层文件,需要使用msync(2)函数。)
使用MAP_SHARED标志可以实现共享内存,让多个进程可以共享同一区域的映射,并且对映射的更新可以相互可见。对于基于文件的映射,更新也会被传递到底层文件。需要注意的是,要精确控制更新何时传递到底层文件,可以使用msync(2)函数。
NAMEmsync - synchronize a file with a memory map
(2)MAP_PRIVATE:用于创建私有的写时复制(copy-on-write)映射。对映射的更新对于其他映射同一文件的进程不可见,并且不会传递到底层文件。在mmap()调用后对文件进行的更改是否在映射的区域中可见是未指定的。
使用MAP_PRIVATE标志可以创建一个独立的映射副本,对该映射的写入操作会在需要时进行写时复制,即只有在修改映射的页面时才会复制相应的页面内容,以确保每个进程都有自己的私有副本。这样,对映射的更新不会影响其他进程的映射,并且不会对底层文件进行实际的修改。
(3)MAP_ANONYMOUS:用于创建一个不由任何文件支持的映射,其内容被初始化为零。fd参数会被忽略;但是,一些实现要求如果指定了MAP_ANONYMOUS(或MAP_ANON),则fd必须为-1,因此可移植的应用程序应确保这一点。offset参数应为零。只有在Linux内核2.4及更高版本上,才支持将MAP_ANONYMOUS与MAP_SHARED结合使用。
使用MAP_ANONYMOUS标志创建的映射不与任何文件相关联,其内容被初始化为零。这种映射通常用于实现匿名内存,用于共享数据或作为临时存储。由于没有与文件的关联,对映射的更改不会影响任何文件,并且不需要指定文件描述符(fd参数被忽略)。
如下图所示:
Memory mmaping segment 就属于内存映射区。
二、代码演示
2.1 mmap使用场景
物理内存页主要分为两种:一种是匿名页,另一种是文件页。
根据物理内存页的类型分类,内存映射自然也分为两种:一种是虚拟内存对匿名物理内存页的映射,另一种是虚拟内存对文件页的映射。
(1)匿名页(Anonymous Pages):匿名页是一种没有与之关联的文件的内存页。它们通常用于存储进程的堆栈、堆分配的内存以及共享内存等。匿名页的内容在映射时可以初始化为零或未初始化状态,不会与任何文件进行关联。
(2)文件页(File Pages):文件页是与文件关联的内存页。它们用于将文件的内容映射到进程的地址空间,允许进程通过内存访问文件的内容,而无需直接进行读取和写入操作。文件页可以用于读取文件的内容,也可以用于将修改的数据写回文件。
(1)私有匿名映射:malloc分配大内存在glibc中对应的mmap()实现,以及BSS 段,堆,栈。
(2)私有文件映射:映射动态库,文件的text、data段。
(3)共享匿名映射:用于进程间(父子进程)共享内存。
(4)共享文件映射:用于进程间(不同的进程)共享内存,通信。
(5)其他,比如大页内存。
2.2 私有匿名映射
私有匿名映射使用一下标志位:
MAP_PRIVATE | MAP_ANONYMOUS
其中fd = -1,与文件没有关联。
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>#define SIZE 4096int main() {// 创建一个私有匿名映射void* addr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);if (addr == MAP_FAILED) {perror("mmap failed");exit(1);}// 在映射的内存中进行读写操作int* data = (int*)addr;*data = 42;printf("Value at mapped memory: %d\n", *data);// 取消映射if (munmap(addr, SIZE) == -1) {perror("munmap failed");exit(1);}return 0;
}
# ./a.out
Value at mapped memory: 42
私有匿名映射(mmap/brk/malloc)申请的内存是一段虚拟地址空间,当没有在这段虚拟地址空间写入的时候,没有对应的物理内存,只有在这段虚拟地址空间写入的时候,就会发生缺页异常,然后分配对应的物理地址,建立虚拟地址空间和物理地址的影映射关系。
从上图我们可以看到进程虚拟内存空间中的 BSS 段,堆,栈这些虚拟内存区域都是私有匿名映射区域,glibc 中的 malloc函数当申请比较大的内存时,也使用私有匿名映射区域。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>int main() {size_t size = 256 * 1024; // 128K内存的大小getpid();// 使用malloc分配内存char* buffer = (char*)malloc(size);if (buffer == NULL) {perror("malloc");exit(1);}getpid();// 内存分配成功,可以使用buffer指针访问分配的内存// 这里可以进行读取、写入或处理数据的操作// 释放内存free(buffer);return 0;
}
# strace ./a.out
......
getpid() = 101479
brk(NULL) = 0x55a399f12000
brk(0x55a399f33000) = 0x55a399f33000
mmap(NULL, 266240, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fec8b5ea000
getpid() = 101479
munmap(0x7fec8b5ea000, 266240) = 0
mmap(NULL, 266240, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fec8b5ea000
2.3 私有文件映射
私有文件映射标志位:
MAP_PRIVATE
其中fd与文件有关联。
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>#define FILE_PATH "example.txt"
#define SIZE 4096int main() {// 打开文件int fd = open(FILE_PATH, O_RDWR);if (fd == -1) {perror("open failed");exit(1);}// 获取文件大小struct stat st;if (fstat(fd, &st) == -1) {perror("fstat failed");exit(1);}off_t file_size = st.st_size;// 创建私有文件映射void* addr = mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);if (addr == MAP_FAILED) {perror("mmap failed");exit(1);}// 在映射的内存中进行读取操作char* data = (char*)addr;printf("Content of the file:%s\n", data);// 在映射的内存中进行写入操作sprintf(data, "Hello, World!");printf("Content of the file:%s\n", data);// 取消映射if (munmap(addr, file_size) == -1) {perror("munmap failed");exit(1);}// 关闭文件if (close(fd) == -1) {perror("close failed");exit(1);}return 0;
}
example.txt 文件的内容是 111。
读取其内容,然后写入:
# cat example.txt
111
# ./a.out
Content of the file:111Content of the file:Hello, World!
# cat example.txt
111
可以看到对私有文件映射区域的修改不会修改实际的文件。
私有文件映射允许多个进程将文件的内容映射到各自的虚拟内存空间中,但对映射的修改只反映到各自的文件页上,而不会影响其他进程的文件页。这种方式可以用于加载二进制可执行文件的代码段和数据段到进程的虚拟内存空间中以及加载动态库。
从上图我们可以看到进程虚拟内存空间中的 text 段,data 段和.so动态库这些虚拟内存区域都是私有文件映射区域。
2.4 共享匿名映射
私有匿名映射使用一下标志位:
MAP_SHARED | MAP_ANONYMOUS
其中fd = -1,与文件没有关联。
#include <sys/mman.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>#define SIZE 4096int main() {// 创建共享匿名映射void* addr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);if (addr == MAP_FAILED) {perror("mmap failed");exit(1);}// 创建子进程pid_t pid = fork();if (pid == -1) {perror("fork failed");exit(1);} else if (pid == 0) {// 子进程写入数据到共享内存char* data = (char*)addr;sprintf(data, "Hello from the child process!");// 子进程结束exit(0);} else {// 等待子进程结束wait(NULL);// 父进程读取共享内存中的数据char* data = (char*)addr;printf("Content of shared memory: %s\n", data);// 解除映射if (munmap(addr, SIZE) == -1) {perror("munmap failed");exit(1);}}return 0;
}
# ./a.out
Content of shared memory: Hello from the child process!
共享匿名映射在父子进程之间共享内存和实现进程间通信时非常有用。它是一种特殊的共享文件映射,不需要依赖具体的文件,而是将映射的内存区域与进程间共享。
父子进程通信:父进程可以创建一个共享匿名映射,并将其传递给子进程。子进程可以访问并修改映射的内存区域,从而与父进程进行通信。这种方法常用于进程间共享数据或传递消息。
父进程和子进程其页表项是相同的。只要父子进程中的一个发生了缺页中断,就给分配物理内存,建立其虚拟内存和物理内存之间的映射,由于父子进程的页表项是相同的,且共享内存,那么另一个发生缺页中断时,对应页表项已经建立了到物理地址的映射关系。
2.5 共享文件映射
共享文件映射标志位:
MAP_SHARED
其中fd与文件有关联。
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>#define SIZE 4096
#define FILE_NAME "shared_memory"int main() {// 创建共享文件int fd = open(FILE_NAME, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);if (fd == -1) {perror("open failed");exit(1);}// 设置共享文件大小if (ftruncate(fd, SIZE) == -1) {perror("ftruncate failed");exit(1);}// 创建共享文件映射void* addr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (addr == MAP_FAILED) {perror("mmap failed");exit(1);}// 创建子进程pid_t pid = fork();if (pid == -1) {perror("fork failed");exit(1);} else if (pid == 0) {// 子进程写入数据到共享内存char* data = (char*)addr;sprintf(data, "Hello from the child process!");// 将修改刷新到文件if (msync(addr, SIZE, MS_SYNC) == -1) {perror("msync failed");exit(1);}// 子进程结束exit(0);} else {// 等待子进程结束wait(NULL);// 父进程读取共享内存中的数据char* data = (char*)addr;printf("Content of shared memory: %s\n", data);// 解除映射if (munmap(addr, SIZE) == -1) {perror("munmap failed");exit(1);}// 关闭文件if (close(fd) == -1) {perror("close failed");exit(1);}// 删除共享文件if (unlink(FILE_NAME) == -1) {perror("unlink failed");exit(1);}}return 0;
}
# ./a.out
Content of shared memory: Hello from the child process!
共享文件映射在多进程之间共享内存、实现进程间通信,并且避免写时复制的场景中非常常见。
在这种情况下,多个进程可以通过将同一个文件映射到它们的地址空间来实现共享内存。这意味着它们可以直接读取和写入映射的内存区域,而无需进行复制操作。
共享文件映射的优势在于,多个进程可以通过将同一个文件映射到它们的地址空间来共享数据,而无需进行复制。这对于需要频繁读写共享数据的场景非常有用,因为它避免了写时复制带来的性能开销。
需要注意的是,共享文件映射使用文件作为底层存储介质,因此对于共享内存的读写操作会反映到文件中。这也意味着共享文件映射在进程终止后依然存在,并且可以被其他进程访问。因此,需要小心处理共享文件映射的生命周期和访问权限,以确保数据的一致性和安全性。
参考
https://mp.weixin.qq.com/s/AUsgFOaePwVsPozC3F6Wjw
相关文章:

Linux mmap系统调用
文章目录 前言一、mmap()函数简介二、代码演示2.1 mmap使用场景2.2 私有匿名映射2.3 私有文件映射2.4 共享匿名映射2.5 共享文件映射 参考 前言 NAMEmmap, munmap - map or unmap files or devices into memorySYNOPSIS#include <sys/mman.h>void *mmap(void *addr, siz…...

VSCode搭建ARM开发环境
为了构建Cortex M系列单片机免费开源的开发环境,网络上了解来看VSCODEGCCJLINK是一套比较高效的组合方式,下面记录环境搭建的流程。 我这边的PC环境为 WIN7专业版64bit。 需要用到的工具 Visual Studio CodeSTM32CubemxARM GCC 交叉编译工具链&#x…...

centos7 python3.12.1 报错 No module named _ssl
https://blog.csdn.net/Amio_/article/details/126716818 安装python cd /usr/local/src wget https://www.python.org/ftp/python/3.12.1/Python-3.12.1.tgz tar -zxvf Python-3.12.1.tgz cd Python-3.12.1/ ./configure -C --enable-shared --with-openssl/usr/local/opens…...

探索HTTP协议:网络通信的基石
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...

几大开源免费的 JavaScript 富文本编辑器测评
MarkDown 编辑器用的时间长了,发现发现富文本编辑器用起来是真的舒服。 一直以来写博客都是用的 MarkDown 编辑器,MarkDown 文档简单方便,使用几个简单的符号就可以定义出样式统一的富文本内容。写博客的时间长了,小玖就越来越排…...

MongoDB聚合运算符:$dateFromString
文章目录 语法使用使用例子对规则进行说明格式指示符 举例日期转换错误处理onError空值的粗粒 onNull $dateFromString聚合运算符将日期时间字符串转换为日期对象。 语法 { $dateFromString: {dateString: <dateStringExpression>,format: <formatStringExpression&g…...

Postman(注册,使用,作用)【详解】
目录 一、Postman 1. Postman介绍 2. 安装Postman 3. 注册帐号再使用(可保存测试记录) 4. 创建workspace 5. 测试并保存测试记录 一、Postman postman工具可以发送不同方式的请求,浏览器只能发送get请求(所有用这个工具) 在前后端分离开发模式下,前端技术人员…...

电商数据分析18——电商广告投放的数据分析与优化
目录 写在开头1. 电商广告投放的挑战与机遇1.1 广告投放的主要目标与挑战1.2 广告数据分析的价值 2. 数据分析在广告投放中的应用2.1 目标受众分析与精准定位2.2 广告效果评估与ROI分析2.3 A/B测试与广告内容优化 3. 广告投放优化的实践案例3.1 案例分享:精准定向提…...

Docker下Jenkins打包java项目并部署
docker 构建Jenkins sudo docker run --namezen_haslett --userjenkins --privilegedtrue --volume/home/cyf/server/jenkins/jenkins_home:/var/jenkins_home -v /usr/lib/jvm/java-17-openjdk-amd64:/usr/lib/jvm/java-17-openjdk-amd64 -v /usr/lib/maven/apache-mav…...

android 快速实现 recyclerview 的所有item 都执行动画
1.在adapter 里面重写onViewAttachedToWindow 和 onViewDetachedFromWindow 两个方法 package com.example.widget;import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.LinearInterpolator; import android.view.animat…...

Excel转pdf
1、excel-内存值--Workbook 转pdf /** * excel To pdf * * param outPath 输出路径 * param workbook excel-内存值 * throws IOException */ public static void excelToPdf(String outPath,Workbook workbook) throws IOException, DocumentException { Document documentnul…...

QT:用opencv的KNN识别图片中的LED数字(一)
前言 一款功能测试的软件demo,使用了QT作为界面,主要使用了opencv的KNN识别,使用gstreamer作为管道,用来打开图片。后期会写一篇打开摄像头实时识别的文章。 (正在写,未完成,稍候) 效果一预览: 效果二预览: 效果三预览: 正在写。。。 设计思路 1. 软件UI设计 2. …...

leetcode72. 编辑距离
leetcode72. 编辑距离 题目 思路 dp[i][j] 代表 word1 到 i 位置转换成 word2 到 j 位置需要最少步数,所以, 当 word1[i] word2[j],dp[i][j] dp[i-1][j-1]; 当 word1[i] ! word2[j],dp[i][j] 1 min(dp[i-1][j-1]…...

R语言的数据类型与数据结构:向量、列表、矩阵、数据框及操作方法
R语言的数据类型与数据结构:向量、列表、矩阵、数据框及操作方法 介绍向量列表矩阵数据框 介绍 R语言拥有丰富的数据类型和数据结构,以满足各类数据处理和分析的需求。本文将分享R语言中的数据类型,包括向量、列表、矩阵、数据框等ÿ…...

第十六章 构建和配置 Nginx 以与 Web 网关配合使用 (Windows) - 将 NSD 与 Nginx 结合使用
文章目录 第十六章 构建和配置 Nginx 以与 Web 网关配合使用 (Windows) - 将 NSD 与 Nginx 结合使用将 NSD 与 Nginx 结合使用CSPNSD_pass hostname:portNum;CSP on; and CSP off;CSPFileTypes filetype1[ filetype2...];CSPNSD_response_headers_maxsize size;CSPNSD_connect_…...

数据中台:数字中国战略关键技术设施
文章目录 每日一句正能量前言为何要建设数据中台数据中台建设痛点数据中台学习资料聚焦前沿,方法论体系更新与时俱进,紧跟时代热点深入6大行业,提炼实践精华大咖推荐,数字化转型必备案头书购买链接赠书活动 每日一句正能量 人生之…...

mac上更改vscode快捷键
以移动当前行代码为例 mac上的vscode,默认移动当前行代码的快捷键是⌥↑即option↑按键 现在我想改成command↑ 步骤如下 1.打开vscode-code-首选项-键盘快捷键 2.打开快捷键列表 3.输入move line,找到要改动的这个快捷键 当前行-右键-更改键绑定&…...

Day18:信息打点-小程序应用解包反编译动态调试抓包静态分析源码架构
目录 小程序获取-各大平台&关键字搜索 小程序体验-凡科建站&模版测试上线 小程序抓包-Proxifier&BurpSuite联动 小程序逆向-解包反编译&动态调试&架构 思维导图 章节知识点 Web:语言/CMS/中间件/数据库/系统/WAF等 系统:操作系…...

真实案例分享:MOS管电源开关电路,遇到上电冲击电流超标
做硬件,堆经验。 分享一个案例:MOS管电源开关电路,遇到上电冲击电流超标,怎么解决的呢? 下面是正文部分。 —— 正文 —— 最近有一颗用了挺久的MOSFET发了停产通知,供应链部门找到我们研发部门,…...

LCR 164. 破解闯关密码
解题思路: 贪心 class Solution {public String crackPassword(int[] password) {String[] strs new String[password.length];for(int i 0; i < password.length; i)strs[i] String.valueOf(password[i]);Arrays.sort(strs, (x, y) -> (x y).compareTo(…...

【鸿蒙 HarmonyOS 4.0】常用组件:List/Grid/Tabs
一、背景 列表页面:List组件和Grid组件; 页签切换:Tabs组件; 二、列表页面 在我们常用的手机应用中,经常会见到一些数据列表,如设置页面、通讯录、商品列表等。下图中两个页面都包含列表,“…...

打造经典游戏:HTML5与CSS3实现俄罗斯方块
🌟 前言 欢迎来到我的技术小宇宙!🌌 这里不仅是我记录技术点滴的后花园,也是我分享学习心得和项目经验的乐园。📚 无论你是技术小白还是资深大牛,这里总有一些内容能触动你的好奇心。🔍 &#x…...

什么是系统工程(字幕)48
0 00:00:00,760 --> 00:00:03,550 那这里我们要说一下 1 00:00:04,050 --> 00:00:06,163 你看,刚才我们这里 2 00:00:06,163 --> 00:00:06,740 3 00:00:07,440 --> 00:00:13,460 这个我们把它说成,打开这个,关闭这个 4 00:00:…...

Jenkins发送邮件、定时执行、持续部署
集成Allure报告只需要配置构建后操作即可。但如果是web自动化,或是用HTMLTestRunner生成报告,构建后操作要选择Publish HTML reports,而构建中还要添加Execute system Groovy script插件,内容: System.setProperty(&q…...

Mysql的Cardinality值
什么是Cardinality值? Cardinality值是Mysql做索引优化时一个非常关键的值,优化器会根据这个值来判断是否使用这个索引,它表示索引中唯一值的数目估计值,该值应该尽可能接近1,如果非常小,则用户需要考虑是否…...

数据结构 - 栈和队列
本篇博客将介绍栈和队列的定义以及实现。 1.栈的定义 栈是一种特殊的线性表,只允许在固定的一端进行插入和删除数据,插入数据的一端叫做栈顶,另一端叫做栈底。栈中的数据遵守后进先出的原则 LIFO (Last In First Out)。 插入数据的操作称为压…...

C++:模版进阶 | Priority_queue的模拟实现
创作不易,感谢三连支持 一、非类型模版参数 模板参数分类为类型形参与非类型形参。 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。 非类型形参,就是用一个常量作为类(函数)模板的一个参数&…...

【刷题记录】详谈设计循环队列
下题目为个人的刷题记录,在本节博客中我将详细谈论设计循环队列的思路,并给出代码,有需要借鉴即可。 题目:LINK 循环队列是线性表吗?或者说循环队列是线性结构吗? 对于这个问题,我们来看一下线…...

人工智能|机器学习——k-近邻算法(KNN分类算法)
1.简介 k-最近邻算法,也称为 kNN 或 k-NN,是一种非参数、有监督的学习分类器,它使用邻近度对单个数据点的分组进行分类或预测。虽然它可以用于回归问题,但它通常用作分类算法,假设可以在彼此附近找到相似点。 对于分类…...

乐得瑞 1C to 2C快充线:引领充电数据线新潮流,高效快充解决接口难题
随着科技的不断进步,数据线的接口种类也日渐繁多,但在早些时候,三合一和二合一的数据线因其独特的设计而备受欢迎。这类数据线通常采用USB-A口作为输入端,并集成了Micro USB、Lightning以及USB-C三种接口,满足了当时市…...