[16]Windows内核情景分析 --- 服务管理
随时可以看到任务管理器中有一个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内核情景分析 --- 服务管理的更多相关文章
- [3]windows内核情景分析--内存管理
		32位系统中有4GB的虚拟地址空间 每个进程有一个地址空间,共4GB,(具体分为低2GB的用户地址空间+高2GB的内核地址空间) 各个进程的用户地址空间不同,属于各进程专有,内核地址空间部分则几乎完全 ... 
- [15]Windows内核情景分析 --- 权限管理
		Windows系统是支持多用户的.每个文件可以设置一个访问控制表(即ACL),在ACL中规定每个用户.每个组对该文件的访问权限.不过,只有Ntfs文件系统中的文件才支持ACL. (Ntfs文件系统中, ... 
- 几个常用内核函数(《Windows内核情景分析》)
		参考:<Windows内核情景分析> 0x01 ObReferenceObjectByHandle 这个函数从句柄得到对应的内核对象,并递增其引用计数. NTSTATUS ObRefer ... 
- [1]windows 内核情景分析---说明
		本文说明:这一系列文章(笔记)是在看雪里面下载word文档,现转帖出来,希望更多的人能看到并分享,感谢原作者的分享精神. 说明 本文结合<Windows内核情景分析>(毛德操著).< ... 
- windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数
		windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数 1.KeRaiseIrql函数 这个 KeRaiseIrql() 只是简单地调用 hal 模块的 KfRa ... 
- [14]Windows内核情景分析 --- 文件系统
		文件系统 一台机器上可以安装很多物理介质来存放资料(如磁盘.光盘.软盘.U盘等).各种物理介质千差万别,都配备有各自的驱动程序,为了统一地访问这些物理介质,windows设计了文件系统机制.应用程序要 ... 
- [11]Windows内核情景分析---设备驱动
		设备驱动 设备栈:从上层到下层的顺序依次是:过滤设备.类设备.过滤设备.小端口设备[过.类.过滤.小端口] 驱动栈:因设备堆栈原因而建立起来的一种堆栈 老式驱动:指不提供AddDevice的驱动,又叫 ... 
- [4]Windows内核情景分析---内核对象
		写过Windows应用程序的朋友都常常听说"内核对象"."句柄"等术语却无从得知他们的内核实现到底是怎样的, 本篇文章就揭开这些技术的神秘面纱. 常见的内核对象 ... 
- [7] Windows内核情景分析---线程同步
		基于同步对象的等待.唤醒机制: 一个线程可以等待一个对象或多个对象而进入等待状态(也叫睡眠状态),另一个线程可以触发那个等待对象,唤醒在那个对象上等待的所有线程. 一个线程可以等待一个对象或多个对象, ... 
随机推荐
- bootstrape学习
			bootstrape学习 已分享到有道上:http://note.youdao.com/share/?id=076fb6314c99c742a79f6fb66b2a58b0&type=note ... 
- Page11:状态反馈、输出反馈的概念及性能比较,极点配置的基本概念、意义及其算法[Linear System Theory]
			内容包含离散时间线性时不变系统的稳定判据 状态反馈.输出反馈的基本概念及其性能比较 极点配置的基本概念.意义及其算法 
- 将获得datebox值的文本形式转为日期格式
			在使用datebox时,已选择结束日期后,再次选择开始日期.此时判断开始日期不能大于结束日期. datebox的onSelect: function (date){}事件传入的参数是日期类型,而使用d ... 
- day5_集合
			集合也是一种数据类型,一个类似列表东西,它的特点是无序的,不重复的,也就是说集合中是没有重复的数据 集合的作用: 1.它可以把一个列表中重复的数据去掉,而不需要你再写判断---天生去重 2.可以做关系 ... 
- filter的基本介绍和使用
			简介 过滤器是处在客户端和服务器资源之间的一到过滤网,我们可以根据具体的需求来对请求头和数据就行预处理,也可以对响应头和和数据进行后处理.例如Jsp, Servlet, 静态图片文件或静态 html ... 
- 深度剖析fork()的原理及用法
			我们都知道通过fork()系统调用我们可以创建一个和当前进程印象一样的新进程.我们通常将新进程称为子进程,而当前进程称为父进程.而子进程继承了父进程的整个地址空间,其中包括了进程上下文,堆栈地址,内存 ... 
- Python摸爬滚打之day04----基本数据类型(列表,元组)
			1.列表 列表是可变的, 有序的数据类型,列表是按照添加顺序来保存的,可以存放各种数据类型. 1.1 列表的切片(同字符串) 1.2 列表的增删改查 注意: 列表是可以直接在列表上面进行操 ... 
- 洛谷P4064 加法 [JXOI2017] 贪心
			正解:贪心 解题报告: 传送门! 首先最小值最大显然考虑二分?然后就二分一个值mid,从左往右考虑,对于小于等于mid的点显然可以求出这个点至少要加几次,然后找到覆盖这个点的右端点max的区间区间加上 ... 
- Linux命令小计
			一.yum和apt-get的区别 Linux系统下安装包格式有:rpm包和deb包. pm包主要应用在RedHat系列包括 Fedora等发行版的Linux系统上 deb包主要应用于Debian系列包 ... 
- 腾讯互动课堂(Tencent Interact Class,TIC)SDK 词汇表
			词汇表 https://cloud.tencent.com/document/product/266/11732 封装格式 封装格式(Format)是将已经编码压缩好的视频流和音频流按照一定的格式规范 ... 
