Windows NT 驱动程序的编译、安装、调试

简介: Windows 驱动分为两类,一类是从 Windows NT 遗留下来的驱动模型称为传统的 Windows NT 驱动程序模型,另一类是 Windows 添加了电源管理后的 KMDF (WDM)驱动程序。本文这里首先以最简单的 Windows NT 驱动模型为例介绍 Windows 驱动的简单编写、编译、安装及调试。

Windows NT 驱动介绍

Windows 驱动分为两类,一类是从 Windows NT 遗留下来的驱动模型称为传统的 Windows NT 驱动程序模型,另一类是 Windows 添加了电源管理后的 KMDF (WDM)驱动程序。本文这里首先以最简单的 Windows NT 驱动模型为例介绍 Windows 驱动的简单编写、编译、安装及调试。

NT 驱动代码分析

如果有学习过 Linux 驱动的同学,基本上会了解到驱动的编写其实都是内核规定好的 API 需要去声明和定义的,根据一定的驱动模型或者范例来进行编写即可实现自己定义的驱动功能。(可参考 Linux 驱动:Linux驱动开发——(Linux内核GPIO操作库函数)gpio(1))。

在 Linux 驱动中内核规定要添加驱动,需要自己实现驱动的入口函数和退出函数,然后用如下的宏定义进行声明:

module_init(led_init);    //驱动入口函数声明
module_exit(led_exit);    //驱动退出函数声明
MODULE_LICENSE("GPL");    //驱动遵循的开源规则声明
AI 代码解读

而在 Windows 驱动中也有类似的模块入口函数和模块退出函数。区别是在 Windows 驱动中函数入口都统一由一个固定的函数名称 DriverEntry,你只需要在自己的驱动代码中实现这个固定的函数入口(Windows 内核会在加载驱动后主动调用这个入口函数进行驱动的初始化操作),该函数的具体声明如下:

NTSTATUS DriverEntry(
  _In_ PDRIVER_OBJECT  DriverObject,
  _In_ PUNICODE_STRING RegistryPath
);
AI 代码解读
  • 参数

    • DriverObject [in]:指向 DRIVER_OBJECT 结构的指针,该结构表示驱动程序的 WDM 驱动程序对象。
    • RegistryPath [in]:指向 UNICODE_STRING 结构的指针,该结构指定注册表中驱动程序 的 Parameters 键 的路径。
  • 返回值

如果例程成功,则必须返回STATUS_SUCCESS。 否则,它必须返回 ntstatus.h 中定义的错误状态值之一。

Windows NT 模型驱动和 WDM 模型驱动都是由 DriverEntry 入口函数开始加载驱动和完成驱动最初的初始化操作的(这也是 Windows 内核固定会调用的驱动入口函数)。所以我们编写 Windows 驱动其实也是从最初的 DriverEntry 入口函数开始。

这里首先编写一个最简单的 Windows NT 驱动作为模型例程进行讲解,整个驱动源文件有两个文件组成:驱动源文件(helloNt.cpp)和驱动头文件(helloNt.h)。

这里最好参考《Windows 驱动开发环境搭建》这篇文章的内容已经搭建好了 Windows 驱动开发环境,并且使用 VS2019 创建 KMDF 驱动解决方案项目,如下图:

image.png

image.png

使用 VS2019 创建完成解决方案后会自动创建如下文件,这里由于我们想要创建一个最简单的 Windows NT 驱动,所以删除不需要的文件即可(Device.c、Device.h、Queue.c、Queue.h、helloNt.inf)。

image.png

删除完成后只保留 Driver.c 和 Driver.h

image.png

这里需要注意,将项目属性进行一些设置(平台参数 x64、编译将警告视为错误设置为否、链接将警告视为错误设置为否、取消 WPP trace 运行)

image.png

image.png

image.png

驱动模块具体代码内容如下:

Driver.c

#include "driver.h"


#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, CreateDevice)
#pragma alloc_text (PAGE, HelloDDKDispatchRoutine)
#pragma alloc_text (PAGE, HelloDDKUnload)
#endif

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
/*++

Routine Description:
    DriverEntry initializes the driver and is the first routine called by the
    system after the driver is loaded. DriverEntry specifies the other entry
    points in the function driver, such as EvtDevice and DriverUnload.

Parameters Description:

    DriverObject - represents the instance of the function driver that is loaded
    into memory. DriverEntry must initialize members of DriverObject before it
    returns to the caller. DriverObject is allocated by the system before the
    driver is loaded, and it is released by the system after the system unloads
    the function driver from memory.

    RegistryPath - represents the driver specific path in the Registry.
    The function driver can use the path to store driver related data between
    reboots. The path does not store hardware instance specific data.

Return Value:

    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise.

--*/
{
    NTSTATUS status;
    KdPrint(("Enter DriverEntry\n"));

    //注册其他驱动调用函数入口
    DriverObject->DriverUnload = HelloDDKUnload;
    DriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;
    DriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;
    DriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;

    //创建驱动设备对象
    status = CreateDevice(DriverObject);

    KdPrint(("DriverEntry end\n"));
    return status;
}




/************************************************************************
* 函数名称:CreateDevice
* 功能描述:初始化设备对象
* 参数列表:
      pDriverObject:从I/O管理器中传进来的驱动对象
* 返回 值:返回初始化状态
*************************************************************************/
#pragma INITCODE
NTSTATUS CreateDevice(
    IN PDRIVER_OBJECT    pDriverObject)
{
    NTSTATUS status;
    PDEVICE_OBJECT pDevObj;
    PDEVICE_EXTENSION pDevExt;

    //创建设备名称
    UNICODE_STRING devName;
    RtlInitUnicodeString(&devName, L"\\Device\\MyDDKDevice");

    //创建设备
    status = IoCreateDevice(pDriverObject,
        sizeof(DEVICE_EXTENSION),
        &devName,
        FILE_DEVICE_UNKNOWN,
        0, TRUE,
        &pDevObj);
    if (!NT_SUCCESS(status))
        return status;

    pDevObj->Flags |= DO_BUFFERED_IO;
    pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
    pDevExt->pDevice = pDevObj;
    pDevExt->ustrDeviceName = devName;
    //创建符号链接
    UNICODE_STRING symLinkName;
    RtlInitUnicodeString(&symLinkName, L"\\??\\HelloDDK");
    pDevExt->ustrSymLinkName = symLinkName;
    status = IoCreateSymbolicLink(&symLinkName, &devName);
    if (!NT_SUCCESS(status))
    {
        IoDeleteDevice(pDevObj);
        return status;
    }
    return STATUS_SUCCESS;
}

/************************************************************************
* 函数名称:HelloDDKUnload
* 功能描述:负责驱动程序的卸载操作
* 参数列表:
      pDriverObject:驱动对象
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
VOID HelloDDKUnload(IN PDRIVER_OBJECT pDriverObject)
{
    PDEVICE_OBJECT    pNextObj;
    KdPrint(("Enter DriverUnload\n"));
    pNextObj = pDriverObject->DeviceObject;
    while (pNextObj != NULL)
    {
        PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
            pNextObj->DeviceExtension;

        //删除符号链接
        UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
        IoDeleteSymbolicLink(&pLinkName);
        pNextObj = pNextObj->NextDevice;
        IoDeleteDevice(pDevExt->pDevice);
    }
}

/************************************************************************
* 函数名称:HelloDDKDispatchRoutine
* 功能描述:对读IRP进行处理
* 参数列表:
      pDevObj:功能设备对象
      pIrp:从IO请求包
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
    IN PIRP pIrp)
{
    KdPrint(("Enter HelloDDKDispatchRoutine\n"));
    NTSTATUS status = STATUS_SUCCESS;
    // 完成IRP
    pIrp->IoStatus.Status = status;
    pIrp->IoStatus.Information = 0;    // bytes xfered
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    KdPrint(("Leave HelloDDKDispatchRoutine\n"));
    return status;
}
AI 代码解读

Driver.h

#pragma once

#ifdef __cplusplus
extern "C"
{
#endif

#include <ntddk.h>
#include <NTDDK.h>

#ifdef __cplusplus
}
#endif 

#define arraysize(p) (sizeof(p)/sizeof((p)[0]))

typedef struct _DEVICE_EXTENSION {
    PDEVICE_OBJECT pDevice;
    UNICODE_STRING ustrDeviceName;
    UNICODE_STRING ustrSymLinkName;
} DEVICE_EXTENSION, * PDEVICE_EXTENSION;

NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDriverObject);
VOID HelloDDKUnload(IN PDRIVER_OBJECT pDriverObject);
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
    IN PIRP pIrp);

AI 代码解读

编译

这里在编译前,首先已经根据文章《Windows 驱动开发环境搭建》完成了基本的编译环境搭建。所以我们直接在 VS 2019 中将解决方案进行构建即可。

前面我们已经对项目的属性进行了设置,这里只要选取我们设置的平台 debug-X64,然后点击“生成”——“生成解决方案”/“重新生成解决方案”即可生成我们需要的驱动文件。

image.png

image.png

安装

对于我们自己编写的驱动程序,为了防止驱动程序的加载使用导致我们的操作系统崩溃(Windows 驱动程序加载后就会运行在 RING-0 内核空间中,这个空间中的程序崩溃会直接导致 Windows 操作系统崩溃,最常见的现象就是蓝屏)。所以我们需要搭建 Windows 虚拟机环境,然后在虚拟机中进行新驱动的安装调试。

由于 Windows NT 驱动程序实际上就是 Windows 服务驱动程序,我们这里编写的并没有真正与设备进行相关,所以你可以理解它是一个运行在 Windows 内核空间的服务程序。对于 Windows NT 驱动程序的安装,这里可以给出两种方式:

  • 通过工具 DriverMonitor 进行打开加载安装
  • 修改注册表,添加驱动服务,重启计算机使用 net start helloNt 命令进行加载启动

修改注册表进行安装

使用快捷键 Win+R 打开对话框后输入 regedit 运行后打开系统的注册表。在注册表中我们找到服务驱动程序加载的注册位置。

计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\helloNt

在其中添加一个新的服务程序项目,然后加入一下内容进行描述,注意其中的 ImagePath 描述内容为加载的驱动程序文件(helloNt.sys)。

image.png

其中涉及到驱动文件的路径,输入内容如下:
image.png

\??\C:\Tools\helloNt\helloNt\helloNt.sys

驱动文件存放位置:C:\Tools\helloNt\helloNt
在这里插入图片描述

注册表内容都添加配置完成后,我们重启计算机(重启才会使得注册表生效)。
重启完成后,使用快捷键 Win+X,选择 Windows powershell(管理员)打开终端对话框,使用命令 net start helloNt 启动加载驱动程序。

image.png

这时候驱动程序就已经加载启动了。我们可以打开“系统信息”工具来查看这个服务的状态。

image.png

可以看到这里已经启动了这个驱动程序。

使用工具 DriverMonitor 进行打开加载安装

本来这个 DriverMonitor 工具是 DriverStudio 工具集的一部分,但是这个 DriverStudio 工具集也已经被淘汰了,所以目前其实这个并不好找。我在 github 上找到一个仓库中存放有这部分的工具,所以可以从这里使用 git clone 进行下载使用。(https://github.com/HotIce0/windows_kernel_driver_learn_note.git

image.png

将这个工具下载下来解压后,打开可以看到一个简单的界面:

image.png

选择“File”,“Open Driver” 打开驱动文件。

image.png

image.png

然后选择“File”-“Start Driver”,即可完成驱动的加载和运行。

image.png

调试

这里的例程其实是从书籍《Windows 驱动开发技术详解》(张帆)中得到的,驱动加载的时候都是正常的,但是我们如果使用手动卸载启动时就会出现问题,使用 net 命令卸载驱动:

net stop helloNt

结果发现系统蓝屏了(Windows 崩溃了)

image.png

这时候就需要对驱动进行简单调试来找到出问题的地方。虽然我们在驱动中添加了 log 打印(KdPrint),但是当系统崩溃后,我们并不能及时看到 log 的打印输出(已经崩溃了还看个锤子,只能看到蓝屏)。针对这种情况就需要我们使用 windbg 双机调试来进行了。(双机调试环境请参考文章《windbg 双机调试环境搭建(虚拟机)》)

在宿主机上启动 windbg 后,与虚拟机建立连接。执行 windbg 传统调试步骤:

  1. 加载符号表(尤其是自己编译的驱动程序的符号表)
  2. 设置驱动模块入口断点,或者直接操作使系统崩溃(崩溃会自动停止在断点位置)
  3. 查看当前调用堆栈,参考源代码推断崩溃原因
  4. 修改源码,重新编译,加载验证

image.png

image.png

image.png

这里首先 break 下来,然后通过命令加载符号表,或者使用 GUI 操作加载符号表。

.srcpath+ "C:\Tools\helloNt\*.pdb"

image.png

image.png

由于是卸载操作引起的崩溃,所以我们在卸载函数这里添加断点:

image.png

执行 net stop helloNt 命令后,windbg 会自动跳转到断点(记载了符号表)也会显示对应的代码。

image.png

单步执行,然后发现崩溃点的调用栈:
image.png

image.png

这里发现是在调用 Unload() 中的 DeleteSymbolicLink 的时候出现的问题。回顾我们这里的代码:

VOID HelloDDKUnload(IN PDRIVER_OBJECT pDriverObject)
{
    PDEVICE_OBJECT    pNextObj;
    KdPrint(("Enter DriverUnload\n"));
    pNextObj = pDriverObject->DeviceObject;
    while (pNextObj != NULL)
    {
        PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
            pNextObj->DeviceExtension;
        UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
        IoDeleteSymbolicLink(&pLinkName);
        pNextObj = pNextObj->NextDevice;
        IoDeleteDevice(pDevExt->pDevice);
    }
}
AI 代码解读

发现这里调用了 IoDeleteSymbolicLink(&pLinkName);,然后我们在这里打印 pLinkName 的地址看一下这个地址空间是否正常可访问。

也可以使用 !analyze -v 进行分析(这个分析结果可供参考)
image.png

发现结果和我们预测的位置基本一致。那问题就出现在这个链接变量这里了。为什么我们卸载链接变量会崩溃呢?

image.png

通过 Unload 局部变量中的数据可以看到这里的 pDevExt->ustrSymLinkName; 内存是不可以访问的,也就是说在这里的时候已经出现问题了。回到源代码中查看初始化部分发现这个字符串变量是在 CreateDevice 的时候放进去的:

    //创建设备名称
    UNICODE_STRING devName;
    RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
    
    //创建设备
    status = IoCreateDevice( pDriverObject,
                        sizeof(DEVICE_EXTENSION),
                        &(UNICODE_STRING)devName,
                        FILE_DEVICE_UNKNOWN,
                        0, TRUE,
                        &pDevObj );
    if (!NT_SUCCESS(status))
        return status;

    pDevObj->Flags |= DO_BUFFERED_IO;
    pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
    pDevExt->pDevice = pDevObj;
    pDevExt->ustrDeviceName = devName;
AI 代码解读

这里创建了一个字符串变量 devName,然后将这个变量放到了 pDevExt 中。但是这里发现这个 devName 是这个函数的局部变量,这里放进去的只是字符串指针,而在后面的函数调用中取出了已经释放空间的指针进行使用肯定会有问题。

找到问题后,我们修改一下,把这里的局部变量修改成全局变量或者静态变量试一下:


UNICODE_STRING gdevName;
UNICODE_STRING gsymLinkName;

/************************************************************************
* 函数名称:CreateDevice
* 功能描述:初始化设备对象
* 参数列表:
      pDriverObject:从I/O管理器中传进来的驱动对象
* 返回 值:返回初始化状态
*************************************************************************/
#pragma INITCODE
NTSTATUS CreateDevice(
    IN PDRIVER_OBJECT    pDriverObject)
{
    NTSTATUS status;
    PDEVICE_OBJECT pDevObj;
    PDEVICE_EXTENSION pDevExt;

    //创建设备名称
    //UNICODE_STRING devName;
    RtlInitUnicodeString(&gdevName, L"\\Device\\MyDDKDevice");

    //创建设备
    status = IoCreateDevice(pDriverObject,
        sizeof(DEVICE_EXTENSION),
        &gdevName,
        FILE_DEVICE_UNKNOWN,
        0, TRUE,
        &pDevObj);
    if (!NT_SUCCESS(status))
        return status;

    pDevObj->Flags |= DO_BUFFERED_IO;
    pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
    pDevExt->pDevice = pDevObj;
    pDevExt->ustrDeviceName = gdevName;
    //创建符号链接
    //UNICODE_STRING symLinkName;
    RtlInitUnicodeString(&gsymLinkName, L"\\??\\HelloDDK");
    pDevExt->ustrSymLinkName = gsymLinkName;
    status = IoCreateSymbolicLink(&gsymLinkName, &gdevName);
    if (!NT_SUCCESS(status))
    {
        IoDeleteDevice(pDevObj);
        return status;
    }
    return STATUS_SUCCESS;
}
AI 代码解读

发现修改成全局变量后,结果还是一样的,这里的 memory 还是无法正常 read。

image.png

image.png

比较发现这里是由于这部分的 buffer 空间 read error,修改代码,将这部分的字符串初始化从这个 #pragma INITCODE 声明的 CreateDevice 函数中移到 DriverEntry 中:


NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
{
    NTSTATUS status;
    KdPrint(("Enter DriverEntry\n"));

    UNICODE_STRING devName;
    UNICODE_STRING symLinkName;
    RtlInitUnicodeString(&devName, L"\\Device\\MyDDKDevice");
    RtlInitUnicodeString(&symLinkName, L"\\??\\HelloDDK");

    //注册其他驱动调用函数入口
    DriverObject->DriverUnload = HelloDDKUnload;
    DriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;
    DriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;
    DriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;

    //创建驱动设备对象
    status = CreateDevice(DriverObject, devName, symLinkName);

    KdPrint(("DriverEntry end\n"));
    return status;
}

/************************************************************************
* 函数名称:CreateDevice
* 功能描述:初始化设备对象
* 参数列表:
      pDriverObject:从I/O管理器中传进来的驱动对象
* 返回 值:返回初始化状态
*************************************************************************/
#pragma INITCODE
NTSTATUS CreateDevice(
    IN PDRIVER_OBJECT    pDriverObject,
    IN UNICODE_STRING  devName,
    IN UNICODE_STRING  symLinkName)
{
    NTSTATUS status;
    PDEVICE_OBJECT pDevObj;
    PDEVICE_EXTENSION pDevExt;

    //创建设备名称
    //UNICODE_STRING devName;
    //RtlInitUnicodeString(&devName, L"\\Device\\MyDDKDevice");

    //创建设备
    status = IoCreateDevice(pDriverObject,
        sizeof(DEVICE_EXTENSION),
        &devName,
        FILE_DEVICE_UNKNOWN,
        0, TRUE,
        &pDevObj);
    if (!NT_SUCCESS(status))
        return status;

    pDevObj->Flags |= DO_BUFFERED_IO;
    pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
    pDevExt->pDevice = pDevObj;
    pDevExt->ustrDeviceName = devName;
    //创建符号链接
    //UNICODE_STRING symLinkName;
    //RtlInitUnicodeString(&symLinkName, L"\\??\\HelloDDK");
    pDevExt->ustrSymLinkName = symLinkName;
    status = IoCreateSymbolicLink(&symLinkName, &devName);
    if (!NT_SUCCESS(status))
    {
        IoDeleteDevice(pDevObj);
        return status;
    }
    return STATUS_SUCCESS;
}
AI 代码解读

image.png

并且可以看到加载和卸载驱动操作已经正常了。说明我们的判断是正确的。

image.png

目录
打赏
0
0
0
0
15
分享
相关文章
YashanDB Windows客户端安装
本文介绍YashanDB客户端(Windows)的安装、使用及卸载步骤。首先,下载并解压软件包至本地路径,配置环境变量。接着,通过cmd窗口使用yasql命令连接数据库,执行SQL操作。最后,卸载时删除相关环境变量和客户端目录。更多功能请参考官方文档。
YashanDB Windows客户端安装
Docker Desktop 4.38 安装与配置全流程指南(Windows平台)
Docker Desktop 是容器化应用开发与部署的一体化工具,支持本地创建、管理和运行 Docker 容器。4.38 版本新增 GPU 加速、WSL 2 性能优化和 Kubernetes 1.28 集群管理功能,适用于微服务开发和 CI/CD 流水线搭建。安装要求为 Windows 10 2004 及以上(64 位),需启用 Hyper-V 或 WSL 2。硬件最低配置为 4GB 内存、20GB 存储和虚拟化技术支持的 CPU。安装步骤包括启用系统功能、下载并运行安装程序,完成后配置镜像加速并验证功能。常见问题涵盖 WSL 2 安装不完整、磁盘空间清理及容器外网访问等。
709 11
Windows 7纯净版重装教程|附微软原版镜像下载+驱动安装避坑技巧
本文详细介绍如何安全、高效地重装电脑系统,解决蓝屏、崩溃等问题。基于10年经验,涵盖从官方镜像获取、启动盘制作、数据备份到系统部署的全流程,并针对老旧机型优化。提供驱动一键安装工具和系统激活指南,确保无后门风险。文中还列出常见问题解决方案及操作禁忌,帮助用户顺利完成系统重装,让电脑重获新生。建议收藏并转发给有需要的朋友,欢迎留言咨询疑难问题。
Windows用户必备:Postman v11详细安装指南与API测试入门教程(附官网下载
Postman是全球领先的API开发与测试工具,支持REST、SOAP、GraphQL等协议调试。2025年最新版v11新增AI智能生成测试用例、多环境变量同步等功能,适用于前后端分离开发、自动化测试、接口文档自动生成及团队协作共享API资源。本文详细介绍Postman的软件定位、核心功能、安装步骤、首次配置、基础使用及常见问题解答,帮助用户快速上手并高效利用该工具进行API开发与测试。
Windows下CUDA+pytorch安装
以下是关于在Windows下安装CUDA和PyTorch的简要介绍及参考链接:
74 0
Windows下CUDA+pytorch安装
Windows下Minio的安装以及基本使用
MinIO 是一个开源的云原生分布式对象存储系统,兼容亚马逊S3接口,适合存储大容量非结构化数据。本文介绍Windows下MinIO的安装与基本使用:通过以上步骤,您可以在Windows环境中成功安装并使用MinIO。
386 17
【MySQL基础篇】MySQL概述、Windows下载MySQL8.0超详细图文安装教程
在这一章节,主要介绍两个部分,数据库相关概念及MySQL数据库的介绍、下载、安装、启动及连接。接着,详细描述了MySQL 8.0的版本选择与下载,推荐使用社区版(免费)。安装过程包括自定义安装路径、配置环境变量、启动和停止服务、以及客户端连接测试。此外,还提供了在同一台电脑上安装多个MySQL版本的方法及卸载步骤。最后,解释了关系型数据库(RDBMS)的特点,即基于二维表存储数据,使用SQL语言进行操作,格式统一且便于维护。通过具体的结构图展示了MySQL的数据模型,说明了数据库服务器、数据库、表和记录之间的层次关系。
【MySQL基础篇】MySQL概述、Windows下载MySQL8.0超详细图文安装教程
Windows Server 安装 MySQL 8.0 详细指南
安装 MySQL 需要谨慎,特别注意安全配置和权限管理。根据实际业务需求调整配置,确保数据库的性能和安全。
364 9
WINDOWS安装eiseg遇到的问题和解决方法
通过本文的详细步骤和问题解决方法,希望能帮助你顺利在 Windows 系统上安装和运行 EISeg。
164 2
Windows server 2012R2系统安装远程桌面服务后无法多用户同时登录是什么原因?
【11月更文挑战第15天】本文介绍了在Windows Server 2012 R2中遇到的多用户无法同时登录远程桌面的问题及其解决方法,包括许可模式限制、组策略配置问题、远程桌面服务配置错误以及网络和防火墙问题四个方面的原因分析及对应的解决方案。
256 4
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等