3.5 Windows驱动开发:应用层与内核层内存映射

在上一篇博文《内核通过PEB得到进程参数》中我们通过使用KeStackAttachProcess附加进程的方式得到了该进程的PEB结构信息,本篇文章同样需要使用进程附加功能,但这次我们将实现一个更加有趣的功能,在某些情况下应用层与内核层需要共享一片内存区域通过这片区域可打通内核与应用层的隔离,此类功能的实现依附于MDL内存映射机制实现。

3.5.1 应用层映射到内核层

先来实现将R3内存数据拷贝到R0中,功能实现所调用的API如下:

  • 调用IoAllocateMdl创建一个MDL结构体。这个结构体描述了一个要锁定的内存页的位置和大小。
  • 调用MmProbeAndLockPages用于锁定创建的地址其中UserMode代表用户层,IoReadAccess以读取的方式锁定
  • 调用MmGetSystemAddressForMdlSafe用于从MDL中得到映射内存地址
  • 调用RtlCopyMemory用于内存拷贝,将DstAddr应用层中的数据拷贝到pMappedSrc
  • 调用MmUnlockPages拷贝结束后解锁pSrcMdl
  • 调用IoFreeMdl释放之前创建的MDL结构体。

如上则是应用层数据映射到内核中的流程,我们将该流程封装成SafeCopyMemory_R3_to_R0方便后期的使用,函数对数据的复制进行了分块操作,因此可以处理更大的内存块。

下面是对该函数的分析:

  • 1.首先进行一些参数的检查,如果有任何一个参数为0,那么函数就会返回 STATUS_UNSUCCESSFUL

  • 2.使用一个 while 循环来分块复制数据,每个块的大小为 PAGE_SIZE (通常是4KB)。这个循环在整个内存范围内循环,每次复制一个内存页的大小,直到复制完整个内存范围。

  • 3.在循环内部,首先根据起始地址和当前要复制的大小来确定本次要复制的大小。如果剩余的内存不足一页大小,则只复制剩余的内存。

  • 4.调用 IoAllocateMdl 创建一个 MDL,表示要锁定和复制的内存页。这里使用了 (PVOID)(SrcAddr & 0xFFFFFFFFFFFFF000) 来确定页的起始地址。因为页的大小为 0x1000,因此在计算页的起始地址时,将 SrcAddr 向下舍入到最接近的 0x1000 的倍数。

  • 5.如果 IoAllocateMdl 成功,则调用 MmProbeAndLockPages 来锁定页面。这个函数将页面锁定到物理内存中,并返回一个虚拟地址,该虚拟地址指向已锁定页面的内核地址。

  • 6.使用 MmGetSystemAddressForMdlSafe 函数获取一个映射到内核空间的地址,该地址可以直接访问锁定的内存页。

  • 6.如果获取到了映射地址,则使用 RtlCopyMemory 函数将要复制的数据从应用层内存拷贝到映射到内核空间的地址。在复制结束后,使用 MmUnlockPages 函数解锁内存页,释放对页面的访问权限。

最后,释放 MDL 并更新 SrcAddrDstAddr 以复制下一个内存块。如果复制过程中发生任何异常,函数将返回 STATUS_UNSUCCESSFUL

总的来说,这个函数是一个很好的实现,它遵循了内核驱动程序中的最佳实践,包括对内存的安全处理、分块复制、错误处理等。

#include <ntifs.h>
#include <windef.h>

// 分配内存
void* RtlAllocateMemory(BOOLEAN InZeroMemory, SIZE_T InSize)
{
    void* Result = ExAllocatePoolWithTag(NonPagedPool, InSize, 'lysh');
    if (InZeroMemory && (Result != NULL))
        RtlZeroMemory(Result, InSize);
    return Result;
}

// 释放内存
void RtlFreeMemory(void* InPointer)
{
    ExFreePool(InPointer);
}

/*
将应用层中的内存复制到内核变量中

SrcAddr  r3地址要复制
DstAddr  R0申请的地址
Size     拷贝长度
*/
NTSTATUS SafeCopyMemory_R3_to_R0(ULONG_PTR SrcAddr, ULONG_PTR DstAddr, ULONG Size)
{
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    ULONG nRemainSize = PAGE_SIZE - (SrcAddr & 0xFFF);
    ULONG nCopyedSize = 0;

    if (!SrcAddr || !DstAddr || !Size)
    {
        return status;
    }

    while (nCopyedSize < Size)
    {
        PMDL pSrcMdl = NULL;
        PVOID pMappedSrc = NULL;

        if (Size - nCopyedSize < nRemainSize)
        {
            nRemainSize = Size - nCopyedSize;
        }

        // 创建MDL
        pSrcMdl = IoAllocateMdl((PVOID)(SrcAddr & 0xFFFFFFFFFFFFF000), PAGE_SIZE, FALSE, FALSE, NULL);
        if (pSrcMdl)
        {
            __try
            {
                // 锁定内存页面(UserMode代表应用层)
                MmProbeAndLockPages(pSrcMdl, UserMode, IoReadAccess);

                // 从MDL中得到映射内存地址
                pMappedSrc = MmGetSystemAddressForMdlSafe(pSrcMdl, NormalPagePriority);
            }
            __except (EXCEPTION_EXECUTE_HANDLER)
            {
            }
        }

        if (pMappedSrc)
        {
            __try
            {
                // 将MDL中的映射拷贝到pMappedSrc内存中
                RtlCopyMemory((PVOID)DstAddr, (PVOID)((ULONG_PTR)pMappedSrc + (SrcAddr & 0xFFF)), nRemainSize);
            }
            __except (1)
            {
                // 拷贝内存异常
            }

            // 释放锁
            MmUnlockPages(pSrcMdl);
        }

        if (pSrcMdl)
        {
            // 释放MDL
            IoFreeMdl(pSrcMdl);
        }

        if (nCopyedSize)
        {
            nRemainSize = PAGE_SIZE;
        }

        nCopyedSize += nRemainSize;
        SrcAddr += nRemainSize;
        DstAddr += nRemainSize;
    }

    status = STATUS_SUCCESS;
    return status;
}

有了封装好的SafeCopyMemory_R3_to_R0函数,那么接下来就是使用该函数实现应用层到内核层中的拷贝,为了能实现拷贝我们需要做以下几个准备工作;

  • 1.使用PsLookupProcessByProcessId函数通过进程ID查找到对应的EProcess结构体,以获取该进程在内核中的信息。
  • 2.使用KeStackAttachProcess函数将当前进程的执行上下文切换到指定进程的上下文,以便能够访问该进程的内存。
  • 3.使用RtlAllocateMemory函数在当前进程的内存空间中分配一块缓冲区,用于存储从指定进程中读取的数据。
  • 4.调用SafeCopyMemory_R3_to_R0函数将指定进程的内存数据拷贝到分配的缓冲区中。
  • 5.将缓冲区中的数据转换为BYTE类型的指针,并将其输出。

PsLookupProcessByProcessId函数用于通过进程ID查找到对应的EProcess结构体,这个结构体是Windows操作系统内核中用于表示一个进程的数据结构。

NTSTATUS status = PsLookupProcessByProcessId(ProcessId, &ProcessObject);
if (!NT_SUCCESS(status)) {
    return status;
}

KeStackAttachProcess函数将当前进程的执行上下文切换到指定进程的上下文,以便能够访问该进程的内存。这个函数也只能在内核态中调用。

KeStackAttachProcess(ProcessObject, &ApcState);

RtlAllocateMemory函数在当前进程的内存空间中分配一块缓冲区,用于存储从指定进程中读取的数据。这个函数是Windows操作系统内核中用于动态分配内存的函数,其中第一个参数TRUE表示允许操作系统在分配内存时进行页面合并,以减少内存碎片的产生。第二个参数nSize表示需要分配的内存空间的大小。如果分配失败,就需要将之前的操作撤销并返回错误状态。

PVOID pTempBuffer = RtlAllocateMemory(TRUE, nSize);
if (pTempBuffer == NULL) {
    KeUnstackDetachProcess(&ApcState);
    ObDereferenceObject(ProcessObject);
    return STATUS_NO_MEMORY;
}

SafeCopyMemory_R3_to_R0函数将指定进程的内存数据拷贝到分配的缓冲区中。

if (!SafeCopyMemory_R3_to_R0(ModuleBase, pTempBuffer, nSize)) {
    RtlFreeMemory(pTempBuffer);
    KeUnstackDetachProcess(&ApcState);
    ObDereferenceObject(ProcessObject);
    return STATUS_UNSUCCESSFUL;
}

最后,将缓冲区中的数据转换为BYTE类型的指针,并将其输出。需要注意的是,在返回之前需要先将当前进程的执行上下文切换回原先的上下文。

BYTE* data = (BYTE*)pTempBuffer;
KeUnstackDetachProcess(&ApcState);
ObDereferenceObject(ProcessObject);
return data;

如上实现细节用一段话总结,首先PsLookupProcessByProcessId得到进程EProcess结构,并KeStackAttachProcess附加进程,声明pTempBuffer指针用于存储RtlAllocateMemory开辟的内存空间,nSize则代表读取应用层进程数据长度,ModuleBase则是读入进程基址,调用SafeCopyMemory_R3_to_R0即可将应用层数据拷贝到内核空间,并最终BYTE* data转为BYTE字节的方式输出。这样就完成了将指定进程的内存数据拷贝到当前进程中的操作。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint("hello lyshark.com \n");

    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PEPROCESS eproc = NULL;
    KAPC_STATE kpc = { 0 };

    __try
    {
        // HANDLE 进程PID
        status = PsLookupProcessByProcessId((HANDLE)4556, &eproc);

        if (NT_SUCCESS(status))
        {
            // 附加进程
            KeStackAttachProcess(eproc, &kpc);

            // -------------------------------------------------------------------
            // 开始映射
            // -------------------------------------------------------------------

            // 将用户空间内存映射到内核空间
            PVOID pTempBuffer = NULL;
            ULONG nSize = 0x1024;
            ULONG_PTR ModuleBase = 0x0000000140001000;

            // 分配内存
            pTempBuffer = RtlAllocateMemory(TRUE, nSize);
            if (pTempBuffer)
            {
                // 拷贝数据到R0
                status = SafeCopyMemory_R3_to_R0(ModuleBase, (ULONG_PTR)pTempBuffer, nSize);
                if (NT_SUCCESS(status))
                {
                    DbgPrint("[*] 拷贝应用层数据到内核里 \n");
                }

                // 转成BYTE方便读取
                BYTE* data = pTempBuffer;

                for (size_t i = 0; i < 10; i++)
                {
                    DbgPrint("%02X \n", data[i]);
                }
            }

            // 释放空间
            RtlFreeMemory(pTempBuffer);

            // 脱离进程
            KeUnstackDetachProcess(&kpc);
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Driver->DriverUnload = UnDriver;
        return STATUS_SUCCESS;
    }

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

代码运行后即可将进程中0x0000000140001000处的数据读入内核空间并输出:

3.5.2 内核层映射到应用层

与上方功能实现相反SafeCopyMemory_R0_to_R3函数则用于将一个内核层中的缓冲区写出到应用层中,SafeCopyMemory_R0_to_R3函数接收源地址SrcAddr、要复制的数据长度Length以及目标地址DstAddr作为参数,其写出流程可总结为如下步骤:

  • 1.使用IoAllocateMdl函数分别为源地址SrcAddr和目标地址DstAddr创建两个内存描述列表(MDL)。

  • 2.使用MmBuildMdlForNonPagedPool函数,将源地址的MDL更新为描述非分页池的虚拟内存缓冲区,并更新MDL以描述底层物理页。

  • 3.通过两次调用MmGetSystemAddressForMdlSafe函数,分别获取源地址和目标地址的指针,即pSrcMdlpDstMdl

  • 4.使用MmProbeAndLockPages函数以写入方式锁定用户空间中pDstMdl指向的地址,并将它的虚拟地址映射到物理内存页,从而确保该内存页在复制期间不会被交换出去或被释放掉。

  • 5.然后使用MmMapLockedPagesSpecifyCache函数将锁定的用户空间内存页映射到内核空间,并返回内核空间中的虚拟地址。

  • 6.最后使用RtlCopyMemory函数将源地址的数据复制到目标地址。

  • 7.使用MmUnlockPages函数解除用户空间内存页的锁定,并使用MmUnmapLockedPages函数取消内核空间与用户空间之间的内存映射。

  • 8.最后释放源地址和目标地址的MDL,使用IoFreeMdl函数进行释放。

内存拷贝SafeCopyMemory_R0_to_R3函数,函数首先分配源地址和目标地址的MDL结构,然后获取它们的虚拟地址,并以写入方式锁定目标地址的MDL,最后使用RtlCopyMemory函数将源地址的内存数据拷贝到目标地址。拷贝完成后,函数解锁目标地址的MDL,并返回操作状态。

封装代码SafeCopyMemory_R0_to_R3()功能如下:

// 分配内存
void* RtlAllocateMemory(BOOLEAN InZeroMemory, SIZE_T InSize)
{
    void* Result = ExAllocatePoolWithTag(NonPagedPool, InSize, 'lysh');
    if (InZeroMemory && (Result != NULL))
        RtlZeroMemory(Result, InSize);
    return Result;
}

// 释放内存
void RtlFreeMemory(void* InPointer)
{
    ExFreePool(InPointer);
}

/*
将内存中的数据复制到R3中

SrcAddr  R0要复制的地址
DstAddr  返回R3的地址
Size     拷贝长度
*/
NTSTATUS SafeCopyMemory_R0_to_R3(PVOID SrcAddr, PVOID DstAddr, ULONG Size)
{
    PMDL  pSrcMdl = NULL, pDstMdl = NULL;
    PUCHAR pSrcAddress = NULL, pDstAddress = NULL;
    NTSTATUS st = STATUS_UNSUCCESSFUL;

    // 分配MDL 源地址
    pSrcMdl = IoAllocateMdl(SrcAddr, Size, FALSE, FALSE, NULL);
    if (!pSrcMdl)
    {
        return st;
    }

    // 该 MDL 指定非分页虚拟内存缓冲区,并对其进行更新以描述基础物理页。
    MmBuildMdlForNonPagedPool(pSrcMdl);

    // 获取源地址MDL地址
    pSrcAddress = MmGetSystemAddressForMdlSafe(pSrcMdl, NormalPagePriority);


    if (!pSrcAddress)
    {
        IoFreeMdl(pSrcMdl);
        return st;
    }

    // 分配MDL 目标地址
    pDstMdl = IoAllocateMdl(DstAddr, Size, FALSE, FALSE, NULL);
    if (!pDstMdl)
    {
        IoFreeMdl(pSrcMdl);
        return st;
    }

    __try
    {
        // 以写入的方式锁定目标MDL
        MmProbeAndLockPages(pDstMdl, UserMode, IoWriteAccess);

        // 获取目标地址MDL地址
        pDstAddress = MmGetSystemAddressForMdlSafe(pDstMdl, NormalPagePriority);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
    }

    if (pDstAddress)
    {
        __try
        {
            // 将源地址拷贝到目标地址
            RtlCopyMemory(pDstAddress, pSrcAddress, Size);
        }
        __except (1)
        {
            // 拷贝内存异常
        }
        MmUnlockPages(pDstMdl);
        st = STATUS_SUCCESS;
    }

    IoFreeMdl(pDstMdl);
    IoFreeMdl(pSrcMdl);

    return st;
}

调用该函数实现拷贝,此处除去附加进程以外,在拷贝之前调用了ZwAllocateVirtualMemory将内存属性设置为PAGE_EXECUTE_READWRITE可读可写可执行状态,然后在向该内存中写出pTempBuffer变量中的内容,此变量中的数据是0x90填充的区域。

此处的ZwAllocateVirtualMemory函数,用于在进程的虚拟地址空间中分配一块连续的内存区域,以供进程使用。它属于Windows内核API的一种,与用户态的VirtualAlloc函数相似,但是它运行于内核态,可以分配不受用户空间地址限制的虚拟内存,并且可以用于在驱动程序中为自己或其他进程分配内存。

函数的原型为:

NTSYSAPI NTSTATUS NTAPI ZwAllocateVirtualMemory(
  _In_    HANDLE    ProcessHandle,
  _Inout_ PVOID     *BaseAddress,
  _In_    ULONG_PTR ZeroBits,
  _Inout_ PSIZE_T   RegionSize,
  _In_    ULONG     AllocationType,
  _In_    ULONG     Protect
);

其中ProcessHandle参数是进程句柄,BaseAddress是分配到的内存区域的起始地址,ZeroBits指定保留的高位,RegionSize是分配内存大小,AllocationTypeProtect分别表示内存分配类型和内存保护属性。

ZwAllocateVirtualMemory函数成功返回NT_SUCCESS,返回值为0,否则返回相应的错误代码。如果函数成功调用,会将BaseAddress参数指向分配到的内存区域的起始地址,同时将RegionSize指向的值修改为实际分配到的内存大小。

当内存属性被设置为PAGE_EXECUTE_READWRITE之后,则下一步直接调用SafeCopyMemory_R0_to_R3进行映射即可,其调用完整案例如下所示;

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint("hello lyshark.com \n");

    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PEPROCESS eproc = NULL;
    KAPC_STATE kpc = { 0 };

    __try
    {
        // HANDLE 进程PID
        status = PsLookupProcessByProcessId((HANDLE)4556, &eproc);

        if (NT_SUCCESS(status))
        {
            // 附加进程
            KeStackAttachProcess(eproc, &kpc);

            // -------------------------------------------------------------------
            // 开始映射
            // -------------------------------------------------------------------

            // 将用户空间内存映射到内核空间
            PVOID pTempBuffer = NULL;
            ULONG nSize = 0x1024;
            PVOID ModuleBase = 0x0000000140001000;

            // 分配内存
            pTempBuffer = RtlAllocateMemory(TRUE, nSize);
            if (pTempBuffer)
            {
                memset(pTempBuffer, 0x90, nSize);

                // 设置内存属性 PAGE_EXECUTE_READWRITE
                ZwAllocateVirtualMemory(NtCurrentProcess(), &ModuleBase, 0, &nSize, MEM_RESERVE, PAGE_EXECUTE_READWRITE);
                ZwAllocateVirtualMemory(NtCurrentProcess(), &ModuleBase, 0, &nSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

                // 将数据拷贝到R3中
                status = SafeCopyMemory_R0_to_R3(pTempBuffer, &ModuleBase, nSize);
                if (NT_SUCCESS(status))
                {
                    DbgPrint("[*] 拷贝内核数据到应用层 \n");
                }
            }

            // 释放空间
            RtlFreeMemory(pTempBuffer);

            // 脱离进程
            KeUnstackDetachProcess(&kpc);
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Driver->DriverUnload = UnDriver;
        return STATUS_SUCCESS;
    }

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

拷贝成功后,应用层进程内将会被填充为Nop指令。

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

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

相关文章

freertos源码下载和目录结构分析

1、源码下载 下载网址&#xff1a;https://www.freertos.org/zh-cn-cmn-s/&#xff1b; 2、源码目录结构 3、关键的代码文件

全国行政区划2023年最新版

全国行政区划包含以下字段&#xff0c;行政区划第五级&#xff0c;省、市、县、乡镇、村。文章末尾已整理成sql文件。 父级行政代码,行政代码,邮政编码,区号,名称,简称,组合名,拼音,经度,纬度, 获取方式 关键词“行政区划”获取文件。 免费获取行政区划

我们常说的网络资产,具体是如何定义的?

文章目录 什么叫网络资产&#xff1f;官方定义的网络资产网络资产数字化定义推荐阅读 什么叫网络资产&#xff1f; 通过百度查询搜索什么叫网络资产&#xff1f;大体上都将网络资产归类为计算机网络中的各类设备。 基本上会定义网络传输通信架构中用到的主机、网络设备、防火…

通过注释来埋点

目录 开始 插件编写 功能一 功能二 功能三 合并功能 运行代码 总结 这篇文章主要讲如何根据注释&#xff0c;通过babel插件自动地&#xff0c;给相应函数插入埋点代码&#xff0c;在实现埋点逻辑和业务逻辑分离的基础上&#xff0c;配置更加灵活 这篇文章想要达到的效…

010.cat、find

1、用cat进行拼接 cat命令能够显示或拼接文件内容&#xff0c;不过它的能力远不止如此。比如说&#xff0c;cat能够将标准输入数据与文件数据组合在一起。通常的做法是将stdin重定向到一个文件&#xff0c;然后再合并两个文件。而cat命令一次就能搞定这些操作。 用cat读取文件…

Go fsnotify简介

fsnotify是一个用Go编写的文件系统通知库。它提供了一种观察文件系统变化的机制&#xff0c;例如文件的创建、修改、删除、重命名和权限修改。它使用特定平台的事件通知API&#xff0c;例如Linux上的inotify&#xff0c;macOS上的FSEvents&#xff0c;以及Windows上的ReadDirec…

中小企业如何最大程度地利用CRM系统的潜力?

在当今竞争激烈的商业世界中&#xff0c;客户关系管理&#xff08;CRM&#xff09;数字化转型已经成为大企业成功的重要秘诀。大型跨国公司如亚马逊、苹果和微软等已经在CRM数字化方面走在了前列&#xff0c;实现了高度个性化的客户体验&#xff0c;加强了客户忠诚度。 然而&a…

手把手云开发一个小程序-(二)-uniclould的购买和默认库的使用

一,前言 因为平时喜欢记录一些文案,看小说或者上网冲浪的时候,遇到拍案叫绝的文字,就会截图保存下来,但是时间久了,手机里截图保留了很多,却不会再去看,想删除又舍不得,于是就想着自己开发个文案记录的小程序.自用的同时让有同样需求的人也能用. 目前已经把第一个版本开发完了…

【教3妹学编程-算法题】最长奇偶子数组

3妹&#xff1a;2哥&#xff0c;你有没有看到新闻&#xff0c; 网红快乐小赵去世了。 2哥 :啊&#xff1f; 这么突然 3妹&#xff1a;是啊&#xff0c; 伤心&#xff0c;以前还特别喜欢他的作品&#xff0c;幽默搞笑。 2哥&#xff1a;哎&#xff0c;人有悲欢离合&#xff0c; …

Open3D 进阶(17)间接平差拟合二维直线

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。 一、算法原理 见:PCL 间接平差法拟合二维直线。 二、代码实现 import numpy as np import open3d as o3d import matplotlib.pyplot as plt

关于数据mysql ->maxwell->kafka的数据传输

个人名片&#xff1a; &#x1f405;作者简介&#xff1a;一名大三在校生&#xff0c;热爱生活&#xff0c;爱好敲码&#xff01; \ &#x1f485;个人主页 &#x1f947;&#xff1a;holy-wangle ➡系列内容&#xff1a; &#x1f5bc;️ tkinter前端窗口界面创建与优化 &…

鸿蒙原生应用开发-折叠屏、平板设备服务卡片适配

一、多设备卡片适配原则 为不同尺寸的卡片提供不同的功能 在卡片开发过程中请考虑适配不同尺寸的设备&#xff0c;特别是在折叠屏和平板设备上&#xff0c;设备屏幕尺寸的变化直接影响了卡片内容的展示。请发挥想象力设计具有自适应能力的卡片&#xff0c;避免在卡片内容不做…

git clone:SSL: no alternative certificate subject name matches target host name

git clone 时的常见错误&#xff1a; fatal: unable to access ‘https://ip_or_domain/xx/xx.git/’: SSL: no alternative certificate subject name matches target host name ‘ip_or_domain’ 解决办法&#xff1a; disable ssl verify git config --global http.sslVe…

[Jenkins] Docker 安装Jenkins及迁移流程

系统要求 最低推荐配置: 256MB可用内存1GB可用磁盘空间(作为一个Docker容器运行jenkins的话推荐10GB) 为小团队推荐的硬件配置: 1GB可用内存50 GB 可用磁盘空间 软件配置: Java 8—无论是Java运行时环境&#xff08;JRE&#xff09;还是Java开发工具包&#xff08;JDK&#xff…

前端 react 面试题 (一)

文章目录 vue与react的区别。react的生命周期有哪些及它们的作用。setState是同步的还是异步的。如何更新数据后&#xff0c;立刻获取最新的dom或者更新后的数据。使用回调函数&#xff1a;在生命周期方法中处理&#xff1a; 函数式组件和class组件的区别。class组件函数式组件…

【5G PHY】5G SS/PBCH块介绍(三)

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

C/C++ 实现获取硬盘序列号

获取硬盘的序列号、型号和固件版本号&#xff0c;此类功能通常用于做硬盘绑定或硬件验证操作&#xff0c;通过使用Windows API的DeviceIoControl函数与物理硬盘驱动程序进行通信&#xff0c;发送ATA命令来获取硬盘的信息。 以下是该程序的主要功能和流程&#xff1a; 定义常量…

使用 Redis 构建轻量的向量数据库应用:图片搜索引擎(二)

本篇文章我们来继续聊聊轻量的向量数据库方案&#xff1a;Redis&#xff0c;如何完成整个图片搜索引擎功能。 写在前面 在上一篇文章《使用 Redis 构建轻量的向量数据库应用&#xff1a;图片搜索引擎&#xff08;一&#xff09;》中&#xff0c;我们聊过了构建图片搜索引擎的…

深度学习入门(第二天)——走进深度学习的世界 神经网络模型

反向传播计算方法 简单的例子&#xff1a; 如何让 f 值更小&#xff0c;就是改变x、y、z&#xff0c;而损失函数也是这样&#xff0c;那么我们分别求偏导&#xff0c;则能得出每个值对结果的影响 链式法则 梯度是一步一步传的 复杂的例子&#xff1a; 神经网络整体架构 类生…

浪涌防护器件要选对,布局布线更重要!|深圳比创达电子EMC(上)

浪涌测试&#xff0c;作为最常见的EMC抗干扰测试项目之一&#xff0c;基本上是家用消费电子必测的项目&#xff1b;其测试目的是为了验证产品在承受外部的浪涌冲击时能否正常工作。 一、浪涌冲击产生机理及其防护设计 浪涌冲击主要包括雷击浪涌冲击、电力系统内部的开关噪声冲…