当前位置: 首页 > news >正文

精读计算机体系结构基础 第三章 特权指令系统

3. 1 特权指令系统简介

想象一下,计算机就像一个大楼,而这个大楼有很多不同的楼层。每个楼层都有不同的功能和使用者。最上面的楼层是应用层,那里住着各种各样的应用程序,比如你用来写作的文字处理软件、用来浏览网页的浏览器等等。

在应用层的下面是操作系统层,操作系统就像是大楼的管理者,负责确保每个应用程序都能顺利运行。操作系统有一些特别的权限,能够控制计算机的硬件,比如CPU(中央处理器)和内存。为了保护大楼里的每个住户(应用程序),操作系统设定了一些规则,确保它们不会互相干扰。

这里有一个重要的概念叫做“特权指令系统”。可以把它想象成大楼的安全门,只有管理者(操作系统)才能打开。应用程序只能使用一些特定的“钥匙”,也就是它们能使用的指令。这些指令是经过筛选的,确保应用程序在安全的范围内运行,不会做出可能损害其他应用或计算机本身的事情。

当应用程序需要做一些更复杂的事情,比如访问硬件或进行系统级的操作时,它们就会请求操作系统的帮助。操作系统会暂时接管控制权,确保一切安全无误,然后再把控制权交还给应用程序。这样,应用程序就能在一个安全的环境中运行,而不会影响到其他程序或计算机的正常工作。

不同指令系统的特权态部分差别较大,但就其机制而言,可以分为以下几类:
1)运行模式定义及其转换

现代计算机的操作系统为了确保安全和稳定,采用了一种叫做保护模式的机制。这种机制至少有两种运行模式:用户态核心态

  1. 用户态:这是应用程序运行的地方。应用程序在这个模式下执行,不能直接访问硬件或系统资源。这样做是为了保护系统,防止应用程序做出可能会影响其他程序或系统稳定性的操作。

  2. 核心态:这是操作系统运行的地方。操作系统在这个模式下拥有完全的控制权,可以直接访问硬件和系统资源。核心态可以处理各种系统级的任务,比如管理内存、调度进程等。

不同的计算机架构定义了不同的运行模式。例如:

  • MIPS架构有三种模式:用户模式(user)、监控模式(supervisor)和核心模式(kernel)。
  • x86架构有四种模式:Ring 0(核心态)、Ring 1、Ring 2和Ring 3(用户态)。
  • LoongArch架构有四种模式:PLV0(核心态)、PLV1、PLV2和PLV3(用户态)。

当你打开计算机时,CPU会首先进入核心态,执行一个叫做引导程序的程序,这个程序负责加载操作系统。加载完成后,操作系统会进行一系列初始化工作,然后将CPU切换到用户态,让应用程序开始运行。

在应用程序运行时,如果发生了用户态无法处理的事件,比如需要访问硬件或发生错误,CPU会通过异常中断机制返回到核心态。在核心态,操作系统会处理这些事件,比如修复错误或提供所需的服务。

处理完成后,操作系统会将CPU切换回用户态,继续执行原来的应用程序,或者调度另一个应用程序来运行。

在LoongArch架构中,CPU当前的运行模式由一个叫做**控制状态寄存器(CSR)**的特殊寄存器中的PLV域来决定。PLV的值可以是0到3,分别对应于PLV0到PLV3四种运行模式。

在这里插入图片描述
运行模式的转换过程与虚拟存储和异常中断紧密相关,共同构建出完备的保护模式。不少指令系统还支持虚拟机模式、调试模式等,使计算机系统更为易用。

2)虚拟存储管理

虚拟存储管理的核心思想是让计算机软件(包括操作系统和应用程序)在一个虚拟地址的环境中运行,而不是直接使用物理地址(实际的内存地址)。这样做的好处是可以让每个程序都有自己的独立内存空间,避免了程序之间的干扰,提高了系统的安全性和稳定性。

  • 虚地址:这是程序使用的地址,程序在运行时只需要关心这些地址,而不需要知道它们在物理内存中的实际位置。
  • 实地址(物理地址):这是计算机内存中实际的地址,CPU最终需要访问的地址。

在程序运行时,CPU需要将虚地址转换为实地址。这个转换主要有两种方式:

  1. 查表转换:这是应用程序使用的主要方式。操作系统维护一个映射表,记录虚地址和实地址之间的关系。当程序需要访问某个虚地址时,CPU会查这个表,找到对应的实地址。这种方式允许每个进程有自己独立的虚地址空间,互不干扰。

  2. 直接映射:这种方式与使用物理地址的方式相似,主要用于操作系统内部。因为在操作系统初始化之前,负责虚拟存储管理的代码不能在被管理的虚地址空间中运行,所以直接映射通常不适用于用户态的应用程序。

每个进程都有自己的虚地址空间,这意味着即使不同的程序使用相同的虚地址,它们也不会相互影响。操作系统通过映射表来确保每个程序在运行时都能正确访问自己的内存区域。

3)异常与中断处理

异常中断都是用来打断正常程序执行流的机制,它们允许计算机在特定情况下转向执行专门的处理函数。这种机制在不同的运行模式之间切换时起着关键作用。

异常通常是在程序执行过程中发生的错误或特殊情况,比如:

  • 访问特权空间:当用户态的程序试图访问操作系统的核心资源时。
  • 访问未定义地址:当程序试图访问一个没有映射到物理内存的虚拟地址时。
  • 调用操作系统服务:当程序需要请求操作系统提供的服务,比如文件操作或网络通信。

当这些情况发生时,CPU会发出一个异常信号,这会导致程序的执行流被打断,CPU切换到核心态,并进入操作系统定义的处理函数。这些处理函数负责解决异常情况,比如修复错误或提供所需的服务。

中断则通常是由外部事件引发的,比如:

  • 硬件中断:例如,键盘输入、鼠标点击或网络数据到达等事件。
  • 定时器中断:操作系统使用定时器来管理任务调度和时间片分配。

当中断发生时,CPU会暂停当前正在执行的程序,保存其状态,然后转向执行中断处理程序。中断处理程序会处理相应的事件,完成后,CPU会恢复之前的程序状态,继续执行。

  1. 触发异常或中断:在用户态代码执行过程中,发生异常或中断。
  2. 切换到核心态:CPU发出信号,切换到核心态,进入操作系统的处理函数。
  3. 执行处理函数:操作系统处理异常或中断,执行相应的操作。
  4. 返回用户态:处理完成后,操作系统将控制权返回给用户态程序,继续执行。

4)控制状态寄存器

控制状态寄存器位于一个独立的地址空间,是支撑前面3种机制的具体实现,不同的指令系统差别较大。下面以LoongArch指令系统为例,列出其控制状态寄存器的功能。
在这里插入图片描述
好的,让我们用简单易懂的语言来解释控制状态寄存器(CSR)及其在指令系统中的作用,特别是在LoongArch指令系统中的读写操作。

控制状态寄存器是计算机中一个非常重要的寄存器,它用于存储系统的控制信息和状态信息。虽然CSR很重要,但由于它的操作频率通常远低于通用寄存器(如用于存储数据的寄存器),所以指令系统通常不会设计复杂的指令来直接操作CSR。

尽管CSR的操作不频繁,大多数指令系统仍然会提供一些基本的指令,用于在控制状态寄存器和通用寄存器之间进行数据搬运。这些指令允许程序将CSR中的数据移动到通用寄存器中进行处理,或者将处理结果写回到CSR中。

在LoongArch指令系统中,有两条主要的指令用于控制状态寄存器的读写操作:

  1. CSRRD:这个指令用于读取控制状态寄存器的值并将其存入通用寄存器中。

    • 例如,指令 csrrd $t0, CSR_CRMD4 的意思是将控制状态寄存器CRMD的值读取出来,并存入通用寄存器$t0中。
  2. CSRWR:这个指令用于将通用寄存器中的值写入控制状态寄存器,同时将控制状态寄存器的旧值存入通用寄存器中。

    • 例如,指令 csrwr $t0, CSR_CRMD 的意思是将通用寄存器 t 0 中的值写入控制状态寄存器 C R M D 中,并将 C R M D 的旧值存入通用寄存器 t0中的值写入控制状态寄存器CRMD中,并将CRMD的旧值存入通用寄存器 t0中的值写入控制状态寄存器CRMD中,并将CRMD的旧值存入通用寄存器t0中。

3.2 异常和中断

计算机通常按照软件的执行流进行顺序执行和跳转,但有时会需要中断正常的执行流程去处理其他任务,可以触发这一过程的事件统称为异常。

3.2.1 异常分类

  1. 外部事件

    • 这些事件来自CPU外部,通常被称为中断。中断可以来自处理器内部的其他模块或外部设备(如键盘、鼠标、网络等)。中断的存在使得CPU能够异步处理多个事件,避免了程序在等待输入时浪费时间。为了提高效率,操作系统通常使用中断来处理与输入输出(IO)相关的任务。由于中断事件往往是不可预测的,因此需要一套健全的软硬件机制来确保中断不会干扰正常的程序执行。
  2. 指令执行中的错误

    • 当CPU执行指令时,如果遇到操作码或操作数不符合要求的情况,就会产生异常。例如,尝试执行一个不存在的指令、进行除以零的运算、地址不对齐、在用户态下调用核心态专有指令,或者访问非法地址空间等。这些错误会导致当前指令无法继续执行,系统需要转到错误处理程序来解决这些问题。
  3. 数据完整性问题

    • 在使用像ECC(错误检测与纠正)等硬件校验方式的存储器时,如果发生校验错误,就会产生异常。可纠正的错误可以用于统计硬件的风险,而不可纠正的错误则需要根据出错的位置进行相应的处理,以确保数据的完整性和系统的稳定性。
  4. 地址转换异常

    • 在存储管理单元进行地址转换时,如果硬件转换表中没有有效的转换对应项,就会产生地址转换异常。这通常发生在程序试图访问未映射的内存页时,操作系统需要处理这种异常,以确保程序不会访问非法内存。
  5. 系统调用和陷入

    • 这类异常是由专有指令产生的,目的是让操作系统识别并处理特定的请求。这通常用于在保护模式下调用核心态的相关操作,例如请求系统资源或服务。
  6. 需要软件修正的运算

    • 某些运算(如浮点运算)可能会导致异常,特别是当操作和操作数的组合过于复杂时,硬件可能不愿意直接处理这些情况。这时,系统会寻求软件的帮助来解决这些问题。

下表列举了LoongArch指令系统中主要的异常。

在这里插入图片描述

3.2.2 异常处理

3.2.2.1 异常处理流程

异常处理的流程包括异常处理准备、确定异常来源、保存执行状态、处理异常、恢复执行状态并返回等。主要内容是确定并处理异常,同时正确维护上下文环境。异常处理是一个软硬件协同的过程,通常CPU硬件需要维护一系列控制状态寄存器(域)以用于软硬件之间的交互。LoongArch指令系统中与异常(含中断)处理相关的控制状态寄存器格式如图3.2所示。

在这里插入图片描述
下面对异常处理流程的五个阶段进行介绍。

  1. 异常处理准备

    • 当异常发生时,CPU需要进行一系列准备工作,以便顺利转向异常处理。

    • 记录被打断的指令地址(EPTR):这一步涉及到“精确异常”的概念。精确异常意味着在发生异常时,所有在异常指令之前的指令都已经执行完,而异常指令之后的指令则不会执行。CPU需要记录下被打断的指令地址,以便在异常处理结束后能够重新执行这条指令。不同的指令集有不同的存放位置,例如:

      • LoongArch存放在CSR.ERA
      • PowerPC存放在SRR0/CSRR0
      • SPARC存放在TPC[TL]
      • x86则用栈存放CS和EIP组合。
    • 调整CPU权限等级:通常将权限等级调整至最高特权等级,以便进行异常处理。在LoongArch中,硬件会将CSR.CRMD的PLV域置为0,进入最高特权等级,并将CSR.CRMD的IE域置为0,屏蔽所有中断输入。

    • 保存异常现场信息:硬件会保存异常发生时的一些状态信息。在LoongArch中,CSR.CRMD中的PLV和IE域的旧值会被记录到CSR.PRMD的PPLV和PIE域中,以便后续恢复。

    • 记录异常相关信息:这包括记录异常编号等信息,以帮助确定异常来源。在LoongArch中,异常编号会记录在CSR.ESTAT的Ecode和EsubCode域中,可能还会记录引发异常的指令的机器码(CSR.BADI)或造成异常的访存虚地址(CSR.BADV)。

  2. 确定异常来源

    • 不同类型的异常需要不同的处理方式。处理器确定异常来源的方式主要有两种:
      • 编号方式:将不同的异常进行编号,异常处理程序根据编号进行区分并跳转到指定的处理入口。
      • 入口地址方式:为不同的异常指定不同的异常处理程序入口地址,这样每个入口的异常处理程序自然知道待处理的异常来源。
    • 在x86中,硬件会查询中断描述符表(IDT)来获取不同异常处理的入口地址。在LoongArch中,异常处理程序的入口地址通过“入口页号与页内偏移进行按位逻辑或”的方式计算,入口页号通过CSR.EENTRY配置。
  3. 保存执行状态

    • 在进行异常处理之前,操作系统需要保存被打断程序的状态。通常需要将通用寄存器和程序状态字寄存器的值保存到栈中,以便后续恢复。
  4. 处理异常

    • CPU跳转到对应的异常处理程序,执行异常处理逻辑。这一步是异常处理的核心,处理程序会根据异常类型采取相应的措施。
  5. 恢复执行状态并返回

    • 在异常处理完成后,软件需要将之前保存的执行状态从栈中恢复出来。最后,执行专用的异常返回指令,以原子方式完成恢复权限等级、恢复中断使能状态、跳转至异常返回目标等多个操作。
    • 在LoongArch中,异常返回的指令是ERTN,它会将CSR.PRMD的PPLV和PIE域的值回填至CSR.CRMD的PLV和IE域,从而恢复CPU的权限等级和中断响应状态。同时,该指令还会将CSR.ERA中的值作为目标地址跳转回去。x86的IRET指令有类似的效果。

3.2.2.2 异常嵌套

好的,让我们用简单易懂的语言来解释异常嵌套的问题及其在LoongArch指令系统中的处理方式。

异常嵌套

在异常处理过程中,如果新的异常发生,就会出现异常嵌套的问题。这种情况需要保存当前正在处理的异常处理程序的状态,以便在处理完新的异常后能够返回到之前的异常处理。这种状态保存会消耗一定的栈资源,因此无限的异常嵌套是不可接受的。

异常嵌套的优先级

异常嵌套通常是基于优先级的:

  • 高优先级异常:只有优先级更高的异常才能打断当前的异常处理程序。
  • 低优先级或同优先级异常:这些异常只能等待当前的异常处理完成,不能进行嵌套。

系统支持的优先级级数决定了异常嵌套的最大层数。

在LoongArch指令系统中,异常嵌套时被打断的异常处理程序的状态保存和恢复主要由软件处理。这就要求:

  1. 保证当前上下文的保存:在完成当前上下文的保存操作之前,异常处理程序不能产生新的异常,或者产生的新异常不能修改当前需要保存的上下文。
  2. 不可预知的异常:有些异常的产生原因是无法预知的,例如中断、机器错误、TLB(翻译后备缓冲区)重填等。

为了应对这些挑战,LoongArch设计了一些硬件机制,以确保在发生异常嵌套时能够正确保存上下文信息:

  • 关闭全局中断使能:在跳转到异常处理入口时,可以关闭全局中断,使能以禁止中断异常的发生。这可以防止在处理当前异常时被新的中断打断。

  • 硬件暂存上下文信息:在发生嵌套异常时,可能会有一些上下文信息被破坏,而软件又来不及保存。这时,硬件可以将这些上下文信息暂存到指定的控制状态寄存器或内存区域,以确保在异常处理完成后能够正确恢复。

3.2.3 中断

异常处理的流程是通用的,但有两类异常出现的机会确实比其他类型大很多。一类是地址转换异常,当片内从虚地址到物理地址的地址转换表不包含访问地址时,就会产生缺页异常,在3.3节中我们将进行详细介绍。另一类常见的异常就是中断,中断在外部事件想要获得CPU注意时产生。由于外部事件的不可控性,中断处理所用的时间较为关键。在嵌入式系统中,CPU的主要作用之一就是处理外设相关事务,因此中断发生的数量很多且非常重要。本节以LoongArch指令系统为例介绍中断相关的重要内容。

3.2.3.1 中断传递机制

中断从系统中各个中断源传递到处理器主要有两种形式:中断线和消息中断。

  1. 中断线

    • 定义:中断线是一种直接的中断传递方式,通常用于将中断信号从系统中的各个中断源传递到处理器。

    • 连接方式

      • 当中断源数量较少时,可以直接将它们连接到处理器的引脚。
      • 如果中断源较多,通常会使用中断控制器来汇总中断信号,然后再与处理器连接。
    • 共享方式:在复杂的系统中,通常采用共享中断线的方式。例如,PCI总线有四根中断线供所有设备共享。中断处理程序需要定位到哪根中断线发生了中断,然后逐个调用注册在该中断线的设备中断服务。

    • LoongArch支持:在LoongArch指令系统中,共定义了13个中断,包括核间中断、定时器中断、性能监测计数溢出中断、外部硬中断和软中断。所有中断信号采用高电平有效的电平中断方式,处理器会持续采样这些信号并记录到CSR.ESTAT中。

    • 优点

      • 实现简单,适合中断源较少的系统。
    • 缺点

      • 扩展性差:在复杂系统中,过多的共享会降低中断处理效率。
      • 延迟问题:中断处理需要查询中断控制器和设备状态寄存器,可能导致较长的延迟。
      • 多处理器支持不足:在多处理器平台中,传统的中断线方式难以实现负载均衡和中断绑定等功能。
  2. 消息中断

    • 定义:消息中断是一种通过数据在总线上传递中断的方式。发出中断时,设备向指定地址写入一个特定的数值。
    • 传输方式:消息中断在“带内”(In-Band)传输,不需要额外的中断线,增加中断时不需要改动消息传递的数据通路。
    • 优点
      • 高扩展性和灵活性:可以轻松增加中断号,设备可以申请更多的中断号,使得中断处理程序无需查询设备状态,只需根据中断号进行处理。
      • 减少延迟:由于不需要查询中断控制器,消息中断可以减少处理延迟,提高效率。

3.2.3.2 向量化中断

让我们详细解释LoongArch指令系统和x86指令系统中向量化中断的机制及其特点。

LoongArch指令系统中的向量化中断

  1. 向量化中断支持

    • LoongArch指令系统默认支持8个向量化中断,并且有13个线中断,每个中断都有独立的中断处理程序入口地址。
  2. 中断作为异常处理

    • 在LoongArch中,中断被视为一种特殊的异常进行处理。具体来说,SWI0到IPI这13个中断被依次视作异常编号64到76,并使用统一的异常处理程序入口地址计算方式来确定中断处理程序的入口地址。
  3. 加速中断处理

    • 向量化中断的一个主要优点是省去了中断处理程序开头处识别具体中断源的开销。这意味着在处理中断时,CPU可以更快地跳转到相应的处理程序,从而提高中断处理的效率。

x86指令系统中的向量化中断

  1. 复杂的向量化中断方案

    • x86指令系统的向量化中断机制相对复杂,使用**中断向量表(IVT)中断描述符表(IDT)**来管理中断。
    • 在实模式下,中断向量表默认存放在地址0,而在保护模式下则使用中断描述符表。
  2. 中断向量表和描述符表

    • 中断向量表(IVT):存放中断入口地址的段地址和偏移量。
    • 中断描述符表(IDT):除了存放中断入口地址外,还包含权限等级和描述符类别的信息。
  3. 中断和异常的支持数量

    • x86的向量化中断机制最多可支持256个中断和异常
      • 0~19号:系统预设的异常和非屏蔽中断(NMI)。
      • 20~31号:Intel保留的编号。
      • 32号及以上:可用于外部中断。
  4. 实现参考

    • 详细的实现和使用方法可以参考Intel相关手册,以了解如何配置和使用中断向量表和描述符表。

3.2.3.3 中断的优先级

在支持多个中断源输入的指令系统中,处理器如何处理同时触发的多个中断是一个重要的设计考虑。下面我们将详细介绍非向量中断模式和向量中断模式下的中断优先级处理机制。

在非向量中断模式下,处理器通常不区分中断的优先级。如果需要对中断进行优先级处理,可以通过软件实现,通常的方案包括:

  1. 维护中断优先级(IPL)

    • 软件会维护一个中断优先级列表,每个中断源被赋予特定的优先级。
  2. 最低优先级运行

    • 在正常状态下,CPU运行在最低优先级,此时任何中断都可以触发。
  3. 最高优先级禁止

    • 当CPU处于最高中断优先级时,任何中断都被禁止。这意味着在处理高优先级中断时,系统不会响应其他中断。
  4. 抢占机制

    • 如果更高优先级的中断发生,可以抢占正在处理的低优先级中断。这种机制允许系统在处理重要任务时及时响应更紧急的中断。

在向量中断模式下,处理器需要依照既定的优先级规则从多个已生效的中断源中选择一个进行处理。LoongArch指令系统实现了向量中断,采用固定优先级仲裁机制,具体规则如下:

  • 优先级规则
    • 硬件中断号越大,优先级越高。具体优先级顺序为:
      • IPI(核间中断):优先级最高
      • TI(定时器中断):次高
      • PMI(性能监测计数溢出中断):次之
      • HWI0~HWI7(外部硬中断):优先级依次降低
      • SWI0~SWI1(软中断):优先级最低

3.2.3.4 中断使能控制位的原子修改

在中断处理程序中,经常会涉及中断使能控制位的修改,如关闭、开启全局中断使能。在大多数指令系统中,这些中断使能控制位位于控制状态寄存器中,因此软件在进行中断使能控制调整时,必须关注修改的原子性问题。以LoongArch指令系统为例,全局中断使能控制位IE位于CRMD控制寄存器的第2位。如果仅用CSRRD和CSRWR指令访问CRMD控制寄存器,那么需要通过下面的一段程序才能完成开启中断使能的功能:

 li      $t1, IE_BITMASKcsrrd   $t0, CSR_CRMD
1:andn    $t0, $t0, $t1or      $t0, $t0, $t1
2:csrwr   $t0, CSR_CRMD

这段程序本身也可能被中断,若在标号1和2之间被中断且中断处理程序修改了CRMD控制寄存器的值,则在返回时该中断处理程序对CRMD控制寄存器的改写会被这段程序覆盖。若不想让这种情况发生,就需要保证这段程序不会被打断,更正式地说是保证这段程序的原子性。保证原子性的方法有很多种,例如添加专门的位原子修改指令、在程序执行时禁用中断、不允许中断处理程序修改SR,或者使用通用的方法保证程序段的原子性,即将被访问的控制寄存器作为临界区来考虑。LoongArch指令系统中定义了按位掩码修改控制寄存器的指令CSRXCHG。使用该指令时,上述开启全局中断使能的代码改写如下:

 li       $t0, IE_BITMASKcsrxchg  $t0, $t0, CSR_CRMD

上面的例子中,CRMD寄存器的IE位置1的操作仅通过csrxchg一条指令完成,所以自然确保了修改的原子性。

相关文章:

  • 使用 SHAP 进行特征交互检测:揭示变量之间的复杂依赖关系
  • 豆包:国内 web 辅助开发的领头羊
  • 以党建网为例,深入分析IT技术栈,实战经验
  • 基于多层权重博弈与广播机制的仿生类脑 AI 决策框架
  • 文件(分片)并行上传时计算总的上传进度
  • Linux基础开发工具一(yum/apt ,vim)
  • C++内存管理详解
  • ES 面试题系列「二」
  • HTML难点小记:一些简单标签的使用逻辑和实用化
  • 49.EFT测试与静电测试环境和干扰特征分析
  • RS485和RS232 通信配置
  • 【Linux高级全栈开发】2.1高性能网络-网络编程——2.1.1 网络IO与IO多路复用——select/poll/epoll
  • Kubernetes排错(十四):Pod状态异常排查手册
  • 每日脚本 5.11 - 进制转换和ascii字符
  • Lambda表达式能用在哪些场景?
  • libcurl简单使用
  • TeledyneLeCroy在OFC2025 EA展台上展示了其400G/800G的全包围的测试解决方案,满足了UEC联盟和UALINK联盟的技术需求
  • [Java][Leetcode simple]26. 删除有序数组中的重复项
  • 欧拉路与欧拉回路(模板)
  • Java学习笔记(对象)
  • 市场监管总局等五部门约谈外卖平台企业
  • “11+2”复式票,宝山购彩者领走大乐透1170万头奖
  • 支持企业增强战略敏捷更好发展,上海市领导密集走访外贸外资企业
  • 寒武纪陈天石:公司的产品力获得了行业客户广泛认可,市场有望迎来新增量需求
  • 《审判》|“被告”的魅力:K在等什么?
  • 让胖东来“自闭”的玉石生意,究竟水有多深?