当前位置: 首页 > news >正文

C/C++ 开发SCM服务管理组件

SCM(Service Control Manager)服务管理器是 Windows 操作系统中的一个关键组件,负责管理系统服务的启动、停止和配置。服务是一种在后台运行的应用程序,可以在系统启动时自动启动,也可以由用户或其他应用程序手动启动。本篇文章中,我们将通过使用 Windows 的服务管理器(SCM)提供的API接口,实现一个简单的服务管理组件的编写。

服务管理器的主要功能包括:

  1. 服务启动和停止: SCM 管理系统服务的启动和停止。在系统启动时,SCM 会根据每个服务的配置启动相应的服务。用户也可以通过服务管理器手动启动或停止服务。
  2. 服务配置: SCM 管理服务的配置信息,包括服务的启动类型(如自动、手动、禁用)、服务的依赖关系、服务的用户身份等。
  3. 服务状态监控: SCM 监控运行中服务的状态。服务可以处于运行、暂停、停止等状态。SCM 提供 API 函数,允许应用程序查询和控制服务的状态。
  4. 事件日志: SCM 记录服务启动、停止等事件到系统的事件日志中,这有助于故障排查和系统管理。
  5. 服务通知: SCM 允许应用程序注册服务状态变化的通知,以便及时响应服务状态的改变。
  6. 服务安全性: SCM 确保服务以适当的权限和身份运行,以保障系统的安全性。

开发者可以通过使用 Windows API 提供的相关函数(例如 OpenSCManagerCreateServiceStartService 等)与 SCM 进行交互,管理系统中的服务。这些 API 函数允许开发者创建、配置、启动、停止和查询服务,以及监控服务的状态变化。

枚举SCM系统服务

Windows 的服务控制管理器(SCM)允许开发者通过 EnumServicesStatus 函数来枚举系统中正在运行的服务。这个功能非常有用,可以用于监控系统中的服务状态、获取服务的详细信息等。在这篇文章中,我们将学习如何使用 EnumServicesStatus 函数来实现对 SCM 系统服务的枚举,并获取相关信息。

OpenSCManager 用于打开服务控制管理器数据库,并返回一个指向服务控制管理器的句柄。通过这个句柄,你可以进行对服务的查询、创建、启动、停止等操作。

以下是 OpenSCManager 函数的原型:

SC_HANDLE OpenSCManager(LPCTSTR lpMachineName,LPCTSTR lpDatabaseName,DWORD   dwDesiredAccess
);
  • lpMachineName: 指定远程计算机的名称。如果为 NULL,表示本地计算机。

  • lpDatabaseName: 指定要打开的服务控制管理器数据库的名称。通常为 SERVICES_ACTIVE_DATABASE

  • dwDesiredAccess
    

    : 指定所请求的访问权限。可以是以下之一或它们的组合:

    • SC_MANAGER_CONNECT: 允许连接服务控制管理器。
    • SC_MANAGER_CREATE_SERVICE: 允许创建服务。
    • SC_MANAGER_ENUMERATE_SERVICE: 允许枚举服务。
    • SC_MANAGER_LOCK: 允许锁定服务数据库。
    • SC_MANAGER_QUERY_LOCK_STATUS: 允许查询服务数据库的锁定状态。
    • SC_MANAGER_MODIFY_BOOT_CONFIG: 允许修改系统启动配置。
    • SC_MANAGER_ALL_ACCESS: 允许执行上述所有操作。

函数返回一个指向服务控制管理器的句柄 (SC_HANDLE)。如果操作失败,返回 NULL,可以通过调用 GetLastError 函数获取错误代码。

EnumServicesStatus 用于枚举指定服务控制管理器数据库中的服务。通过这个函数,你可以获取正在运行的服务的信息,如服务的名称、显示名称、状态等。

以下是 EnumServicesStatus 函数的原型:

BOOL EnumServicesStatus(SC_HANDLE hSCManager,DWORD     dwServiceType,DWORD     dwServiceState,LPENUM_SERVICE_STATUS lpServices,DWORD     cbBufSize,LPDWORD   pcbBytesNeeded,LPDWORD   lpServicesReturned,LPDWORD   lpResumeHandle
);
  • hSCManager: 指定服务控制管理器的句柄,通过 OpenSCManager 函数获取。
  • dwServiceType: 指定服务的类型,如 SERVICE_WIN32
  • dwServiceState: 指定服务的状态,如 SERVICE_STATE_ALL
  • lpServices: 指向 ENUM_SERVICE_STATUS 结构体数组的指针,用于接收服务的信息。
  • cbBufSize: 指定 lpServices 缓冲区的大小,以字节为单位。
  • pcbBytesNeeded: 接收所需的缓冲区大小,以字节为单位。
  • lpServicesReturned: 接收实际返回的服务数。
  • lpResumeHandle: 用于标识服务的遍历位置。

函数返回 BOOL 类型,如果调用成功,返回 TRUE,否则返回 FALSE。如果函数返回 FALSE,可以通过调用 GetLastError 函数获取错误代码。

上述EnumServicesStatus中的第二个参数dwServiceType非常重要,在 Windows 操作系统中,服务的启动类型和服务类型是通过服务的标志(Service Flags)来指定的。这些标志是用于定义服务的性质和启动方式的。以下是其中几个标志的含义:

  1. 0x0 (SERVICE_KERNEL_DRIVER): 设备驱动程序。这种服务类型表示一个内核模式的设备驱动程序。
  2. 0x2 (SERVICE_FILE_SYSTEM_DRIVER): 内核模式文件系统驱动程序。这种服务类型表示一个内核模式的文件系统驱动程序。
  3. 0x8 (SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER): 文件系统识别器驱动程序。这种服务类型表示一个同时具有文件系统驱动程序和文件系统识别器驱动程序功能的服务。
  4. 0x10 (SERVICE_WIN32_OWN_PROCESS): 独占一个进程的服务。这种服务类型表示服务运行在自己的进程中。
  5. 0x20 (SERVICE_WIN32_SHARE_PROCESS): 与其他服务共享一个进程的服务。这种服务类型表示服务可以与其他服务运行在同一个进程中。

需要注意的是,上述标志可以通过按位 OR 运算来组合使用,以表示服务的多个特性。例如,SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS 表示一个交互式服务,即运行在自己的进程中并与桌面交互。

除了上述标志之外,还有一些其他的标志,如:

  • SERVICE_INTERACTIVE_PROCESS (0x100): 交互式服务。表示服务可以与桌面进行交互,通常用于服务需要显示用户界面的情况。
  • SERVICE_AUTO_START (0x2): 自动启动。表示服务会在系统启动时自动启动。
  • SERVICE_DEMAND_START (0x3): 手动启动。表示服务需要由用户手动启动。
  • SERVICE_DISABLED (0x4): 禁用。表示服务被禁用,不会自动启动。

这些标志允许开发者灵活地定义服务的启动方式和性质。在使用服务相关的 API 函数时,这些标志会在函数参数中进行指定。例如,在使用 CreateService 函数时,可以通过设置 dwServiceTypedwStartType 参数来指定服务的类型和启动方式。

如下代码则实现了对系统内特定服务的枚举功能,通过向Enum_Services函数中传入不同的参数来实现枚举不同的服务类型;

#include <stdio.h>
#include <Windows.h>void Enum_Services(DWORD dwServiceType)
{DWORD ServiceCount = 0, dwSize = 0;LPENUM_SERVICE_STATUS lpInfo;SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);BOOL bRet = EnumServicesStatus(hSCM, dwServiceType, SERVICE_STATE_ALL, NULL, 0, &dwSize, &ServiceCount, NULL);if (!bRet && GetLastError() == ERROR_MORE_DATA){// 分配缓冲区,保存服务列表lpInfo = (LPENUM_SERVICE_STATUS)(new BYTE[dwSize]);bRet = EnumServicesStatus(hSCM, dwServiceType, SERVICE_STATE_ALL, (LPENUM_SERVICE_STATUS)lpInfo,dwSize, &dwSize, &ServiceCount, NULL);if (NULL == hSCM){return;}// 逐个遍历获取服务信息for (int x = 0; x < ServiceCount; x++){printf("名称:%-30s 名称: %-30s 状态: ", lpInfo[x].lpServiceName, lpInfo[x].lpDisplayName);switch (lpInfo[x].ServiceStatus.dwCurrentState){case SERVICE_PAUSED:  printf("暂停 \n"); break;case SERVICE_STOPPED: printf("停止 \n"); break;case SERVICE_RUNNING: printf("运行 (*) \n"); break;default: printf("其他 \n");}}delete lpInfo;}CloseServiceHandle(hSCM);
}int main(int argc, char *argv[])
{// 0x0 => 设备驱动程序// 0x2=> 内核模式文件系统驱动程序// 0x8 => 文件系统识别器驱动程序// 0x10 => 独占一个进程的服务// 0x20 => 与其他服务共享一个进程的服务Enum_Services(0x10);system("pause");return 0;
}

我们传入0x10则代表枚举当前系统中的独占一个进程的服务,代码需要使用管理员权限运行,输出效果图如下所示;

编写SCM系统服务

Windows 服务程序的主体框架需要包括关键的两个函数,其中ServiceMain标志着服务程序的入口,而ServiceCtrlHandle则是服务程序的控制处理流程,最后的TellSCM函数则用于通知SCM服务的当前状态,当然了TellSCM可以单独出来也可以写在ServiceCtrlHandle都可以,任何一个正常的服务程序都必须包含这两个关键位置,并且需要将该函数导出,首先展示核心API函数的定义信息。

SERVICE_TABLE_ENTRY 用于定义服务表的结构体。服务表是一个包含服务入口函数和服务名的数组,它告诉 SCM (服务控制管理器)哪个服务程序入口函数与哪个服务相关联。

以下是 SERVICE_TABLE_ENTRY 结构体的定义:

typedef struct _SERVICE_TABLE_ENTRY {LPSTR lpServiceName;          // 服务名LPSERVICE_MAIN_FUNCTION lpServiceProc;  // 服务入口函数
} SERVICE_TABLE_ENTRY, *LPSERVICE_TABLE_ENTRY;
  • lpServiceName: 指向服务名的指针。服务名是服务在 SCM 中的标识符,可以通过该名字启动、停止、控制服务等。
  • lpServiceProc: 指向服务入口函数的指针。该函数是服务的主要执行点,当 SCM 启动服务时会调用该函数。

在主程序中,你通过创建 SERVICE_TABLE_ENTRY 数组来定义服务表,然后将其传递给 StartServiceCtrlDispatcher 函数。代码中,服务表包含一个 SERVICE_TABLE_ENTRY 结构体:

SERVICE_TABLE_ENTRY stDispatchTable[] = {{ g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain },{ NULL, NULL }
};
  • g_szServiceName: 是你的服务的名字,这里定义了为 “ServiceTest.exe”。
  • (LPSERVICE_MAIN_FUNCTION)ServiceMain: 是指向服务入口函数 ServiceMain 的指针。当 SCM 启动服务时,将调用这个函数。

这个服务表告诉 SCM 与哪个服务相关联,通过哪个函数来启动和管理服务。 StartServiceCtrlDispatcher 函数接受这个服务表作为参数,并负责将控制传递给适当的服务。

StartServiceCtrlDispatcher 用于启动服务控制分发器。这个函数通常在服务程序的 main 函数中调用,它接受一个包含服务表的数组作为参数,并将控制传递给适当的服务。

以下是 StartServiceCtrlDispatcher 函数的原型:

BOOL StartServiceCtrlDispatcher(const SERVICE_TABLE_ENTRY *lpServiceTable
);
  • lpServiceTable: 指向 SERVICE_TABLE_ENTRY 结构体数组的指针,该数组定义了服务表。服务表中的每个元素指定了服务的名称和服务入口函数。

该函数返回 BOOL 类型。如果调用成功,返回 TRUE,否则返回 FALSE。如果返回 FALSE,可以通过调用 GetLastError 函数获取错误代码。

RegisterServiceCtrlHandler 用于注册一个服务控制处理程序,该处理程序将接收来自 SCM(服务控制管理器)的控制请求。每个服务都需要注册一个服务控制处理程序,以便在服务状态发生变化时接收通知。

以下是 RegisterServiceCtrlHandler 函数的原型:

SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(LPCTSTR                  lpServiceName,LPHANDLER_FUNCTION_EX    lpHandlerProc
);
  • lpServiceName: 指定要注册的服务的名称。这应该是服务在 SCM 中注册的唯一标识符。
  • lpHandlerProc: 指定服务控制处理程序的地址。这是一个指向处理函数的指针,该函数将在接收到控制请求时被调用。

函数返回一个 SERVICE_STATUS_HANDLE 类型的句柄。这个句柄用于标识服务控制管理器中的服务控制处理程序。

SetServiceStatus 用于通知 SCM(服务控制管理器)关于服务的当前状态。这个函数通常在服务的主循环中调用,以便及时向 SCM 报告服务的状态变化。

以下是 SetServiceStatus 函数的原型:

BOOL SetServiceStatus(SERVICE_STATUS_HANDLE hServiceStatus,LPSERVICE_STATUS      lpServiceStatus
);
  • hServiceStatus: 指定服务控制管理器中的服务的句柄,即由 RegisterServiceCtrlHandler 返回的句柄。
  • lpServiceStatus: 指向 SERVICE_STATUS 结构体的指针,该结构体描述了服务的当前状态。

SERVICE_STATUS 结构体定义如下:

typedef struct _SERVICE_STATUS {DWORD dwServiceType;DWORD dwCurrentState;DWORD dwControlsAccepted;DWORD dwWin32ExitCode;DWORD dwServiceSpecificExitCode;DWORD dwCheckPoint;DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;
  • dwServiceType: 服务的类型,例如 SERVICE_WIN32_OWN_PROCESS
  • dwCurrentState: 服务的当前状态,例如 SERVICE_RUNNING
  • dwControlsAccepted: 服务接受的控制码,例如 SERVICE_ACCEPT_STOP 表示服务接受停止控制。
  • dwWin32ExitCode: 服务的 Win32 退出码。
  • dwServiceSpecificExitCode: 服务的特定退出码。
  • dwCheckPoint: 在操作进行中时,用于指示操作的进度。
  • dwWaitHint: SCM 期望服务完成操作所需的等待时间。

有了上述接口的说明,并通过遵循微软的对服务编写的定义即可实现一个系统服务,这里的DoTask()是一个自定义函数,该服务在启动后会率先执行此处,此处可用于定义特定的功能,例如开机自启动某个进程,或者是远程创建套接字等,当然了服务程序也可以是exe如下可以使用控制台方式创建。

#include <Windows.h>// 服务入口函数以及处理回调函数
void __stdcall ServiceMain(DWORD dwArgc, char *lpszArgv);
void __stdcall ServiceCtrlHandle(DWORD dwOperateCode);
BOOL TellSCM(DWORD dwState, DWORD dwExitCode, DWORD dwProgress);
// 自定义函数
void DoTask();// 全局变量
char g_szServiceName[MAX_PATH] = "ServiceTest.exe";    // 自身服务名称 
SERVICE_STATUS_HANDLE g_ServiceStatusHandle = { 0 };int main(int argc, char * argv[])
{// 注册服务入口函数SERVICE_TABLE_ENTRY stDispatchTable[] = { { g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { NULL, NULL } };::StartServiceCtrlDispatcher(stDispatchTable);return 0;
}void __stdcall ServiceMain(DWORD dwArgc, char *lpszArgv)
{g_ServiceStatusHandle = ::RegisterServiceCtrlHandler(g_szServiceName, ServiceCtrlHandle);TellSCM(SERVICE_START_PENDING, 0, 1);TellSCM(SERVICE_RUNNING, 0, 0);while (TRUE){Sleep(5000);DoTask();}
}void __stdcall ServiceCtrlHandle(DWORD dwOperateCode)
{switch (dwOperateCode){case SERVICE_CONTROL_PAUSE:{// 暂停TellSCM(SERVICE_PAUSE_PENDING, 0, 1);TellSCM(SERVICE_PAUSED, 0, 0);break;}case SERVICE_CONTROL_CONTINUE:{// 继续TellSCM(SERVICE_CONTINUE_PENDING, 0, 1);TellSCM(SERVICE_RUNNING, 0, 0);break;}case SERVICE_CONTROL_STOP:{// 停止TellSCM(SERVICE_STOP_PENDING, 0, 1);TellSCM(SERVICE_STOPPED, 0, 0);break;}case SERVICE_CONTROL_INTERROGATE:{// 询问break;}default:break;}
}BOOL TellSCM(DWORD dwState, DWORD dwExitCode, DWORD dwProgress)
{SERVICE_STATUS serviceStatus = { 0 };BOOL bRet = FALSE;::RtlZeroMemory(&serviceStatus, sizeof(serviceStatus));serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;serviceStatus.dwCurrentState = dwState;serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;serviceStatus.dwWin32ExitCode = dwExitCode;serviceStatus.dwWaitHint = 3000;bRet = ::SetServiceStatus(g_ServiceStatusHandle, &serviceStatus);return bRet;
}void DoTask()
{// 自己程序实现部分代码放在这里
}

设置SCM开机运行

独立的SCM程序无法直接双击运行,该服务程序只能通过服务管理器运行,通过使用CreateService将服务管理器程序设置为开机自动运行,并使用StartService将服务启动。

CreateService 函数用于创建一个新的服务。这个函数通常在安装服务时使用。在服务安装过程中,需要指定服务的名称、显示名称、服务类型、启动类型、二进制路径等信息。

以下是 CreateService 函数的原型:

SC_HANDLE CreateService(SC_HANDLE hSCManager,LPCTSTR   lpServiceName,LPCTSTR   lpDisplayName,DWORD     dwDesiredAccess,DWORD     dwServiceType,DWORD     dwStartType,DWORD     dwErrorControl,LPCTSTR   lpBinaryPathName,LPCTSTR   lpLoadOrderGroup,LPDWORD   lpdwTagId,LPCTSTR   lpDependencies,LPCTSTR   lpServiceStartName,LPCTSTR   lpPassword
);
  • hSCManager: 服务控制管理器的句柄,可以通过 OpenSCManager 函数获取。
  • lpServiceName: 要创建的服务的名称。这是服务在 SCM 中的唯一标识符。
  • lpDisplayName: 服务的显示名称,这是在服务列表中显示的名称。
  • dwDesiredAccess: 对服务的访问权限,例如 SERVICE_ALL_ACCESS
  • dwServiceType: 服务的类型,例如 SERVICE_WIN32_OWN_PROCESS
  • dwStartType: 服务的启动类型,例如 SERVICE_AUTO_START
  • dwErrorControl: 当服务无法启动时的错误处理控制。
  • lpBinaryPathName: 服务程序的可执行文件的路径。
  • lpLoadOrderGroup: 指定服务应属于的加载顺序组。
  • lpdwTagId: 指向接收服务标识符的指针。
  • lpDependencies: 指定服务依赖的服务名称。
  • lpServiceStartName: 服务启动时使用的用户名。
  • lpPassword: 服务启动时使用的密码。

函数返回一个 SC_HANDLE 类型的句柄,该句柄标识了新创建的服务。如果函数调用失败,返回 NULL。可以通过调用 GetLastError 函数获取错误代码。

StartService 函数用于启动一个已注册的服务。这个函数通常在服务程序中的启动代码或者通过服务管理工具中手动启动服务时使用。

以下是 StartService 函数的原型:

BOOL StartService(SC_HANDLE hService,DWORD     dwNumServiceArgs,LPCTSTR   *lpServiceArgVectors
);
  • hService: 要启动的服务的句柄,可以通过 OpenService 函数获取。
  • dwNumServiceArgs: 指定传递给服务的命令行参数数量。
  • lpServiceArgVectors: 指向包含服务命令行参数的字符串数组。

函数返回一个 BOOL 类型的值,如果调用成功返回 TRUE,否则返回 FALSE。可以通过调用 GetLastError 函数获取错误代码。

ControlService 函数用于向已注册的服务发送控制码,以便执行特定的操作。这个函数通常在服务程序中的控制逻辑或者通过服务管理工具中手动控制服务时使用。

以下是 ControlService 函数的原型:

BOOL ControlService(SC_HANDLE        hService,DWORD            dwControl,LPSERVICE_STATUS lpServiceStatus
);
  • hService: 要控制的服务的句柄,可以通过 OpenService 函数获取。
  • dwControl: 指定服务的控制码,可以是以下之一:
    • SERVICE_CONTROL_CONTINUE: 继续服务。
    • SERVICE_CONTROL_PAUSE: 暂停服务。
    • SERVICE_CONTROL_STOP: 停止服务。
    • 等等,还有其他服务控制码。
  • lpServiceStatus: 指向 SERVICE_STATUS 结构体的指针,用于接收服务的当前状态信息。

函数返回一个 BOOL 类型的值,如果调用成功返回 TRUE,否则返回 FALSE。可以通过调用 GetLastError 函数获取错误代码。

如下代码实现了服务管理的两个关键功能:AutoRunService 函数用于注册并启动服务,使其在系统启动时自动运行;SetServiceStatus 函数用于设置服务的状态,包括停止服务、启动服务和删除服务。

这样的功能对于管理系统服务的状态和自启动行为具有重要意义。然而,需要注意确保在执行这些操作时具有足够的权限,并在实际应用中加强错误处理以确保操作的可靠性。

#include <stdio.h>
#include <Windows.h>
#include <Shlwapi.h>#pragma comment(lib, "Shlwapi.lib")// 注册服务自启动
void AutoRunService(char* szFilePath, char* szDescribe)
{char szName[MAX_PATH] = { 0 };SC_HANDLE scHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);lstrcpy(szName, szFilePath);PathStripPath(szName);SC_HANDLE scHandleOpen = OpenService(scHandle, szName, SERVICE_ALL_ACCESS);if (scHandleOpen == NULL){// SERVICE_AUTO_START => 随系统自动启动 | SERVICE_DEMAND_START => 手动启动SC_HANDLE scNewHandle = CreateService(scHandle, szName, szDescribe,SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,SERVICE_ERROR_IGNORE, szFilePath, NULL, NULL, NULL, NULL, NULL);StartService(scNewHandle, 0, NULL);CloseServiceHandle(scNewHandle);printf("[*] 创建服务完成 \n");}CloseServiceHandle(scHandleOpen);CloseServiceHandle(scHandle);
}// 设置服务状态
BOOL SetServiceStatus(char* szName, int Status)
{SERVICE_STATUS ss;SC_HANDLE scHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);SC_HANDLE scHandleOpen = OpenService(scHandle, szName, SERVICE_ALL_ACCESS);BOOL bRet = TRUE;if (scHandleOpen != NULL){switch (Status){case 1: if (!ControlService(scHandleOpen, SERVICE_CONTROL_STOP, &ss)) { bRet = FALSE; }; break;case 2: if (!StartService(scHandleOpen, 0, NULL)) { bRet = FALSE; }; break;case 3: if (!DeleteService(scHandleOpen)) { bRet = FALSE; }break;default:break;}}CloseServiceHandle(scHandleOpen);CloseServiceHandle(scHandle);return bRet;
}int main(int argc, char* argv[])
{// 注册为自启动服务将d:/myservice.exe 注册为自启动服务 后面是描述信息AutoRunService((char *)"d:/myservice.exe", (char *)"Microsoft Windows Security Services");// 根据服务名称管理服务 1=>停止服务 2=>启动服务 3=>删除服务BOOL ret = SetServiceStatus((char *)"myservice.exe", 2);printf("状态: %d \n", ret);system("pause");return 0;
}

运行上述代码将自动把d:/myservice.exe添加至服务自启动列表,并可以通过枚举的方式找到该服务的具体信息,如下图所示;

相关文章:

C/C++ 开发SCM服务管理组件

SCM&#xff08;Service Control Manager&#xff09;服务管理器是 Windows 操作系统中的一个关键组件&#xff0c;负责管理系统服务的启动、停止和配置。服务是一种在后台运行的应用程序&#xff0c;可以在系统启动时自动启动&#xff0c;也可以由用户或其他应用程序手动启动。…...

springboot程序启动成功后执行的方法

//实现该接口&#xff0c;run方法既程序启动成功后将要执行的方法 // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) //package org.springframework.boot;FunctionalInterface public interface CommandLineRunner {…...

STM32 寄存器配置笔记——USART配置 打印

一、概述 本文主要介绍如何配置USART&#xff0c;并通过USART打印验证结果。以stm32f10为例&#xff0c;将PA9、PA10复用为USART功能&#xff0c;使用HSE PLL输出72MHZ时钟 APB2 clk不分频提供配置9600波特率。波特率计算公式如下&#xff1a; fck即为APB2 clk参考计算&#xf…...

Spine深入学习 —— 数据

atlas数据的处理 作用 图集&#xff0c;描述了spine使用的图片信息。 结构 page 页块 页块包含了页图像名称, 以及加载和渲染图像的相关信息。 page1.pngsize: 640, 480format: RGBA8888filter: Linear, Linearrepeat: nonepma: truename: 首行为该页中的图像名称. 图片位…...

Debian 11.3 ARM64 安装中文语言包

文章目录 Debian 介绍1、执行命令2、语言选择3、修改设置 Debian 介绍 Debian是一种自由开源的操作系统&#xff0c;被广泛用于服务器、个人计算机和嵌入式设备。它是由全球志愿者组成的开发团队开发和维护的&#xff0c;以稳定性、安全性和自由性而闻名。 以下是一些关于Deb…...

计算机应用基础_错题集_PPT演示文稿_操作题_计算机多媒体技术操作题_文字处理操作题---网络教育统考工作笔记007

PPT演示文稿操作题 提示:PPT部分操作题 将第2~第4张幻灯片背景效果设为渐变预置的“雨后初晴”效果(2)设置幻灯片放映方式...

vue开发中遇到的问题记录

文章目录 前言1、css 即时使用了scoped子组件依然会生效2、路由配置如果出现重复name&#xff0c;只会生效最后一个&#xff0c;且前端的路由无效3、组件之间事件传递回调需要先定义emits: []&#xff0c;不然会警告提示总结如有启发&#xff0c;可点赞收藏哟~ 前言 1、css 即…...

Fiddler抓包工具不会用?点这里手把手超详细教学!

Fiddler 是一个 HTTP 协议调试代理工具&#xff0c;它能够记录并检查所有你的电脑和互联网之间的 HTTP 通讯。 Fiddler 提供了电脑端、移动端的抓包、包括 http 协议和 https 协议都可以捕获到报文并进行分析&#xff1b;可以设置断点调试、截取报文进行请求替换和数据篡改&am…...

2023年【道路运输企业安全生产管理人员】最新解析及道路运输企业安全生产管理人员复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 道路运输企业安全生产管理人员最新解析是安全生产模拟考试一点通总题库中生成的一套道路运输企业安全生产管理人员复审考试&#xff0c;安全生产模拟考试一点通上道路运输企业安全生产管理人员作业手机同步练习。2023…...

【超强笔记软件】Obsidian如何实现免费无限流量无套路云同步?

【超强笔记软件】Obsidian如何实现免费无限流量无套路云同步&#xff1f; 文章目录 【超强笔记软件】Obsidian如何实现免费无限流量无套路云同步&#xff1f;一、简介软件特色演示&#xff1a; 二、使用免费群晖虚拟机搭建群晖Synology Drive服务&#xff0c;实现局域网同步1 安…...

redis运维(十八)pipeline

一 pipeline 流水线 说明&#xff1a; 这里讲解的不是jenkins的pipeline流水线这里pipeline: 管道cat redis_pipe.txt | redis-cli -a 123456 -p 6379 --raw 2>/dev/null说明&#xff1a; redis_pipe.txt 中 每行 是一个redis命令 redis为什么要提供pipeline功能 事务和…...

DBeaver连接Oracle时报错:Undefined Error

连接信息检查了很多遍&#xff0c;应该是没问题的&#xff0c;而且驱动也正常下载了&#xff0c;但是就是连不上。 找了好久&#xff0c;终于找到一个可用的方式了&#xff0c;记录一下。 在安装目录修改dbeave.ini文件&#xff0c;最后一行添加 -Duser.nameTest。重启就可以…...

Java入门基础:浅显易懂 switch

文章目录 前言一、switch二、语法三、示例四、case穿透示例 前言 switch 在开发过程中其实并不常用&#xff0c;95%以上都是用 if 而不是 switch。因为 switch 能做的 if 能做&#xff0c;switch 不能做的 if 也能做&#xff0c;而反过来就不行了。所以对于 switch 能够看懂代码…...

BW4HANA 从头到脚 概念详解 ---- 持续更新中

1. 理解BW4HANA是干嘛的 好歹干了这么久的活了&#xff0c;从当初的啥也不懂到现在感觉啥都知道点&#xff0c;虽然知道的有限&#xff0c;但是也不是小白。渐渐的也知道了SAP开发的一些逻辑。本来咱是想当个BW的大牛的。但是现在感觉这条船要沉了是怎么回事。个人才稍微摸到点…...

Navicat 技术指引 | 适用于 GaussDB 的备份与还原功能

Navicat Premium&#xff08;16.2.8 Windows版或以上&#xff09; 已支持对 GaussDB 主备版的管理和开发功能。它不仅具备轻松、便捷的可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结构同步、协同合作、数据迁移等&#xff09;&#xff0c;这…...

【微服务】SaaS云智慧工地管理平台源码

智慧工地系统是一种利用人工智能和物联网技术来监测和管理建筑工地的系统。它可以通过感知设备、数据处理和分析、智能控制等技术手段&#xff0c;实现对工地施工、设备状态、人员安全等方面的实时监控和管理。 一、智慧工地让工程施工智能化 1、内容全面&#xff0c;多维度数…...

Git使用基础总结(从小白到新手版)

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…...

ck 配置 clickhouse-jdbc-bridge

背景 ck可以用过clickhouse-jdbc-bridge技术来直接访问各数据库&#xff0c;包括oracle&#xff0c;oracle也可以用jdbc&#xff0c;odbc没调研过 规划 jdbc-bridge是面向客户端的技术&#xff0c;也就是说&#xff0c;那个节点有客户端&#xff0c;哪个节点就需要&#xff…...

SpectralGPT: Spectral Foundation Model 论文翻译1

遥感领域的通用大模型 2023.11.13在CVPR发表 原文地址&#xff1a;[2311.07113] SpectralGPT: Spectral Foundation Model (arxiv.org) 摘要 ​ 基础模型最近引起了人们的极大关注&#xff0c;因为它有可能以一种自我监督的方式彻底改变视觉表征学习领域。虽然大多数基础模型…...

nuttx sim build

download nuttx: apache/nuttx: Apache NuttX is a mature, real-time embedded operating system (RTOS) (github.com) download genromfs https://github.com/chexum/genromfs make产生genromfs&#xff0c;复制到/usr/bin 编译nuttx&#xff1a; cd nuttx cmake -B b…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

微信小程序 - 手机震动

一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注&#xff1a;文档 https://developers.weixin.qq…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法

用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法 大家好,我是Echo_Wish。最近刷短视频、看直播,有没有发现,越来越多的应用都开始“懂你”了——它们能感知你的情绪,推荐更合适的内容,甚至帮客服识别用户情绪,提升服务体验。这背后,神经网络在悄悄发力,撑起…...

MySQL体系架构解析(三):MySQL目录与启动配置全解析

MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录&#xff0c;这个目录下存放着许多可执行文件。与其他系统的可执行文件类似&#xff0c;这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中&#xff0c;用…...

【java面试】微服务篇

【java面试】微服务篇 一、总体框架二、Springcloud&#xff08;一&#xff09;Springcloud五大组件&#xff08;二&#xff09;服务注册和发现1、Eureka2、Nacos &#xff08;三&#xff09;负载均衡1、Ribbon负载均衡流程2、Ribbon负载均衡策略3、自定义负载均衡策略4、总结 …...