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

谈到中断使用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同时不需要唤醒时;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【回溯】1240. 铺瓷砖

本文涉及知识点 回溯 LeetCode1240. 铺瓷砖 你是一位施工队的工长&#xff0c;根据设计师的要求准备为一套设计风格独特的房子进行室内装修。 房子的客厅大小为 n x m&#xff0c;为保持极简的风格&#xff0c;需要使用尽可能少的 正方形 瓷砖来铺盖地面。 假设正方形瓷砖的…

【C++小语法】引用和内联函数(完结篇)

在使用C语言编程过程中&#xff0c;C语言的要求之严格&#xff0c;编程过程之繁琐&#xff0c;大同小异的重复性工作&#xff0c;令C之父使用C语言编程时也深受其扰&#xff0c;于是乎C兼容C小语法诞生了 一、引用 1.引用概念 在C中&#xff0c;引用&#xff08;Reference&am…

SpringCloud------Feign,Geteway

Feign 所以我们使用一门新的技术&#xff1a;声明式的http客户端Feign 第一步&#xff1a;引入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency> …

C++ | Leetcode C++题解之第90题子集II

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> t;vector<vector<int>> ans;vector<vector<int>> subsetsWithDup(vector<int> &nums) {sort(nums.begin(), nums.end());int n nums.size();for (int mask …

C++青少年简明教程:赋值语句

C青少年简明教程&#xff1a;赋值语句 赋值语句是编程中最基本也是最常用的概念之一&#xff0c;它用于将一个值分配给一个变量。 使用等号&#xff08; 称为赋值运算符&#xff09;来给变量赋值&#xff0c;赋值语句的左边是要赋值的变量&#xff0c;右边是要赋给变量的值。C…

PHP 自提时间

前端: 后台设置: 代码: public function getBusinessHour(){// 需求单门店$data (new StoreModel())->limit(1)->select()->toArray();$days explode(,, $data[0][shop_hours]);$businessHours $days[1];// 使用 explode 分割字符串&#xff0c;获取开始和结束时…

Nodejs 第七十章(OSS)

OSS OSS&#xff08;Object Storage Service&#xff09;是一种云存储服务&#xff0c;提供了一种高度可扩展的、安全可靠的对象存储解决方案 OSS 对象存储以对象为基本存储单元&#xff0c;每个对象都有唯一的标识符&#xff08;称为对象键&#xff09;和数据。这些对象可以…

【教程】Jetson安装PyQt5和CUDA版OpenCV

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;请不吝给个[点赞、收藏、关注]哦~ 安装PyQt5 注意目前似乎只支持Python3.6&#xff01;&#xff01;&#xff01; sudo apt install pyqt5* -y sudo apt-get install python3-pyqt…

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

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

python数据分析——seaborn绘图2

参考资料&#xff1a;活用pandas库 # 导入库 import pandas as pd import matplotlib.pyplot as plt import seaborn as sns tipspd.read_csv(r"...\seaborn常用数据案例\tips.csv") print(tips.head()) 1、成对关系表示 当数据大部分是数据时&#xff0c;可以使用…

AI图像生成-调整

一、两张图画风不相似 2、在两张图的共同输出口新添加一个空白正面提示词板块和条件合并板块 二、预处理插件&#xff08;提取人物姿态&#xff09; 1、新建节点-》ControlNet预处理器-》面部与姿态-》Openpose姿态预处理器 2、添加上传图片板块与预览图片板块 3、提取姿态 右…

数据库学习之select语句练习

目录 素材 练习 1、显示所有职工的基本信息。 结果 2、查询所有职工所属部门的部门号&#xff0c;不显示重复的部门号。 结果 3、求出所有职工的人数。 结果 4、列出最高工和最低工资。 结果 5、列出职工的平均工资和总工资。 结果 6、创建一个只有职…

【全开源】房屋出租出售预约系统支持微信小程序+H5+APP

一款基于FastAdminThinkPHPUniapp开发的房屋出租出售预约系统&#xff0c;支持小程序、H5、APP&#xff1b;包含房客、房东(高级授权)、经纪人(高级授权)三种身份。核心功能有&#xff1a;新盘销售、房屋租赁、地图找房、小区找房&#xff0c;地铁找房等方式。 特色功能&#…

Salesforce AI研究: 从奖励建模到在线RLHF工作流

摘要 该研究在本技术报告中介绍了在线迭代基于人类反馈的强化学习(Online Iterative Reinforcement Learning from Human Feedback, RLHF)的工作流程,在最近的大语言模型(Large Language Model, LLM)文献中,这被广泛报道为大幅优于其离线对应方法。然而,现有的开源RLHF项目仍然…

【爬虫之scrapy框架——尚硅谷(学习笔记two)--爬取电影天堂(基本步骤)】

爬虫之scrapy框架--爬取电影天堂——解释多页爬取函数编写逻辑 &#xff08;1&#xff09;爬虫文件创建&#xff08;2&#xff09;检查网址是否正确&#xff08;3&#xff09;检查反爬&#xff08;3.1&#xff09; 简写输出语句&#xff0c;检查是否反爬&#xff08;3.2&#x…

初识鸿蒙之ArkTS基础

前言 学习一种应用程序开发&#xff0c;需要从这种程序的开发语言开始&#xff0c;比如说Android开发从入门到放弃&#xff0c;肯定是从Java基础或者是Kotlin语言基础开始学习的&#xff0c;IOS程序开发也肯定是从object-c开始学习的。鸿蒙软件开发也不例外&#xff0c;如果做…

二叉树的前序遍历(leetcode)

144. 二叉树的前序遍历 - 力扣&#xff08;LeetCode&#xff09; 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 这道题的启发性真的很强 &#xff0c;这里必须传入i的指针进去&#xff0c;下一次栈帧i&#xff0c;但回到了上一层i又变回到了原来的i&#…

办公园区建筑科技风效果(html+threejs)

办公楼科技风(Htmlthreejs) 初始化三维场景 function init() {container document.getElementById(container);camera new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 150000000);camera.position.set(550, 600, 690);scene new THREE.Sce…

短视频的拍摄方式有哪些:四川京之华锦信息技术公司

创意与技术并存的艺术之旅 在数字媒体高速发展的今天&#xff0c;短视频已经成为人们获取信息、表达情感、展示才艺的重要窗口。从社交平台到新闻资讯&#xff0c;再到教育娱乐&#xff0c;短视频无处不在&#xff0c;其独特的魅力和广泛的传播力让人们对它的拍摄方式产生了浓…

应急响应-Windows-挖矿病毒

随着虚拟货币市场的繁荣&#xff0c;挖矿病毒已成为网络安全领域一大挑战。该类病毒利用计算机资源进行加密货币的挖掘&#xff0c;给个人用户和企业网络带来了严重的安全风险。本文将针对挖矿病毒的应急响应和防范措施进行分析和总结。 一.判断挖矿病毒 服务器突然发现CPU资…