进程的创建与可执行程序的加载

SA6196  闫**

进程的创建与可执行程序的加载


 1 Linux下的进程创建与执行

在传统的Linux环境下,有两个基本的操作用于创建和修改进程:函数fork( )用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;函数族exec( )用来启动另外的进程以取代当前运行的进程。

1.1 fork()函数的详解和执行的步骤:

       fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

我们可以分析一段关于fork()的程序:

fork.c

#include<unistd.h>
#include<stdio.h>

int main()
{
	pid_t fpid;
	int flag=0;
	fpid=fork();
	if(fpid<0){
		printf("error");
	}else if(fpid==0){
		printf("我是子进程  我的id是: %d\n",getpid());	
		flag++;	
		}else{
			printf("我是父进程 我的id是: %d\n",getpid());	
		flag++;
		}

	printf("标志数为: %d\n",flag);
}

执行结果如下:


在语句fpid=fork()之前,只有一个进程在执行这段代码,执行fork()后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)…… 为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

1)在父进程中,fork返回新创建子进程的进程ID;

2)在子进程中,fork返回0;

3)如果出现错误,fork返回一个负值;

在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。


 执行步骤如下:

1.通过查找pidmap_array位图,为子进程分配新的PID;

2.检查父进程的ptrace字段(current->ptrace),如果它的值不是0,说明有另一个进程正在跟踪父进程;fork()检查编译器程序自己是否想跟踪子进程。在这种情况下,如果子进程不是内核线程那么fork()函数设置CLONE_PTRACE标志;

3.调用copy_process()复制进程描述符,若所有必需的资源都是可用的,该函数返回刚创建的task_struct描述符地址。

4.如果设置了CLONE_PTRACE标志获知必须跟踪子进程,那么子进程的状态被设置成TASK_STOPPED,并未子进程增加挂起的SIGSTOP信号。在另外一个进程把子进程的状态恢复成TASK_RUNNING之前的状态,子进程则保持TASK_STOPPED,等待被唤醒;

5.如果没有设置CLONE_STOPPED标志,则调用wake_up_new_task()函数执行以下操作:

5.1)调整父进程和子进程的调度参数

5.2)如果子进程和父进程运行在同一个CPU上,而父进程和子进程再能共享同一组页表,那么就把子进程插入父进程运行队列,插入时让子进程恰好在父进程之前,因而迫使子进程先于父进程运行。如果子进程刷新其地址空间,并在创建之后执行新程序,那么这种简单的处理会产生较好的性能。而如果我们让父进程先运行的话,那么写时复制机制将会执行一系列不必要的页面复制;

5.3)如果子进程和父进程运行在不同的CPU上,或者父进程和子进程共享同一组页表,就把子进程插入父进程的队尾。

6.如果CLONE_STOPPED标志被设置,则把子进程设置为TASK_STOPPED状态;

7.如果父进程被跟踪,则把子进程的PID存入current的ptrace——message字段并调用ptrace_notify。ptrace_notify()时当前进程停止运行,并向当前进程的父进程发送SIGCHLD信号。子进程的祖父进程是跟踪父进程的调试器进程。SIGCHLD信号通知编译器进程:current已经创建了一个子进程,可以通过查找current->ptrace_message字段获得子进程的PID;

8.如果设置了CLONE_VFOKE标志,则把父进程插入队列,并挂起父进程知道子进程释放自己的内存地址空进;

9.结束并返回子进程的PID。


1.2 exec()函数族的分析

使用 fork()创建子进程后,子程序通常会调用 exec 函数族来执行另外一个程序,这个 exec 函数族就提供了一个在进程中启动另一个程序执行的方法。它根据指定的文件名或目录名找到可执行文件,并用它来代替当前进程的执行映像。也就是说,exec调用并没有生成新进程,一个进程一旦调用 exec函数,它本身就“死亡”了,系统把代码段替换成新程序的代码,放弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,惟一保留的就是进程的 ID。也就是说,对系统而言,还是同一个进程,不过执行的已经是另外一个程序了。

exec()函数族执行步骤:

系统中存在一个formats链表,其链表结构分别对应一种可执行文件的执行方法,execl()函数对应的系统调用sys_exece()函数会分配一个linux_binprm数据结构并将可执行文件的数据拷贝到其中,并依次扫描formats链表试图执行这个可执行文件,一旦找到了就执行链表结构中的load_binary方法,其主要步骤为:
1.将可执行文件的首部拷贝至内存;
2.根据动态链接程序路径名将共享库对应函数映射到内存;
3.释放原进程的内存描述符、线性区描述符、所有页框;
4.选择线性区的布局;
5.为可执行文件的代码段、数据段以及动态链接程序的代码段、数据段分别进行内存映射;
6.修改内核态堆栈中eip、esp寄存器的值,使其分别指向程序的入口点以及新的用户态堆栈顶并返回;


exec函数族的函数原型如下所示:

int execl(const char *path, const char *arg, ...)

int execv(const char *path, char *const argv[])

int execle(const char *path, const char *arg, ... , char *const envp[])
int execve(const char *path, char *const argv[], char *const envp[])
int execlp(const char *file, const char *arg, ...)
int execvp(const char *file, *const argv[])

我们知道进程调用exec函数族执行程序时,是将可执行程序(ELF)加载执行,下面我将分析ELF的文件格式并分析其加载执行过程;


2.task_struct进程控制块和 ELF可执行文件的格式

         2.1 task_struct进程控制块的结构图


       2.2 ELF可执行文件与进程空间的表现形式

         当在LINUX系统下,用C编译器把C源代码编译成可执行文件时,C编译驱动器一般将调用C的预处理,编译器,汇编器和连接器。C编译驱动器首先把C源代码传到C的预处理器,它以处理过的宏和指示器形式输出纯C语言代码。 C编译器把处理过的C语言代码翻译为机器相关的汇编代码。 汇编器把结果的汇编语言代码翻译成目标的机器指令。结果这些机器指令就被存储成指定的二进制文件格式,就是我们的ELF格式。

其格式为:



利用readelf -a fork查看一下ELF的格式(下图列出了ELF的头部内存分布):



分析:

task_struct进程控制块中的mm字段所指向的mm_struct结构描述了进程地址空间的信息,包括代码段、数据段、堆段、栈段所在地址空间里的起始和结束地址等信息。ELF文件格式中的 ELF头部、段头部表对应进程地址空间中的代码段,在加载可执行文件时,会把它们映射到进程地址空间中的代码段区域。ELF文件格式中的 .data对应进程地址空间中的数据段,在加载可执行文件时,会把它们映射到进程地址空间的数据段区域。



3. ELF可执行文件的动态装载

动态装载是这样一个过程:把共享库放到执行时进程的地址空间,在库中查找函数的地址,然后调用那个函数,当不再需要的时候,卸载共享库。它的执行过程作为动态连接的服务接口。

动态链接,是指库函数的代码并不进入应用软件的目标映像,应用软件在编译/链接阶段并不完成跟库函数的链接,而是把函数库的映像也交给用户,到启动应用软件目标映像运行时才把程序库的映像也装入用户空间(并加以定位),再完成应用软件与库函数的连接。

链接装载时候的内存空间表现为:


ELF文件加载和链接的简要总结

 用户通过shell执行程序,shell通过exceve进入系统调用。sys_execve经过一系列过程,并最终通过ELF文件的处理函数load_elf_binary将用户程序和ELF解释器加载进内存,并将控制权交给解释器。 ELF解释器进行相关库的加载,并最终把控制权交给用户程序。由解释器处理用户程序运行过程中符号的动态解析。



附录:

ELF可执行文件加载时进程的内存空间分布如下:


 yanzl@yanzl:~$ readelf -a fork
ELF 头:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (可执行文件)
  Machine:                           Intel 80386
  Version:                           0x1
  入口点地址:               0x8048380
  程序头起点:          52 (bytes into file)
  Start of section headers:          4440 (bytes into file)
  标志:             0x0
  本头的大小:       52 (字节)
  程序头大小:       32 (字节)
  Number of program headers:         9
  节头大小:         40 (字节)
  节头数量:         30
  字符串表索引节头: 27


节头:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481cc 0001cc 000070 10   A  6   1  4
  [ 6] .dynstr           STRTAB          0804823c 00023c 000058 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          08048294 000294 00000e 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         080482a4 0002a4 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             080482c4 0002c4 000008 08   A  5   0  4
  [10] .rel.plt          REL             080482cc 0002cc 000028 08   A  5  12  4
  [11] .init             PROGBITS        080482f4 0002f4 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        08048320 000320 000060 04  AX  0   0 16
  [13] .text             PROGBITS        08048380 000380 0001f4 00  AX  0   0 16
  [14] .fini             PROGBITS        08048574 000574 000014 00  AX  0   0  4
  [15] .rodata           PROGBITS        08048588 000588 00006b 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS        080485f4 0005f4 00002c 00   A  0   0  4
  [17] .eh_frame         PROGBITS        08048620 000620 0000b0 00   A  0   0  4
  [18] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4
  [19] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4
  [20] .jcr              PROGBITS        08049f10 000f10 000004 00  WA  0   0  4
  [21] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4
  [22] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        0804a000 001000 000020 04  WA  0   0  4
  [24] .data             PROGBITS        0804a020 001020 000008 00  WA  0   0  4
  [25] .bss              NOBITS          0804a028 001028 000004 00  WA  0   0  4
  [26] .comment          PROGBITS        00000000 001028 00002a 01  MS  0   0  1
  [27] .shstrtab         STRTAB          00000000 001052 000106 00      0   0  1
  [28] .symtab           SYMTAB          00000000 001608 000450 10     29  45  4
  [29] .strtab           STRTAB          00000000 001a58 000273 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)


There are no section groups in this file.


程序头:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
  INTERP         0x000154 0x08048154 0x08048154 0x00013 0x00013 R   0x1
      [正在请求程序解释器:/lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x006d0 0x006d0 R E 0x1000
  LOAD           0x000f08 0x08049f08 0x08049f08 0x00120 0x00124 RW  0x1000
  DYNAMIC        0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW  0x4
  NOTE           0x000168 0x08048168 0x08048168 0x00044 0x00044 R   0x4
  GNU_EH_FRAME   0x0005f4 0x080485f4 0x080485f4 0x0002c 0x0002c R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
  GNU_RELRO      0x000f08 0x08049f08 0x08049f08 0x000f8 0x000f8 R   0x1


 Section to Segment mapping:
  段节...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     
   08     .init_array .fini_array .jcr .dynamic .got 


Dynamic section at offset 0xf14 contains 24 entries:
  标记        类型                         名称/值
 0x00000001 (NEEDED)                     共享库:[libc.so.6]
 0x0000000c (INIT)                       0x80482f4
 0x0000000d (FINI)                       0x8048574
 0x00000019 (INIT_ARRAY)                 0x8049f08
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x8049f0c
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x6ffffef5 (GNU_HASH)                   0x80481ac
 0x00000005 (STRTAB)                     0x804823c
 0x00000006 (SYMTAB)                     0x80481cc
 0x0000000a (STRSZ)                      88 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000015 (DEBUG)                      0x0
 0x00000003 (PLTGOT)                     0x804a000
 0x00000002 (PLTRELSZ)                   40 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x80482cc
 0x00000011 (REL)                        0x80482c4
 0x00000012 (RELSZ)                      8 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffe (VERNEED)                    0x80482a4
 0x6fffffff (VERNEEDNUM)                 1
 0x6ffffff0 (VERSYM)                     0x8048294
 0x00000000 (NULL)                       0x0


重定位节 '.rel.dyn' 位于偏移量 0x2c4 含有 1 个条目:
 Offset     Info    Type            Sym.Value  Sym. Name
08049ffc  00000306 R_386_GLOB_DAT    00000000   __gmon_start__


重定位节 '.rel.plt' 位于偏移量 0x2cc 含有 5 个条目:
 Offset     Info    Type            Sym.Value  Sym. Name
0804a00c  00000107 R_386_JUMP_SLOT   00000000   printf
0804a010  00000207 R_386_JUMP_SLOT   00000000   getpid
0804a014  00000307 R_386_JUMP_SLOT   00000000   __gmon_start__
0804a018  00000407 R_386_JUMP_SLOT   00000000   __libc_start_main
0804a01c  00000507 R_386_JUMP_SLOT   00000000   fork


The decoding of unwind sections for machine type Intel 80386 is not currently supported.


Symbol table '.dynsym' contains 7 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.0 (2)
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND getpid@GLIBC_2.0 (2)
     3: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (2)
     5: 00000000     0 FUNC    GLOBAL DEFAULT  UND fork@GLIBC_2.0 (2)
     6: 0804858c     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used


Symbol table '.symtab' contains 69 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 08048154     0 SECTION LOCAL  DEFAULT    1 
     2: 08048168     0 SECTION LOCAL  DEFAULT    2 
     3: 08048188     0 SECTION LOCAL  DEFAULT    3 
     4: 080481ac     0 SECTION LOCAL  DEFAULT    4 
     5: 080481cc     0 SECTION LOCAL  DEFAULT    5 
     6: 0804823c     0 SECTION LOCAL  DEFAULT    6 
     7: 08048294     0 SECTION LOCAL  DEFAULT    7 
     8: 080482a4     0 SECTION LOCAL  DEFAULT    8 
     9: 080482c4     0 SECTION LOCAL  DEFAULT    9 
    10: 080482cc     0 SECTION LOCAL  DEFAULT   10 
    11: 080482f4     0 SECTION LOCAL  DEFAULT   11 
    12: 08048320     0 SECTION LOCAL  DEFAULT   12 
    13: 08048380     0 SECTION LOCAL  DEFAULT   13 
    14: 08048574     0 SECTION LOCAL  DEFAULT   14 
    15: 08048588     0 SECTION LOCAL  DEFAULT   15 
    16: 080485f4     0 SECTION LOCAL  DEFAULT   16 
    17: 08048620     0 SECTION LOCAL  DEFAULT   17 
    18: 08049f08     0 SECTION LOCAL  DEFAULT   18 
    19: 08049f0c     0 SECTION LOCAL  DEFAULT   19 
    20: 08049f10     0 SECTION LOCAL  DEFAULT   20 
    21: 08049f14     0 SECTION LOCAL  DEFAULT   21 
    22: 08049ffc     0 SECTION LOCAL  DEFAULT   22 
    23: 0804a000     0 SECTION LOCAL  DEFAULT   23 
    24: 0804a020     0 SECTION LOCAL  DEFAULT   24 
    25: 0804a028     0 SECTION LOCAL  DEFAULT   25 
    26: 00000000     0 SECTION LOCAL  DEFAULT   26 
    27: 00000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    28: 08049f10     0 OBJECT  LOCAL  DEFAULT   20 __JCR_LIST__
    29: 080483c0     0 FUNC    LOCAL  DEFAULT   13 deregister_tm_clones
    30: 080483f0     0 FUNC    LOCAL  DEFAULT   13 register_tm_clones
    31: 08048430     0 FUNC    LOCAL  DEFAULT   13 __do_global_dtors_aux
    32: 0804a028     1 OBJECT  LOCAL  DEFAULT   25 completed.6339
    33: 08049f0c     0 OBJECT  LOCAL  DEFAULT   19 __do_global_dtors_aux_fin
    34: 08048450     0 FUNC    LOCAL  DEFAULT   13 frame_dummy
    35: 08049f08     0 OBJECT  LOCAL  DEFAULT   18 __frame_dummy_init_array_
    36: 00000000     0 FILE    LOCAL  DEFAULT  ABS fork.c
    37: 00000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    38: 080486cc     0 OBJECT  LOCAL  DEFAULT   17 __FRAME_END__
    39: 08049f10     0 OBJECT  LOCAL  DEFAULT   20 __JCR_END__
    40: 00000000     0 FILE    LOCAL  DEFAULT  ABS 
    41: 08049f0c     0 NOTYPE  LOCAL  DEFAULT   18 __init_array_end
    42: 08049f14     0 OBJECT  LOCAL  DEFAULT   21 _DYNAMIC
    43: 08049f08     0 NOTYPE  LOCAL  DEFAULT   18 __init_array_start
    44: 0804a000     0 OBJECT  LOCAL  DEFAULT   23 _GLOBAL_OFFSET_TABLE_
    45: 08048570     2 FUNC    GLOBAL DEFAULT   13 __libc_csu_fini
    46: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
    47: 080483b0     4 FUNC    GLOBAL HIDDEN    13 __x86.get_pc_thunk.bx
    48: 0804a020     0 NOTYPE  WEAK   DEFAULT   24 data_start
    49: 00000000     0 FUNC    GLOBAL DEFAULT  UND printf@@GLIBC_2.0
    50: 0804a028     0 NOTYPE  GLOBAL DEFAULT   24 _edata
    51: 08048574     0 FUNC    GLOBAL DEFAULT   14 _fini
    52: 00000000     0 FUNC    GLOBAL DEFAULT  UND getpid@@GLIBC_2.0
    53: 0804a020     0 NOTYPE  GLOBAL DEFAULT   24 __data_start
    54: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    55: 0804a024     0 OBJECT  GLOBAL HIDDEN    24 __dso_handle
    56: 0804858c     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    57: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
    58: 08048500    97 FUNC    GLOBAL DEFAULT   13 __libc_csu_init
    59: 0804a02c     0 NOTYPE  GLOBAL DEFAULT   25 _end
    60: 08048380     0 FUNC    GLOBAL DEFAULT   13 _start
    61: 08048588     4 OBJECT  GLOBAL DEFAULT   15 _fp_hw
    62: 0804a028     0 NOTYPE  GLOBAL DEFAULT   25 __bss_start
    63: 0804847c   130 FUNC    GLOBAL DEFAULT   13 main
    64: 00000000     0 FUNC    GLOBAL DEFAULT  UND fork@@GLIBC_2.0
    65: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
    66: 0804a028     0 OBJECT  GLOBAL HIDDEN    24 __TMC_END__
    67: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
    68: 080482f4     0 FUNC    GLOBAL DEFAULT   11 _init


Histogram for `.gnu.hash' bucket list length (total of 2 buckets):
 Length  Number     % of total  Coverage
      0  1          ( 50.0%)
      1  1          ( 50.0%)    100.0%


版本符号节“.gnu.version”含有 7 个条目:
 地址: 0000000008048294  Offset: 0x000294  Link: 5 (.dynsym)
  000:   0 (*本地*)       2 (GLIBC_2.0)     2 (GLIBC_2.0)     0 (*本地*)    
  004:   2 (GLIBC_2.0)     2 (GLIBC_2.0)     1 (*全局*)   


Version needs section '.gnu.version_r' contains 1 entries:
 地址:0x00000000080482a4  Offset: 0x0002a4  Link: 6 (.dynstr)
  000000: Version: 1  文件:libc.so.6  计数:1
  0x0010:   Name: GLIBC_2.0  标志:无  版本:2


注释位于偏移量 0x00000168 长度为 0x00000020:
  Owner                 Data size Description
  GNU                  0x00000010 NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 2.6.24


注释位于偏移量 0x00000188 长度为 0x00000024:
  Owner                 Data size Description
  GNU                  0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 45c2b571da6920c0b36358efe2dce4b2fff5ff2e
yanzl@yanzl:~$ 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值