哈工大计算机系统2022春 大作业 程序人生

计算机系统

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业         计算机科学与技术              

学     号                       

班     级                       

学       生                 

指 导 教 师                 刘宏伟      

计算机科学与技术学院

2021年5月

摘  要

本文对hello程序的整个生命周期进行了的分析,包括 hello.c源程序的编写,预处理生成hello.i文件,翻译生成汇编语言文件hello.s,以及可重定位目标文件hello.o,最后将hello.o和系统目标文件链接起来,创建了一个可执行目标文件hello。调用fork函数创建进程,execve加载hello进入内存,控制程序逻辑流的运行,中断,上下文切换和异常的处理,最后结束进程并由父进程进行回收

目  录

第1章 概述................................................................................................................ - 4 -

1.1 Hello简介......................................................................................................... - 4 -

1.2 环境与工具........................................................................................................ - 4 -

1.3 中间结果............................................................................................................ - 4 -

1.4 本章小结............................................................................................................ - 4 -

第2章 预处理............................................................................................................ - 5 -

2.1 预处理的概念与作用........................................................................................ - 5 -

2.2在Ubuntu下预处理的命令............................................................................. - 5 -

2.3 Hello的预处理结果解析................................................................................. - 5 -

2.4 本章小结............................................................................................................ - 5 -

第3章 编译................................................................................................................ - 6 -

3.1 编译的概念与作用............................................................................................ - 6 -

3.2 在Ubuntu下编译的命令................................................................................ - 6 -

3.3 Hello的编译结果解析..................................................................................... - 6 -

3.4 本章小结............................................................................................................ - 6 -

第4章 汇编................................................................................................................ - 7 -

4.1 汇编的概念与作用............................................................................................ - 7 -

4.2 在Ubuntu下汇编的命令................................................................................ - 7 -

4.3 可重定位目标elf格式.................................................................................... - 7 -

4.4 Hello.o的结果解析.......................................................................................... - 7 -

4.5 本章小结............................................................................................................ - 7 -

第5章 链接................................................................................................................ - 8 -

5.1 链接的概念与作用............................................................................................ - 8 -

5.2 在Ubuntu下链接的命令................................................................................ - 8 -

5.3 可执行目标文件hello的格式........................................................................ - 8 -

5.4 hello的虚拟地址空间..................................................................................... - 8 -

5.5 链接的重定位过程分析.................................................................................... - 8 -

5.6 hello的执行流程............................................................................................. - 8 -

5.7 Hello的动态链接分析..................................................................................... - 8 -

5.8 本章小结............................................................................................................ - 9 -

第6章 hello进程管理....................................................................................... - 10 -

6.1 进程的概念与作用.......................................................................................... - 10 -

6.2 简述壳Shell-bash的作用与处理流程........................................................ - 10 -

6.3 Hello的fork进程创建过程......................................................................... - 10 -

6.4 Hello的execve过程..................................................................................... - 10 -

6.5 Hello的进程执行........................................................................................... - 10 -

6.6 hello的异常与信号处理............................................................................... - 10 -

6.7本章小结.......................................................................................................... - 10 -

第7章 hello的存储管理................................................................................... - 11 -

7.1 hello的存储器地址空间................................................................................ - 11 -

7.2 Intel逻辑地址到线性地址的变换-段式管理............................................... - 11 -

7.3 Hello的线性地址到物理地址的变换-页式管理.......................................... - 11 -

7.4 TLB与四级页表支持下的VA到PA的变换................................................ - 11 -

7.5 三级Cache支持下的物理内存访问............................................................. - 11 -

7.6 hello进程fork时的内存映射..................................................................... - 11 -

7.7 hello进程execve时的内存映射................................................................. - 11 -

7.8 缺页故障与缺页中断处理.............................................................................. - 11 -

7.9动态存储分配管理........................................................................................... - 11 -

7.10本章小结........................................................................................................ - 12 -

第8章 hello的IO管理.................................................................................... - 13 -

8.1 Linux的IO设备管理方法............................................................................. - 13 -

8.2 简述Unix IO接口及其函数.......................................................................... - 13 -

8.3 printf的实现分析........................................................................................... - 13 -

8.4 getchar的实现分析....................................................................................... - 13 -

8.5本章小结.......................................................................................................... - 13 -

结论............................................................................................................................ - 14 -

附件............................................................................................................................ - 15 -

参考文献.................................................................................................................... - 16 -

第1章 概述

1.1 Hello简介

 1. 编写hello.c程序,得到源程序

 2. hello.c通过gcc编译器处理得到可执行文件hello,实际过程为

    2.1  hello.c经过预处理修改源程序代码生成hello.i文本文件,这是一个新的c程序

    2.2  hello.i再经过编译生成汇编程序hello.s

    2.3  hello.s通过汇编指令集变成二进制可重定位目标文件hello.o但此时该文件还不可以执行

    2.4  最后经过链接,将hello.o文件与一些提前编译好的文件合并生成hello可执行文件

 3. hello文件存入磁盘,等待调用

 4.通过命令解释程序shell为hello创建新的进程,在通过调用execve对虚拟内存进行映射,通过mmap为hello开辟一片空间。

 5.最后中央处理器CPU从虚拟内存中截取代码和数据,调度器为进程规划时间片,在发生异常时触发异常处理子程序

1.2环境与工具

  

1.硬件环境:Intel(R) Core(TM) i7-10875H CPU 2.30 GHz;16GRAM;1024Disk

2.软件环境:Windows11 64位; Ubuntu 20.04 LTS 64位

3.工具:codeblocks;gdb;

1.3 中间结果

  1.hello.c 程序的源文件,用于编写代码

  2.hello.s 编译后的文件

  3.hello.o 汇编后的文件,用于查看反汇编后的代码

  4.hello.i 预处理后的文件,可以对比查看预处理的内容

  5.hello.elf  hello.o的elf格式文件

  6.hello.txt  hello反汇编的文件

  7.hello 最终生成的可执行文件

8.hello1.elf  hello的elf格式文件

1.4 本章小结

本章主要介绍了hello程序的编译执行大概过程,说明了软件运行的设备环境和硬件设施。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

1.概念:根据以#开头的代码,修改源程序

2.作用:读取指定文件的内容并插入源程序,得到一个新的c程序。

2.2在Ubuntu下预处理的命令

命令:gcc -E -o hello.i hello.c

2.3 Hello的预处理结果解析

hello.i文件部分内容如图所示:

目测文件内容依旧是可阅读的c语言程序代码,但是对一些函数等内容进行了替换。可以得出预处理的内容是对源程序进行的展开。

2.4 本章小结

本章了解了对hello文件的预处理的具体过程和结果,通过对比hello.i和hello文件可以看出预处理的本质工作是进行c语言代码的补充和替换。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:将文本文件hello.i转化为文件hello.s

作用:进行词法分析和语法分析,判断是否存在语法词法错误

3.2 在Ubuntu下编译的命令

命令:gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析

hello.s文件部分内容如下:

  1. 初始部分(main前):

.file “hello.c”      声明源文件

.text                  代码段,存放函数的二进制指令

.section  .rofata      只读数据段,存放“”中的字符串     

.string “hello %s %s\n” 声明一段字符

.globl  main           声明全局变量              

.type  main,@function  声明符号类型(函数还是数据)

.align 8                声明存放地址对齐的方式

  1. 数据部分:

hello程序中声明了两个字符串,都保存在只读数据段中

hello程序中也声明了一个局部变量i,暂时保存在堆栈 -4(%rbp)的位置

hello程序的参数argc也保存在堆栈中

hello程序中还存在着一个数组argv[]作为函数的参数,存放在 -32(%rbp)的位置

汇编代码中立即数直接在代码中表示

  1. 操作:

赋值操作:mov a  b  代码表示将a中的数据复制到b中,从而实现赋值操作, mov代码一般会带后缀表示操作的数据大小

运算操作:add  a  b  代码表示将a加给b,可以实现加法运算,hello程序中主要的运算操作为循环过程中的i++,实现方式如图

循环与跳转:通过cmpl代码对后条件码进行判断,je和jle则利用条件码进行跳转,从而实现循环

  1. 函数:

函数传参:先设定一个寄存器,将参数传给这个设定的寄存器后

函数调用:通过call来跳转到调用的函数开头的地址。

3.4 本章小结

本章主要从c语言的数据,赋值语句,算术操作,逻辑/位操作,跳转操作,函数操作这几点入手,分析了编译器是如何把hello.i文件从高级的c语言变成了低阶的汇编语言

第4章 汇编

4.1 汇编的概念与作用

    概念:通过指令集将汇编程序hello.s转化为机器指令并按一定规则打包得到可重定位目标文件hello.o

作用:将汇编语言翻译成机器语言,使其在链接时能被找到,链接后能够被机器识别并执行。

4.2 在Ubuntu下汇编的命令

命令:gcc -c -o hello.o hello.s

4.3 可重定位目标elf格式

hello.elf文件的部分内容如下:

分析内容:

  1. ELF头 :ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。
  2. 节头:记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。
  3. 重定位节:重定位节保存的是需要被修正的信息。
  4. 符号表:包含目标模块定义和引用的符号的信息

4.4 Hello.o的结果解析

通过objdump -d -r hello.o命令可以得到hello.o文件的反汇编代码

内容如下:

与前面hello.s文件进行对比:

  1. 分支转移代码不同:

Hello.o的反汇编代码中,je后的代码不再是原来的段名,改成了确定的地址,这是因为段本身只是汇编语言中用于帮助编写的记忆符号,汇编成机器语言后不能继续使用

2.函数调用代码不同:

Call指令后不再接函数的名字,而是一条重定位条目指引的信息

  1. 立即数表示不同:

立即数都变成了十六进制,较于十进制更容易处理

4.5 本章小结

本章主要介绍了汇编过程,我们通过查看hello.elf文件内容了解了可重定位目标文件的格式,反汇编得到了hello.o文件的汇编代码并与hello.i进行了对比,了解了汇编语言转化为机器语言的过程。

5章 链接

5.1 链接的概念与作用

1.概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载到内存并执行

2.作用:允许我们把程序分解为更小,更好管理的模块,可以独立的修改和编译这些模块,不必重新编译整个程序。

5.2 在Ubuntu下链接的命令

命令:ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o

5.3 可执行目标文件hello的格式

通过readelf -a hello>hello1.elf命令可以生成hello1.elf文件,文件部分内容如下图:

1.hello是一个可执行目标文件,有27个节

2. 节头对 hello中所有的节信息进行了声明,包括大小和偏移量

3.重定位节:

4.符号表:

5.4 hello的虚拟地址空间

分析程序头LOAD可加载的程序段的地址为0x400000

使用edb加载程序,查看程序信息:

在0x400000~0x401000段中,程序被载入,虚拟地址0x400000开始,到0x400fff结束,可以通过edb找到各个节的信息,虚拟地址开始于0x4010f0,大小为0x145

5.5 链接的重定位过程分析

命令:objdump -d -r hello > hello.txt

生成的文件部分内容如下:

hello的重定位过程:

   1.重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。

   2.重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。

   3.重定位条目当编译器遇到对最终位置未知的目标引用时,它会生成一个重定位条目。代码的重定位条目放在.rel.txt中。

5.6 hello的执行流程

通过edb打开hello文件,运用step into指令调试,遇到函数会发生跳转

程序地址

        00007f3cda2f9df0

        00007f264db03df0

        00007f035f63f7d0

        0000400550

        000007fd34770bb10

        000007f03a4843b27

        00000400610

        00000400582

        00000400530

 

5.7 Hello的动态链接分析

5.8 本章小结

本章研究了链接的过程。通过edb查看hello的虚拟地址空间,对比hello与hello.o的反汇编代码

6章 hello进程管理

6.1 进程的概念与作用

1.概念:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

2.作用:进程作为一个执行中程序的实例,系统中每个程序都运行在某个进程的上下文中。

6.2 简述壳Shell-bash的作用与处理流程

1.shell程序的作用:Linux系统中,Shell是一个命令解释程序,它为用户提供操作命令和文件的界面,用户可以直接通过在shell程序上输入的指令调用相应的程序

2.流程:

a.在shell命令行中输入命令:$./hello,解释器构造argv和envp;

b. 调用fork()函数创建子进程,

c. 调用execve()函数在当前进程(新创建的子进程)的上下文中加载并运行hello

程序。将hello中的.text节、.data节、.bss节等内容加载到当前进程的虚拟地址空

d. 调用hello程序的main()函数,hello程序开始在一个进程的上下文中运行。

6.3 Hello的fork进程创建过程

在终端中输入./hello 学号 姓名 1命令后,shell会处理该命令,判断出不是内置命令,则会调用fork函数创建一个新的子进程,子进程几乎但不完全与父进程相同。通过fork函数,子进程得到与父进程用户级虚拟地址空间相同的但是虚拟地址独立、PID也不相同的一份副本。

6.4 Hello的execve过程

创建了一个子进程时,子进程调用exceve函数在当前子进程的上下文加载并运行一个新的程序,会经过以下步骤:

1.删除之前进程在用户部分中已存在的结构。

2.创建新的代码、数据、堆和栈段。

3.映射共享区域。先动态链接共享对象,然后再映射到用户虚拟地址空间中的共享区域。

4.设置程序计数器(PC)。Exceve会设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。

6.5 Hello的进程执行

1.进程上下文切换:如果系统调用因为等待某个事件发生而阻塞,那么内核可以让当前进程休眠,切换到另一个进程

2.进程时间片:进程的运行本质上是CPU不断从程序计数器 PC 指示的地址处取出指令并执行,值的序列叫做逻辑控制流。在一个程序被调运行开始到被另一个进程打断,中间的时间就是运行的时间片

3.用户态和核心态: 进程只有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,这样可以保证系统的安全性。

4.进程调度:即加载保存的寄存器,切换虚拟地址空间

6.6 hello的异常与信号处理

1.正常运行:

2.CTRL+Z: 输入ctrl-z默认结果是挂起前台的作业,hello进程并没有回收,而是运行在后台下,用ps命令可以看到,hello进程并没有被回收。

3.CTRL+C:

内核发送一个SIGINT信号到前台进程组的每个进程,默认情况是终止前台作业,用ps查看前台进程组发现没有hello进程,如图所示。

  1. 乱按:无关输入被缓存到stdin,并随着printf指令被输出到结果。

6.7本章小结

本章了解了hello进程的执行过程。内核为程序调度,异常处理程序处理各种异常。不同信号有不同的处理机制,不同的shell命令,hello也有不同的响应结果。

7章 hello的存储管理

7.1 hello的存储器地址空间

1.逻辑地址:程序经过编译后出现在汇编代码中的地址。

2.线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址

3.虚拟地址:就是线性地址。

4.物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。

7.2 Intel逻辑地址到线性地址的变换-段式管理

1.原理:在段式存储管理 中,将程序的地址空间划分为若干个段(segment),为每个段分配一个连续的分区,进程中的各个段可以不连续地存放在内存的不同分区中。程序加载时,操作系统为 所有段分配其所需内存,这些段不必连续。程序通过分段可以划分为多个模块。

2.逻辑地址的组成:一个逻辑地址由段标识符和段内偏移量组成。段标识符是一个16位长的字段。其中前13位是一个索引号。后面3位包含一些硬件细节。

3.查看方式:

给定一个完整的逻辑地址 段标识符a:段内偏移量b

看段选择符的T1=0还是1,了解当前要转换是全局段描述符表中的段还是局部段描述符表中的段。

根据段选择符的前13位查找到对应的段描述符,确定Base基地址。

将Base+offset,就是线性地址

7.3 Hello的线性地址到物理地址的变换-页式管理

1.基本原理:将 程序的逻辑地址空间划分为固定大小的页,而物理内存划分为同样大小的页框。程序加载时,可将任意一页放人内存中任意一个页框,这些页框不必连续,从而实现了离散分配。

2. 传输单元:系统将虚拟页作为进行数据传输的单元。Linux下每个虚拟页大小为4KB。物理内存也被分割为物理页, MMU(内存管理单元)负责地址翻译,MMU使用页表将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。

3. n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(VPO),一个n-p位的虚拟页号(VPN),MMU利用VPN选择适当的PTE,根据PTE,我们知道虚拟页的信息,如果虚拟页是已缓存的,那直接将页表条目的物理页号和虚拟地址的VPO串联起来就得到一个相应的物理地址。VPO和PPO是相同的。如果虚拟页是未缓存的,会触发一个缺页故障。调用一个缺页处理子程序将磁盘的虚拟页重新加载到内存中,然后再执行这个导致缺页的指令。

7.4 TLB与四级页表支持下的VA到PA的变换

1.TLB:当处理 器要在主内存寻址时,不是直接在内存的物理地址里查找的,而是通过一组虚拟地址转换到主内存的物理地址,TLB就是负责将虚拟内存地址翻译成实际的物理内 存地址,而CPU寻址时会优先在TLB中进行寻址。

2:VA->PA:处理器生成一个虚拟地址,并将其传送给MMU。MMU用VPN向TLB请求对应的PTE,命中得到PA。否则MMU查询页表,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,最终在第四级页表中找到PPN,与VPO组合成物理地址PA,添加到PLT。

7.5 三级Cache支持下的物理内存访问

获得物理地址之后,先取出组索引对应位,在L1中寻找对应组。如果存在,则比较标志位,相等后检查有效位是否为1.如果都满足则命中取出值传给CPU,否则按顺序对L2cache、L3cache、内存进行相同操作,直到出现命中。然后再一级一级向上传,如果有空闲块则将目标块放置到空闲块中,否则将缓存中的某个块驱逐,将目标块放到被驱逐块的位置。

7.6 hello进程fork时的内存映射

当fork 函数被 shell 进程调用时,内核创建子进程并分配给它一个唯一的 PID,同时创建了当前进程的mm_struct、区域结构和页表的原样副本。它将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。

7.7 hello进程execve时的内存映射

execve 函数调用加载器代码,在当前进程中加载并运行hello 需要以下几个步骤:

1. 删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。

2. 映射私有区域,为新程序的代码、数据、 bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。

3.共享对象由动态链接映射到本进程共享区域

4.设置程序计数器使之指向代码区域的入口点

7.8 缺页故障与缺页中断处理

缺页故障:当指令引用一个虚拟地址,在 MMU中查找页表时发现与该地址相对应的物理地址不在内存中,因此必须从磁盘中取出的时候就会发生故障。

缺页中断处理:先检查虚拟地址是否合法,再检查进程是否有读、写或执行该区域页面的权限。如果两者皆无误,选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。再将控制转移给hello进程,再次执行触发缺页故障的指令

7.9动态存储分配管理

程序运行时使用动态内存分配器获得虚拟内存。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。

对于每个进程,内核维护着一个变量 brk,它指向堆的顶部。分配器将堆视为一组不同大小的块的结合来维护。每个块就是一个连续的拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。分配器有两种基本风格,两种风格都要求应用显式地分配块,不同在于由哪个实体来负责释放。

显式分配器:要求应用显式地释放任何已分配的块。

隐式分配器:要求分配器检测一个已分配块何时不再使用,那么就释放这个块,垃圾收集:自动释放未使用的已经分配的块的过程。

隐式空闲链表:空闲块通过头部中的大小字段隐含地连接着。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。

显式空闲链表:每个空闲块中,都包含一个前驱和后继指针。使用双向链表使首次适配的时间减少到空闲块数量的线性时间。

7.10本章小结

本章介绍了存储器地址空间、段式管理、页式管理,VA 到 PA 的变换、物理内存访问, h内存映射、缺页故障与缺页中断处理、包括隐式空闲链表和显式空闲链表的动态存储分配管理。

8章 hello的IO管理

8.1 Linux的IO设备管理方法

以下格式自行编排,编辑时删除

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

以下格式自行编排,编辑时删除

8.3 printf的实现分析

以下格式自行编排,编辑时删除

[转]printf 函数实现的深入剖析 - Pianistx - 博客园

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

以下格式自行编排,编辑时删除

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

以下格式自行编排,编辑时删除

(第81分)

结论

抽象对计算机系统是很重要的概念,底层信息用二进制来抽象表示,进程是对处

理器、主存和I/O设备的抽象,虚拟内存是对主存和磁盘设备的抽象,文件是对I/O

设备的抽象,等等。

附件

1.hello.c 程序的源文件,用于编写代码

2.hello.s 编译后的文件

3.hello.o 汇编后的文件,用于查看反汇编后的代码

4.hello.i 预处理后的文件,可以对比查看预处理的内容

5.hello.elf  hello.o的elf格式文件

6.hello.txt  hello反汇编的文件

7.hello 最终生成的可执行文件

8.hello1.elf  hello的elf格式文件

参考文献

为完成本次大作业你翻阅的书籍与网站等

[1]  深入理解计算机系统原书第3版-文字版.pdf

[2]  哈尔滨工业大学计算机系统大作业2022春_Drama_coder的博客-CSDN博客

[3]  https://blog.csdn.net/yueyansheng2/article/details/78860040

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值