文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Linux | PCIe Hotplug | 概念及工作原理的不完全总结

2023-09-22 18:14

关注

本文对 PCIe 热插拔的概念及工作原理进行不完全总结。

更新: 2022 / 12 / 31


Linux | PCIe Hotplug | 概念及工作原理的不完全总结

参考这里 12345

1992年初始版本的 PCI 规范并不支持运行时的板卡添加和移除。在20世纪90年代后期到21世纪初期,各种专有的热拔插控制器或者与厂商无关的标准热拔插控制器被构思出来,同时 Linux 通过位于 drivers/pci/hotplug 的驱动开始支持热拔插功能。然而,直到2002年 Linux 才开始支持PCI Express 的热拔插功能,但是具体的形式随时间不断变化。最初,PCI Express 热拔插主要是为服务器的热交换板卡或笔记本的 ExpressCards 设计的,如今,它已经广泛地应用于数据中心 ( 数据中心 NVMe Flash 硬盘需要运行时拔插 ) 和雷电接口。


热插拔

PCIe 热插拔,是指在系统上电运行时,允许插入或拔出 PCIe 设备。拔出时不需要平台提前下电,插入时直接生效,不需要系统重启。这对于服务器实现高可靠性而言是一个必不可少的特性。

热插拔可以分为2种,通知式热插拔暴力热插拔。这两种差异主要体现在拔盘的操作上。


组成部件

为了 pcie 热插拔功能的实现,PCIe 协议定义了一系列需要实现的组件:

组件目的
Indicatorshow the power and attention state of the slot。

标准模型中定义两个 indicators:一个Power Indicator 和一个 Attention Indicator。都会有三种状态:ONOFFBlinking
Attention Indicator 是黄色的灯,用来指示出现了操作的问题,或者表示 hotplug slot 已经被识别到了,手动操作可以很容易定位到。

ON: 表示热插拔槽位故障
OFF: 表示一切正常
BLINKING: 表示热插拔流程正在执行
Power Indicator 是绿色的灯,表示 slotpower 状态,可以用 blink 指示总线对用户的操作产生了响应。

OFF: 表示槽位下电,可以进行设备的热插或热拔
ON: 表示热插拔操作已完成,槽位上电,不可以进行热插或热拔
BLINKING: 表示此时正在处于上电或下电槽位, 或者此时 attention button 被按下,正在等待反馈,或者表示 hot-plug 操作正在进行软件的初始化
Manually-operated Retention Latch (MRL)Holds adds-in cards in place
MRL SensorAllows the Port and system software to detect the MRL being opened

MRL 是一种手动操作保留机制。保持插入卡在 slot 上,防止用户移除卡。系统添加了一个 MRL sensor,以便侦测每个 port 对应的 slotMRL
Electromechanical InterlockPrevents removal of add-in cards while slot is powered

一种互斥机制,确保在热插拔流程都执行完成后 PCIe 设备才可被物理移除。
Attention ButtonAllows user to request Hot-Plug operations

hotplug 中定义的一个开关按钮,一般会在 slot 上或者在卡片上,按一下表明要做一个 hotplug 动作或者 removal 动作。
Software User InterfaceAllows user to request Hot-Plug operations
Slot NumberingProvides visual identification of slots

槽位编号,由底板号( Classic Number )和物理槽位号( Physical Slot Number ) 组成,可在用户接口上显示。

实现代码

通知式热插拔

热拔的流程图如下所示:
在这里插入图片描述
热插流程类似,不再赘述。

PCIe 热插拔功能的实现需要 pcie 热插拔控制器和 pcie 热插拔驱动的配合。
代码主要集中在 driver/pci/hotplug/pciehp_hpc.c 6driver/pci/hotplug/pciehp_ctrl.c 7

  1. pciehp 初始化
    PCIe 热插拔是作为 pcie 端口服务实现的,它已在 driver/pci/hotplug/pciehp_core.c 8 中的 pcie 端口驱动程序中注册:
int __init pcie_hp_init(void){int retval = 0;retval = pcie_port_service_register(&hpdriver_portdrv);pr_debug("pcie_port_service_register = %d\n", retval);if (retval)pr_debug("Failure to register service\n");return retval;}

之后会调用 pcie_probe 进行端口注册,

+->pciehp_probe(struct pcie_device *dev)+-> pcie_init()+-> pcie_init_slot() // 该函数中会创建hotplug_slot, hotplug_slot_info, hotplug_slot_ops等热插拔驱动关键的数据结构+-> init_slot()+-> pci_hp_initialize()+-> pci_create_slot()+-> kobject_init_and_add()  // 添加sysfs对象+-> list_add(&slot->list, &parent->slots) //  将hotplug_slot添加到pci_hotplug_slot_list+-> pcie_init_notification()+-> pciehp_request_irq() //中断申请+-> pcie_enable_notification()+->  pciehp_enable_slot() // 使能hp槽位+-> board_added()
  1. 注册热插拔中断服务 6
static inline int pciehp_request_irq(struct controller *ctrl){int retval, irq = ctrl->pcie->irq;....retval = request_threaded_irq(irq, pciehp_isr, pciehp_ist,      IRQF_SHARED, MY_NAME, ctrl);...}

这里使用了中断线程化,创建一个中断服务 pciehp_isr, 并且创建了一个 irq_thread 内核线程以及线程会执行的函数 pciehp_ist
当中断发生时,中断处理程序会先去处理 pciehp_isr, 如果 pciehp_isr 返回IRQ_WAKE_THREAD, 会去唤醒内核线程,处理 pciehp_ist

case IRQ_WAKE_THREAD:if (unlikely(!action->thread_fn)) {warn_no_thread(irq, action);break;}__irq_wake_thread(desc, action);

中断线程化之后,中断将作为内核线程运行而且被赋予不同的实时优先级,实时任务可以有比中断线程更高的优先级。这样,具有最高优先级的实时任务就能得到优先处理。

  1. 热插拔控制器上报中断后,pciehp 驱动会进入 pcie_isr 函数执行
static irqreturn_t pciehp_isr(int irq, void *dev_id){struct controller *ctrl = (struct controller *)dev_id;struct pci_dev *pdev = ctrl_dev(ctrl);struct device *parent = pdev->dev.parent;u16 status, events = 0;if (pdev->current_state == PCI_D3cold ||    (!(ctrl->slot_ctrl & PCI_EXP_SLTCTL_HPIE) && !pciehp_poll_mode))return IRQ_NONE;if (parent) {pm_runtime_get_noresume(parent);if (!pm_runtime_active(parent)) {pm_runtime_put(parent);disable_irq_nosync(irq);atomic_or(RERUN_ISR, &ctrl->pending_events);return IRQ_WAKE_THREAD;}}read_status:pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status);if (PCI_POSSIBLE_ERROR(status)) {ctrl_info(ctrl, "%s: no response from device\n", __func__);if (parent)pm_runtime_put(parent);return IRQ_NONE;}status &= PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD |  PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_CC |  PCI_EXP_SLTSTA_DLLSC;if (ctrl->power_fault_detected)status &= ~PCI_EXP_SLTSTA_PFD;else if (status & PCI_EXP_SLTSTA_PFD)ctrl->power_fault_detected = true;events |= status;if (!events) {if (parent)pm_runtime_put(parent);return IRQ_NONE;}if (status) {pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, status);if (pci_dev_msi_enabled(pdev) && !pciehp_poll_mode)goto read_status;}ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events);if (parent)pm_runtime_put(parent);if (events & PCI_EXP_SLTSTA_CC) {ctrl->cmd_busy = 0;smp_mb();wake_up(&ctrl->queue);if (events == PCI_EXP_SLTSTA_CC)return IRQ_HANDLED;events &= ~PCI_EXP_SLTSTA_CC;}if (pdev->ignore_hotplug) {ctrl_dbg(ctrl, "ignoring hotplug event %#06x\n", events);return IRQ_HANDLED;}atomic_or(events, &ctrl->pending_events);return IRQ_WAKE_THREAD;}

pciehp_isr 函数几个操作进行分析:
[1.] 检查设备当前状态是否处于 D3_cold 状态。
D3hot( 通常只称作 D3 )是设备的 软关闭 状态,在此状态下,总线扫描可以检测到设备,并且发送给设备的命令可能会导致它再次打开电源,而在 D3cold 中,将切断所有电源,只保留少量电源以驱动设备的唤醒逻辑。
因此,在这里检测到如果处于 D3cold 状态,直接退出。
[2.] 如果设备存在父设备,在该设备 resume 前要先 resume 父设备
[3.] 读 pcie slot status 确认 events 状态。hotplug controller 监控各种 events 并把这些 events 上报给 hotplug system driver
这里需要关注这些 events 即可。Attention button pressed ( Attention 按键按下), power fault detected (电源错误), presence detect changed (在位状态变化), command completed (命令完成), data link layer state changed (链路状态改变)。

所以整个 isr 流程主要的处理就是决定哪些 events 可以通过 system interrupt 上报给系统,然后唤醒内核线程,处理线程函数 pciehp_ist

  1. 中断线程化处理 pciehp_ist
static irqreturn_t pciehp_ist(int irq, void *dev_id){struct controller *ctrl = (struct controller *)dev_id;struct pci_dev *pdev = ctrl_dev(ctrl);irqreturn_t ret;u32 events;ctrl->ist_running = true;pci_config_pm_runtime_get(pdev);if (atomic_fetch_and(~RERUN_ISR, &ctrl->pending_events) & RERUN_ISR) {ret = pciehp_isr(irq, dev_id);enable_irq(irq);if (ret != IRQ_WAKE_THREAD)goto out;}synchronize_hardirq(irq);events = atomic_xchg(&ctrl->pending_events, 0);if (!events) {ret = IRQ_NONE;goto out;}if (events & PCI_EXP_SLTSTA_ABP) {ctrl_info(ctrl, "Slot(%s): Attention button pressed\n",  slot_name(ctrl));pciehp_handle_button_press(ctrl);}if (events & PCI_EXP_SLTSTA_PFD) {ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(ctrl));pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF,      PCI_EXP_SLTCTL_ATTN_IND_ON);}if ((events & PCI_EXP_SLTSTA_DLLSC) && pci_dpc_recovered(pdev) &&    ctrl->state == ON_STATE) {events &= ~PCI_EXP_SLTSTA_DLLSC;pciehp_ignore_dpc_link_change(ctrl, pdev, irq);}down_read_nested(&ctrl->reset_lock, ctrl->depth);if (events & DISABLE_SLOT)pciehp_handle_disable_request(ctrl);else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC))pciehp_handle_presence_or_link_change(ctrl, events);up_read(&ctrl->reset_lock);ret = IRQ_HANDLED;out:pci_config_pm_runtime_put(pdev);ctrl->ist_running = false;wake_up(&ctrl->requester);return ret;}

[1.] 这里主要涉及到的是 runtime pm 的运行机制。
需要使用设备时,device driver 调用pm_runtime_get 接口,增加引用计数;
不再使用设备时,device driver 调用 pm_runtime_put 接口,减少引用计数。
[2.] 检测到了 attention button 按钮被按下。检测到一个 attention button pressed 中断,对该中断的处理需要依据当前 pcie 槽位的状态

#define BLINKINGON_STATE1#define BLINKINGOFF_STATE2#define POWERON_STATE3#define POWEROFF_STATE4

在这里插入图片描述
如果 5 s 内没有取消 attention button, 最终会执行到 pcie_init_slot 中初始化的 slot->work, 进行slotenabledisable ( pciehp_queue_pushbutton_work )。
[3.] 检测到了电源异常。
power_fault_detected 标志先置为 1,表示电源异常不会重复处理,然后将电源指示灯熄灭。
[4.] 检测到了 disble_slot
DISABLE_SLOT 主要用于响应用户通过 sysfs 或者 attention button 禁用槽位的请求,此事件的优先级要高于在位信号状态或者链路状态改变。
[5.] 如果检测到在位状态改变或链路状态改变

+->pciehp_handle_presence_or_link_change()+-> slot->state = POWEROFF_STATE;+-> pciehp_disable_slot()+->remove_board()
+->present || link_active+-> pciehp_enable_slot() +-> board_added() +-> pciehp_configure_device()

线程中断

LinuxPCIe 热拔插驱动 pciehp 在 2004 年被 Dely Sy 引入。Kenji Kaneshige 对其进行了第一次清理和修订,直到 2011 年才结束相关工作。在这之后,贡献者们的工作大多局限于完善驱动的缺陷,尤其是事件处理。

Threaded Interrupts 是内核中主要的中断处理模式,是实时 Linux 的基石,但是不幸的,它们直到 Kaneshige 的修订完成后才被引入。因此,pciehp 的硬中断处理器识别发生了什么事件,比如 link-up 或者 link-down,并为每个事件安排工作项。这种方法的问题是当执行 ( work item )时,link 状态可以再次发生变化。此外,如果 link 状态翻转比硬件处理器速度快时,可能导致检测到不均衡的 link-uplink-down 事件。最终,多个 work item 并存的可能性以及它们的交互方式使得很难判断事件处理代码的正确性。当前,PCI 维护员 Bjorn Helgaaspciph 的事件处理复杂且怪异。除非驱动的修订不再是一个选项时,那么驱动的基本反思是不可避免地。

Linux 4.19 版本中,把 pciehp 转换为线程中断处理 ( threaded interrupt handling )。现在硬件中断处理器负责收集事件,中断线程负责处理事件。检测 link 变化是 link up 还是 link down 这项工作推迟到中断线程中事件处理函数处理,以避免处理陈旧的事件。新的方法可以快速处理事件序列 (比如,link down 后紧接着 link up ),可以容忍 pcie slot 启动过程中 link 状态发生翻转 (比如电磁干扰引起的)。补丁集也修复了大量的 bug 和做了大量清理工作,因此,PCI Express 驱动的可靠性和健壮性得到了显著的提升。

对于 Linux 4.20 版本中安排的后续补丁将从pcihp 和其他热拔插驱动中删除将近 500 行代码,进而使代码简化和标准化。


功耗管理

Linux 4.19 增加了运行时挂起 PCIe 热拔插端口的功能,这项功能对雷电控制器的掉电是必不可少,雷电控制器在操作系统中表现为一个 PCIe 上游端口和多个 PCIe 下游端口。在控制器掉电前,所有的 PCIe 端口运行时挂起。Linux4.8 版本开始就可以运行时挂起上游端口,但是在 4.19 版本前并不能挂起运行时挂起下游端口。

运行时挂起一个雷电 PCIe 端口本身并不会引起任何节能,端口将会对通过聚集 IO Switch 传 输的 PCIe 数据包进行封装和解封,那么只要给 Switch 供电,那么就会消耗能量。然而,Linux 的功耗管理模型要求所有的子设备在其父节点挂起前被挂起。通过运行时挂起雷电控制器的所有端口,它的父端口(根端口)允许挂起,这反过来通过 ACPI 平台方法触发掉电。控制器下电可以节约大约 1.5W 的功耗。

换句话说,运行时挂起雷电 PCIe 端口满足了 Linux 的层级化功耗管理模型。单个雷电 PCIe 端口在 PCI 电源状态 D0 (全功耗) 或者 D3hot (挂起) 这两种状态消耗同样多的功耗,但是当所有端口运行时挂起时控制器作为整体是可以掉电的。在 MacBook 平台,雷电控制器掉电功能可能需要进一步打补丁解决,预计在 4.21 版本中出现。

当一个 PCIe 热拔插端口运行时挂起时热拔插事件处理发生一个有趣的细节:如果它的父端口也运行时挂起,那么该端口是不可访问的。因此,它不能带内发出中断信号,内核不能与之交互,或者甚至在父端口恢复运行时挂起前不能确定事件类型。当前有两种硬件方法可以解决这个问题。

第一个方法遵循 PCIe 规范:热拔插端口信号是一个电源管理事件 ( PME ),它可以通过平台提供带外信号方式发生,比如一个通用 IO 引脚 ( 在 PCIE 中的 WAKE# 信号)。PME 唤醒雷电Host 控制器下面所有层级,随之,热拔插端口可以被访问。这个方法被联想和 Dell 笔记本采用。它允许在连接设备的情况下让控制器掉电。Mika Westerberg 已经在 4.20 版本中提交了支持该功能的补丁。

第二种方法是非标准的:雷电硬件直到哪个通道建立了连接,因此可以将聚合 IO 层发生的热拔插事件转换为一个 overlaid PCIe 层发生的热拔插事件。这样,当设备被添加或移除时,无论其自己还是其父端口是否处在 D3hot 状态,都会魔幻般从受影响的 PCIe 端口接收到一个中断。这种方法在 Apple Macs ( 雷电 1 ) 中采用,只要设备已经连接的情况下雷电 Host 控制器需要保持供电。在 4.19 版本中已经添加了这种功能。

运行时电源管理当前对非-雷电热拔插端口是禁止的,因为当非雷电热拔插端口在 D3hot 状态时可能引起诸如不可屏蔽的中断 ( NMI )。厂商可以在命令行传递 pcie_port_pm=force 验证它们的热拔插端口是否支持运行时挂起,也许这个功能可能在晚些时候默认使能。


意外移除

初始 PCIe 规范定义了标准用法模型,该模型定义了手动保留锁固定板卡位置和一个按钮用于请求从操作系统移除一个 PCIe Slot。但是当前热拔插实现通常忽略这些元素和仅仅使用surprise removal 这种方式。

当一个设备被拽出时,pciehp 要求它的驱动解绑,然后 bring down 该槽位。但是直到这一切发生,对设备的读请求将会在 17ms 后溢出,然后返回一个全 1 的响应。这个超时降低了请求任务的速度,如果构造的响应对实际的数据是错误的,那么该任务可能崩溃或者陷入到无限循环中。因此,驱动需要从一个设备读取的数据的有效性,尤其是,检查全 1 但是不是一个有效响应的情况。一个惯用的方法是调用 pci_device_is_present() ,该函数读取厂商 ID 寄存器,检查该寄存器是否全 1。然而,这也不是万能药;如果发生一个 PCIe 不可修复的错误,那么设备也可能以全 1 进行响应,但是如果错误可以恢复,那么也可以还原到有效响应。此外,对于不支持的请求或者位于桥地址窗口内内任一目标设备基址寄存器 ( BARs ) 外的读请求也可能返回全 1 响应。唯一能够权威且明确识别移除的实体是 pciehp

许多驱动甚至 PCI 核并不会对一个全 1 响应的每个读进行检查。效力于 Facebook “Lighting"存储架构的工程师需要艰难地学习这个问题。Surprise 移除一个 NVMe 硬盘实体阵列可能花费数秒钟时间,也会偶发性引起 MCE ( machine-check exceptions )。拔出过程如此缓慢以至于驱动可能在完成上一个拔出处理过程前,认为自己在跟一个新插入的硬盘进行交互。由Keith BuschLinux 4.12 版本提交的补丁中一个成果是让 pciehpsurprise 移除的设备设置标志位,在 PCI 核中在一些战略性的位置跳过该设备的访问。这足以将移除过程加速到毫秒级别。尤其是当设置标志位后,pci_device_is_present() 返回 false。之前,如果一个设备跟另一个设备快速交换,那么一旦新设备的厂商 ID 可读后对于已移除的设备会错误地返回 True

Benjamin Herrenschmidt's behest 中,由 Busch 提交的另一个补丁已经安排引入到 4.20 版本,目的是统一 PCI 设备错误状态的标志。错误状态可以用于识别设备是经历一个不可修复错误,但是有机会恢复后的临时性不可访问,还是永久性不可访问。驱动也会直接检查 struct pci_dev 结构体中 error_state 成员变量或者调用 pci_channel_offline() 来确定设备的可访问性。

然而,Helgass 对标志使用表示了疑虑。其一,标志是异步设置的,因此,在设备被移除到标志被设置之间存在一个延迟。驱动开发者需要谨慎小心:即使根据标志位设备似乎在位,但是设备可能已经不在了。相反,如果设置了标志位,标志提供了明确指示,后来的设备访问是无效的,可以跳过。因此,设置标志位并不会使驱动开发者免于验证来自设备的响应,但是一旦设置标志位,它可以作为一个 cache ,避免了不明确的厂商ID检查。简而言之,问题只是得到了缓解,而没有完美解决。尽管一个完美的解决方案似乎是不可能的,但是我们不能获得用户的互斥锁来阻止用户召回设备,处于性能考量,我们不能在每次设备访问后都对设备在位变化进行检查。Austin Bolen 指出新的 PCIe 扩展 ( root port programmed IO ) 允许失败设备访问的同步异常处理,这样看起来似乎是一个完美的解决方案。但是这个特征在未来一段时间是不可得的。

Helgaas 对标志的第二个担心是标志可能使 surprise 拔插发生的 bug 更加难以发现。当设置标志后,这些 bug 变得更难以发现。For example, a search for the advanced error recovery (AER) capability on device removal caused numerous configuration-space accesses and, before introduction of the flag, was noticeable through a significant slowdown upon surprise removal。但是恰当的方式是:缓存AER能力的位置,而不是使用标志跳过配置访问糊弄问题。


错误处理整合

向线程中断的转变也减轻了整合 pciehp 处理 PCIe 不可修复错误的处理压力:当在热拔插端口或者子端口发生这样错误时,它可能引起一个 link-down 事件。但是有时候错误可能通过软件得以恢复,比如执行 secondary bus reset。在这种情况下,pciehp 通过使设备和驱动解绑,同时使slot 掉电来应对 link-down 事件是不合时宜的。相反,它应该等待以确定错误是否可以恢复,如果可以恢复,那么忽略该 link 事件。为了达到这个目的,BuschSinan Kaya 当前致力于开发补丁将 pciehp 中的 AER 与下游端口控制服务驱动联系在一起。


移除BAR

PCIe 设备分配了内存范围,用于在设备的 BAR 中配置的内存映射 I/O。内存范围通常由 BIOS 预定义,但 Linux 可能会在枚举时移动它们。PCI 设备上游的桥将其地址窗口配置为正确路由以设备 BAR 为目标的事务。

当设备被热添加时,它们的内存需求可能无法适应其上游网桥的窗口,从而需要重新组织资源:需要移动相邻的 BAR 并调整网桥窗口。 MacOS2013 年获得此功能以改进 Thunderbolt 支持,并将其称为 PCIe pause。驱动程序被告知暂停对受影响设备的 I/O;取消暂停时,BAR 可能已更改,并且驱动程序需要重新配置其设备并根据需要更新内部数据结构。

Sergey Miroshnichenko 最近提交了将 BAR 迁移到 Linux 的初始补丁,获得了积极的反响。这些补丁使用现有的回调在 PCI reset 之前暂停对设备的访问并在之后重新启动访问。drivers 将不得不选择加入 BAR 移除。除了 BAR 之外,MacOS 还支持 PCI bus number 和消息信号中断的重新分配;Miroshnichenko 正在考虑在补丁集的未来修订版中添加它。


参考链接


  1. Linux系统PCIe hotplug的现代化 ↩︎

  2. The modernization of PCIe hotplug in Linux ↩︎

  3. Zynq UltraScale+ PCIe Root Port Lessons Learned ↩︎

  4. 32】linux5.0之后 pciehp引入的bug ↩︎

  5. Linux内核笔记之PCIe hotplug介绍及代码分析 ↩︎

  6. linux/drivers/pci/hotplug/pciehp_hpc.c ↩︎ ↩︎

  7. linux/drivers/pci/hotplug/pciehp_ctrl.c ↩︎

  8. linux/drivers/pci/hotplug/pciehp_core.c ↩︎

来源地址:https://blog.csdn.net/MissMango0820/article/details/128497422

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-人工智能
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯