《C++多线程编程实战》——2.10 在内核实现线程

2.10 在内核实现线程

整个内核就是一个进程,许多系统(内核)线程在其上下文中运行。内核有一个线程表,跟踪该系统中所有的线程。

内核维护这个传统的进程表以跟踪进程。那些可以阻塞线程的函数调用可作为系统调用执行,这比执行系统过程的代价更高。当线程被阻塞时,内核必须运行其他线程。当线程被毁坏时,则被标记为不可运行。但是,它的内核数据结构不会受到影响。然后在创建新的线程时,旧的线程将被再次激活,回收资源以备后用。当然,也可以回收用户级线程,但如果线程管理开销非常小,就没必要这样做。

准备就绪
下面的示例要求安装WinDDK(Driver Development Kit,驱动程序开发工具包),详情请参阅附录。成功安装WinDDK后,运行Visual Studio。

操作步骤
1. 创建一个新的Win32应用程序项目,并命名为KernelThread

2. 打开【解决方案资源管理器】,并添加一个新的头文件,命名为ThreadApp.h。打开ThreadApp.h,并输入下面的代码:

#include <windows.h>
#include <tchar.h>

#define DRIVER_NAME TEXT( "TestDriver.sys" )
#define DRIVER_SERVICE_NAME TEXT( "TestDriver" )
#define Message(n) MessageBox(0, TEXT(n), \
TEXT("Test Driver Info"), 0)

BOOL StartDriver(LPTSTR szCurrentDriver);
BOOL StopDriver(void);```
3.现在,打开【解决方案资源管理器】,并添加一个新的源文件,命名为`ThreadApp.cpp`。打开`ThreadApp.cpp`,并输入下面的内容:

include "ThreadApp.h"

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
  LPSTR szCommandLine, int iCmdShow)
{
  StartDriver(DRIVER_NAME);
  ShellAbout(0, DRIVER_SERVICE_NAME, TEXT(""), NULL);
  StopDriver();
  return 0;
}

BOOL StartDriver(LPTSTR szCurrentDriver)
{
  HANDLE hFile = 0;
  DWORD dwReturn = 0;
  SC_HANDLE hSCManager = { 0 };
  SC_HANDLE hService = { 0 };
  SERVICE_STATUS ServiceStatus = { 0 };
  TCHAR szDriverPath[MAX_PATH] = { 0 };
  GetSystemDirectory(szDriverPath, MAX_PATH);
  TCHAR szDriver[MAX_PATH + 1];

ifdef _UNICODE

  wsprintf(szDriver, L"\drivers\%ws", DRIVER_NAME);

else

  sprintf(szDriver, "\drivers\%s", DRIVER_NAME);

endif

  _tcscat_s(szDriverPath, (_tcslen(szDriver) + 1) * sizeof(TCHAR),
    szDriver);
  BOOL bSuccess = CopyFile(szCurrentDriver, szDriverPath, FALSE);
  if (bSuccess == FALSE)
  {
    Message("copy driver failed");
    return bSuccess;
  }
  hSCManager = OpenSCManager(NULL, NULL,
    SC_MANAGER_CREATE_SERVICE);
  if (hSCManager == 0)
  {
    Message("open sc manager failed!");
    return FALSE;
  }
  hService = CreateService(hSCManager, DRIVER_SERVICE_NAME,
    DRIVER_SERVICE_NAME, SERVICE_START | DELETE | SERVICE_STOP,
    SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE,
    szDriverPath, NULL, NULL, NULL, NULL, NULL);
  if (hService == 0)
  {
    hService = OpenService(hSCManager, DRIVER_SERVICE_NAME,
      SERVICE_START | DELETE | SERVICE_STOP);
    Message("create service failed!");
  }
  if (hService == 0)
  {
    Message("open service failed!");
    return FALSE;
  }
  BOOL startSuccess = StartService(hService, 0, NULL);
  if (startSuccess == FALSE)
  {
    Message("start service failed!");
    return startSuccess;
  }
  CloseHandle(hFile);
  return TRUE;
}

BOOL StopDriver(void)
{
  SC_HANDLE hSCManager = { 0 };
  SC_HANDLE hService = { 0 };
  SERVICE_STATUS ServiceStatus = { 0 };
  TCHAR szDriverPath[MAX_PATH] = { 0 };
  GetSystemDirectory(szDriverPath, MAX_PATH);
  TCHAR szDriver[MAX_PATH + 1];

ifdef _UNICODE

  wsprintf(szDriver, L"\drivers\%ws", DRIVER_NAME);

else

  sprintf(szDriver, "\drivers\%s", DRIVER_NAME);

endif

  _tcscat_s(szDriverPath, (_tcslen(szDriver) + 1) * sizeof(TCHAR),
    szDriver);
  hSCManager = OpenSCManager(NULL, NULL,
    SC_MANAGER_CREATE_SERVICE);
  if (hSCManager == 0)
  {
    return FALSE;
  }
  hService = OpenService(hSCManager, DRIVER_SERVICE_NAME,
    SERVICE_START | DELETE | SERVICE_STOP);
  if (hService)
  {
    ControlService(hService, SERVICE_CONTROL_STOP,
      &ServiceStatus);
    DeleteService(hService);
    CloseServiceHandle(hService);
    BOOL ifSuccess = DeleteFile(szDriverPath);
    return TRUE;
  }
  return FALSE;
}`
4.现在,打开【解决方案资源管理器】,创建一个新的空Win32控制台项目,并命名为DriverApp

5.添加一个新的头文件,命名为DriverApp.h,并输入以下代码:

#include <ntddk.h> 

DRIVER_INITIALIZE DriverEntry;
DRIVER_UNLOAD OnUnload;```
6.打开【解决方案资源管理器】,在`DriverApp`项目下,添加一个新的源文件,命名为`DriverApp.cpp`。打开`DriverApp.cpp`,并输入以下代码:

include "DriverApp.h"

VOID ThreadStart(PVOID lpStartContext)
{
  PKEVENT pEvent = (PKEVENT)lpStartContext;
  DbgPrint("Hello! I am kernel thread. My ID is %u. Regards..",
    (ULONG)PsGetCurrentThreadId());
  KeSetEvent(pEvent, 0, 0);
  PsTerminateSystemThread(STATUS_SUCCESS);
}

NTSTATUS DriverEntry(PDRIVER_OBJECT theDriverObject, PUNICODE_STRING
  theRegistryPath)
{
  HANDLE hThread = NULL;
  NTSTATUS ntStatus = 0;
  OBJECT_ATTRIBUTES ThreadAttributes;
  KEVENT kEvent = { 0 };
  PETHREAD pThread = 0;
  theDriverObject->DriverUnload = OnUnload;
  DbgPrint("Entering KERNEL mode..");
  InitializeObjectAttributes(&ThreadAttributes, NULL, OBJ_KERNEL_HANDLE,
    NULL, NULL);
  __try
  {
    KeInitializeEvent(&kEvent, SynchronizationEvent, 0);
    ntStatus = PsCreateSystemThread(&hThread, GENERIC_ALL,
      &ThreadAttributes, NULL, NULL, (PKSTART_ROUTINE)&ThreadStart,
      &kEvent);
    if (NT_SUCCESS(ntStatus))
    {
      KeWaitForSingleObject(&kEvent, Executive, KernelMode, FALSE,
        NULL);
      ZwClose(hThread);
    }
    else
    {
      DbgPrint("Could not create system thread!");
    }
  }
  __except (EXCEPTION_EXECUTE_HANDLER)
  {
    DbgPrint("Error while creating system thread!");
  }
  return STATUS_SUCCESS;
}

VOID OnUnload(PDRIVER_OBJECT DriverObject)
{
  DbgPrint("Leaving KERNEL mode..");
}`
示例分析
首先,创建了一个Win32应用程序,仅作为演示用。我们只想把驱动程序加载至内核中,还没有UI,甚至没有消息循环。然后,程序将显示ShellAbout对话框,这仅仅是为了让用户有时间阅读DbgView输出(欲详细了解DbgView,请参阅附录)。在用户关闭ShellAbout对话框后,程序将卸载驱动程序,应用程序结束。

我们创建的Win32应用程序只能加载和卸载驱动程序,所以不做进一步解释了。现在,来看DriverApp项目。为编译驱动程序设置好Visual Studio后(请查阅附录了解详细的Visual Studio的编译设置),我们声明了下面两个主例程,每个驱动程序都必须在DriverApp.h头文件中:

DRIVER_INITIALIZE DriverEntry;
DRIVER_UNLOAD OnUnload;```
这两个例程是驱动程序入口点和驱动程序的卸载例程。我们将使用驱动程序入口点初始化一个线程对象,并启动内核线程。新创建的线程将只写入一条显示它唯一标识符的消息,然后立刻返回。要说明的是,深入探讨和开发内核超出了本书讨论的范围,我们在这里浅尝辄止。因为驱动程序被编译为/TC2,我们必须确保在执行第一条命令之前已经声明了所有变量。如下代码所示:

HANDLE hThread = NULL;
NTSTATUS ntStatus = 0;
OBJECT_ATTRIBUTES ThreadAttributes;
KEVENT kEvent = { 0 };
PETHREAD pThread = 0;`
然后,还必须设置卸载例程:
`
theDriverObject->DriverUnload = OnUnload;`
另外,在创建内核线程之前,要用InitializeObjectAttributes例程初始化ThreadAttribute对象:
`
InitializeObjectAttributes(&ThreadAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);`
内核开发必须执行得非常谨慎,哪怕是一丁点儿错误都会导致蓝屏死机BSOD)或机器崩溃。为此,我们使用_ try - _except块,它与我们熟悉的try-catch块稍有不同。

在内核中创建句柄和在用户空间中创建句柄不同。KeWaitForSingleObject例程无法使用PsCreateSystemThread返回的句柄。我们要在KeWaitForSingleObject返回时在线程中初始化一个触发的事件(事件将在第3章中详细介绍)。PsCreateSystemThread例程必须与ZwClose例程成对调用。调用ZwClose关闭内核句柄和防止内存泄漏。

最后,我们要实现PKSTART_ROUTINE或线程的开始地址,线程的指令从这里开始执行。下面是一个示例:
`
VOID (__stdcall* KSTART_ROUTINE)( PVOID StartContext );`
我们已经通过PsCreateSystemThread的最后一个参数传递了一个指向KEVENT的指针。现在,使用DbgPrint把相应的线程ID写入消息供用户阅读。然后,设置一个事件,以便相应的KeWaitForSingleObject调用可以返回并安全地退出驱动程序。确保PsTerminateSystemThread没有返回。内核将在卸载驱动程序时清理线程对象。

更多讨论
虽然内核线程能解决一些问题,但也不是万能的。例如,当多线程进程创建其他多线程进程时会发生什么情况?应该创建与旧进程的线程一样多的新进程,还是创建只有一个线程的新进程?在多数情况下,这取决于你下一步打算用进程做什么。如果要启动一个新程序,也许应该创建只有一个线程的进程。但如果是继续执行,也许应该创建具有同样数量线程的进程才对。

本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

时间: 2024-09-11 15:54:24

《C++多线程编程实战》——2.10 在内核实现线程的相关文章

《C++多线程编程实战》导读

前言 C++多线程编程实战多线程编程正逐渐成为IT行业和开发人员关注的焦点.开发商希望开发出用户友好.界面丰富,而且能并发执行的应用程序.强大的C++语言和本地Win32 API特性为多线程编程提供了良好开端.有了强大的C++,可以轻松地创建不同类型的应用程序,执行并行,而且还能优化现有的工作. 本书是一本实践为主.通俗易懂的Windows多线程编程指导.你将学到如何从多线程方案中受益,增强你的开发能力,构建更好的应用程序.本书不仅讲解了创建并行代码时遇到的问题,而且还帮助读者详细理解同步技术.

《C++多线程编程实战》——第2章 进程和线程的概念2.1 简介

第2章 进程和线程的概念 C++多线程编程实战本章介绍以下内容: 进程和线程解释进程模型进程的实现进程间通信(IPC)解决典型的IPC问题线程模型的实现线程的用法在用户空间实现线程在内核实现线程 2.1 简介 现在的计算机能同时处理多件事,许多Windows用户还没有完全意识到这一点.我们举例说明一下.当启动PC系统时,许多进程都在后台启动(例如,管理电子邮件的进程.负责更新病毒库的进程等).通常,用户在执行其他任务时(如,上网),还会打印文件或播放CD.这些活动都需要管理.支持多进程的多任务系

Java多线程编程实战之不提倡的方法

不提倡使用的方法是为支持向后兼容性而保留的那些方法,它们在以后的版本中可能出现,也可能不出现.Java 多线程支持在版本 1.1 和版本 1.2 中做了重大修订,stop().suspend() 和 resume() 函数已不提倡使用.这些函数在 JVM 中可能引入微妙的错误.虽然函数名可能听起来很诱人,但请抵制诱惑不要使用它们. 调试线程化的程序 在线程化的程序中,可能发生的某些常见而讨厌的情况是死锁.活锁.内存损坏和资源耗尽. 死锁 死锁可能是多线程程序最常见的问题.当一个线程需要一个资源而

《C++多线程编程实战》——第1章 C++概念和特性简介1.1 介绍

第1章 C++概念和特性简介 C++多线程编程实战 本章介绍以下内容: 创建一个C++项目 程序结构.执行流.运行时对象 结构编程方法 理解面向对象编程方法 解释继承.重载和覆盖 理解多态 事件处理器和消息传递接口 链表.队列.栈示例 1.1 介绍 系统所执行的程序的进程或抽象是所有操作系统的核心概念.现在,绝大多数的操作系统在同一时间内都可以进行多项操作.例如,计算机在用户编辑Word文档时,还可以打印该文档.从硬盘缓冲区读数据.播放音乐等.在多任务操作系统中,中央处理单元(CPU)在程序中快

《JAVA多线程编程实战指南》之Two-phase Termination(两阶段终止)模式

本文是<JAVA多线程编程实战指南>的样章,感谢作者授权并发网(ifeve.com)发表此文.感谢demochen整理此文. 5.1Two-phase Termination模式简介 停止线程是一个目标简单而实现却不那么简单的任务.首先,Java没有提供直接的API用于停止线程.此外,停止线程还有一些额外的细节需要考虑,如停止的线程处于阻塞(如等待锁)或者等待状态(等待其他线程),尚有未处理完的任务等. Two-phase Termination模式通过将停止线程这个动作分解为准备阶段和执行阶

《C#多线程编程实战(原书第2版)》——2.10 使用SpinWait类

2.10 使用SpinWait类 本节将描述如何不使用内核模型的方式来使线程等待.另外,我们介绍了SpinWait,它是一个混合同步构造,被设计为使用用户模式等待一段时间,然后切换到内核模式以节省CPU时间. 2.10.1 准备工作 为了学习本节,你需要安装Visual Studio 2015.除此之外无需其他准备.本节的源代码放置在BookSamples\Chapter2\Recipe9目录中. 2.10.2 实现方式 请执行以下步骤来了解如何不借助于内核模式方法来实现线程等待: 1.启动Vi

《C#多线程编程实战(原书第2版)》——1.10 使用C#中的lock关键字

1.10 使用C#中的lock关键字 本节将描述如何确保当一个线程使用某些资源时,同时其他线程无法使用该资源.我们将了解该情况的必要性及整个线程安全概念都包含什么. 1.10.1 准备工作 为了学习本节,你需要安装Visual Studio 2015.除此之外无需其他准备.本节的源代码放置在BookSamplesChapter1Recipe9目录中. 1.10.2 实现方式 请执行以下步骤来了解如何使用C#中的lock关键字: 1.启动Visual Studio 2015.新建一个C#控制台应用

《C#多线程编程实战(原书第2版)》——导读

前 言 不久前,典型的个人计算机的CPU还只有一个计算核心,并且功耗足以煎熟鸡蛋.2005年,英特尔推出了其首款多核心CPU,从此计算机开始向不同的方向发展.低耗电量及多个计算核心变得比提高行计算(row computing)的核心性能更重要.这也导致了编程范式的改变.现在我们需要学习如何有效地使用所有CPU核心来最优化性能,并同时通过在特定时间只运行需要的程序来节省电池电量.除此之外,我们在编写服务器端应用程序时需要有效地利用多个CPU核心,甚至多台计算机来支持尽可能多的用户. 为了创建这样的

Java多线程编程实战之限制优先级

限制线程优先级和调度 Java 线程模型涉及可以动态更改的线程优先级.本质上,线程的优先级是从 1 到 10 之间的一个数字,数字越大表明任务越紧急.JVM 标准首先调用优先级较高的线程,然后才调用优先级较低的线程.但是,该标准对具有相同优先级的线程的处理是随机的.如何处理这些线程取决于基层的操作系统策略.在某些情况下,优先级相同的线程分时运行:在另一些情况下,线程将一直运行到结束.请记住,Java 支持 10 个优先级,基层操作系统支持的优先级可能要少得多,这样会造成一些混乱.因此,只能将优先