Linux---线程_底层原理

《Linux多线程同步之底层原理futex》 假设没有多线程对全局资源进行竞争,那此时futex在应用层就是个全局的原子变量,用于记录当前锁的状态,需要持有该锁时,修改这个原子变量的状态,进而获取该锁,释放锁时,如果没有其它线程在等待这把锁,直接将该原子变量的状态还原,随后释放锁资源。c、当线程退出临界区,unlock释放锁资源,如果state==2,表明有线程竞争该锁,通过CAS操作将state置为0,通过futex_wake(&state) 陷入内核态,唤醒等待&state的线程,当前线程切回用户态,完整地退出临界区。此刻没有进入内核态。 阅读详情

Linux 下的线程底层原理是一个非常经典且由于历史原因略显独特的领域。要理解它,我们不能仅仅停留在 pthread_create 的 API 层面,而必须深入到内核(Kernel)的数据结构、系统调用以及内存管理机制中。

以下是关于 Linux 线程底层原理的详细讲解,分为几个核心层面:

  1. Linux 只有 LWP:线程本质是共享了内存空间的进程。

  2. clone() 是基石:通过 CLONE_VM 等标志实现资源共享。

  3. 1:1 模型:NPTL 库将用户线程映射为内核 LWP。

  4. Futex:实现了高效的混合态同步锁。

  5. 栈是私有的,堆是共享的。


一、 核心概念:Linux 中没有真正的“线程”

这是理解 Linux 线程最关键的一点。

在 Windows 或 Solaris 等操作系统中,进程(Process)和线程(Thread)是内核中两种截然不同的数据结构。进程是资源容器,线程是执行单元。

但在 Linux 内核看来,线程和进程没有本质区别。 它们都被视为执行实体(Execution Entity),在内核中都用同一个数据结构 task_struct 来表示。

  • 进程(Process): 独占虚拟内存空间、文件描述符表的 task_struct。

  • 线程(Thread): 与其他 task_struct 共享虚拟内存空间、文件描述符表的 task_struct。

因此,Linux 中的线程通常被称为 轻量级进程(LWP, Light Weight Process)。

  • 用户态:你调用 pthread 库创建的线程(pthread_t)是「用户态线程」;
  • 内核态:每个用户态线程对应一个内核态的 LWP(轻量级进程),内核调度的最小单位是LWP(而非进程);
维度普通进程(fork 创建)线程(pthread_create 创建)
内核描述符独立的task_struct独立的task_struct
地址空间(mm_struct)独立(复制父进程地址空间)共享(与线程组其他成员共用同一个)
文件描述符表独立(复制父进程)共享
信号处理(signal)独立共享
用户 / 组 ID(uid/gid)独立共享
栈空间独立(私有栈)独立(每个线程有私有栈,默认 8MB)
寄存器 / 程序计数器独立独立(调度时保存 / 恢复)
PID(内核态)唯一(tgid=pid,线程组 ID = 自身 PID)线程组 ID(tgid)= 主线程 PID,自身 PID=LWP(ps -Lf 看到的 LWP)

二、 内核数据结构与标识

1. task_struct

这是 Linux 内核中最重要的结构体(PCB - Process Control Block)。无论你启动的是一个进程还是一个线程,内核都会分配一个 task_struct。

2. PID 与 TGID(极其重要的区分)

在用户空间(User Space)和内核空间(Kernel Space),对“ID”的定义是不同的,这常让人困惑。

内核视角:

  • PID (Process ID): 每个 task_struct 都有唯一的 PID。如果是多线程程序,每个线程在内核里的 PID 都是不一样的。(对应ps -Lf的LWP字段)

  • TGID (Thread Group ID): 这是线程组 ID。(对应ps -Lf的PID字段)
    主线程(进程):tgid = pid(自身 PID 就是线程组 ID);
    子线程:tgid = 主线程pid(所有线程同属一个线程组)。

用户视角(POSIX 标准):

  • 用户调用 getpid() 时,期望看到所有线程返回同一个 ID。

  • 因此,Linux 的 getpid() 系统调用返回的是当前 task_struct 的 TGID,而不是内核里的 PID。

  • 用户看到的“线程 ID”(通过 pthread_self() 获得)其实是线程库(NPTL)在用户态维护的一个内存地址,而非内核 ID。

UID        PID  PPID   LWP  C NLWP STIME TTY      STAT   TIME CMD
root      1234  1100  1234  0    3 10:00 pts/0    Sl     0:05 ./my_app
root      1234  1100  1235  2    3 10:01 pts/0    Sl     0:12 ./my_app
root      1234  1100  1236  1    3 10:01 pts/0    Sl     0:08 ./my_app


三、 线程的创建:clone() 系统调用

在 Linux 中,创建进程通常用 fork(),而底层实现线程(包括 fork 自身)最终都指向了 clone() 系统调用。

clone() 的神奇之处在于它可以精细控制父子执行实体之间共享哪些资源。

函数原型大致如下:

int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...);
关键的 flags 参数:

通过设置不同的标志位,决定了创建出来的是“进程”还是“线程”:

创建进程 (fork 的行为):

  1. flags 基本为空(或包含 SIGCHLD)。

  2. 子进程会复制父进程的页表(Copy-on-Write)、文件描述符表等。资源是隔离的。

创建线程 (pthread_create 的行为):

flags 会包含以下关键位:

  1. CLONE_THREAD: 将子进程放入父进程的线程组中(设置 TGID)。

  2. CLONE_SIGHAND: 共享信号处理函数。

  3. CLONE_FS: 共享文件系统信息(根目录、当前工作目录)。

  4. CLONE_FILES: 共享文件描述符表(打开的文件)。

  5. CLONE_VM: 共享虚拟内存空间(堆、代码段、全局变量)。这是线程通信快的基础。

总结: Linux 线程本质上就是通过 clone() 系统调用,带上 CLONE_VM 等标志创建出来的、与父进程共享地址空间的“进程”。


四、 线程库:NPTL (Native POSIX Thread Library)

内核提供了基础的 clone 机制,但离好用的 POSIX 线程标准(pthread)还有距离。这就是 glibc 中 NPTL 的工作。

1. 1:1 线程模型

Linux 目前使用的是 1:1 模型。

  • 含义: 用户空间的每一个线程(pthread),都严格对应内核空间的一个 LWP(task_struct)。

  • 优点: 能够真正利用多核 CPU(内核可以把不同的 LWP-轻量级进程 调度到不同的 CPU 核上并行执行)。

  • 缺点: 线程创建和上下文切换(Context Switch)还是有内核开销的(虽然比纯进程切换小得多)。

对比其他模型:

  • N:1 模型(用户态线程映射到 1 个内核 LWP):内核无感知,无法利用多核;
  • M:N 模型(M 个用户线程映射到 N 个内核 LWP):复杂且 Linux 未采用;
  • 1:1 模型:优点是能充分利用多核,缺点是线程创建 / 切换有内核开销(pthread 库已做优化)。
2. NPTL 的职责

当你调用 pthread_create 时,NPTL 做了很多幕后工作:

  1. 分配栈: 线程的栈是私有的。主线程的栈在传统的栈区,而其他线程的栈通常是在 mmap 区域(堆区附近) 分配出来的固定大小内存。

  2. **调用 clone:**以此栈为参数调用系统调用。

  3. 管理元数据: 在用户态维护一个 pthread 结构体,用于存放线程 ID、栈地址、退出状态等。


五、 内存布局:共享与私有

理解线程安全,必须理解内存布局。

1. 共享资源(所有线程都能看、能改)

由于 CLONE_VM,整个进程的虚拟地址空间是共享的:

  • .text (代码段): 所有线程执行同一份代码。

  • .data / .bss (全局/静态变量): 需注意同步竞争。

  • Heap (堆): malloc 出来的内存,指针传递给谁,谁就能访问。

2. 私有资源(线程独享)
  • Stack (栈): 极其重要!虽然理论上通过指针可以访问别的线程的栈,但逻辑上是私有的。局部变量、函数调用链都在这里。

  • Registers (寄存器): 线程调度的本质就是保存和恢复寄存器上下文(PC 指针、SP 栈指针等)。

  • TLS (Thread Local Storage): 通过 __thread 关键字定义的变量。

    • 底层实现: 在 x86_64 架构下,通常利用 FS 或 GS 段寄存器指向当前线程的 TCB(Thread Control Block),从而快速访问线程局部变量。


六、 同步机制的底层:Futex

线程共享内存带来了竞争,所以需要锁(Mutex)。早期的 Linux 线程锁非常慢,因为每次加锁解锁都要陷入内核(System Call)。

现代 Linux 线程同步的核心机制是 Futex (Fast Userspace muTEX)。

原理:

  1. 用户态快路径: 锁本质上是一个整数。线程尝试用原子操作(CAS - Compare And Swap)修改它。如果成功(无竞争),直接拿到锁,完全不经过内核,速度极快。

  2. 内核态慢路径: 只有当原子操作发现锁已经被别人拿走了(有竞争),线程才会调用 futex() 系统调用,陷入内核,将自己挂起(Sleeping),等待被唤醒。

这使得 Linux 线程在无竞争情况下的性能非常接近无锁操作。

1. 互斥锁(pthread_mutex_t)的底层

  • 无竞争场景:完全在用户态通过原子操作(CAS)加锁 / 解锁,无需内核介入,性能极高;
  • 有竞争场景:竞争失败的线程通过 Futex 系统调用陷入内核,进入休眠状态(避免忙等消耗 CPU);
  • 解锁时:唤醒内核中休眠的线程,使其重新竞争锁。

2. 条件变量(pthread_cond_t)的底层

  • 基于 Futex 实现,pthread_cond_wait:释放互斥锁 + 陷入内核休眠,等待信号;
  • pthread_cond_signal:通过 Futex 唤醒休眠的线程,使其重新获取互斥锁并继续执行。

对比:自旋锁 vs 互斥锁(底层)

  • 自旋锁:全程在用户态忙等(CAS),适用于临界区极短的场景(避免内核切换开销);
  • 互斥锁:竞争时陷入内核休眠,适用于临界区较长的场景(减少 CPU 消耗)。

七、 线程调度

既然线程在内核里就是 task_struct,那么调度器(Scheduler)其实不区分你是进程的主线程,还是子线程。

  • 调度算法: 主要是 CFS (Completely Fair Scheduler)。

  • 策略: 内核根据 task_struct 的优先级、消耗的时间片等进行调度。

  • 优势: 因为内核直接调度 LWP,所以假如一个进程里的线程 A 阻塞了(比如读磁盘),内核会立刻调度该进程里的线程 B 运行,不会导致整个进程卡死(这是 1:1 模型相对于用户级线程 M:N 模型的巨大优势)。


八、 总结图解

用户空间 (User Space)
---------------------------------------
进程 (Process)
   |
   +--- 主线程 (Main Thread) ----+
   |   [栈] [TLS] [pthread结构]  |
   |                            | <--- 共享堆、全局变量、文件描述符
   +--- 子线程 (Thread 1) -------+
   |   [栈] [TLS] [pthread结构]  |
   |                            |
---------------------------------------
        | 调用 clone() |
        v              v
---------------------------------------
内核空间 (Kernel Space)
   |
   +--- task_struct (PID=100, TGID=100)  <-- 对应主线程
   |      | map same mm_struct |
   |
   +--- task_struct (PID=101, TGID=100)  <-- 对应子线程
          | map same mm_struct |

Linux线程实现原理 一,说在前面 1.Linux内核不区分线程和进程,PCB和TCB都是tasc_struct 2.task_struct中pid成员值,不论是进程的还是线程的,都是独一无二的 3.当我们用fork创建一个新进程时,会创建一个新的独一无二的task_struct,含有独一无二的pid值 4.当我们用pthread_create创建一个或多个属于原进程的线程时,也会创建新的独一无二的task_struct,含有独一无二的pid值,但是他们隶属于一个线程组,这个线程组的leader thread是原始进程, 阅读详情

相关推荐

【Linux】线程底层原理完全指南

本文全面解析Linux线程底层原理,从基础概念到实际应用。主要内容包括:1) 线程与进程的核心差异对比;2) Linux轻量级进程(LWP)实现机制;3) 线程创建与管理(pthread_create/std::thread);4) 线程同步机制(互斥锁/读写锁)及其C++11实现;5) 死锁避免策略。重点剖析了线程共享/独有资源、内核级实现细节,以及现代C++多线程编程的最佳实践。

fakerth的博客 503

从源码视角彻底搞懂Linux线程实现原理

上面代码看完是否产生一个疑问,man手册中说pthread_detach的线程要自己释放资源,而上面源码中并未看到释放资源的代码,线程执行完比之后是怎么实现自己释放资源的?内核执行完clone系统调用,创建完线程之后会跳转到__start_thread执行,最终会调用到用户pthread_create传入的线程执行函数体中。与pthread_join使用的场景相反,如果线程A请线程B帮忙做事,线程A和线程B可以并行执行,不需要等待线程B的结果反馈,此时由于可能B线程完毕了,

GetnextWindow的专栏 3136

Linux 线程实现机制分析POSIX线程编程

进程是资源管理的最小单位,线程是程序执行的最小单位。在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销。

linux 线程池原理以及实现

线程池实现

818

Linux 线程实现机制分析

LinuxThreads所采用的是线程-进程"一对一"模型,调度交给核心,而在用户级实现一个包括信号处理在内的线程管理机制。 本文描述Linux-LinuxThreads的运行机制

一文掌握SourceTree使用(超级详细)

SourceTree是一款开源的代码仓库客户端工具,界面美观,操作简单,具有代码克隆、本地创建、提交代码、撤销修改,拉取(pull)代码、上传(push)代码、合并分支等操作。对于软件开发人员是一款必备的应用软件。

GM_115的博客 3万+

【Linux】线程

线程相关内容

weixin_74078718的博客 3397

Android GNSS 可视卫星星空图/卫星天顶图 原理及画法介绍

目录1 概念介绍2 绘制步骤2.1 获取卫星信息2.2 绘制底图 效果图: 1 概念介绍 首先介绍一下方位角和高度角的概念: 方位角:是从某点的指北(地理北极)方向线起,依顺时针方向到目标方向线之间的水平夹角。 高度角:从一点至观测目标的方向线与水平面间的夹角。 卫星天顶图 即是根据每一颗卫星的方位角和高度角将其画在以观测位置为中心的天顶图上。 天顶图其底图为由外向内的三个圆和四条直线。三个圆由外向内依次代表高度角0°、30°、60°,中心点代表90°;四条直线分别表示正北-正南、东北-西南、正东

x1471898145的博客 1万+

线程(Linux系统实现)

本文讲述了什么是线程,以及在Linux系统下线程的相关操作

好好学习,天天向上 1723

分享一个N-MOS管和P-MOS管驱动应用实例

大家好,我是『芯知识学堂』的SingleYork,今天给大家分享一下MOS管驱动相关的知识。 MOS管在电路设计中是比较常见的,按照驱动方式来分的话,有两种,即:N-MOS管和P-MOS管。MOS管跟三极管的驱动方式有点类似,但又不完全相同,那么今天笔者将会给大家简单介绍一下N-MOS管和P-MOS管的工作原理,并结合自己实际的应用来给大家分享一下如何来驱动N-MOS管和P-MOS管。 首先,我们先来看一下N-MOS管和P-MOS管分别在电路中的电气符号: 咋一看上面两个电气符号,似乎非常的相似,可能让很

『芯知识学堂』SingleYork 的博客 3万+

详解STM32F407双重ADC同步采集与DMA传输的配置技巧及实战应用

本文详细解析了STM32F407双重ADC同步采集与DMA传输的配置技巧及实战应用。通过CubeMX配置、代码实现和常见问题解决方案,帮助开发者高效实现多通道数据同步采集,提升工业自动化和嵌入式系统的数据采集效率与精度。

ii567的博客 364

【Linux】多线程开发封神之路:Linux 页表基础 + pthread 实战 + 底层原理拆解

摘要: 本文系统讲解了页表结构、线程操作等底层核心机制。首先分析了页表项标志位的作用(存在位、读写位等)及页表结构体设计,指出页目录固定占用4KB空间,其本质是物理地址索引数组。随后详细探讨线程操作,包括pthread_create创建线程、pthread_join获取返回值、参数传递的void*泛型设计,并强调线程终止应避免使用exit()。最后说明线程分离(detach)可自动回收资源,但分离后不可再join。通过多级页表与线程控制的原理解析,揭示了内存管理与并发执行的底层逻辑。 (150字)

欢迎来到我的博客,私信我可以一起交流技术 1614

springboot&mybatis中数组和字符串数据类型的转换

/前端数组字段//数据库中字段/*与前端交互时使用的get/set*/System.out.println("接收前端数据");/*与前端交互时使用的get/set*//*与数据库交互时使用的get/set*/System.out.println("从数据库中读取数据");/*与数据库交互时使用的get/set*/

l_q_l的专栏 2474

聊聊 Linux 线程栈内存的底层实现原理!

第一部分是在用户态管理了用户态的线程对象 struct pthread,以及独立的线程栈。但每一个线程为了保持独立的运行,是各自需要栈空间的。每个线程在并行调用的时候会在栈上独立地执行进栈和出栈,如果都使用进程地址空间中默认的 stack 区域,多线程跑起来就就乱套了。Glibc 中的每个线程在结束阶段都会做一个公共的操作,即释放那些已结束线程的栈内存。我们今天要讨论的线程栈,其实是在第一部分 - 用户态的 glibc 库中执行的。当然了,本文介绍了半天的线程栈地址空间,仍然围绕的是虚拟地址空间来介绍的。

zhangyanfei01的专栏 736

知识图谱设计简记

知识图谱是一种语义化的知识表示框架,通过实体-关系-属性三元组形式表达现实世界关联。其核心价值在于关联化整合数据、支持可解释性推理和赋能智能化应用。知识图谱可分为通用型和领域型两类,基本构成包括实体、关系、属性等基础元素,以及本体和实例等架构层元素。设计方法主要有自上而下、自下而上和混合式三种,需遵循业务导向、一致性等原则。在建模时需要明确属性与实体的判断标准,规范处理关系的方向性与对称性,并合理构建多重关系。主流技术框架如RDF和属性图体系均支持多重关系建模,选择需考虑业务需求。本体作为知识图谱的概念骨架

生活在别处 352

SAP Fiori Elements 扩展实战:UI 层扩展、OData 服务扩展、CDS 视图增强及全流程案例

UI 层扩展主要关注在不修改标准生成界面的基础上,自定义用户界面元素和交互逻辑。典型需求包括:在现有 Fiori Elements 界面上添加新的字段(如表格列或表单项)、增加自定义按钮并绑定操作、以及通过控制器扩展加入事件处理等。无论在 RAP 还是传统 BOPF 架构下,前端 UI 扩展通常采用 SAPUI5 提供的**扩展点(Extension Points)和控制器扩展(Controller Extension)**机制来安全地插入自定义代码。下面分别说明两种后端架构下 UI 扩展的实现方式。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 895

Linux C多线程的**所有底层核心原理**

摘要:抢占式调度是多线程问题的根源,由操作系统CPU调度器通过硬件时钟中断和软件上下文切换实现。时间片耗尽或高优先级任务触发抢占,强制剥夺当前线程CPU使用权。非原子操作(如g_count++)在指令间隙被抢占会导致数据错乱。互斥锁通过让其他线程阻塞来保证临界区完整执行,而非禁止抢占。原子操作因单条指令不可打断而线程安全。Linux中线程/进程抢占机制相同,仅切换开销不同。互斥锁不阻止抢占但能消除其危害,正确理解抢占机制是解决多线程同步问题的关键。(149字)

2301_80136889的博客 805

基于STM32单片机的智能分拣系统

基于STM32单片机的智能分拣系统是一种高效、精准的自动化分拣解决方案,适用于物流、仓储、制造业等领域。该系统利用STM32单片机作为核心控制器,结合传感器技术、电机控制算法和通信模块,实现对物品的自动识别、分类和分拣。通过智能化的设计,该系统能够大幅提高分拣效率,降低人工成本,并减少错误率。基于STM32单片机的智能分拣系统是一种高效、精准、低成本的自动化解决方案,能够广泛应用于物流、制造、环保等领域。

qq_62634009的博客 1939

【转载】【好文、通俗易懂】Linux 进程、线程、文件描述符的底层原理

看到一篇非常通俗易懂的文章,分享给对这块不熟悉的小白: Linux 进程、线程、文件描述符的底层原理

lgfun的专栏 334

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Linux 进程、线程、文件描述符的底层原理

说到进程,恐怕面试中最常见的问题就是线程和进程的关系了,那么先说一下答案:在 Linux 系统中,进程和线程几乎没有区别。Linux 中的进程其实就是一个数据结构,顺带可以理解文件描述符...

fdl123456的博客 1363

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

Linux线程底层原理与实战:从pthread到clone与futex

多线程编程是Linux服务端高并发场景下绕不开的模型,它与进程相比更轻量,能共享地址空间和文件描述符,使数据交换从IPC降级为内存读写。但这种便利的代价是同步复杂、异常难排查:一个线程崩溃会拖垮整个进程,主线程退出会带走所有子线程,锁竞争激烈时性能反而大幅下滑。要真正驾驭线程,需理解pthread_create背后的clone系统调用、用户态互斥锁的futex机制,以及NPTL线程模型的演进。线程栈布局、TLS、取消点等细节,正是线上故障的常见根源。掌握这些底层原理,才能在服务端并发编程中有效避免死锁、栈溢

weixin_34094282的博客 273

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Linux 线程入门到理解:从 pthread 使用到线程库底层原理

关于Linux系统下线程的详细介绍,超用心,必定让你一篇搞懂。代码+运行结果,快来看看吧!!

Zach_yuan的博客 739

深入Linux线程底层原理:从clone到线程池的完整解析

在操作系统与并发编程中,进程和线程是资源分配与任务调度的核心概念。Linux内核通过task_struct统一管理所有执行实体,线程本质上是共享地址空间与文件表的轻量级进程,其创建依赖clone()系统调用及CLONE_VM等标志位。理解线程组、调度实体与TLS机制,有助于掌握线程安全的底层根基。同步层面,futex、互斥锁与条件变量构成了锁与唤醒的完整路径,锁竞争与死锁的排查则常借助gdb、pstack等工具。线程池通过复用线程限制并发度,核心参数需依据CPU密集或IO密集场景调整,Java虚拟线程的引入

weixin_29913663的博客 284

根据Linux内核原理 详细介绍kswapd内核线程何时会被唤醒?其底层原理是什么?

min_free 不可回收的应急内存,由 vm.min_free_kbytes 和系统内存大小决定(默认 ≈ 0.3% 总内存)当 __alloc_pages() 尝试分配内存失败时,若空闲内存 ≤ low_mem,调用 wakeup_kswapd()。waitqueue_active(&pgdat->kswapd_wait)) // 避免重复唤醒。调整 /proc/sys/vm/watermark_scale_factor(默认 10)优化敏感型系统。

sinat_37817094的博客 1381
上一篇: Linux---线程---全面讲解
下一篇: 虚幻引擎_用户小控件_准星
Howrun777
博客等级 码龄1年 133粉丝 100原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值