Linux内存布局详解

在32位的Linux中,进程的虚拟地址空间大小为4GB,其中低地址空间的3GB属于用户空间,高地址空间的1GB属于内核空间。

在用户空间的3GB中,又划分出6个区域:代码段、数据段、BSS段、堆段、内存映射区、栈段。虚拟地址空间布局如下所示:
在这里插入图片描述
代码段:具有只读权限,用于存放可执行程序、字符串、只读变量等。如定义的const变量、printf函数的格式化字符串等。

数据段:具有读写权限,用于存放初始值非0的全局变量、静态变量。

BSS段:具有读写权限,用于存放初始值为0或未初始化的全局变量、静态变量,这块内存会由操作系统初始化为0。

堆段:运行时可动态分配的内存段,向上生长,由用户进行申请和释放等管理操作。

内存映射段:也称为文件映射区和匿名映射区,加载的动态库、打开的文件等均映射到该区域。

栈段:用于存放非静态的局部变量、函数调用过程的栈帧信息等,地址空间向下生长,由编译器自动分配和释放,栈大小在运行时由内核动态调整,栈动态增长后就不会再收缩

主要介绍下堆段、栈段和内存映射段。

内存映射段:
内存映射就是在进程的虚拟地址空间中创建一个映射,分为文件映射和匿名映射。
文件映射:文件支持的内存映射,把文件映射到进程的虚拟地址空间中,进程打开的那些文件都会映射到这块区域。
匿名映射:没有文件支持的内存映射,把物理内存映射到进程的虚拟地址空间,如进程间通信

堆段:
堆段范围由两个指针进行限制,start_brk指针表示堆段的起始地址,brk指针表示堆段的结束地址。堆段的扩张和收缩实际上都是通过移动brk指针来实现的,扩张堆段就将brk指针向高地址移动(申请内存),收缩堆段就将brk指针向低地址移动(释放内存)。系统调用brk()和sbrk()专门用于移动brk指针。

#include <unistd.h>
int brk(void *addr);
void *sbrk(intptr_t increment);

brk()函数用于让brk指针指向给定地址addr,sbrk()函数用于让堆内存增长指定大小的字节数并返回上一次的brk所指的位置,即本次申请内存的起始地址。

对于开发者来说,申请/释放堆内存通常是使用libc库提供的malloc/free函数族,libc库实现了对堆内存的管理。

#include <stdlib.h>
void *malloc(size_t size);
void free(void *ptr);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
void *reallocarray(void *ptr, size_t nmemb, size_t size);

malloc():从堆段分配出size字节的内存,不会初始化这块内存,返回这块内存的起始地址。不执行整数相乘后的溢出检查。
free():释放ptr指向的内存块,ptr的值必须是malloc()calloc()realloc()返回值。ptr为NULL,不会执行任何操作;不能对同一个地址释放多次,会产生未定义行为。
calloc():从堆段分配出nmemb*size字节的内存,即分配一块可容纳nmemb个元素、每个元素大小为size字节的数组的内存,将这块内存的初始化为0,并返回这块内存的起始地址。若nmemb或size为0,则返回NULL。会执行整数相乘后的溢出检查。
realloc():重新调整ptr所指向的已分配的内存大小为size字节,不会改变原来的内存的内容,也不会初始化新增的内存。若ptr为NULL,就等价于malloc(size)。若size为0,ptr不为NULL,等价于free(ptr)。ptr指针的值必须是NULL或malloc()calloc()realloc()的返回值。不执行整数相乘后的溢出检查。
reallocarray():调整ptr所指内存块的大小,使其足以容纳具有nmemb个元素、每个元素size字节的数组,等价于realloc(ptr, nmemb * size)。会进行整数相乘后的溢出检查,若相乘的结果发生溢出,则返回NULL,设置errno为ENOMEM,且不会改变原有内存。

malloc/free函数族里面使用了brksbrkmmap等系统调用,从而可以在堆段和内存映射区分配内存。
当用户申请的内存小于128KB时,malloc会通过brk()从堆上申请内存,申请方式就是移动堆段的brk指针,并返回这块内存的起始地址。当用户释放从堆上申请的内存时,malloc不会直接把内存返回给Linux内核,而是缓存在它自己的内存池中,待下次使用。
malloc什么时候才会真正地归还堆段内存呢?默认情况下,当堆段最高地址有连续的超过128KB的空闲内存时,会执行内存紧缩操作(trim)。这个128KB可通过malloc_trim()进行调节,比如要求堆段最高地址有连续的超过20KB的空闲内存时进行紧缩,调用malloc_trim(20)
那不是位于堆段最高地址的那些已释放内存什么时候归还呢?得等它上面的内存全部为空闲时才行。这也是内存碎片产生的原因。

当用户申请的内存大于等于128KB时,malloc会通过mmap从内存映射区申请内存,且释放时也可直接归还内存映射区的这块内存给内核,但是内核需要建立一系列复杂的数据结构来完成内存映射区的内存申请。
在这里插入图片描述

mmap();
malloc_get_state();
malloc_info();
malloc_trim();
malloc_usable_size();
mallocopt();
mcheck();
mtrace();

在使用堆段时,经常会出现两种问题:内存破坏和内存泄漏。内存破坏指释放或篡改正在使用的内存,如踩内存。内存泄漏指未释放不再使用的内存。还有内存碎片问题,大量空闲的小块内存无法归还给内核,申请新内存时这些小块内存又太小。

栈段:
栈段由编译器自动分配和释放,主要有三个用途:

  1. 存储函数内部的非静态局部变量
  2. 记录函数调用过程相关的维护性信息(栈帧),包括函数返回地址、不适合装入寄存器的函数参数和一些寄存器值。
  3. 作为临时存储区,暂存算术表达式部分的计算结果和alloca函数分配的栈内内存

栈的最大容量RLIMIT_STACK可使用命令ulimit -s [LIMIT]查看,LIMIT为0就返回栈的最大容量,否则就将栈的最大容量设为新值。当程序使用的栈超过该最大容量时,就会发生栈溢出,导致段错误。

栈支持静态分配和动态分配。静态分配由编译器完成,动态分配由alloca函数在栈上申请空间,用完后自动释放,但不会释放物理内存。

#include <alloca.h>
void *alloca(size_t size);

alloca():从调用者的栈帧上分配一块size字节的空间,当调用函数返回时会自动释放这块临时空间。不能用free()来释放alloca()申请的内存空间。

栈帧:记录函数调用过程相关的维护性信息,这块信息存储在栈段上。实际上栈段就是由一个个栈帧组成的。
ESP寄存器:始终指向栈顶(也是最后一个栈帧的顶部)
EBP寄存器:始终指向最后一个栈帧的底部
在这里插入图片描述

以一个实例来对栈进行说明:

void func(int m, int n)
{
	int a, b;
	a = m;
	b = n;
}

int main()
{
	...
	func(m, n);
	/* 下一条语句L */
	...
	return 0;
}

在main函数中调用func函数前,栈中只有一个main函数的栈帧,范围是esp到ebp这块区域:
在这里插入图片描述

当main函数调用func函数时,需要将返回地址(当前PC值的下一个值)、传给func函数的参数填入main函数的栈帧中,并跳转到func函数中执行。在调用func函数但未跳转前,main函数的栈帧结构变为:
在这里插入图片描述
跳转到func函数前,还需要将调用者函数main栈帧的ebp值存入main栈帧中,然后形成被调用者函数func的栈帧,并调整ebp和esp寄存器所指的位置。
在这里插入图片描述

当从被调用函数func中返回时,上述EBP和ESP变化:

  1. 将ESP移动到当前栈帧底部,即func函数的栈帧底部,释放整个当前栈帧
  2. 将调用函数main的栈帧底部的值弹出到EBP中,再将返回地址弹出到CS:IP。至此,EBP和ESP都恢复到main函数调用func函数前的状态,即恢复了main函数的栈帧。

Linux内核对栈的分类:

  • 进程栈
  • 线程栈
  • 内核栈
  • 中断栈
  • 信号栈

进程栈:实际上就是上面的虚拟地址空间中的栈段。
线程栈:
Linux内核实际上没有线程的概念,它把所有线程都当作进程来实现,都使用task_struct来表示,仅仅将线程视为一个与其他进程共享某些资源的进程,是否共享地址空间几乎是进程和线程的唯一区别。
线程栈位于进程虚拟地址空间的内存映射段,线程栈大小在创建线程时决定(由线程属性pthread_attr_t或pthread_attr_setstacksize来设置)。
线程栈默认大小和进程栈大小的关系:

  1. 进程栈大小为无限制,则线程栈默认大小为2MB
  2. 进程栈大小小于16KB,则线程栈默认大小为16KB
  3. 进程栈大小大于16KB,则线程栈默认大小由ulimit决定

参考资料:
1 Linux系统–栈帧详解
2 linux系统进程的内存布局
3 Linux虚拟地址空间布局以及进程栈和线程栈总结
4 Linux中的进程栈和线程栈
5 内存泄漏之malloc_trim
6 GLIBC内存管理机制
7 malloc是如何分配内存的?
8 共享内存(内存映射的使用、注意事项、进程间通信、systemV共享内存)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内容简介回到顶部↑ 本书同时收录了Linux领域两位领导人物的作品—相当于“Linux 文档项目”的一个印刷版本,展示了Linux 核心概念及其基本结构。对于面向所有主流Linux子系统的支持与管理任务,本书都进行了恰到好处的讲解。涵盖的主题包括文件系统、目录、引导和关机、打印、网络兼容性、网络应用、网络登录、安全、用户账号、数据备份等等。此外,还针对TCP/IP网络、电子邮件、硬件问题以及网络的管理与配置,进行了专门讨论。 目录回到顶部↑ 目 录 序 前言 第一部分 Linux网络管理员指南 第1章 网络基础 3 1.1 UUCP网络 3 1.2 TCP/IP网络 5 1.2.1 TCP/IP网络入门 5 1.2.2 以太网 6 1.2.3 其他类型的硬件 7 1.2.4 网际协议 8 1.2.5 串行线路网际协议 9 1.2.6 传输控制协议 9 1.2.7 用户数据报协议 10 1.2.8 端口问题 10 1.2.9 套接字库 10 1.3 连网 11 1.4 系统维护 11 1.5 后续章节提要 13 第2章 TCP/IP网络 14 .2.1 网络接口 14 2.2 IP地址 14 2.3 地址解析 15 2.4 IP路由 16 2.4.1 IP网络 16 2.4.2 子网 16 2.4.3 网关 16 2.5 路由表 17 2.6 Internet控制消息协议 18 2.7 域名系统 19 2.7.1 主机名解析 19 2.7.2 输入DNS 20 2.7.3 利用DNS进行名字查找 21 2.7.4 域名服务器 22 2.7.5 DNS数据库 22 2.7.6 逆向查找 23 第3章 网络硬件的配置 26 3.1 内核配置 27 3.1.1 内核选项1.0及以上版本 27 3.1.2 内核选项1.1.14及以上版本 27 3.2 网络设备指南 28 3.3 以太网安装 29 3.3.1 以太网接缆 29 3.3.2 已获支持的网卡 29 3.3.3 以太网自动侦测 30 3.4 PLIP驱动程序 31 3.5 SLIP和PPP驱动程序 31 第4章 串行硬件的设置 32 4.1 Modem通信软件 32 4.2 串行设备概述 32 4.3 访问串行设备 33 4.4 串行硬件 34 第5章 TCP/IP网络配置 36 5.1 proc文件系统的设置 36 5.2 二进制文件的安装 37 5.3 另一个例子 37 5.4 设置主机名 37 5.5 分配IP地址 37 5.6 编写主机和网络文件 39 5.7 IP接口配置 40 5.7.1 回送接口 40 5.7.2 以太网接口 41 5.7.3 通过网关的路由 43 5.7.4 网关的配置 43 5.7.5 PLIP接口 44 5.7.6 SLIP和PPP接口 45 5.7.7 伪接口 45 5.8 ifconfig详解 45 5.9 netstat详解 46 5.9.1 显示路由表 47 5.9.2 显示接口特性 47 5.9.3 显示链接 48 5.10 检查ARP表格 48 5.11 未来展望 50 5.12 名字服务和解析器配置 50 5.13 解析器库 50 5.13.1 host.conf文件 51 5.13.2 解析器环境变量 51 5.13.3 域名服务器查找 ——resolv.conf的配置 52 5.13.4 解析器的健壮性 53 5.14 named的运行 53 5.14.1 named.boot文件 53 5.14.2 DNS数据库文件 55 5.14.3 编写Master文件 57 5.14.4 验证域名服务器的设置 59 5.14.5 其他工具 61 第6章 串行链路网际协议 62 6.1 常规需求 62 6.2 SLIP的工作原理 62 6.3 dip的使用 63 6.4 运行于服务器模式 68 第7章 点到点协议 69 7.1 PPP打开 69 7.2 运行pppd 70 7.3 使用选项文件 71 7.4 用chat拨出 71 7.5 PPP设置的调试 73 7.6 IP配置选项 73 7.6.1 IP地址的选择 73 7.6.2 通过PPP链路的路由 74 7.7 链路控制选项 75 7.8 常规安全问题 76 7.9 PPP身份验证 76 7.9.1 CHAP和PAP 76 7.9.2 CHAP密钥文件 77 7.9.3 PAP密钥文件 78 7.10 PPP服务器的配置 79 第8章 各种网络应用 80 8.1 inetd超级服务器 80 8.2 tcpd访问控制工具 81 8.3 服务和协议文件 83 8.4 远程过程调用 84 8.5 r命令的配置 86 第9章 网络信息系统 88 9.1 NIS概述 89 9.2 NIS与NIS+之比较 90 9.3 NIS的客户端 90 9.4 NIS服务器的运行 91 9.5 用NYS设置一个NIS客户机 91 9.6 挑选合适的映射 93 9.7 使用passwd和group映射 94 9.8 NIS与影子支持 95 9.9 使用传统的NIS代码 96 第10章 网络文件系统 97 10.1 NFS的准备工作 98 10.2 NFS卷的安装 98 10.3 NFS Daemon 100 10.4 导出文件 100 10.5 自动安装器 102 第11章 泰勒式UUCP 103 11.1 关于UUCP 103 11.1.1 UUCP传输和远程作业的执行 104 11.1.2 UUCICO的内部运行 104 11.1.3 UUCICO命令行选项 105 11.2 UUCP配置文件 105 11.2.1 泰勒式UUCP简介 105 11.2.2 UUCP需要知道些什么 107 11.2.3 站点的命名 107 11.3 泰勒式配置文件 108 11.3.1 常规配置选项:config文件 108 11.3.2 如何将其他的系统 告知UUCP:sys文件 108 11.3.3 设备:端口文件 110 11.3.4 如何拨号:拨号文件 111 11.3.5 TCP上的UUCP 112 11.3.6 直接连接的使用 112 11.4 UUCP的注意事项:调节权限 112 11.4.1 命令执行 112 11.4.2 文件传输 112 11.4.3 文件转发 113 11.5 如何设置拨入 113 11.5.1 设置getty 113 11.5.2 提供UUCP账号 114 11.5.3 预防措施 115 11.5.4 呼叫序列号检查 115 11.5.5 匿名UUCP 116 11.6 UUCP低级协议 116 11.6.1 协议概述 116 11.6.2 传输协议的调节 117 11.6.3 如何选定特殊协议 117 11.7 故障排除 117 11.8 日志文件 118 第12章 电子邮件 120 12.1 何谓邮件消息 121 12.2 邮件如何发送 122 12.3 邮件地址 123 12.4 邮件路由的工作原理 124 12.4.1 因特网上的邮件路由 124 12.4.2 UUCP网络内的邮件路由 124 12.4.3 UUCP和RFC-822 125 12.5 路径别名和映射文件格式 126 12.6 elm的配置 128 12.6.1 全局elm选项 128 12.6.2 国家特有字符集 129 第13章 smail的设置和运行 130 13.1 UUCP的设置 130 13.2 在局域网环境中的设置 132 13.2.1 编写配置文件 132 13.2.2 运行smail 133 13.3 故障排除 134 13.4 邮件投递模式 135 13.5 其他配置选项 136 13.6 消息路由和投递 136 13.7 消息的路由 137 13.8 将消息投递至本地地址 139 13.8.1 本地用户 139 13.8.2 转发 140 13.8.3 别名文件 140 13.8.4 邮件列表 141 13.9 以UUCP为基础的传输 141 13.10 以SMTP为基础的传输 141 13.11 主机名的限制 142 第14章 Sendmail+IDA指南 143 14.1 配置文件综述 143 14.2 sendmail.cf文件 144 14.3 Sendmail+IDA表格指南 146 14.3.1 mailertable 146 14.3.2 uucpxtable 146 14.3.3 pathtable 147 14.3.4 domaintable 147 14.3.5 别名 147 14.4 sendmail的安装 147 14.4.1 sendmail.cf文件的建立 148 14.4.2 sendmail.cf文件的测试 148 14.4.3 对sendmail.cf和表格 进行综合测试 148 14.5 邮件的操作技巧 148 14.5.1 向中转主机转发邮件 149 14.5.2 强制邮件进入配置不当的 远程站点 149 14.5.3 强制邮件通过UUCP进行传输 149 14.5.4 防止邮件通过UUCP进行传输 149 14.5.5 按需运行sendmail队列 149 14.5.6 报告邮件特征 150 14.6 二进制附件的合成和匹配 150 14.7 获取更多的信息 150 第15章 网络新闻 151 15.1 何谓Usenet 151 15.2 Usenet如何对新闻加以控制 152 第16章 C-News 154 16.1 新闻投递 154 16.2 安装 155 16.3 sys文件 156 16.4 active文件 158 16.5 新闻组文章的批处理 159 16.6 对新闻进行过期处理 161 16.7 其他文件 163 16.8 控制消息 164 16.8.1 cancel消息 164 16.8.2 newgroup和rmgroup消息 165 16.8.3 checkgroups消息 165 16.8.4 sendsys、version和senduuname 166 16.9 NFS环境中的C-News 166 16.10 维护工具及任务 167 第17章 NNTP简介 168 17.1 NNTP服务器的安装 169 17.2 限制NNTP访问权限 169 17.3 NNTP身份验证 170 17.4 nntpd与C-News的沟通 171 第18章 新闻阅读机的配置 172 18.1 tin配置 172 18.2 trn配置 173 18.3 nn配置 174 第19章 其他问题 176 19.1 PLIP的空打印机电缆 176 19.2 示范smail配置文件 176 第二部分 Linux系统管理员指南 作者简介 本书简介 前言 第1章 Linux系统综述 189 1.1 操作系统的各个组件 189 1.2 内核的重要组件 189 1.3 Unix系统提供的主要服务 190 1.3.1 init 190 1.3.2 从终端登录 191 1.3.3 syslog 191 1.3.4 周期性执行的命令:cron和at 191 1.3.5 图形化用户接口 191 1.3.6 连网 191 1.3.7 网络登录 192 1.3.8 网络文件系统 192 1.3.9 邮件 192 1.3.10 打印 192 1.3.11 文件系统布局 193 第2章 目录树简介 194 2.1 背景知识 194 2.2 root文件系统 195 2.3 /etc文件系统 196 2.4 /dev文件系统 198 2.5 /usr文件系统 198 2.6 /var文件系统 198 2.7 /proc文件系统 199 第3章 磁盘和其他存储媒体的使用 201 3.1 两类设备 201 3.2 硬盘 202 3.3 软盘 204 3.4 CD-ROM 204 3.5 磁带 205 3.6 格式化 206 3.7 分区 207 3.7.1 主引导记录、引导扇区和分区表 207 3.7.2 扩展和逻辑分区 208 3.7.3 分区类型 209 3.7.4 对硬盘进行分区 209 3.7.5 设备文件和分区 210 3.8 文件系统 210 3.8.1 何谓文件系统 210 3.8.2 文件系统综述 211 3.8.3 如何选用文件系统 212 3.8.4 如何建立文件系统 212 3.8.5 装入和卸装 214 3.8.6 利用fsck检查文件系统的完整性 216 3.8.7 利用badblocks检查磁盘错误 217 3.8.8 抵制碎片 217 3.8.9 适用于所有文件系统的其他工具 217 3.8.10 适用于ext2文件系统的其他工具 218 3.9 无文件系统的磁盘 219 3.10 磁盘空间的分配 219 3.10.1 分区方案 219 3.10.2 空间要求 220 3.10.3 硬盘分区示例 220 3.10.4 为Linux增添更多磁盘空间 221 3.10.5 关于节省磁盘空间的几个提示 221 第4章 内存管理 222 4.1 何谓虚拟内存 222 4.2 创建交换空间 222 4.3 交换空间的使用 223 4.4 与其他操作系统共享交换空间 224 4.5 交换空间的分配 224 4.6 缓冲区 225 第5章 引导和关机 227 5.1 概论 227 5.2 深入研究引导过程 227 5.3 关机详情 229 5.4 重新启动 230 5.5 单用户模式 230 5.6 应急盘 231 第6章 init 232 6.1 init的重要作用 232 6.2 通过init启动getty:/etc/inittab文件 232 6.3 运行级别 233 6.4 /etc/inittab中的特殊配置 234 6.5 在单用户模式下引导 234 第7章 登录和注销 236 7.1 通过终端登录 236 7.2 通过网络登录 237 7.3 登录的意义 237 7.4 访问控制 238 7.5 外壳的启动 238 第8章 用户账号的管理 239 8.1 何谓账号 239 8.2 创建用户 239 8.2.1 /etc/passwd和其他的信息性文件 239 8.2.2 如何选择数字式用户和组ID 240 8.2.3 初始化环境:/etc/skel 240 8.2.4 手工创建一个用户 240 8.3 更改用户属性 241 8.4 删除用户 241 8.5 临时禁用用户 242 第9章 备份 243 9.1 备份的重要性 243 9.2 选择备份媒体 243 9.3 选择备份工具 244 9.4 简单备份 244 9.4.1 如何利用tar进行备份 245 9.4.2 如何利用tar恢复文件 246 9.5 多级备份 247 9.6 要备份什么 248 9.7 压缩备份 248 第10章 时间同步 250 10.1 时区概论 250 10.2 硬件及软件时钟 250 10.3 时间的显示与设置 251 10.4 时间有误怎么办 252 第三部分 附 录 附录A 词汇表 255 附录B Linux 文档项目复制许可证 256 附录C GNU通用公共许可证 257
Linux共享内存是一种高效的进程间通信方式,它可以使多个进程共享同一块内存区域,从而避免了进程间的数据拷贝和通信的开销。下面是Linux共享内存的原理详解: 1.创建共享内存: 在Linux系统中,使用shmget()函数创建共享内存,该函数的原型如下: ``` int shmget(key_t key, size_t size, int shmflg); ``` 其中,key是共享内存的标识符,size是共享内存的大小,shmflg是共享内存的访问权限。shmget()函数返回一个共享内存的标识符,该标识符可以用于后续的共享内存操作。 2.映射共享内存: 使用shmat()函数将共享内存映射到进程的虚拟地址空间中,该函数的原型如下: ``` void *shmat(int shmid, const void *shmaddr, int shmflg); ``` 其中,shmid是共享内存的标识符,shmaddr是共享内存的映射地址,shmflg是共享内存的访问权限。shmat()函数返回一个指向共享内存映射区域的指针,该指针可以用于后续的共享内存操作。 3.使用共享内存: 通过共享内存映射的指针,进程可以直接访问共享内存中的数据,从而实现进程间的数据共享。需要注意的是,由于多个进程共享同一块内存区域,因此需要使用信号量等同步机制来避免数据竞争等问题。 4.撤销共享内存: 使用shmdt()函数将共享内存从进程的虚拟地址空间中撤销,该函数的原型如下: ``` int shmdt(const void *shmaddr); ``` 其中,shmaddr是共享内存的映射地址。shmdt()函数返回0表示成功,-1表示失败。 5.删除共享内存: 使用shmctl()函数删除共享内存,该函数的原型如下: ``` int shmctl(int shmid, int cmd, struct shmid_ds *buf); ``` 其中,shmid是共享内存的标识符,cmd是删除共享内存的命令,buf是共享内存的状态信息。shmctl()函数返回0表示成功,-1表示失败。 总结: Linux共享内存通过创建一块共享内存区域,使多个进程可以直接访问同一块内存,从而实现进程间的高效通信。需要注意的是,由于多个进程共享同一块内存区域,因此需要使用信号量等同步机制来避免数据竞争等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值