(12)ATF BL31中断

news/发布时间2024/5/15 3:18:48

欢迎关注“安全有理”微信公众号。

安全有理

概述

系统在运行过程中的任何阶段,都有可能产生中断。在Armv8架构系统中,TEE-OS运行在安全世界的EL1,Rich-OS运行在非安全世界的EL1,而BL31则运行于EL3。想实现各种中断在三种状态下被处理的统一性和正确性,就需要确保各种状态下中断向量表以及GIC的正确配置。 在ATF的BL31阶段启用了中断,BL31主要完成了GIC驱动初始化,中断注册,中断处理,中断路由配置等功能。

GIC

中断控制器(General Interruption Controller,GIC) 模块是CPU的外设之一, 它的作用是接收来自其他外设的中断引脚输入, 然后根据中断触发模式、 中断类型优先级等设置来控制发送不同的中断信号到CPU。

GIC主要实现distributor、redistributor和cpu interface组件的初始化,包括驱动加载,中断路由,中断的参数配置等。ATF实现了GICv3和GICv2,这里只关注GICv3,源码位于drivers/arm/gic/v3,头文件位于include/drivers/arm/gicv3.h

中断属性

include/common/interrupt_props.h定义了中断属性的描述。

/* Create an interrupt property descriptor from various interrupt properties */
#define INTR_PROP_DESC(num, pri, grp, cfg) \{ \.intr_num = (num), \.intr_pri = (pri), \.intr_grp = (grp), \.intr_cfg = (cfg), \}typedef struct interrupt_prop {unsigned int intr_num:10;unsigned int intr_pri:8;unsigned int intr_grp:2;unsigned int intr_cfg:2;
} interrupt_prop_t;
  • INTR_PROP_DESC:用于描述中断属性的宏
  • intr_num:中断号
  • intr_pri:中断优先级
  • intr_grp:中断分组
  • intr_cfg:触发方式,边沿触发还是电平触发

驱动结构

gicv3_driver_data_t定义了GICv3 IP的一些属性,结构如下。

typedef unsigned int (*mpidr_hash_fn)(u_register_t mpidr);typedef struct gicv3_driver_data {uintptr_t gicd_base;uintptr_t gicr_base;const interrupt_prop_t *interrupt_props;unsigned int interrupt_props_num;unsigned int rdistif_num;uintptr_t *rdistif_base_addrs;mpidr_hash_fn mpidr_to_core_pos;
} gicv3_driver_data_t;
  • gicd_base:Distributor接口的基地址
  • gicr_base:Re-distributor接口的基地址
  • interrupt_props:安全中断及其属性
  • interrupt_props_num:interrupt_props的个数
  • rdistif_num:GIC实施的Redistributor接口个数,其等于CPU的个数或者GIC实现的CPU接口个数
  • rdistif_base_addrs:指向一个数组的指针,该数组存储了每个CPU的Redistributor接口的基地址,数组大小等于rdistif_num
  • mpidr_to_core_pos:指向一个哈希函数指针,驱动用来将MPIDR值转换为core 索引,这个索引用来访问rdistif_base_addrs数组

GIC驱动初始化

在EL3中,GICv3驱动初始化函数是gicv3_driver_init,如下:

/******************************************************************************** This function initialises the ARM GICv3 driver in EL3 with provided platform* inputs.******************************************************************************/
void __init gicv3_driver_init(const gicv3_driver_data_t *plat_driver_data)
{unsigned int gic_version;unsigned int gicv2_compat;assert(plat_driver_data != NULL);assert(plat_driver_data->gicd_base != 0U);assert(plat_driver_data->rdistif_num != 0U);assert(plat_driver_data->rdistif_base_addrs != NULL);assert(IS_IN_EL3());assert((plat_driver_data->interrupt_props_num != 0U) ?(plat_driver_data->interrupt_props != NULL) : 1);/* Check for system register support */
#ifndef __aarch64__assert((read_id_pfr1() &(ID_PFR1_GIC_MASK << ID_PFR1_GIC_SHIFT)) != 0U);
#elseassert((read_id_aa64pfr0_el1() &(ID_AA64PFR0_GIC_MASK << ID_AA64PFR0_GIC_SHIFT)) != 0U);
#endif /* !__aarch64__ */gic_version = gicd_read_pidr2(plat_driver_data->gicd_base);gic_version >>= PIDR2_ARCH_REV_SHIFT;gic_version &= PIDR2_ARCH_REV_MASK;/* Check GIC version */
#if !GIC_ENABLE_V4_EXTNassert(gic_version == ARCH_REV_GICV3);
#endif/** Find out whether the GIC supports the GICv2 compatibility mode.* The ARE_S bit resets to 0 if supported*/gicv2_compat = gicd_read_ctlr(plat_driver_data->gicd_base);gicv2_compat >>= CTLR_ARE_S_SHIFT;gicv2_compat = gicv2_compat & CTLR_ARE_S_MASK;if (plat_driver_data->gicr_base != 0U) {/** Find the base address of each implemented Redistributor interface.* The number of interfaces should be equal to the number of CPUs in the* system. The memory for saving these addresses has to be allocated by* the platform port*/gicv3_rdistif_base_addrs_probe(plat_driver_data->rdistif_base_addrs,plat_driver_data->rdistif_num,plat_driver_data->gicr_base,plat_driver_data->mpidr_to_core_pos);
#if !HW_ASSISTED_COHERENCY/** Flush the rdistif_base_addrs[] contents linked to the GICv3 driver.*/flush_dcache_range((uintptr_t)(plat_driver_data->rdistif_base_addrs),plat_driver_data->rdistif_num *sizeof(*(plat_driver_data->rdistif_base_addrs)));
#endif}gicv3_driver_data = plat_driver_data;/** The GIC driver data is initialized by the primary CPU with caches* enabled. When the secondary CPU boots up, it initializes the* GICC/GICR interface with the caches disabled. Hence flush the* driver data to ensure coherency. This is not required if the* platform has HW_ASSISTED_COHERENCY enabled.*/
#if !HW_ASSISTED_COHERENCYflush_dcache_range((uintptr_t)&gicv3_driver_data,sizeof(gicv3_driver_data));flush_dcache_range((uintptr_t)gicv3_driver_data,sizeof(*gicv3_driver_data));
#endifgicv3_check_erratas_applies(plat_driver_data->gicd_base);INFO("GICv%u with%s legacy support detected.\n", gic_version,(gicv2_compat == 0U) ? "" : "out");INFO("ARM GICv%u driver initialized in EL3\n", gic_version);
}
  • gicd_read_pidr2:获取GIC的版本信息
  • gicd_read_ctlr:判断GIC是否兼容GICv2
  • gicv3_rdistif_base_addrs_probe:初始化redistributor的寄存器基地址

GIC初始化

Distributor Interface

函数gicv3_distif_init主要初始化GIC Distributor接口,完成所有SPI中断的属性配置。

/******************************************************************************** This function initialises the GIC distributor interface based upon the data* provided by the platform while initialising the driver.******************************************************************************/
void __init gicv3_distif_init(void)
{unsigned int bitmap;assert(gicv3_driver_data != NULL);assert(gicv3_driver_data->gicd_base != 0U);assert(IS_IN_EL3());/** Clear the "enable" bits for G0/G1S/G1NS interrupts before configuring* the ARE_S bit. The Distributor might generate a system error* otherwise.*/gicd_clr_ctlr(gicv3_driver_data->gicd_base,CTLR_ENABLE_G0_BIT |CTLR_ENABLE_G1S_BIT |CTLR_ENABLE_G1NS_BIT,RWP_TRUE);/* Set the ARE_S and ARE_NS bit now that interrupts have been disabled */gicd_set_ctlr(gicv3_driver_data->gicd_base,CTLR_ARE_S_BIT | CTLR_ARE_NS_BIT, RWP_TRUE);/* Set the default attribute of all (E)SPIs */gicv3_spis_config_defaults(gicv3_driver_data->gicd_base);bitmap = gicv3_secure_spis_config_props(gicv3_driver_data->gicd_base,gicv3_driver_data->interrupt_props,gicv3_driver_data->interrupt_props_num);/* Enable the secure (E)SPIs now that they have been configured */gicd_set_ctlr(gicv3_driver_data->gicd_base, bitmap, RWP_TRUE);
}
  • gicd_clr_ctlr:首先清除所有中断分组使能位,即清除Group 0,Secure Group 1,和 Non-secure Group 1
  • gicd_set_ctlr:然后设置ARE_S和ARE_NS位,使能中断路由
  • gicv3_spis_config_defaults:接着配置所有SPI中断属性为默认值,即将SPI中断分组配置为Non-secure Group 1,优先级默认最高,电平触发
  • gicv3_secure_spis_config_props:接着配置平台提供的Secure SPI中断的属性,包括将每个SPI设置为Secure,中断分组为Group 0或Secure Group 1,触发方式,优先级,中断路由到primary cpu,最后使能该中断
  • gicd_set_ctlr:最后使能Secure SPI中断

Redistributor Interface

函数gicv3_rdistif_init初始化当前CPU(proc_num)的GIC Redistributor接口,完成所有PPI/SGI中断的属性配置。

/******************************************************************************** This function initialises the GIC Redistributor interface of the calling CPU* (identified by the 'proc_num' parameter) based upon the data provided by the* platform while initialising the driver.******************************************************************************/
void gicv3_rdistif_init(unsigned int proc_num)
{uintptr_t gicr_base;unsigned int bitmap;uint32_t ctlr;assert(gicv3_driver_data != NULL);assert(proc_num < gicv3_driver_data->rdistif_num);assert(gicv3_driver_data->rdistif_base_addrs != NULL);assert(gicv3_driver_data->gicd_base != 0U);ctlr = gicd_read_ctlr(gicv3_driver_data->gicd_base);assert((ctlr & CTLR_ARE_S_BIT) != 0U);assert(IS_IN_EL3());/* Power on redistributor */gicv3_rdistif_on(proc_num);gicr_base = gicv3_driver_data->rdistif_base_addrs[proc_num];assert(gicr_base != 0U);/* Set the default attribute of all SGIs and (E)PPIs */gicv3_ppi_sgi_config_defaults(gicr_base);bitmap = gicv3_secure_ppi_sgi_config_props(gicr_base,gicv3_driver_data->interrupt_props,gicv3_driver_data->interrupt_props_num);/* Enable interrupt groups as required, if not already */if ((ctlr & bitmap) != bitmap) {gicd_set_ctlr(gicv3_driver_data->gicd_base, bitmap, RWP_TRUE);}
}
  • gicd_read_ctlr:首先读取ARE_S位是否使能,并判断当前是否在EL3异常等级
  • gicv3_ppi_sgi_config_defaults:然后将所有SGI和PPI属性配置为默认值,即中断分组配置为Non-secure Group 1,中断优先级,电平触发
  • gicv3_secure_ppi_sgi_config_props:接着配置平台提供的Secure PPI/SGI中断属性,包括设置每个SPI/SGI中断为Secure,中断分组为Group 0或Secure Group 1,优先级,触发方式,使能中断
  • gicd_set_ctlr:最后使能中断分组

CPU Interface

同样,函数gicv3_cpuif_enable当前CPU(proc_num)的GIC CPU接口。

/******************************************************************************** This function enables the GIC CPU interface of the calling CPU using only* system register accesses.******************************************************************************/
void gicv3_cpuif_enable(unsigned int proc_num)
{uintptr_t gicr_base;u_register_t scr_el3;unsigned int icc_sre_el3;assert(gicv3_driver_data != NULL);assert(proc_num < gicv3_driver_data->rdistif_num);assert(gicv3_driver_data->rdistif_base_addrs != NULL);assert(IS_IN_EL3());/* Mark the connected core as awake */gicr_base = gicv3_driver_data->rdistif_base_addrs[proc_num];gicv3_rdistif_mark_core_awake(gicr_base);/* Disable the legacy interrupt bypass */icc_sre_el3 = ICC_SRE_DIB_BIT | ICC_SRE_DFB_BIT;/** Enable system register access for EL3 and allow lower exception* levels to configure the same for themselves. If the legacy mode is* not supported, the SRE bit is RAO/WI*/icc_sre_el3 |= (ICC_SRE_EN_BIT | ICC_SRE_SRE_BIT);write_icc_sre_el3(read_icc_sre_el3() | icc_sre_el3);scr_el3 = read_scr_el3();/** Switch to NS state to write Non secure ICC_SRE_EL1 and* ICC_SRE_EL2 registers.*/write_scr_el3(scr_el3 | SCR_NS_BIT);isb();write_icc_sre_el2(read_icc_sre_el2() | icc_sre_el3);write_icc_sre_el1(ICC_SRE_SRE_BIT);isb();/* Switch to secure state. */write_scr_el3(scr_el3 & (~SCR_NS_BIT));isb();/* Write the secure ICC_SRE_EL1 register */write_icc_sre_el1(ICC_SRE_SRE_BIT);isb();/* Program the idle priority in the PMR */write_icc_pmr_el1(GIC_PRI_MASK);/* Enable Group0 interrupts */write_icc_igrpen0_el1(IGRPEN1_EL1_ENABLE_G0_BIT);/* Enable Group1 Secure interrupts */write_icc_igrpen1_el3(read_icc_igrpen1_el3() |IGRPEN1_EL3_ENABLE_G1S_BIT);isb();/* Add DSB to ensure visibility of System register writes */dsb();
}
  • gicv3_rdistif_mark_core_awake:首先将连接的Core标记为在线,即将 GICR_WAKER.ProcessorsSleep清零,然后轮询GICR_WAKER.ChildrenASleep,直至读到0
  • write_icc_sre_el3:设置ICC_SRE_EL3寄存器中的 SRE 位,启用用对 CPU 接口寄存器的访问
  • write_scr_el3 & write_icc_sre_el2 & write_icc_sre_el1:然后切换到非安全状态,同样设置ICC_SRE_EL2和寄存器ICC_SRE_EL1中的 SRE 位
  • write_scr_el3 & write_icc_sre_el1:接着切换回到安全状态,先设置寄存器ICC_SRE_EL1中的 SRE 位
  • write_icc_pmr_el1:配置优先级mask,这里为允许所有的优先级中断
  • write_icc_igrpen0_el1:使能Group 0中断
  • write_icc_igrpen1_el3:最后使能Secure Group 1中断

GIC示例

以qemu平台为例,在BL31阶段启用GIC中断,GIC初始化函数为plat_qemu_gic_init,包括GIC驱动初始化和GIC初始化,实现如下。

void plat_qemu_gic_init(void)
{gicv3_driver_init(&qemu_gicv3_driver_data);gicv3_distif_init();gicv3_rdistif_init(plat_my_core_pos());gicv3_cpuif_enable(plat_my_core_pos());
}

GIC驱动初始化

gicv3_driver_init根据平台提供的数据初始化EL3中的GICv3驱动,qemu提供的gic驱动数据为:

static const gicv3_driver_data_t qemu_gicv3_driver_data = {.gicd_base = GICD_BASE,.gicr_base = GICR_BASE,.interrupt_props = qemu_interrupt_props,.interrupt_props_num = ARRAY_SIZE(qemu_interrupt_props),.rdistif_num = PLATFORM_CORE_COUNT,.rdistif_base_addrs = qemu_rdistif_base_addrs,.mpidr_to_core_pos = qemu_mpidr_to_core_pos
};

其中qemu定义的中断分组包括Group 0和Secure Group 1,如下:

static const interrupt_prop_t qemu_interrupt_props[] = {PLATFORM_G1S_PROPS(INTR_GROUP1S),PLATFORM_G0_PROPS(INTR_GROUP0)
};

宏展开如下,所有的Secure Group 1都是SGI中断,最高优先级,边沿触发;未定义Group 0中断。

#define PLATFORM_G1S_PROPS(grp)						\INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_0, GIC_HIGHEST_SEC_PRIORITY,	\grp, GIC_INTR_CFG_EDGE),	\INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_1, GIC_HIGHEST_SEC_PRIORITY,	\grp, GIC_INTR_CFG_EDGE),	\INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_2, GIC_HIGHEST_SEC_PRIORITY,	\grp, GIC_INTR_CFG_EDGE),	\INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_3, GIC_HIGHEST_SEC_PRIORITY,	\grp, GIC_INTR_CFG_EDGE),	\INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_4, GIC_HIGHEST_SEC_PRIORITY,	\grp, GIC_INTR_CFG_EDGE),	\INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_5, GIC_HIGHEST_SEC_PRIORITY,	\grp, GIC_INTR_CFG_EDGE),	\INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_6, GIC_HIGHEST_SEC_PRIORITY,	\grp, GIC_INTR_CFG_EDGE),	\INTR_PROP_DESC(QEMU_IRQ_SEC_SGI_7, GIC_HIGHEST_SEC_PRIORITY,	\grp, GIC_INTR_CFG_EDGE)#define PLATFORM_G0_PROPS(grp)

然后定义了一个数组,用于存储Redistributor接口的基地址。

static uintptr_t qemu_rdistif_base_addrs[PLATFORM_CORE_COUNT];

最后定义一个将MPIDR转换为core索引的函数。

static unsigned int qemu_mpidr_to_core_pos(unsigned long mpidr)
{return (unsigned int)plat_core_pos_by_mpidr(mpidr);
}

其实是调用plat_core_pos_by_mpidr函数,实现如下。

/******************************************************************************** This function implements a part of the critical interface between the psci* generic layer and the platform that allows the former to query the platform* to convert an MPIDR to a unique linear index. An error code (-1) is returned* in case the MPIDR is invalid.******************************************************************************/
int plat_core_pos_by_mpidr(u_register_t mpidr)
{unsigned int cluster_id, cpu_id;mpidr &= MPIDR_AFFINITY_MASK;if (mpidr & ~(MPIDR_CLUSTER_MASK | MPIDR_CPU_MASK))return -1;cluster_id = (mpidr >> MPIDR_AFF1_SHIFT) & MPIDR_AFFLVL_MASK;cpu_id = (mpidr >> MPIDR_AFF0_SHIFT) & MPIDR_AFFLVL_MASK;if (cluster_id >= PLATFORM_CLUSTER_COUNT)return -1;if (cpu_id >= PLATFORM_MAX_CPUS_PER_CLUSTER)return -1;return plat_qemu_calc_core_pos(mpidr);
}

core索引计算的方法为CorePos = (ClusterId * 4) + CoreId

/**  unsigned int plat_qemu_calc_core_pos(u_register_t mpidr);*  With this function: CorePos = (ClusterId * 4) + CoreId*/
func plat_qemu_calc_core_posand	x1, x0, #MPIDR_CPU_MASKand	x0, x0, #MPIDR_CLUSTER_MASKadd	x0, x1, x0, LSR #(MPIDR_AFFINITY_BITS -\PLATFORM_CPU_PER_CLUSTER_SHIFT)ret
endfunc plat_qemu_calc_core_pos

GIC初始化

Distributor Interface

调用函数gicv3_distif_init初始化GIC Distributor接口,根据平台提供的中断信息数据,完成所有SPI中断的属性配置。

gicv3_distif_init();

Redistributor Interface

调用函数gicv3_rdistif_init初始化GIC Redistributor接口,参数为plat_my_core_pos,其返回当前CPU的core索引。

gicv3_rdistif_init(plat_my_core_pos());

最终根据MPIDR返回当前CPU的core索引。

func plat_my_core_posmrs	x0, mpidr_el1b	plat_qemu_calc_core_pos
endfunc plat_my_core_pos

CPU Interface

调用函数gicv3_cpuif_enable初始化GIC CPU接口,同样参数为plat_my_core_pos()

至此,qemu平台BL31阶段完成了GIC初始化和中断配置,后续中断发生后,GIC会向相应的CPU发送中断信号。

中断管理

中断管理框架负责管理路由到 EL3 的中断,它还允许 EL3 软件配置中断的路由行为,源码位于bl31/interrupt_mgmt.c。中断管理框架定义了一个结构描述中断的相关信息:

typedef struct intr_type_desc {interrupt_type_handler_t handler;uint32_t flags;uint32_t scr_el3[2];
} intr_type_desc_t;
  • handler:中断处理程序
  • flags:低两位存储中断的路由模型。Bit[0] 存储处于安全状态时的路由模型,Bit[1] 存储处于非安全状态时的路由模型, 值0意味着中断路由到 FEL ,值1意味着中断路由到 EL3
  • scr_el3:存储路由模型的映射,如从NS进入EL3,并在安全态的EL1进行处理等等

interrupt_type_handler_t的原型声明如下:

typedef uint64_t (*interrupt_type_handler_t)(uint32_t id,uint32_t flags,void *handle,void *cookie);
  • id:保留
  • flags:目前只用bits[0],表示中断生成时较低异常等级的安全状态。1表示处于非安全状态,0表示它处于安全状态
  • handle:指向当前安全状态的cpu_context上下文

在中断初始化时需要进行中断注册,用于中断处理,函数为register_interrupt_type_handler,实现如下:

/******************************************************************************** This function registers a handler for the 'type' of interrupt specified. It* also validates the routing model specified in the 'flags' for this type of* interrupt.******************************************************************************/
int32_t register_interrupt_type_handler(uint32_t type,interrupt_type_handler_t handler,uint32_t flags)
{int32_t rc;/* Validate the 'handler' parameter */if (handler == NULL)return -EINVAL;/* Validate the 'flags' parameter */if ((flags & INTR_TYPE_FLAGS_MASK) != 0U)return -EINVAL;/* Check if a handler has already been registered */if (intr_type_descs[type].handler != NULL)return -EALREADY;rc = set_routing_model(type, flags);if (rc != 0)return rc;/* Save the handler */intr_type_descs[type].handler = handler;return 0;
}
  • 检查中断类型,及中断是否已经注册
  • set_routing_model:配置路由模型,根据flags配置SCR_EL3.IRQ和SCR_EL3.FIQ的值,这里只是暂时将其存放到上下文cpu context中,当后面执行实际的上下文切换时,再配置到实际的SCR_EL3寄存器中去,以实现中断的路由
  • 保存中断处理程序到intr_type_descs数组中

上面set_routing_model函数实现如下:

/******************************************************************************** This function validates the routing model specified in the 'flags' and* updates internal data structures to reflect the new routing model. It also* updates the copy of SCR_EL3 for each security state with the new routing* model in the 'cpu_context' structure for this cpu.******************************************************************************/
int32_t set_routing_model(uint32_t type, uint32_t flags)
{int32_t rc;rc = validate_interrupt_type(type);if (rc != 0)return rc;rc = validate_routing_model(type, flags);if (rc != 0)return rc;/* Update the routing model in internal data structures */intr_type_descs[type].flags = flags;set_scr_el3_from_rm(type, flags, SECURE);set_scr_el3_from_rm(type, flags, NON_SECURE);return 0;
}
  • validate_interrupt_type:首先校验中断类型
  • validate_routing_model:接着校验中断模型
  • intr_type_descs[type].flags = flags:变量intr_type_descs.flags用来描述安全/正常模式下SCR的设定
  • set_scr_el3_from_rm(type, flags, SECURE):设置在CPU安全状态下(SCR.NS=0)的SCR.IRQ、SCR.FIQ位
  • set_scr_el3_from_rm(type, flags, NON_SECURE):设置在CPU非安全状态下(SCR.NS=1)的SCR.IRQ、SCR.FIQ位

路由模型

Armv8架构和GIC一起设计了一套路由模型。GICv3将中断分为以下几组:

中断类型使用示例
Secure Group 0EL3中断(Secure Firmware)
Secure Group 1Secure EL1中断(Trusted OS)
Non-secure Group 1No-secure状态中断(OS或Hypervisor)

Group 0 中断始终以 FIQ 形式发出信号,而 Group 1 中断以 IRQ 或 FIQ 形式,这取决于 PE 当前的安全状态和异常等级,如下:

PE的异常等级和安全状态Group 0Group 1Group 1
SecureNon-Secure
Secure EL0/1FIQIRQFIQ
Non-secure EL0/1/2FIQFIQIRQ
EL3FIQFIQFIQ

因此,总结起来看,不考虑EL2,在BL31中的中断路由模型要求如下:

  • 如果当前PE正在EL3下执行,屏蔽所有IRQ和FIQ
  • 对于Group 0(EL3中断),总是以FIQ触发,不论在哪个异常等级执行,最终均路由到EL3处理
  • 对于Secure Group 1(Secure-EL1中断),如果PE正在Secure-EL1执行,以IRQ触发,直接在当前环境处理;如果PE正在Non-secure-EL1执行,以FIQ触发,路由到EL3,由EL3转发到Secure-EL1处理
  • 对于Non-secure Group 1(Non-secure-EL1中断),如果PE正在Non-secure-EL1执行,以IRQ触发,直接在当前环境处理;如果PE正在Secure-EL1执行,以FIQ触发,路由到EL3,由EL3转发到Non-secure-EL1处理

根据当前PE的异常等级和安全状态,GIC实现中断以哪种方式触发(FIQ或IRQ形式)。而要配置中断路由到哪个异常等级处理,需要CPU配合参与,ARMv8是通过设置SCR_EL3寄存器中irq和fiq位实现,确定中断是被路由到当前异常等级还是EL3,其中SCR寄存器的定义如下:

SCR_EL3寄存器
SCR_EL3的IRQ和FIQ位

  • FIQ,bit[2]:0表示如果当前执行状态低于EL3,则FIQ不会路由到EL3,如果当前执行状态在EL3,则FIQ不会发生,即被屏蔽;1表示无论当前执行状态在哪个异常等级,FIQ均会路由到EL3
  • IRQ,bit[2]:0表示如果当前执行状态低于EL3,则IRQ不会路由到EL3,如果当前执行状态在EL3,则IRQ不会发生,即被屏蔽;1表示无论当前执行状态在哪个异常等级,IRQ均会路由到EL3

中断处理

BL31异常处理位于bl31/aarch64/runtime_exceptions.S文件中,其定义了bl31可以处理的异常向量表,如下

.globl	runtime_exceptions.globl	sync_exception_sp_el0
.globl	irq_sp_el0
.globl	fiq_sp_el0
.globl	serror_sp_el0.globl	sync_exception_sp_elx
.globl	irq_sp_elx
.globl	fiq_sp_elx
.globl	serror_sp_elx.globl	sync_exception_aarch64
.globl	irq_aarch64
.globl	fiq_aarch64
.globl	serror_aarch64.globl	sync_exception_aarch32
.globl	irq_aarch32
.globl	fiq_aarch32
.globl	serror_aarch32

对于Armv8架构,异常向量有四张表,根据中断的不同状态,跳到相应的异常向量表中执行:

  • 使用SP_EL0,异常来自当前异常等级
  • 使用SP_ELx,异常来自当前异常等级
  • 异常来自低异常等级,并且至少一个低异常等级是AArch64状态
  • 异常来自低异常等级,并且所有低异常等级是AArch32状态

对于BL31,如果当前系统运行在EL3,则不允许再产生中断,即前两种类型异常BL31不会进行处理。ATF的BL31只实现了后面两种异常向量,以AArch64为例,又细分为四种异常类型:

  • sync_exception_aarch64:同步异常,主要是SMC调用
  • irq_aarch64:异步异常IRQ
  • fiq_aarch64:异步异常FIQ
  • serror_aarch64:系统错误SError

这里以fiq_aarch64为例说明,当系统在低异常等级产生中断FIQ信号时,进入EL3异常向量表,调用handle_interrupt_exception宏进行处理。

vector_entry fiq_aarch64apply_at_speculative_wacheck_and_unmask_eahandle_interrupt_exception fiq_aarch64
end_vector_entry fiq_aarch64

handle_interrupt_exception主要实现系统寄存器保存,切换运行栈,检查中断是否合法,获取中断处理函数指针,跳转到中断处理函数,异常返回,其宏实现如下:

	/* ---------------------------------------------------------------------* This macro handles FIQ or IRQ interrupts i.e. EL3, S-EL1 and NS* interrupts.* ---------------------------------------------------------------------*/.macro	handle_interrupt_exception label/** Save general purpose and ARMv8.3-PAuth registers (if enabled).* If Secure Cycle Counter is not disabled in MDCR_EL3 when* ARMv8.5-PMU is implemented, save PMCR_EL0 and disable Cycle Counter.* Also set the PSTATE to a known state.*/bl	prepare_el3_entry#if ENABLE_PAUTH/* Load and program APIAKey firmware key */bl	pauth_load_bl31_apiakey
#endif/* Save the EL3 system registers needed to return from this exception */mrs	x0, spsr_el3mrs	x1, elr_el3stp	x0, x1, [sp, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]/* Switch to the runtime stack i.e. SP_EL0 */ldr	x2, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]mov	x20, spmsr	spsel, #MODE_SP_EL0mov	sp, x2/** Find out whether this is a valid interrupt type.* If the interrupt controller reports a spurious interrupt then return* to where we came from.*/bl	plat_ic_get_pending_interrupt_typecmp	x0, #INTR_TYPE_INVALb.eq	interrupt_exit_\label/** Get the registered handler for this interrupt type.* A NULL return value could be 'cause of the following conditions:** a. An interrupt of a type was routed correctly but a handler for its*    type was not registered.** b. An interrupt of a type was not routed correctly so a handler for*    its type was not registered.** c. An interrupt of a type was routed correctly to EL3, but was*    deasserted before its pending state could be read. Another*    interrupt of a different type pended at the same time and its*    type was reported as pending instead. However, a handler for this*    type was not registered.** a. and b. can only happen due to a programming error. The* occurrence of c. could be beyond the control of Trusted Firmware.* It makes sense to return from this exception instead of reporting an* error.*/bl	get_interrupt_type_handlercbz	x0, interrupt_exit_\labelmov	x21, x0mov	x0, #INTR_ID_UNAVAILABLE/* Set the current security state in the 'flags' parameter */mrs	x2, scr_el3ubfx	x1, x2, #0, #1/* Restore the reference to the 'handle' i.e. SP_EL3 */mov	x2, x20/* x3 will point to a cookie (not used now) */mov	x3, xzr/* Call the interrupt type handler */blr	x21interrupt_exit_\label:/* Return from exception, possibly in a different security state */b	el3_exit.endm

在BL31中,中断处理函数分为两种:Group 0中断,由异常处理框架ehf注册和管理;Secure Group 1中断,一般是bl31为bl32接收并转发中断。

Group 0中断(EHF)

当编译使能EL3_EXCEPTION_HANDLING宏时,就启用了EL3中的异常处理。在bl31_main中会初始化EHF框架,如下:

#if EL3_EXCEPTION_HANDLINGINFO("BL31: Initialising Exception Handling Framework\n");ehf_init();
#endif

ehf_init会初始化EL3异常处理,执行中断检查,中断路由配置,注册EL3中断,如下:

/** Initialize the EL3 exception handling.*/
void __init ehf_init(void)
{unsigned int flags = 0;int ret __unused;/* Ensure EL3 interrupts are supported */assert(plat_ic_has_interrupt_type(INTR_TYPE_EL3) != 0);/** Make sure that priority water mark has enough bits to represent the* whole priority array.*/assert(exception_data.num_priorities <= (sizeof(ehf_pri_bits_t) * 8U));assert(exception_data.ehf_priorities != NULL);/** Bit 7 of GIC priority must be 0 for secure interrupts. This means* platforms must use at least 1 of the remaining 7 bits.*/assert((exception_data.pri_bits >= 1U) ||(exception_data.pri_bits < 8U));/* Route EL3 interrupts when in Non-secure. */set_interrupt_rm_flag(flags, NON_SECURE);/** Route EL3 interrupts when in secure, only when SPMC is not present* in S-EL2.*/
#if !(defined(SPD_spmd) && (SPMD_SPM_AT_SEL2 == 1))set_interrupt_rm_flag(flags, SECURE);
#endif /* !(defined(SPD_spmd) && (SPMD_SPM_AT_SEL2 == 1)) *//* Register handler for EL3 interrupts */ret = register_interrupt_type_handler(INTR_TYPE_EL3,ehf_el3_interrupt_handler, flags);assert(ret == 0);
}
  • assert:首先检查是否支持EL3中断,优先级位检查
  • set_interrupt_rm_flag(flags, NON_SECURE):设置非安全状态执行的EL3中断路由标志
  • set_interrupt_rm_flag(flags, SECURE):设置安全状态执行的EL3中断路由标志
  • register_interrupt_type_handler:注册EL3中断,中断处理函数为ehf_el3_interrupt_handler

当EL3中断触发后,跳转到中断处理函数ehf_el3_interrupt_handler,如下:

/** Top-level EL3 interrupt handler.*/
static uint64_t ehf_el3_interrupt_handler(uint32_t id, uint32_t flags,void *handle, void *cookie)
{int ret = 0;uint32_t intr_raw;unsigned int intr, pri, idx;ehf_handler_t handler;/** Top-level interrupt type handler from Interrupt Management Framework* doesn't acknowledge the interrupt; so the interrupt ID must be* invalid.*/assert(id == INTR_ID_UNAVAILABLE);/** Acknowledge interrupt. Proceed with handling only for valid interrupt* IDs. This situation may arise because of Interrupt Management* Framework identifying an EL3 interrupt, but before it's been* acknowledged here, the interrupt was either deasserted, or there was* a higher-priority interrupt of another type.*/intr_raw = plat_ic_acknowledge_interrupt();intr = plat_ic_get_interrupt_id(intr_raw);if (intr == INTR_ID_UNAVAILABLE)return 0;/* Having acknowledged the interrupt, get the running priority */pri = plat_ic_get_running_priority();/* Check EL3 interrupt priority is in secure range */assert(IS_PRI_SECURE(pri));/** Translate the priority to a descriptor index. We do this by masking* and shifting the running priority value (platform-supplied).*/idx = pri_to_idx(pri);/* Validate priority */assert(pri == IDX_TO_PRI(idx));handler = (ehf_handler_t) RAW_HANDLER(exception_data.ehf_priorities[idx].ehf_handler);if (handler == NULL) {ERROR("No EL3 exception handler for priority 0x%x\n",IDX_TO_PRI(idx));panic();}/** Call registered handler. Pass the raw interrupt value to registered* handlers.*/ret = handler(intr_raw, flags, handle, cookie);return (uint64_t) ret;
}
  • plat_ic_acknowledge_interrupt:首先是获取中断控制器(IC)中挂起的优先级最高的中断
  • plat_ic_get_interrupt_id:获取最高优先级的中断ID,如果ID无效说明有其他优先级中断正在处理,直接返回
  • plat_ic_get_running_priority:应答中断,获取运行优先级
  • RAW_HANDLER:获取中断的处理程序
  • handler:调用注册的处理程序,进行中断处理

上面的提到handler是提前注册的处理程序,调用接口ehf_register_priority_handler根据优先级注册到ehf中,如下:

/** Register a handler at the supplied priority. Registration is allowed only if* a handler hasn't been registered before, or one wasn't provided at build* time. The priority for which the handler is being registered must also accord* with the platform-supplied data.*/
void ehf_register_priority_handler(unsigned int pri, ehf_handler_t handler)
{unsigned int idx;/* Sanity check for handler */assert(handler != NULL);/* Handler ought to be 4-byte aligned */assert((((uintptr_t) handler) & 3U) == 0U);/* Ensure we register for valid priority */idx = pri_to_idx(pri);assert(idx < exception_data.num_priorities);assert(IDX_TO_PRI(idx) == pri);/* Return failure if a handler was already registered */if (exception_data.ehf_priorities[idx].ehf_handler != EHF_NO_HANDLER_) {ERROR("Handler already registered for priority 0x%x\n", pri);panic();}/** Install handler, and retain the valid bit. We assume that the handler* is 4-byte aligned, which is usually the case.*/exception_data.ehf_priorities[idx].ehf_handler =(((uintptr_t) handler) | EHF_PRI_VALID_);EHF_LOG("register pri=0x%x handler=%p\n", pri, handler);
}

当前SDEI在初始化时,会注册两种优先级的EHF中断,如下:

/* SDEI dispatcher initialisation */
void sdei_init(void)
{plat_sdei_setup();sdei_class_init(SDEI_CRITICAL);sdei_class_init(SDEI_NORMAL);/* Register priority level handlers */ehf_register_priority_handler(PLAT_SDEI_CRITICAL_PRI,sdei_intr_handler);ehf_register_priority_handler(PLAT_SDEI_NORMAL_PRI,sdei_intr_handler);
}

Secure Group 1中断

以optee为例,当optee初始化完成后,通过SMC(TEESMC_OPTEED_RETURN_ENTRY_DONE)返回到BL31,会执行opteed_smc_handler函数,然后注册了一个Secure-EL1中断处理函数,如下:

static uintptr_t opteed_smc_handler(uint32_t smc_fid,u_register_t x1,u_register_t x2,u_register_t x3,u_register_t x4,void *cookie,void *handle,u_register_t flags)
{cpu_context_t *ns_cpu_context;uint32_t linear_id = plat_my_core_pos();optee_context_t *optee_ctx = &opteed_sp_context[linear_id];uint64_t rc;/*.....*//** Returning from OPTEE*/switch (smc_fid) {/** OPTEE has finished initialising itself after a cold boot*/case TEESMC_OPTEED_RETURN_ENTRY_DONE:/** Stash the OPTEE entry points information. This is done* only once on the primary cpu*/assert(optee_vector_table == NULL);optee_vector_table = (optee_vectors_t *) x1;if (optee_vector_table) {set_optee_pstate(optee_ctx->state, OPTEE_PSTATE_ON);/** OPTEE has been successfully initialized.* Register power management hooks with PSCI*/psci_register_spd_pm_hook(&opteed_pm);/** Register an interrupt handler for S-EL1 interrupts* when generated during code executing in the* non-secure state.*/flags = 0;set_interrupt_rm_flag(flags, NON_SECURE);rc = register_interrupt_type_handler(INTR_TYPE_S_EL1,opteed_sel1_interrupt_handler,flags);if (rc)panic();}/** OPTEE reports completion. The OPTEED must have initiated* the original request through a synchronous entry into* OPTEE. Jump back to the original C runtime context.*/opteed_synchronous_sp_exit(optee_ctx, x1);break;/** These function IDs is used only by OP-TEE to indicate it has* finished:* 1. turning itself on in response to an earlier psci*    cpu_on request* 2. resuming itself after an earlier psci cpu_suspend*    request.*/case TEESMC_OPTEED_RETURN_ON_DONE:case TEESMC_OPTEED_RETURN_RESUME_DONE:/* .... */default:panic();}
}
  • set_interrupt_rm_flag(flags, NON_SECURE):配置在non-secure状态执行时的中断路由标志
  • register_interrupt_type_handler:注册Secure-EL1中断处理程序,即当PE在non-secure状态执行时,触发了S-EL1中断的处理程序

opteed_sel1_interrupt_handler是Secure-EL1的中断处理函数,实现如下:

/******************************************************************************** This function is the handler registered for S-EL1 interrupts by the* OPTEED. It validates the interrupt and upon success arranges entry into* the OPTEE at 'optee_fiq_entry()' for handling the interrupt.******************************************************************************/
static uint64_t opteed_sel1_interrupt_handler(uint32_t id,uint32_t flags,void *handle,void *cookie)
{uint32_t linear_id;optee_context_t *optee_ctx;/* Check the security state when the exception was generated */assert(get_interrupt_src_ss(flags) == NON_SECURE);/* Sanity check the pointer to this cpu's context */assert(handle == cm_get_context(NON_SECURE));/* Save the non-secure context before entering the OPTEE */cm_el1_sysregs_context_save(NON_SECURE);/* Get a reference to this cpu's OPTEE context */linear_id = plat_my_core_pos();optee_ctx = &opteed_sp_context[linear_id];assert(&optee_ctx->cpu_ctx == cm_get_context(SECURE));cm_set_elr_el3(SECURE, (uint64_t)&optee_vector_table->fiq_entry);cm_el1_sysregs_context_restore(SECURE);cm_set_next_eret_context(SECURE);/** Tell the OPTEE that it has to handle an FIQ (synchronously).* Also the instruction in normal world where the interrupt was* generated is passed for debugging purposes. It is safe to* retrieve this address from ELR_EL3 as the secure context will* not take effect until el3_exit().*/SMC_RET1(&optee_ctx->cpu_ctx, read_elr_el3());
}
  • assert:首先检查中断生成时的安全状态是否是非安全,检查CPU上下文指针
  • cm_el1_sysregs_context_save(NON_SECURE):保存非安全状态的系统寄存器上下文
  • cm_get_context(SECURE)):获取optee的执行上下文
  • cm_set_elr_el3:配置ELR_EL3寄存器,中断入口为optee_fiq_entry
  • cm_el1_sysregs_context_restore:恢复Secure状态系统寄存器上下文
  • cm_set_next_eret_context:设置异常返回的上下文为Secure
  • SMC_RET1:执行SMC,进入optee的中断处理程序,处理FIQ

中断示例

以optee+atf+linux系统架构为例,即运行在Secure-EL1的optee,运行在Non-secure-ELl1的linux和运行在EL3的bl31,简单介绍一下两种需要路由转发的中断处理流程。

  • PE在Non-Secure执行,来了S-EL1中断,以FIQ信号触发,路由到EL3,由EL3转发到Secure-EL1处理
  • PE在Secure-EL1执行,来了Non-Secure中断,以FIQ信号触发,主动切换到EL3,由EL3进入Non-secure-EL1,中断以IRQ信号重新触发,进行处理

PE在Non-Secure执行,触发了S-EL1中断

interrupt_handling1

  1. 当PE在非安全世界执行时,来了一个S-EL1中断,此中断在非安全世界以FIQ触发,将被路由到EL3
  2. PE读取VBAR_EL3寄存器获取EL3异常向量表基地址,并跳转到bl31的异常向量表
  3. 在bl31异常处理程序中,调用宏handle_interrupt_exception查询注册的中断处理函数,即opteed_sel1_interrupt_handler,该函数将上下文切换成Secure-EL1,并配置上下文的入口函数optee_vector_table->fiq_entry
  4. 执行el3_exit退出EL3,使用获取到的CPU运行上下文进入optee,并从fiq_entry指定入口函数执行,开始对FIQ中断进行处理
  5. 当中断在optee中处理完成后,通过执行SMC(TEESMC_OPTEED_RETURN_FIQ_DONE) 返回到bl31, 恢复non-secure上下文
  6. 执行el3_exit,返回非安全世界,恢复被中断的non-secure上下文,继续执行其他代码

PE在Secure-EL1执行,触发了Non-Secure中断

interrupt_handling2

  1. 当PE在Secure-EL1执行,来了Non-Secure中断,此中断在安全世界以FIQ触发
  2. optee读取FIQ异常向量表,但是未对该中断执行应答操作,主动调用SMC(TEESMC_OPTEED_RETURN_CALL_DONE),切换到BL31,进入opteed_smc_handler函数,保存安全状态,并恢复non-secure上下文
  3. 执行el3_exit退出EL3,进入Non-Secure世界,此时中断重新以IRQ方式触发,进入Non-secure-EL中断向量表,执行相应的中断处理函数
  4. 当中断处理完成后,执行SMC(OPTEE_SMC_CALL_RETURN_FROM_RPC)调用,再次进入bl31,进入opteed_smc_handler函数,保存Non-Secure状态,并恢复Secure上下文
  5. 再次执行el3_exit退出EL3,进入Secure世界,恢复optee的执行

参考

  1. ATF bl31中断分析
  2. Arm通用中断控制器GICv3和GICv4
  3. 基于optee的可信操作系统(五) optee中断处理

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

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

相关文章

一、初始 Vue

1、Vue 1.1 Vue简介 1.1.1 Vue.js 是什么 Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第…

【Redis】Redis的数据分布算法

一共有五种算法&#xff0c;分别为&#xff1a;哈希算法、一致性哈希算法、带有限负载的一致性哈希算法、虚拟节点一致性哈希算法、虚拟槽分区 哈希算法 思想&#xff1a;根据某个key的值或者key 的哈希值与当前可用的 master 节点数取模&#xff0c;根据取模的值获取具体的服…

ELK入门(三)-Kibana

Kibana Kibana是一个开源的分析与可视化平台&#xff0c;设计出来用于和Elasticsearch一起使用的。你可以用kibana搜索、查看存放在Elasticsearch中的数据。Kibana与Elasticsearch的交互方式是各种不同的图表、表格、地图等&#xff0c;直观的展示数据&#xff0c;从而达到高级…

The method toList() is undefined for the type Stream

The method toList() is undefined for the type Stream &#xff08;JDK16&#xff09; default List<T> toList() { return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray()))); }

LeetCode.590. N 叉树的后序遍历

题目 590. N 叉树的后序遍历 分析 我们之前有做过LeetCode的 145. 二叉树的后序遍历&#xff0c;其实对于 N 叉树来说和二叉树的思路是一模一样的。 二叉树的后序遍历是【左 右 根】 N叉树的后序遍历顺序是【孩子 根】&#xff0c;你可以把二叉树的【左 右 根】想象成【孩子…

CTF--Web安全--SQL注入之‘绕过方法’

一、什么是绕过注入 众所周知&#xff0c;SQL注入是利用源码中的漏洞进行注入的&#xff0c;但是有攻击手段&#xff0c;就会有防御手段。很多题目和网站会在源码中设置反SQL注入的机制。SQL注入中常用的命令&#xff0c;符号&#xff0c;甚至空格&#xff0c;会在反SQL机制中…

IP地理位置查询定位:技术原理与实际应用

在互联网时代&#xff0c;IP地址是连接世界的桥梁&#xff0c;而了解IP地址的地理位置对于网络管理、个性化服务以及安全监控都至关重要。IP数据云将深入探讨IP地理位置查询定位的技术原理、实际应用场景以及相关的隐私保护问题&#xff0c;旨在为读者提供全面了解和应用该技术…

C语言第二十八弹---整数在内存中的存储

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 目录 1、整数在内存中的存储 2、大小端字节序和字节序 2.1、什么是大小端&#xff1f; 2.2、为什么有大小端? 2.3、练习 2.3.1、练习1 2.3.2、练习2 2.…

网络爬虫基础(上)

1. 爬虫的基本原理 爬虫就是在网页上爬行的蜘蛛&#xff0c;每爬到一个节点就能够访问该网页的信息&#xff0c;所以又称为网络蜘蛛&#xff1b; 网络爬虫就是自动化从网页上获取信息、提取信息和保存信息的过程&#xff1b; 2. URL的组成部分 URL全称为Uniform Resource L…

【Vuforia+Unity】AR04-地面、桌面平面识别功能

不论你是否曾有过相关经验&#xff0c;只要跟随本文的步骤&#xff0c;你就可以成功地创建你自己的AR应用。 官方教程Ground Plane in Unity | Vuforia Library 这个功能很棒&#xff0c;但是要求也很不友好&#xff0c;只能支持部分移动设备&#xff0c;具体清单如下&#xf…

跟着cherno手搓游戏引擎【25】封装2DRenderer,封装shader传参,自定义Texture

封装2DRenderer&#xff1a; Renderer.h: #include"ytpch.h" #include"Renderer.h" #include <Platform/OpenGL/OpenGLShader.h> #include"Renderer2D.h" namespace YOTO {Renderer::SceneData* Renderer::m_SceneData new Renderer::S…

深度学习基础——YOLOv5目标检测

YOLO系列算法属于基于回归的单阶段目标检测算法&#xff0c;它将定位与分类两个任务整合成一个任务&#xff0c;直接通过CNN网络提取全局信息并预测图片上的目标。给目标检测算法提供了新的解决方案&#xff0c;并且图片检测速度准确率与召回率达到实时检测的要求。其中YOLOv1、…

css知识:盒模型盒子塌陷BFC

1. css盒模型 标准盒子模型&#xff0c;content-box 设置宽度即content的宽度 width content 总宽度content&#xff08;width设定值&#xff09; padding border IE/怪异盒子模型&#xff0c;border-box width content border padding 总宽度 width设定值 2. 如何…

openai chatGPT 原理通俗介绍

引言 近年来&#xff0c;随着深度学习技术的不断发展&#xff0c;自然语言处理&#xff08;NLP&#xff09;领域取得了长足的进步。ChatGPT&#xff08;Generative Pre-trained Transformer&#xff09;作为一种先进的语言生成模型&#xff0c;在各类对话系统和智能助手中得到…

Ansible yum模块 主要用于软件安装

目录 选项 实例 安装一个tree实例卸载一个 tree 选项 name   #所安装的包的名称 state  #present—>安装&#xff0c; latest—>安装最新的, absent—> 卸载软件。 update_cache  #强制更新yum的缓存 conf_file  #指定远程yum安装时所依赖的配置文件&…

Linux常见的指令

目录 01. ls 指令02. pwd命令03. cd 指令04. touch指令05.mkdir指令&#xff08;重要&#xff09;&#xff1a;06.rmdir指令 && rm 指令&#xff08;重要&#xff09;&#xff1a;07.man指令&#xff08;重要&#xff09;&#xff1a;08.cp指令&#xff08;重要&#x…

学习笔记-Git

Git 问题一描述解决方法注意事项 问题一 描述 在commit和push的时候因为网络太慢了中途强行关闭了进程&#xff0c;而push的内容因为文件过大导致无法正常push 按照原本的流程在push的时候会提示失败&#xff0c;并且需要在解决了大文件之后重新push 而因为中途中断了&#x…

flink state原理,TTL,状态后端,数据倾斜一文全

flink state原理 1. 状态、状态后端、Checkpoint 三者之间的区别及关系&#xff1f;2 算子状态与键控状态的区别2.1 算子状态2.2 键控状态2.3 算子状态api2.4 键控状态api 3 HashMapStateBackend 状态后端4 EmBeddedRocksDbStateBackend 状态后端5 状态数据结构介绍5.1 算子状态…

计算机网络-局域网和城域网(一)

1.什么是局域网&#xff1f; 单一机构所拥有的专用计算机网络&#xff0c;中等规模地理范围&#xff0c;实现多种设备互联、信息交换和资源共享。 2.逻辑链路控制LLC&#xff1a; 目的是屏蔽不同的介质访问控制方法&#xff0c;以向高层&#xff08;网络层&#xff09;提供统…

并发编程入门指南

文章目录 并发编程进程和线程的区别并发和并行的区别创建线程的方式线程之间的状态&#xff0c;状态之间的转换新建三个线程&#xff0c;如何保证按顺序执行wait方法和sleep的区别如何停止一个正在运行的线程synchronized关键字底层原理Monitor属于重量级锁&#xff0c;了解过锁…
推荐文章