LWN:BPF程序可更灵活地访问内存!

Out-of-memory victim selection with BPF Linux内核默认允许进程分配超过物理内存的限制,这种策略虽然提高了内存利用率,但在内存耗尽时会触发OOM-killer机制杀死进程。现有机制通过oom_badness()函数计算进程的OOM分数,但选择机制仍不够精准。最新补丁提出通过BPF(Berkeley Packet Filter)编程接口实现更灵活的进程选择策略,允许自定义评估函数动态决定OOM受害者。该方案支持四种决策结果:继续默认评估、中止选择、跳过当前进程或直接选定目标。虽然开发者对具体实现方式仍有分歧,但这一创新为优化内存管理提供了新思路, 阅读详情

关注了就能看到更多这么棒的文章哦~

More flexible memory access for BPF programs

October 21, 2022
This article was contributed by David Vernet
DeepL assisted translation
https://lwn.net/Articles/910873/

BPF 程序中,所有内存访问都先要使用 verifier 以静态方式进行安全检查,verifier 在允许程序运行之前会对其进行整体分析。虽然这样就可以让 BPF 程序在内核空间能安全运行了,但它限制了 BPF 程序使用指针的方式。最近碰到的这种类型的一个限制就是,当 BPF 程序被加载时,BPF 程序中由指针所指向的内存区域的大小必须是静态检查状态下就是已知的。Joanne Koong 最近提供的一个 patch set 增强了 BPF,可以支持加载带有这种指向动态指定大小(dynamically sized)的内存区域的指针的 BPF 程序。

Verifying kernel pointers in BPF programs

为了要能安全地加载一个 BPF 程序,verifier 必须验证该程序中的所有内存访问都不会使内核 crash。这个工作很复杂,因为 "memory" 在程序中可能代表了各种不同的上下文。例如,一些指针可能指向的是 BPF 程序的 stack,而其他指针,如 kptrs,可能指向的是一个通过 kfunc 从 main kernel 传递过来的结构。这两种类型的指针都有各种使用场景,在这些场景中访问,有的是安全,有的是不安全的,因此需要 verifier 中专门一部分代码来确保对它们的任何访问都是安全的。对于 stack 指针,verifier 需要确保所有可能会读取的偏移量都是在程序的 active stack 区域内的。对于从 kfunc 返回的 kptrs,verifier 必须确保所读的偏移量都在 struct 的 BPF Type Format(BTF)信息所指定的结构的范围内(write access 受到更严格的控制)。

然而,虽然这两种不同类型的内存区域的边界是不一样的,但它们都要求所有相关 read 的 offset 都必须是静态固定的,从而能够让 verifier 来确保访问的安全性。当然,这种限制禁止了任何需要指向动态大小的数据区域的指针。例如,BPF ring-buffer map type 允许 BPF 程序向 ring buffer 中写入一些内容,供用户空间读取使用。如果所有的内存引用都需要在运行时就静态地确定下来,那么 BPF 程序就只能写那些在程序加载时已知 size 小的位置了。然而,实际上经常需要能够写入那些可以在运行时动态改变大小的内容。

dynptrs - Referencing dynamically size memory

Koong 的 patch set 添加了对 BPF 程序中访问动态大小的内存区域的支持,这个新特性称为 dynptrs。dynptrs 的主要思想是将一个指向拥有动态 size 的数据区域的指针跟 verifier 以及一些 BPF helper 函数为了判断这个区域是有效的而使用的 metadata 关联起来。Koong 的 patch set 在一个新定义的 bpf_dynptr 结构中建立了这种关联。这个 struct 对 BPF 程序来说是不透明的;在内核中,它的定义是:

/* the implementation of the opaque uapi struct bpf_dynptr */
struct bpf_dynptr_kern {
  void *data;
  u32 size;
  u32 offset;
} __aligned(8);

这个动态区域的 size 被存储在一个 32 位的 unsigned int 数里,最高 8 位被保留用于 dynptr 本身的 metadata。其中最高 bit 表示 dynptr 是否是只读的,接下来的七个 bit 表示 dynptr 所指向的内存类型。后面给 size 只留下了 24 位,也就意味着一个 dynptr 可以指向一个不大于 16MB 的区域。这个 patch set 添加了两种类型的 dynptrs 支持。BPF_DYNPTR_TYPE_LOCAL,是指向程序本地可以访问到的内存,比如一个 map 值;BPF_DYNPTR_TYPE_RINGBUF,指向 BPF_MAP_TYPE_RINGBUF map 中的数据。

Dynptrs 是通过一系列的 helper 函数来创建和访问的。可以使用 bpf_dynptr_read()辅助函数来读取 dynptr 中的内容,或者使用 bpf_dynptr_write()来对可写的 dynptr 进行写入。bpf_dynptr_read() 是用来从 dynptr 数据区域复制内存内容到另一个指定的 buffer 的,而 bpf_dynptr_write()将从程序中的一个 buffer 复制数据到 dynptr 数据区域。在执行 copy 动作之前,helper 函数会检查这里提供的 length 和 offset 是否指向 dynptr 内存区域中的有效部分。如果用户需要直接访问包含在 dynptr 中的内存区域,他们可以使用 bpf_dynptr_data() 这个 helper 函数,尽管在这种情况下,被请求的内存区域的 size 必须是静态可确定的,以便 verifier 能确保对它的任何访问都是有效的。

Local memory dynptrs

BPF_DYNPTR_TYPE_LOCAL,即 local dynptr 支持,是由该系列的第二个 patch 所添加的。BPF 中的 "local memory" 可以指程序使用的几种不同类型的内存,例如,map value、map key 和 stack memory 等。Koong 的 patch set 允许通过一个新的 helper 函数 bpf_dynptr_from_mem() 来创建本地 dynptrs。尽管存在各种各样的 local memory type,但是这个 patch set 的初版只添加了对创建指向 map value 的 local dynptrs 的支持。这个限制可能是因为 verifier 已经确保了那些收到了指向 map value 的指针的 helper 函数都能正确地初始化以及确定 size,从而使 dynptrs 的初版实现可以尽可能简单。

其他 local memory type 在未来也可以被支持,尽管每一种内存类型都需要 verifier 中添加额外逻辑来验证 bpf_dynptr_from_mem() 的输入参数。虽然在这组 patch set 中没有说明何时(或是否)会添加其他类型的 local memory,但添加对它们的支持似乎是保险的做法,这样才能在使用 API 时得到一致性更好的体验。在最初的实现中,用户没有办法知道 local dynptr 只支持 map value,于是会看到他们的程序被 verifier 拒绝了。

Dynamically sized ring-buffer entries using dynptrs

如上所述,static-sizing 的限制迫使内核在 BPF_MAP_TYPE_RINGBUF map 中所发布(published)的 ring buffer 中每一条的 size 在程序加载时都必须是静态已知的。为了解决这个问题,Koong 给出了一个 patch,其中定义了一个新的 BPF_DYNPTR_TYPE_RINGBUF 类型的 dynptr。该 patch 包括 bpf_ringbuf_reserve_dynptr() helper 函数,用于 reserve 动态 size 的 ring-buffer entry,以及 bpf_ringbuf_submit_dynptr()和 bpf_ringbuf_discard_dynptr()分别用于将这个 entry 发布到 ring buffer 区域或 discard 丢弃掉。这些 API 跟已有的用于 reserve 和 post 静态 size 的 ring buffer entry 的 API 密切相关。

Dynptrs 也被用在了新的 BPF_MAP_TYPE_USER_RINGBUF map type 这组 patch set 中,它最近被合并到了 bpf-next 中,这是笔者的 patch。这个 map type 允许用户空间向 BPF 程序发布 ring buffer entry,它提供了一个 bpf_user_ringbuf_drain() helper 函数,允许 BPF 程序从 ring buffer 中提取并消化 entry,并对每个 entry 都调用一些指定的 callback 函数。这个 callback 会接收一个指向 ring buffer entry 的 dynptr 来作为它的第一个参数。为了读取这些 entry,BPF 程序可以简单地使用 bpf_dynptr_read() 或者 bpf_dynptr_data(),如上面已经介绍过的。

Holding off on a kmalloc() type dynptr

有一点需要注意的是,上述所支持的 dynptr 类型中没有一个是用在通过 kmalloc()分配的内存上的。乍一看,这似乎是一个非常常见的使用场景,事实上在早期版本的 patch set 中提供过一个 BPF_DYNPTR_TYPE_MALLOC dynptr type 来给出支持。然而,这个 type 最终被放弃了,因为在讨论中发现了一些细微但是会影响到根基的问题,需要在支持这个功能之前先解决掉。

例如,在对 patch set 的回复中,Daniel Borkmann 提出了一个问题,就是应该由哪个 cgroup(memcg)来对这里所分配的内存进行统计(收费?)的问题。这一点确实是个好问题;在内核中代表用户空间进程所进行的分配,应该向包含了分配进程的 memcg 来收取费用。但是不是很容易能确定是哪个进程。选择加载程序的进程的 memcg 看起来挺合理,但是正如 Alexei Starovoitov 所指出的,该进程(和它的 memcg)在程序被加载后不一定还存在了。

Starovoitov 提出的另一个问题是,由 BPF 程序所分配的内存是否应该被记入 memcg 中。内核中的大多数 kmalloc()调用都没有采用这种方式来统计进去,而且,正如在 2022 年 Linux 内核维护者峰会上所强调的,BPF 程序是内核程序的实例,而不是用户程序。Borkmann 回应说,也许合理的解决方案是允许用户明确指定要负责承担费用的 memcg,而不是像 BPF 目前那样隐含指定加载任务的 memcg。这样一来,使用者就需要获得一个 memcg 的文件描述符,并在程序被加载时将其传递给内核。如果没有传递描述符,那么默认行为就是不把这部分内存算到任何一个 memcg 上去。这个建议得到了 Starovoitov 和 Andrii Nakryiko 的好评,尽管讨论在没有确定结论的情况下渐渐结束,不过 Koong 最终发送了一个后续 patch,将 BPF_DYNPTR_TYPE_MALLOC 替换为 BPF_DYNPTR_TYPE_LOCAL。

在 BPF 程序中进行动态分配,这是一个有趣的前景,所以一旦内存统计的解决方案明确下来,似乎就有可能再来看看这个功能的实现了。

Ongoing work with dynptrs

目前正在进行的工作是增加新的 dynptr 类型,从而支持网络协议栈代码中的后续 BPF 使用场景。在最近的一个 patch set 中,Koong 提议增加两种新的 dynptrs 类型,一种是用在包含了 socket buffer 的内存区域上,另一种是用在包含了一个 eXpress Data Path(XDP)buffer 的内存区域上。这些 dynptr 类型的是一样的,主要就是允许了 BPF 程序使用更符合人体工程学的 API 来读取(reading)和修改(mutating)buffer 里的内存内容。

比如假设用户想解析 TCP 数据包中包含在 struct xdp_md buffer 中的 type-length-value(TLV)header。这个结构里包含了 data 和 data_end 字段,分别代表了 packet 中数据区域的开始和结束。每个 TLV header 中包含了一个 header entry,其中包含 type 的编码,后面是 header value 的长度,然后是符合这个 length 的数据。header 中的 length 项的值可以在运行时根据不同的 packet 和 header type 来改变,所以在一个 packet 中遍历 header 需要的指针偏移量就不是一个静态确定的数值。如果没有 dynptrs 的话,用户将不得不对 packet header 的每一次读取进行显式检查,以确保它适合 xdp_md buffer 的 data 和 data_end 区域。有了 dynptrs,只要简单利用一个从前一个未知类型的 header TLV 中计算得来的 offset 调用一下 bpf_dynptr_data() 就可以获得指向下一个 header TLV 的指针,然后检查一下从 helper 这里获取的指针是不是 non-NULL 就好。

虽然这并没有实现新的使用场景,但它确实解决了 BPF networking program 中的一个重要的可用性问题,这也是一个经常会收到抱怨的地方。此外,它使得生成的 BPF program 代码受 Clang 和 LLVM 的改动的影响更加小,目前这些部分的改动有时会导致 verifier 拒绝了一个以前认为是 safe 的程序。

到目前为止,这些 patch 还没有收到任何强烈的反对意见,而且似乎也不太可能。此时此刻有另一个 patch set 也被提交到 upstream,其中增加了更多的 dynptr helper 函数。这些函数可能是未来另一篇文章的主要内容。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

format,png

5、AIX系统硬件管理与故障诊断全解析 本文全面解析了AIX系统的硬件管理与故障诊断方法,涵盖系统管理服务(SMS)的使用、设备兼容性配置、设备配置数据库结构、常用命令如cfgmgr、lsattr、smitty devices的操作详解,并深入介绍了错误日志管理机制,包括errdemon、errpt、errlogger等命令的应用。通过实际案例分析和流程图展示,帮助系统管理员掌握从问题怀疑到诊断执行再到日志清理的完整流程,同时提供注意事项与最佳实践,助力AIX系统的稳定运行与高效维护。 阅读详情

相关推荐

STM32F103电压采集proteus仿真

基于HAL库,仿真单片机型号为stm32f103R6,代码keil5。通过单片机的ADC采集功能,测量四路电压,数据经由DMA传输,并将结果通过串口输出,结果比较确,误差在0.01V左右。目前仿真采集四路比较稳定,多了会乱码。proteus版本8.11安装包链接:https://pan.baidu.com/s/1yhNKLl1lGSU9KU0tTuAxcg?pwd=dxe8提取码:dxe8项目下载地址:.

weixin_42490599的博客 4766

libbpf-bootstrap开发指南:使用ringbuf 进行通信 - bootstrap

做全网最好的libbpf-bootstrap开发指南

阿呆的隐秘角落 1710

【学习路线】 游戏客户端开发入门到进阶

游戏开发是一个交叉性极强的技术领域,对开发者提出了高维度的要求。作为初学者,不要急于求成,打牢基础、注重原理、勤于实践,你的成长将会非常快速而稳健。希望这份学习路线与书单能为你指明方向,迈出职业开发者的第一步。

hhw_hhw的博客 2127

bpf-ringbuf-examples 编译报错error: use of undeclared identifier ‘BPF_MAP_TYPE_RINGBUF‘

错误如下: ringbuf-reserve-submit.bpf.c:11:15: error: use of undeclared identifier 'BPF_MAP_TYPE_RINGBUF' __uint(type, BPF_MAP_TYPE_RINGBUF); ^ 1 error generated. 查找系统的bpf.h头文件,查看enum bpf_map_type 中没有对应的BPF_MAP_TYPE_RINGBUF。 我目...

guoguangwu的专栏 1197

MFC使用Websocket通讯介绍和实例(附完整源码)

踩坑点,下载了好几个boost版本编译,经常出问题,后来搜索了好久资料才发现如下评论:WebSocket++0.8.2 has been tested through Boost 1.72。WebSocketpp依赖于boost,因此在使用WebSocketpp前需要编译Boost;所以我们直接下载Boost 1.72的windows版的进行安装,安装完成还需要编译;WebSocketpp也叫WebSocket++;服务编译好之后,使用。

weixin_42530684的博客 1171

【Realtek sdk-3.4.14b】RTL8197FH+RTL8363NB当WAN/LAN IP冲突的时候,再次切换回正常的WAN,出现电脑无法上网也无法ping通设备网关问题分析及解决方案

如上图所示,当WAN和LAN网段冲突的时候,会导致路由设备异常,无法上网,但是修改为不冲突的网段之后,依然无法上网,同时下级PC也无法ping通路由设备,正常的如下图所示如上图所示,正常网络拓扑是可以正常的,但是在上级和路由LAN冲突的时候,再次切换为正常的网络也是异常问题复现步骤1.路由设备WAN口接到一个和本级路由是相同网段的设备2.拔掉WAN口,将WAN口接到一个和本级路由是不同网段的设备3.此时PC无法访问路由器。

wgl307293845的博客 665

BumbleBee: 如丝般顺滑构建、交付和运行 eBPF 程序

1. 前言几天前,Solo.io[1] 公司在官网博客宣布了开源了一个名称为 BumbleBee[2] 的新项目。该项目专注于简化构建 eBPF 工具的门槛和优化使用体验,通过将 eBPF...

云原生实验室 714

Intellij IDEA 使用教程(一)Jdk安装和配置

一.JDK下载 这里下载的是Java SE 8u181 64位版本。可以根据自己电脑配置来选择不同版本。 官网下载JDK 二.安装JDK 双击运行jdk-8u181-windows-x64.exe安装程序 选择安装目录,然后点击下一步 等待安装 安装JRE,就是java的运行环境,可以安装在java目录下 等待安装 安装完成。点击关闭。 三.java环境变量配置 关于环境变量的配置,...

岁月不饶人,我亦未曾饶过岁月 4052

【eBPF】使用ringbuf过程中的常见错误

这是一个简单的 eBPF 程序,用于跟踪静态检查点 openat 函数。该程序通过利用 eBPF 的 tracepoint 功能,捕获内核中的 sys_enter_openat 事件,这是每次执行 openat 系统调用时触发的事件。通过这个程序,我们可以实时监控文件打开的操作,并且记录与每个 openat 调用相关的进程信息及其打开的文件路径。

CeerDecy的博客 542

【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能

在探讨计算机性能优化的旅程中,我们常常被引导去关注算法的复杂度、代码的优化,或是更高效的数据结构。然而,有一个经常被忽视的角色在幕后默默地影响着程序的运行效率——那就是缓存(Cache)。正如哲学家亨利·戴维·梭罗在《瓦尔登湖》中所说:“细节是造成美丽的源泉”,在软件开发的世界里,对缓存的理解和优化,就是那些决定程序性能优雅与否的细节。

探索C++编程的奥秘,分享深入的技术见解和实践,旨在激发读者创造力与解决问题的思维。 3732

Qwen-Image(阿里通义千问)技术浅析(一)

Qwen-Image(阿里通义千问多模态模型)是阿里巴巴推出的视觉-语言多模态大模型,能够理解图像内容并完成复杂的跨模态任务。

m0_75253143的博客 675

BPF ringbuf vs. BPF perfbuf

为了兼容性改为宏定义,同时支持 3 个参数和 6 个参数,但使用 3 个参数时 libbpf 会提示 warning,6 个参数分别表示 map 编号、每个 CPU 分配的空间、数据到达回调函数、数据丢失回调函数、传递的上下文,以及选项。函数支持 3 个参数,分别传入 map 的编号,每个 CPU 分配的空间(页面数量),和。:perf 缓冲区为每个 CPU 分配了一个独立的缓冲区,这就引入了如下几个问题。:环形缓冲区,维护了一个所有 CPU 共享的缓冲区,解决了上面。,其中指定了数据到达时的回调函数。

weixin_44200757的博客 736

斗鱼直播间弹幕爬虫实战:WebSocket连接与实时数据采集

在数据驱动的时代,直播平台的弹幕数据蕴含着巨大的价值——用户行为分析、舆情监控、弹幕情感分析、直播热度评估等应用场景层出不穷。斗鱼作为国内领先的直播平台,其弹幕系统基于WebSocket协议实现实时双向通信。本文将带您从零开始,使用Python构建一个完整的斗鱼直播间弹幕爬虫系统。

2201_76125261的博客 134

LWN:在今后十年让BPF保持活力!

关注了就能看到更多这么棒的文章哦~Modernizing BPF for the next 10 yearsBy Daroc AldenJune 7, 2024LSFMM+BPFGemini-1.5-flash translationhttps://lwn.net/Articles/977013/BPF 最初采用来作为对数据包过滤的一个通用化处理方案,这发生在十多年前。在那之后,BPF 发生了巨...

Linux News搬运工 1848

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

LWN:BPF 中可以长存的指针!

关注了就能看到更多这么棒的文章哦~Long-lived kernel pointers in BPFJuly 14, 2022This article was contributed by David VernetDeepL assisted translationhttps://lwn.net/Articles/900749/BPF 子系统允许程序员编写一些可以在内核空...

Linux News搬运工 675

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

LWN:使用BPF来选择OOM kill的对象!

关注了就能看到更多这么棒的文章哦~Out-of-memory victim selection with BPFBy Jonathan CorbetAugust 17, 2023ChatGPT assisted translationhttps://lwn.net/Articles/941614/默认配置下的 Linux 内核允许进程分配的内存比系统实际可提供的内存更多。这个策略可以更好地利用物理...

Linux News搬运工 1047

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

LWN:利用BPF来检查kernel数据结构内容!

关注了就能看到更多这么棒的文章哦~Dumping kernel data structures with BPFByJonathan CorbetApril 27, 2020原文来自:...

Linux News搬运工 793

LWN:代码标记以及内存分配追踪!

关注了就能看到更多这么棒的文章哦~Code tagging and memory-allocation profilingBy Jonathan CorbetMay 31, 2023LSFMM+BPFDeepL assisted translationhttps://lwn.net/Articles/932402/去年由 Suren Baghdasaryan 和 Kent Overstreet 提...

Linux News搬运工 1269

LWN:自动对kernel进行调优!

关注了就能看到更多这么棒的文章哦~Auto-tuning the kernelBy Daroc AldenDecember 11, 2024Gemini-1.5-flash translationhttps://lwn.net/Articles/998576/Linux 内核(Linux kernel)拥有许多可调参数(tunable parameters)。尽管网络上充斥着大量关于如何设置这些参...

Linux News搬运工 1470

LWN:更好地控制透明巨页的使用!

PR_SET_THP_DISABLE 的实现是强制性的;相反,Hildenbrand 的补丁为 PR_SET_THP_DISABLE 添加了一个新选项,名为 PR_THP_DISABLE_EXCEPT_ADVISED,它提供了新的语义。正如 David Hildenbrand 的这个补丁(Usama Arif 已将其作为 这个系列 的一部分)所描述的,人们可以考虑让 madvise() 覆盖 PR_SET_THP_DISABLE,但正如 Hildenbrand 所说,“这会大大改变现有文档中描述的语义”。

Linux News搬运工 1812

LWN:给kernel的swap添加一个抽象层!

关注了就能看到更多这么棒的文章哦~A new swap abstraction layer for the kernelBy Jonathan CorbetMay 23, 2024LSFMM+BPFGemini-1.5-flash translationhttps://lwn.net/Articles/974587/交换(swapping)在本质上可能是一种内存管理技术,但其实现也涉及内核的文件系...

Linux News搬运工 698

LWN:io_uring可以自动选择使用哪个buffer了!

关注了就能看到更多这么棒的文章哦~Automatic buffer selection for io_uringByJonathan CorbetMarch 20, 2020原文来自...

Linux News搬运工 1009

LWN:在Linux中模拟Windows系统调用!

关注了就能看到更多这么棒的文章哦~Emulating Windows system calls in LinuxByJonathan CorbetJune 25, 2020原文来自:h...

Linux News搬运工 848

LWN:CXL 如何管理以及分层!

关注了就能看到更多这么棒的文章哦~CXL 1: Management and tieringBy Jonathan CorbetMay 13, 2022LSFMMDeepL assisted translationhttps://lwn.net/Articles/894598/Compute Express Link(CXL)是一项新出现的内存技术,显然是 Linux 内...

Linux News搬运工 2523

LWN: 用eBPF实现seccomp()

关注了就能看到更多这么棒的文章哦~eBPF seccomp() filtersBy Jonathan CorbetMay 31, 2021DeepL assisted translatio...

Linux News搬运工 740

LWN:6.9合并窗口的第一部分!

关注了就能看到更多这么棒的文章哦~The first half of the 6.9 merge windowBy Jonathan CorbetMarch 14, 2024ChatGPT translationhttps://lwn.net/Articles/965141/截至撰写本文时,已将超过 4900 个非合并的变更集合并到了主线内核以准备发布 6.9 版本。这项工作包括了内核代码各处的一...

Linux News搬运工 252
上一篇: LWN:char 是否应该是signed?
下一篇: LWN:Ghost 发布系统!
LinuxNews搬运工
博客等级 码龄7年 438粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值