windows驱动开发-PCI和中断(二)

news2024/12/23 1:23:54

谈到中断使用PCI总线来作为例子是最合适的,在Windows发展过程中,PCI作为最成功的底层总线,集成了大量的外设,不夸张的说,目前PCI几乎是唯一的总线选择,故大部分情况下,只有PCI设备驱动程序会遇到需要使用中断的情况。

PCI的升级版本是PCIe,但是在协议上,PCIe兼容PCI总线,在编程上,这两者几乎一致,PCIe兼容了PCI的结构和定义,以至于仅看代码很难理解这些知识,这个文档推迟了很久,是因为我还需要一些PCI方面的文档。

直接架构于PCI总线的设备类型包括: USB总线、1394总线、网络适配器、显示适配器等等,PCI可能目前最广泛的总线体系,这套体系不仅仅是软件也是硬件。

PCI总线最早采用的中断机制是INTx,这是基于边带信号的。后续的PCI/PCI-X版本,为了消除边带信号,降低系统的硬件设计复杂度,逐渐采用了MSI/MSI-X的中断机制。INTx一般被称为传统的(Legacy)PCI中断机制,MSI和MSIX中断则属于PCIE后来使用的中断方式。

注意: 由于PCI和PCIe是前向兼容的,所以在下面的讨论中,讨论PCI就是指PCIe。

PCI配置空间

PCI配置空间是PCI(Peripheral Component Interconnect)设备特有的一个物理空间,它支持即插即用功能,允许操作系统自动配置PCI设备的参数。PCI配置空间总长度为256个字节,分为两部分:

配置首部:前64个字节的配置空间称为配置头,对于所有设备都一样,主要功能是用来识别设备、定义主机访问PCI卡的方式(I/O访问或存储器访问,以及中断信息);
本地配置空间:其余的192个字节称为本地配置空间,主要定义卡上局部总线的特性、本地空间基地址及范围等;

PCI分为两种,一种成为PCI设备,另一种则是PCI桥,PCI设备容易理解,PCI桥则是用于将几个PCI总线连接起来的,一般认为每个PCI桥上可以有多个PCI设备。它们都使用相同的首部,不过PCI设备的首部称为type 0,PCI桥则是type 1,不过,遗憾的是,PCI的配置空间实际上是0x0或者0x81,位于PCI配置空间的14字节。

学习PCI最好实际看看PCI的配置空间,下图是我截取我系统上的PCI网卡的配置空间图;:

同样的PCI桥的配置空间如下:

在后续的文档中会详细解释这部分。 遗憾的是,我无法直接修改PCI的中断寄存器,无法演示两种中断模式。

INTx中断

在设备早期的开发中,很可能采用这种方式的中断,但是这种有边带信号触发的中断,有许多缺点: 系统其实无法准确分辨是PCI桥中具体哪一个设备引发的中断,需要调用所有的中断处理程序、系统每次收到中断的时候,数据其实并未准备好;每个设备最多有4个引脚可以触发传统中断。

但是无论如何,这种中断方式在硬件体系设计的早期是非常方便的,设备设计的早些版本很容易触发一个INTx中断,从而传输数据,从INTx中断升级到MSIX中断非常简单,并且对于主机侧来说,代码改动并不算太复杂,也不会改变程序结构。

目前的PCIe设备之间的中断信息传输中使用的并非边带信号INTx,而是基于消息(Message)的。其中Assert_INTx消息表示INTx信号的下降沿;Dessert_INTx消息表示INTx信号的上升沿。当发送这两种消息时,PCIe设备还会将配置空间的相关中断状态bit的值也进行更新。接收中断的物理设备的驱动程序会注册一个或多个中断服务例程, ISR为中断提供服务。 系统每次收到该中断时都会调用 ISR。

当驱动程序收到 IRP_MN_START_DEVICE 请求时,驱动程序将可以处理 IO_STACK_LOCATION 结构的 Parameters.StartDevice.AllocatedResources 和 Parameters.StartDevice.AllocatedResourcesTranslated 成员中的原始中断和已转换的硬件资源。 为了连接其中断,驱动程序使用 AllocatedResourcesTranslated.List.PartialResourceList.PartialDescriptors[] 中的资源。 驱动程序必须扫描部分描述符数组中 CmResourceTypeInterrupt 类型的资源。

如果驱动程序为 SpinLock 提供存储,则必须先调用 KeInitializeSpinLock ,然后才能将其中断旋转锁传递给 IoConnectInterrupt。

从成功调用 IoConnectInterrupt 返回后,如果在驱动程序的设备上启用了中断,或者 ShareVector 设置为 TRUE,则可以调用调用方 ISR。 在 IoConnectInterrupt 返回之前,驱动程序不得启用中断。

注意: 原始中断和已经分配的中断都是系统提供的,一般都会存储它们来为后面的一系列操作做准备。

有两个中断函数,不过先介绍第一个中断函数:

NTSTATUS IoConnectInterrupt(
  [out]          PKINTERRUPT       *InterruptObject,
  [in]           PKSERVICE_ROUTINE ServiceRoutine,
  [in, optional] PVOID             ServiceContext,
  [in, optional] PKSPIN_LOCK       SpinLock,
  [in]           ULONG             Vector,
  [in]           KIRQL             Irql,
  [in]           KIRQL             SynchronizeIrql,
  [in]           KINTERRUPT_MODE   InterruptMode,
  [in]           BOOLEAN           ShareVector,
  [in]           KAFFINITY         ProcessorEnableMask,
  [in]           BOOLEAN           FloatingSave
);
  • [out] InterruptObject: 指向指向一组中断对象的指针的驱动程序提供的存储地址的指针。 此指针必须在后续调用 KeSynchronizeExecution 中传递;
  • [in] ServiceRoutine: 指向驱动程序提供的 InterruptService 例程的入口点的指针;
  • [in, optional] ServiceContext: 指向驱动程序确定的上下文的指针,该上下文将在调用时提供给 InterruptService 例程。 ServiceContext 区域必须位于驻留内存中:在驱动程序创建的设备对象的设备扩展中、驱动程序创建的控制器对象的控制器扩展中,或位于设备驱动程序分配的非分页池中;
  • [in, optional] SpinLock:指向初始化的旋转锁的指针,驱动程序为其提供存储,该锁将用于同步对由其他驱动程序例程共享的驱动程序确定数据的访问。 如果 ISR 处理多个向量或驱动程序有多个 ISR,则此参数是必需的。 否则,驱动程序无需为中断旋转锁分配存储,并且输入指针为 NULL;
  • [in] Vector: 指定在CM_PARTIAL_RESOURCE_DESCRIPTOR的 u.Interrupt.Vector 成员处的中断资源中传递的中断向量;
  • [in] Irql: 指定在 CM_PARTIAL_RESOURCE_DESCRIPTOR 的 u.Interrupt.Level 成员处的中断资源中 传递的 DIRQL;
  • [in] SynchronizeIrql: 指定运行 ISR 的 DIRQL。 如果 ISR 处理多个中断向量或驱动程序具有多个 ISR,则此值必须是每个中断资源中 u.Interrupt.Level 传递的 Irql 值的最高值。 否则, Irql 和 SynchronizeIrql 值是相同的;
  • [in] InterruptMode: 指定设备中断是 LevelSensitive 还是 Latched;
  • [in] ShareVector: 指定中断向量是否可共享;
  • [in] ProcessorEnableMask: 指定一个 KAFFINITY 值,该值表示此平台中设备中断可能发生的处理器集。 此值在 u.Interrupt.Affinity 处的中断资源中传递;
  • [in] FloatingSave: 指定在驱动程序的设备中断时是否保存浮点堆栈。 对于基于 x86 和基于 Itanium 的平台,此值必须设置为 FALSE; 

 中断例程函数原型如下:

KSERVICE_ROUTINE KserviceRoutine;

BOOLEAN KserviceRoutine(
  [in] _KINTERRUPT *Interrupt,
  [in] PVOID ServiceContext
)
{...}

 下面我们从一个实际的案例来看中断:

NTSTATUS
NICMapHWResources(
    __in PFDO_DATA FdoData,
    __in PIRP Irp
    )
{
    PCM_PARTIAL_RESOURCE_DESCRIPTOR resourceTrans;
    PCM_PARTIAL_RESOURCE_LIST       partialResourceListTranslated;
    PIO_STACK_LOCATION              stack;
    ULONG                           i;
    NTSTATUS                        status = STATUS_SUCCESS;
    DEVICE_DESCRIPTION              deviceDescription;
    ULONG                           MaximumPhysicalMapping;
    PDMA_ADAPTER                    DmaAdapterObject;
    ULONG                           maxMapRegistersRequired, miniMapRegisters;
    ULONG                           MapRegisters;
    BOOLEAN bResPort = FALSE, bResInterrupt = FALSE, bResMemory = FALSE;
    ULONG                           numberOfBARs = 0;
#if defined(DMA_VER2) // To avoid  unreferenced local variables error
    ULONG                           SGMapRegsisters;
    ULONG                           ScatterGatherListSize;
#endif

    stack = IoGetCurrentIrpStackLocation (Irp);

    PAGED_CODE();

    if (NULL == stack->Parameters.StartDevice.AllocatedResourcesTranslated) {
        status = STATUS_DEVICE_CONFIGURATION_ERROR;
        goto End;
    }

    partialResourceListTranslated = &stack->Parameters.StartDevice.\
                      AllocatedResourcesTranslated->List[0].PartialResourceList;

    resourceTrans = &partialResourceListTranslated->PartialDescriptors[0];

    // 此处我们可以遍历所有IRP提供的硬件资源,一般是寄存器、端口、中断这三类
    for (i = 0;
         i < partialResourceListTranslated->Count;
         i++, resourceTrans++) {

        switch (resourceTrans->Type) {

        case CmResourceTypePort:

            numberOfBARs++;

            DebugPrint(LOUD, DBG_INIT,
                "I/O mapped CSR: (%x) Length: (%d)\n",
                resourceTrans->u.Port.Start.LowPart,
                resourceTrans->u.Port.Length);

            if(numberOfBARs != 2) {
                DebugPrint(ERROR, DBG_INIT, "I/O mapped CSR is not in the right order\n");
                status = STATUS_DEVICE_CONFIGURATION_ERROR;
                goto End;
            }


            FdoData->IoBaseAddress = ULongToPtr(resourceTrans->u.Port.Start.LowPart);
            FdoData->IoRange = resourceTrans->u.Port.Length;

            FdoData->ReadPort = NICReadPortUShort;
            FdoData->WritePort = NICWritePortUShort;

            bResPort = TRUE;
            FdoData->MappedPorts = FALSE;
            break;

        case CmResourceTypeMemory:

            numberOfBARs++;

            if(numberOfBARs == 1) {
                DebugPrint(LOUD, DBG_INIT, "Memory mapped CSR:(%x:%x) Length:(%d)\n",
                                        resourceTrans->u.Memory.Start.LowPart,
                                        resourceTrans->u.Memory.Start.HighPart,
                                        resourceTrans->u.Memory.Length);

                ASSERT(resourceTrans->u.Memory.Length == 0x1000);
                FdoData->MemPhysAddress = resourceTrans->u.Memory.Start;
                FdoData->CSRAddress = MmMapIoSpace(
                                                resourceTrans->u.Memory.Start,
                                                NIC_MAP_IOSPACE_LENGTH,
                                                MmNonCached);
                if(FdoData->CSRAddress == NULL) {
                    DebugPrint(ERROR, DBG_INIT, "MmMapIoSpace failed\n");
                    status = STATUS_INSUFFICIENT_RESOURCES;
                    goto End;
                }
                DebugPrint(LOUD, DBG_INIT, "CSRAddress=%p\n", FdoData->CSRAddress);

                bResMemory = TRUE;

            } else if(numberOfBARs == 2){

                DebugPrint(LOUD, DBG_INIT,
                    "I/O mapped CSR in Memory Space: (%x) Length: (%d)\n",
                    resourceTrans->u.Memory.Start.LowPart,
                    resourceTrans->u.Memory.Length);


                FdoData->IoBaseAddress = MmMapIoSpace(
                                                resourceTrans->u.Memory.Start,
                                                resourceTrans->u.Memory.Length,
                                                MmNonCached);
                if(FdoData->IoBaseAddress == NULL) {
                       DebugPrint(ERROR, DBG_INIT, "MmMapIoSpace failed\n");
                       status = STATUS_INSUFFICIENT_RESOURCES;
                       goto End;
                }

                FdoData->ReadPort = NICReadRegisterUShort;
                FdoData->WritePort = NICWriteRegisterUShort;
                FdoData->MappedPorts = TRUE;
                bResPort = TRUE;

            } else if(numberOfBARs == 3){

                DebugPrint(LOUD, DBG_INIT, "Flash memory:(%x:%x) Length:(%d)\n",
                                        resourceTrans->u.Memory.Start.LowPart,
                                        resourceTrans->u.Memory.Start.HighPart,
                                        resourceTrans->u.Memory.Length);

            } else {
                DebugPrint(ERROR, DBG_INIT,
                            "Memory Resources are not in the right order\n");
                status = STATUS_DEVICE_CONFIGURATION_ERROR;
                goto End;
            }

            break;

        case CmResourceTypeInterrupt:

            ASSERT(!bResInterrupt);

            bResInterrupt = TRUE;
            //
            // 将所有中断特定信息保存在设备中
            // 因为我们稍后在电源暂停和恢复期间需要它来断开和连接
            //
            FdoData->InterruptLevel = (UCHAR)resourceTrans->u.Interrupt.Level;
            FdoData->InterruptVector = resourceTrans->u.Interrupt.Vector;
            FdoData->InterruptAffinity = resourceTrans->u.Interrupt.Affinity;

            if (resourceTrans->Flags & CM_RESOURCE_INTERRUPT_LATCHED) {

                FdoData->InterruptMode = Latched;

            } else {

                FdoData->InterruptMode = LevelSensitive;
            }


            ASSERT(FdoData->InterruptMode == LevelSensitive);

            DebugPrint(LOUD, DBG_INIT,
                "Interrupt level: 0x%0x, Vector: 0x%0x, Affinity: 0x%x\n",
                FdoData->InterruptLevel,
                FdoData->InterruptVector,
                (UINT)FdoData->InterruptAffinity); // casting is done to keep WPP happy
            break;

        default:

            DebugPrint(LOUD, DBG_INIT, "Unhandled resource type (0x%x)\n",
                                        resourceTrans->Type);
            break;

        }
    }

    if (!(bResPort && bResInterrupt && bResMemory)) {
        status = STATUS_DEVICE_CONFIGURATION_ERROR;
        goto End;
    }

    NICDisableInterrupt(FdoData);

    IoInitializeDpcRequest(FdoData->Self, NICDpcForIsr);

    status = IoConnectInterrupt(&FdoData->Interrupt,
                              NICInterruptHandler,
                              FdoData,                   // ISR Context
                              NULL,
                              FdoData->InterruptVector,
                              FdoData->InterruptLevel,
                              FdoData->InterruptLevel,
                              FdoData->InterruptMode,
                              TRUE, // shared interrupt
                              FdoData->InterruptAffinity,
                              FALSE);
    if (status != STATUS_SUCCESS)
    {
        DebugPrint(ERROR, DBG_INIT, "IoConnectInterrupt failed %x\n", status);
        goto End;
    }

    MP_SET_FLAG(FdoData, fMP_ADAPTER_INTERRUPT_IN_USE);


    RtlZeroMemory(&deviceDescription, sizeof(DEVICE_DESCRIPTION));


#if defined(DMA_VER2)
    deviceDescription.Version = DEVICE_DESCRIPTION_VERSION2;
#else
    deviceDescription.Version = DEVICE_DESCRIPTION_VERSION;
#endif

    deviceDescription.Master = TRUE;
    deviceDescription.ScatterGather = TRUE;
    deviceDescription.Dma32BitAddresses = TRUE;
    deviceDescription.Dma64BitAddresses = FALSE;
    deviceDescription.InterfaceType = PCIBus;

    miniMapRegisters = ((NIC_MAX_PACKET_SIZE * 2 - 2) / PAGE_SIZE) + 2;

    maxMapRegistersRequired = FdoData->NumTcb * NIC_MAX_PHYS_BUF_COUNT;

    MaximumPhysicalMapping = (maxMapRegistersRequired-1) << PAGE_SHIFT;
    deviceDescription.MaximumLength = MaximumPhysicalMapping;

    DmaAdapterObject = IoGetDmaAdapter(FdoData->UnderlyingPDO,
                                        &deviceDescription,
                                        &MapRegisters);
    if (DmaAdapterObject == NULL)
    {
        DebugPrint(ERROR, DBG_INIT, "IoGetDmaAdapter failed\n");
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto End;
    }

    if(MapRegisters < miniMapRegisters) {
        DebugPrint(ERROR, DBG_INIT, "Not enough map registers: Allocated %d, Required %d\n",
                                        MapRegisters, miniMapRegisters);
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto End;
    }

    FdoData->AllocatedMapRegisters = MapRegisters;

    FdoData->NumTcb = MapRegisters/miniMapRegisters;

    FdoData->NumTcb = min(FdoData->NumTcb, NIC_MAX_TCBS);

    DebugPrint(TRACE, DBG_INIT, "MapRegisters Allocated %d\n", MapRegisters);
    DebugPrint(TRACE, DBG_INIT, "Adjusted TCB count is %d\n", FdoData->NumTcb);

    FdoData->DmaAdapterObject = DmaAdapterObject;
    MP_SET_FLAG(FdoData, fMP_ADAPTER_SCATTER_GATHER);

#if defined(DMA_VER2)

    status = DmaAdapterObject->DmaOperations->CalculateScatterGatherList(
                                            DmaAdapterObject,
                                            NULL,
                                            0,
                                            MapRegisters * PAGE_SIZE,
                                            &ScatterGatherListSize,
                                            &SGMapRegsisters);


    ASSERT(NT_SUCCESS(status));
    ASSERT(SGMapRegsisters == MapRegisters);
    if (!NT_SUCCESS(status))
    {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto End;
    }

    FdoData->ScatterGatherListSize = ScatterGatherListSize;

#endif

    FdoData->AllocateCommonBuffer =
                   *DmaAdapterObject->DmaOperations->AllocateCommonBuffer;
    FdoData->FreeCommonBuffer =
                   *DmaAdapterObject->DmaOperations->FreeCommonBuffer;
End:
    return status;
}

可以看到,在上面的例子中,我们会调用IoConnectInterrupt函数来连接中断。

这里的代码中有一部分遗憾,由于没有使用自旋锁,所以调用SpinLock之前需要初始化自旋锁这一点并未体现。

还有另外一个遗憾就是,中断本身适合处理器相关的,但是IoConnectInterrupt的返回值中,可能出现STATUS_INVALID_PARAMETER,这个错误的本质是没有指定合适的处理器,但是它的返回值上没有说明这一点,参数ProcessorEnableMask指的就是处理器组,它的解释如下: 

KAFFINITY 类型在 32 位版本的 Windows 上为 32 位,在 64 位版本的 Windows 上为 64 位。

如果组包含 n 个逻辑处理器,则处理器的编号从 0 到 n-1。 组中的处理器编号 i 由关联掩码中的位 i 表示,其中 i 在 0 到 n-1 的范围内。 与逻辑处理器不对应的关联掩码位始终为零。

例如,如果 KAFFINITY 值标识组中的活动处理器,如果处理器处于活动状态,则处理器的掩码位为 1;如果处理器不处于活动状态,则为0。

关联掩码中的位数确定组中逻辑处理器的最大数目。 对于 64 位版本的 Windows,每个组的最大处理器数为 64。 对于 32 位版本的 Windows,每个组的最大处理器数为 32。 调用 KeQueryMaximumProcessorCountEx 例程以获取每个组的最大处理器数。 此数字取决于多处理器系统的硬件配置,但永远不能超过 64 位和 32 位版本的 Windows 分别设置的固定 64 处理器和 32 处理器限制。

GROUP_AFFINITY 结构包含一个相关性掩码和一个组编号。 组号标识应用相关性掩码的组。

禁止和启用中断

和我们的直觉相反,禁止和启用中断非常频繁,这有几个原因:

首先,中断等级直接等于代码运行等级,windows明确规定中断不会被同级别的中断打断,只能被更高级别的中断打断;其次,边带信号可以理解为引脚上拉这种操作,这对于硬件那边也很好设计;最后,中断可以共享,因为系统内可以使用的中断向量只有两百多个,但是明显系统需要使用的中断远多于这个,故共享中断的时候,也需要注意避免中断之间的干扰。

中断的禁用也非常简单:

__inline VOID
NICDisableInterrupt(
    __in PFDO_DATA FdoData
    )
{
   FdoData->CSRAddress->ScbCommandHigh = SCB_INT_MASK;
}

KSYNCHRONIZE_ROUTINE NICEnableInterrupt;
    
__inline 
BOOLEAN
NICEnableInterrupt(
    PVOID Context
    )
{
    PFDO_DATA FdoData = Context;
    FdoData->CSRAddress->ScbCommandHigh = 0;
    return TRUE;
}

此处解释一下, CSRAddress->ScbCommandHigh是已经映射进系统空间的中断寄存器的地址,对它修改之后,系统会检测到这种行为并设置,硬件那边在发起中断之前可以查询这个寄存器来决定要不要中断。

我们由此也能知道什么时候禁用和启用中断:

在中断例程正在执行时;

系统进入深度睡眠(< S4)同时设备不需要唤醒时;

设备禁用和启用;设备进入D3同时不需要唤醒时;

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

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

相关文章

前端 performance api使用 —— mark、measure计算vue3页面echarts渲染时间

文章目录 ⭐前言&#x1f496;vue3系列文章 ⭐Performance api计算持续时间&#x1f496; mark用法&#x1f496; measure用法 ⭐计算echarts渲染的持续时间⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享关于 前端 performance api使用 —— mark、measure计…

java springboot连接sqlserver使用

pom.xml增加sqlserver驱动 <dependency><groupId>com.microsoft.sqlserver</groupId><artifactId>mssql-jdbc</artifactId><version>9.4.0.jre8</version></dependency>application.yml配置文件 server:port: 9001 #spring: …

多格式兼容的在线原型查看:Axure RP的便捷解决方案

Axure rp不仅可以绘制详细的产品构思&#xff0c;还可以在浏览器中生成html页面&#xff0c;但需要安装插件才能打开。安装Axure后 rpchrome插件后&#xff0c;还需要在扩展程序中选择“允许访问文件网站”&#xff0c;否则无法在Axure中成功选择 rp在线查看原型。听起来很麻烦…

用友GRP-U8 userInfoWeb SQL注入致RCE漏洞复现 (XVE-2024-10539)

0x01 产品简介 用友GRP-U8R10行政事业内控管理软件是用友公司专注于国家电子政务事业,基于云计算技术所推出的新一代产品,是我国行政事业财务领域最专业的政府财务管理软件。 0x02 漏洞概述 用友GRP-U8R10行政事业内控管理软件 userInfoWeb接口处存在SQL注入漏洞,未授权的…

不懂数字后端Box List、Polygon的意思?

什么是BOX&#xff1f; 景芯SoC做design planning的第一步就是确定floorplan的box&#xff0c;也就是设计的区域。这个区域可以划分为三个边界&#xff0c;如下图所示&#xff1a; Die Box 最外面一圈&#xff0c;我们称为 Die Box&#xff0c;也就是用来放置 IO 单元&#x…

高中数学:平面向量-加减运算

一、向量的加法运算 三角形法则&#xff08;推荐&#xff09; 两个或多个向量收尾相连的加法运算&#xff0c;用三角形法则 简便算法 首尾相连的多个向量&#xff0c;去掉中间点&#xff0c;就是最终的和。 也可以用三角形法则证明 向量加法交换律 向量加法结合律 平行四…

pycharm如何有效读取到win10设置的环境变量

参考链接&#xff1a; 参考文章 该参考文章的第一种方法&#xff1a;设置win10环境变量。 在设置完环境变量后&#xff0c;在pycharm终端上不能有效读取到刚刚设置的环境变量的&#xff0c;需要启动win的cmd&#xff0c;在项目路径下执行脚本。如下所示的对比&#xff1a; cm…

C++基础与深度解析 | 语句 | 分支语句 | 循环语句 | 达夫设备

文章目录 一、语句基础二、分支语句1.分支语句--if2.分支语句--switch 三、循环语句1.循环语句--while2.循环语句--do-while3.循环语句--for4.循环语句--基于范围的for循环5.break / continue语句四、语句的综合应用--达夫设备 一、语句基础 语句的常见类别&#xff1a; 表达…

数据结构与算法学习笔记三---栈和队列

目录 前言 一、栈 1.栈的表示和实现 1.栈的顺序存储表示和实现 1.C语言实现 2.C实现 2.栈的链式存储表示和实现 1.C语言实现 2.C实现 2.栈的应用 1.数制转换 二、队列 1.栈队列的表示和实现 1.顺序队列的表示和实现 2.链队列的表示和实现 2.循环队列 前言 这篇文…

OpenAI推出旗舰AI模型GPT-4o并免费开放

&#x1f989; AI新闻 &#x1f680; OpenAI推出旗舰AI模型GPT-4o并免费开放 摘要&#xff1a; OpenAI 未来的产品将以免费为优先&#xff0c;以让更多人使用为目标。OpenAI 发布了桌面版本的程序和更新后的 UI&#xff0c;更加简单自然。推出了新一代大模型 GPT-4o&#xf…

C语言 | Leetcode C语言题解之第89题格雷编码

题目&#xff1a; 题解&#xff1a; int* grayCode(int n, int* returnSize) {int ret_size 1 << n;int *ret (int *)malloc(ret_size * sizeof(int));for (int i 0; i < ret_size; i) {ret[i] (i >> 1) ^ i;}*returnSize ret_size;return ret; }

生态系统类型分布数据、土地利用数据、植被类型分布、自然保护区分布数据

引言 全国自然保护区生态系统类型分布信息产品是指基于Landsat TM数字影像&#xff08;以地形图纠正&#xff09;&#xff0c;采用全数字化人机交互遥感快速提取方法&#xff0c;建立全国自然保护区生态系统结构数据集&#xff0c;同时做成多种尺度的栅格数据&#xff0c;其中包…

Python | Leetcode Python题解之第90题子集II

题目&#xff1a; 题解&#xff1a; class Solution:def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:if not nums:return list()results list()nums.sort()visited [False] * len(nums)self.dfs(nums, results, list(), visited, 0)return resultsdef df…

2024 National Invitational of CCPC (Zhengzhou)(CCPC郑州邀请赛暨CCPC河南省赛)

2024 National Invitational of CCPC (Zhengzhou) 2024CCPC郑州邀请赛暨CCPC河南省赛 2024 National Invitational of CCPC (Zhengzhou) B. 扫雷 1 题意&#xff1a;扫n轮雷&#xff0c;每轮开始获得一枚扫雷币&#xff0c;可保存&#xff0c;从第一轮开始&#xff0c;可以…

如何管理多个版本的Node.js

我们如何在本地管理多个版本的Node.js&#xff0c;有没有那种不需要重新安装软件再修改配置文件和环境变量的方法&#xff1f;经过我的查找&#xff0c;还真有这种方式&#xff0c;那就是nvm&#xff08;Node Version Manager&#xff09;。 下面我就给大家介绍下NVM的使用 1…

MSR810-LM快速配置通过LTE模块上网

正文共&#xff1a;1111 字 13 图&#xff0c;预估阅读时间&#xff1a;1 分钟 之前买了一个无线版本的MSR810-W&#xff08;淘了一台二手的H3C企业路由器&#xff0c;就用它来打开网络世界的大门&#xff09;&#xff0c;并整理了一份快速配置&#xff08;脚本案例来了&#x…

AI大事记(持续更新)

文章目录 前言 一、人工智能AI 1.基本概念 2.相关领域 2.1基础设施 2.2大模型 2.3大模型应用 二、大事记 2024年 2024-05-14 GPT-4o发布 2024-02-15 Sora发布 2023年 2023-03-14 GPT-4.0发布 2022年 2022-11-30 ChatGPT发布 总结 前言 2022年11月30日openai的…

【数据结构】栈的实现(链式栈)

文章目录 栈的实现&#xff08;链式栈&#xff09;栈的定义初始化栈进栈判断是否为空栈出栈销毁栈获取栈顶元素获取栈的长度栈的打印 完整代码&#xff08;包括测试代码&#xff09;Stack.hStack.ctest.c 栈的实现&#xff08;链式栈&#xff09; 首先新建一个工程&#xff1a…

基于HTTP GET方式获取网络时间的实现

上一节&#xff0c;我们介绍了基于NTP服务器获取网络时间的例子&#xff0c;但在有些情况下&#xff0c;比如我最近在使用RNDIS协议通过4G模块上网&#xff0c;这个协议不支持UDP协议&#xff0c;所以就用不了NTP服务器。或者有时候我们需要有更多的网络时间获取方式&#xff0…

Transformers实战01-开箱即用的 pipelines

文章目录 简介安装pipelines图片转文本文本生成情感分析零训练样本分类遮盖词填充命名实体识别自动问答自动摘要 pipeline 背后做了什么&#xff1f;使用分词器进行预处理将预处理好的输入送入模型对模型输出进行后处理 简介 Transformers 是由 Hugging Face 开发的一个 NLP 包…