【操作系统总结】进程和线程

1. 操作系统的特性:

  • 并发:同一段时间内多个程序执行;使得OS能有效地提高系统中的资源利用率,增加系统的吞吐量。
  • 共享:系统中的资源可以被内存中多个并发执行的进线程共同使用;
  • 虚拟:通过时分复用以及空分复用(如虚拟内存),把一个物理实体虚拟为多个逻辑上的对应物理的功能;时分复用,空分复用。
  • 异步:进程是以人们不可预知的速度向前推进的。(先开始的进程不一定先结束)

2. 操作系统的主要功能

  • 进程管理:进程控制,进程同步,进程通信和进程调度
  • 内存管理:内存分配,内存保护,地址映射,内存扩充
  • 设备管理:管理所有外围设备,包括完成用户IO请求,为用户进程分配IO设备,提高IO设备利用率,提高IO速度,方便IO使用
  • 文件管理:管理用户文件和系统文件,方便使用的同时保证安全性。包括磁盘存储空间管理,目录管理,文件读写管理以及文件共享及保护
  • 提供用户接口:程序接口(如API)和用户接口(如GUI)

3. 进程和线程

(1)进程和线程的定义以及它们的区别:

  • 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位;
  • 线程是进程的一个实体,是CPU调度和分配的基本单位,它是比进程更小的能独立运行的基本单位;
  • 一个进程可以有多个线程,多个线程可以并发运行;
  • 线程基本不拥有系统资源,只拥有一些在运行中必不可少的资源,比如程序计数器、寄存器和栈;线程可以与其他线程共享进程所拥有的全部资源;
  • 线程可以创建和撤销另一个线程。
  • 真正的并行执行多任务只能在多核CPU上实现,对于操作系统来说,一个任务就是一个进程,打开一个Word就启动了一个Word进程,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程。
  • 一个进程至少有一个线程。
  • 多任务的实现有3种方式:多进程模式;多线程模式;多进程+多线程模式。
  • 如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

(2)协程与多线程相比的优点:

  • 协程具有极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
  • 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
  • 因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

(3)抢占式调度与协同式调度

  • 抢占式调度:指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。
  • 协同式调度:指某一线程执行完后主动通知系统切换到另一线程上执行,这种模式就像接力赛一样,一个人跑完自己的路程就把接力棒交接给下一个人,下个人继续往下跑。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但它有一个致命弱点:如果一个线程编写有问题,运行到一半就一直堵塞,那么可能导致整个系统崩溃。

JVM规范中规定每个线程都有优先级,且优先级越高越优先执行,但优先级高并不代表能独自占用执行时间片,可能是优先级高得到越多的执行时间片,反之,优先级低的分到的执行时间少但不会分配不到执行时间。java使用的线程调度式抢占式调度Java中线程会按优先级分配CPU时间片运行。

(4)用户态和内核态的区别:操作系统需要两种CPU状态:内核态:运行操作系统程序;用户态:运行用户程序。

比如:用户运行一个程序,该程序创建的进程开始时运行自己的代码,处于用户态。如果要执行文件操作、网络数据发送等操作必须通过write、send等系统调用,这些系统调用会调用内核的代码。内核态的进程执行完后又会回到用户态。这样,用户态的程序就不能随意操作内核地址空间,具有一定的安全保护作用。这说的保护模式是指通过内存页表操作等机制,保证进程间的地址空间不会互相冲突,一个进程的操作不会修改另一个进程地址空间中的数据。

(5)用户态切换到内核态的三种方式:系统调用;异常;外围设备中断。

  • 系统调用:用户态进程主动要求切换到内核态的一种方式。用户态进程通过系统调用,申请使用操作系统提供的服务程序,以完成工作。系统调用的机制的核心是使用了操作系统为用户特别开放的一个中断来实现。
  • 异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,会触发由当前运行进程切换到处理此异常的内核相关程序中。因此也就转到了内核态,比如缺页异常。
  • 外围设备中断:外围设备完成用户请求的操作后,会向CPU发送相应的中断信号。此时,CPU会暂停执行下一条即将要执行的指令,转而执行与中断信号对应的处理程序。如果先前执行的指令是用户态的程序,那么转换的过程自然就发生了由用户态到内核态的转换。

其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。从触发方式上看,切换方式都不一样,但从最终实际完成由用户态到内核态的切换操作来看,都相当于执行了一个中断响应的过程。系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本一致。

(6)程序和进程的区别

  • 程序是永存的;进程是暂时的,是程序在数据集上的一次执行,有创建有撤销,存在是暂时的;
  • 程序是静态的观念,进程是动态的观念;
  • 进程具有并发性,而程序没有;
  • 进程是竞争计算机资源的基本单位,程序不是。
  • 进程和程序不是一一对应的: 一个程序可对应多个进程即多个进程可执行同一程序; 一个进程可以执行一个或几个程序

(7)多线程共享什么数据

  • 进程代码段
  • 进程的公有数据
  • 进程打开的文件描述符
  • 信号的处理器
  • 进程的当前目录
  • 进程用户ID(PID)与进程组ID(PGID):进程组是一个或多个进程的集合;他们与同一作业相关联;每一个进程组都有唯一的进程组ID(PGID)。

(8)多线程独享的数据

  • 栈是独享的
  • 寄存器  这个可能会误解,因为电脑的寄存器是物理的,每个线程去取值难道不一样吗?其实线程里存放的是副本,包括程序计数器PC

(9)进程的状态

  • 就绪状态:进程已经获得了除处理机以外的所需资源,正在等待分配处理机资源
  • 运行状态:占用处理机资源运行,处于此状态的进程数小于CPU数
  • 阻塞状态:进程等待某种条件,在条件满足之前无法执行

(10)线程的状态

(11)多线程锁实现多线程同步

  • 互斥锁:保护临界区,确保同一时间,只有一个线程访问数据。如果互斥量已经上锁,调用线程会阻塞,直到互斥量被解锁
  • 自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting
  • 信号量:一个计数器,用来控制多个进程对共享资源的访问。互斥锁为信号量的一个特殊情况。信号量实际上就是一个值,这个值被用来解决临界区问题以及实现进程在多处理器环境下的进程同步。其中,两个最重要的信号量为二进制信号量和计数信号量,计数信号量可以表示为非负的整数而二进制信号量只存在0和1两个值。https://blog.csdn.net/qq_19782019/article/details/79746627
  • 读写锁:特殊的自旋锁,处于读锁时,允许其他线程和本线程的读锁,但不允许写锁。处于写锁时,任何锁操作都会睡眠等待。
  • 递归锁:特殊的互斥锁。同样地,只能由一个线程访问该对象,但允许同一个线程在未释放其拥有的锁时,反复对锁进行加锁操作。

(12)什么是死锁:http://c.biancheng.net/view/1236.html
在两个或者多个并发进程(线程)中,如果每个进程持有某种资源,又等待其他进程释放它目前持有的资源,在未改变这种状态之前,都不能向前推进。这一情况被称作死锁。

(13)死锁产生的四个条件:所有四个条件必须同时成立才会出现死锁。循环等待条件意味着请求与保持条件,这样四个条件并不完全独立。

  • 互斥条件:一个资源一次只能被一个进程使用
  • 请求与保持条件:一个进程因请求资源而阻塞时,对其自身拥有的资源保持不放。
  • 不可剥夺条件:进程获得的资源,在未完全使用完之前,不能强行剥夺,只能被进程在完成任务后自愿释放。
  • 循环等待条件:若干进程之间形成一种头尾相接的环形等待资源关系,有一组等待进程 {P0,P1,…,Pn},P0 等待的资源为 P1 占有,P1 等待的资源为 P2 占有,……,Pn-1 等待的资源为 Pn 占有,Pn 等待的资源为 P0 占有。

(14) 死锁资源分配图

如果分配图有环,那么可能存在死锁。如果每个资源类型刚好有一个实例,那么有环就意味着已经出现死锁。在这种情况下,图中的环就是死锁存在的充分且必要条件。如果每个资源类型有多个实例,那么有环并不意味着已经出现了死锁。

(14)解决死锁的基本方法:

  • 预防死锁:确保死锁发生的四个必要条件中,至少有一个不成立;通过限制如何申请资源的方法来预防死锁。

  • 避免死锁:动态检测资源分配状态,确保循环等待条件不成立,使系统处于安全状态;通过限制如何申请资源的方法来预防死锁。(银行家算法)

  • 解决死锁:包括进程终止和资源抢占;

    • 选择一个牺牲品:进程终止,释放这个牺牲品所占用的资源
    • 回滚:回滚造成死锁的某个语句,以便释放冲突的锁,从而解决死锁问题
    • 饥饿(使回滚得越多的,越不可能继续作为牺牲品)

(15)进程间通信方式

  • 管道
  • 信号(也叫事件)是一种比较复杂的通信方式,用于通知接受进程某个事件已经发生。
  • 消息队列消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息;
  • 共享内存:这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等;
  • 信号量:主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段;
  • 套接字:这是一种更为一般法人进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。

(16)进程调度策略   https://www.jianshu.com/p/6754a5e72067

  • 先来先服务:非抢占式的FCFS算法既可用于作业调度,也可用于进程调度。每次调度是从就绪队列中选择一个最先进入该队列的进程,为之分配CPU,使之投入运行。该进程一直运行到完成或发生某事件而阻塞后才放弃CPU。
    • 优点:易于理解且实现简单,只需要一个队列(FIFO),且相当公平,
    • 缺点:FCFS 策略的缺点是,平均等待时间往往很长。比较有利于长进程,而不利于短进程,有利于CPU 繁忙的进程,而不利于I/O 繁忙的进程
  • 最短作业优先:非抢占式,对预计执行时间短的进程优先分派CPU。通常后来的短进程不抢先正在执行的进程。
    • 优点:相比FCFS 算法,该算法可改善平均周转时间和平均带权周转时间,缩短进程的等待时间,提高系统的吞吐量。
    • 缺点:对长进程非常不利,可能长时间得不到执行,且未能依据进程的紧迫程度来划分执行的优先级,以及难以准确估计进程的执行时间,从而影响调度性能。

  • 优先级:按照优先级来执行就绪进程队列中的调度。低优先级进程的无穷等待问题的解决方案之一是老化。老化逐渐增加在系统中等待很长时间的进程的优先级。
    • 高响应比优先:是对FCFS方式和SJF方式的一种综合平衡。FCFS方式只考虑每个作业的等待时间而未考虑执行时间的长短,而SJF方式只考虑执行时间而未考虑等待时间的长短。HRRN调度策略同时考虑每个作业的等待时间长短估计需要的执行时间长短,从中选出响应比最高的作业投入执行。这样,即使是长作业,随着它等待时间的增加,W / T也就随着增加,也就有机会获得调度执行。这种算法是介于FCFS和SJF之间的一种折中算法。每当要进行作业调度时,系统计算每个作业的响应比,选择其中R最大者投入执行。非抢占式
      • R =(等待时间+执行时间)/执行时间= 1+W/T
      • 优点:由于长作业也有机会投入运行,在同一时间内处理的作业数显然要少于SJF法,从而采用HRRN方式时其吞吐量将小于采用SJF 法时的吞吐量。
      • 缺点:由于每次调度前要计算响应比,系统开销也要相应增加。
  • 时间片轮转:该算法采用抢占策略。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。算法原理:让就绪进程以FCFS 的方式按时间片轮流使用CPU 的调度方式,即将系统中所有的就绪进程按照FCFS 原则,排成一个队列,每次调度时将CPU 分派给队首进程,让其执行一个时间片。在一个时间片结束时,发生时钟中断,调度程序据此暂停当前进程的执行,将其送到就绪队列的末尾,并通过上下文切换执行当前的队首进程,进程可以未使用完一个时间片,就出让CPU(如阻塞)。
    • 优点:时间片轮转调度算法的特点是简单易行、平均响应时间短。
    • 缺点:不利于处理紧急作业。在时间片轮转算法中,时间片的大小对系统性能的影响很大,因此时间片的大小应选择恰当怎样确定时间片的大小。
  • 多级反馈:多级反馈队列调度算法是一种CPU处理机调度算法。进程在进入待调度的队列等待时,首先进入优先级最高的Q1等待。首先调度优先级高的队列中的进程。若高优先级中队列中已没有调度的进程,则调度次优先级队列中的进程。例如:Q1,Q2,Q3三个队列,只有在Q1中没有进程等待时才去调度Q2,同理,只有Q1,Q2都为空时才会去调度Q3。如果第一队列队首进程在规定的时间片内未执行完,则直接调送至第二队尾,依次向后放一个队列的队尾。因此一个长作业进程会分配到n个队列的时间片执行。
    • 按照队列先后依次执行,如果新进的待处理进程优先级较高,则新进程将抢占正在运行的进程,被抢占的进程放置正在运行的队尾。

(17)线程同步有哪几种机制:原子操作、信号量机制、自旋锁管程、会合、分布式系统

  • 信号量机制:一个信号量只能置一次初值,以后只能对之进行p操作或v操作。由此也可以看到,信号量机制必须有公共内存不能用于分布式操作系统,这是它最大的弱点。
    • 信号量机制可以用来实现同步和互斥:对信号量使用P和V操作,p操作(wait):申请一个单位资源,在进程进入临界区前执行;v操作(signal):释放一个单位资源,在进程退出临界区之后执行。P将信号量值减一,V将信号量值加一。
      • 当信号量值S大于0时,表示当前可用资源的数量,当S小于0时,表示当前正在等待该资源的进程数。
      • 使用PV操作实现进程互斥时,要注意:PV操作必须成对出现。先做P操作,进临界区,后做V操作,出临界区。PV操作应分别紧靠临界区的头尾部,临界区的代码应该尽可能短,不能有死循环。
      • 使用PV操作实现进程同步时,要注意:先分析进程间的制约关系,确定信号量种类。在保证进程间有正确的同步关系情况下,哪个进程先执行,哪个进程后执行,彼此间通过什么资源(信号量)进行协调,从而明确要设置哪些信号量。信号量的初值与相应资源的数量有关,也与PV操作在程序代码中出现的位置有关。同一信号量的PV操作要成对出现,但它们分别在不同的进程代码中。
      • 三个同步问题:
      • (a)生产者-消费者(缓冲区问题):我们可把共享缓冲区中的n个缓冲块视为共享资源,生产者写入数据的缓冲块成为消费者可用资源,而消费者读出数据后的缓冲块成为生产者的可用资源。full+empty=N。
      • /*生产者进程结构:生产者消费empty,生产full*/
        do{  
             wait(empty) ; 
             wait(mutex) ;  //先查资源再互斥,否则可能会造成死锁
              
             add nextp to buffer  
              
             signal(mutex) ;  
             signal(full) ;  
        }while(1) ;
         
        /*消费者进程结构:消费者消费full,生产empty*/
        do{  
             wait(full) ;  
             wait(mutex) ;  
              
             remove an item from buffer to nextp  
              
             signal(mutex) ;  
             signal(empty) ;  
        }while(1) 
        (b)读写者问题:是指多个进程对一个共享资源进行读写操作的问题。假设“读者”进程可对共享资源进行读操作,“写者”进程可对共享资源进行写操作;任一时刻“写者”最多只允许一个,而“读者”则允许多个。即对共享资源的读写操作限制关系包括:“读—写,互斥、“写一写”互斥和“读—读”允许。
      • 互斥信号量:
        • wmutex: 是读写的互斥信号量
        • rmutex: 是读进程互斥访问Readcount的信号量
        • 整型变量Readcount: 表示正在读的进程数目;
      • 所以,仅当Readcount=0,即无Reader进程在读时,Reader才需要执行Wait(wmutex)操作。若Wait(wmutex)操作成功,Reader进程便可去读,相应地,做Readcount+1操作。同理,仅当Reader进程在执行了Readcount减1操作后其值为0时,才需执行signal(wmutex)操作,以便让Write进程写
  • Var  wmutex, rmutex :semaphore :=1, 1;
            Readcount :integer :=0;
    begin
        parbegin
            Reader : begin
                 repeat
                    wait(rmutex);
                    if Readcount=0 then wait(wmutex);
                    Readcount :=Readcount +1;
                    signal(rmutex);
                       …
                       读;
                       …
                    wait(rmutex);
                    Readcount :=Readcount -1;
                    if Readcount=0 then signal(wmutex);
                    signal(rmutex);
                until  false;
           end
         parend
    end 
    
    Writer : begin
         repeat
             wait(wmutex);
             写;
             signal(wmutex);
         until  false;
    end 
    

    (c)哲学家用餐问题:它是大量并发控制问题的一个例子。这个代表型的例子满足:在多个进程之间分配多个资源,而且不会出现死锁和饥饿。

  • 一种简单的解决方法是每只筷子都用一个信号量来表示。一个哲学家通过执行操作 wait() 试图获取相应的筷子,他会通过执行操作 signal() 以释放相应的筷子。虽然这一解决方案保证两个邻居不能同时进食,但是它可能导致死锁,因此还是应被拒绝的。假若所有 5 个哲学家同时饥饿并拿起左边的筷子。所有筷子的信号量现在均为 0。当每个哲学家试图拿右边的筷子时,他会被永远推迟。

  • semaphore chopstick[5];
    do {
        wait(chopstick[i]);
        wait(chopstick[(i+1) % 5]);
        /* eat for awhile */
        signal(chopstick[i]);
        signal(chopstick[(i+1) % 5]);
        /* think for awhile */
    } while (true);

    死锁问题有多种可能的补救措施:

  • 允许最多 4 个哲学家同时坐在桌子上。将room 作为信号量,只允许4 个哲学家同时进入餐厅就餐,这样就能保证至少有一个哲学家可以就餐,而申请进入餐厅的哲学家进入room 的等待队列,根据FIFO 的原则,总会进入到餐厅就餐,因此不会出现饿死和死锁的现象。 
  • 只有一个哲学家的两根筷子都可用时,他才能拿起它们。在一个原语中,将一段代码同时需要的多个临界资源,要么全部分配给它,要么一个都不分配,因此不会出现死锁的情形。当某些资源不够时阻塞调用进程;由于等待队列的存在,使得对资源的请求满足FIFO 的要求, 因此不会出现饥饿的情形。 
  • 使用非对称解决方案。即单号的哲学家先拿起左边的筷子,接着右边的筷子;而双号的哲学家先拿起右边的筷子,接着左边的筷子。
//最多允许四个人在桌上。
semaphore chopstick[5]={1,1,1,1,1};  
semaphore room=4;   
void philosopher(int i)   
{   
    while(true)   
    {   
        think();   
        wait(room); //请求进入房间进餐   
        wait(chopstick[i]); //请求左手边的筷子   
        wait(chopstick[(i+1)%5]); //请求右手边的筷子   
        eat();   
        signal(chopstick[(i+1)%5]); //释放右手边的筷子   
        signal(chopstick[i]); //释放左手边的筷子   
        signal(room); //退出房间释放信号量room   
    }   
}

//左右筷子都可用才能吃饭

semaphore chopstick[5]={1,1,1,1,1};   
void philosopher(int I)   
{   
    while(true)   
    {   
        think();   
        Swait(chopstick[(I+1)]%5,chopstick[I]);   
        eat();   
        Ssignal(chopstick[(I+1)]%5,chopstick[I]);   
    }   
}
  • 自旋锁:自旋锁是为了保护共享资源提出的一种锁机制。 调用者申请的资源如果被占用,即自旋锁被已经被别的执行单元保持,则调用者一直循环在那里看是否该自旋锁的保持着已经释放了锁,自旋锁是一种比较低级的保护数据结构和代码片段的原始方式,可能会引起以下两个问题; 
    (1)死锁 
    (2)过多地占用CPU资源 
    • 管程是为了解决信号量在临界区的PV操作上的配对的麻烦,把配对的PV操作集中在一起,生成的一种并发编程方法。其中使用了条件变量这种同步机制。
    • 管程与临界区不同的是,在管程中的线程可以临时放弃管程的互斥访问,让其他线程进入到管程中来。而临界区中的线程只能在线程退出临界区时,才可以放弃对临界区的访问。
  • 管程:信号量机制功能强大,但使用时对信号量的操作分散,而且难以控制,读写和维护都很困难。因此后来又提出了一种集中式同步进程——管程。其基本思想是将共享变量和对它们的操作集中在一个模块中,操作系统或并发程序就由这样的模块构成。这样模块之间联系清晰,便于维护和修改,易于保证正确性。
  • 会合:进程直接进行相互作用。一个进程可以有许多入口,一个入口对应一段程序,一个进程可调用另一个进程的入口。当一个进程调用另一个进程的入口,而且被调用的进程已准备好接受这个调用时,会合就发生了。当调用者发出调用请求时,被调用的进程未准备接受这个调用时,则调用者等待;反之,当被调用者准备接受调用,而当前尚无调用者时,则被调用者等待。即先到达会合处等待后到达者。当多个进程调用同一个进程的同一个入口时,被调用者按先来先服务(FCFS)的次序接受调用。入口处可以携带调用参数,还可以有返回参数,以实现信息的交换。被调用者可以选择会合的入口。
  • 分布式系统:由于在分布式操作系统中没有公共内存,因此参数全为值参,而且不可为指针。

(18)临界区:每个进程中,访问临界资源的那段程序块被称为临界区。每次只准许一个进程进入临界区,进入后不允许其他进程进入。若有若干进程要求进入空闲临界区,一次仅允许一个进程进入。若已有进程进入临界区,其他进程必须等待,进入临界区的进程必须在有限时间内退出,如果进程不能进入临界区,则必须让出CPU。

(19)中断与轮询

  • 中断指的是,在计算机执行期间,系统内发生任何非寻常或非预期的急需处理事件,使得CPU中断当前正在执行的程序,而转去执行相应的事件处理程序。处理完毕后,处理器又返回原来被中断的地方,继续执行或调度新的进程。
  • 轮询指的是,系统定时对各种设备轮流询问一遍是否有处理要求。有要求的,则加以处理。轮询占据了CPU相当一部分处理时间,是一种效率较低的方式。
  • 中断:容易遗漏一些问题,CPU 利用率高
  • 轮询:效率低,等待时间长,CPU利用率低
  •  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值