前言

我看的是这本,豆瓣9.4,很经典的教材书,在文章末尾我附上了网盘链接
这篇博客算是一个完整的读书笔记,我大概读了20天看完的,电子版做笔记很方便,推荐一波,很多课上没有讲到的知识在这本书里都详细阐释了

全书的思维导图

操作系统介绍

一个正在运行的程序会做:取址执行。从内存中获取指令,对其解码,执行。

操作系统主要利用一种通用的技术,我们称之为虚拟化(virtualization)。 也就是说,操作系统将物理(physical)资源(如处理器、内存或磁盘)转换为更通用、更强大且更易于使用的虚拟形式。

操作系统取得CPU,内存和磁盘等物理资源,并对它们进行虚拟化;处理与并发相关的麻烦事;持久化地存储文件,使文件长期安全。

虚拟化

4.抽象 进程

进程可以被简单地视为正在运行的程序。

进程的创建首先将代码和静态数据加载到内存中,创建和初始化栈,执行I/O设置相关的其他工作,操作系统就为程序的执行搭建好了舞台,最后需要启动程序。

一般来说进程可以在三个状态:运行,就绪,阻塞。

存储关于进程的信息的个体结构称为进程控制块(Process Control Block,PCB),这是谈论包含每个进程信息的 C 结构的一种方式。

5.插叙:进程API

进程描述符(process identifier,PID)

  1. fork() 系统调用fork()用于创建新进程
  2. wait() 调用wait()延迟自己的进程进行
  3. exec() 给定可执行程序的名称(如 wc)及 需要的参数(如 p3.c)后,exec()会从可执行程序中加载代码和静态数据,并用它覆写自己的代码段(以及静态数据),堆、栈及其他内存空间也会被重新初始化。这个命令并没有创建新进程,而是直接将当前的程序替换为不同的运行程序

6.机制:受限直接执行

关键问题:操作系统如何高效可控地虚拟化CPU

通过受限的直接执行可以让程序运行地尽可能快,直接在CPU上运行程序即可。当 OS 希望启动程序运行时,它会在进程列表中为其创建一个进程条目,为其分配一些内存,将程序代码(从磁盘)加载到内存中,找到入口点(main()函数或类似的),跳转到那里,并开始运行用户的代码。

为了安全高效地进行上述的步骤,我们需要解决

  1. 如果我们只运行一个程序,操作系统怎么能确保程序不做任何我们不希望它做的事,同时仍然高效地运行它?
  2. 当我们运行一个进程时,操作系统如何让它停下来并切换到另一个进程,从而实现虚拟化 CPU 所需的时分共享?

第一个问题:

我们引入用户模式和内核模式,可以实现权限的管理,为了让有些用户模式可以访问内核,引入trap指令,该指令同时跳入内核并将特权级别提升到内核模式。

为了在操作系统发出从陷阱返回指令时能够正确返回,必须确保存储足够的调用者寄存器。

内核在启动时会设置陷阱表,来实现一些前置的“规定”,例如在出错时调用什么代码?根据需要配置的是哪个硬件,哪个硬件可以访问哪个不能随意访问?

第二个问题:

在进程中切换:一个进程在CPU中运行时,操作系统就没有运行,那么操作系统如何重新获得CPU控制权来实现进程间切换?两种方式:协作方式,非协作方式。

  1. 协作方式:有些文件会进行yield系统调用
  2. 非协作方式:使用时钟中断来让操作系统运行中断处理程序,操作系统会重新获得CPU控制权。在启动过程中,系统必须先开启启动时钟。

为了保存切换时的前后两个程序的内容,操作系统会执行一些底层汇编代码,来保存通用寄存器、程序计数器,以及当前正在运行的进程的内核栈指针,然后恢复寄存器、程序计数器,并切换内核栈,供即将运行的进程使用。

在这个例子中,进程A正在运行,然后被中断时钟中断。硬件保存它的寄存器(在内核栈中),并进入内核(切换到内核模式)。在时钟中断处理程序中,操作系统决定从正在运行的进程 A 切换到进程 B。此时,它调用 switch()例程,该例程仔细保存当前寄存器的值(保存到 A 的进程结构),恢复寄存器进程 B(从它的进程结构),然后切换上下文(switch context),具体来说是通过改变栈指针来使用 B 的内核栈(而不是 A 的)。最后,操作系统从陷阱返回,恢复 B 的寄存器并开始运行它。

7.进程调度:介绍

我们使用公平,响应时间和周转时间来比较不同的调度策略。响应时间是指任务首次运行的时间-任务到达的时间。周转时间指进程的完成时间-进程的到达时间。

基本的调度方法:

先进先出(FIFO,FCFS)可能遇到先到的任务运行很久,平均周转时间过长

最短任务优先(SJF,shortest job first)因为它是非抢占形式的,可能在运行一个很长时间的任务的时候到达运行时间短的任务,导致平均周转时间降低

最短完成时间优先(STCF,shortest time-to-completion first)在SJF中引入抢占机制,更短运行时间的可以抢占资源

轮转调度(RR,Round-Robin)在一个时间片内运行一个工作,然后在下一个时间片切换到队列中的下一个任务,反复执行,知道所有任务完成。响应时间很低,但是周转时间很高。

8.调度:多级反馈队列

多级反馈队列(MLFQ,multi-level feedback queue):关注进程的一贯表现,然后区别对待。

  • 规则 1:如果 A 的优先级 > B 的优先级,运行 A(不运行 B)。
  • 规则 2:如果A的优先级 = B的优先级,轮转运行A和B。
  • 规则 3:工作进入系统时,放在最高优先级(最上层队列)。
  • 规则 4:一旦工作用完了其在某一层中的时间配额(无论中间主动放弃了多少次 CPU),就降低其优先级(移入低一级队列)。
  • 规则 5:经过一段时间 S,就将系统中所有工作重新加入最高优先级队列。

9.调度:比例份额

彩票调度,步长调度;这两种方式都不能很好地适合 I/O;其中最难的票数分配问题并没有确定的解决方式

彩票调度:彩票数(ticket)代表了进程(或用户或其他)占有某个资源的份额。一个进程拥有的彩票数占总彩票数的百分比,就是它占有资源的份额。通过不断定时地(比如,每个时间片)抽取彩票,彩票调度从概率上(但不是确定的)获得这种份额比例。

步长调度:步长调度也很简单。系统中的每个工作都有自己的步长,这个值与票数值成反比。在 上面的例子中,A、B、C 这 3 个工作的票数分别是 100、50 和 250,我们通过用一个大数分别除以他们的票数来获得每个进程的步长。每次进程运行后,我们会让它的计数器 [称为行程(pass)值] 增加它的步长,记录它的总体进展。

10.多处理器调度(高级)

13.抽象:地址空间

一个进程的地址空间包含运行的程序的所有内存状态。例如:程序代码;当程序在运行的时候,利用栈(stack)来保存当前的函数调用信息,分配空间给局部变量,传递参数和函数返回值。堆(heap)用于管理动态分配的、用户管理的内存(调用new获得的内存,静态初始化变量)。

虚拟内存系统负责为程序提供一 个巨大的、稀疏的、私有的地址空间的假象,其中保存了程序的所有指令和数据。操作系统在专门硬件的帮助下,通过每一个虚拟内存的索引,将其转换为物理地址,物理内存根据获得的物理地址但获取所需的信息。操作系统会同时对许多进程执行此操作,并且确保程序之间互相不会受到影响,也不会影响操作系统。

14.插叙:内存操作API

内存包括堆和栈两种形式,程序运行需要内存的分配和释放两个步骤,在哪一步出错都可能导致程序的崩溃。

一般来说,系统中实际存在两级内存管理:

第一级是由操作系统执行的内存管理,操作系统在进程运行时将内存交给进程,并在进程退出(或以其他方式结束)时将其回收。

第二级管理在每个进程中,例如在调用 malloc()和 free()时,在堆内管理。 即使你没有调用 free()(并因此泄露了堆中的内存),操作系统也会在程序结束运行时,收回进程的所有内存(包括用于代码、栈,以及相关堆的内存页)。无论地址空间中堆的状态如何,操作系统都会在进程终止时收回所有这些页面,从而确保即使没有释放内存,也不会丢失内存。

15.机制:地址转换

在实现CPU虚拟化的时候,一般准则为受限直接访问(LDE)。LDE 背后的想法很简单:让程序运行的大部分指令直接访问硬件,只在一些关键点(如进程发起系统调用或发生时钟中断)由操作系统介入来确保“在正确时间, 正确的地点,做正确的事”。

为了实现高效的虚拟化,操作系统应该尽量让程序自己运行, 同时通过在关键点的及时介入(interposing),来保持对硬件的控制。高效和控制是现代操作系统的两个主要目标。

将虚拟地址转换为物理地址,这正是所谓的地址转换(address translation)技术。

这种地址转换实现的方式是动态重定位,首先,CPU需要两个寄存器:基址寄存器,界限寄存器。

基址寄存器通过记录基址,当程序计数器(例如128)被设置后,硬件获取这个地址后,就加上基址寄存器里的值(例如是32kb:32768),最终得到实际的物理地址32896。

界限寄存器提供访问保护,当进程访问超过边界或者访问负的虚拟地址时,CPU就会触发异常。

基址寄存器配合界限寄存器的硬件结构是芯片中的(每个CPU一对),我们将CPU负责地址转换的部分称为内存管理单元(MMU)

除此之外,操作系统还需要记录哪些空闲内存没有使用,我们使用空闲列表(free list)来实现。

操作系统在内核模式下可以通过特权指令修改这两个寄存器。操作系统和硬件进行配合,实现了简单的虚拟内存。

操作系统介入的过程:

  1. 创建进程时,为进程的地址空间找到内存空间(利用空闲列表标记空闲或已经使用)
  2. 进程中之时,操作系统回收内存,更新空闲列表
  3. 切换进程时,操作系统保存当前两个寄存器的值(保存在进程控制块PCB中)
  4. 异常发生时,操作系统提供应急行动

重定位会发生内存资源浪费的情况,因为被界限寄存器保护起来的内存无法被其他进程访问,如果这个进程使用的内存很少,资源就浪费了,所以要引入分段的概念。

16.分段

分段可以更高效的虚拟内存,避免了地址空间中的逻辑段之间的内存浪费,而且算法实现很容易,可以实现代码共享的功能。

用15节里面的例子可能存在栈和堆之间的空间没有被使用却占用了大量内存的问题,将进程分为代码段,栈段,堆段,分别将之放到不同的物理内存上,,这样就避免了虚拟地址空间中未使用部分占用物理内存。

进行分段之后,仍然需要确认当前的地址引用的是哪个段,可以使用显式方式,用虚拟地址的开头几位来表示不同的段,例如前两位00,01,10,11分别对应代码段,栈段,堆段;有时也用一位来判断(堆,栈当作一位)

栈的处理方式特殊些,它的增长方式是反向的,段寄存器中有一个专门的字段记录是否反向增长。除此之外,各个段之间有时为了提高效率还要进行地址空间的共享,所以在段寄存器中又加入了保护信息,来判断代码段的权限。

加入保护信息后,界限寄存器功能需要升级,不仅需要检查虚拟地址是否越界,还需要检查特定访问是否允许。

分段执行之后,在物理内存中会存在很多较小的未分配内容的空间,这样会造成很大的资源浪费,虽然可以使用紧凑物理内存,最接近匹配,最坏匹配,首次匹配等等方法缩小这种资源浪费,但是无法完全解决这个问题,所以要引入分片。

17.空闲空间管理

基本策略:

  1. 最优匹配:遍历整个空闲列表,找到最接近的空闲块
  2. 最差匹配:遍历后找到最大的空闲块
  3. 首次匹配:找到第一个足够大的块
  4. 下次匹配:首次匹配的策略下加入指针,指向上次查找结束的位置,为了将空闲空间的查找操作分散在整个地址空间

离奇的策略:

  1. 分离空闲列表:将一部分内存拿出来专门满足某种大小的请求,碎片就不会再成为问题了。这种方法省去了列表查找过程,特定大小的内存分配和释放都很快

  2. 伙伴系统:将空闲空间递归的一分为二,知道刚好可以满足请求,这种方法优秀在内存释放时,直接向上合并就可以了。

18. 分页:介绍

将空间分割成固定长度的分片就是分页,相应地,我们把物理内存看成是定长槽块的阵列,叫作页帧(page frame)。

页表是一种数据结构,用于将虚拟地址映射到物理地址上。VPN为虚拟页号,PFN为物理帧号。

页表项中可能包含很多不同的位:例如有效位(标记当前物理内存是否可用),保护位(标记当前物理内存是否可读可写),存在位(当前页在物理存储器还是在磁盘),脏位,参考位等等。

页表会造成机器访问很慢(很多内存访问用来访问页表),内存浪费(内存大量存储页表)

19. 分页:快速地址转换(TLB)

对每次内存访问,硬件先检查 TLB,看看其中是否有期望的转换映射,如果有,就完成转换(很快),不用访问页表(其中有全部的转换映射)。

TLB(地址转换旁路缓冲存储器)会将最近转换过的VPN记录在缓存中,由于软件存在两种局限性(时间局限性,空间局限性),所以软件大概率在一段时间内,访问相近的一段内存,所以TLB就可以大展身手了。然而缓存不能设计的太大,只有很小的TLB才可以实现快速的缓存。

正在运行的进程生成虚拟内存引用(用于获取指令 或访问数据),在这种情况下,硬件将其转换为物理地址,再从内存中获取所需数据。

硬件首先从虚拟地址获得 VPN,检查 TLB 是否匹配(TLB 命中),如果命中,则获得最终的物理地址并从内存中取回。这希望是常见情形,因为它很快(不需要额外的内存访问)。

如果在 TLB 中找不到 VPN(即 TLB 未命中),则硬件在内存中查找页表(使用页表基址寄存器),并使用 VPN 查找该页的页表项(PTE)作为索引。如果页有效且存在于物理内存中,则硬件从 PTE 中获得 PFN,将其插入 TLB,并重试该指令,这次产生 TLB 命中。

在上下文切换的时候,TLB还会遇到两个相同的虚拟地址对应不同的物理地址的情况,这时候不作区分就会乱套,TLB也不知道该怎么映射了。

简单的解决方法是切换进程的时候,将之前TLB的内容清零,但是也会出现很多次未命中的问题。所以一些系统增加了硬件支持,加入ASID地址空间标识符来作区分。

TLB满了之后会需要替换策略,可以使用简单的最近最少使用策略LRU(n+1个页被访问,大小只有n,LRU就乱套了),随机策略。

20. 分页:较小的表

页表总是占据极大的内存,简单的数据结构(基于数组)效率太低了。

多级页表能很好的解决问题,普通的线性表中存在很多无效空间,但是都分配了该页的页表,多级页表仅将两页标记为有效。

线性页表通常很大,线性表是按VPN索引的PTE数组,需要连续驻留在物理空间中;有了多级结构,我们增加了一个间接层 (level of indirection),使用了页目录,它指向页表的各个部分。这种间接方式,让我们能够将页表页放在物理内存的任何地方。

多级页表的缺点在于,TLB未命中,就需要从内存加载两次,这样会造成时间的浪费(空间换时间的典型);同时页表查找也会很复杂。

21. 超越物理内存:机制

为了支持更大的地址空间,操作系统需要把当前没有在用的那部分地址空间找个地方存储起来。一般来说,这个地方有一个特点,那就是比内存有更大的容量。增加交换空间让操作系统为多个并发运行的进程都提供巨大地址空间的假象。交换空间位于硬盘驱动器上,它比进入物理内存要慢。

硬件判断页是否在内存中的方法是通过页表的存在位,如果不存在,存在位为0,这时候会出现页错误。页错误通常由操作系统进行处理,操作系统通过PTE中的某些位来找到当前进程所对应的硬盘地址,然后发送请求给硬盘,将页读取到内存中。页表I/O完成后,操作系统会更新页表,这页就标记为存在,I/O运行中,操作系统仍然可以运行其他的进程

内存访问TLB未命中会遇到很多情况:

  1. 该页存在且有效,TLB未命中页也可以从PTE中获取PFN,然后重试指令就可以命中了
  2. 存在位为0,页错误处理程序运行
  3. 访问页为无效页,硬件捕获这个非法访问,操作系统陷阱处理程序运行,可能会杀死程序

22. 超越物理内存:策略

当操作系统中的空闲内存不够时,出于内存压力操作系统必须要换出一些页为常用页留下空间,我们应该踢出哪个页?

问题:内存中只包含系统中所有页的子集,因此可以将其视为系统中虚拟内存页的缓存,我们的目标是在缓存选择替换策略时,让缓存命中更多,如何做?

因为内存访问的速度比磁盘访问速度快很多很多,所以即使是小概率的未命中也会严重影响效率。

最优策略

替换内存中在最远将来才会访问到的页。

虽然不切实际(我们无法预测未来),但是可以作为一个标准来和其他方法进行对比,

FIFO 先入先出 & Random 随机策略

先入先出虽然简单,但是因为根本无法确定页的重要性,即使经常使用的页也会将其踢出,所以命中率会很低

随机策略在内存满了的时候随机选择一个页进行替换,命中率全凭运气

这两种方法都有可能踢出重要的页,而这个页马上就又要被引用

LRU 利用历史数据

局部性原则:程序倾向于频繁地访问某些代码例如(循环)和数据结构(例如循环访问的数组)。

局限性:空间局限性,时间局限性

LRU方法就是找到最近使用最少的页将它踢出,但是这种方法很难实现,因为一台机器会有上百万页,如果遍历寻找,性能代价太大了

在局部性:80%的引用是访问 20%的页(“热门”页)。剩下的 20%是对剩余的 80%的页(“冷门”页)访问时,LRU会有更好的表现

这展示了缓存较小导致的FIFO和LRU在性能方面的瓶颈。

近似LRU

增加一个使用位的概念,当最近使用了这个页,硬件就会将当前页的使用位设置成1。

当必须进行页替换时,操作系统检查当前指向的页 P 的使用位是 1 还是 0。如果是 1,则意味着页面 P 最近被使用, 因此不适合被替换。然后,P 的使用位设置为 0,时钟指针递增到下一页(P + 1)。该算法一直持续到找到一个使用位为 0 的页,使用位为 0 意味着这个页最近没有被使用过(在最坏的情况下,所有的页都已经被使用了,那么就将所有页的使用位都设置为 0,周期性地清除使用位可以防止这个问题出现)。

clock算法不如完美的LRU,但是比无记忆的方法好多了

因为踢出一个已经被更改过的页需要将它重写回磁盘,这很昂贵,我们称这种页为脏页;但是如果没有被修改过,这就是干净的页,脏位就是为了记录这个页修改了么,每次写入页时都会设置此位,因此可以将其合并到页面替换算法中。例如,时钟算法可以被改变, 以扫描既未使用又干净的页先踢出。无法找到这种页时,再查找脏的未使用页面,等等。

当内存被超额请求,操作系统只能不停地换页才能满足需求,一般的解决方式就是杀死其中的内存密集型进程来控制内存请求。

23. VAX/VMS虚拟内存系统

VAX-11为每个进程提供了32位的虚拟地址空间,虚拟地址由23位VPN和9位偏移组成,VPN高两位勇于区分页所在的段。

代码不会访问第0页,这一页被设置为不可访问,以方便空指针的检测(对调试的支持)

并发

26.并发:介绍

多线程程序会有多个执行点(多个程序计数器,每个都用于取指令和执行),每个线程类似于独立的进程,但是线程之间共享地址空间,能够访问相同的数据

两个线程运行在同一个处理器上,进行线程切换时,我们将状态保存在线程控制块(Thread Control Block TCB),进程是保存在PCB中。然而线程之间的切换地址空间保持不变,不用切换当前使用的页表。

除此之外,进程线程另一个区别在栈,每个线程都有一个自己的栈

27. 插叙:线程API

好吧,我其实不咋喜欢看这种章,一堆代码

《操作系统导论》吐血万字整理 - 附下载地址及思维导图相关推荐

  1. 《计算机网络 自顶向下》吐血万字整理 - 附下载地址及思维导图

    前言 我看的是这本,豆瓣9.4,很经典的教材书,在文章末尾我附上了网盘链接 这篇博客是重要的前六章的读书笔记,面试需要掌握的部分,读了之后脑子里能有一个整体的框架,还是收获很大的.但是如果要应付面试, ...

  2. 吐血整理的 Android 性能优化思维导图,让面试官眼前一亮

    引言 现如今 Android 开发行业的主要问题是因为初级的 Android 开发者太多了,导致初级开发的市场过于饱和,所以也就进一步导致初级和中级的开发者面临更大的竞争,因此想要脱离这种竞争现状,只 ...

  3. html5 css3思维导图,手把手整理CSS3知识汇总【思维导图】

    手把手整理CSS3知识汇总[思维导图] CSS3知识汇总思维导图请见文章底部 这两天总结了一下CSS3中的基本知识点,没有做到很全面,因为之前也记过一些笔记,就没有再整理成文档.这里我会把之前的笔记拍 ...

  4. ce 扫雷实验报告,棋盘布局,雷数,笑脸,计时器内存地址,思维导图分析

    ce 扫雷实验报告,棋盘布局,雷数,笑脸,计时器内存地址,思维导图分析.. 如果有帮到大家,哥哥姐姐不要吝啬点个关注呗!后续会更新更多其他有关汇编,逆向和web的实操超详细解析.谢谢大家! 目录 一. ...

  5. MindMapper免费下载版附序列号激活儿童思维导图软件

    现在少儿教育的受重视程度越来大,很多新手家长都会对孩子的教育颇费心力却又不知该如何下手.其实少儿的教育,最优先最重要的就是要锻炼出孩子的逻辑性与分析性.相比于传统的教学方式,思维导图更能让孩子快速地养 ...

  6. 【整理分享】14张思维导图构建 Python 核心知识体系

    原文:https://woaielf.github.io/2017/06/13/python3-all/ 本文主要涵盖了 Python 编程的核心知识(暂不包括标准库及第三方库). 按顺序依次展示了以 ...

  7. MindManager2020官方免费版下载激活版思维导图

    MindManager 2020中文版,附带的注册机和免费文件可以完美成功激活软件,其详细的安装教程可参考下文操作,希望对用户有所帮助. MindManager软件的专业版本,该软件具备简洁的操作界面 ...

  8. iThoughtsX for Mac 5.9 中文破解版下载 强大的思维导图软件

    iThoughtsX for Mac 是一款强大易用的思维导图应用软件.可导入导出大部分软件格式,如:MindManager.XMind.ConceptDraw.Excel 等,并可导出为 PPT.W ...

  9. SimpleMind Pro(电脑版思维导图软件)官方中文版V1.30.0.6068下载 | 电脑版思维导图软件哪个好用?

    ​        Simplemind Pro 是一款优秀的跨平台电脑版思维导图软件领导者,全球超过1000万用户,可帮助用户组织想法.记住信息并产生新想法,允许用户将主题放置在自由格式布局中的任何位 ...

最新文章

  1. 多分类 数据不平衡的处理 lightgbm
  2. mysql常见内置函数_MySQL常用内置函数
  3. 哲学家就餐 java_java模拟哲学家就餐问题
  4. 关于Python类属性与实例属性的讨论
  5. DBSCAN算法理论和Python实现
  6. python if判断字符串_python之条件判断、循环和字符串格式化
  7. hibernate clob mysql_Hibernate操作Clob类型数据
  8. JavaScript HTML5脚本编程——“跨文档消息传递”的注意要点
  9. 高效延时消息设计与实现的场景
  10. html meta标签
  11. Thinkphp学习笔记——友情链接的添加和验证
  12. 动易CMS - 模板的一些常用标签
  13. 论文翻译——Skin Lesion Synthesis with Generative Adversarial Networks
  14. 解决虚拟机与宿主机不在同一个网段中,不能相互ping通的问题
  15. MYSQL数据库导出和备份----mysqldump
  16. 【IoT】 产品设计之α、β、λ测试
  17. 对话亚洲高校首个博士论文奖-裘捷中丨KDD2022
  18. 建模计算机处理器,实战建模渲染,用锐龙7 5800X拒绝拖稿
  19. win10家庭版如何修改用户名对应的用户文件夹下的用户文件名字(中文该成英文字符)
  20. 禁止input密码自动填充及浏览器记住密码完整解决方案

热门文章

  1. 京东数据库智能运维平台建设之路
  2. 什么耳机对而伤害最小,传闻不伤耳的骨传导耳机是真的吗?
  3. hdu5769Substring
  4. 【闲得无聊】写个web版功德无量附代码+静态资源
  5. Q3财报“牛虎“斗日趋激烈,能否追上美国券商大户“罗宾汉“?
  6. 泰凌微ble mesh蓝牙模组天猫精灵学习之旅 ⑤ 阿里天猫精灵官方Genie BT mesh Stack框架:编译天猫精灵例程,实现语音控制!
  7. 《左手数据,右手图表》
  8. bam获取序列_bam格式文件处理大全(四)
  9. 别TM去外包公司!工作群里抢个红包都得退回去...
  10. Premiere Pro 2022带来离线语音转文本教程