tiechui_lesson05_内核小文件拷贝

news2025/1/14 1:18:19

主要学习在内核中的文件操作,包括文件的打开,创建,读取,写入,查询文件属性等。

涉及的API和宏函数

  • ZwOpenFile
  • ZwCreateFile
  • ZwQueryInformationFile
  • ZwReadFile
  • ZwWriteFile
  • ZwClose
  • InitializeObjectAttributes

1.文件的打开

ZwOpenFile

/************************************************************************
* 函数名称:ZwOpenFile
* 功能描述:打开文件
* 参数列表:
        FileHandle:返回打开的文件句柄
        DesiredAccess:打开的权限,一般设为GENERIC_ALL
        ObjectAttributes:objectAttributes结构
        IoStatusBlock:指向一个结构体的指针。该结构体指明打开文件的状态
        ShareAccess:共享的权限。可以是FILE_SHARE_READ或者FILE_SHARE_WRITE
        OpenOptions:打开选项,一般设为FILE_SYNCHRONOUS_IO_NONALERT
* 返回 值:指明文件是否被成功打开
*************************************************************************/
NTSTATUS ZwOpenFile(
    OUT PHANDLE FileHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes,
    OUT PIO_STATUS_BLOCK IoStatusBlock,
    IN ULONG ShareAccess,
    IN ULONG OpenOptions);

 使用示例:

OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK iostatus;
HANDLE hfile;
UNICODE_STRING logFileUnicodeString;

//初始化UNICODE_STRING字符串
RtlInitUnicodeString(&logFileUnicodeString, L"\\??\\C:\\1.log");
//或者写成 \\Device\\HarddiskVolume1\\1.log

//初始化objectAttributes
InitializeObjectAttributes(
    &objectAttributes,
    &logFileUnicodeString,
    OBJ_CASE_INSENSITIVE,
    NULL,
    NULL);

//创建文件
NTSTATUS ntStatus = ZwOpenFile(&hfile,
                               GENERIC_ALL,
                               &objectAttributes,
                               &iostatus,
                               FILE_SHARE_READ || FILE_SHARE_WRITE,
                               FILE_SYNCHRONOUS_IO_NONALERT);
if( NT_SUCCESS(ntStatus) )
{
    KdPrint(("Create FILE successfully!\n"));
}
else
{
    KdPrint(("Create FILE unsuccessfully!\n"));
}

//文件操作
//..........

//关闭文件句柄
ZwClose(hfile);

2.文件的创建

ZwCreateFile

/************************************************************************
* 函数名称:ZwCreateFile
* 功能描述:文件的创建
* 参数列表:
        FileHandle:返回打开文件的句柄
        DesiredAccess:对打开文件操作的描述,读,写或是其他。一般指定为GENERIC_READ 或 GENERIC_WRITE
        ObjectAttributes:是OBJECT_ATTRIBUTES结构的地址,该结构包含要打开的文件名
        IoStatusBlock:指向一个IO_STATUS_BLOCK结构,该结构接收ZwCreateFile操作的结果状态
        AllocationSize:是一个指针,指向一个64位整数,该数指定文件初始分配时的大小
                    该参数仅关系到创建或重写文件操作,如果忽略它,那么文件长度从0开始
                    病随着写入而增长
        FileAttributes:0或FILE_ATTRIBUTE_NORMAL,指定新创建文件的属性
        ShareAccess:FILE_SHARE_READ或0,指定文件的共享方式。
                    如果为写数据而打开文件,可能不希望其他线程访问该文件
        CreateDisposition:FILE_OPEN或FILE_OVERWRITE_IF,表明当指定文件存在或不存在时应如何处理
        CreateOptions:FILE_SYNCHARONOUS_IO_NONALERT,指定控制打开操作和句柄使用的附加标志位
        EaBuffer:一个指针,指向可选的扩展属性区
        EaLength:扩展属性区的长度
* 返回 值:    NTSTATUS
*************************************************************************/
NTSTATUS ZwCreateFile(
    OUT PHANDLE FileHandle, 
    IN ACCESS_MASK DesiredAccess, 
    IN POBJECT_ATTRIBUTES ObjectAttributes, 
    OUT PIO_STATUS_BLOCK IoStatusBlock, 
    IN PLARGE_INTEGER AllocationSize OPTIONAL, 
    IN ULONG FileAttributes, 
    IN ULONG ShareAccess, 
    IN ULONG CreateDisposition,
    IN ULONG CreateOptions, 
    IN PVOID EaBuffer OPTIONAL,
    IN ULONG EaLength);

/*
*备注:
* 1、CreateDisposition参数。
*     如果想打开文件,CreateDisposition参数设置成FILE_OPEN. 
*     如果想创建文件,CreateDisposition参数设置成FILE_OVERWRITE_IF
*        此时,无论文件是否存在,都会创建新文件
* 2、文件名是通过第三个参数ObjectAttributes
*     这个参数是一个OBJECT_ATTRIBUTES结构体
*     通过InitializeObjectAttributes初始化
*/

InitializeObjectAttributes

这实际是一个宏,用来初始化属性变量的内容:

/************************************************************************
* 函数名称:InitializeObjectAttributes
* 功能描述:初始化OBJECT_ATTRIBUTES结构体
* 参数列表:
        InitializedAttributes:返回的OBJECT_ATTRIBUTES结构体
        ObjectName:对象名称,用UNICODE_STRING描述,这里设置的是文件名
        Attributes:一般设置为OBJ_CASE_INSENSITIVE,对大小写敏感
        RootDirectory:一般设置为NULL
        SecurityDescriptor:一般设置为NULL
* 返回 值:相等的字节数
        不一致返回零        
*************************************************************************/
VOID InitializeObjectAttributes(
    OUT POBJECT_ATTRIBUTES InitializedAttributes,
    IN PUNICODE_STRING ObjectName,
    IN ULONG Attributes,
    IN HANDLE RootDirectory,
    IN PSECURITY_DESCRIPTOR SecurityDescriptor);

/*
*备注:
* 1、文件名[必须]是符号链接或者是设备名
* 2、例如:盘符 "c:",就是一个符号链接
*        这里应该用 "\??\c:" 代替 
*        "c:\1.log" 要写成 "\??\c:\1.log"
* 3、其中 "\??\c:" 是符号链接,内核会将它转换成设备名 "\Device\HarddiskColume1"
*/

3.获取文件属性 

ZwQueryInformationFile

/************************************************************************
* 函数名称:ZwQueryInformationFile
* 功能描述:获取文件属性
* 参数列表:
        FileHandle:文件句柄
        IoStatusBlock:返回设置的状态
        FileInformation:依据FileInformationClass不同而不同。作为输出信息
        Length:FileInformation数据的长度
        FileInformationClass:描述修改属性的类型
* 返回 值:设置属性查询
*************************************************************************/
NTSTATUS ZwQueryInformationFile(
            IN HANDLE FileHandle,
            OUT PIO_STATUS_BLOCK IoStatusBlock,
            OUT PVOID FileInformation,
            IN ULONG Length,
            IN FILE_INFORMATION_CLASS FileInformationClass);

4.文件的读取

ZwReadFile

/************************************************************************
* 函数名称:ZwReadFile
* 功能描述:文件的读操作
* 参数列表:
        FileHandle:文件打开的句柄
        Event:很少用到,一般设为NULL
        ApcRoutine:很少用到,一般设为NULL
        ApcContext:很少用到,一般设为NULL
        IoStatusBlock:记录些操作的状态。其中,IoStatusBlock.Infomation记录实际写了多少字节
        Buffer:从这个缓冲区开始开始从文件里读
        Length:准备读多少字节
        Byteoffset:从文件的多少偏移地址开始读
        Key:很少用到,一般设为NULL
* 返回 值:
*************************************************************************/
NTSTATUS ZwReadFile(
    IN HANDLE FileHandle,
    IN HANDLE Event OPTIONAL,
    IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
    IN PVOID ApcContext OPTIONAL,
    OUT PIO_STATUS_BLOCK IoStatusBlock,
    IN PVOID Buffer,
    IN ULONG Length,
    IN PLARGE_INTEGER Byteoffset OPTIONAL,
    IN PULONG Key OPTIONAL);

5.文件的写入

ZwWriteFile

/************************************************************************
* 函数名称:ZwWriteFile
* 功能描述:文件的写操作
* 参数列表:
        FileHandle:文件打开的句柄
        Event:很少用到,一般设为NULL
        ApcRoutine:很少用到,一般设为NULL
        ApcContext:很少用到,一般设为NULL
        IoStatusBlock:记录些操作的状态。其中,IoStatusBlock.Infomation记录实际写了多少字节
        Buffer:从这个缓冲区开始往文件里写
        Length:准备写多少字节
        Byteoffset:从文件的多少便宜地址开始写
        Key:很少用到,一般设为NULL
* 返回 值:
*************************************************************************/
NTSTATUS ZwWriteFile(
    IN HANDLE FileHandle,
    IN HANDLE Event OPTIONAL,
    IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
    IN PVOID ApcContext OPTIONAL,
    OUT PIO_STATUS_BLOCK IoStatusBlock,
    IN PVOID Buffer,
    IN ULONG Length,
    IN PLARGE_INTEGER Byteoffset OPTIONAL,
    IN PULONG Key OPTIONAL);

综合使用:实现文件拷贝

仅支持小文件,而且没有和应用层交互

#include <ntifs.h>
#include <windef.h>	//使用DWORD等类型

#define DEVICE_NAME L"\\Device\\MyFirstDeviceFilerw"		//设备名称
#define SYM_NAME	L"\\??\\MyFirstDeviceFilerw"			//符号链接

//定义自定义控制码 (做一个减法)
#define IOCTL_MUL	CTL_CODE(FILE_DEVICE_UNKNOWN,0x855,METHOD_BUFFERED, FILE_ANY_ACCESS)

//分发函数
NTSTATUS MyCreate(PDEVICE_OBJECT pdevice, PIRP pirp)
{
	UNREFERENCED_PARAMETER(pdevice);
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device has be opened!");
	pirp->IoStatus.Status = status;
	pirp->IoStatus.Information = 0;
	IoCompleteRequest(pirp, IO_NO_INCREMENT);

	return status;
}
NTSTATUS MyClearUp(PDEVICE_OBJECT pdevice, PIRP pirp)
{
	UNREFERENCED_PARAMETER(pdevice);
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device MyClearUp!");
	pirp->IoStatus.Status = status;
	pirp->IoStatus.Information = 0;
	IoCompleteRequest(pirp, IO_NO_INCREMENT);

	return status;
}
NTSTATUS MyClose(PDEVICE_OBJECT	pdevice, PIRP pirp)
{
	UNREFERENCED_PARAMETER(pdevice);
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device MyClose!");
	pirp->IoStatus.Status = status;
	pirp->IoStatus.Information = 0;
	IoCompleteRequest(pirp, IO_NO_INCREMENT);

	return status;
}
NTSTATUS MyRead(PDEVICE_OBJECT	pdevice, PIRP pirp)
{
	UNREFERENCED_PARAMETER(pdevice);
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device MyRead!");

	//获取当前IRP堆栈信息
	PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);

	//对应用户层的读请求
	ULONG readsize = pstack->Parameters.Read.Length;
	DbgPrint("用户请求读大小为%u\n", readsize);
	//对应用户层所分配的缓冲区的内存位置
	PCHAR readbuffer = pirp->AssociatedIrp.SystemBuffer;	

	// readbuffer的赋值实际上是对用户缓冲区的改变
	RtlCopyMemory(readbuffer,
		"This Message Come From Kernel.",
		strlen("This Message Come From Kernel."));

	pirp->IoStatus.Status = status;
	//对于Information的赋值是返回给用户程序实际读取的长度。
	pirp->IoStatus.Information = strlen("This Message Come From Kernel.");

	//输出下字符串的长度
	DbgPrint("Really Read Info Len is %lld\n", 
		strlen("This Message Come From Kernel."));
	IoCompleteRequest(pirp, IO_NO_INCREMENT);

	return status;
}
NTSTATUS MyWrite(PDEVICE_OBJECT	pdevice, PIRP pirp)
{
	UNREFERENCED_PARAMETER(pdevice);
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device MyWrite!");

	//获取当前IRP堆栈信息
	PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);

	//对应用户层的写请求
	ULONG writesize = pstack->Parameters.Write.Length;
	DbgPrint("用户请求写大小为%u\n", writesize);
	//对应用户层所分配的缓冲区的内存位置
	PCHAR writebuffer = pirp->AssociatedIrp.SystemBuffer;

	// 写入扩展设备之前先进行清0操作
	RtlZeroMemory(pdevice->DeviceExtension, 200);
	// writebuffer的数据写入到设备扩展里边
	RtlCopyMemory(pdevice->DeviceExtension,writebuffer,writesize);

	DbgPrint("写缓冲区内存地址:%p,设备扩展内容%s\n", writebuffer,
		(PCHAR)pdevice->DeviceExtension);

	pirp->IoStatus.Status = status;
	pirp->IoStatus.Information = 13;

	IoCompleteRequest(pirp, IO_NO_INCREMENT);

	return status;
}
NTSTATUS MyControl(PDEVICE_OBJECT	pdevice, PIRP pirp)
{
	UNREFERENCED_PARAMETER(pdevice);
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device MyControl!");

	PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);
	ULONG	iocode = pstack->Parameters.DeviceIoControl.IoControlCode;	// 获取控制码

	ULONG inlen = pstack->Parameters.DeviceIoControl.InputBufferLength;
	ULONG outlen = pstack->Parameters.DeviceIoControl.OutputBufferLength;
	ULONG ioinfo = 0;

	DbgPrint("InputBufferLength is %u\n", inlen);
	DbgPrint("OutputBufferLength is %u\n", outlen);

	switch (iocode)
	{
	case IOCTL_MUL:
	{
		//做一个减法

		DWORD indata = *(PDWORD)pirp->AssociatedIrp.SystemBuffer;
		DbgPrint("--Kernel Indata %d \n", indata);
		indata = indata * 5;
		*(PDWORD)pirp->AssociatedIrp.SystemBuffer = indata;
		ioinfo = 50;

		//别忘记break!
		break;
	}
	default:
		status = STATUS_UNSUCCESSFUL;
		ioinfo = 0;
		break;
	}

	pirp->IoStatus.Status = status;
	pirp->IoStatus.Information = ioinfo;
	IoCompleteRequest(pirp, IO_NO_INCREMENT);

	return status;
}


//内核文件拷贝
NTSTATUS KernelCopyFile(PWCHAR dstfile_path, PWCHAR sourcefile_path)
{
	NTSTATUS status = STATUS_SUCCESS;
	HANDLE hfile1 = NULL;
	UNICODE_STRING sourcefilepath = { 0 };
	OBJECT_ATTRIBUTES obja1 = { 0 };
	IO_STATUS_BLOCK iostack = { 0 };

	//初始化
	RtlInitUnicodeString(&sourcefilepath, sourcefile_path);
	InitializeObjectAttributes(&obja1, &sourcefilepath, 
		OBJ_CASE_INSENSITIVE |OBJ_KERNEL_HANDLE, NULL, NULL);
	
	//打开文件句柄
	status = ZwOpenFile(
		&hfile1, 
		GENERIC_ALL, 
		&obja1,
		&iostack, 
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		FILE_SYNCHRONOUS_IO_NONALERT
	);

	if (!NT_SUCCESS(status))
	{
		DbgPrint("ZwOpenFile 文件打开失败 错误码 %x \n",status);
		return status; 
	}

	//查询文件大小
	FILE_STANDARD_INFORMATION fsiFileInfo = { 0 };
	status = ZwQueryInformationFile(
		hfile1, 
		&iostack,
		&fsiFileInfo,
		sizeof(FILE_STANDARD_INFORMATION),
		FileStandardInformation
	);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("ZwQueryDirectoryFile 查询失败 错误码 %x \n", status);
		ZwClose(hfile1);
		return status;
	}

	//查询成功过后申请缓冲区放置查询内容
	PVOID filebuffer = NULL;
	filebuffer = ExAllocatePoolWithTag(NonPagedPool,
		fsiFileInfo.EndOfFile.QuadPart, '1212');
	if (!filebuffer)
	{
		DbgPrint("filebuffer 缓冲区申请失败 错误码 %x \n", status);
		ZwClose(hfile1);
		return status;
	}

	RtlZeroMemory(filebuffer, fsiFileInfo.EndOfFile.QuadPart);

	//读文件
	LARGE_INTEGER readoffset = { 0 };
	readoffset.QuadPart = 0;	//从0开始读
	status = ZwReadFile(
		hfile1, 
		NULL, 
		NULL,
		NULL,
		&iostack,
		filebuffer,
		(ULONG)fsiFileInfo.EndOfFile.QuadPart, 
		&readoffset,
		NULL
	);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("读文件失败 错误码 %x \n", status);
		ZwClose(hfile1);
		ExFreePoolWithTag(filebuffer, '1212');
		return status;
	}

	DbgPrint("Ioinfo--- %lld", iostack.Information);

	ZwClose(hfile1);

	//创建新文件
	HANDLE hfile2 = NULL;
	UNICODE_STRING dstfilepath = { 0 };
	OBJECT_ATTRIBUTES obja2 = { 0 };
	IO_STATUS_BLOCK iostack2 = { 0 };

	RtlInitUnicodeString(&dstfilepath, dstfile_path);
	InitializeObjectAttributes(&obja2, &dstfilepath,
		OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);	//文件操作描述
	// 参数好多!
	status = ZwCreateFile(
		&hfile2, 
		GENERIC_ALL, 
		&obja2, 
		&iostack2,
		NULL, 
		FILE_ATTRIBUTE_NORMAL, 
		FILE_SHARE_WRITE,
		FILE_SUPERSEDE,
		FILE_SYNCHRONOUS_IO_NONALERT, 
		NULL, 
		0
	);
	
	if (!NT_SUCCESS(status))
	{
		DbgPrint("创建文件失败   错误码 %x \n", status);
		ExFreePoolWithTag(filebuffer, '1212');
		return status;
	}

	// 写入文件
	LARGE_INTEGER writeoffset = { 0 };
	writeoffset.QuadPart = 0;
	status = ZwWriteFile(
		hfile2, 
		NULL, 
		NULL, 
		NULL,
		&iostack2,
		filebuffer, 
		(ULONG)fsiFileInfo.EndOfFile.QuadPart, 
		&writeoffset,
		NULL
	);

	if (!NT_SUCCESS(status))
	{
		DbgPrint("写入失败 错误码 %x \n", status);
		ExFreePoolWithTag(filebuffer, '1212');
		ZwClose(hfile2);
		return status;
	}

	DbgPrint("write length = %lld ", iostack2.Information);

	ExFreePoolWithTag(filebuffer, '1212');
	ZwClose(hfile2);

	return status;
}

//卸载函数
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
	DbgPrint("basedriver 卸载驱动\n");
	if (DriverObject->DeviceObject)
	{
		IoDeleteDevice(DriverObject->DeviceObject);
	}
	UNICODE_STRING symLink = RTL_CONSTANT_STRING(SYM_NAME);
	IoDeleteSymbolicLink(&symLink);
}

//入口函数
NTSTATUS DriverEntry(
	IN PDRIVER_OBJECT DriverObject, 
	IN PUNICODE_STRING RegistryPath
)
{
	UNREFERENCED_PARAMETER(RegistryPath);
	//注册卸载函数
	DriverObject->DriverUnload = DriverUnload;

	NTSTATUS status = STATUS_SUCCESS;
	PDEVICE_OBJECT pdevice;		// 用来接收创建的设备对象
	UNICODE_STRING devicename = { 0 };
	RtlInitUnicodeString(&devicename, DEVICE_NAME);

	//创建设备对象
	status = IoCreateDevice(DriverObject, 
							200,	// 定义设备扩展的大小,用来存放写入的数据
							&devicename, 
							FILE_DEVICE_UNKNOWN, 
							0, 
							TRUE, 
							&pdevice);

	if (!NT_SUCCESS(status))
	{
		KdPrint(("IoCreateDevice 虚拟设备打开失败 状态码 (0x%08X)\n",status));
		DbgPrint("IoCreateDevice 虚拟设备打开失败 状态码 (0x%08X)\n", status);
	}

	if (pdevice->Flags)
	{
		//不要忘了设备对象的读写方式,否则会蓝屏
		pdevice->Flags |= DO_BUFFERED_IO;	// 缓冲区方式的读写
	}



	//
	//创建成功,创建符号链接
	//
	UNICODE_STRING symname = { 0 };
	RtlInitUnicodeString(&symname, SYM_NAME);
	status = IoCreateSymbolicLink(&symname, &devicename);	// 符号链接名 设备名
	if (!NT_SUCCESS(status))
	{
		KdPrint(("IoCreateSymbolicLink 符号链接创建失败 状态码 (0x%08X)", status));
		DbgPrint("IoCreateSymbolicLink 符号链接创建失败 状态码 (0x%08X)", status);
	}

	//设置分发例程
	DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreate;
	DriverObject->MajorFunction[IRP_MJ_CLEANUP] = MyClearUp;
	DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyClose;
	DriverObject->MajorFunction[IRP_MJ_READ] = MyRead;
	DriverObject->MajorFunction[IRP_MJ_WRITE] = MyWrite;
	DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyControl;

	KernelCopyFile(L"\\??\\C:\\888.exe", L"\\??\\C:\\567.exe");

	return 0;
}

效果:

小结

这节课主要是使用内核API了做些处理,对于文件来说还是和应用层类似,注意文件句柄的创建和销毁,还有对文件操作的时的权限设置要符合需求。

另外还可以拓展,通过设定一个临时长度循环读写来拷贝大文件,还有和应用层进行应用交互文件地址等。看完系列课程有时间再做😉😉😉

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

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

相关文章

学习日记,java学习

题目 生病了,可能是羊了,喉咙好干,好难受 每日一题 1010. 总持续时间可被 60 整除的歌曲 难度中等268收藏分享切换为英文接收动态反馈 在歌曲列表中&#xff0c;第 i 首歌曲的持续时间为 time[i] 秒。 返回其总持续时间&#xff08;以秒为单位&#xff09;可被 60 整除的…

vue+elementui在线教学互动学习考试系统nodejs师生互动交流网站kpm58

语言 node.js 框架&#xff1a;Express 前端:Vue.js 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat 开发软件&#xff1a;VScode 前端vueelementui, (1) vue引入elementui 1.使用npm安装element-ui npm i element-ui -S 2.在main.js中导入使用vue import element-u…

Android WorldWind的使用与加载数方瓦片

明确下定义&#xff0c;数方瓦片也叫五层十五级瓦片。现记录下如何在World Wind Android中加载数方瓦片的方法。 原理 加载数方瓦片最核心的方法是WorldWindAndroid加载图片的方法&#xff0c;更加详细的使用说明请访问worldwind官网。核心代码如下&#xff1a; Sector sect…

Daimayuan Online Judge #613. 好序列(思维题 暴力/启发式分裂)

题目 思路来源 知乎严格鸽 (暴力/启发式分裂)代码源每日一题 Div1 好序列 - 知乎 题解 启发式分裂&#xff0c;可以认为是启发式合并的逆过程 比较直白的想法是找到第一个只出现一次的数的位置x&#xff0c;然后分治[1,x-1]和[x1,n] 但是这样最坏复杂度是O(n^2)的&#xf…

数字化转型对企业的好处有哪些(上)

过时的技术、流程和客户关系管理困难是当前阻碍企业增长的主要障碍&#xff0c;CRM系统是企业数字化转型的主要工具&#xff0c;帮助企业简化操作、提高效率&#xff0c;为客户提供更加卓越的客户体验。下面说说&#xff0c;企业数字化转型的十大好处。 放眼全球来看&#xff…

第四十三章 Unity 开关 (Toggle) UI

本章节我们介绍开关 (Toggle)和开关组 (Toggle Group)。首先&#xff0c;我们点击菜单栏“GameObject”->“UI”->“Toggle”&#xff0c;然后调整它的位置&#xff0c;效果如下所示 相信大家在很多网页中也看到过类似的UI元素&#xff0c;它通常用于让用户勾选某些选项。…

计算机二级精选习题精讲精练

请点击↑关注、收藏&#xff0c;本博客免费为你获取精彩知识分享&#xff01;有惊喜哟&#xff01;&#xff01; 1、在线性表的链式存储结构中&#xff0c;其存储空间一般是不连续的&#xff0c;并且( ) A&#xff09;前件结点的存储序号大于后件结点的存储序号 B&#x…

java排序算法精讲

排序算法 概要一、冒泡排序概念实现步骤 代码 二、选择排序概念实现步骤 代码 三、插入排序概念实现步骤 代码 四、快速排序概念实现步骤 代码 五、归并排序概念实现步骤 代码 六、堆排序概念实现步骤 代码 总结以二维表表现出各个排序的关系 概要 Java是一种面向对象的编程语言…

一篇文章带您区分GNSS欺骗模拟测试的两种方式

写在前面 注意&#xff1a;提供的设备与案例、使用指南等指导性文件是为了在测试环境中对接收机的抗干扰能力进行验证&#xff0c;而非出于欺骗或干扰真实环境中的GNSS信号的目的&#xff01;请确保通过线缆连接应用或暗室应用&#xff0c;若因为违规使用产生的任何法律后果和…

携带数据的Ajax POST请求

前端页面代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title>发送ajax POST请求 看如何携带数据</title> <script type"text/javascript"> …

MES管理系统有什么功能?前期实施MES需要做些什么

MES系统是在制造业数字化的环境下&#xff0c;围绕生产制造执行而开发的一套生产管理系统。它以车间为管理核心&#xff0c;通过集成各信息系统&#xff0c;整合企业资源&#xff0c;实现从订单下达到产品完成的整个生产制造过程的数字化管理。 MES系统在实施前需要进行各种准备…

“UTONMOS吧”的出现,能否唤起百度贴吧曾经的辉煌?

百度贴吧&#xff0c;诞生于一个中文互联网尚且信息不足的时代&#xff0c;并建立起了一个庞大的社区。然而进入自媒体时代&#xff0c;贴吧这个大广场日渐冷清&#xff0c;2015年月活用户超过3亿&#xff0c;到2021年已不足4000万。 梗的流行&#xff0c;带动一大批人开始网上…

OpenShift 4 - KubeVirt 虚机使用的磁盘和卷

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在 OpenShift 4.12 环境中验证 文章目录 KubeVirt 虚机的磁盘和卷Disk 磁盘Volume 卷 磁盘和卷示例containerDisk 卷示例cloudInitNoCloud 卷示例dataVolume 卷示例基于容器镜像基于 qcow2 文件基于 DataSo…

放大招:三步搞定ChatGPT提示词,轻松写出高质量提问,最新经验分享

在撰写ChatGPT提示语的时候&#xff0c;有一个基本的三层结构&#xff0c;经过在工作和生活上的实践&#xff0c;有助于解决大多数不同类型的任务。 尤其在你的问题有点复杂&#xff0c;想不清楚要怎么问比较好的时候&#xff0c;通过三层结构&#xff0c;让我们有结构的提出问…

力扣简单题

目录 9、回文数 13、罗马数字转整数 14、最长公共前缀 20、有效的括号 58、最后一个单词的长度 66、加一 69、x的平方根 94、二叉树的中序遍历 100、相同的树 202、快乐数 205、同构字符串 242、有效的字母异位词 258、各位相加 268、丢失的数字 290、单词…

Github 使用教学

&#x1f4ad; 写在前面&#xff1a;本章我们将介绍 Git 的基本使用方法&#xff0c;包括注册 GitHub 账号、设置 Git、创建本地存储库、复制本地存储库、导入远程存储库、追加、提交、合并等常用操作。我们还会教你如何在 GitHub 上创建项目&#xff0c;使用 git clone 命令克…

盘“底座”,盘出新生意经

本文转自首席信息官 作者 徐蕊 导读 卖“底座”&#xff0c;这是一门新的生意&#xff0c;也是用友与友商差异化的商业竞争优势所在。 大型企业都在建“数智化底座” 有这样两类企业&#xff0c;他们截然不同&#xff0c;但在数智化的建设上殊途同归。 随着中国经济的发展&a…

SLAM论文速递:经典动态SLAM解析—(2021)DP-SLAM:面向动态环境的移动概率视觉SLAM—5.08(1)

论文信息 题目&#xff1a; DP-SLAM:A visual SLAM with moving probability towards dynamic environments DP-SLAM:面向动态环境的移动概率视觉SLAM论文地址&#xff1a; 发表期刊&#xff1a; 标签 语义分割几何约束、动态概率传播、 摘要 文中提出了一种基于稀疏特征的视觉…

DDD分层架构浅析

大家好&#xff0c;我是易安&#xff01;今天我们聊下DDD分层架构 微服务架构模型有好多种&#xff0c;例如整洁架构、CQRS和六边形架构等等。每种架构模式虽然提出的时代和背景不同&#xff0c;但其核心理念都是为了设计出“高内聚低耦合”的架构&#xff0c;轻松实现架构演进…

【P12】JMeter 准确的吞吐量定时器(Precise Throughput Timer)

&#xff08;1&#xff09;、测试计划右键 <<< 添加 <<< 定时器 <<< 准确的吞吐量定时器&#xff08;Precise Throughput Timer&#xff09; 目标吞吐量&#xff08;每个“吞吐期”的样本&#xff09;&#xff1a;15.0 吞吐量周期&#xff08;秒&a…