随时可以看到任务管理器中有一个services.exe进程,这个就是系统的服务控制管理进程,简称SCM

这个进程专门用来管理服务(启动、停止、删除、配置等操作)

系统中所有注册的服务都登记在\HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services键下,这个键专门叫做‘服务键’,服务键下面的每个子键代表一个服务,记录了各个服务的信息。

每个服务可以是独立的服务,也可以位于某个服务组内。用户不仅可以注册服务,还可以注册服务组,并指定服务与服务组之间的隶属关系。\HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\ServiceGroupOrder键下有一个值List,类型是多字符串,记录了系统中注册的所有服务组(蕴含了他们之间的相对加载顺序)。系统加载启动服务时,首先是以服务组为加载顺序进行加载的,各个服务组的加载先后顺序不同,然后同一服务组内的各个服务也是有加载顺序的。

下面是各种启动类型:

#define SERVICE_BOOT_START 0    //系统引导时启动

#define SERVICE_SYSTEM_START 1  //系统初始哈时启动

#define SERVICE_AUTO_START 2    //AutoStart启动类型,即系统初始化完毕后,由SCM开机时自动启动

#define SERVICE_DEMAND_START 3  //按需启动

#define SERVICE_DISABLED 4  //禁用

系统在初始化完毕后,启动services.exe时,会检查注册表中那些登记为AutoStart启动类型的服务,一一予以启动。

我们看services.exe的WinMain函数

int WINAPI

wWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPWSTR lpCmdLine,int nShowCmd)

{

    HANDLE hScmStartEvent;

    HANDLE hEvent;

    DWORD dwError;

    if (!ScmCreateStartEvent(&hScmStartEvent))

        ExitThread(0);

//读取系统中注册的所有服务组和服务,记录到各自的全局链表中

    dwError = ScmCreateServiceDatabase();

    if (dwError != ERROR_SUCCESS)

        ExitThread(0);

    ScmGetBootAndSystemDriverState();//标记出全局链表中那些已经启动了的驱动类型服务

    ScmStartRpcServer();

    RegisterServicesProcess(GetCurrentProcessId());

    SetEvent(hScmStartEvent);

    SetConsoleCtrlHandler(ShutdownHandlerRoutine, TRUE);

    ScmWaitForLsass();

    AcquireLoadDriverPrivilege();

    ScmAutoStartServices();//关键。启动所有那些注册为AutoStart启动类型的服务

    hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (hEvent)

        WaitForSingleObject(hEvent, INFINITE);//services.exe进程永远不会结束

    CloseHandle(hScmStartEvent);

    ExitThread(0);

    return 0;

}

DWORD  ScmCreateServiceDatabase(VOID)

{

    WCHAR szSubKey[MAX_PATH];

    HKEY hServicesKey;

    HKEY hServiceKey;

    DWORD dwSubKey;

    DWORD dwSubKeyLength;

    FILETIME ftLastChanged;

    DWORD dwError;

    dwError = ScmCreateGroupList();//读取注册表中所有注册的服务组,加入全局服务组链表

    if (dwError != ERROR_SUCCESS)

        return dwError;

    InitializeListHead(&ServiceListHead);

    RtlInitializeResource(&DatabaseLock);

    dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,L"System\\CurrentControlSet\\Services",

                            0,KEY_READ,&hServicesKey);

    if (dwError != ERROR_SUCCESS)

        return dwError;

    dwSubKey = 0;

    for (;;)

 {

        dwSubKeyLength = MAX_PATH;

        //枚举系统中注册的所有服务(按注册顺序)

        dwError = RegEnumKeyExW(hServicesKey,dwSubKey,szSubKey,&dwSubKeyLength,NULL,NULL,

                                NULL,&ftLastChanged);

        if (dwError == ERROR_SUCCESS && szSubKey[0] != L'{') //跳过com服务

        {

            dwError = RegOpenKeyExW(hServicesKey,szSubKey,0,KEY_READ,&hServiceKey);

            if (dwError == ERROR_SUCCESS)

            {

                dwError = CreateServiceListEntry(szSubKey,hServiceKey);//加入全局服务链表

                RegCloseKey(hServiceKey);

            }

        }

        if (dwError != ERROR_SUCCESS)

            break;

        dwSubKey++;

    }

    RegCloseKey(hServicesKey);

    WaitForLSA();

    ScmDeleteMarkedServices();//删除那些标记为‘已删除’的服务

    return ERROR_SUCCESS;

}

DWORD  ScmCreateGroupList(VOID)

{

    RTL_QUERY_REGISTRY_TABLE QueryTable[2];

    NTSTATUS Status;

    InitializeListHead(&GroupListHead);

    InitializeListHead(&UnknownGroupListHead);

    RtlZeroMemory(&QueryTable,sizeof(QueryTable));

    QueryTable[0].Name = L"List";

QueryTable[0].QueryRoutine = CreateGroupListRoutine;

//查询ServiceGroupOrder键中的List值,对list中的每个组执行CreateGroupListRoutine函数

    Status = RtlQueryRegistryValues(RTL_REGISTRY_CONTROL,L"ServiceGroupOrder",//这个键

                                    QueryTable,NULL,NULL);

    return RtlNtStatusToDosError(Status);

}

NTSTATUS WINAPI

CreateGroupListRoutine(PWSTR ValueName,//值名,即list

                       ULONG ValueType,

                       PVOID ValueData,//即list中的每个组名

                       ULONG ValueLength,

                       PVOID Context,

                       PVOID EntryContext)

{

    PSERVICE_GROUP Group;

    RTL_QUERY_REGISTRY_TABLE QueryTable[2];

    NTSTATUS Status;

    if (ValueType == REG_SZ)

    {

        //分配一个服务组描述符

        Group = (PSERVICE_GROUP)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,

                                          sizeof(SERVICE_GROUP) + ((wcslen((const wchar_t*) ValueData) + 1) * sizeof(WCHAR)));

        wcscpy(Group->szGroupName, (const wchar_t*) ValueData);

        Group->lpGroupName = Group->szGroupName;

        Group->dwRefCount = (DWORD)-1;

        RtlZeroMemory(&QueryTable, sizeof(QueryTable));

        QueryTable[0].Name = (PWSTR)ValueData;//组名

        QueryTable[0].QueryRoutine = CreateGroupOrderListRoutine;

        //查询GroupOrderList键中对应组名的信息,执行CreateGroupOrderListRoutine函数

        Status = RtlQueryRegistryValues(RTL_REGISTRY_CONTROL,L"GroupOrderList",QueryTable,

                                        (PVOID)Group,NULL);//返回组描述符

        InsertTailList(&GroupListHead,&Group->GroupListEntry);//将组描述符挂入全局服务组链表

    }

    return STATUS_SUCCESS;

}

NTSTATUS 

CreateGroupOrderListRoutine(PWSTR ValueName,//即组名

                            ULONG ValueType,

                            PVOID ValueData,//值的数据,即tag总数|tag|tag…|tag格式

                            ULONG ValueLength,

                            PVOID Context,//返回该组包含的服务tag个数和各个服务的tag

                            PVOID EntryContext)

{

    PSERVICE_GROUP Group;

    if (ValueType == REG_BINARY &&

        ValueData != NULL &&

        ValueLength >= sizeof(DWORD) &&

        ValueLength >= (*(PULONG)ValueData + 1) * sizeof(DWORD))

    {

        Group = (PSERVICE_GROUP)Context;

        Group->TagCount = ((PULONG)ValueData)[0];

        if (Group->TagCount > 0)

        {

            if (ValueLength >= (Group->TagCount + 1) * sizeof(DWORD))//多此一举

            {

                //即该组中每个服务的tag

                Group->TagArray = (PULONG)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,

                                                    Group->TagCount * sizeof(DWORD));

                RtlCopyMemory(Group->TagArray, ValueData + 1,Group->TagCount * sizeof(DWORD));

            }

            else

            {

                Group->TagCount = 0;

                return STATUS_UNSUCCESSFUL;

            }

        }

    }

    return STATUS_SUCCESS;

}

//获取指定服务的信息,构造一个服务描述符,将服务加入全局服务链表中

DWORD  CreateServiceListEntry(LPCWSTR lpServiceName,HKEY hServiceKey)

{

    PSERVICE lpService = NULL;

    LPWSTR lpDisplayName = NULL;

    LPWSTR lpGroup = NULL;

    DWORD dwSize;

    DWORD dwError;

    DWORD dwServiceType;

    DWORD dwStartType;

    DWORD dwErrorControl;

    DWORD dwTagId;

    if (*lpServiceName == L'{')

        return ERROR_SUCCESS;

    dwSize = sizeof(DWORD);

    dwError = RegQueryValueExW(hServiceKey,L"Type",NULL,NULL, (LPBYTE)&dwServiceType,

                               &dwSize);

    if (dwError != ERROR_SUCCESS)

        return ERROR_SUCCESS;

    //没有进程的服务不必加入全局链表(但驱动型服务除外)

    if (((dwServiceType & ~SERVICE_INTERACTIVE_PROCESS) != SERVICE_WIN32_OWN_PROCESS) &&

        ((dwServiceType & ~SERVICE_INTERACTIVE_PROCESS) != SERVICE_WIN32_SHARE_PROCESS) &&

        (dwServiceType != SERVICE_KERNEL_DRIVER) &&

        (dwServiceType != SERVICE_FILE_SYSTEM_DRIVER))

        return ERROR_SUCCESS;

    dwSize = sizeof(DWORD);

    dwError = RegQueryValueExW(hServiceKey,L"Start",NULL,NULL, (LPBYTE)&dwStartType,&dwSize);

    if (dwError != ERROR_SUCCESS)

        return ERROR_SUCCESS;

    dwSize = sizeof(DWORD);

    dwError = RegQueryValueExW(hServiceKey,L"ErrorControl",NULL,NULL,

                               (LPBYTE)&dwErrorControl,&dwSize);

    if (dwError != ERROR_SUCCESS)

        return ERROR_SUCCESS;

    //查询该服务的tag id

    dwError = RegQueryValueExW(hServiceKey,L"Tag",NULL,NULL, (LPBYTE)&dwTagId,&dwSize);

    if (dwError != ERROR_SUCCESS)

        dwTagId = 0;

    //查询该服务所属的组

    dwError = ScmReadString(hServiceKey,L"Group",&lpGroup);

    if (dwError != ERROR_SUCCESS)

        lpGroup = NULL;

    dwError = ScmReadString(hServiceKey,L"DisplayName",&lpDisplayName);

    if (dwError != ERROR_SUCCESS)

        lpDisplayName = NULL;

    //创建一个服务描述符

    dwError = ScmCreateNewServiceRecord(lpServiceName,  &lpService);

    if (dwError != ERROR_SUCCESS)

        goto done;

    lpService->Status.dwServiceType = dwServiceType;

    lpService->dwStartType = dwStartType;

    lpService->dwErrorControl = dwErrorControl;

    lpService->dwTag = dwTagId;

    if (lpGroup != NULL)

    {

        dwError = ScmSetServiceGroup(lpService, lpGroup);//记录它所属的服务组

        if (dwError != ERROR_SUCCESS)

            goto done;

    }

    if (lpDisplayName != NULL)

    {

        lpService->lpDisplayName = lpDisplayName;

        lpDisplayName = NULL;

    }

    if (ScmIsDeleteFlagSet(hServiceKey))

        lpService->bDeleted = TRUE;

done:;

    if (lpGroup != NULL)

        HeapFree(GetProcessHeap(), 0, lpGroup);

    if (lpDisplayName != NULL)

        HeapFree(GetProcessHeap(), 0, lpDisplayName);

    return dwError;

}

当构造好了服务组链表和服务链表后,下面的函数用于标记出那些已经启动了的驱动服务

VOID  ScmGetBootAndSystemDriverState(VOID)

{

    PLIST_ENTRY ServiceEntry;

    PSERVICE CurrentService;

    ServiceEntry = ServiceListHead.Flink;

    while (ServiceEntry != &ServiceListHead)

    {

        CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);

        if (CurrentService->dwStartType == SERVICE_BOOT_START ||

            CurrentService->dwStartType == SERVICE_SYSTEM_START)

        {

            ScmCheckDriver(CurrentService);//检查这两种早期启动型服务

        }

        ServiceEntry = ServiceEntry->Flink;

    }

}

NTSTATUS  ScmCheckDriver(PSERVICE Service) //标记成所有已经启动了的驱动型服务

{

    OBJECT_ATTRIBUTES ObjectAttributes;

    UNICODE_STRING DirName;

    HANDLE DirHandle;

    NTSTATUS Status;

    POBJECT_DIRECTORY_INFORMATION DirInfo;

    ULONG BufferLength;

    ULONG DataLength;

    ULONG Index;

    if (Service->Status.dwServiceType == SERVICE_KERNEL_DRIVER)

        RtlInitUnicodeString(&DirName,L"\\Driver");

    else

        RtlInitUnicodeString(&DirName,L"\\FileSystem");

    InitializeObjectAttributes(&ObjectAttributes,&DirName,0,NULL,NULL);

    Status = NtOpenDirectoryObject(&DirHandle,  DIRECTORY_QUERY | DIRECTORY_TRAVERSE,

                                   &ObjectAttributes);

    if (!NT_SUCCESS(Status))

    {

        return Status;

    }

    BufferLength = sizeof(OBJECT_DIRECTORY_INFORMATION) + 2 * MAX_PATH * sizeof(WCHAR);

    DirInfo = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,BufferLength);

    Index = 0;

    while (TRUE)

    {

        Status = NtQueryDirectoryObject(DirHandle,DirInfo,BufferLength,TRUE,FALSE,&Index,

                                        &DataLength);

        if (Status == STATUS_NO_MORE_ENTRIES)

            break;

        if (!NT_SUCCESS(Status))

            break;

        //if 该服务的驱动对象已在对象目录中,也即if该服务已经启动

        if (_wcsicmp(Service->lpServiceName, DirInfo->Name.Buffer) == 0)

        {

            Service->Status.dwCurrentState = SERVICE_RUNNING;//标记

            if (Service->lpGroup != NULL)

                Service->lpGroup->ServicesRunning = TRUE;//标记那个组有服务启动了

            break;

        }

    }

    HeapFree(GetProcessHeap(),0,DirInfo);

    NtClose(DirHandle);

    return STATUS_SUCCESS;

}

下面是最关键的了:我们看下所有AutoStart启动类型的服务是按什么顺序启动的

VOID  ScmAutoStartServices(VOID)  //启动所有AutoStart启动类型的服务

{

    PLIST_ENTRY GroupEntry;

    PLIST_ENTRY ServiceEntry;

    PSERVICE_GROUP CurrentGroup;

    PSERVICE CurrentService;

    ULONG i;

ServiceEntry = ServiceListHead.Flink;

//先全部标记为‘未启动’

    while (ServiceEntry != &ServiceListHead)

    {

      CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);

      CurrentService->ServiceVisited = FALSE;

      ServiceEntry = ServiceEntry->Flink;

    }

   // 1、先按服务组之间的相对顺序,启动所有服务组中的服务

    GroupEntry = GroupListHead.Flink;

    while (GroupEntry != &GroupListHead)

    {

        CurrentGroup = CONTAINING_RECORD(GroupEntry, SERVICE_GROUP, GroupListEntry);

        for (i = 0; i < CurrentGroup->TagCount; i++)

        {

            ServiceEntry = ServiceListHead.Flink;

            while (ServiceEntry != &ServiceListHead)

            {

                CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);

                if ((CurrentService->lpGroup == CurrentGroup) &&

                    (CurrentService->dwStartType == SERVICE_AUTO_START) &&

                    (CurrentService->ServiceVisited == FALSE) &&

                    (CurrentService->dwTag == CurrentGroup->TagArray[i]))

                {

                    CurrentService->ServiceVisited = TRUE;

                    ScmStartService(CurrentService, 0, NULL);

                }

                ServiceEntry = ServiceEntry->Flink;

             }

        }

      //  2、启动处于服务组中,但没有tag或者tag无效的那些服务

        ServiceEntry = ServiceListHead.Flink;

        while (ServiceEntry != &ServiceListHead)

        {

            CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);

            if ((CurrentService->lpGroup == CurrentGroup) &&

                (CurrentService->dwStartType == SERVICE_AUTO_START) &&

                (CurrentService->ServiceVisited == FALSE))

            {

                CurrentService->ServiceVisited = TRUE;

                ScmStartService(CurrentService, 0, NULL);

            }

            ServiceEntry = ServiceEntry->Flink;

        }

        GroupEntry = GroupEntry->Flink;

    }

    //3、启动那些有服务组,但服务组本身不存在的服务

    ServiceEntry = ServiceListHead.Flink;

    while (ServiceEntry != &ServiceListHead)

    {

        CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);

        if ((CurrentService->lpGroup != NULL) &&

            (CurrentService->dwStartType == SERVICE_AUTO_START) &&

            (CurrentService->ServiceVisited == FALSE))

        {

            CurrentService->ServiceVisited = TRUE;

            ScmStartService(CurrentService, 0, NULL);

        }

        ServiceEntry = ServiceEntry->Flink;

    }

   // 4、启动所有独立的服务(不在任何服务组中)

    ServiceEntry = ServiceListHead.Flink;

    while (ServiceEntry != &ServiceListHead)

    {

        CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);

        if ((CurrentService->lpGroup == NULL) &&

            (CurrentService->dwStartType == SERVICE_AUTO_START) &&

            (CurrentService->ServiceVisited == FALSE))

        {

            CurrentService->ServiceVisited = TRUE;

            ScmStartService(CurrentService, 0, NULL);

        }

        ServiceEntry = ServiceEntry->Flink;

}

//完毕

    ServiceEntry = ServiceListHead.Flink;

    while (ServiceEntry != &ServiceListHead)

    {

        CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);

        CurrentService->ServiceVisited = FALSE;

        ServiceEntry = ServiceEntry->Flink;

    }

}

可以看出,各个服务组之间相对的加载顺序、组内服务之间的相对加载顺序都记录在注册表。

有组的服务优先比没组的服务优先启动,没组的那些服务,则按注册表中的登记的顺序启动。

DWORD  ScmStartService(PSERVICE Service, DWORD argc, LPWSTR *argv)

{

    PSERVICE_GROUP Group = Service->lpGroup;

    DWORD dwError = ERROR_SUCCESS;

    Service->ControlPipeHandle = INVALID_HANDLE_VALUE;

    if (Service->Status.dwServiceType & SERVICE_DRIVER)//驱动类型服务

    {

        dwError = ScmLoadDriver(Service);//用这个函数加载驱动,启动服务

        if (dwError == ERROR_SUCCESS)

        {

            Service->Status.dwControlsAccepted = SERVICE_ACCEPT_STOP;

            Service->Status.dwCurrentState = SERVICE_RUNNING;

        }

    }

    Else 。。。

    return dwError;

}

DWORD   ScmLoadDriver(PSERVICE lpService)

{

    WCHAR szDriverPath[MAX_PATH];

    UNICODE_STRING DriverPath;

    NTSTATUS Status;

    DWORD dwError = ERROR_SUCCESS;

    wcscpy(szDriverPath,L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\");

    wcscat(szDriverPath, lpService->lpServiceName);

RtlInitUnicodeString(&DriverPath, szDriverPath);

//看到没。通过SCM启动的服务,都使用这个函数加载驱动,而这个函数我们前面看过,只能加载NT式驱动,不能加载PNP驱动,或者即使是个PNP驱动,通过这个函数加载的,也会变成老式的NT式驱动,不支持即插即用。

    Status = NtLoadDriver(&DriverPath); 

    if (!NT_SUCCESS(Status))

        dwError = RtlNtStatusToDosError(Status);

    return dwError;

}

[16]Windows内核情景分析 --- 服务管理的更多相关文章

  1. [3]windows内核情景分析--内存管理

    32位系统中有4GB的虚拟地址空间 每个进程有一个地址空间,共4GB,(具体分为低2GB的用户地址空间+高2GB的内核地址空间) 各个进程的用户地址空间不同,属于各进程专有,内核地址空间部分则几乎完全 ...

  2. [15]Windows内核情景分析 --- 权限管理

    Windows系统是支持多用户的.每个文件可以设置一个访问控制表(即ACL),在ACL中规定每个用户.每个组对该文件的访问权限.不过,只有Ntfs文件系统中的文件才支持ACL. (Ntfs文件系统中, ...

  3. 几个常用内核函数(《Windows内核情景分析》)

    参考:<Windows内核情景分析> 0x01  ObReferenceObjectByHandle 这个函数从句柄得到对应的内核对象,并递增其引用计数. NTSTATUS ObRefer ...

  4. [1]windows 内核情景分析---说明

    本文说明:这一系列文章(笔记)是在看雪里面下载word文档,现转帖出来,希望更多的人能看到并分享,感谢原作者的分享精神. 说明 本文结合<Windows内核情景分析>(毛德操著).< ...

  5. windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数

    windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数 1.KeRaiseIrql函数 这个 KeRaiseIrql() 只是简单地调用 hal 模块的 KfRa ...

  6. [14]Windows内核情景分析 --- 文件系统

    文件系统 一台机器上可以安装很多物理介质来存放资料(如磁盘.光盘.软盘.U盘等).各种物理介质千差万别,都配备有各自的驱动程序,为了统一地访问这些物理介质,windows设计了文件系统机制.应用程序要 ...

  7. [11]Windows内核情景分析---设备驱动

    设备驱动 设备栈:从上层到下层的顺序依次是:过滤设备.类设备.过滤设备.小端口设备[过.类.过滤.小端口] 驱动栈:因设备堆栈原因而建立起来的一种堆栈 老式驱动:指不提供AddDevice的驱动,又叫 ...

  8. [4]Windows内核情景分析---内核对象

    写过Windows应用程序的朋友都常常听说"内核对象"."句柄"等术语却无从得知他们的内核实现到底是怎样的, 本篇文章就揭开这些技术的神秘面纱. 常见的内核对象 ...

  9. [7] Windows内核情景分析---线程同步

    基于同步对象的等待.唤醒机制: 一个线程可以等待一个对象或多个对象而进入等待状态(也叫睡眠状态),另一个线程可以触发那个等待对象,唤醒在那个对象上等待的所有线程. 一个线程可以等待一个对象或多个对象, ...

随机推荐

  1. static的含义

    static的含义:(1)设置变量的存储域,函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍持上次的值:(2)限制变量的作用域,在模块 ...

  2. iOS-静态库,动态库,framework,bundle浅析(四)

    1. 创建bundle,如图,点击 +  ,弹出选择框, macOS 下的Framework & Library  ,点击bundle,输入bundle的名字,然后点击 finish.   图 ...

  3. php安全

    1.会话安全性 会话固化 一种获取有效回话标识符的方法,他将运行恶意用户通过强制使用回话ID来轻松模拟一个真实用户 攻击方法:<a href="http://a.com/index.p ...

  4. 把MP3保存到数据库中

    使用JdbcUtils得到连接con java.sql包下的Interface Blob----其实现类SerialBlob Blob是一个可以存储二进制文件的容器. BLOB常常是数据库中用来存储二 ...

  5. <大话设计模式>工厂模式,策略模式

    第一章:工厂模式: 通过封装,继承,多态解耦合 业务逻辑和界面逻辑分开 用单独的类创造实例,工厂:创造实例 工厂模式还可以用反射来实现,nsstringFromClass UML类图 聚合表示一众弱的 ...

  6. idea设置文件的编码格式为utf-8

    file ---> settings... 然后 editor --->  file encoding 然后设置path和encoding什么的.

  7. 【pyqtgraph绘图】安装pyqtgraph

    解读官方API-安装 安装 参考:http://www.pyqtgraph.org/documentation/installation.html 根据您的需要,有许多不同的方式来安装pyqtgrap ...

  8. vue获取地址栏传过来的参数VS原生js获取地址栏的参数

    Vue的方式 Vue的 query方式 ①this.$route.query.companyId ( companyId 为参数的名称 是$route 不是 $router) Vue的 params方 ...

  9. shell下的几个命令

    参考博客: https://www.cnblogs.com/-zyj/p/5760484.html 1. 批量删除筛选的文件夹 ls -l | grep ^d | xargs rm -rf   2. ...

  10. oracle用户被锁

    使用PLSQL客户端:1.用管理员账户登录PLSQL Developer(登录名可以为system,选择类型的时候把Normal修改为Sysdba).2.左侧选择My Objects,查看Users文 ...