113-新的容器-fd_set

本文介绍了fd_set容器的基本概念及其在Linux系统中的应用。fd_set容器主要用于存放文件描述符,通过整型数组实现,并利用特定宏操作集合。文章还提供了一个简单的实验示例,演示如何使用fd_set容器对文件描述符进行添加、删除和查询。

差不多在几个月前,我们学过一个装信号的容器 sigset,它通过 bit 位来保存数据。而今天要学习的 fd_set 容器,是用来装描述符的。它和 sigset 有着异曲同工之妙。

1. fd_set 容器

容器,说的通俗点就是用来放东西的,而且可以放很多很多。就像你家的柜子,里面有很多小隔间,一号隔间放爸爸的东西,二号隔间放妈妈的东西,三号隔间放你的东西。

相比我们的 fd_set 容器,它就有更多的小隔间,默认情况下有 1024 个。分别是 0 号,1 号,…… ,1023 号隔间。

对于 fd_set 来说,0 号隔间只能放 0 号描述符,1 号隔间只能放 1 号描述符……

实际上,fd_set 类型是利用整型数组实现的,每个元素中的每个 bit 位被置 1 就表示该位置保存了描述符,如果为 0 就表示没有该描述符。

图 1 提供了一种实现方式:


这里写图片描述
图1 一种 fd_set 的实现

在图 1 中,数组类型是 char,占 8 个 bit,可以看到,在第 5 位和第 14 位中 bit 位被置 1,说明这个 fd_set 中保存了 5 号描述符和 14 号描述符。

2. 操作 fd_set 容器

要使用 fd_set 容器,首先得定义一个容器出来,比如 fd_set st,这样就定义了一个容器,名为 st.

OS 提供了四个宏或函数,来帮助我们对 fd_set 容器进行增删查和初始化。

#include <sys/select.h>
// 判断描述符 fd 是否在集合中
int FD_ISSET(int fd, fd_set *fdset);

// 将描述符 fd 从集合中删除
int FD_CLR(int fd, fd_set *fdset);

// 将描述符 fd 添加到集合中
int FD_SET(int fd, fd_set *fdset);

// 将集合清空(所有 bit 置 0)
int FD_ZERO(fd_set *fdset);

在 linux 中,这些操作是用宏来实现的。

3. 实验

程序 fdset.c 文件主要功能是先定义一个名为 st 的容器然后打印其内容。接下来主要对前 16 个描述符进行操作和打印。

  • 清空 st,打印容器的值。
  • 将所有奇数描述符保存到 st 中,打印容器的值。
  • 将 3 号描述符从 st 中删除,打印容器的值。
  • 清空 st,打印容器的值。

3.1 代码

#include <unistd.h>
#include <sys/select.h>

// 打印前 16 个小隔间
void printset(const fd_set *st) {
  int i;
  for (i = 0; i < 16; ++i) {
    if (FD_ISSET(i, st))
      putchar('1');
    else
      putchar('0');
  }
  puts("");
}

int main() {
  int i;
  fd_set st; 
  // 未初始化的集合
  puts("uninitial");
  printset(&st);

  // 清空集合
  puts("zeros");
  FD_ZERO(&st);
  printset(&st);

  // 将奇数描述符保存到集合
  puts("set odd");
  for (i = 0; i < 16; ++i) {
    if (i%2) {
      FD_SET(i, &st);
    }   
  }
  printset(&st);

  // 将 3 号描述符删除
  puts("clear 3");
  FD_CLR(3, &st);
  printset(&st);

  // 清空集合
  puts("zeros");
  FD_ZERO(&st);
  printset(&st);

  return 0;
}

3.2 编译和运行

  • 编译和运行
$ gcc fdset.c -o fdset
$ ./fdset
  • 运行结果


这里写图片描述
图2 运行结果

4. 总结

  • 知道 fd_set 是用来装描述符的
  • 掌握 fd_set 的操作

实际上,用来装描述符只是一种常规的做法,你当然可以在程序中用作别的用途。

在我现在的代码基础上改 # -*- coding: utf-8 -*- # Copyright (c) 2013, Eduard Broecker # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, are permitted provided that # the following conditions are met: # # Redistributions of source code must retain the above copyright notice, this list of conditions and the # following disclaimer. # Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the # following disclaimer in the documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. # # this script exports arxml-files from a canmatrix-object # arxml-files are the can-matrix-definitions and a lot more in AUTOSAR-Context # currently Support for Autosar 3.2 and 4.0-4.3 is planned # AUTOSAR 4.2.2 is partial support -> 2024/05/20 import copy import decimal import logging import re import typing from builtins import * import lxml.etree import io from arxml_base import * # for typing only _Element = lxml.etree._Element option = {} # option = { # "gatewayArPackegeName" : "RouteGateway", # "gatewayName" : "FLZCU", # "ecuInstanceName" : "FLZCU" # } class gatewayDoc(Arxmldoc): def __init__(self, opt: dict | None = None): super().__init__() # instance-level options self.ecuInstance = None self.option: dict = {} if opt is None else dict(opt) # internal caches / holders self.gatewayElements: _Element | None = None self.ecuInstancePath: str | None = None self.FibexElements: _Element | None = None self.GwFibexElementTable: list[str] = [] self.GwGatewayTable: dict[str, _Element] = {} self.GwiPduMappingTable: dict[str, _Element] = {} self.GwiSignalMappingTable: dict[str, _Element] = {} # 添加 IPDUGroup 包引用 self.ipdu_group_package: _Element | None = None self.ipdu_group_elements: _Element | None = None def init(self, **options) -> None: # initialize parent and merge options super().init(**options) # merge provided options into instance option dict self.option.update(options) # ensure defaults self.option.setdefault("gatewayArPackegeName", "RouteGateway") self.option.setdefault("gatewayName", "FLZCU") self.option.setdefault("ecuInstanceName", "FLZCU") # create System package and FIBEX elements ar_package = arxml_create_sub_element(self.top_level_packages, "AR-PACKAGE") arxml_UpdateElemUUID(ar_package) arxml_create_sub_element(ar_package, "SHORT-NAME", "System") elements = arxml_create_sub_element(ar_package, "ELEMENTS") system = arxml_create_sub_element(elements, "SYSTEM") arxml_UpdateElemUUID(system) arxml_create_sub_element(system, "SHORT-NAME", "System") arxml_create_sub_element(system, "CATEGORY", "SYSTEM_EXTRACT") self.FibexElements = arxml_create_sub_element(system, "FIBEX-ELEMENTS") # add ECU-INSTANCE fibex element fibexElemCond = arxml_create_sub_element(self.FibexElements, "FIBEX-ELEMENT-REF-CONDITIONAL") fibexElemRef = arxml_create_sub_element(fibexElemCond, "FIBEX-ELEMENT-REF") fibexElemRef.set("DEST", "ECU-INSTANCE") self.ecuInstancePath = "/ECU/" + self.option["ecuInstanceName"] fibexElemRef.text = self.ecuInstancePath # add gateway package ar_package = arxml_create_sub_element(self.top_level_packages, "AR-PACKAGE") arxml_UpdateElemUUID(ar_package) arxml_create_sub_element(ar_package, "SHORT-NAME", self.option["gatewayArPackegeName"]) self.gatewayElements = arxml_create_sub_element(ar_package, "ELEMENTS") # 创建 IPDUGroup 包(只创建一次) self.ipdu_group_package = arxml_create_sub_element(self.top_level_packages, "AR-PACKAGE") arxml_UpdateElemUUID(self.ipdu_group_package) arxml_create_sub_element(self.ipdu_group_package, "SHORT-NAME", "IPDUGroup") self.ipdu_group_elements = arxml_create_sub_element(self.ipdu_group_package, "ELEMENTS") return def addEcuInstance(self, **options) -> None: """ 创建完整的 ECU package 和 ECU-INSTANCE,包含所有必要的元素和属性 Args: options: 可选参数,用于覆盖默认设置 """ # 更新选项 self.option.update(options) # 创建 ECU package ar_package = arxml_create_sub_element(self.top_level_packages, "AR-PACKAGE") arxml_UpdateElemUUID(ar_package) arxml_create_sub_element(ar_package, "SHORT-NAME", "ECU") # 创建 ELEMENTS elements = arxml_create_sub_element(ar_package, "ELEMENTS") # 创建 ECU-INSTANCE ecuInstance = arxml_create_sub_element(elements, "ECU-INSTANCE") arxml_UpdateElemUUID(ecuInstance) arxml_create_sub_element(ecuInstance, "SHORT-NAME", self.option["ecuInstanceName"]) # 添加 CATEGORY arxml_create_sub_element(ecuInstance, "CATEGORY", "WIRED") # 添加 ASSOCIATED-COM-I-PDU-GROUP-REFS (空容器,可以在后续添加引用) associated_refs = arxml_create_sub_element(ecuInstance, "ASSOCIATED-COM-I-PDU-GROUP-REFS") # 添加 CONNECTORS (空容器,可以在后续添加连接器) connectors = arxml_create_sub_element(ecuInstance, "CONNECTORS") # 保存 ECU instance 以便后续使用 self.ecuInstance = ecuInstance self.ecuPackage = ar_package return def add_ecu_connector(self, connector_name, controller_ref, frame_ports=None, pdu_ports=None): """ 为ECU-INSTANCE添加通信连接器 Args: connector_name: 连接器名称,例如"CN_TestCAN1" controller_ref: 控制器引用路径,例如"/ECU/MyECU/CT_TestCAN1" frame_ports: 帧端口列表,每个元素是字典,包含name和direction pdu_ports: PDU端口列表,每个元素是字典,包含name和direction """ if not hasattr(self, 'ecuInstance') or self.ecuInstance is None: arxml_Abort("ECU-INSTANCE not found") return # 获取或创建CONNECTORS元素 connectors = self.ecuInstance.find("CONNECTORS") if connectors is None: connectors = arxml_create_sub_element(self.ecuInstance, "CONNECTORS") # 创建CAN-COMMUNICATION-CONNECTOR can_connector = arxml_create_sub_element(connectors, "CAN-COMMUNICATION-CONNECTOR") arxml_create_sub_element(can_connector, "SHORT-NAME", connector_name) # 添加COMM-CONTROLLER-REF controller_ref_elem = arxml_create_sub_element(can_connector, "COMM-CONTROLLER-REF", controller_ref) controller_ref_elem.set("DEST", "CAN-COMMUNICATION-CONTROLLER") # 添加ECU-COMM-PORT-INSTANCES ecu_comm_ports = arxml_create_sub_element(can_connector, "ECU-COMM-PORT-INSTANCES") # 添加帧端口 if frame_ports: for port in frame_ports: frame_port = arxml_create_sub_element(ecu_comm_ports, "FRAME-PORT") arxml_create_sub_element(frame_port, "SHORT-NAME", port['name']) arxml_create_sub_element(frame_port, "COMMUNICATION-DIRECTION", port['direction']) # 添加PDU端口 if pdu_ports: for port in pdu_ports: pdu_port = arxml_create_sub_element(ecu_comm_ports, "I-PDU-PORT") arxml_create_sub_element(pdu_port, "SHORT-NAME", port['name']) arxml_create_sub_element(pdu_port, "COMMUNICATION-DIRECTION", port['direction']) def add_associated_pdu_group_ref(self, pdu_group_ref): """ 为ECU-INSTANCE添加关联的PDU组引用 Args: pdu_group_ref: PDU组引用路径,例如"/IPDUGroup/MyECU_oTestCAN1_Tx" """ if not hasattr(self, 'ecuInstance') or self.ecuInstance is None: arxml_Abort("ECU-INSTANCE not found") return # 获取或创建ASSOCIATED-COM-I-PDU-GROUP-REFS元素 associated_refs = self.ecuInstance.find("ASSOCIATED-COM-I-PDU-GROUP-REFS") if associated_refs is None: associated_refs = arxml_create_sub_element(self.ecuInstance, "ASSOCIATED-COM-I-PDU-GROUP-REFS") # 添加引用 group_ref = arxml_create_sub_element(associated_refs, "ASSOCIATED-COM-I-PDU-GROUP-REF", pdu_group_ref) group_ref.set("DEST", "I-SIGNAL-I-PDU-GROUP") def addGwFibexElement(self, name: str) -> None: if self.FibexElements is None: arxml_Abort("FibexElements is None") return if name in self.GwFibexElementTable: # arxml_info(name + "is in GwFibexElementTable already") return fibexElemCond = arxml_create_sub_element(self.FibexElements, "FIBEX-ELEMENT-REF-CONDITIONAL") fibexElemRef = arxml_create_sub_element(fibexElemCond, "FIBEX-ELEMENT-REF") fibexElemRef.set("DEST", "GATEWAY") pack = self.option.get("gatewayArPackegeName", "RouteGateway") fibexElemRef.text = f"/{pack}/{name}" self.GwFibexElementTable.append(name) def addGwIPduRoutingPath(self, name: str) -> _Element | None: if self.gatewayElements is None: arxml_Abort("gatewayElements is None") return None if name in self.GwiPduMappingTable: iPduMappings = self.GwiPduMappingTable[name] else: if name in self.GwGatewayTable: gateway = self.GwGatewayTable[name] else: gateway = arxml_create_sub_element(self.gatewayElements, 'GATEWAY') arxml_UpdateElemUUID(gateway) arxml_create_sub_element(gateway, 'SHORT-NAME', name) ecuRef = arxml_create_sub_element(gateway, 'ECU-REF', self.ecuInstancePath) ecuRef.set("DEST","ECU-INSTANCE") self.GwGatewayTable[name] = gateway iPduMappings = arxml_create_sub_element(gateway, 'I-PDU-MAPPINGS') self.GwiPduMappingTable[name] = iPduMappings return iPduMappings def addGwISignaloutingPath(self, name: str) -> _Element | None: if self.gatewayElements is None: arxml_Abort("gatewayElements is None") return None if name in self.GwiSignalMappingTable: ISignalMappings = self.GwiSignalMappingTable[name] else: if name in self.GwGatewayTable: gateway = self.GwGatewayTable[name] else: gateway = arxml_create_sub_element(self.gatewayElements, 'GATEWAY') arxml_UpdateElemUUID(gateway) arxml_create_sub_element(gateway, 'SHORT-NAME', name) ecuRef = arxml_create_sub_element(gateway, 'ECU-REF', self.ecuInstancePath) ecuRef.set("DEST","ECU-INSTANCE") self.GwGatewayTable[name] = gateway ISignalMappings = arxml_create_sub_element(gateway, 'SIGNAL-MAPPINGS') self.GwiSignalMappingTable[name] = ISignalMappings return ISignalMappings def AddPduMapping(self, RoutingPathName: str, srcPduRefText: str, tagPduRefText: str) -> None: self.addGwFibexElement(RoutingPathName) iPduMappings = self.addGwIPduRoutingPath(RoutingPathName) if iPduMappings is None: arxml_Abort("iPduMappings is None") return mapping = arxml_create_sub_element(iPduMappings, "I-PDU-MAPPING") srcIpduRef = arxml_create_sub_element(mapping, "SOURCE-I-PDU-REF", srcPduRefText) srcIpduRef.set("DEST", "PDU-TRIGGERING") tagIpdu = arxml_create_sub_element(mapping, "TARGET-I-PDU") tagIpduRef = arxml_create_sub_element(tagIpdu, "TARGET-I-PDU-REF", tagPduRefText) tagIpduRef.set("DEST", "PDU-TRIGGERING") def AddSignalMapping(self, RoutingPathName: str, srcSignalRefText: str, tagSignalRefText: str) -> None: self.addGwFibexElement(RoutingPathName) ISignalMappings = self.addGwISignaloutingPath(RoutingPathName) if ISignalMappings is None: arxml_Abort("ISignalMappings is None") return mapping = arxml_create_sub_element(ISignalMappings, "I-SIGNAL-MAPPING") srcISignalRef = arxml_create_sub_element(mapping, "SOURCE-SIGNAL-REF", srcSignalRefText) srcISignalRef.set("DEST", "I-SIGNAL-TRIGGERING") tagISignalRef = arxml_create_sub_element(mapping, "TARGET-SIGNAL-REF", tagSignalRefText) tagISignalRef.set("DEST", "I-SIGNAL-TRIGGERING") def AddClusterStructure(self, cluster_name="TestCAN1", channel_name="CHNL"): """ 创建 Cluster 基础结构,并返回 CAN-PHYSICAL-CHANNEL 元素 """ # 1. 创建 Cluster AR-PACKAGE ar_package_cluster = arxml_create_sub_element(self.top_level_packages, "AR-PACKAGE") arxml_UpdateElemUUID(ar_package_cluster) arxml_create_sub_element(ar_package_cluster, "SHORT-NAME", "Cluster") # 2. ELEMENTS cluster_elements = arxml_create_sub_element(ar_package_cluster, "ELEMENTS") # 3. CAN-CLUSTER can_cluster = arxml_create_sub_element(cluster_elements, "CAN-CLUSTER") arxml_UpdateElemUUID(can_cluster) arxml_create_sub_element(can_cluster, "SHORT-NAME", cluster_name) # 4. CAN-CLUSTER-VARIANTS -> CAN-CLUSTER-CONDITIONAL cluster_variants = arxml_create_sub_element(can_cluster, "CAN-CLUSTER-VARIANTS") cluster_conditional = arxml_create_sub_element(cluster_variants, "CAN-CLUSTER-CONDITIONAL") arxml_create_sub_element(cluster_conditional, "BAUDRATE", "500000") # 5. PHYSICAL-CHANNELS -> CAN-PHYSICAL-CHANNEL physical_channels = arxml_create_sub_element(cluster_conditional, "PHYSICAL-CHANNELS") physical_channel = arxml_create_sub_element(physical_channels, "CAN-PHYSICAL-CHANNEL") arxml_UpdateElemUUID(physical_channel) arxml_create_sub_element(physical_channel, "SHORT-NAME", channel_name) arxml_create_sub_element(cluster_conditional, "PROTOCOL-NAME", "CAN") arxml_create_sub_element(cluster_conditional, "CAN-FD-BAUDRATE", "500000") return physical_channel def create_ipdu_group_package(self, group_name: str, communication_direction: str, pdu_refs: list[str]) -> _Element: """ 在 IPDUGroup 包中创建 I-SIGNAL-I-PDU-GROUP 元素 Args: group_name: I-PDU组的名称,例如"FLZCU_oEEA_5_1_Message_List_EP_CANFD_V1_0_20250825_Rx" communication_direction: 通信方向,例如"IN"或"OUT" pdu_refs: I-SIGNAL-I-PDU引用路径列表 Returns: 创建的I-SIGNAL-I-PDU-GROUP元素 """ if self.ipdu_group_elements is None: arxml_Abort("IPDUGroup elements not initialized") return None # 创建I-SIGNAL-I-PDU-GROUP ipdu_group = arxml_create_sub_element(self.ipdu_group_elements, "I-SIGNAL-I-PDU-GROUP") arxml_UpdateElemUUID(ipdu_group) arxml_create_sub_element(ipdu_group, "SHORT-NAME", group_name) arxml_create_sub_element(ipdu_group, "COMMUNICATION-DIRECTION", communication_direction) # 创建I-SIGNAL-I-PDUS容器 ipdus = arxml_create_sub_element(ipdu_group, "I-SIGNAL-I-PDUS") # 为每个PDU引用创建引用元素 for pdu_ref in pdu_refs: ref_conditional = arxml_create_sub_element(ipdus, "I-SIGNAL-I-PDU-REF-CONDITIONAL") ref_elem = arxml_create_sub_element(ref_conditional, "I-SIGNAL-I-PDU-REF", pdu_ref) ref_elem.set("DEST", "I-SIGNAL-I-PDU") return ipdu_group def create_can_frame_package(self, frame_name: str, frame_length: int, pdu_mappings: list[dict]) -> _Element: """ 创建CanFrame包和CAN-FRAME元素 Args: frame_name: CAN帧名称,例如"TestCan1_Msg_001_Tx_oTestCAN1" frame_length: 帧长度,例如64 pdu_mappings: PDU映射列表,每个映射包含: - name: 映射名称 - pdu_ref: PDU引用路径 - start_position: 起始位置 - byte_order: 字节顺序,默认为"MOST-SIGNIFICANT-BYTE-LAST" Returns: 创建的AR-PACKAGE元素 """ # 创建CanFrame包 ar_package = arxml_create_sub_element(self.top_level_packages, "AR-PACKAGE") arxml_UpdateElemUUID(ar_package) arxml_create_sub_element(ar_package, "SHORT-NAME", "CanFrame") # 创建ELEMENTS elements = arxml_create_sub_element(ar_package, "ELEMENTS") # 创建CAN-FRAME can_frame = arxml_create_sub_element(elements, "CAN-FRAME") arxml_UpdateElemUUID(can_frame) arxml_create_sub_element(can_frame, "SHORT-NAME", frame_name) arxml_create_sub_element(can_frame, "FRAME-LENGTH", str(frame_length)) # 创建PDU-TO-FRAME-MAPPINGS容器 pdu_mappings_container = arxml_create_sub_element(can_frame, "PDU-TO-FRAME-MAPPINGS") # 为每个PDU映射创建映射元素 for mapping in pdu_mappings: pdu_mapping = arxml_create_sub_element(pdu_mappings_container, "PDU-TO-FRAME-MAPPING") arxml_create_sub_element(pdu_mapping, "SHORT-NAME", mapping.get("name", "")) # 设置字节顺序,默认为"MOST-SIGNIFICANT-BYTE-LAST" byte_order = mapping.get("byte_order", "MOST-SIGNIFICANT-BYTE-LAST") arxml_create_sub_element(pdu_mapping, "PACKING-BYTE-ORDER", byte_order) # 创建PDU引用 pdu_ref = arxml_create_sub_element(pdu_mapping, "PDU-REF", mapping.get("pdu_ref", "")) pdu_ref.set("DEST", "I-SIGNAL-I-PDU") # 设置起始位置 arxml_create_sub_element(pdu_mapping, "START-POSITION", str(mapping.get("start_position", 0))) return ar_package def create_pdu_package(self, pdu_name: str, pdu_length: int, timing_spec: dict = None) -> _Element: """ 创建PDU包和I-SIGNAL-I-PDU元素 Args: pdu_name: PDU名称,例如"TestCan1_Msg_001_Tx_oTestCAN1" pdu_length: PDU长度,例如64 timing_spec: 时序规范字典,包含: - min_delay: 最小延迟,默认为0 - time_offset: 时间偏移,默认为0 - time_period: 时间周期,默认为0.01 Returns: 创建的AR-PACKAGE元素 """ # 创建PDU包 ar_package = arxml_create_sub_element(self.top_level_packages, "AR-PACKAGE") arxml_UpdateElemUUID(ar_package) arxml_create_sub_element(ar_package, "SHORT-NAME", "PDU") # 创建ELEMENTS elements = arxml_create_sub_element(ar_package, "ELEMENTS") # 创建I-SIGNAL-I-PDU isignal_ipdu = arxml_create_sub_element(elements, "I-SIGNAL-I-PDU") arxml_UpdateElemUUID(isignal_ipdu) arxml_create_sub_element(isignal_ipdu, "SHORT-NAME", pdu_name) arxml_create_sub_element(isignal_ipdu, "LENGTH", str(pdu_length)) # 创建I-PDU-TIMING-SPECIFICATIONS容器 timing_specs = arxml_create_sub_element(isignal_ipdu, "I-PDU-TIMING-SPECIFICATIONS") # 创建I-PDU-TIMING ipdu_timing = arxml_create_sub_element(timing_specs, "I-PDU-TIMING") # 设置默认时序规范 if timing_spec is None: timing_spec = { "min_delay": 0, "time_offset": 0, "time_period": 0.01 } arxml_create_sub_element(ipdu_timing, "MINIMUM-DELAY", str(timing_spec.get("min_delay", 0))) # 创建TRANSMISSION-MODE-DECLARATION transmission_mode = arxml_create_sub_element(ipdu_timing, "TRANSMISSION-MODE-DECLARATION") # 创建FALSE和TRUE两种传输模式的时序 for mode in ["FALSE", "TRUE"]: mode_timing = arxml_create_sub_element( transmission_mode, f"TRANSMISSION-MODE-{mode}-TIMING" ) cyclic_timing = arxml_create_sub_element(mode_timing, "CYCLIC-TIMING") time_offset = arxml_create_sub_element(cyclic_timing, "TIME-OFFSET") arxml_create_sub_element(time_offset, "VALUE", str(timing_spec.get("time_offset", 0))) time_period = arxml_create_sub_element(cyclic_timing, "TIME-PERIOD") arxml_create_sub_element(time_period, "VALUE", str(timing_spec.get("time_period", 0.01))) # 添加未使用位模式 arxml_create_sub_element(isignal_ipdu, "UNUSED-BIT-PATTERN", "0") return ar_package def AddPduTriggeringUnderPhysicalChannel(self, physical_channel_elem: _Element, pdu_short_name: str, ipdu_port_path: str, ipdu_ref_path: str) -> _Element: """ 在给定的 CAN-PHYSICAL-CHANNEL 下创建 PDU-TRIGGERINGS -> PDU-TRIGGERING 节点并返回它。 参数: - physical_channel_elem: CAN-PHYSICAL-CHANNEL 元素 - pdu_short_name: e.g. "PT_TestCan1_Msg_001_Tx" - ipdu_port_path: e.g. "/ECU/MyECU/CN_TestCAN1/PP_TestCan1_Msg_001_Tx_Tx" - ipdu_ref_path: e.g. "/PDU/TestCan1_Msg_001_Tx_oTestCAN1" """ pdu_triggerings_container = arxml_get_or_create_sub_element(physical_channel_elem, "PDU-TRIGGERINGS") pdu_trig = arxml_create_sub_element(pdu_triggerings_container, "PDU-TRIGGERING") arxml_create_sub_element(pdu_trig, "SHORT-NAME", pdu_short_name) ipdu_port_refs = arxml_create_sub_element(pdu_trig, "I-PDU-PORT-REFS") ipdu_port_ref = arxml_create_sub_element(ipdu_port_refs, "I-PDU-PORT-REF", ipdu_port_path) ipdu_port_ref.set("DEST", "I-PDU-PORT") ipdu_ref = arxml_create_sub_element(pdu_trig, "I-PDU-REF", ipdu_ref_path) ipdu_ref.set("DEST", "I-SIGNAL-I-PDU") return pdu_trig def AddCanFrameTriggeringUnderPhysicalChannel( self, physical_channel_elem: _Element, frame_triggering_short_name: str, frame_port_path: str, can_frame_ref_path: str, pdu_triggering_ref_paths: list, identifier: int = 0, addressing_mode: str = "STANDARD", rx_behavior: str = "ANY", tx_behavior: str = "CAN-FD" ) -> _Element: """ 在给定的 CAN-PHYSICAL-CHANNEL 元素下创建 FRAME-TRIGGERINGS -> CAN-FRAME-TRIGGERING 节点, 返回新创建的 CAN-FRAME-TRIGGERING 元素。 参数: - physical_channel_elem: 已创建的 CAN-PHYSICAL-CHANNEL 元素 - frame_triggering_short_name: e.g. "FT_TestCan1_Msg_001_Tx" - frame_port_path: 完整 path 文本, e.g. "/ECU/MyECU/CN_TestCAN1/FP_TestCan1_Msg_001_Tx_Tx" - can_frame_ref_path: e.g. "/CanFrame/TestCan1_Msg_001_Tx_oTestCAN1" - pdu_triggering_ref_paths: list of full PDU-TRIGGERING 路径,例如 ["/Cluster/TestCAN1/CHNL/PT_TestCan1_Msg_001_Tx"] - identifier/addressing_mode/rx_behavior/tx_behavior: 可选属性 """ # 确保 FRAME-TRIGGERINGS 容器存在(若已存在就复用) frame_triggerings = arxml_get_or_create_sub_element(physical_channel_elem, "FRAME-TRIGGERINGS") # 创建 CAN-FRAME-TRIGGERING frame_trig = arxml_create_sub_element(frame_triggerings, "CAN-FRAME-TRIGGERING") arxml_create_sub_element(frame_trig, "SHORT-NAME", frame_triggering_short_name) # FRAME-PORT-REFS frame_port_refs = arxml_create_sub_element(frame_trig, "FRAME-PORT-REFS") frame_port_ref = arxml_create_sub_element(frame_port_refs, "FRAME-PORT-REF", frame_port_path) frame_port_ref.set("DEST", "FRAME-PORT") # FRAME-REF frame_ref = arxml_create_sub_element(frame_trig, "FRAME-REF", can_frame_ref_path) frame_ref.set("DEST", "CAN-FRAME") # PDU-TRIGGERINGS -> 多个 PDU-TRIGGERING-REF-CONDITIONAL / PDU-TRIGGERING-REF pdu_triggerings = arxml_create_sub_element(frame_trig, "PDU-TRIGGERINGS") for pdu_ref in pdu_triggering_ref_paths: pdu_ref_cond = arxml_create_sub_element(pdu_triggerings, "PDU-TRIGGERING-REF-CONDITIONAL") pdu_ref_elem = arxml_create_sub_element(pdu_ref_cond, "PDU-TRIGGERING-REF", pdu_ref) pdu_ref_elem.set("DEST", "PDU-TRIGGERING") # 其它属性 arxml_create_sub_element(frame_trig, "CAN-ADDRESSING-MODE", addressing_mode) arxml_create_sub_element(frame_trig, "CAN-FRAME-RX-BEHAVIOR", rx_behavior) arxml_create_sub_element(frame_trig, "CAN-FRAME-TX-BEHAVIOR", tx_behavior) arxml_create_sub_element(frame_trig, "IDENTIFIER", str(identifier)) return frame_trig # if __name__ == "__main__": # now = GetTimeStr() # opts = { # "gatewayArPackegeName": "RouteGateway", # "gatewayName": "GEN_GW", # "ecuInstanceName": "GEN_GW", # } # arxml = gatewayDoc(opt=opts) # arxml.init() # arxml.AddPduMapping("GEN_GW","/Cluster/ICM_ADAS/CHNL/PT_ADAS_IPC12", "/Cluster/ICM_AUX/CHNL/PT_AUX_BCM_RC5") # arxml.save("gatewayOutput.arxml") if __name__ == "__main__": # 从 JSON 文件读取路由信息 with open('TestInputFile/routing_info1.json', 'r') as f: routing_info = json.load(f) now = GetTimeStr() opts = { "gatewayArPackegeName": "RouteGateway", "gatewayName": "FLZCU", "ecuInstanceName": "FLZCU", } # 1. 初始化 gatewayDoc arxml = gatewayDoc(opt=opts) arxml.init() # 收集所有网络(Cluster)信息 clusters = {} for route in routing_info: # 假设每个路由项都有一个 cluster_name 字段 cluster_name = route.get("cluster_name", "TestCAN1") if cluster_name not in clusters: # 创建 Cluster 结构 physical_channel = arxml.AddClusterStructure( cluster_name=cluster_name, channel_name=f"CHNL_{cluster_name}" ) clusters[cluster_name] = physical_channel # 处理每个路由项 for route in routing_info: routing_type = route.get("routing_type", "Message") cluster_name = route.get("cluster_name", "TestCAN1") physical_channel = clusters[cluster_name] # 添加 Gateway PDU Mapping 或 Signal Mapping if routing_type == "Message": arxml.AddPduMapping( route.get("routing_path_name", "GEN_GW"), route.get("rx_pdu_trigger", ""), route.get("tx_pdu_trigger", "") ) elif routing_type == "Signal": # 如果有信号映射,可以在这里添加 pass # 添加 CAN-FRAME-TRIGGERING frame_triggering = route.get("frame_triggering", {}) if frame_triggering: arxml.AddCanFrameTriggeringUnderPhysicalChannel( physical_channel_elem=physical_channel, frame_triggering_short_name=frame_triggering.get("short_name", ""), frame_port_path=frame_triggering.get("frame_port_path", ""), can_frame_ref_path=frame_triggering.get("can_frame_ref_path", ""), pdu_triggering_ref_paths=frame_triggering.get("pdu_triggering_ref_paths", []), identifier=frame_triggering.get("identifier", 0) ) # 添加 PDU-TRIGGERING pdu_triggering = route.get("pdu_triggering", {}) if pdu_triggering: arxml.AddPduTriggeringUnderPhysicalChannel( physical_channel_elem=physical_channel, pdu_short_name=pdu_triggering.get("short_name", ""), ipdu_port_path=pdu_triggering.get("ipdu_port_path", ""), ipdu_ref_path=pdu_triggering.get("ipdu_ref_path", "") ) # 创建 IPDUGroup 包 ipdu_group = route.get("ipdu_group", {}) if ipdu_group: arxml.create_ipdu_group_package( group_name=ipdu_group.get("group_name", ""), communication_direction=ipdu_group.get("communication_direction", "OUT"), pdu_refs=ipdu_group.get("pdu_refs", []) ) # 创建 CanFrame 包 can_frame = route.get("can_frame", {}) if can_frame: arxml.create_can_frame_package( frame_name=can_frame.get("frame_name", ""), frame_length=can_frame.get("frame_length", 64), pdu_mappings=can_frame.get("pdu_mappings", []) ) # 创建 PDU 包 pdu_info = route.get("pdu", {}) if pdu_info: arxml.create_pdu_package( pdu_name=pdu_info.get("pdu_name", ""), pdu_length=pdu_info.get("pdu_length", 64) ) # 创建 ECU package arxml.addEcuInstance() # 添加关联的 PDU 组引用 for route in routing_info: ipdu_group = route.get("ipdu_group", {}) if ipdu_group: arxml.add_associated_pdu_group_ref(f"/IPDUGroup/{ipdu_group.get('group_name', '')}") # 添加 ECU 连接器 for cluster_name in clusters: arxml.add_ecu_connector( connector_name=f"CN_{cluster_name}", controller_ref=f"/ECU/{opts['ecuInstanceName']}/CT_{cluster_name}", frame_ports=[{ "name": f"FP_{cluster_name}_Tx", "direction": "OUT" }], pdu_ports=[{ "name": f"PP_{cluster_name}_Tx", "direction": "OUT" }] ) # 保存 ARXML 文件 arxml.save("TestOutputFile/gatewayOutput1.arxml") print("ARXML 文件已生成:TestOutputFile/gatewayOutput1.arxml")
最新发布
09-13
/* Copyright (c) 2023 Renmin University of China RMDB is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. */ #include "ix_index_handle.h" #include "ix_scan.h" /** * @brief 在当前node中查找第一个>=target的key_idx * * @return key_idx,范围为[0,num_key),如果返回的key_idx=num_key,则表示target大于最后一个key * @note 返回key index(同时也是rid index),作为slot no */ int IxNodeHandle::lower_bound(const char *target) const { int left = 0, right = get_size(); while (left < right) { int mid = left + (right - left) / 2; int cmp = ix_compare(get_key(mid), target, file_hdr->col_types_, file_hdr->col_lens_); if (cmp < 0) { left = mid + 1; } else { right = mid; } } return left; } /** * @brief 在当前node中查找第一个>target的key_idx * * @return key_idx,范围为[1,num_key),如果返回的key_idx=num_key,则表示target大于等于最后一个key * @note 注意此处的范围从1开始 */ int IxNodeHandle::upper_bound(const char *target) const { // 将left初始值从1改为0,确保从第一个键开始搜索 int left = 0, right = get_size(); while (left < right) { int mid = left + (right - left) / 2; // 比较中间键与目标键 if (ix_compare(get_key(mid), target, file_hdr->col_types_, file_hdr->col_lens_) <= 0) { left = mid + 1; } else { right = mid; } } return left; } /** * @brief 用于叶子结点根据key来查找该结点中的键值对 * 值value作为传出参数,函数返回是否查找成功 * * @param key 目标key * @param[out] value 传出参数,目标key对应的Rid * @return 目标key是否存在 */ bool IxNodeHandle::leaf_lookup(const char *key, Rid **value) { int pos = lower_bound(key); if (pos < get_size() && ix_compare(get_key(pos), key, file_hdr->col_types_, file_hdr->col_lens_) == 0) { *value = get_rid(pos); return true; } return false; } /** * 用于内部结点(非叶子节点)查找目标key所在的孩子结点(子树) * @param key 目标key * @return page_id_t 目标key所在的孩子节点(子树)的存储页面编号 */ page_id_t IxNodeHandle::internal_lookup(const char *key) { // 处理最小键值情况(保持不变) if (key == nullptr) { return value_at(0); } // 更可靠的最小键值检查(保持不变) bool is_min_key = true; for (int i = 0; i < file_hdr->col_tot_len_; i++) { if (static_cast<unsigned char>(key[i]) != 0) { is_min_key = false; break; } } if (is_min_key) { return value_at(0); } int pos = upper_bound(key); // 修复:直接返回pos位置的值,而不是pos-1 return value_at(pos); } /** * @brief 在指定位置插入n个连续的键值对 * 将key的前n位插入到原来keys中的pos位置;将rid的前n位插入到原来rids中的pos位置 * * @param pos 要插入键值对的位置 * @param (key, rid) 连续键值对的起始地址,也就是第一个键值对,可以通过(key, rid)来获取n个键值对 * @param n 键值对数量 * @note [0,pos) [pos,num_key) * key_slot * / \ * / \ * [0,pos) [pos,pos+n) [pos+n,num_key+n) * key key_slot */ void IxNodeHandle::insert_pairs(int pos, const char *key, const Rid *rid, int n) { assert(pos >= 0 && pos <= get_size()); assert(get_size() + n <= get_max_size()); // 移动键数组 char *keys_dst = get_key(pos + n); char *keys_src = get_key(pos); int keys_bytes = (get_size() - pos) * file_hdr->col_tot_len_; memmove(keys_dst, keys_src, keys_bytes); // 移动值数组 Rid *rids_dst = get_rid(pos + n); Rid *rids_src = get_rid(pos); int rids_bytes = (get_size() - pos) * sizeof(Rid); memmove(rids_dst, rids_src, rids_bytes); // 插入新键值对 memcpy(get_key(pos), key, n * file_hdr->col_tot_len_); memcpy(get_rid(pos), rid, n * sizeof(Rid)); set_size(get_size() + n); // 修正拼写错误 } /** * @brief 用于在结点中插入单个键值对。 * 函数返回插入后的键值对数量 * * @param (key, value) 要插入的键值对 * @return int 键值对数量 */ int IxNodeHandle::insert(const char *key, const Rid &value) { int pos = lower_bound(key); if (pos < get_size() && ix_compare(get_key(pos), key, file_hdr->col_types_, file_hdr->col_lens_) == 0) { return get_size(); // 键已存在,不插入 } insert_pair(pos, key, value); return get_size(); } /** * @brief 用于在结点中的指定位置删除单个键值对 * * @param pos 要删除键值对的位置 */ void IxNodeHandle::erase_pair(int pos) { assert(pos >= 0 && pos < get_size()); // 移动键数组 char *keys_dst = get_key(pos); char *keys_src = get_key(pos + 1); int keys_bytes = (get_size() - pos - 1) * file_hdr->col_tot_len_; memmove(keys_dst, keys_src, keys_bytes); // 移动值数组 Rid *rids_dst = get_rid(pos); Rid *rids_src = get_rid(pos + 1); int rids_bytes = (get_size() - pos - 1) * sizeof(Rid); memmove(rids_dst, rids_src, rids_bytes); set_size(get_size() - 1); } /** * @brief 用于在结点中删除指定key的键值对。函数返回删除后的键值对数量 * * @param key 要删除的键值对key值 * @return 完成删除操作后的键值对数量 */ int IxNodeHandle::remove(const char *key) { int pos = lower_bound(key); if (pos < get_size() && ix_compare(get_key(pos), key, file_hdr->col_types_, file_hdr->col_lens_) == 0) { erase_pair(pos); } return get_size(); } IxIndexHandle::IxIndexHandle(DiskManager *disk_manager, BufferPoolManager *buffer_pool_manager, int fd) : disk_manager_(disk_manager), buffer_pool_manager_(buffer_pool_manager), fd_(fd), file_hdr_(nullptr) { // 初始化指针 char* buf = new char[PAGE_SIZE]; memset(buf, 0, PAGE_SIZE); // 读取文件头页面 disk_manager_->read_page(fd, IX_FILE_HDR_PAGE, buf, PAGE_SIZE); // 反序列化到file_hdr_ file_hdr_ = new IxFileHdr(); file_hdr_->deserialize(buf); delete[] buf; // 释放缓冲区 // 设置下一个可用页号(基于文件头记录的页数) int next_page_no = file_hdr_->num_pages_; disk_manager_->set_fd2pageno(fd, next_page_no); } /** * @brief 用于查找指定键所在的叶子结点 * @param key 要查找的目标key值 * @param operation 查找到目标键值对后要进行的操作类型 * @param transaction 事务参数,如果不需要则默认传入nullptr * @return [leaf node] and [root_is_latched] 返回目标叶子结点以及根结点是否加锁 * @note need to Unlatch and unpin the leaf node outside! * 注意:用了FindLeafPage之后一定要unlatch叶结点,否则下次latch该结点会堵塞! */ std::pair<IxNodeHandle *, bool> IxIndexHandle::find_leaf_page(const char *key, Operation operation, Transaction *transaction, bool find_first) { std::scoped_lock root_lock(root_latch_); bool root_is_latched = true; if (file_hdr_->root_page_ == IX_NO_PAGE) { return {nullptr, false}; } IxNodeHandle *node = fetch_node(file_hdr_->root_page_); while (!node->is_leaf_page()) { page_id_t child_page_no; if (find_first) { // 使用空指针表示最小键值 child_page_no = node->internal_lookup(nullptr); } else { child_page_no = node->internal_lookup(key); } IxNodeHandle *child = fetch_node(child_page_no); if (root_is_latched) { root_latch_.unlock(); root_is_latched = false; } // 释放当前节点资源 buffer_pool_manager_->unpin_page(node->get_page_id(), false); delete node; node = child; } return {node, root_is_latched}; } /** * @brief 用于查找指定键在叶子结点中的对应的值result * * @param key 查找的目标key值 * @param result 用于存放结果的容器 * @param transaction 事务指针 * @return bool 返回目标键值对是否存在 */ bool IxIndexHandle::get_value(const char *key, std::vector<Rid> *result, Transaction *transaction) { auto [leaf, root_is_latched] = find_leaf_page(key, Operation::FIND, transaction); if (root_is_latched) { root_latch_.unlock(); } Rid *rid; bool found = leaf->leaf_lookup(key, &rid); if (found) { result->push_back(*rid); } buffer_pool_manager_->unpin_page(leaf->get_page_id(), false); return found; } /** * @brief 将传入的一个node拆分(Split)成两个结点,在node的右边生成一个新结点new node * @param node 需要拆分的结点 * @return 拆分得到的new_node * @note need to unpin the new node outside * 注意:本函数执行完毕后,原node和new node都需要在函数外面进行unpin */ IxNodeHandle *IxIndexHandle::split(IxNodeHandle *node) { IxNodeHandle *new_node = create_node(); new_node->set_parent_page_no(node->get_parent_page_no()); // 计算分裂位置 - 确保左右节点都有足够的键 int split_pos = node->get_min_size(); new_node->set_size(node->get_size() - split_pos); // 复制键值对到新结点 memcpy(new_node->get_key(0), node->get_key(split_pos), new_node->get_size() * file_hdr_->col_tot_len_); memcpy(new_node->get_rid(0), node->get_rid(split_pos), new_node->get_size() * sizeof(Rid)); node->set_size(split_pos); // 维护叶子节点双向链表 if (node->is_leaf_page()) { new_node->set_prev_leaf(node->get_page_no()); new_node->set_next_leaf(node->get_next_leaf()); node->set_next_leaf(new_node->get_page_no()); if (new_node->get_next_leaf() != IX_NO_PAGE) { IxNodeHandle *next_leaf = fetch_node(new_node->get_next_leaf()); next_leaf->set_prev_leaf(new_node->get_page_no()); buffer_pool_manager_->unpin_page(next_leaf->get_page_id(), true); delete next_leaf; } else { file_hdr_->last_leaf_ = new_node->get_page_no(); } // 更新第一个叶子指针(如果需要) if (file_hdr_->first_leaf_ == node->get_page_no()) { // 原节点仍然是第一个叶子,不需要更改 } } return new_node; } /** * @brief Insert key & value pair into internal page after split * 拆分(Split)后,向上找到old_node的父结点 * 将new_node的第一个key插入到父结点,其位置在 父结点指向old_node的孩子指针 之后 * 如果插入后>=maxsize,则必须继续拆分父结点,然后在其父结点的父结点再插入,即需要递归 * 直到找到的old_node为根结点时,结束递归(此时将会新建一个根R,关键字为key,old_node和new_node为其孩子) * * @param (old_node, new_node) 原结点为old_node,old_node被分裂之后产生了新的右兄弟结点new_node * @param key 要插入parent的key * @note 一个结点插入了键值对之后需要分裂,分裂后左半部分的键值对保留在原结点,在参数中称为old_node, * 右半部分的键值对分裂为新的右兄弟节点,在参数中称为new_node(参考Split函数来理解old_node和new_node) * @note 本函数执行完毕后,new node和old node都需要在函数外面进行unpin */ void IxIndexHandle::insert_into_parent(IxNodeHandle *old_node, const char *key, IxNodeHandle *new_node, Transaction *transaction) { if (old_node->is_root_page()) { // 创建新根节点 IxNodeHandle *new_root = create_node(); new_root->set_parent_page_no(IX_NO_PAGE); new_root->page_hdr->is_leaf = false; new_root->set_size(1); *new_root->get_rid(0) = Rid{old_node->get_page_no(), 0}; memcpy(new_root->get_key(0), new_node->get_key(0), file_hdr_->col_tot_len_); *new_root->get_rid(1) = Rid{new_node->get_page_no(), 0}; old_node->set_parent_page_no(new_root->get_page_no()); new_node->set_parent_page_no(new_root->get_page_no()); file_hdr_->root_page_ = new_root->get_page_no(); if (old_node->is_leaf_page()) { file_hdr_->first_leaf_ = old_node->get_page_no(); file_hdr_->last_leaf_ = new_node->get_page_no(); } buffer_pool_manager_->unpin_page(new_root->get_page_id(), true); delete new_root; } else { IxNodeHandle *parent = fetch_node(old_node->get_parent_page_no()); // 关键修复:使用传入的key而不是new_node->get_key(0) int insert_pos = parent->insert(key, Rid{new_node->get_page_no(), 0}); if (parent->get_size() >= parent->get_max_size()) { IxNodeHandle *new_parent = split(parent); // 关键修复:使用新父节点的第一个键 insert_into_parent(parent, new_parent->get_key(0), new_parent, transaction); buffer_pool_manager_->unpin_page(new_parent->get_page_id(), true); delete new_parent; } else { maintain_parent(parent); } buffer_pool_manager_->unpin_page(parent->get_page_id(), true); delete parent; } } /** * @brief 将指定键值对插入到B+树中 * @param (key, value) 要插入的键值对 * @param transaction 事务指针 * @return page_id_t 插入到的叶结点的page_no */ page_id_t IxIndexHandle::insert_entry(const char *key, const Rid &value, Transaction *transaction) { auto [leaf, root_is_latched] = find_leaf_page(key, Operation::INSERT, transaction); if (!leaf) { // 处理根节点不存在的情况 IxNodeHandle *new_leaf = create_node(); new_leaf->page_hdr->is_leaf = true; new_leaf->insert(key, value); file_hdr_->root_page_ = new_leaf->get_page_no(); file_hdr_->first_leaf_ = new_leaf->get_page_no(); file_hdr_->last_leaf_ = new_leaf->get_page_no(); if (root_is_latched) { root_latch_.unlock(); } page_id_t ret = new_leaf->get_page_no(); buffer_pool_manager_->unpin_page(new_leaf->get_page_id(), true); delete new_leaf; return ret; } // 检查节点是否已满 if (leaf->get_size() < leaf->get_max_size()) { // 节点未满,直接插入 leaf->insert(key, value); } else { // 节点已满,需要分裂 IxNodeHandle *new_leaf = split(leaf); // 确定键值应该插入哪个节点 if (ix_compare(key, new_leaf->get_key(0), file_hdr_->col_types_, file_hdr_->col_lens_) >= 0) { new_leaf->insert(key, value); } else { leaf->insert(key, value); } // 处理父节点 insert_into_parent(leaf, new_leaf->get_key(0), new_leaf, transaction); // 释放新节点资源 buffer_pool_manager_->unpin_page(new_leaf->get_page_id(), true); delete new_leaf; } // 确保最后释放资源 if (root_is_latched) { root_latch_.unlock(); } page_id_t ret = leaf->get_page_no(); buffer_pool_manager_->unpin_page(leaf->get_page_id(), true); delete leaf; return ret; } /** * @brief 用于删除B+树中含有指定key的键值对 * @param key 要删除的key值 * @param transaction 事务指针 */ bool IxIndexHandle::delete_entry(const char *key, Transaction *transaction) { auto [leaf, root_is_latched] = find_leaf_page(key, Operation::DELETE, transaction); if (!leaf->remove(key)) { if (root_is_latched) { root_latch_.unlock(); } buffer_pool_manager_->unpin_page(leaf->get_page_id(), false); return false; } // 直接使用返回值 if (coalesce_or_redistribute(leaf, transaction, &root_is_latched)) { // 处理合并或重分配结果 } if (root_is_latched) { root_latch_.unlock(); } buffer_pool_manager_->unpin_page(leaf->get_page_id(), true); return true; } /** * @brief 用于处理合并和重分配的逻辑,用于删除键值对后调用 * * @param node 执行完删除操作的结点 * @param transaction 事务指针 * @param root_is_latched 传出参数:根节点是否上锁,用于并发操作 * @return 是否需要删除结点 * @note User needs to first find the sibling of input page. * If sibling's size + input page's size >= 2 * page's minsize, then redistribute. * Otherwise, merge(Coalesce). */ bool IxIndexHandle::coalesce_or_redistribute(IxNodeHandle *node, Transaction *transaction, bool *root_is_latched) { if (node->is_root_page()) { return adjust_root(node); } if (node->get_size() >= node->get_min_size()) { return false; } IxNodeHandle *parent = fetch_node(node->get_parent_page_no()); int index = parent->find_child(node); IxNodeHandle *neighbor; bool is_prev = (index > 0); if (is_prev) { neighbor = fetch_node(parent->value_at(index - 1)); } else { neighbor = fetch_node(parent->value_at(index + 1)); } if (node->get_size() + neighbor->get_size() >= node->get_max_size()) { redistribute(neighbor, node, parent, index); buffer_pool_manager_->unpin_page(parent->get_page_id(), true); buffer_pool_manager_->unpin_page(neighbor->get_page_id(), true); return false; } else { bool parent_deleted = coalesce(&neighbor, &node, &parent, index, transaction, root_is_latched); buffer_pool_manager_->unpin_page(parent->get_page_id(), true); buffer_pool_manager_->unpin_page(neighbor->get_page_id(), true); return parent_deleted; } } /** * @brief 用于当根结点被删除了一个键值对之后的处理 * @param old_root_node 原根节点 * @return bool 根结点是否需要被删除 * @note size of root page can be less than min size and this method is only called within coalesce_or_redistribute() */ bool IxIndexHandle::adjust_root(IxNodeHandle *old_root_node) { if (old_root_node->get_size() > 1 || old_root_node->is_leaf_page()) { return false; } // 根节点只有一个孩子,让孩子成为新的根节点 page_id_t child_page_no = old_root_node->remove_and_return_only_child(); IxNodeHandle *child = fetch_node(child_page_no); child->set_parent_page_no(IX_NO_PAGE); file_hdr_->root_page_ = child_page_no; release_node_handle(*old_root_node); buffer_pool_manager_->unpin_page(child->get_page_id(), true); return true; } /** * @brief 重新分配node和兄弟结点neighbor_node的键值对 * Redistribute key & value pairs from one page to its sibling page. If index == 0, move sibling page's first key * & value pair into end of input "node", otherwise move sibling page's last key & value pair into head of input "node". * * @param neighbor_node sibling page of input "node" * @param node input from method coalesceOrRedistribute() * @param parent the parent of "node" and "neighbor_node" * @param index node在parent中的rid_idx * @note node是之前刚被删除过一个key的结点 * index=0,则neighbor是node后继结点,表示:node(left) neighbor(right) * index>0,则neighbor是node前驱结点,表示:neighbor(left) node(right) * 注意更新parent结点的相关kv对 */ void IxIndexHandle::redistribute(IxNodeHandle *neighbor_node, IxNodeHandle *node, IxNodeHandle *parent, int index) { if (index > 0) { // neighbor是前驱节点 if (!node->is_leaf_page()) { // 内部节点需要移动最后一个键值对 int neighbor_size = neighbor_node->get_size(); node->insert_pair(0, neighbor_node->get_key(neighbor_size - 1), *neighbor_node->get_rid(neighbor_size - 1)); // 解引用 neighbor_node->erase_pair(neighbor_size - 1); parent->set_key(index, node->get_key(0)); maintain_child(node, 0); } else { // 叶子节点需要移动最后一个键值对 int neighbor_size = neighbor_node->get_size(); node->insert_pair(0, neighbor_node->get_key(neighbor_size - 1), *neighbor_node->get_rid(neighbor_size - 1)); // 解引用 neighbor_node->erase_pair(neighbor_size - 1); parent->set_key(index, node->get_key(0)); } } else { // neighbor是后继节点 if (!node->is_leaf_page()) { // 内部节点需要移动第一个键值对 node->insert_pair(node->get_size(), neighbor_node->get_key(0), *neighbor_node->get_rid(0)); // 解引用 neighbor_node->erase_pair(0); parent->set_key(1, neighbor_node->get_key(0)); maintain_child(node, node->get_size() - 1); } else { // 叶子节点需要移动第一个键值对 node->insert_pair(node->get_size(), neighbor_node->get_key(0), *neighbor_node->get_rid(0)); // 解引用 neighbor_node->erase_pair(0); parent->set_key(1, neighbor_node->get_key(0)); } } } /** * @brief 合并(Coalesce)函数是将node和其直接前驱进行合并,也就是和它左边的neighbor_node进行合并; * 假设node一定在右边。如果上层传入的index=0,说明node在左边,那么交换node和neighbor_node,保证node在右边;合并到左结点,实际上就是删除了右结点; * Move all the key & value pairs from one page to its sibling page, and notify buffer pool manager to delete this page. * Parent page must be adjusted to take info of deletion into account. Remember to deal with coalesce or redistribute * recursively if necessary. * * @param neighbor_node sibling page of input "node" (neighbor_node是node的前结点) * @param node input from method coalesceOrRedistribute() (node结点是需要被删除的) * @param parent parent page of input "node" * @param index node在parent中的rid_idx * @return true means parent node should be deleted, false means no deletion happend * @note Assume that *neighbor_node is the left sibling of *node (neighbor -> node) */ bool IxIndexHandle::coalesce(IxNodeHandle **neighbor_node, IxNodeHandle **node, IxNodeHandle **parent, int index, Transaction *transaction, bool *root_is_latched) { if (index == 0) { // 交换node和neighbor_node,保证node在右边 std::swap(*neighbor_node, *node); index = 1; } // 将node合并到neighbor_node int neighbor_insert_pos = (*neighbor_node)->get_size(); (*neighbor_node)->insert_pairs(neighbor_insert_pos, (*node)->get_key(0), (*node)->get_rid(0), (*node)->get_size()); if (!(*node)->is_leaf_page()) { // 内部节点需要更新子节点的父指针 for (int i = 0; i < (*node)->get_size(); i++) { maintain_child(*neighbor_node, neighbor_insert_pos + i); } } else { // 叶子节点需要更新链表指针 (*neighbor_node)->set_next_leaf((*node)->get_next_leaf()); if ((*node)->get_next_leaf() != IX_NO_PAGE) { IxNodeHandle *next_leaf = fetch_node((*node)->get_next_leaf()); next_leaf->set_prev_leaf((*neighbor_node)->get_page_no()); buffer_pool_manager_->unpin_page(next_leaf->get_page_id(), true); } else { file_hdr_->last_leaf_ = (*neighbor_node)->get_page_no(); } } // 从父节点中删除node对应的键值对 (*parent)->remove((*node)->get_key(0)); release_node_handle(**node); // 递归处理父节点 return coalesce_or_redistribute(*parent, transaction, root_is_latched); } /** * @brief 这里把iid转换成了rid,即iid的slot_no作为node的rid_idx(key_idx) * node其实就是把slot_no作为键值对数组的下标 * 换而言之,每个iid对应的索引槽存了一对(key,rid),指向了(要建立索引的属性首地址,插入/删除记录的位置) * * @param iid * @return Rid * @note iid和rid存的不是一个东西,rid是上层传过来的记录位置,iid是索引内部生成的索引槽位置 */ Rid IxIndexHandle::get_rid(const Iid &iid) const { IxNodeHandle *node = fetch_node(iid.page_no); if (iid.slot_no >= node->get_size()) { throw IndexEntryNotFoundError(); } buffer_pool_manager_->unpin_page(node->get_page_id(), false); // unpin it! return *node->get_rid(iid.slot_no); } /** * @brief FindLeafPage + lower_bound * * @param key * @return Iid * @note 上层传入的key本来是int类型,通过(const char *)&key进行了转换 * 可用*(int *)key转换回去 */ Iid IxIndexHandle::lower_bound(const char *key) { return Iid{-1, -1}; } /** * @brief FindLeafPage + upper_bound * * @param key * @return Iid */ Iid IxIndexHandle::upper_bound(const char *key) { return Iid{-1, -1}; } /** * @brief 指向最后一个叶子的最后一个结点的后一个 * 用处在于可以作为IxScan的最后一个 * * @return Iid */ Iid IxIndexHandle::leaf_end() const { IxNodeHandle *node = fetch_node(file_hdr_->last_leaf_); Iid iid = {.page_no = file_hdr_->last_leaf_, .slot_no = node->get_size()}; buffer_pool_manager_->unpin_page(node->get_page_id(), false); // unpin it! return iid; } /** * @brief 指向第一个叶子的第一个结点 * 用处在于可以作为IxScan的第一个 * * @return Iid */ Iid IxIndexHandle::leaf_begin() const { Iid iid = {.page_no = file_hdr_->first_leaf_, .slot_no = 0}; return iid; } /** * @brief 获取一个指定结点 * * @param page_no * @return IxNodeHandle* * @note pin the page, remember to unpin it outside! */ IxNodeHandle *IxIndexHandle::fetch_node(int page_no) const { Page *page = buffer_pool_manager_->fetch_page(PageId{fd_, page_no}); IxNodeHandle *node = new IxNodeHandle(file_hdr_, page); return node; } /** * @brief 创建一个新结点 * * @return IxNodeHandle* * @note pin the page, remember to unpin it outside! * 注意:对于Index的处理是,删除某个页面后,认为该被删除的页面是free_page * 而first_free_page实际上就是最新被删除的页面,初始为IX_NO_PAGE * 在最开始插入时,一直是create node,那么first_page_no一直没变,一直是IX_NO_PAGE * 与Record的处理不同,Record将未插入满的记录页认为是free_page */ IxNodeHandle *IxIndexHandle::create_node() { IxNodeHandle *node; file_hdr_->num_pages_++; PageId new_page_id = {.fd = fd_, .page_no = INVALID_PAGE_ID}; // 从3开始分配page_no,第一次分配之后,new_page_id.page_no=3,file_hdr_.num_pages=4 Page *page = buffer_pool_manager_->new_page(&new_page_id); node = new IxNodeHandle(file_hdr_, page); return node; } /** * @brief 从node开始更新其父节点的第一个key,一直向上更新直到根节点 * * @param node */ void IxIndexHandle::maintain_parent(IxNodeHandle *node) { IxNodeHandle *curr = node; while (curr->get_parent_page_no() != IX_NO_PAGE) { // Load its parent IxNodeHandle *parent = fetch_node(curr->get_parent_page_no()); int rank = parent->find_child(curr); char *parent_key = parent->get_key(rank); char *child_first_key = curr->get_key(0); if (memcmp(parent_key, child_first_key, file_hdr_->col_tot_len_) == 0) { assert(buffer_pool_manager_->unpin_page(parent->get_page_id(), true)); break; } memcpy(parent_key, child_first_key, file_hdr_->col_tot_len_); // 修改了parent node curr = parent; assert(buffer_pool_manager_->unpin_page(parent->get_page_id(), true)); } } /** * @brief 要删除leaf之前调用此函数,更新leaf前驱结点的next指针和后继结点的prev指针 * * @param leaf 要删除的leaf */ void IxIndexHandle::erase_leaf(IxNodeHandle *leaf) { assert(leaf->is_leaf_page()); IxNodeHandle *prev = fetch_node(leaf->get_prev_leaf()); prev->set_next_leaf(leaf->get_next_leaf()); buffer_pool_manager_->unpin_page(prev->get_page_id(), true); IxNodeHandle *next = fetch_node(leaf->get_next_leaf()); next->set_prev_leaf(leaf->get_prev_leaf()); // 注意此处是SetPrevLeaf() buffer_pool_manager_->unpin_page(next->get_page_id(), true); } /** * @brief 删除node时,更新file_hdr_.num_pages * * @param node */ void IxIndexHandle::release_node_handle(IxNodeHandle &node) { file_hdr_->num_pages_--; } /** * @brief 将node的第child_idx个孩子结点的父节点置为node */ void IxIndexHandle::maintain_child(IxNodeHandle *node, int child_idx) { if (!node->is_leaf_page()) { // Current node is inner node, load its child and set its parent to current node int child_page_no = node->value_at(child_idx); IxNodeHandle *child = fetch_node(child_page_no); child->set_parent_page_no(node->get_page_no()); buffer_pool_manager_->unpin_page(child->get_page_id(), true); } }
06-05
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值