minos 2.3 中断虚拟化——GICv2 管理

首发公号:Rand_cs
该项目来自乐敏大佬:https://github.com/minosproject/minos

硬件肯定需要软件配合,这一节就来实战 GICv2

首先准备好 GICv2 手册:https://developer.arm.com/documentation/ihi0048/bb/?lang=en,对于硬件的管理,最底层的操作就是读写硬件的寄存器,所以这里准备好手册,随时查阅手册

// gicv2.c static void *gicv2_dbase;
static void *gicv2_cbase;

首先定义了两个变量,gicc(cpu interface) 和 gicd(distributor)基地值,这个值是在设备树文件中定义,然后 gic 初始化函数中映射到 hyp 虚拟地址

static inline void writeb_gicd(uint8_t val, unsigned int offset)
{writeb_relaxed(val, gicv2_dbase + offset);
}static inline void writel_gicd(uint32_t val, unsigned int offset)
{writel_relaxed(val, gicv2_dbase + offset);
}static inline uint32_t readl_gicd(unsigned int offset)
{return readl_relaxed(gicv2_dbase + offset);
}static inline void writel_gicc(uint32_t val, unsigned int offset)
{writel_relaxed(val, gicv2_cbase + offset);
}static inline uint32_t readl_gicc(unsigned int offset)
{return readl_relaxed(gicv2_cbase + offset);
}

随后定义了一系列读写寄存器的函数,就是相对 (gicc/gicd) 不同的偏移上进行读写,下面我们来看看主要需要操作哪些寄存器

GICD

GICD_ISENABLERn、 GICD_ICENABLERn

一个 GICD_ISENABLER 寄存器 32 bit,每一位控制一个中断的使能情况,n 表示第几个 GICD_ISENABLER 寄存器。向某位写 1 表示使能该中断,写 0 无效。

一个 GICD_ICENABLER 寄存器 32 bit,每一位控制一个中断的屏蔽情况,n 表示第几个 GICD_ICENABLER 寄存器。向某位写 1 表示屏蔽该中断,写 0 无效。

在 ARM 平台,使能和屏蔽一个中断是通过不同的寄存器来控制的,而不是一个寄存器使用 0 1 来分别表示屏蔽使能。这么做的好处之前有了解过是为了操作可以并行,比如说现在有操作使能第一号中断和屏蔽第二号中断,如果只使用0、1的方式来控制中断的屏蔽使能情况,第一号中断和第二号中断位于同一个寄存器,那么想要实现此操作,需要串行执行,但是如果使用 GICD_ISENABLER 来控制使能,GICD_ICENABLER 控制屏蔽,那么两个步骤就可以并行。另外因为写 0 无效,所以想要执行写操作的时候,不用先读后写(一般操作:read a,a |= x,write a,现在直接 write x)

// 屏蔽中断号为 irq 的中断
static void gicv2_mask_irq(uint32_t irq)
{unsigned long flags;spin_lock_irqsave(&gicv2_lock, flags);// 写 0 无效,所以可以直接写入值 1UL << (irq % 32)// irq / 32 表示第几个 GICD_ICENABLER 寄存器// (irq / 32) * 4,按字节算偏移,乘以 4writel_gicd(1UL << (irq % 32), GICD_ICENABLER + (irq / 32) * 4);dsb();spin_unlock_irqrestore(&gicv2_lock, flags);
}
// 使能中断号为 irq 的中断
static void gicv2_unmask_irq(uint32_t irq)
{unsigned long flags;spin_lock_irqsave(&gicv2_lock, flags);writel_gicd(1UL << (irq % 32), GICD_ISENABLER + (irq / 32) * 4);dsb();spin_unlock_irqrestore(&gicv2_lock, flags);
}

GICD_ICFGRn

Interrupt Configuration Registers,用来配置中断的类型

中断类型:边沿触发、电平触发

中断模型:

  1. 1-N 模型,只有一个 cpu 能够处理该中断,这指的是一个中断发送给多个 cpu,当此中断被某个 cpu 响应之后,gic 会清除掉此中断在其他所有 cpu 上的 pending 状态。
    1. 这是专门用来描述 SPI 类型中断的。试想,磁盘数据好了,发送磁盘中断信号给 cpu,每个 cpu 都可以处理,但是肯定只能有一个 cpu 处理,否则数据紊乱
    2. 当 CPU 从 GICC_IAR 读取中断号的时候,可能会读取到正确的中断号,这是响应 cpu。其他 cpu 会读取到 1023,这表示一个伪中断。
  2. N-N 模型,所有 cpu 都会独立的收到该中断信号,当一个 cpu 相响应中断后,只是为该 cpu 清除掉此中断的 pending 状态。此中断对于其他 cpu 来说仍然是 pending 状态,其他 cpu 仍然需要响应处理此中断

GICD_ICFGR 寄存器就是用来配置一个中断的类型和模型

一个 GICD_ICFGR 寄存器 32bits,一个中断占 2bits

bit[0] 表示中断模型,为 0 表示 N-N 模型,为 1 表示 1-N 模型

bit[1] 表示中断类型,为 0 表示电平触发,为 1 表示边沿触发

static int gicv2_set_irq_type(uint32_t irq, uint32_t type)
{uint32_t cfg, edgebit;if (irq < 16)return 0;spin_lock(&gicv2_lock);/* Set edge / level */cfg = readl_gicd(GICD_ICFGR + (irq / 16) * 4);edgebit = 2u << (2 * (irq % 16));  // 边沿触发if ( type & IRQ_FLAGS_LEVEL_BOTH)cfg &= ~edgebit;else if (type & IRQ_FLAGS_EDGE_BOTH)cfg |= edgebit;writel_gicd(cfg, GICD_ICFGR + (irq / 16) * 4);spin_unlock(&gicv2_lock);return 0;
}

GICD_IPRIORITYR

Interrupt Priority Registers,设置每个中断的优先级

一个 GICD_IPRIORITYR 寄存器 32 bits,分为 4 组,每 8 bits 表示一个中断的优先级

static int gicv2_set_irq_affinity(uint32_t irq, uint32_t pcpu)
{if (pcpu > NR_GIC_CPU_IF || irq < 32)return -EINVAL;spin_lock(&gicv2_lock);/* Set target CPU mask (RAZ/WI on uniprocessor) */writeb_gicd(1 << pcpu, GICD_ITARGETSR + irq);spin_unlock(&gicv2_lock);return 0;
}

GICD_ITARGETSR

Interrupt Processor Targets Registers,设置中断亲和性

一个 GICD_ITARGETSR 寄存器 32 bits,分为 4 组,每 8 bits 表示一个中断的亲和性。举个例子,如果某个中断的亲和性设置 3(0b11),那么这个中断将会被 distributor 转发给 0、1 号 cpu interface。

另外,前面提到过,SPI 的中断号从 32 开始,所以 GICD_ITARGETSR0~GICD_ITARGETSR7 “无用”,读取GICD_ITARGETSR0~GICD_ITARGETSR7会返回执行读取操作 cpu 的 id 值

static int gicv2_set_irq_affinity(uint32_t irq, uint32_t pcpu)
{if (pcpu > NR_GIC_CPU_IF || irq < 32)return -EINVAL;spin_lock(&gicv2_lock);/* Set target CPU mask (RAZ/WI on uniprocessor) */writeb_gicd(1 << pcpu, GICD_ITARGETSR + irq);spin_unlock(&gicv2_lock);return 0;
}

GICD_SGIR

Software Generated Interrupt Register,写这个寄存器来产生 SGI 中断

关注两个字段:

  • TargetListFilter
    • 0b00,表示向位于 CPUTargetList 位图中的 CPU Interface集合发送 SGI
    • 0b01,表示向所有 CPU Interface发送一个 SGI 中断
    • 0b10,表示向自己发送一个 SGI
    • 0b11,reserved
  • CPUTargetList,一个 CPU 位图,某一位为 1 表示要向该 CPU Interface 发送一个 SGI(TargetListFilter=0b00 的情况下)
static void gicv2_send_sgi(uint32_t sgi, enum sgi_mode mode, cpumask_t *mask)
{unsigned int cpu;unsigned int value = 0;switch (mode) {// 发送一个 SGI 给所有 CPUcase SGI_TO_OTHERS:writel_gicd(GICD_SGI_TARGET_OTHERS | sgi, GICD_SGIR);break;// 给自己发送一个 SGIcase SGI_TO_SELF:writel_gicd(GICD_SGI_TARGET_SELF | sgi, GICD_SGIR);break;// 发送一个 SGI 给目标 CPU 组case SGI_TO_LIST:for_each_cpu(cpu, mask)value |= gic_cpu_mask[cpu];// 填写目标 CPU 位图集合writel_gicd(GICD_SGI_TARGET_LIST |(value << GICD_SGI_TARGET_SHIFT) | sgi,GICD_SGIR);isb();break;default:break;;}
}

NOTE,Distributor 只会向 CPU Interface 转发中断请求,CPU Interface 才会向 CPU 发送中断信号

GICD_TYPER

Interrupt Controller Type Register,有关 GICD 的一些信息

ITLinesNumber:支持的中断个数

CPUNumber:CPU 个数

GICC

GICC_EOIR、GICC_DIR

当一个中断完成的时候,CPU 必须发送一个 “complete” 信号给 GIC,“complete” 步骤分为两步:

  1. Priority drop,优先级降低。中断状态并未改变,仍然是 active。优先级降低指的是当前 CPU 上 “running priority”,降低之后 cpu interface 可以继续发送优先级较低的中断给 cpu。
  2. Interrupt deactivation,真正改变中断状态了。可以从 active->inactive,active and pending->pending

这涉及到了 3 个寄存器,GICC_EOIR(End of Interrupt Register),GICC_DIR(Deactivate Interrupt Register),GICC_CTLR(CPU Interface Control Register)

当 GICC_CTLR.EOImode = 1 时,Priority drop 和 Interrupt deactivation 两个步骤是分开的,也就是说写 GICC_EOIR 会 Priority drop,写 GICC_DIR 会 Interrupt deactivation

当 GICC_CTLR.EOImode = 0 是,Priority drop 和 Interrupt deactivation 两个步骤是在一起的,写 GICC_EOIR 寄存器就表示完成两个步骤

GICC_DIR 和 GICC_EOIR 格式都如下所示:

static void gicv2_eoi_irq(uint32_t irq)
{writel_gicc(irq, GICC_EOIR);dsb();
}static void gicv2_dir_irq(uint32_t irq)
{writel_gicc(irq, GICC_DIR);dsb();
}

用法也很简单,写入相应的中断号就行

GICC_IAR

Interrupt Acknowledge Register,CPU 读取此寄存器来获取中断号,并且也是一个 ACK 操作

static uint32_t gicv2_read_irq(void)
{uint32_t irq;irq = readl_gicc(GICC_IAR);isb();irq = irq & GICC_IA_IRQ;return irq;
}

格式同前 EOIR,操作也是很简单, 从低 10 bits 获取中断号

GICC_PMR

Interrupt Priority Mask Register,只有优先级比 GICC_PMR 里面设置的高,才会将该中断信号发送给 cpu

格式如上,一般我们写 0xff,表示不屏蔽任何中断(值越大,优先级越低)

GIC INIT

gicv2_dist_init

static void __init_text gicv2_dist_init(void)
{uint32_t type;uint32_t cpumask;uint32_t gic_cpus;unsigned int nr_lines;int i;// 所有中断都往 pcpu0 发送cpumask = readl_gicd(GICD_ITARGETSR) & 0xff;cpumask = (cpumask == 0) ? (1 << 0) : cpumask;cpumask |= cpumask << 8;cpumask |= cpumask << 16;/* Disable the distributor */writel_gicd(0, GICD_CTLR);// 从 GICD_TYPER 寄存器里面获取 cpu 数量和支持的中断数type = readl_gicd(GICD_TYPER);nr_lines = 32 * ((type & GICD_TYPE_LINES) + 1);gic_cpus = 1 + ((type & GICD_TYPE_CPUS) >> 5);pr_notice("GICv2: %d lines, %d cpu%s%s (IID %x).\n",nr_lines, gic_cpus, (gic_cpus == 1) ? "" : "s",(type & GICD_TYPE_SEC) ? ", secure" : "",readl_gicd(GICD_IIDR));/* Default all global IRQs to level, active low */// 配置所有 irq 为边沿触发for ( i = 32; i < nr_lines; i += 16 )writel_gicd(0x0, GICD_ICFGR + (i / 16) * 4);/* Route all global IRQs to this CPU */// 配置所有中断的亲和性为 cpu0for ( i = 32; i < nr_lines; i += 4 )writel_gicd(cpumask, GICD_ITARGETSR + (i / 4) * 4);/* Default priority for global interrupts */for ( i = 32; i < nr_lines; i += 4 )writel_gicd(GIC_PRI_IRQ << 24 | GIC_PRI_IRQ << 16 |GIC_PRI_IRQ << 8 | GIC_PRI_IRQ,GICD_IPRIORITYR + (i / 4) * 4);/* Disable all global interrupts */// 屏蔽所有中断for ( i = 32; i < nr_lines; i += 32 )writel_gicd(~0x0, GICD_ICENABLER + (i / 32) * 4);/* Only 1020 interrupts are supported */// 从支持的中断数和 1020 之中选一个小的gicv2_nr_lines = min(1020U, nr_lines);/* Turn on the distributor */// 使能所有中断writel_gicd(GICD_CTL_ENABLE, GICD_CTLR);dsb();
}

gicv2_cpu_init

static void __init_text gicv2_cpu_init(void)
{int i;int cpuid = smp_processor_id();// 读取 GICD_ITARGETSR0 会返回当前 cpuidgic_cpu_mask[cpuid] = readl_gicd(GICD_ITARGETSR) & 0xff;pr_debug("gicv2 gic mask of cpu%d: 0x%x\n", cpuid, gic_cpu_mask[cpuid]);if (gic_cpu_mask[cpuid] == 0)gic_cpu_mask[cpuid] = 1 << cpuid;/* The first 32 interrupts (PPI and SGI) are banked per-cpu, so* even though they are controlled with GICD registers, they must* be set up here with the other per-cpu state. */// TODO ???writel_gicd(0xffff0000, GICD_ICENABLER); /* Disable all PPI */writel_gicd(0x0000ffff, GICD_ISENABLER); /* Enable all SGI *//* Set SGI priorities */// 设置 SGI 的优先级,IPIs must preempt normal interruptsfor ( i = 0; i < 16; i += 4 )writel_gicd(GIC_PRI_IPI << 24 | GIC_PRI_IPI << 16 |GIC_PRI_IPI << 8 | GIC_PRI_IPI,GICD_IPRIORITYR + (i / 4) * 4);/* Set PPI priorities */// 设置 PPI 的优先级for ( i = 16; i < 32; i += 4 )writel_gicd(GIC_PRI_IRQ << 24 | GIC_PRI_IRQ << 16 |GIC_PRI_IRQ << 8 | GIC_PRI_IRQ,GICD_IPRIORITYR + (i / 4) * 4);/* Local settings: interface controller *//* Don't mask by priority */writel_gicc(0xff, GICC_PMR);/* Finest granularity of priority */writel_gicc(0x0, GICC_BPR);/* Turn on delivery */// GICC_CTL_ENABLE 允许 group1(非安全中断,目前minos里面都是)中断发送给 cpu// GICC_CTL_EOI drop priority 和 deactivate interrupt 分开writel_gicc(GICC_CTL_ENABLE|GICC_CTL_EOI, GICC_CTLR);dsb();
}

gicv2_init

static struct irq_chip gicv2_chip = {.irq_mask       = gicv2_mask_irq,.irq_mask_cpu       = gicv2_mask_irq_cpu,.irq_unmask         = gicv2_unmask_irq,.irq_unmask_cpu     = gicv2_unmask_irq_cpu,.irq_eoi        = gicv2_eoi_irq,.irq_dir        = gicv2_dir_irq,.irq_set_type       = gicv2_set_irq_type,.irq_set_affinity   = gicv2_set_irq_affinity,.send_sgi       = gicv2_send_sgi,.get_pending_irq    = gicv2_read_irq,.irq_set_priority   = gicv2_set_irq_priority,.irq_xlate      = gic_xlate_irq,.init           = gicv2_init,.secondary_init     = gicv2_secondary_init,
};

irq_chip 是一个 gic 芯片抽象,对于 gicv2 的抽象定义在了 gicv2_chip,大多操作我们都讲述了

gicv2_init 涉及设备树操作以以及虚拟化的初始化,后面讲述。gicv2_secondary_init 这是级联相关(一个 gic 芯片不够,多个 gic 芯片连接起来共同工作)目前不涉及

首发公号:Rand_cs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/846700.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入剖析Java逻辑运算符,解决日常开发难题

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

SpringBoot:手动创建应用

Spring提供了在线的Spring Initialzr在线创建Spring Boot项目&#xff0c;为了更好的理解Spring Boot项目&#xff0c;这里我们选择手动创建。 1.新建Web应用 1.1 生成工程 首先要做是创建一个Java项目&#xff0c;这里我们选择使用Maven来支持&#xff0c;使用archetype:ge…

现成方案 - 复刻版类似 Perplexity 与秘塔 AI 的搜索引擎

这里为大家带来一个极具创新性的开源 AI 搜索引擎&#xff0c;其灵感源自 Perplexity。 该搜索引擎主要具备以下功能&#xff1a; 能够接收用户提出的各种问题。借助 Bing 搜索 API 可查找出前 6 个结果并予以展示。会抓取这 6 个链接的文本内容&#xff0c;将其作为重要的上下…

Gavin Wood 访谈|Polkadot 从何而来,又将如何面对 AI 时代?

如果没有宏观经济&#xff0c;加密世界可能无法存在。或许&#xff0c;Satoshi Nakamoto 也永远不会写出那篇开创性的白皮书。区块链技术作为指数时代的核心之一&#xff0c;在宏观经济理论中占有重要地位。传统的经济增长公式是人口增长加生产率增长加债务增长。然而&#xff…

Python 高级数据类型

列表List 定义列表 可以将不同的基本数据类型或者列表装到一个列表里 my_list [1,2,3,4,5] print(my_list) # [1, 2, 3, 4, 5] 直接打印出列表的内容 print(type(my_list)) # <class list>my_list ["1","2","3","4","…

基于51单片机的智能晾衣架设计

一.硬件方案 智能自动晾衣架能够实现晾衣架的自动升降。与传统的手动晾衣架相比&#xff0c;自动晾衣架具有升降更省力&#xff0c;升降速度更快等优势&#xff0c;随着技术的日臻完善&#xff0c;自动晾衣架将成为市场的主导产品。 电路主要由 51单片机最小系统无线收发模块编…

数学建模之MATLAB入门教程(上)

前言&#xff1a; • MATLAB是美国Math Works公司出品的商业数学软件&#xff0c;用于数据分析、无线通信、深度学习、图像处理与计算机视觉、信号处理、量化金融与风险管理、机器人&#xff0c;控制系统等领域。 • MATLAB将数值分析、矩阵计算、科学数据可视化以及非线性动…

Python自动化识别与删除Excel表格空白行和列

在处理Excel数据时&#xff0c;经常会遇到含有空白行和空白列的情况。这些空白区域不仅占用表格显示空间&#xff0c;还可能导致数据分析时出现偏差&#xff0c;影响数据处理的效率与结果的准确性&#xff0c;如空白行可能干扰数据聚合操作&#xff0c;导致统计计数不准确&…

集合类源码浅析のArrayList

源码分析路线图&#xff1a; 初级部分&#xff1a;ArrayList->LinkedList->Vector->HashMap(红黑树数据结构&#xff0c;如何翻转&#xff0c;变色&#xff0c;手写红黑树)->ConcurrentHashMap 中级部分&#xff1a;Spring->Spring MVC->Spring Boot->M…

240602-通过命令行实现HuggingFace文件上传

A. 登录显示 A.1 MacOS A.2 Windows B. 操作步骤 B.1 操作细节 要通过命令行将文件上传到 Hugging Face&#xff0c;可以使用 huggingface-cli 工具。以下是详细步骤&#xff1a; 安装 huggingface_hub 包&#xff1a; 首先&#xff0c;确保已经安装了 huggingface_hub 包。可…

基于springboot实现青年公寓服务平台系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现青年公寓服务平台系统演示 摘要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;房屋信息因为其管理内容繁杂&#xff…

“人工智能AI+” 应用场景盘点

在这个科技与梦想交相辉映的时代&#xff0c;人工智能已不再停留于遥不可及的概念构想&#xff0c;而是化身为一股汹涌的创新洪流&#xff0c;深刻塑造着社会的每一个角落。从文化艺术的智慧火花到生命科学的精密探索&#xff0c;从工业制造的革新升级到日常生活的细致入微&…

Delphi使用TMS.MQTT开发Mqtt客户端

服务端用的是 mosquitto ,下载地址Download | Eclipse Mosquitto 安装完成后需要配置 找到安装目录:mosquitto.conf,打开后修改 allow_anonymous false(禁止匿名登录),password_file D:\Program Files\mosquitto\pwfile.example(密码存放位置) 创建新用户,安装目录…

Least-Squares Rigid Motion Using SVD——文献精读(使用 SVD 方法求解 ICP 问题)

一、文章信息与摘要 文章标题&#xff1a;Least-Squares Rigid Motion Using SVD&#xff08;使用奇异值分解的最小二乘刚性运动&#xff09; 说明本文的核心目标&#xff1a;计算对齐两组对应点的最佳拟合刚性变换的步骤 二、问题描述 假设P{p1,p2,...,pn}和Q{q1,q2,...,qn…

Feature Manipulation for DDPM based Change Detection

基于去噪扩散模型的特征操作变化检测 文章提出了一种基于去噪扩散概率模型&#xff08;DDPM&#xff09;的特征操作变化检测方法。变化检测是计算机视觉中的经典任务&#xff0c;涉及分析不同时间捕获的图像对&#xff0c;以识别场景中的重要变化。现有基于扩散模型的方法主要…

C++ 习题精选(2)

目录 1. 验证回文串2. 字符串相乘 1. 验证回文串 题目描述&#xff1a;如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。字母和数字都属于字母数字字符。给你一个字符串 s&#xff…

Vue01-vue的简介

一、Vue是什么&#xff1f; 一套用于构建用户界面的渐进式javaScript框架。 构建用户界面&#xff1a; 渐进式&#xff1a; 目前Vue的地位&#xff1a;生态完善&#xff0c;国内前端工程师必备技能。 二、Vue的特点 一个XXX.vue就是一个组件&#xff0c;封装的概念&#xff0c…

threejs的三维前端项目用THREEPlaneGeometry创建平面几何体对象的方法

在 Three.js 中&#xff0c;可以使用THREE.PlaneGeometry类来创建平面几何体对象。THREE.PlaneGeometry类用于创建一个具有指定宽度和高度的平面几何体。 艾斯视觉作为行业ui设计与前端开发服务商很高兴能在这里与你共同探讨&#xff1a;以下是一个示例代码&#xff0c;展示了如…

文心一言 VS 讯飞星火 VS chatgpt (272)-- 算法导论20.2 7题

七、试说明如果 PROTO-vEB-MINIMUM 中的第 9 行被执行&#xff0c;则proto-vEB 结构为空。如果要写代码&#xff0c;请用go语言。 文心一言&#xff1a; 在 PROTO-vEB-MINIMUM 算法中&#xff0c;我们假设你指的是一个基于位向量&#xff08;bit-vector&#xff09;的扩展二进…

OSG学习记录

学习自OpenSceneGraph Quick Start Guide的中文版&#xff0c;建议学习书&#xff08;比较全面&#xff09; OSG的内存管理机制 程序保存一个指向根节点的指针&#xff0c;不保存场景图形中其他节点的指针。根节点将直接或间接地“引用”场景图形中的所有的节点。 当应用程序…