Linux-进程的管理与调度20(基于6.1内核)

Linux-进程的管理与调度20(基于6.1内核)---Linux进程优先级

一、 linux优先级的表示


1.1、 优先级的内核表示


1. 进程优先级类型

Linux 使用不同的优先级类型来表示进程的优先级,主要有两个:静态优先级(Static Priority)和动态优先级(Dynamic Priority)。

(1) 静态优先级(Static Priority)

静态优先级是操作系统在创建进程时根据某些因素(如进程的优先级参数)确定的。静态优先级较高的进程在调度时会优先获得 CPU 资源。静态优先级的范围通常在 0139 之间,其中 0 表示最高优先级,139 表示最低优先级。静态优先级的改变通常与进程的优先级设置(例如通过 nice 命令)有关。

  • 0-99:标准优先级(可以通过 nice 命令调整)
  • 100-139:实时优先级(由实时调度策略控制)

(2) 动态优先级(Dynamic Priority)

动态优先级是由 Linux 内核根据进程的执行状态(如进程的等待时间、CPU 使用情况等)动态调整的。在大多数情况下,Linux 使用 CFS(完全公平调度器) 来处理动态优先级。CFS 根据进程的执行时间、等待时间和系统负载来动态调整优先级。

动态优先级的范围一般为 100-139,它表示进程的“相对优先级”。在 CFS 中,进程的动态优先级会根据其“虚拟运行时间”(vruntime)进行调整,vruntime 较小的进程会优先得到调度。动态优先级较低的进程将被延迟执行。

2. nice 值与优先级

nice 是 Linux 中用来调整进程静态优先级的工具。通过设置进程的 nice 值,可以影响其调度优先级。nice 值 的取值范围是 -20 到 19,其中:

  • -20 表示最高优先级,进程会尽可能占用 CPU 资源。
  • 19 表示最低优先级,进程会尽可能等待其他高优先级的进程执行。

通过调整 nice 值,用户可以影响某个进程的优先级,或者给一些进程设置较低优先级,以便为系统中的其他进程腾出更多的资源。

例如:

# 启动一个nice值为10的进程(较低优先级)
nice -n 10 my_program

3. 实时调度与优先级

Linux 支持实时进程调度,实时进程拥有最高的调度优先级。实时调度的优先级范围是 1-99,其中 1 是最高优先级,99 是最低的实时优先级。实时进程不受 nice 值的影响,实时任务会被调度器优先执行。

Linux 提供了两种主要的实时调度策略:

  • SCHED_FIFO:先进先出调度策略,进程按顺序执行,当前进程执行完毕后才会切换到下一个进程。实时进程不会被打断,直到它执行完毕。
  • SCHED_RR:轮转调度策略,实时进程按照优先级和时间片轮流执行。每个实时进程执行一段时间后会被中断,调度器会选择下一个同等优先级的进程执行。

例如,使用 chrt 命令可以设置一个进程为实时进程:

# 将进程的调度策略设置为 SCHED_FIFO,优先级为50
chrt -f 50 my_program

4. 调度器与优先级

Linux 中的调度器是基于 CFS(完全公平调度器)实时调度策略(SCHED_FIFO 和 SCHED_RR)来处理进程调度的:

  • CFS(完全公平调度器):对于普通进程,CFS 使用“虚拟运行时间”(vruntime)来决定进程的调度顺序。CFS 会尽可能保证每个进程获得公平的 CPU 时间,优先调度那些等待时间较长的进程。
  • 实时调度策略:对于实时任务,Linux 使用 FIFO 或轮转策略来进行调度,实时任务优先级高,不会被其他普通进程打断。

5. 优先级与 CPU 时间分配

进程的优先级直接影响其在 CPU 时间分配中的权重:

  • 高优先级进程:更频繁地获得 CPU 资源,优先执行。
  • 低优先级进程:只有当系统空闲或高优先级进程没有任务时才会被执行。

6. 优先级和负载均衡

在多核系统中,Linux 还会考虑负载均衡的问题。操作系统会在多个 CPU 核心之间动态地分配任务,以避免某些核心过载,而其他核心空闲。

优先级范围描述
0——99实时进程
100——139非实时进程


内核的优先级表示

内核表示优先级的所有信息:include/linux/sched/prio.h, 其中定义了一些表示优先级的宏和函数.

优先级数值通过宏来定义, 如下所示,其中MAX_NICE和MIN_NICE定义了nice的最大最小值

而MAX_RT_PRIO指定了实时进程的最大优先级, 而MAX_PRIO则是普通进程的最大优先级数值


#define MAX_NICE	19
#define MIN_NICE	-20
#define NICE_WIDTH	(MAX_NICE - MIN_NICE + 1)

/*
 * Priority of a process goes from 0..MAX_PRIO-1, valid RT
 * priority is 0..MAX_RT_PRIO-1, and SCHED_NORMAL/SCHED_BATCH
 * tasks are in the range MAX_RT_PRIO..MAX_PRIO-1. Priority
 * values are inverted: lower p->prio value means higher priority.
 */

#define MAX_RT_PRIO		100

#define MAX_PRIO		(MAX_RT_PRIO + NICE_WIDTH)
#define DEFAULT_PRIO		(MAX_RT_PRIO + NICE_WIDTH / 2)
描述
MIN_NICE-20对应于优先级100, 可以使用NICE_TO_PRIO和PRIO_TO_NICE转换
MAX_NICE19对应于优先级139, 可以使用NICE_TO_PRIO和PRIO_TO_NICE转换
NICE_WIDTH40nice值得范围宽度, 即[-20, 19]共40个数字的宽度
MAX_RT_PRIO, MAX_USER_RT_PRIO100实时进程的最大优先级
MAX_PRIO140普通进程的最大优先级
DEFAULT_PRIO120进程的默认优先级, 对应于nice=0
MAX_DL_PRIO0使用EDF最早截止时间优先调度算法的实时进程最大的优先级

而内核提供了一组宏将优先级在各种不同的表示形之间转移。

/*
 * Convert user-nice values [ -20 ... 0 ... 19 ]
 * to static priority [ MAX_RT_PRIO..MAX_PRIO-1 ],
 * and back.
 */
#define NICE_TO_PRIO(nice)	((nice) + DEFAULT_PRIO)
#define PRIO_TO_NICE(prio)	((prio) - DEFAULT_PRIO)

/*
 * Convert nice value [19,-20] to rlimit style value [1,40].
 */
static inline long nice_to_rlimit(long nice)
{
	return (MAX_NICE - nice + 1);
}

/*
 * Convert rlimit style value [1,40] to nice value [-20, 19].
 */
static inline long rlimit_to_nice(long prio)
{
	return (MAX_NICE - prio + 1);
}

DEF最早截至时间优先实时调度算法的优先级描述

include/linux/sched/deadline.h

static inline int dl_prio(int prio)
{
    if (unlikely(prio < MAX_DL_PRIO))
            return 1;
    return 0;
}

static inline int dl_task(struct task_struct *p)
{
    return dl_prio(p->prio);
}

static inline bool dl_time_before(u64 a, u64 b)
{
    return (s64)(a - b) < 0;
}

1.2、 进程的优先级表示


struct task_struct
{
    /* 进程优先级
     * prio: 动态优先级,范围为100~139,与静态优先级和补偿(bonus)有关
     * static_prio: 静态优先级,static_prio = 100 + nice + 20 (nice值为-20~19,所以static_prio值为100~139)
     * normal_prio: 没有受优先级继承影响的常规优先级,具体见normal_prio函数,跟属于什么类型的进程有关
     */
    int prio, static_prio, normal_prio;
    /* 实时进程优先级 */
    unsigned int rt_priority;
}

动态优先级 静态优先级 实时优先级

其中task_struct采用了三个成员表示进程的优先级:prio和normal_prio表示动态优先级, static_prio表示进程的静态优先级.

为什么表示动态优先级需要两个值prio和normal_prio?

调度器会考虑的优先级则保存在prio. 由于在某些情况下内核需要暂时提高进程的优先级, 因此需要用prio表示. 由于这些改变不是持久的, 因此静态优先级static_prio和普通优先级normal_prio不受影响。

此外还用了一个字段rt_priority保存了实时进程的优先级

字段描述
static_prio用于保存静态优先级, 是进程启动时分配的优先级, ,可以通过nice和sched_setscheduler系统调用来进行修改, 否则在进程运行期间会一直保持恒定
rt_priority用于保存实时优先级
normal_prio表示基于进程的静态优先级static_prio和调度策略计算出的优先级. 因此即使普通进程和实时进程具有相同的静态优先级, 其普通优先级也是不同的, 进程分叉(fork)时, 子进程会继承父进程的普通优先级
prio保存进程的动态优先级

实时进程的优先级用实时优先级rt_priority来表示。

二、 进程优先级的计算


前面说了task_struct中的几个优先级的字段:

静态优先级实时优先级普通优先级动态优先级
static_priort_prioritynormal_prioprio

但是这些优先级是如何关联的呢, 动态优先级prio又是如何计算的呢?

2.1、 normal_prio函数设置普通优先级normal_prio



静态优先级static_prio(普通进程)和实时优先级rt_priority(实时进程)是计算的起点。

因此他们也是进程创建的时候设定好的, 我们通过nice修改的就是普通进程的静态优先级static_prio

首先通过静态优先级static_prio计算出普通优先级normal_prio, 该工作可以由nromal_prio来完成kernel/sched/core.c

/*
 * Calculate the expected normal priority: i.e. priority
 * without taking RT-inheritance into account. Might be
 * boosted by interactivity modifiers. Changes upon fork,
 * setprio syscalls, and whenever the interactivity
 * estimator recalculates.
 */
static inline int normal_prio(struct task_struct *p)
{
	return __normal_prio(p->policy, p->rt_priority, PRIO_TO_NICE(p->static_prio));
}

static inline int __normal_prio(int policy, int rt_prio, int nice)
{
    int prio;

    if (dl_policy(policy))              /*  EDF调度的实时进程  */
            prio = MAX_DL_PRIO-1;
    else if (rt_policy(policy))       /*  普通实时进程的优先级  */
            prio = MAX_RT_PRIO-1 - rt_priority;
    else                                              /*  普通进程的优先级  */
            prio = NICE_TO_PRIO(nice);
    return prio;
}
进程类型调度器普通优先级normal_prio
EDF实时进程EDFMAX_DL_PRIO-1 = -1
普通实时进程RT

prio = MAX_RT_PRIO - 1 - rt_prio;

普通进程CFS

prio = NICE_TO_PRIO(nice);

普通优先级normal_prio需要根据普通进程和实时进程进行不同的计算, 其中__normal_prio适用于普通进程, 直接将普通优先级normal_prio设置为静态优先级static_prio. 而实时进程的普通优先级计算依据其实时优先级rt_priority.

1、 辅助函数task_has_dl_policy和task_has_rt_policy


kernel/sched/sched.h

其本质其实就是传入task->policy调度策略字段看其值等于SCHED_NORMAL, SCHED_BATCH, SCHED_IDLE, SCHED_FIFO, SCHED_RR, SCHED_DEADLINE中的哪个, 从而确定其所属的调度类, 进一步就确定了其进程类型

static inline int idle_policy(int policy)
{
    return policy == SCHED_IDLE;
}
static inline int fair_policy(int policy)
{
    return policy == SCHED_NORMAL || policy == SCHED_BATCH;
}

static inline int rt_policy(int policy)
{
    return policy == SCHED_FIFO || policy == SCHED_RR;
}

static inline int dl_policy(int policy)
{
        return policy == SCHED_DEADLINE;
}
static inline bool valid_policy(int policy)
{
        return idle_policy(policy) || fair_policy(policy) ||
                rt_policy(policy) || dl_policy(policy);
}

static inline int task_has_rt_policy(struct task_struct *p)
{
        return rt_policy(p->policy);
}

static inline int task_has_dl_policy(struct task_struct *p)
{
        return dl_policy(p->policy);
}

2、 关于rt_priority数值越大, 实时进程优先级越高的问题


对于一个实时进程,他有两个参数来表明优先级——prio 和 rt_priority,

prio才是调度所用的最终优先级数值,这个值越小,优先级越高;

而rt_priority 被称作实时进程优先级,他要经过转化——prio=MAX_RT_PRIO - 1- rt_priority;

MAX_RT_PRIO = 100, ;这样意味着rt_priority值越大,优先级越高;

而内核提供的修改优先级的函数,是修改rt_priority的值,所以越大,优先级越高。

所以用户在使用实时进程或线程,在修改优先级时,就会有“优先级值越大,优先级越高的说法”,也是对的。

3、 为什么需要__normal_prio函数


为什么增加了一个__normal_prio函数做了这么简单的工作, 这个其实是有历史原因的: 在早期的O(1)O(1)调度器中, 普通优先级的计算涉及相当多技巧性地工作, 必须检测交互式进程并提高其优先级, 而必须”惩罚”非交互进程, 以便是得系统获得更好的交互体验. 这需要很多启发式的计算, 他们可能完成的很好, 也可能不工作.

2.2、 effective_prio函数设置动态优先级prio



可以通过函数effective_prio用静态优先级static_prio计算动态优先级prio, 即·

p->prio = effective_prio(p);

kernel/sched/core.c

/*
 * Calculate the current priority, i.e. the priority
 * taken into account by the scheduler. This value might
 * be boosted by RT tasks, or might be boosted by
 * interactivity modifiers. Will be RT if the task got
 * RT-boosted. If not then it returns p->normal_prio.
 */
static int effective_prio(struct task_struct *p)
{
    p->normal_prio = normal_prio(p);
    /*
     * If we are RT tasks or we were boosted to RT priority,
     * keep the priority unchanged. Otherwise, update priority
     * to the normal priority:
     */
    if (!rt_prio(p->prio))
            return p->normal_prio;
    return p->prio;
}

首先effective_prio设置了普通优先级, 显然我们用effective_prio同时设置了两个优先级(普通优先级normal_prio和动态优先级prio)

因此计算动态优先级的流程如下:

  • 设置进程的普通优先级(实时进程99-rt_priority, 普通进程为static_priority)

  • 计算进程的动态优先级(实时进程则维持动态优先级的prio不变, 普通进程的动态优先级即为其普通优先级)

不同类型进程的计算结果:

进程类型实时优先级rt_priority静态优先级static_prio普通优先级normal_prio动态优先级prio
EDF调度的实时进程rt_priority不使用MAX_DL_PRIO-1维持原prio不变
RT算法调度的实时进程rt_priority不使用MAX_RT_PRIO-1-rt_priority维持原prio不变
普通进程不使用static_priostatic_priostatic_prio
优先级提高的普通进程不使用static_prio(改变)static_prio维持原prio不变

1、 为什么effective_prio使用优先级数值检测实时进程


t_prio会检测普通优先级是否在实时范围内, 即是否小于MAX_RT_PRIOinclude/linux/sched/rt.h

static inline int rt_prio(int prio)
{
    if (unlikely(prio < MAX_RT_PRIO))
        return 1;
    return 0;
}

而前面我们在normal_prio的时候, 则通过task_has_rt_policy来判断其policy属性来确定

policy == SCHED_FIFO || policy == SCHED_RR;

那么为什么effective_prio重检测实时进程是rt_prio基于优先级数值, 而非task_has_rt_policy或者rt_policy?

对于临时提高至实时优先级的非实时进程来说, 这个是必要的, 这种情况可能发生在是哦那个实时互斥量(RT-Mutex)时.

2.3、 设置prio的时机


  • 在新进程用wake_up_new_task唤醒时, 或者使用nice系统调用改变其静态优先级时, 则会通过effective_prio的方法设置p->prio

wake_up_new_task(), 计算此进程的优先级和其他调度参数,将新的进程加入到进程调度队列并设此进程为可被调度的,以后这个进程可以被进程调度模块调度执行。

  • 进程创建时copy_process通过调用sched_fork来初始化和设置调度器的过程中会设置子进程的优先级

2.4、 nice系统调用的实现


nice系统调用是的内核实现是sys_nice, kernel/sched/core.c

/*
 * sys_nice - change the priority of the current process.
 * @increment: priority increment
 *
 * sys_setpriority is a more generic, but much slower function that
 * does similar things.
 */
SYSCALL_DEFINE1(nice, int, increment)
{
	long nice, retval;

	/*
	 * Setpriority might change our priority at the same moment.
	 * We don't have to worry. Conceptually one call occurs first
	 * and we have a single winner.
	 */
	increment = clamp(increment, -NICE_WIDTH, NICE_WIDTH);
	nice = task_nice(current) + increment;

	nice = clamp_val(nice, MIN_NICE, MAX_NICE);
	if (increment < 0 && !can_nice(current, nice))
		return -EPERM;

	retval = security_task_setnice(current, nice);
	if (retval)
		return retval;

	set_user_nice(current, nice);
	return 0;
}

 

2.5、 fork时优先级的继承


在进程分叉处子进程时, 子进程的静态优先级继承自父进程. 子进程的动态优先级p->prio则被设置为父进程的普通优先级, 这确保了实时互斥量引起的优先级提高不会传递到子进程.

可以参照sched_fork函数, 在进程复制的过程中copy_process通过调用sched_fork来设置子进程优先级。kernel/sched/core.c

/*
 * fork()/clone()-time setup:
 */
int sched_fork(unsigned long clone_flags, struct task_struct *p)
{
    /*  ......  */
    /*
     * Make sure we do not leak PI boosting priority to the child.
     * 子进程的动态优先级被设置为父进程普通优先级 
     */
    p->prio = current->normal_prio;

    /*
     * Revert to default priority/policy on fork if requested.
     * sched_reset_on_fork标识用于判断是否恢复默认的优先级或调度策略

     */
    if (unlikely(p->sched_reset_on_fork))  /*  如果要恢复默认的调度策略, 即SCHED_NORMAL  */
    {
        /*   首先是设置静态优先级static_prio
         *   由于要恢复默认的调度策略
         *   对于父进程是实时进程的情况, 静态优先级就设置为DEFAULT_PRIO
         *
         *   对于父进程是非实时进程的情况, 要保证子进程优先级不小于DEFAULT_PRIO
         *   父进程nice < 0即static_prio < 的重新设置为DEFAULT_PRIO的重新设置为DEFAULT_PRIO
         *   父进程nice > 0的时候, 则什么也没做
         *   */
        if (task_has_dl_policy(p) || task_has_rt_policy(p))
        {
            p->policy = SCHED_NORMAL;           /*  普通进程调度策略  */
            p->static_prio = NICE_TO_PRIO(0);   /*  静态优先级为nice = 0 即DEFAULT_PRIO*/
            p->rt_priority = 0;                             /*  实时优先级为0  */
        }
        else if (PRIO_TO_NICE(p->static_prio) < 0)  /*  */
            p->static_prio = NICE_TO_PRIO(0);   /*  */

        /*  接着就通过__normal_prio设置其普通优先级和动态优先级
          *  这里做了一个优化, 因为用sched_reset_on_fork标识设置恢复默认调度策略后
          *  创建的子进程是是SCHED_NORMAL的非实时进程
          *  因此就不需要绕一大圈用effective_prio设置normal_prio和prio了 
          *  直接用__normal_prio设置就可  */
        p->prio = p->normal_prio = __normal_prio(p); /*  设置*/

        /*  设置负荷权重  */
        set_load_weight(p);

        /*
         * We don't need the reset flag anymore after the fork. It has
         * fulfilled its duty:
         */
        p->sched_reset_on_fork = 0;
    }
    /*  ......  */
}
<think>我们正在讨论Linux内核代码的兼容性问题。用户希望确认一段代码是否适用于Linux内核版本6.1.20。由于用户没有提供具体的代码,我将给出一般性的指导方法。 根据引用[1],我们知道在树莓派3B+上编译Linux内核6.1版本(具体是6.1.79)的过程,这涉及到配置内核编译。而引用[2]提到了Linux 6.1.79中关于内存描述符MMU notifier的机制。虽然用户的内核版本是6.1.20,但我们可以假设6.1.y版本的内核在主要机制上相似(同一个大版本,小版本之间可能会有补丁,但通常不会有大改动)。不过,为了准确判断,我们需要考虑以下几点: 1. 内核版本差异:6.1.206.1.79都是6.1大版本下的不同小版本,它们之间的差异通常只是修复bug安全补丁,不会引入大的API变更。但是,也有可能存在某个小版本中修复某个问题而修改了函数签名或数据结构。 2. 代码内容:要确认的代码可能涉及哪些内核子系统?例如,如果涉及内存管理(如MMU notifier),那么需要查看6.1.20版本中相关函数的实现数据结构是否代码中使用的匹配。 3. 如何检查:最直接的方法是查看目标内核版本(6.1.20)的源代码,并代码进行比较。如果没有6.1.20的源代码,可以查看内核的ChangeLog,或者使用在线内核代码库(如https://elixir.bootlin.com)来查看特定版本的内核代码。 4. 编译测试:如果条件允许,可以将代码放入6.1.20内核中编译,看是否有编译错误或警告。 由于用户没有提供具体代码,我无法直接判断,但可以给出一般步骤: 步骤1:确定代码中使用的关键函数、数据结构、宏等。 步骤2:在Linux 6.1.20内核源代码中查找这些函数结构,确认它们是否存在,以及签名是否一致。 步骤3:如果代码中使用了特定版本引入的特性,需要确认该特性在6.1.20中是否已经存在(可以通过查看该特性的提交记录,判断它被合并到内核的主线版本,以及是否被backport到6.1.20)。 例如,引用[2]中提到的MMU notifier机制,在6.1.20中应该已经存在,因为它是内存管理的基础机制。但具体到某个回调函数的参数或行为,可能有细微变化。 因此,在没有具体代码的情况下,我建议用户: 1. 提供需要确认的代码片段。 2. 或者,自行检查代码中使用的内核API在6.1.20中的存在性一致性。 另外,注意6.1.20是一个较早的6.1版本,而6.1.79较新,所以代码在6.1.20上可能存在一些在6.1.79中已经修复的问题。如果代码是从更新的内核版本(比如6.1.79)中提取的,那么它可能在6.1.20上需要调整。 如果用户有具体的函数或结构体名称,我们可以尝试通过在线内核代码浏览器查看6.1.20版本中是否存在。 例如,我们可以查看6.1.20版本中`mmu_notifier`相关的头文件:`include/linux/mmu_notifier.h`,以及实现文件`mm/mmu_notifier.c`。 总结:由于没有具体代码,无法给出确切答案,但提供了一般性方法。建议用户提供具体代码以便进一步分析。 生成相关问题: 1. 如何获取Linux内核6.1.20的源代码? 2. 如何检查一个内核函数在不同版本间的差异? 3.Linux内核开发中,如何确保代码向后兼容? 4. 如果遇到内核API变化,如何修改代码以适应旧版本内核?</think>### 确认代码兼容性的方法 要确认代码是否兼容 Linux 内核版本 6.1.20,需执行以下系统化步骤: #### 1. **检查内核 API 变更** - **关键点**:6.1.y 是长期支持(LTS)版本,但小版本更新(如 6.1.206.1.79)可能涉及 API 调整。 - **操作建议**: - 对比目标代码 [Linux 6.1.20 官方源码](https://elixir.bootlin.com/linux/v6.1.20/source),重点验证函数签名、结构体成员、宏定义是否一致。 - 示例:若代码涉及内存管理(如 `struct mm_struct`),需确认 `mmu_notifier` 回调机制是否 6.1.20 实现匹配[^2]。 #### 2. **编译验证** - **步骤**: 1.6.1.20 内核环境中编译代码: ```bash # 配置环境(参考引用[1]方法) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make ``` 2. 检查编译错误: - 若出现 `implicit declaration of function` 或 `unknown type name` 错误,表明 API 已变更。 - 若涉及硬件驱动,需额外验证设备树(DTS)兼容性。 #### 3. **运行时行为测试** - **关键场景**: - 内存管理操作(如页表更新)需验证 `mmu_notifier` 事件触发逻辑是否正常[^2]。 - 并发场景(如多进程共享内存)需测试锁机制是否引发死锁。 #### 4. **补丁差异分析** - **方法**: - 使用 `git diff v6.1.20..v6.1.79 -- <代码相关路径>` 检查目标文件在 6.1.20 最新 6.1.y 的差异。 - 若差异涉及关键函数(如 `sched_setaffinity()` 或内存回收逻辑),需针对性适配。 ### 兼容性结论 - **大概率兼容**:若代码仅使用稳定核心子系统(如进程调度、基础内存分配)且无编译报错。 - **需适配**:若涉及以下模块: - 硬件抽象层(如 ARM 架构特定函数) - 内存事件通知(`mmu_notifier` 注册流程)[^2] - NUMA 平衡机制(`numa_balance` 策略变更)[^2] > **建议**:提供具体代码片段或模块名称,可进一步精准分析。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值