• Windows内核之线程的调度,优先级,亲缘性


    1 调度

             Windows不是实时操作系统,它是抢占式多线程操作系统。在如果全部优先级同样的情况下,CPU对线程的调度原则是每隔20m就会切换到下一个线程,依据Context中的IP和SP来接着运行上次的东西。Windows永远不会让1个线程去独占一段时间。

    2 可调度性

             系统仅仅调用能够调度的线程,事实上系统的大部分线程都是处于不可调度的状态,要么处于暂停的状态,要么处于休眠的状态。

    3 线程的暂停和恢复

    <1>在CreateThread的时候通过制定CREATE_SUSPENDED来让线程暂停运行

    <2>在DWORD SuspendThread(HANDLE hThread)函数来暂停一个线程,最多次数为MAXIMUM_SUSPEND_COUNT(127)

    <3>通过函数DWORD ResumeThread(HANDLE hThread)来唤醒线程

    4 进程的暂停

             Windows中从来不存在进程的暂停和恢复,由于进程是不会被调度的。可是在特殊情况下Windows会冻结进程中全部的线程:调试程序中处理函数WaitForDebugEvent返回的debug事件;直到调用ContinueDebugEvent.函数才会恢复进程中的全部线程。

             可是我们能够通过遍历系统中全部的线程,通过检查线程所属的进程ID是否满足指定值,就能够做到暂停全部的线程。

             弊端:

    <1>遍历线程ID时候,假设有新线程在创建,那么新线程将不会被暂停

    <2>当又一次恢复线程的时候,可能会对新创建的没有被暂停的线程去恢复

    <3>遍历线程ID的时候,撤销的线程跟新建的线程可能具有具有同样的ID,这就可能导致暂停多个具有同样ID的线程。

             进程中全部线程暂停函数例如以下所看到的:

    VOID SuspendProcess(DWORD dwProcessID, BOOL fSuspend) {
    // Get the list of threads in the system.
    HANDLE hSnapshot = CreateToolhelp32Snapshot(
    TH32CS_SNAPTHREAD, dwProcessID);
    if (hSnapshot != INVALID_HANDLE_VALUE) {
    // Walk the list of threads.
    THREADENTRY32 te = { sizeof(te) };
    BOOL fOk = Thread32First(hSnapshot, &te);
    for (; fOk; fOk = Thread32Next(hSnapshot, &te)) {
    // Is this thread in the desired process?
    if (te.th32OwnerProcessID == dwProcessID) {
    // Attempt to convert the thread ID into a handle.
    HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME,
    FALSE, te.th32ThreadID);
    if (hThread != NULL) {
    // Suspend or resume the thread.
    if (fSuspend)
    SuspendThread(hThread);
    else
    ResumeThread(hThread);
    }
    CloseHandle(hThread);
    }
    }
    CloseHandle(hSnapshot);
    }
    }
    

    5 进程的休眠

    VOID Sleep(DWORD dwMilliseconds)

    <1>线程的休眠导致线程在一定时间段内放弃被调度的机会

    <2>线程休眠的时间大约是指定的时间,可是可能远大于这个时间,这取决于操作系统

    <3>參数值为INFINITE,表示系统永远不去调度线程,可是这种方法不好

    <4>參数为0,表示放弃此次的时间片,切换到下一个线程,可是线程可能切换到自身假设没有同等优先级或者更高的优先级的存在。

    6 线程的切换

    BOOL SwitchToThread();

             当调用这个函数的时候,系统检測是否有一个线程迫切需求CPU时间,假设没函数就马上返回,假设有就切换到这个线程,即便线程的优先级可能低于当前的线程优先级。

             函数的功能和Sleep函数在參数值为0的时候非常相似,可是不同点是SwitchToThread函数同意优先级低的线程被调用,Sleep函数却不行。

    7 线程的运行时间

    <1>通常的程序执行时间计算方法:

    ULONGLONG qwStartTime = GetTickCount64();

    // Perform complex algorithm here.

    ULONGLONG qwElapsedTime = GetTickCount64()- qwStartTime;

    可是这样事实上是错误的,由于它如果线程运行不被为中断。

    <2>Windows提供了一个获取线程和进程时间信息的函数GetThreadTime, GetProcessTime

    BOOL GetThreadTimes(
    HANDLE hThread,
    PFILETIME pftCreationTim
    PFILETIME pftExitTime,
    PFILETIME pftKernelTime,
    PFILETIME pftUserTime);
    
    BOOL GetProcessTimes(
    HANDLE hProcess,
    PFILETIME pftCreationTime,
    PFILETIME pftExitTime,
    PFILETIME pftKernelTime,
    PFILETIME pftUserTime);


    <3>TSC 计时方法

             眼下线程的计时时间方式发生了变换,和之前精度为10-15ms的内部时钟计时器不同,系统如今採用一种Time Stamp Counter (TSC)计算时间,它表示的是自从计算机开机后执行的CPU周期个数。

             通过QueryThreadCycleTime和QueryProcessCycleTime来获取线程和进程运行的周期个数。

    BOOL WINAPI QueryThreadCycleTime(
     _In_   HANDLE ThreadHandle,
     _Out_  PULONG64 CycleTime      //包括用户时间和内核时间总和的周期计数值
    );
    BOOL WINAPI QueryProcessCycleTime(
      _In_   HANDLE ProcessHandle,
      _Out_  PULONG64 CycleTime      //包括用户时间和内核时间总和的周期计数值
    );

    <4>高精度计时方法

    BOOLQueryPerformanceFrequency(LARGE_INTEGER* pliFrequency);
    BOOL QueryPerformanceCounter(LARGE_INTEGER*pliCount);
    可是若用这两个函数来计算线程的运行时间的话,前提是如果线程不被抢占。

    8      Context的使用

             我们说Context中存放着线程的状态信息,同意线程在调用时候继续上次的运行。CONTEXT结构体是唯一的依赖于CPU的结构体。比如在X86体系结构中,它包括以下寄存器。

    CONTEXT_CONTROL,CONTEXT_DEBUG_REGISTERS,CONTEXT_FLOATING_POINT,CONTEXT_SEGMENTS,CONTEXT_INTEGER,CONTEXT_EXTENDED_REGISTERS

    比如在x86中,它例如以下所看到的:

    typedef struct _CONTEXT {
    //
    // The flag values within this flag control the contents of
    // a CONTEXT record.
    //
    // If the context record is used as an input parameter, then
    // for each portion of the context record controlled by a flag
    // whose value is set, it is assumed that that portion of the
    // context record contains valid context. If the context record
    // is being used to modify a thread's context, only that
    // portion of the thread's context will be modified.
    //
    // If the context record is used as an IN OUT parameter to capture
    // the context of a thread, only those portions of the thread's
    // context corresponding to set flags will be returned.
    //
    // The context record is never used as an OUT only parameter.
    //
    DWORD ContextFlags;
    //
    // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
    // set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT
    // included in CONTEXT_FULL.
    //
    DWORD    Dr0;
    DWORD    Dr1;
    DWORD    Dr2;
    DWORD    Dr3;
    DWORD    Dr6;
    DWORD    Dr7;
    // This section is specified/returned if theContextFlags word contains the flag //CONTEXT_FLOATING_POINT.
    FLOATING_SAVE_AREA FloatSave;
    //
    // This section is specified/returned if the
    // ContextFlags word contains the flag CONTEXT_SEGMENTS.
    //
    DWORD   SegGs;
    DWORD    SegFs;
    DWORD    SegEs;
    DWORD    SegDs;
    //
    // This section is specified/returned if the
    // ContextFlags word contains the flag CONTEXT_INTEGER.
    //
    DWORD    Edi;
    DWORD    Esi;
    DWORD    Ebx;
    DWORD    Edx;
    DWORD    Ecx;
    DWORD    Eax;
    //
    // This section is specified/returned if the
    // ContextFlags word contains the flag CONTEXT_CONTROL.
    //
    DWORD    Ebp;
    DWORD    Eip;
    DWORD    SegCs;               // MUST BE SANITIZED
    DWORD    EFlags;              // MUST BE SANITIZED
    DWORD    Esp;
    DWORD    SegSs;
    //
    // This section is specified/returned if the ContextFlags word
    // contains the flag CONTEXT_EXTENDED_REGISTERS.
    // The format and contexts are processor specific
    //
    BYTE     ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
    } CONTEXT;
    

    我们能够对CONTEXT中寄存器的内容进行读取和改写,这是相当酷比的行为。

    <1> 获取CONTEXT内容

    BOOL GetThreadContext(

    HANDLE hThread,

    PCONTEXT pContext);

    演示样例:

    CONTEXT Context;
    Context.ContextFlags = CONTEXT_CONTROL;
    GetThreadContext(hThread, &Context);

    <2>设置CONTEXT内容

    BOOL SetThreadContext(

    HANDLE hThread,

    CONST CONTEXT *pContext);

    演示样例:

    CONTEXT Context;
    SuspendThread(hThread);
    Context.ContextFlags = CONTEXT_CONTROL;
    GetThreadContext(hThread, &Context);
    Context.Eip = 0x00010000;
    Context.ContextFlags = CONTEXT_CONTROL;
    SetThreadContext(hThread, &Context);
    ResumeThread(hThread);

    9 线程的优先级

    <1>每一个线程都会被赋予编号为0-31的一个优先级别,31表示最高,0表示最低

    <2>仅仅要有优先级为31的可调用,就绝不会调用0-30的

    <3>即便低优先级正在执行,仅仅要系统发现一个高的优先级要执行,低的会被暂停。

    <4>当系统引导时会创建一个特殊线程叫做0页线程,是系统中唯一优先级为0的线程,当系统没有别的线程执行时候,0页线程会负责将系统全部空暇RAM页面置0.

    10 优先级的抽象说明

             应用程序的作用是不断变化的,需求也是不断变化的,今天设置的优先级可能在在明天已经不合时宜,为了能使今天写的程序能在以后的系统上正常的执行,所以调度算法不能一成不变,因此为微软给应用程序设置了一个抽象的优先级别,Windows支持6种优先级别

    通过这样的方式,能够简单改变程序的优先级别就达到改变程序中全部线程优先级别的作用。在应用程序的基础上再依据线程的优先级别,给线程设计优先类,一共7个例如以下所看到的:

    那么线程结合进程后,线程的优先级例如以下所看到的:



    注意:进程没有被调度,实质上没有优先级可言,这里说的进程的优先级仅仅是个抽象概念,通过这个抽象的优先级,能够改变线程的优先级。

    11 设置程序的优先级

    <1>CreateProcess的时候,dwCreationFlags參数能够设置


    <2> 子进程在执行的时候改变优先级

    BOOL SetPriorityClass(HANDLE  hProcess, DWORD  fdwPriority);

    <3>命令行启动程序

    当正常启动时候,默认进程是正常的优先级,当用STRAT启动进程的时候,能够附带优先级开关。例如以下所看到的:

    C:>START /LOW CALC.EXE

    /BELOWNORMAL, /NORMAL, /ABOVENORMAL,/HIGH,  /REALTIME,这些都是可选的模式

    <4> 通过任务管理器来设置进程的优先级别


    <5>设置线程优先级

    BOOL SetThreadPriority( HANDLE hThread, int nPriority);

    线程刚刚创建的时候,优先级是默认的正常优先级,设置优先级的代码例如以下所看到的:

    DWORD dwThreadID;
    HANDLE hThread = CreateThread(NULL, 0,ThreadFunc, NULL,
    CREATE_SUSPENDED, &dwThreadID);
    SetThreadPriority(hThread,THREAD_PRIORITY_IDLE);
    ResumeThread(hThread);
    CloseHandle(hThread)

    <6> 动态提高线程优先级

    线程的基本优先级:线程的相对优先级和线程所属的进程的优先级综合考虑得到的优先级

             系统经常要提高线程的优先级等级,以便对窗体消息或读取磁盘等I / O事件作出响应。

             系统仅仅能为基本优先级等级在1至1 5之间的线程提高其优先级等级

             线程的当前优先级不会低于线程的基本优先级

             系统决不会将线程的优先级等级提高到实时范围(高于 1 5)

    假设要拒绝操作系统动态的提高线程的优先级,那么就能够使用以下的两个函数:

    BOOL SetProcessPriorityBoost(HANDLEhProcess,         BOOLbDisablePriorityBoost);
    BOOL SetThreadPriorityBoost(HANDLE hThread,BOOLbDisablePriorityBoost);

    检查是否启动自己主动调整优先级,使用以下的两个函数:

    BOOL GetProcessPriorityBoost(HANDLEhProcess,PBOOL pbDisablePriorityBoost);
    BOOL GetThreadPriorityBoost(HANDLE hThread,PBOOLpbDisablePriorityBoost);

    <7>为前台进程调整调度程序

             当用户对进程的窗体进行操作时,该进程就称为前台进程,全部其它进程则称为后台进程。当然,用户希望他正在使用的进程比后台进程具有更强的响应性。为了提高前台进程的响应性,Wi n d o w s可以为前台进程中的线程调整其调度算法。对于 Windows 2000来说,系统可以为前台进程的线程提供比通常多的 C P U时间量。这样的调整仅仅能在前台进程属于正常优先级类的进程时才干进行。假设它属于其它不论什么优先级类,就无法进行不论什么调整。

             当一个优先级为正常的进程移到前台时,系统便将最低、低于正常、正常、高于正常和最高等优先级的线程的优先级提高 1,优先级为空暇和关键时间的线程的优先级则不予提高。

             设置是否開始提高前台调度性能的方法:

    10  亲缘性

             这个不多说了,就是在有多个CPU的时候,指定进程或者线程在哪几个指定的CPU上执行的策略,在一定情况下能够提高CPU的使用率。












  • 相关阅读:
    override与new的区别
    预处理指令关键字
    索引器
    可选参数与命名参数
    sealed关键字
    获取变量默认值
    is和as
    throw和throw ex的区别
    位操作
    unsafe关键字
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/4027161.html
Copyright © 2020-2023  润新知