26、ASPI编程概述

ASPI编程概述

1. SRB超时与事件超时

在ASPI编程中, WaitForSingleObject 使用了无限超时,因为SRB超时和事件超时是不同的。可以使用 SC_GETSET_TIMEOUT 将超时与SRB关联起来。以下是一个示例代码:

BYTE byInquiry[32];
DWORD dwASPIStatus;
HANDLE heventSRB;
SRB_ExecSCSICmd srbExec;
heventSRB = CreateEvent( NULL, TRUE, FALSE, NULL );
if( !heventSRB )
{
    // Couldn't get manual reset event, put error handling code here!
}
memset( &srbExec, 0, sizeof(SRB_ExecSCSICmd) );
srbExec.SRB_Cmd = SC_EXEC_SCSI_CMD;
srbExec.SRB_Flags = SRB_DIR_IN | SRB_EVENT_NOTIFY;
srbExec.SRB_Target = 5;
srbExec.SRB_BufLen = 32;
srbExec.SRB_BufPointer = byInquiry;
srbExec.SRB_SenseLen = SENSE_LEN;
srbExec.SRB_CDBLen = 6;
srbExec.SRB_PostProc = (LPVOID)heventSRB;
srbExec.CDBByte[0] = SCSI_INQUIRY;
srbExec.CDBByte[4] = 32;
ResetEvent( hevenSRB );
dwASPIStatus = SendASPI32Command( (LPSRB)&srbExec );
if( dwASPIStatus == SS_PENDING )
{
    WaitForSingleObject( heventSRB, INFINITE );
}
if( srbExec.SRB_Status != SS_COMP )
{
    // Error processing the SRB, put error handling code here.
}
2. SC_ABORT_SRB命令

SendASPI32Command 函数使用命令代码 SC_ABORT_SRB 来请求中止一个挂起的SRB。如果应用程序希望停止某个未完成的I/O请求,就可以发出此命令,但该中止命令的成功与否无法保证。

SRB_Abort结构体定义如下:

typedef struct
{
    BYTE SRB_Cmd; 
    // ASPI command code = SC_ABORT_SRB
    BYTE SRB_Status; 
    // ASPI command status byte
    BYTE SRB_HaId; 
    // ASPI host adapter number
    BYTE SRB_Flags; 
    // Reserved, MUST = 0
    DWORD SRB_Hdr_Rsvd; 
    // Reserved, MUST = 0
    LPSRB SRB_ToAbort; 
    // Pointer to SRB to abort
}
SRB_Abort, *PSRB_Abort;

SRB各字段说明:
| 字段 | 输入/输出 | 说明 |
| ---- | ---- | ---- |
| SRB_Cmd | 输入 | 必须包含 SC_ABORT_SRB (0x03) |
| SRB_Status | 输出 | 此SRB是同步的,返回值与 SendASPI32Command 相同,可能为 SS_COMP SS_INVALID_HA SS_INVALID_SRB SS_COMP 仅表示尝试中止SRB,而非已成功中止 |
| SRB_HaId | 输入 | 指定请求所针对的已安装主机适配器编号,从0开始分配 |
| SRB_ToAbort | 输入 | 指向要中止的SRB的指针,中止操作的实际结果由该SRB最终返回的状态指示 |

需要注意的是, SC_ABORT_SRB 命令的成功性无法保证,在Win32下其用途大大降低,建议在新的ASPI模块中使用 SC_GETSET_TIMEOUTS SRB来管理SRB超时。

3. SC_RESET_DEV命令

SendASPI32Command 函数使用命令代码 SC_RESET_DEV 向指定目标发送SCSI总线设备复位信号。

SRB_BusDeviceReset结构体定义如下:

typedef struct
{
    BYTE SRB_Cmd; 
    // ASPI command code = SC_RESET_DEV
    BYTE SRB_Status; 
    // ASPI command status byte
    BYTE SRB_HaId; 
    // ASPI host adapter number
    BYTE SRB_Flags; 
    // Reserved, MUST = 0
    DWORD SRB_Hdr_Rsvd; 
    // Reserved, MUST = 0
    BYTE SRB_Target; 
    // Target's SCSI ID
    BYTE SRB_Lun; 
    // Target's LUN number
    BYTE SRB_Rsvd1[12]; 
    // Reserved, MUST = 0
    BYTE SRB_HaStat; 
    // Host Adapter Status
    BYTE SRB_TargStat; 
    // Target Status
    LPVOID SRB_PostProc; 
    // Post routine
    BYTE SRB_Rsvd2[36]; 
    // Reserved, MUST = 0
}
SRB_BusDeviceReset, *PSRB_BusDeviceReset;

SRB各字段说明:
| 字段 | 输入/输出 | 说明 |
| ---- | ---- | ---- |
| SRB_Cmd | 输入 | 必须包含 SC_RESET_DEV (0x04) |
| SRB_Status | 输出 | 此SRB是异步的,完成后可能为 SS_COMP SS_ERR 等。 SS_COMP 表示成功完成, SS_ERR 需检查 SRB_HaStat SRB_TargStat 字段 |
| SRB_HaId | 输入 | 指定请求所针对的已安装主机适配器编号,从0开始分配 |
| SRB_Target | 输入 | 目标设备的SCSI ID |
| SRB_Lun | 输入 | 目标设备的逻辑单元号,在Win32中此字段被忽略 |
| SRB_HaStat | 输出 | SCSI命令完成后,设置为主机适配器状态。仅当 SRB_Status 不为 SS_COMP 时有效 |
| SRB_TargStat | 输出 | SCSI命令完成后,设置为最终SCSI目标状态。仅当 SRB_Status 不为 SS_COMP 时有效 |
| SRB_PostProc | 输入 | 如果启用了发布( SRB_POSTING ),包含指向函数的指针;如果启用了事件通知( SRB_EVENT_NOTIFY ),包含事件句柄 |

主机适配器状态表如下:
| 符号 | 值 | 描述 |
| ---- | ---- | ---- |
| HASTAT_OK | 0x00 | 主机适配器未检测到错误 |
| HASTAT_TIMEOUT | 0x09 | 总线事务分配的时间用完 |
| HASTAT_COMMAND_TIMEOUT | 0x0B | SRB在等待处理时过期 |
| HASTAT_MESSAGE_REJECT | 0x0D | 处理SRB时收到MESSAGE REJECT |
| HASTAT_BUS_RESET | 0x0E | 检测到总线复位 |
| HASTAT_PARITY_ERROR | 0x0F | 检测到奇偶校验错误 |
| HASTAT_REQUEST_SENSE_FAILED | 0x10 | 目标设备报告检查条件后,适配器发出请求感测失败 |
| HASTAT_SEL_TO | 0x11 | 选择目标超时 |
| HASTAT_DO_DU | 0x12 | 数据溢出 |
| HASTAT_BUS_FREE | 0x13 | 意外的总线空闲 |
| HASTAT_PHASE_ERR | 0x14 | 目标总线相位序列失败 |

SCSI目标状态表如下:
| 符号 | 值 | 描述 |
| ---- | ---- | ---- |
| STATUS_GOOD | 0x00 | 无目标状态 |
| STATUS_CHKCOND | 0x02 | 检查状态(感测数据在SenseArea中) |
| STATUS_BUSY | 0x08 | 指定的目标/LUN繁忙 |
| STATUS_RESCONF | 0x18 | 预留冲突 |

需要注意的是,Windows 95和Windows NT操作系统目前不能正确处理总线设备复位,因此 SC_RESET_DEV 调用不能保证正常工作。

4. SC_GET_DISK_INFO命令

SendASPI32Command 函数使用命令代码 SC_GET_DISK_INFO 来获取磁盘类型SCSI设备的信息,包括BIOS Int 13h控制和设备可访问性、驱动器的Int 13h物理驱动器号以及Int 13h服务使用的驱动器几何信息。此命令在Windows NT中无效。

SRB_GetDiskInfo结构体定义如下:

typedef struct
{
    BYTE SRB_Cmd; 
    // ASPI command code = SC_GET_DISK_INFO
    BYTE SRB_Status; 
    // ASPI command status byte
    BYTE SRB_HaId; 
    // ASPI host adapter number
    BYTE SRB_Flags; 
    // Reserved
    DWORD SRB_Hdr_Rsvd; 
    // Reserved
    BYTE SRB_Target; 
    // Target's SCSI ID
    BYTE SRB_Lun; 
    // Target's LUN number
    BYTE SRB_DriveFlags; 
    // Driver flags
    BYTE SRB_Int13HDriveInfo; 
    // Host Adapter Status
    BYTE SRB_Heads; 
    // Preferred number of heads translation
    BYTE SRB_Sectors; 
    // Preferred number of sectors translation
    BYTE SRB_Rsvd1[10]; 
    // Reserved
}
SRB_GetDiskInfo, *PSRB_GetDiskInfo;

SRB各字段说明:
| 字段 | 输入/输出 | 说明 |
| ---- | ---- | ---- |
| SRB_Cmd | 输入 | 必须包含 SC_GET_DISK_INFO (0x06) |
| SRB_Status | 输出 | 此SRB是同步的,返回值可能为 SS_COMP SS_INVALID_HA SS_NO_DEVICE SS_INVALID_SRB |
| SRB_HaId | 输入 | 指定请求所针对的已安装主机适配器编号,从0开始分配 |
| SRB_Target | 输入 | 目标设备的SCSI ID |
| SRB_Lun | 输入 | 目标设备的逻辑单元号 |
| SRB_DriveFlags | 输出 | 根据以下表格设置 |
| SRB_Int13DriveInfo | 输出 | 仅当 SRB_DriveFlags DISK_INT13_AND_DOS DISK_INT13 时有效,为Int 13h服务分配给设备的物理驱动器号 |
| SRB_Heads | 输出 | 仅当 SRB_DriveFlags DISK_INT13_AND_DOS DISK_INT13 时有效,为Int 13h服务使用的设备几何信息中的磁头数 |
| SRB_Sectors | 输出 | 仅当 SRB_DriveFlags DISK_INT13_AND_DOS DISK_INT13 时有效,为Int 13h服务使用的设备几何信息中的扇区数 |

驱动器标志表如下:
| 符号 | 值 | 描述 |
| ---- | ---- | ---- |
| DISK_NOT_INT13 | 0x00 | 设备不受Int 13h服务控制 |
| DISK_INT13_AND_DOS | 0x01 | 设备受Int 13h控制且被DOS占用 |
| DISK_INT13 | 0x02 | 设备受Int 13h控制但未被DOS占用 |

示例代码:

SRB_GetDiskInfo srbGetDiskInfo;
memset( &srbGetDiskInfo, 0, sizeof(SRB_GetDiskInfo) );
srbGetDiskInfo.SRB_Header.SRB_Cmd = SC_GET_DISK_INFO;
srbGetDiskInfo.SRB_Target = 2;
SendASPI32Command( (LPSRB)&srbGetDiskInfo );
if( srbGetDiskInfo.SRB_Status != SS_COMP )
{
    // Error handling GetDiskInfo SRB. Error handling code goes here!
}
5. SC_RESCAN_SCSI_BUS命令

SendASPI32Command 函数使用命令代码 SC_RESCAN_SCSI_BUS 来重新扫描SRB中指定主机适配器编号的SCSI总线,会指示I/O子系统重新扫描SCSI总线并更新系统设备映射和ASPI管理器设备表。

SRB_RescanPort结构体定义如下:

typedef struct
{
    BYTE SRB_Cmd; 
    // ASPI command code = SC_RESCAN_SCSI_BUS
    BYTE SRB_Status; 
    // ASPI command status byte
    BYTE SRB_HaId; 
    // ASPI host adapter number
    BYTE SRB_Flags; 
    // Reserved, MUST = 0
    DWORD SRB_Hdr_Rsvd; 
    // Reserved, MUST = 0
}
SRB_RescanPort, *PSRB_RescanPort;

SRB各字段说明:
| 字段 | 输入/输出 | 说明 |
| ---- | ---- | ---- |
| SRB_Cmd | 输入 | 必须包含 SC_RESCAN_SCSI_BUS (0x07) |
| SRB_Status | 输出 | 此SRB是同步的,返回值可能为 SS_COMP SS_INVALID_HA |
| SRB_HaId | 输入 | 指定请求所针对的已安装主机适配器编号,从0开始分配 |

在Windows NT下,I/O子系统不会重新扫描已知的设备/ID,只能检测新设备;在Windows 95下,重新扫描命令发出后,设备映射的更新可能会有延迟。

示例代码:

SRB_RescanPort srbRescanPort;
memset( &srbRescanPort, 0, sizeof(SRB_RescanPort) );
srbRescanPort.SRB_Cmd = SC_RESCAN_SCSI_BUS;
SendASPI32Command( (LPSRB)&srbRescanPort );
if( srbRescanPort.SRB_Status != SS_COMP )
{
    // Error issuing port rescan. Error handling code goes here.
}
6. SC_GETSET_TIMEOUTS命令

SendASPI32Command 函数使用命令代码 SC_GETSET_TIMEOUTS 可以以半秒为增量设置特定目标的超时时间。设置后,该超时时间将应用于通过 SC_EXEC_SCSI_CMD 命令发送的所有SCSI命令。超时是针对进程的,不同应用程序可以为同一目标设置不同的超时时间。 SRB_HaId SRB_Target SRB_Lun 字段可以设置为通配符值,以便为多个目标设置超时时间。默认情况下,所有目标超时时间设置为30小时(最大允许值)。

SRB_GetSetTimeouts结构体定义如下:

typedef struct
{
    BYTE SRB_Cmd; 
    // ASPI command code = SC_GETSET_TIMEOUTS
    BYTE SRB_Status; 
    // ASPI command status byte
    BYTE SRB_HaId; 
    // ASPI host adapter number
    BYTE SRB_Flags; 
    // ASPI request flags
    DWORD SRB_Hdr_Rsvd; 
    // Reserved
    BYTE SRB_Target; 
    // Target's SCSI ID
    BYTE SRB_Lun; 
    // Target's LUN number
    DWORD SRB_Timeout; 
    // Timeout in half seconds
}
SRB_GetSetTimeouts, *PSRB_GetSetTimeouts;

SRB各字段说明:
| 字段 | 输入/输出 | 说明 |
| ---- | ---- | ---- |
| SRB_Cmd | 输入 | 必须包含 SC_GETSET_TIMEOUTS (0x08) |
| SRB_Status | 输出 | 此SRB是同步的,返回值可能为 SS_COMP SS_INVALID_HA SS_NO_DEVICE SS_INVALID_SRB |
| SRB_HaId | 输入 | 指定请求所针对的已安装主机适配器编号。如果 SRB_DIR_OUT SRB_Flags 中设置,则此值可以为通配符(0xFF),表示所有主机适配器上的 SRB_Target/SRB_Lun 组合都应设置新的超时时间 |
| SRB_Flags | 输入 | 可以设置为以下两个常量之一:
- SRB_DIR_IN (0x08) :用于检索当前超时设置,ASPI地址字段中不允许使用通配符
- SRB_DIR_OUT (0x10) :用于更改当前超时设置,ASPI地址字段中允许使用通配符 |
| SRB_Target | 输入 | 目标设备的SCSI ID。如果 SRB_DIR_OUT SRB_Flags 中设置,则此值可以为通配符(0xFF),表示指定 SRB_HaId/SRB_Lun 的所有SCSI ID都应设置新的超时时间 |
| SRB_Lun | 输入 | 设备的逻辑单元号。如果 SRB_DIR_OUT SRB_Flags 中设置,则此值可以为通配符(0xFF),表示指定 SRB_HaId/SRB_Target 的所有LUN都应设置新的超时时间 |
| SRB_Timeout | 输入 | 目标的超时时间(半秒)。如果是 SRB_DIR_OUT ,则此值为指定目标的新超时时间;如果是 SRB_DIR_IN ,则由ASPI设置为指定目标的当前超时时间。超时范围为0 - 108000(30小时),0表示最大超时(30小时) |

当为目标设置超时时间后,所有通过 SC_EXEC_SCSI_CMD 发送的SRB都将使用该超时时间。如果某个SRB超时,SCSI总线将被复位,所有正在执行的SRB将被取消,驱动程序会设置相应的错误代码。超时SRB的结果取决于驱动程序设置的错误代码,不同的控制器可能有不同的结果。使用事件通知和超时设置时,要注意 SRB_PostProc 字段中的 HEVENT 有独立的超时时间。

流程图如下:

graph TD;
    A[开始] --> B[设置超时时间];
    B --> C{SRB是否超时};
    C -- 是 --> D[复位SCSI总线];
    D --> E[取消所有SRB];
    E --> F[设置错误代码];
    F --> G[处理错误并重试];
    C -- 否 --> H[继续执行];
    H --> I[完成操作];
    I --> J[结束];

通过以上介绍,我们对ASPI编程中的各种命令和超时设置有了更深入的了解。在实际应用中,需要根据具体需求选择合适的命令和设置,以确保程序的稳定性和可靠性。

ASPI编程概述(续)

7. 超时设置注意事项及示例

在使用 SC_GETSET_TIMEOUTS 命令设置超时时间时,除了上述提到的基本信息,还有一些细节需要注意。当使用通配符设置超时时间时,虽然可以方便地为多个目标统一设置,但要明确通配符是有特定作用范围的。例如,设置 HaId 0xFF 并不意味着 SRB_Target SRB_Lun 可以随意取值,它们仍然需要根据具体情况合理设置。

以下是一个使用通配符设置超时时间的示例:

SRB_GetSetTimeouts srbGetSetTimeouts;
memset(&srbGetSetTimeouts, 0, sizeof(SRB_GetSetTimeouts));
srbGetSetTimeouts.SRB_Cmd = SC_GETSET_TIMEOUTS;
srbGetSetTimeouts.SRB_Flags = SRB_DIR_OUT;
srbGetSetTimeouts.SRB_HaId = 0xFF; // 通配所有主机适配器
srbGetSetTimeouts.SRB_Target = 0xFF; // 通配所有SCSI ID
srbGetSetTimeouts.SRB_Lun = 0xFF; // 通配所有LUN
srbGetSetTimeouts.SRB_Timeout = 100; // 设置超时时间为50秒(100个半秒)

DWORD dwASPIStatus = SendASPI32Command((LPSRB)&srbGetSetTimeouts);
if (dwASPIStatus != SS_COMP)
{
    // 处理设置超时失败的情况
    // Error handling code goes here!
}
8. 不同操作系统下的ASPI命令表现对比

不同操作系统对ASPI命令的支持和处理方式存在差异,这会影响到程序的兼容性和稳定性。以下是对Windows NT和Windows 95系统下部分ASPI命令的对比:
| 命令 | Windows NT表现 | Windows 95表现 |
| ---- | ---- | ---- |
| SC_RESCAN_SCSI_BUS | I/O子系统不会重新扫描已知的设备/ID,只能检测新设备,无法检测设备的移除或更换 | 重新扫描命令发出后,设备映射的更新可能会有延迟,最好结合即插即用消息和 TranslateASPI32Address 处理,或在命令发出5 - 10秒后自行刷新 |
| SC_RESET_DEV | 操作系统目前不能正确处理总线设备复位,调用不能保证正常工作 | 同样不能保证 SC_RESET_DEV 调用正常工作,该命令主要用于兼容从Win16移植的旧代码 |

9. 错误处理流程

在ASPI编程中,错误处理是确保程序健壮性的重要环节。不同的命令在执行过程中可能会出现各种错误,需要根据 SRB_Status SRB_HaStat SRB_TargStat 等字段的值进行相应的处理。

以下是一个通用的错误处理流程图:

graph TD;
    A[执行ASPI命令] --> B{SRB_Status是否为SS_COMP};
    B -- 是 --> C[命令成功完成];
    B -- 否 --> D{检查SRB_HaStat和SRB_TargStat};
    D --> E{是否为已知错误类型};
    E -- 是 --> F[根据错误类型处理];
    E -- 否 --> G[记录未知错误信息];
    F --> H[重试命令或采取其他措施];
    G --> H;
    H --> I{是否重试成功};
    I -- 是 --> C;
    I -- 否 --> J[输出错误提示并终止操作];
10. 综合应用示例

假设我们需要编写一个程序,获取磁盘信息并设置超时时间,同时支持对SCSI总线的重新扫描。以下是一个综合示例代码:

#include <windows.h>

// 假设的常量定义
#define SC_GET_DISK_INFO 0x06
#define SC_RESCAN_SCSI_BUS 0x07
#define SC_GETSET_TIMEOUTS 0x08
#define SS_COMP 0x00
#define SRB_DIR_OUT 0x10

// SRB结构体定义(省略部分重复代码)
typedef struct
{
    BYTE SRB_Cmd; 
    BYTE SRB_Status; 
    BYTE SRB_HaId; 
    BYTE SRB_Flags; 
    DWORD SRB_Hdr_Rsvd; 
    BYTE SRB_Target; 
    BYTE SRB_Lun; 
    // 其他字段...
} SRB_GetDiskInfo, *PSRB_GetDiskInfo;

typedef struct
{
    BYTE SRB_Cmd; 
    BYTE SRB_Status; 
    BYTE SRB_HaId; 
    BYTE SRB_Flags; 
    DWORD SRB_Hdr_Rsvd; 
} SRB_RescanPort, *PSRB_RescanPort;

typedef struct
{
    BYTE SRB_Cmd; 
    BYTE SRB_Status; 
    BYTE SRB_HaId; 
    BYTE SRB_Flags; 
    DWORD SRB_Hdr_Rsvd; 
    BYTE SRB_Target; 
    BYTE SRB_Lun; 
    DWORD SRB_Timeout; 
} SRB_GetSetTimeouts, *PSRB_GetSetTimeouts;

// 假设的SendASPI32Command函数
DWORD SendASPI32Command(LPSRB srb);

int main()
{
    // 获取磁盘信息
    SRB_GetDiskInfo srbGetDiskInfo;
    memset(&srbGetDiskInfo, 0, sizeof(SRB_GetDiskInfo));
    srbGetDiskInfo.SRB_Cmd = SC_GET_DISK_INFO;
    srbGetDiskInfo.SRB_Target = 2;
    srbGetDiskInfo.SRB_Lun = 0;
    srbGetDiskInfo.SRB_HaId = 0;

    DWORD dwASPIStatus = SendASPI32Command((LPSRB)&srbGetDiskInfo);
    if (dwASPIStatus != SS_COMP)
    {
        // 处理获取磁盘信息失败的情况
        // Error handling code goes here!
    }

    // 重新扫描SCSI总线
    SRB_RescanPort srbRescanPort;
    memset(&srbRescanPort, 0, sizeof(SRB_RescanPort));
    srbRescanPort.SRB_Cmd = SC_RESCAN_SCSI_BUS;
    srbRescanPort.SRB_HaId = 0;

    dwASPIStatus = SendASPI32Command((LPSRB)&srbRescanPort);
    if (dwASPIStatus != SS_COMP)
    {
        // 处理重新扫描失败的情况
        // Error handling code goes here!
    }

    // 设置超时时间
    SRB_GetSetTimeouts srbGetSetTimeouts;
    memset(&srbGetSetTimeouts, 0, sizeof(SRB_GetSetTimeouts));
    srbGetSetTimeouts.SRB_Cmd = SC_GETSET_TIMEOUTS;
    srbGetSetTimeouts.SRB_Flags = SRB_DIR_OUT;
    srbGetSetTimeouts.SRB_HaId = 0;
    srbGetSetTimeouts.SRB_Target = 2;
    srbGetSetTimeouts.SRB_Lun = 0;
    srbGetSetTimeouts.SRB_Timeout = 200; // 设置超时时间为100秒(200个半秒)

    dwASPIStatus = SendASPI32Command((LPSRB)&srbGetSetTimeouts);
    if (dwASPIStatus != SS_COMP)
    {
        // 处理设置超时失败的情况
        // Error handling code goes here!
    }

    return 0;
}
11. 总结

ASPI编程涉及到多种命令和超时设置,每个命令都有其特定的用途和参数设置。在实际开发中,需要根据具体的应用场景选择合适的命令,并注意不同操作系统下的差异。同时,合理设置超时时间可以避免程序长时间等待,提高程序的响应速度和稳定性。通过对错误处理流程的设计,可以增强程序的健壮性,确保在出现异常情况时能够正确处理。希望本文的介绍能帮助开发者更好地掌握ASPI编程,开发出高效、稳定的程序。

在未来的ASPI编程中,随着操作系统和硬件的不断发展,可能会有更多的功能和优化出现。开发者需要持续关注相关技术的更新,及时调整和改进自己的代码,以适应新的需求和挑战。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值