随时可以看到任务管理器中有一个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. elasticsearch ingest node and docker-cluster---quey using sql]

    es-docker-cluster https://stefanprodan.com/2016/elasticsearch-cluster-with-docker/ https://github.co ...

  2. [filesystem][archlinux][disk encryption][btrfs] btrfs

    fork from here http://www.cnblogs.com/hugetong/p/6914248.html boot分区,MBR加密:https://wiki.archlinux.or ...

  3. Flink – submitJob

    Jobmanager的submitJob逻辑, /** * Submits a job to the job manager. The job is registered at the library ...

  4. Transparent HugePages(透明大页)

    Transparent HugePages(透明大页) 1. 介绍 从RedHat6, RedHat7, OL6, OL7 SLES11 and UEK2 kernels开始,透明大页默认是被开启的以 ...

  5. POJ2274 Long Long Message 字符串

    正解:SA/哈希+二分 解题报告: 传送门! 啊先放下翻译,,,?大意就有两个字符串,求这两个字符串的最长公共子串 先港SA的做法趴 就把两个子串拼接起来,然后题目就变成了求后缀的最长公共前缀了 所以 ...

  6. TensorFlow设置GPU占用量

    默认开启Tensorflow的session之后,就会占用几乎所有的显存,进行如下设置即可: 指定GPU编号: import os os.environ["CUDA_VISIBLE_DEVI ...

  7. CentOS+Uwsgi+Nginx发布Flask开发的WebAPI

    1.WebAPI 开发工具VS 于Windows环境中开发完成后使用SFTP进行同步文件到Centos中使用 2.重点:WebAPI触发的方法是为了发送Celery异步调度任务 Celery框架使用涉 ...

  8. ansible实现keepalived和nginx高可用

    实验环境 ansible节点 keepalived+nginx节点1    ansible自动安装配置 keepalived+nginx节点2    ansible自动安装配置 httpd节点1 ht ...

  9. LongAdder,AtomicIntegerFieldUpdater深入研究

    从LongAdder看更高效的无锁实现 AtomicIntegerFieldUpdater字段原子更新类 div:not([id]){display:none;} --> ul{padding: ...

  10. Java的transient关键字(转)

    Volatile修饰的成员变量在每次被线程访问时,都强迫从主内存中重读该成员变量的值.而且,当成员变量发生变化时,强迫线程将变化值回写到主内存.这样在任何时刻,两个不同的线程总是看到某个成员变量的同一 ...