Kernel space lock contention配置及其使用

本文详细介绍Linux内核中锁调试的配置与使用,包括lock contention的跟踪、lock trace event的捕获及proc节点信息的解析,为解决锁竞争和死锁问题提供有效手段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

本文涉及到的内容如下:

  1. kernel lock相关debug方式,比如lock耗时,拿不到lock,lock依赖等等
  2. trace的使用
1.开启lock contenttion涉及到的config配置
config LOCKDEP  
        bool  
        depends on DEBUG_KERNEL && TRACE_IRQFLAGS_SUPPORT && STACKTRACE_SUPPORT && LOCKDEP_SUPPORT  
        select STACKTRACE  
        select FRAME_POINTER if !MIPS && !PPC && !ARM_UNWIND && !S390 && !MICROBLAZE && !ARC && !SCORE && !X86  
        select KALLSYMS  
        select KALLSYMS_ALL  
  
config LOCKDEP_SMALL  
        bool  
  
config LOCK_STAT  
        bool "Lock usage statistics"  
        depends on DEBUG_KERNEL && TRACE_IRQFLAGS_SUPPORT && STACKTRACE_SUPPORT && LOCKDEP_SUPPORT  
        select LOCKDEP  
        select DEBUG_SPINLOCK  
        select DEBUG_MUTEXES  
        select DEBUG_RT_MUTEXES if RT_MUTEXES  
        select DEBUG_LOCK_ALLOC  
        default n  
        help  
         This feature enables tracking lock contention points  
  
         For more details, see Documentation/locking/lockstat.txt  
  
         This also enables lock events required by "perf lock",  
         subcommand of perf.  
         If you want to use "perf lock", you also need to turn on  
         CONFIG_EVENT_TRACING.  
  
         CONFIG_LOCK_STAT defines "contended" and "acquired" lock events.  
         (CONFIG_LOCKDEP defines "acquire" and "release" events.)  

上面的config默认是关闭状态。

2 menuconfig开启config配置

kernel hacking —> Lock Debugging (spinlock, mutexs, etc…) —>进入之后勾选
Lock Debugging: detect incorrect freeing of live locks 和Lcok usage statistics,就会把依赖项自动勾选.
在这里插入图片描述

保存退出之后,可以查看kernel下面新产生的config diff文件:

# Lock Debugging (spinlocks, mutexes, etc...)  
 #  
-# CONFIG_DEBUG_RT_MUTEXES is not set  
-# CONFIG_DEBUG_SPINLOCK is not set  
-# CONFIG_DEBUG_MUTEXES is not set  
+CONFIG_DEBUG_RT_MUTEXES=y  
+CONFIG_DEBUG_SPINLOCK=y  
+CONFIG_DEBUG_MUTEXES=y  
 # CONFIG_DEBUG_WW_MUTEX_SLOWPATH is not set  
-# CONFIG_DEBUG_LOCK_ALLOC is not set  
+CONFIG_DEBUG_LOCK_ALLOC=y  
 # CONFIG_PROVE_LOCKING is not set  
-# CONFIG_LOCK_STAT is not set  
+CONFIG_LOCKDEP=y  
+CONFIG_LOCK_STAT=y  
+# CONFIG_DEBUG_LOCKDEP is not set  
 # CONFIG_DEBUG_ATOMIC_SLEEP is not set  
 # CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set  
 # CONFIG_LOCK_TORTURE_TEST is not set  

这样开启lock trace event了.。编译boot并刷机即可!

3 确定是否开启trace lock content feature

上面步骤二中开启之后,在adb shell里面多了如下几个接口:

  1. 增加了lock trace event, 有四个lock trace event,具体做什么使用,下面在详细讲解
    在这里插入图片描述
  2. 增加了lock的统计信息, 节点信息实现源码kernel/locking/lockdep_proc.c文件中:
    ● lock_stat: 统计各种类型lock的时延
    ● lockdep: 表示锁的的深度,即一个lock里面调用了多少个lock,并将这些lock的调用全部显示出来
    ● lockdep_stats: 即锁深度里面包含了哪些lock以及频次
    ● locks:锁的状态,这个实现在fs/locks.c文件里面

在这里插入图片描述

4 如何使用lock trace event和proc node信息
4.1 lock trace event

从第三部分可以知道,lock trace event包含四个event,分别讲解如下:
四个event定义在include/trace/event/lock.h里面, 使用在kernel/locking/lockdep.c文件,

  1. lock_acquire: 获取lock,比如mutex lock ,rcu read lock 或者spinlock等等lock
  2. lock_acquired: 表示已经获取lock了
  3. lock_release: lock释放的event
  4. lock_contended: lock被谁hold了

那么如何抓取lock相关的trace event呢?
可以使用下面的脚本抓取(必须添加lock event ),即正常的抓取trace 命令:

echo 40000 > buffer_size_kb && echo irq sched_switch sched_wakeup sched_waking cpu_frequency cpu_idle lock > set_event && cat set_event && echo > trace  && echo 1 > tracing_on && sleep 10 && echo 0 > tracing_on && cat trace > /data/trace.txt   

脚本执行完毕之后, pull出data目录下的trace.txt文件,vim打开即可看到下面类似的信息:

┊   ┊  sleep-2875  [004] d.s2   748.861607: sched_waking: comm=rcu_sched pid=9 prio=120 target_cpu=000  
┊   ┊  sleep-2875  [004] d.s3   748.861608: lock_acquire: 00000000d4eff177 &rq->lock  
┊   ┊  sleep-2875  [004] d.s3   748.861609: lock_contended: 00000000d4eff177 &rq->lock  
┊   ┊  sleep-2875  [004] d.s3   748.861662: lock_acquired: 00000000d4eff177 &rq->lock  
┊   ┊  sleep-2875  [004] d.s3   748.861663: lock_acquire: 000000000ff04d65 read tk_core.seq  
┊   ┊  sleep-2875  [004] d.s3   748.861664: lock_release: 000000000ff04d65 tk_core.seq  
┊   ┊  sleep-2875  [004] d.s3   748.861666: lock_acquire: 00000000cc726398 read rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s3   748.861667: lock_release: 00000000cc726398 rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s3   748.861668: lock_release: 00000000d4eff177 &rq->lock  
┊   ┊  sleep-2875  [004] d.s2   748.861669: lock_acquire: 00000000cc726398 read rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s2   748.861670: lock_acquire: 00000000cc726398 read rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s2   748.861671: lock_release: 00000000cc726398 rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s2   748.861672: lock_acquire: 00000000cc726398 read rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s2   748.861673: lock_release: 00000000cc726398 rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s2   748.861674: lock_acquire: 00000000cc726398 read rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s2   748.861675: lock_release: 00000000cc726398 rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s2   748.861676: lock_acquire: 00000000cc726398 read rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s2   748.861677: lock_release: 00000000cc726398 rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s2   748.861678: lock_acquire: 00000000cc726398 read rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s2   748.861679: lock_release: 00000000cc726398 rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s2   748.861683: lock_release: 00000000cc726398 rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s3   748.861684: lock_acquire: 00000000d4eff177 &rq->lock  
┊   ┊  sleep-2875  [004] d.s3   748.861685: lock_contended: 00000000d4eff177 &rq->lock  
┊   ┊  sleep-2875  [004] d.s3   748.861719: lock_acquired: 00000000d4eff177 &rq->lock  
┊   ┊  sleep-2875  [004] d.s4   748.861720: lock_acquire: 00000000cc2f375c &rq->lock  
┊   ┊  sleep-2875  [004] d.s4   748.861720: lock_acquired: 00000000cc2f375c &rq->lock  
┊   ┊  sleep-2875  [004] d.s4   748.861721: lock_acquire: 000000000ff04d65 read tk_core.seq  
┊   ┊  sleep-2875  [004] d.s4   748.861722: lock_release: 000000000ff04d65 tk_core.seq  
┊   ┊  sleep-2875  [004] d.s4   748.861724: lock_acquire: 00000000cc726398 read rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s4   748.861725: lock_release: 00000000cc726398 rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s4   748.861726: lock_acquire: 00000000cc726398 read rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s4   748.861727: lock_release: 00000000cc726398 rcu_read_lock  
┊   ┊  sleep-2875  [004] d.s4   748.861728: lock_release: 00000000d4eff177 &rq->lock  
┊   ┊  sleep-2875  [004] d.s3   748.861730: lock_release: 00000000cc2f375c &rq->lock  

我们可以看到如下的信息:

  1. rcu_read_lock 是lock_acquire 之后直接lock_release. 持锁时间非常的短
  2. &rq->lock, 是一个spinlock类型. 通过lock_acquire→ lock_acquired→ lock_release的过程. 在lock_acquire过程中出现了lock_contended trace event, 表示此时的&rq->lock spinlock这个lock存在contention或者contended,表示此时有lock的竞争.
  3. 如果出现锁竞争,就会统计当前进程获取lock的等待时间等等统计信息,详细在4.2节讲解.
  4. 可以明显的看到持锁时间, 或者获取锁的时间.
4.2 lock proc node信息

有三个主要的节点信息,都在shell proc目录下:

cat /proc/lock_stat:

/proc # cat lock_stat | head -n 40                                                                                                                                                                 
lock_stat version 0.4  
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
                              class name    con-bounces    contentions   waittime-min   waittime-max waittime-total   waittime-avg    acq-bounces   acquisitions   holdtime-min   holdtime-max holdtime-total   holdtime-avg  
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
  
                 &(&n->list_lock)->rlock:        441821         441946           1.81          80.23     1891806.60           4.28        2488861       10314115           0.00         137.08    64789787.05           6.28  
                 -----------------------  
                 &(&n->list_lock)->rlock         114751          [<0000000051f28fcf>] ___slab_alloc+0x194/0x5ec  
                 &(&n->list_lock)->rlock         105155          [<000000000913f330>] deactivate_slab+0x2ec/0x540  
                 &(&n->list_lock)->rlock         169288          [<00000000fb58074e>] free_debug_processing+0x34/0x26c  
                 &(&n->list_lock)->rlock          12316          [<00000000f00be1d8>] kfree+0x30c/0x55c  
                 -----------------------  
                 &(&n->list_lock)->rlock         111558          [<0000000051f28fcf>] ___slab_alloc+0x194/0x5ec  
                 &(&n->list_lock)->rlock          89847          [<000000000913f330>] deactivate_slab+0x2ec/0x540  
                 &(&n->list_lock)->rlock         192366          [<00000000fb58074e>] free_debug_processing+0x34/0x26c  
                 &(&n->list_lock)->rlock          12305          [<00000000f00be1d8>] kfree+0x30c/0x55c  
  
.............................................................................................................................................................................................................................  
  
                               &rq->lock:        351895         351959           1.88         564.00     9605797.10          27.29        1569868        5892437           0.00        1061.00   172708414.01          29.31  
                               ---------  
                               &rq->lock           5739          [<00000000807dcaa6>] task_rq_lock+0x74/0xc4  
                               &rq->lock           7268          [<00000000f033900c>] pick_next_task_fair+0x378/0x7e4  
                               &rq->lock          85000          [<0000000043e94103>] try_to_wake_up+0x1bc/0x554  
                               &rq->lock          46881          [<0000000078183a31>] try_to_wake_up+0x3b8/0x554  
                               ---------  
                               &rq->lock          24883          [<00000000f57857d0>] update_blocked_averages+0x50/0xc44  
                               &rq->lock           3577          [<00000000807dcaa6>] task_rq_lock+0x74/0xc4  
                               &rq->lock          14525          [<00000000f033900c>] pick_next_task_fair+0x378/0x7e4  
                               &rq->lock              5          [<0000000007411a3e>] walt_set_window_start+0xd4/0x134  
  
.............................................................................................................................................................................................................................  
  
                 &sg_policy->update_lock:        259980         259986           1.88         212.12     1681170.67           6.47        1729740        3292457           1.88         270.81    28368055.35           8.62  
                 -----------------------  
                 &sg_policy->update_lock         259986          [<00000000077def88>] sugov_update_shared+0x5c/0x1b0  
                 -----------------------  
                 &sg_policy->update_lock         259986          [<00000000077def88>] sugov_update_shared+0x5c/0x1b0  
  
.............................................................................................................................................................................................................................  

上面表示从开机到现在cat这个节点的时间内,lock的所有状态信息,包括

  1. lock的名字
  2. contention次数
  3. lock等待的时间,最大时间,最小时间,平均时间以及等待的总时间
  4. lock持有时间,最大,最小,平均以及持有的总时间
  5. 获取这个lock的次数
    等等…

cat /proc/lockdep:

目的是将class_lock上面所有相关联的lock stack全部输出
可能的样式如下:

/proc # cat lockdep | head -n 100                                                                                                                                                              
all lock classes:  
0000000044beef8b ....: logbuf_lock  
  
00000000c80448bf ....: (console_sem).lock  
  
00000000700ad619 ....: console_lock  
  
00000000ef505732 ....: cgroup_mutex  
  
0000000042291e92 ....: console_owner_lock  
  
000000002e29cf8c ....: console_owner  
  
000000004e6f50fd ....: devtree_lock  
  
000000003c9defa6 ....: resource_lock  
  
00000000947b85f2 ....: pm_mutex  
  
0000000010004418 ....: primary_crng.lock  
  
0000000020d31ff4 ....: input_pool.lock  
  
00000000588a9d5b ....: "warn_unseeded_randomness".lock  
  
00000000e13be140 ....: lock  
  
0000000005822fb5 ....: cpu_hotplug_lock.rw_sem  
  
00000000b0cf55a3 ....: cpuhp_state_mutex  

cat /proc/lockdep_stats:

信息如下,主要是一些上下文里面的信息:

/proc # cat lockdep_stats                                                                                                                                                                          
 lock-classes:                         1851 [max: 8191]  
 direct dependencies:                     0 [max: 32768]  
 indirect dependencies:                   0  
 all direct dependencies:                 0  
 in-hardirq chains:                       0  
 in-softirq chains:                       0  
 in-process chains:                       0  
 stack-trace entries:                 20581 [max: 524288]  
 combined max dependencies:               1  
 hardirq-safe locks:                      0  
 hardirq-unsafe locks:                    0  
 softirq-safe locks:                      0  
 softirq-unsafe locks:                    0  
 irq-safe locks:                          0  
 irq-unsafe locks:                        0  
 hardirq-read-safe locks:                 0  
 hardirq-read-unsafe locks:               0  
 softirq-read-safe locks:                 0  
 softirq-read-unsafe locks:               0  
 irq-read-safe locks:                     0  
 irq-read-unsafe locks:                   0  
 uncategorized locks:                  1851  
 unused locks:                            0  
 max locking depth:                      19  
 debug_locks:                             1  

lock event信息还是对于lock debug还是非常有帮助的。
实战在另一篇博文:pr_emerg耗时,影响性能原理排查

<think>我们正在处理一个关于“Lock contention on thread list lock”的问题。根据引用内容,我们可以发现一些相关的锁竞争问题(如enq: TX - row lock contention)的解决思路,但需要针对线程列表锁竞争的具体场景进行分析。 关键点: 1. 引用[2]和[4]提到,锁竞争(Lock Contention)在高并发场景下常见,尤其是行级锁(TX锁)的竞争,通常由于多个线程同时修改同一行数据导致。 2. 引用[3]讨论了临界区和锁护送(Lock Convoys)问题,指出在公平锁机制下,线程切换可能导致性能下降。 针对“线程列表锁竞争”,这通常发生在多线程编程中,当多个线程同时访问或修改全局线程列表时,会争用同一个锁(如线程列表锁)。这种竞争可能导致性能下降,尤其是在线程创建和销毁频繁的应用中。 解决思路: 1. **减少锁的持有时间**:确保在持有线程列表锁时,只执行必要的操作,尽快释放锁。 2. **锁分解(Lock Splitting)**:将一个大锁分解为多个小锁。例如,可以将线程列表按一定规则(如线程ID的哈希)分成多个子列表,每个子列表有自己的锁,从而减少竞争。 3. **无锁数据结构**:考虑使用无锁(lock-free)或基于CAS(Compare-And-Swap)的数据结构来管理线程列表,避免使用互斥锁。 4. **优化线程创建和销毁**:如果竞争是由于频繁创建和销毁线程引起的,可以考虑使用线程池来复用线程,减少对线程列表的修改操作。 5. **避免在锁内执行耗时操作**:如I/O操作、复杂计算等,这些操作应放在锁外。 具体步骤: 步骤1:定位问题 - 使用性能分析工具(如profiler)确认锁竞争的热点,确认是否是线程列表锁。 - 检查代码中所有操作线程列表的地方(如线程创建、销毁、遍历等)。 步骤2:减少锁的持有时间 - 例如,在遍历线程列表时,如果操作耗时,可以考虑先复制一份再遍历(复制时加锁,遍历时无锁)。 步骤3:锁分解 - 将线程列表分成多个桶(bucket),每个桶有自己的锁。这样,当不同线程操作不同桶时就不会竞争。 步骤4:考虑无锁设计 - 使用原子操作和CAS来管理线程列表。例如,可以使用原子指针来更新线程列表的头节点。 步骤5:线程池 - 如果频繁创建销毁线程,使用线程池来复用线程,减少对线程列表的修改。 步骤6:避免不必要的锁 - 检查是否所有操作都需要加锁。例如,某些只读操作在特定情况下(如初始化完成后)可能不需要加锁。 示例代码(锁分解): 假设我们有一个全局的线程列表,我们可以将其分解为多个子列表。 原始代码(伪代码): ```c pthread_mutex_t thread_list_lock; List thread_list; void add_thread(Thread* t) { pthread_mutex_lock(&thread_list_lock); thread_list.append(t); pthread_mutex_unlock(&thread_list_lock); } ``` 修改后(分解为多个桶): ```c #define BUCKET_COUNT 16 pthread_mutex_t bucket_locks[BUCKET_COUNT]; List thread_buckets[BUCKET_COUNT]; void add_thread(Thread* t) { int bucket_index = t->id % BUCKET_COUNT; pthread_mutex_lock(&bucket_locks[bucket_index]); thread_buckets[bucket_index].append(t); pthread_mutex_unlock(&bucket_locks[bucket_index]); } ``` 注意:锁分解需要根据实际访问模式设计,避免跨桶操作带来的复杂性。 步骤7:读写锁(如果读多写少) - 如果线程列表的读操作远多于写操作,可以使用读写锁(如pthread_rwlock_t),允许多个线程同时读取。 步骤8:调整锁的公平性 - 如引用[3]所述,公平锁(FIFO)可能导致性能问题。在某些情况下,非公平锁可能提高吞吐量(但可能导致某些线程饥饿)。 总结: 解决“Lock contention on thread list lock”需要根据具体场景选择优化策略。通常,减少锁的竞争范围、分解锁、使用无锁数据结构是最有效的方法。 相关问题: 1. 如何检测程序中的锁竞争? 2. 无锁编程有哪些常见模式? 3. 线程池如何减少锁竞争? 4. 读写锁适用于哪些场景? 5. 锁护送(Lock Convoys)是如何产生的?如何避免?</think>### 解决线程列表锁竞争(Lock Contention on Thread List Lock)的方法 线程列表锁竞争是典型的高并发场景问题,当多个线程频繁访问全局线程列表时,会导致锁争用和性能下降。以下是系统化的解决方案: --- ### 一、根本原因分析 1. **高频访问** 多个线程同时操作共享资源(如全局线程列表),触发锁竞争[^3]。 2. **锁粒度问题** 单一粗粒度锁保护整个线程列表,导致并发操作串行化[^3]。 3. **长临界区** 在锁内执行耗时操作(如I/O、复杂计算),延长锁持有时间[^3]。 --- ### 二、核心解决方案 #### 1. 减小锁粒度(锁分解) ```c // 原始方案:单一全局锁 pthread_mutex_t global_list_lock; // 优化方案:按哈希分桶+分段锁 #define BUCKET_SIZE 16 pthread_mutex_t bucket_locks[BUCKET_SIZE]; List thread_buckets[BUCKET_SIZE]; void add_thread(Thread* t) { int bucket = hash(t->id) % BUCKET_SIZE; pthread_mutex_lock(&bucket_locks[bucket]); thread_buckets[bucket].append(t); pthread_mutex_unlock(&bucket_locks[bucket]); } ``` - **原理**:将线程列表拆分为多个桶,每个桶独立加锁 - **效果**:降低竞争概率,提升并发度[^3] #### 2. 使用无锁数据结构 ```c // 基于CAS的无锁链表 std::atomic<Node*> head; void push(Thread* t) { Node* new_node = new Node(t); Node* old_head = head.load(); do { new_node->next = old_head; } while (!head.compare_exchange_weak(old_head, new_node)); } ``` - **适用场景**:读多写少,短时操作 - **优势**:完全避免锁竞争,但需处理ABA问题[^3] #### 3. 缩短临界区 ```c // 错误示例:锁内执行耗时操作 pthread_mutex_lock(&lock); process_data(thread); // 耗时操作 list.remove(thread); pthread_mutex_unlock(&lock); // 正确示例:仅锁内执行必要操作 Thread* temp = NULL; pthread_mutex_lock(&lock); temp = list.pop(); pthread_mutex_unlock(&lock); process_data(temp); // 锁外执行耗时操作 ``` - **关键**:确保临界区只包含共享数据操作[^3] #### 4. 读写分离优化 ```c pthread_rwlock_t rwlock; // 读写锁 // 读操作(并发访问) pthread_rwlock_rdlock(&rwlock); search_thread(id); pthread_rwlock_unlock(&rwlock); // 写操作(独占访问) pthread_rwlock_wrlock(&rwlock); update_thread(id); pthread_rwlock_unlock(&rwlock); ``` - **适用场景**:读操作频率远高于写操作时 - **性能提升**:允许多个读线程并发访问[^3] --- ### 三、高级优化策略 1. **线程局部存储(TLS)** 将线程私有数据存储在TLS中,避免访问全局列表。 2. **对象池模式** 预分配线程对象池,减少动态创建销毁的开销。 3. **异步处理机制** 使用消息队列解耦操作,批量处理线程列表变更。 4. **锁护送(Lock Convoys)预防** 禁用锁的公平性策略(如FIFO),改用非公平锁减少上下文切换[^3]。 --- ### 四、诊断工具推荐 1. **Linux** ```perf lock``` 或 ```bpftrace``` 分析锁争用热点 2. **Java** ```jstack``` + ```jvisualvm``` 检测线程阻塞点 3. **Windows** ETW(Event Tracing for Windows)捕获锁事件 > **关键指标**:锁等待时间占比 >5% 即需优化[^3] --- ### 五、Oracle数据库类比(参考引用) 虽然引用[1][2][4]讨论数据库锁(enq: TX),但原理相通: - 行锁竞争 → 线程列表锁竞争 - 解决方案共性:减小锁粒度、缩短事务(临界区)、异步处理[^1][^2][^4] --- ### 相关问题 1. 如何检测代码中的锁竞争热点? 2. 无锁编程的适用场景和风险有哪些? 3. 读写锁与互斥锁的性能差异如何量化? 4. 如何避免锁护送(Lock Convoys)现象? 5. 数据库行锁竞争(enq: TX)与线程锁竞争的解决思路有何异同?[^1][^2][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值