uboot启动内核

uboot启动内核

  1. uboot/common/main.c中的两个命令

void main_loop (void)
{
......
    s = getenv ("bootcmd");//获取环境变量
......  
}

通过在串口输入print命令,知道

bootcmd= nand read.jffs2 0x30007FC0 kernel: bootm 0x30007FC0

从nand 上的kernel分区上读出内核,读到地址0x30007FC0,并从0x30007FC0地址启动内核。
分区在配置文件中写定,
F:\u-boot-1.1.6\include\configs\下的配置文件中指定。
在配置文件中会指定保存在nandflash上的
uboot 的启始地址,长度,
参数的启始地址,长度;
kernel分区:内核的启始地址,长度;
以及根文件系统的启始地址和长度。

启动内核需要两个命令:
1. 读出内核
nand read 命令
在uboot/common/Cmd_bootm.c 中。

nand read.jffs2 0x30007FC0 kernel

采用jffs2文件系统是由于不要块对齐或者页对齐。
2.启动内核:
bootm命令
在uboot/common/Cmd_bootm.c 中

U_BOOT_CMD(
    bootm,  CFG_MAXARGS,    1,  do_bootm,
    "bootm   - boot application image from memory\n",
    "[addr [arg ...]]\n    - boot application image stored in memory\n"
    "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
    "\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
    "\tWhen booting a Linux kernel which requires a flat device-tree\n"
    "\ta third argument is required which is the address of the of the\n"
    "\tdevice-tree blob. To boot that kernel without an initrd image,\n"
    "\tuse a '-' for the second argument. If you do not pass a third\n"
    "\ta bd_info struct will be passed instead\n"
#endif
);

bootm的实现在do_bootm函数里面,即启动过程是在do_bootm函数里面,
在uboot/common/Cmd_bootm.c 中,

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    ulong   iflag;
    ulong   addr;
    ulong   data, len, checksum;
    ulong  *len_ptr;
    uint    unc_len = CFG_BOOTM_LEN;
    int i, verify;
    char    *name, *s;
    int (*appl)(int, char *[]);
    image_header_t *hdr = &header;
    /**/
    s = getenv ("verify");
    verify = (s && (*s == 'n')) ? 0 : 1;

    if (argc < 2) {
        addr = load_addr;
    } else {
        addr = simple_strtoul(argv[1], NULL, 16);
    }

    SHOW_BOOT_PROGRESS (1);
    printf ("## Booting image at %08lx ...\n", addr);

    /* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
    if (addr_dataflash(addr)){
        read_dataflash(addr, sizeof(image_header_t), (char *)&header);
    } else
#endif
    memmove (&header, (char *)addr, sizeof(image_header_t));
    /*将代码的实际地址移动到加载地址,
    如果两个地址相等,就不用移动,
    省去了代码移动的过程,加快启动速度。*/

    if (ntohl(hdr->ih_magic) != IH_MAGIC) {
#ifdef __I386__ /* correct image format not implemented yet - fake it */
        if (fake_header(hdr, (void*)addr, -1) != NULL) {
            /* to compensate for the addition below */
            addr -= sizeof(image_header_t);
            /* turnof verify,
             * fake_header() does not fake the data crc
             */
            verify = 0;
        } else
#endif  /* __I386__ */
        {
        puts ("Bad Magic Number\n");
        SHOW_BOOT_PROGRESS (-1);
        return 1;
        }
    }
    SHOW_BOOT_PROGRESS (2);

    data = (ulong)&header;
    len  = sizeof(image_header_t);

    checksum = ntohl(hdr->ih_hcrc);
    hdr->ih_hcrc = 0;

    if (crc32 (0, (uchar *)data, len) != checksum) {
        puts ("Bad Header Checksum\n");
        SHOW_BOOT_PROGRESS (-2);
        return 1;
    }
    SHOW_BOOT_PROGRESS (3);

#ifdef CONFIG_HAS_DATAFLASH
    if (addr_dataflash(addr)){
        len  = ntohl(hdr->ih_size) + sizeof(image_header_t);
        read_dataflash(addr, len, (char *)CFG_LOAD_ADDR);
        addr = CFG_LOAD_ADDR;
    }
#endif


    /* for multi-file images we need the data part, too */
    print_image_hdr ((image_header_t *)addr);

    data = addr + sizeof(image_header_t);
    len  = ntohl(hdr->ih_size);

    if (verify) {
        puts ("   Verifying Checksum ... ");
        if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
            printf ("Bad Data CRC\n");
            SHOW_BOOT_PROGRESS (-3);
            return 1;
        }
        puts ("OK\n");
    }
    SHOW_BOOT_PROGRESS (4);

    len_ptr = (ulong *)data;

#if defined(__PPC__)
    if (hdr->ih_arch != IH_CPU_PPC)
#elif defined(__ARM__)
    if (hdr->ih_arch != IH_CPU_ARM)
#elif defined(__I386__)
    if (hdr->ih_arch != IH_CPU_I386)
#elif defined(__mips__)
    if (hdr->ih_arch != IH_CPU_MIPS)
#elif defined(__nios__)
    if (hdr->ih_arch != IH_CPU_NIOS)
#elif defined(__M68K__)
    if (hdr->ih_arch != IH_CPU_M68K)
#elif defined(__microblaze__)
    if (hdr->ih_arch != IH_CPU_MICROBLAZE)
#elif defined(__nios2__)
    if (hdr->ih_arch != IH_CPU_NIOS2)
#elif defined(__blackfin__)
    if (hdr->ih_arch != IH_CPU_BLACKFIN)
#elif defined(__avr32__)
    if (hdr->ih_arch != IH_CPU_AVR32)
#else
# error Unknown CPU type
#endif
    {
        printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
        SHOW_BOOT_PROGRESS (-4);
        return 1;
    }
    SHOW_BOOT_PROGRESS (5);

    switch (hdr->ih_type) {
    case IH_TYPE_STANDALONE:
        name = "Standalone Application";
        /* A second argument overwrites the load address */
        if (argc > 2) {
            hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));
        }
        break;
    case IH_TYPE_KERNEL:
        name = "Kernel Image";
        break;
    case IH_TYPE_MULTI:
        name = "Multi-File Image";
        len  = ntohl(len_ptr[0]);
        /* OS kernel is always the first image */
        data += 8; /* kernel_len + terminator */
        for (i=1; len_ptr[i]; ++i)
            data += 4;
        break;
    default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
        SHOW_BOOT_PROGRESS (-5);
        return 1;
    }
    SHOW_BOOT_PROGRESS (6);

    /*
     * We have reached the point of no return: we are going to
     * overwrite all exception vector code, so we cannot easily
     * recover from any failures any more...
     */

    iflag = disable_interrupts();

#ifdef CONFIG_AMIGAONEG3SE
    /*
     * We've possible left the caches enabled during
     * bios emulation, so turn them off again
     */
    icache_disable();
    invalidate_l1_instruction_cache();
    flush_data_cache();
    dcache_disable();
#endif

    switch (hdr->ih_comp) {
    case IH_COMP_NONE:
        if(ntohl(hdr->ih_load) == addr) {
            printf ("   XIP %s ... ", name);
        } else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
            size_t l = len;
            void *to = (void *)ntohl(hdr->ih_load);
            void *from = (void *)data;

            printf ("   Loading %s ... ", name);

            while (l > 0) {
                size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
                WATCHDOG_RESET();
                memmove (to, from, tail);
                to += tail;
                from += tail;
                l -= tail;
            }
#else   /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
            memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif  /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
        }
        break;
    case IH_COMP_GZIP:
        printf ("   Uncompressing %s ... ", name);
        if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
                (uchar *)data, &len) != 0) {
            puts ("GUNZIP ERROR - must RESET board to recover\n");
            SHOW_BOOT_PROGRESS (-6);
            do_reset (cmdtp, flag, argc, argv);
        }
        break;
#ifdef CONFIG_BZIP2
    case IH_COMP_BZIP2:
        printf ("   Uncompressing %s ... ", name);
        /*
         * If we've got less than 4 MB of malloc() space,
         * use slower decompression algorithm which requires
         * at most 2300 KB of memory.
         */
        i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),
                        &unc_len, (char *)data, len,
                        CFG_MALLOC_LEN < (4096 * 1024), 0);
        if (i != BZ_OK) {
            printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);
            SHOW_BOOT_PROGRESS (-6);
            udelay(100000);
            do_reset (cmdtp, flag, argc, argv);
        }
        break;
#endif /* CONFIG_BZIP2 */
    default:
        if (iflag)
            enable_interrupts();
        printf ("Unimplemented compression type %d\n", hdr->ih_comp);
        SHOW_BOOT_PROGRESS (-7);
        return 1;
    }
    puts ("OK\n");
    SHOW_BOOT_PROGRESS (7);

    switch (hdr->ih_type) {
    case IH_TYPE_STANDALONE:
        if (iflag)
            enable_interrupts();

        /* load (and uncompress), but don't start if "autostart"
         * is set to "no"
         */
        if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {
            char buf[32];
            sprintf(buf, "%lX", len);
            setenv("filesize", buf);
            return 0;
        }
        appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);
        (*appl)(argc-1, &argv[1]);
        return 0;
    case IH_TYPE_KERNEL:
    case IH_TYPE_MULTI:
        /* handled below */
        break;
    default:
        if (iflag)
            enable_interrupts();
        printf ("Can't boot image type %d\n", hdr->ih_type);
        SHOW_BOOT_PROGRESS (-8);
        return 1;
    }
    SHOW_BOOT_PROGRESS (8);

    switch (hdr->ih_os) {
    default:            /* handled by (original) Linux case */
    case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
        fixup_silent_linux();
#endif
        do_bootm_linux  (cmdtp, flag, argc, argv,
                 addr, len_ptr, verify);/*启动内核。do_bootm_linux函数 1. 告诉内核一些启动参数=>设置启动参数;
 2. 跳到入口地址启动内核。*/
        break;
    case IH_OS_NETBSD:
        do_bootm_netbsd (cmdtp, flag, argc, argv,
                 addr, len_ptr, verify);
        break;

#ifdef CONFIG_LYNXKDI
    case IH_OS_LYNXOS:
        do_bootm_lynxkdi (cmdtp, flag, argc, argv,
                 addr, len_ptr, verify);
        break;
#endif

    case IH_OS_RTEMS:
        do_bootm_rtems (cmdtp, flag, argc, argv,
                 addr, len_ptr, verify);
        break;

#if (CONFIG_COMMANDS & CFG_CMD_ELF)
    case IH_OS_VXWORKS:
        do_bootm_vxworks (cmdtp, flag, argc, argv,
                  addr, len_ptr, verify);
        break;
    case IH_OS_QNX:
        do_bootm_qnxelf (cmdtp, flag, argc, argv,
                  addr, len_ptr, verify);
        break;
#endif /* CFG_CMD_ELF */
#ifdef CONFIG_ARTOS
    case IH_OS_ARTOS:
        do_bootm_artos  (cmdtp, flag, argc, argv,
                 addr, len_ptr, verify);
        break;
#endif
    }

    SHOW_BOOT_PROGRESS (-9);
#ifdef DEBUG
    puts ("\n## Control returned to monitor - resetting...\n");
    do_reset (cmdtp, flag, argc, argv);
#endif
    return 1;
}

uboot 在flash上存的是uImage=头部+真正的内核
上面代码中

image_header_t *hdr = &header;

在uboot/include/Image.h中,

typedef struct image_header {
    uint32_t    ih_magic;   /* Image Header Magic Number    */
    uint32_t    ih_hcrc;    /* Image Header CRC Checksum    */
    uint32_t    ih_time;    /* Image Creation Timestamp */
    uint32_t    ih_size;    /* Image Data Size      */
    uint32_t    ih_load;    /* Data  Load  Address  
    加载地址,即内核运行时所放的位置,   */
    uint32_t    ih_ep;      /* Entry Point Address      入口地址,运行内核时,调转到这个地址就可以了*/
    uint32_t    ih_dcrc;    /* Image Data CRC Checksum  */
    uint8_t     ih_os;      /* Operating System     */
    uint8_t     ih_arch;    /* CPU architecture     */
    uint8_t     ih_type;    /* Image Type           */
    uint8_t     ih_comp;    /* Compression Type     */
    uint8_t     ih_name[IH_NMLEN];  /* Image Name       */
} image_header_t;

bootm:

  1. 移动内核到加载地址
  2. 启动内核;

在uboot/common/Cmd_bootm.c 中,

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
......
do_bootm_linux  (cmdtp, flag, argc, argv,
                 addr, len_ptr, verify);
.....
}

do_bootm_linux函数

  1. 告诉内核一些启动参数=>设置启动参数;
  2. 跳到入口地址启动内核。
    do_bootm_linux函数lib_arm/Armlinux.c中
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],ulong addr, ulong *len_ptr, int verify)
{
    ulong len = 0, checksum;
    ulong initrd_start, initrd_end;
    ulong data;
    void (*theKernel)(int zero, int arch, uint params);
    image_header_t *hdr = &header;
    bd_t *bd = gd->bd;

#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");
#endif

    theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

    /*
     * Check if there is an initrd image
     */
    if (argc >= 3) {
        SHOW_BOOT_PROGRESS (9);

        addr = simple_strtoul (argv[2], NULL, 16);

        printf ("## Loading Ramdisk Image at %08lx ...\n", addr);

        /* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
        if (addr_dataflash (addr)) {
            read_dataflash (addr, sizeof (image_header_t),
                    (char *) &header);
        } else
#endif
            memcpy (&header, (char *) addr,
                sizeof (image_header_t));

        if (ntohl (hdr->ih_magic) != IH_MAGIC) {
            printf ("Bad Magic Number\n");
            SHOW_BOOT_PROGRESS (-10);
            do_reset (cmdtp, flag, argc, argv);
        }

        data = (ulong) & header;
        len = sizeof (image_header_t);

        checksum = ntohl (hdr->ih_hcrc);
        hdr->ih_hcrc = 0;

        if (crc32 (0, (unsigned char *) data, len) != checksum) {
            printf ("Bad Header Checksum\n");
            SHOW_BOOT_PROGRESS (-11);
            do_reset (cmdtp, flag, argc, argv);
        }

        SHOW_BOOT_PROGRESS (10);

        print_image_hdr (hdr);

        data = addr + sizeof (image_header_t);
        len = ntohl (hdr->ih_size);

#ifdef CONFIG_HAS_DATAFLASH
        if (addr_dataflash (addr)) {
            read_dataflash (data, len, (char *) CFG_LOAD_ADDR);
            data = CFG_LOAD_ADDR;
        }
#endif

        if (verify) {
            ulong csum = 0;

            printf ("   Verifying Checksum ... ");
            csum = crc32 (0, (unsigned char *) data, len);
            if (csum != ntohl (hdr->ih_dcrc)) {
                printf ("Bad Data CRC\n");
                SHOW_BOOT_PROGRESS (-12);
                do_reset (cmdtp, flag, argc, argv);
            }
            printf ("OK\n");
        }

        SHOW_BOOT_PROGRESS (11);

        if ((hdr->ih_os != IH_OS_LINUX) ||
            (hdr->ih_arch != IH_CPU_ARM) ||
            (hdr->ih_type != IH_TYPE_RAMDISK)) {
            printf ("No Linux ARM Ramdisk Image\n");
            SHOW_BOOT_PROGRESS (-13);
            do_reset (cmdtp, flag, argc, argv);
        }

#if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)
        /*
         *we need to copy the ramdisk to SRAM to let Linux boot
         */
        memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
        data = ntohl(hdr->ih_load);
#endif /* CONFIG_B2 || CONFIG_EVB4510 */

        /*
         * Now check if we have a multifile image
         */
    } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
        ulong tail = ntohl (len_ptr[0]) % 4;
        int i;

        SHOW_BOOT_PROGRESS (13);

        /* skip kernel length and terminator */
        data = (ulong) (&len_ptr[2]);
        /* skip any additional image length fields */
        for (i = 1; len_ptr[i]; ++i)
            data += 4;
        /* add kernel length, and align */
        data += ntohl (len_ptr[0]);
        if (tail) {
            data += 4 - tail;
        }

        len = ntohl (len_ptr[1]);

    } else {
        /*
         * no initrd image
         */
        SHOW_BOOT_PROGRESS (14);

        len = data = 0;
    }

#ifdef  DEBUG
    if (!data) {
        printf ("No initrd\n");
    }
#endif

    if (data) {
        initrd_start = data;
        initrd_end = initrd_start + len;
    } else {
        initrd_start = 0;
        initrd_end = 0;
    }

    SHOW_BOOT_PROGRESS (15);

    debug ("## Transferring control to Linux (at address %08lx) ...\n",
           (ulong) theKernel);

#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
    defined (CONFIG_CMDLINE_TAG) || \
    defined (CONFIG_INITRD_TAG) || \
    defined (CONFIG_SERIAL_TAG) || \
    defined (CONFIG_REVISION_TAG) || \
    defined (CONFIG_LCD) || \
    defined (CONFIG_VFD)
    setup_start_tag (bd);/*设置参数*/
    /*设置启动参数*/
#ifdef CONFIG_SERIAL_TAG
    setup_serial_tag (&params);
#endif
#ifdef CONFIG_REVISION_TAG
    setup_revision_tag (&params);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
    setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
    setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
    if (initrd_start && initrd_end)
        setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
    setup_videolfb_tag ((gd_t *) gd);
#endif
    setup_end_tag (bd);
#endif

    /* we assume that the kernel is in place */
    printf ("\nStarting kernel ...\n\n");

#ifdef CONFIG_USB_DEVICE
    {
        extern void udc_disconnect (void);
        udc_disconnect ();
    }
#endif

    cleanup_before_linux ();

    theKernel (0, bd->bi_arch_number, bd->bi_boot_params);/*程序的最后,启动内核。带入3个参数:0, bd->bi_arch_number(机器ID), bd->bi_boot_params(uboot带入的参数所存放的地址)*/
}

lib_arm/Armlinux.c中

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);/*程序的最后,启动内核*/

bi_arch_number在uboot/include/asm-arm/Mach-types.h中相对于2440处理器为362

#define MACH_TYPE_S3C2440              362

本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦:

  • Markdown和扩展Markdown简洁的语法
  • 代码块高亮
  • 图片链接和图片上传
  • LaTex数学公式
  • UML序列图和流程图
  • 离线写博客
  • 导入导出Markdown文件
  • 丰富的快捷键

快捷键

  • 加粗 Ctrl + B
  • 斜体 Ctrl + I
  • 引用 Ctrl + Q
  • 插入链接 Ctrl + L
  • 插入代码 Ctrl + K
  • 插入图片 Ctrl + G
  • 提升标题 Ctrl + H
  • 有序列表 Ctrl + O
  • 无序列表 Ctrl + U
  • 横线 Ctrl + R
  • 撤销 Ctrl + Z
  • 重做 Ctrl + Y

Markdown及扩展

Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]

使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。

本编辑器支持 Markdown Extra ,  扩展了很多好用的功能。具体请参考Github.

表格

Markdown Extra 表格语法:

项目价格
Computer$1600
Phone$12
Pipe$1

可以使用冒号来定义对齐方式:

项目价格数量
Computer1600 元5
Phone12 元12
Pipe1 元234

定义列表

Markdown Extra 定义列表语法: 项目1 项目2
定义 A
定义 B
项目3
定义 C

定义 D

定义D内容

代码块

代码块语法遵循标准markdown代码,例如:

@requires_authorization
def somefunc(param1='', param2=0):
    '''A docstring'''
    if param1 > param2: # interesting
        print 'Greater'
    return (param2 - param1 + 1) or None
class SomeClass:
    pass
>>> message = '''interpreter
... prompt'''

脚注

生成一个脚注1.

目录

[TOC]来生成目录:

数学公式

使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.

  • 行内公式,数学公式为: Γ(n)=(n1)!nN
  • 块级公式:

x=b±b24ac2a

更多LaTex语法请参考 这儿.

UML 图:

可以渲染序列图:

Created with Raphaël 2.1.0 张三 张三 李四 李四 嘿,小四儿, 写博客了没? 李四愣了一下,说: 忙得吐血,哪有时间写。

或者流程图:

Created with Raphaël 2.1.0 开始 我的操作 确认? 结束 yes no
  • 关于 序列图 语法,参考 这儿,
  • 关于 流程图 语法,参考 这儿.

离线写博客

即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.youkuaiyun.com/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。

用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。

博客发表后,本地缓存将被删除。 

用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。

注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱

浏览器兼容

  1. 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
  2. IE9以下不支持
  3. IE9,10,11存在以下问题
    1. 不支持离线功能
    2. IE9不支持文件导入导出
    3. IE10不支持拖拽文件导入


  1. 这里是 脚注内容.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值