操作系统DIY手册

导读:

   操作系统DIY手册

  第一课:引导扇区

  (Lesson 1: The Boot Record)

  这一课主要介绍引导扇区(Boot Record),为我们手动打造引导程序(boot loader)作准备。

  当计算机从软盘引导时,BIOS(Basic Input/Output System)将软盘的第一个扇区(sector)读入内存(从地址0000:7C00开始读入)。这第一个扇区称为DOS引导记录(DOS Boot Record)。然后BIOS跳到地址0x7C00并开始执行相应的程序。正是这个程序负责将操作系统(operating system)读入内存并初始化。

  首先来看看引导记录(Boot Record)中有些什么。DOS中的DEBUG是察看内存或磁盘内容的绝好工具。现在,我们就用它来一窥奥秘。

  在DOS或Windows命令行下输入debug,这时你将看到一个短线(hyphen,连字号)。如果键入’d’并按下回车,你将看到当前内存中的部分内容。输入’?’可获得DEBUG的命令列表。(注意,使用DEBUG时一定要小心,它可以读写磁盘上任何地方的数据,不注意的话,可能造成数据的损坏)

  插入一张刚格式化的软盘,键入下面的命令,将引导记录读入内存:

  -l 0 0 0 1

  (注意,第一个是字母‘l’,最后一个是数字‘1’)字母‘l’表示载入(Load),其后面的四个数字分别表示:①数据读入内存中的何处(起始地址)、②驱动器编号(Drive Number,0表示软驱)、③从第几个扇区开始读(0表示第一个)、④读几个扇区(这里读了一个)。这一命令将软盘中的第一个扇区(也就是引导记录)读入内存首地址为0的地方。

  成功将引导记录(BootRecord)读入内存后,键入下面命令,我们来看看其内容:

  -d 0

  DEBUG将显示八行数据,便是软盘引导记录(BootRecord)前128(十六进制为0x80)个字节(byte)的内容。我软盘中的数据如下:

  0AF6:0000 EB 3C 90 4D 53 44 4F 53-35 2E 30 00 02 01 01 00 .<.MSDOS5.0.....

  0AF6:0010 02 E0 00 40 0B F0 09 00-12 00 02 00 00 00 00 00 ...@............

  0AF6:0020 00 00 00 00 00 00 29 F6-63 30 88 4E 4F 20 4E 41 ......).c0.NO NA

  0AF6:0030 4D 45 20 20 20 20 46 41-54 31 32 20 20 20 33 C9 ME FAT12 3.

  0AF6:0040 8E D1 BC F0 7B 8E D9 B8-00 20 8E C0 FC BD 00 7C ....{.... .....|

  0AF6:0050 38 4E 24 7D 24 8B C1 99-E8 3C 01 72 1C 83 EB 3A 8N$}$....<.r...:

  0AF6:0060 66 A1 1C 7C 26 66 3B 07-26 8A 57 FC 75 06 80 CA f..|&f;.&.W.u...

  0AF6:0070 02 88 56 02 80 C3 10 73-EB 33 C9 8A 46 10 98 F7 ..V....s.3..F...

  初看起来,这些16进制的东东未必能给我什么信息。知道的也只有:这是个MS-DOS5.0的系统,并且用的是FAT12(专用于软盘的)的文件系统(File System)。最左边一列显示内存中的地址,中间部分是内存对应位置的数据,而右边是每个字符对应的ASCII值(若对应的ASCII值不用于显示(not translate to any visible character),则显示一个小点)。这里看到的一些字节,要么是引导程序(Boot Loader)的一部分,要么是关于磁盘的一些信息,例如每个扇区上有多少字节(the number of bytes per sector)、每个磁道上有多少扇区(the number of sectors per track)等等。

  好,让我们来看看引导程序(Boot Loader)的代码。键入下面的命令:

  -u 0

  这个命令表示“反汇编”(unassemble),它显示的数据与我们上面看到的是一样的(从地址0开始),不过这次DEBUG显示的是Intel CPU指令的汇编代码。我得到的结果如下:

  0AF6:0000 EB3C JMP 003E

  0AF6:0002 90 NOP

  0AF6:0003 4D DEC BP

  0AF6:0004 53 PUSH BX

  0AF6:0005 44 INC SP

  0AF6:0006 4F DEC DI

  0AF6:0007 53 PUSH BX

  0AF6:0008 352E30 XOR AX,302E

  0AF6:000B 0002 ADD [BP+SI],AL

  0AF6:000D 0101 ADD [BX+DI],AX

  0AF6:000F 0002 ADD [BP+SI],AL

  0AF6:0011 E000 LOOPNZ 0013

  0AF6:0013 40 INC AX

  0AF6:0014 0BF0 OR SI,AX

  0AF6:0016 0900 OR [BX+SI],AX

  0AF6:0018 1200 ADC AL,[BX+SI]

  0AF6:001A 0200 ADD AL,[BX+SI]

  0AF6:001C 0000 ADD [BX+SI],AL

  0AF6:001E 0000 ADD [BX+SI],AL

  第一条指令表示跳转到内存0x3E的位置。而这条指令后面的数据(到0x3E之前)记录着磁盘的一些信息(上面提过),并不对应于任何指令,但DEBUG还是将它们“翻译”出来了,虽然没什么用处(白打工!^_^)。

  第一条指令将跳过了这些信息,让我们来看看引导程序在0x3E的为时都干了些什么。键入:

  -u 3E

  这里才是引导程序(Boot Loader)的开始。引导程序(对于MS-DOS而言)将在磁盘中寻找IO.SYS和MSDOS.SYS两个文件(DOS的系统文件),将其载入内存并运行。如果未找到文件,我们将会看那条著名的信息:(熟悉DOS的朋友一定不会忘记)

  Invalid system disk

  Disk I/O error

  Replace the disk, and then press any key

  这条信息保存在DOS引导记录(DOS Boot Record)的最后,在我的软盘可看到:

  -d 180

  0AFC:0180 18 01 27 0D 0A 49 6E 76-61 6C 69 64 20 73 79 73 ..'..Invalid sys

  0AFC:0190 74 65 6D 20 64 69 73 6B-FF 0D 0A 44 69 73 6B 20 tem disk...Disk

  0AFC:01A0 49 2F 4F 20 65 72 72 6F-72 FF 0D 0A 52 65 70 6C I/O error...Repl

  0AFC:01B0 61 63 65 20 74 68 65 20-64 69 73 6B 2C 20 61 6E ace the disk, an

  0AFC:01C0 64 20 74 68 65 6E 20 70-72 65 73 73 20 61 6E 79 d then press any

  0AFC:01D0 20 6B 65 79 0D 0A 00 00-49 4F 20 20 20 20 20 20 key....IO

  0AFC:01E0 53 59 53 4D 53 44 4F 53-20 20 20 53 59 53 7F 01 SYSMSDOS SYS..

  0AFC:01F0 00 41 BB 00 07 60 66 6A-00 E9 3B FF 00 00 55 AA .A...`fj..;...U.

  引导记录(Boot Record)大小为一个扇区的内容(512字节)。如果将其由地址为0开始载入内存,则其最后一个字节在0x1FF的位置。看看引导记录(Boot Record)的最后两个字节,你会发现,它们的值永远为0x55和0xAA,否则BIOS是不会执行引导程序的。

  总结一下,DOS引导记录(DOS Boot Record)的第一个指令跳转到0x3E的位置,中间的60个字节(从0x02到0x3D)是磁盘的信息,而0x3E到0x1FD为引导程序,最后是两个特定的字节,0x55和0xAA。OK! Let’s call it a day。下一课,我们将用这些知识,打造自己的引导程序。

  ~欲知后事,请听下回分解~

  --------------------------------------------------------------------------------

  第二课:DIY世界上最简单的启动程序

  (Lesson 2: Making Our First Bootable Disk)

  这一课,我们将学习如何编写软盘启动程序。首先,我们来修改MS-DOS的引导记录。

  思想很简单,我们仅仅修改原本的启动代码,而不改动磁盘的信息记录(0x02到0x3E间的信息)。如果不小心改动了磁盘信息记录,软盘将不能被DOS或Windows正确识别。Windows常常会报错“磁盘未格式化”。

  为了跳过磁盘信息记录,我们不改动引导记录(Boot Record,磁盘的第一个扇区)的第一条指令(jmp 0x3E)。因此,我们从0x3E的位置开始修改。运行DEBUG程序,载入软盘的第一个扇区(上一课的内容,忘了,复习一下),并输入:

  -u 3E

  以察看0x3E位置的代码。OK,开始修改吧,输入:

  -a 3E

  修改0x3E位置的代码。输入下面的指令(Instruction):

  jmp 3E

  这条指令被“翻译”成机器代码存入内存。而接下来显示的内存地址(0AFC:0040)是下一条指令的首地址(可以看出,指令jmp 3E占了两个字节的空间40 – 3E = 2,不同指令的长度一般是不一样的)。在下一个提示的地方直接按回车,结束代码的输入。整个过程如下:

  -a 3E

  0AFC:003E jmp 3E

  0AFC:0040

  -

  OK,让我们来看看刚刚写入的代码:

  -u 3E

  如你所见,我们的jump指令已经在0x3E位置上了。这条指令的结果是个无穷循环(infinite loop),让我们的软盘启动时停在那,什么也不干。最后,在退出DEBUG之前,将修改后的内容写回软盘中:

  -w 0 0 0 1

  DEBUG的“write”命令与前面用的“load”命令类似。这条命令说明从内存地址为0的地方开始,将一个扇区(512字节)的内容写入驱动器0(软驱)的0扇区。(再次提醒,write功能强大,请小心使用,以免造成数据损坏)

  快试试用这张软盘启动。启动时,BIOS将载入软盘第一个扇区的内容并开始执行,之后跳转到0x3E的位置,停在那儿。

  对,你一定会抱怨,“我没看到任何可证明自己的代码已运行的东西”。好吧,为了作个证明,我们来调用BIOS的中断(Interrupt)显示个字符,注意,只能用BIOS提供的功能,DOS中断(如INT 21H什么的)是不能用的。当然罗,这时候DOS还不存在,哈哈。

  AH = 0x0E // 中断号

  AL = 要显示的字符的ASCII码

  BL = 颜色信息

  现在重复上面的步骤,但把原来的jmp 3E指令改一下:

  -a 3E

  0AF6:003E mov ah, 0e

  0AF6:0040 mov al, 48

  0AF6:0042 mov bl, 07

  0AF6:0044 int 10

  0AF6:0046 jmp 46

  0AF6:0048

  -

  首先将AH置0,AL设为0x48(字符’H’的ASCII码),BL为7(黑底白字,color code for white-on-black),然后调用中断0x0E以显示字符。最后一条指令同上,无穷循环。将改好的数据存入软盘(-w 0 0 0 1)并用软盘启动。如何,看到一个字符’H’了吧。

  照上面的方法写几个小程序玩玩,显示多个字符或调用其他的BIOS中断,熟悉一下。如果你兴趣依然,让我们进入下一课吧。下一课我们将使用专门的汇编编译器(assembler),而不是DEBUG了。

  ~欲知后事,请听下回分解~

  --------------------------------------------------------------------------------

  第三课:NASM

  Lesson 3: NASM

  这一课,我们将学习如何使用汇编编译器(Assembler)来编译程序。前面我们用的是DEBUG,但不久你就会发现,用DEBUG写大的程序会让你头痛无比(主要是不便于修改)。这里我们选用Netwide Assembler(NASM),它可从网上下载到,官方网站是:http://nasm.sourceforge.net/index.php。

  现在,我们将用这个汇编编译器来编译第二课中的程序。目录下的h.asm文件就是第二课中的程序,打开浏览一下。原来程序的第一条指令你应该十分熟悉了——jmp 0x3E,目的是跳过启动记录(Boot Record)。而在这个程序中,它变为了跳转到一个标签(Label)begin的位置。跳转指令后的20个字节是磁盘信息,h.asm中的这一部分是我软盘中的信息。使用这些信息是可以正常引导的。当然,你也可以将自己软盘上的信息读出,填写到对应位置。我敢肯定,大部分的信息是相同的。

  补充说明:

  在h.asm中,有下面一句,

  brBPS DW 512 ;000Bh - Bytes/sector

  512是十进制,对应的十六进制为0200,而在DEBUG中,用命令-d 0b察看0x0b位置的信息,看到的是00 20(反过来了)。对大于一个字节的值,都会有这个情况。因为Intel CPU是一个字节一个字节写内存的,而且是从低位向高位写(the least significant byte is stored at the lowest memory address and vice versa)。也称为“byte swapped”现象,是Intel CPU构架决定的,不要忘记罗。

  从标签begin的位置,你可看到与第二课类似的代码:显示字符‘H’,然后无穷循环。在程序的最后,有下面的代码,将不足512字节的地方补0(从当前位置到510之间)。要是程序过大则报错。’$’表示当前位置。

  size equ $ - entry

  %if size+2 >512

  %error "code is too large for boot sector"

  %endif

  times (512 - size - 2) db 0

  最最后面就0x55, 0xAA啦。像下面这样编译程序:

  nasmw h.asm –o h.bin

  这样便得到二进制可执行(binary executable)文件h.bin。确认一下,看看这个文件是否恰好为512字节(一个扇区的大小)。

  最后将这个文件写入软盘的第一个扇区吧。启动DEBUG,照着下面的方法:

  debug

  -n h.bin

  -l 0

  命令将h.bin文件载入内存(从地址为0的位置开始载入)。用命令dump(d)和unassemble(u)确认一下是否成功载入。载入成功,你将看到我们写的几行代码,一大堆0和最后的0x55和0xAA。并且寄存器CX被置为0200(对应于十进制就是512),表示载入的文件的大小。(DEBUG命令r可看到寄存器的内容)

  插入软盘,将数据写入:

  -w 0 0 0 1

  用软盘启动试试,如何,一切OK了吧。休息休息,下一课我们将建立一个“Hello, World”操作系统(operating system),就是简单得不能再简单的那种。

  ~欲知后事,请听下回分解~

  --------------------------------------------------------------------------------

  第四课:Hello, World

  Lesson 4: Hello, World

  令人兴奋的时刻即将到来,我们将向真正意义上的“第一个”操作系统迈进。每本程序书似乎都离不开“Hello, World”这个例子,我们当然也不能免俗啦。这一课就让我们建立一个只会显示一句“Hello, World”的操作系统。如果你前面已经尝试过建立这样的程序,那么可直接跳到下一课。

  如果显示“Hello, World”的每一个字符都需要写一段代码,那将烦琐之至。所以我们写一个函数,它可显示一指定的字符串(以’0’作为结束符)。其实函数仅仅是循环地将字符串中的字符一一显示,代码很简单:

  ---------------------------------------------

  Print a null-terminated string on the screen

  ---------------------------------------------

  putstr:

  lodsb ;AL = [DS:SI]

  or al, al ;Set zero flag if al=0

  jz putstrd ;jump to putstrd if zero flag is set

  mov ah, 0x0e ;video function 0Eh (print char)

  mov bx, 0x0007 ;color

  int 0x10

  jmp putstr

  putstrd:

  retn

  让我们来看看如何使用这个函数。首先,你需将“要显示之字符串”的首地址载入寄存器SI。然后调用函数putstr。用下面的方法在汇编源文件中建立字符串:

  msg db 'Hello, World!', 0 ;注意,要以’0’结尾

  然后,将字符串显示到屏幕:

  mov si, msg ;Load address of message

  call putstr ;Print the message

  msg的值(已加载到SI),其实是以数据段(data segment,其指向的地址存在寄存器DS中)为起始的偏移量(offset)。所以,在使用msg之前,必须将数据段(data segment)设初值。对于当前的情况,我们是从物理内存的最底部(从0开始计算)开始寻址,所以寄存器DS置为0即可。(For now, we will use flat addressing from the bottom of physical RAM. To set the data segment to start from the bottom, set the DS register to zero)

  下面的代码就是完成这个任务的:

  xor ax, ax ;Zero out ax

  mov ds, ax ;Set data segment to base of RAM

  (注:段寄存器、偏址等知识,可参阅汇编语言教程,这里作详细说明。顺带提一下,CPU中的很多寄存器,只能通过AX等寄存器给其赋值,而不能直接访问。段寄存器DS便是如此,所以只能用代码mov ds,ax而不能mov ds,0)

  介绍了这么多,你可尝试改动第三课的h.asm文件来实现上面所说的。如果感到无从下手,看参见我已写好的程序helowrld.asm,动手实践一下,那才有趣。

  完成了这些,下一课将介绍如何与我们的操作系统进行交互(接受键盘事件,处理简单的按键消息)。

  ~欲知后事,请听下回分解~

  --------------------------------------------------------------------------------

  第五课:动起来吧,我的OS

  (Lesson 5: Let’s Make It Interactive)

  将文字显示到屏幕固然有趣,但一个操作系统若不能交互(interactive),不能处理键盘的信息,未免也太单调了。别急,我们这着手处理来自键盘的输入,没错,为此又得求助BIOS的中断调用罗。

  通过中断0x16中的功能0,可实现“等待用户按下一个按键”的效果。代码:

  xor ah, ah ;we want function zero

  int 0x16 ;wait for a keypress

  上面的代码会暂停程序的运行,并等待用户按下一个按键,直到按键被按下,才继续运行。这可用于实现那经典的“按下任意键继续”(”Press any key to continue”),当然,也可用于接收用户的输入。当按键按下,对应的扫描码(scan code)将会返回到AH中,而ASCII码返回到AL。

  作为练习,本课要求你写一个简单并可互动的启动程序。如:一个程序,在每次按键按下时便显示一条信息;或将用户按下的键显示到屏幕上。

  如果无从下手,目录下的example.asm文件是我写的例子,参考一下。还是那句话,只有自己动手才有收获。(It’s no fair peeking until you’ve tried by yourself !)

  到此为止,我们的操作系统还只能存在于软盘的第一个扇区里,不能超出这个界限。才512个字节,要想写出漂亮的操作系统是不可能的,那下一课,我们将学习如何建立大于一个扇区(sector)的操作系统。期待吧……

  ~欲知后事,请听下回分解~

  --------------------------------------------------------------------------------

  第六课:引导程序

  (Lesson 6: Boot Loader)

  之前的所有内容,我们都是在引导扇区(the boot sector)鼓捣鼓捣。仅仅512字节的大小,我们甚至不能建立一个稍复杂些的程序,所以,唯一的方法便是突破一个扇区的限制。本课,我们将制作一个引导程序(Boot Loader)放入引导扇区,其会将软盘上的一个可执行文件(executable file)载入内存,并运行之。这个可执行文件可以任意大小,而不必限制于一个扇区(512字节)之内。

  制作引导程序(Boot Loader),与前面的内容相比,还是挺复杂的。在此之前,我们必须先了解FAT文件系统(file system)的结构(这里我选用FAT文件系统,当然,你也可以选别的格式)。下面对引导过程(boot loading process)进行简单描述。

  从软盘最最开始的地方算起,依次存储了:DOS启动记录(DOS Boot Record,也就是第一个扇区)、文件结构表(FAT,File Allocation Table)、根目录(Root Directory),接下来是其它的各种文件。(硬盘的结构要复杂许多,还包括主引导记录(MBR,Master Boot Record)和分区(multiple partitions))假设我们写了一个操作系统,编译为LOADER.BIN文件存于软盘,那引导程序(Boot Loader)将这样工作:

  载入DOS引导记录(DBR,DOS Boot Record),其中存有DBR、FAT、根目录所占的字节大小,这样,它们在软盘上的位置就知道了。

  接着载入根目录(Root Directory)信息。

  在根目录上查找名为LOADER.BIN的文件。如果找到,我们可在目录入口(directory entry)中找到文件的位置信息(文件起始于磁盘的哪个簇(cluster,文件存储的最小单位))。若没找到,显示错误信息。

  载入文件结构表(FAT, The File Allocation)。

  从FAT中,我们可知道LOADER.BIN文件的占用了几个簇(从文件“起始簇”算起)。然后将这些簇中的内容载入内存的特定位置,即将文件LOADER.BIN载入内存。

  跳转到所载入之位置,运行我们的操作系统。

  从磁盘读取数据,用的也都是BIOS调用。若你想尝试一下,可自己弄清楚如何通过BIOS调用读取磁盘上的数据,并自己写一个引导程序(Boot Loader)。而本课中,为了简便,我使用John S. Fine写的引导程序(FAT12 bootstrap loader,文件BOOT12.ASM既是),并稍作修改。如果你能找到他的“partcopy”工具(BOOT12.ASM内有说明),可按他的方法编译安装(别忘了通知我,那个工具在哪可找到)。否则,就按我们原先所学之方法即可。

  这个引导程序使用FAT12文件系统(FAT12 file system,专用于软盘)。对于其它的文件系统(如FAT16、FAT32、NTFS等等),这个引导程序便不适用了。在John Fine的引导程序中,你可进行许多设置,包括:操作系统与FAT数据载入到内存的什么位置、要载入哪个文件。

  默认地,引导程序在根目录查找名为LOADER.BIN的文件,并将起载入内存的0x1000:0000的位置(在程序中通过这句定义其位置:#define IMAGE_SEG)。因此,你需要编写一个LOADER.BIN文件存于软盘。

  还是用实例来演示。我们将使用第四课的“Hello, World”操作系统,但这里要做一些改动。因为这个文件将载入内存中0x1000:0000的位置,而不是0000:7C00。而且,也没有了DOS引导记录(DOS Boot Record)中关于磁盘的那些信息(0x02到0x3E间的数据)。

  在程序的最开始,我们要对数据段(data segment)、堆栈段(stack segment)以及堆栈指针(stack pointer)的寄存器进行初始化。程序代码段(code segment)的位置存于寄存器CS中,而程序所用到的数据也存于代码段,所以数据段寄存器DS的值与CS相等。且对于现在的程序,堆栈段的地址也与CS相同,但以后的程序可能就不同了。下面的初始化的代码:

  mov ax, cs ;Get the current segment

  mov ds, ax ;The data is in this segment

  cli ;disable interrupts while changing stack

  mov ss, ax ;We'll use this segment for the stack too

  mov sp, 0xfffe ;Start the stack at the top of the segment

  sti ;Reenable interrupts

  原来程序的最后几行可去掉了,这里无需将程序文件填充到正好一个扇区(sector)大小。其它的代码没什么变化。最后的文件为lesson6.asm。

  将其编译并复制到软盘中:

  nasmw lesson6.asm –o lesson6.bin

  copy lesson6.bin a:LOADER.BIN

  假设你已安装了那个引导程序(boot loader),即将起写入软盘的第一个扇区,下面,重新启动吧。成功后,你也可将前面几课的程序改装改装,让其可通过现在的引导程序加载,作为练习吧。接下来的课程大部分都将用引导程序来载入我们的操作系统。

  终于突破一个扇区的限制了,激动ing。

  ~欲知后事,请听下回分解~



本文转自

http://osbase.meyu.com.cn/bbs/read.php?tid=147
商印通3.0版本隆重推出 为满足市场需求,不断提升商印通的品质和服务,保持在行业内的领先地位,商印网推出DIY个性定制系统平台商印通3.0版本。通过底层技术和设计理念的创新,在模板、流程、功能、界面、性能等各个方面再上层楼。 一、用户体验全面升级 商印通DIY个性定制系统平台3.0版本,结合客户需求,以人为本,使操作更加人性化,让您的客户爱不释手! 1、自由DIY背景、贴图、边框等,更趣味。 2、随意拖动图片位置、大小,新增或删除照片区域,更个性。 3、自由更换图层顺序,更灵活。 4、新增剪切、复制、粘贴功能,更便捷。 5、编缉区域可根据屏幕大小进行比例缩放,更人性化。 二、后台拼版自动化 后台自动拼版,下载文件可直接印刷,无需人工拼版,为您节约人力、物力资源、提高效率。 产品类型拼版:可实现同类别下的产品,不同客户的订单一起自动拼版,节省时间效率高。 组合式拼版:同一订单的两个产品可拆分开来,组合其它的订单的产品一起拼版。(如:A客户的订单有2个台历,B、C客户的订单都有一本台历产品,可以将A客户的台历与B客户的台历组合自动拼版。) 三、模块系列化 商印通3.0版本结合市场调查模块全新升级,分为四大系列:个性冲印、个性印品、个性礼品、个性饰品。四大系列精准定位,吸引不同层次的客户群体,为您带来更多的交易量! 个性冲印系列:网上冲印省时、省事、省钱,备受大家青睐!个性冲印市场大众化,需求量大! 个性印品系列:定制印品个性、趣味、方便,是市场增长点。 个性礼品系列:礼尚往来,中国人的传统。传统的礼品已经过时,个性礼品成了刚性需求。 个性潮品系列:潮品是现在年轻人的最爱,个性潮品成为未来发展的趋势。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值