Windows 7 和 Windows Vista 在後台處理方面經過了很多改進。如今實現有效後台處理 的挑戰包括:
• 性能——啟動延遲,登錄延遲,關閉延遲;後台 處理干擾前台處理
• 耗電
• 安全——攻擊面增多
Windows 7 後台服務和計劃任務采用各種機制最大程度地減小耗電量,減少系統攻擊 面,並提高應用程序和系統性能。這些機制包括:
• 服務請求的安全權限
• 服務 SID
• 延遲自動啟動服務
• 觸發器 啟動服務
• 計劃任務條件和設置
由於如今的 Windows 中存在大量服 務,後台處理必須滿足安全性、性能和耗電要求。
目標
在本實驗中,您將學 習如何:
• 設計和實現觸發器啟動服務
• 最大程度地減少 服務請求的權限量
系統要求
要完成本實驗,您必須擁有以下軟件:
• Microsoft Visual Studio 2008
• Windows 7
• Windows 7 SDK
• Windows Sysinternals Process Explorer
設置
本實驗要求 Windows 7 SDK 與 Visual Studio 2008 正確集成。可以按照以下步驟 實現:
1. 單擊“開始”菜單,轉到所有程序 | Microsoft Windows SDK v7.0 | Visual Studio Registration ,然後單擊 Windows SDK Configuration Tool 。在 Windows SDK Configuration Tool 對話框上,確認選擇了 v7.0 版本,然後單擊 Make Current 。
圖 1
Windows SDK 配置工具
2. 還需要更新 Visual C++ 目錄,使之指向 Windows 7 SDK 頭文件、庫和工具。路徑應該與 Windows 7 SDK 本地安裝路徑匹配。要做到 這一點,在 Microsoft Visual Studio 2008 菜單中選擇 Tools | Options 打開 Options 對話框。展開左側窗格中的 Projects and Solutions 節點,選擇 VC++ Directories 並按 照以下步驟操作:
a. 從右側窗格右上角的組合框中選擇 Executable files 。然後 選擇 按鈕,浏覽到 %Windows7SDKInstallDir%\v7.0\Bin 文件夾並單擊 Select Folder 。
圖 2
配置 VC++ 目錄
注意: Windows 7 SDK 默認安裝在 C:\Program Files\Microsoft SDKs\Windows 文件夾。
b. 重復上一步,對 Include files 選項 添加 %Windows7SDKInstallDir%\v7.0\Include 文件夾,然後對 Library Files 選項添加 %Windows7SDKInstallDir%\v7.0\Lib 文件夾。
c. 最後,單擊 OK 保存設置。
此外,本實驗需要特定的“硬編碼”文件夾,需要創建:
1. 在本 地 C 驅動器上,需要創建 C:\FromUSB 文件夾, USB 鍵上的所有映像都將復制到該文件夾 。
2. 在通用 USB 鍵上,需要創建 ToCopy 文件夾,所有的映像都從該文件夾復制。
注意: 本練習中演示的服務不是針對 Windows 背景處理而設計的。它只是一個示例 應用程序,演示如何使用 Windows 7 中的 Trigger Start Services 。有關設計有效背景處 理的更多信息,請參考 Microsoft 白皮書“ Designing Efficient Background Processes ”。
練習 1 :觸發器 - 啟動服務
在本練習 中,您將修改現有服務,使之成為更適合操作系統的服務。需要更改服務的請求安全權限, 使之在最低的權限下運行;您將更改服務配置,使之在需要時自動啟動,而不是在後台運行 並輪詢系統。
使用的示例服務是一個 USB 文件復制服務,它從插入系統的 USB 存儲設備的特定目錄中 復制文件。本練習中使用的技術適用於滿足特定標准的觸發器 - 啟動服務。
任務 1 – 實現服務配置更改
要使用編程方式修改服務配置,必須使用相應的參數調用 Win32 API 函數 ChangeServiceConfig2 。
1. 打開 %TrainingKitInstallDir%\BackgroundServices\Ex1- TriggerStartService\Begin 文件夾下的初始解決方案 Begin.sln ,並選擇要使用的語言( C# 或 VB )。
注意: Visual Studio 2008 必須以提升的模式運行。要做到這一點 ,右鍵單擊 Visual Studio 2008 圖標並選擇 Run as Administrator 。
2. 在 ServiceControlInterop 項目中,在 Source Files 文件夾下找到 ServiceControlInterop.cpp 文件,並在 RemoveAllPrivilegesFromService 方法下添加代 碼指定該服務不需要任何權限:
a. 分配 SERVICE_REQUIRED_PRIVILEGES_INFO 結構並將其 pmszRequiredPrivileges 方 法設置為 SE_CHANGE_NOTIFY_NAME L"\0" 。
b. 在 SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO 信息級別調用 ChangeServiceConfig2 方法, 並傳遞上一個步驟中分配的結構的地址。
完整示例見以下代碼(用粗體顯示):
C++
void ServiceControl::RemoveAllPrivilegesFromService (System::String^ serviceName)
{
....
if (hService == NULL)
{
DWORD dwLastError = GetLastError();
CloseServiceHandle(hSCManager);
throw Marshal::GetExceptionForHR(dwLastError);
}
//This effectively strips out all other privileges.
SERVICE_REQUIRED_PRIVILEGES_INFO requiredPrivileges = {0};
requiredPrivileges.pmszRequiredPrivileges = SE_CHANGE_NOTIFY_NAME L"\0";
if (ChangeServiceConfig2(hService, SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO, &requiredPrivileges) == FALSE)
{
DWORD dwLastError = GetLastError();
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
throw Marshal::GetExceptionForHR(dwLastError);
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
}
3. 在 SetServiceTriggerStartOnUSBArrival 方法中添加代碼,在通用 USB 磁盤可用時 將服務設置為觸發器 - 啟動服務:
a. 分配 SERVICE_TRIGGER_SPECIFIC_DATA_ITEM 結構。要實現這一點:
將其 dwDataType 成員設置為 SERVICE_TRIGGER_DATA_TYPE_STRING 。
將其 cbData 成員設置為字符串 L"USBSTOR\\GenDisk" 的字節長度。
將其 pData 成員設置為該字符串。
b. 分配 SERVICE_TRIGGER 結構。要實現這一點:
將其 dwTriggerType 成員設置為 SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL 。
將其 dwAction 成員設置為 SERVICE_TRIGGER_ACTION_SERVICE_START 。
將其 pTriggerSubtype 成員設置為 GUID_USBDevice GUID 的地址。
將其 cDataItems 成員設置為 1 ,將其 pDataItems 成員設置為上一步分配的結構的地址。
c. 分配 SERVICE_TRIGGER_INFO 結構。要實 現這一點:
將其 cTriggers 成員設置為 1 ,將其 pTriggers 成員設置為上一步中 分配的結構的地址。
d. 在 SERVICE_CONFIG_TRIGGER_INFO 信息級別調用 ChangeServiceConfig2 函數,並傳遞上一步中分配的結構的地址。
完整示例見以下 代碼(用粗體顯示):
C++
void ServiceControl::SetServiceTriggerStartOnUSBArrival(System::String^ serviceName)
{
...
if (hService == NULL)
{
DWORD dwLastError = GetLastError();
CloseServiceHandle(hSCManager);
throw Marshal::GetExceptionForHR(dwLastError);
}
LPCWSTR lpszDeviceString = L"USBSTOR\\GenDisk";
SERVICE_TRIGGER_SPECIFIC_DATA_ITEM deviceData = {0};
deviceData.dwDataType = SERVICE_TRIGGER_DATA_TYPE_STRING;
deviceData.cbData = (wcslen(lpszDeviceString)+1) * sizeof(WCHAR);
deviceData.pData = (PBYTE)lpszDeviceString;
SERVICE_TRIGGER serviceTrigger = {0};
serviceTrigger.dwTriggerType = SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL;
serviceTrigger.dwAction = SERVICE_TRIGGER_ACTION_SERVICE_START;
serviceTrigger.pTriggerSubtype = (GUID*)&GUID_USBDevice;
serviceTrigger.cDataItems = 1;
serviceTrigger.pDataItems = &deviceData;
SERVICE_TRIGGER_INFO serviceTriggerInfo = {0};
serviceTriggerInfo.cTriggers = 1;
serviceTriggerInfo.pTriggers = &serviceTrigger;
if (ChangeServiceConfig2(hService, SERVICE_CONFIG_TRIGGER_INFO, &serviceTriggerInfo) == FALSE)
{
DWORD dwLastError = GetLastError();
CloseServiceHandle (hService);
CloseServiceHandle(hSCManager);
throw Marshal::GetExceptionForHR(dwLastError);
}
CloseServiceHandle(hService);
CloseServiceHandle (hSCManager);
}
這就是 .NET 與觸發器 - 啟動 API 交互所需 C++/CLI 包裝器的完整代碼。
任務 2 – 添加代碼,將服務注冊為觸發器 - 啟動服務
使用 sc.exe 命令行實用工 具(在提升的命令 Shell 中使用 sc triggerinfo 命令)或 ChangeServiceConfig2 API 編 程(上一個任務的一部分)可以將服務注冊為觸發器 - 啟動服務。
1. 在 RegisterService 項目中,找到 RegisterServiceForm.cs (C#) 或 RegisterServiceForm.vb (Visual Basic) 代碼文件,並在 btnRegisterTriggerStart_Click 方法中添加代碼,使用 C++/CLI 包裝器將服務配置更改為 觸發器 - 啟動服務,如下所示(用粗體顯示):
C#
private void btnRegisterTriggerStart_Click(object sender, EventArgs e)
{
AddService();
ServiceControl.SetServiceTriggerStartOnUSBArrival(ServiceName);
StopLogReaderTimer();
StartLogReaderTimer();
}
Visual Basic
Private Sub btnRegisterTriggerStart_Click() Handles btnRegisterTriggerStart.Click
AddService()
ServiceControl.SetServiceTriggerStartOnUSBArrival(ServiceName)
StopLogReaderTimer()
StartLogReaderTimer()
End Sub
2. 在 btnRemovePrivileges_Click 方法中,使用 C++/CLI 包裝器添加 代碼更改服務配置,使其請求最小的權限,如下所示:
C#
private void btnRemovePrivileges_Click(object sender, EventArgs e)
{
ServiceControl.RemoveAllPrivilegesFromService(ServiceName);
}
Visual Basic
Private Sub btnRemovePrivileges_Click() Handles btnRemovePrivileges.Click
ServiceControl.RemoveAllPrivilegesFromService(ServiceName)
End Sub
完成對服務注冊 UI 應用程序的更改後,服務可以使用最少的權限注冊為 觸發器 - 啟動服務。
任務 3 – 為觸發器 - 啟動服務啟用 UsbCopyService
與使用周期性計時器輪詢感興趣事件的自動啟動服務不同,觸發器 - 啟動服務在觸發器啟動它們時活動,在執行完任務之後停用(停止)。
1. 在 UsbCopyService 項目中,找到並打開 USBService.cs (C#) 或 USBService.vb (Visual Basic) 代碼文件。
2. (僅用於 Visual Basic 用戶)添加 ProcessTriggerStart 方法,在服務被配置為觸發器 - 啟動時將調用該方法。
Visual Basic
Private Sub ProcessTriggerStart()
DoWork()
End Sub
3. 修改 OnStart 方法,使用 ServiceControl.IsServiceTriggerStart 方法檢查服務是否配置為觸發器 - 啟動服務(傳 遞從 ServiceBase 類繼承的 ServiceName 屬性作為參數)。為此,使用以下代碼替換方法 實現:
C#
protected override void OnStart(string[] args)
{
if (ServiceControl.IsServiceTriggerStart(ServiceName))
{
}
else
{
_timer = new Timer(delegate
{
DoWork();
});
_timer.Change(0, 5000);
}
}
Visual Basic
Protected Overrides Sub OnStart(ByVal args() As String)
If ServiceControl.IsServiceTriggerStart(ServiceName) Then
Else
_timer = New Timer(AddressOf ProcessTimerEvent)
_timer.Change(0, 5000)
End If
End Sub
4. 現在,如果將服務配置為觸發器 - 啟動服務而不 是創建周期性的計時器,該方法將按順序排列所有僅執行一次的工作。為此,添加以下代碼 (用粗體顯示):
C#
protected override void OnStart(string[] args)
{
if (ServiceControl.IsServiceTriggerStart (ServiceName))
{
ThreadPool.QueueUserWorkItem (delegate
{
DoWork();
});
}
else
{
_timer = new Timer(delegate
{
DoWork();
});
_timer.Change(0, 5000);
}
}
Visual Basic
Protected Overrides Sub OnStart(ByVal args() As String)
If ServiceControl.IsServiceTriggerStart(ServiceName) Then
ThreadPool.QueueUserWorkItem(AddressOf ProcessTriggerStart)
Else
_timer = New Timer(AddressOf ProcessTimerEvent)
_timer.Change(0, 5000)
End If
End Sub
5. 完成工作之後,應該使用從 ServiceBase 類中繼承的 Stop 方法停止服務。為此,將 以下代碼(用粗體顯示)添加到 OnStart 方法 (C#) 或 ProcessTriggerStart 方法 (Visual Basic) :
C#
protected override void OnStart(string [] args)
{
if (ServiceControl.IsServiceTriggerStart (ServiceName))
{
ThreadPool.QueueUserWorkItem (delegate
{
DoWork();
Stop();
});
}
else
{
...
}
}
Visual Basic
Private Sub ProcessTriggerStart()
DoWork()
Me.Stop()
End Sub
6. 下面開始測試服務。運行 RegisterService 項目。
注意: 項目應該由管理員運行。非管理員用戶可以通過提升的 Visual Studio 實例或者通過 shell 或命令提示符運行可執行文件運行項目。
7. 單擊 Register Demand-Start 按鈕將服務注冊為根據需要啟動的服務,並單擊 Start 按鈕啟動它 。它將每隔 5 秒自動輪詢一次 USB 設備。在檢測到帶有 ToCopy 目錄的 USB 設備時,它將 該目錄的內容復制到 C:\FromUSB 目錄。
圖 3
將服務注冊為根據需要啟動的服務
8. 啟動 Sysinternals Process Explorer 檢查服務的訪問令牌。在流程樹中右鍵單擊 UsbCopyService.exe 流程,選擇 Properties 並切換到 Security 選項卡。在服務權限列表下有很多權限,有些是啟用的,有 些是禁用的。
圖 4
檢查服務權限
注意: Sysinternals Process Explorer (procexp.exe) 的 下載鏈接可以從本實驗的先決條件小節找到。
9. 使用 Stop 按鈕停止服務。
10. 單擊 Remove Privileges 按鈕從服務中移除權限。
11. 單擊 Start 按 鈕啟動服務,並使用 Sysinternals Process Explorer 再次檢查訪問令牌。服務的權限列表 現在應該僅包含 SeChangeNotifyPrivilege (為了應用程序兼容目的,總是保留該權限)。
圖 5
服務權限(移除權限之後)
12. 使用 Stop 按鈕停止服務,使用 Delete 按 鈕刪除它。
13. 從計算機移除 USB 設備。
14. 單擊 Register Trigger- Start 按鈕將服務注冊為觸發器 - 啟動服務。不要手動啟動服務。
15. 使用最高權 限從命令提示符運行以下命令,檢查服務的觸發器 - 啟動配置。
命令
sc qtriggerinfo UsbCopyService
圖 6
檢查服務的觸發器 - 啟動配置
16. 插入帶有 ToCopy 目錄的 USB 設 備。該設備應該自動啟動,復制文件然後自動關閉。
17. 可以使用最高權限從命令提 示符運行以下命令,驗證服務關閉。
命令
sc queryex UsbCopyService
圖 7
驗證服務關閉
18. 完成後,單擊 Delete 按鈕刪除服務。
19. 可以使用最高權限從命令提示符運行與上一條相同的命令,確保服務已經刪除。
命令
sc queryex UsbCopyService
圖 8
驗證服務已經刪除
在本練習中,您修改了現有服務,使之變為觸發器 - 啟動服務,在系統檢測到 USB 設備時可以由觸發器啟動。您還減小了服務必需的權限數, 減少了系統的攻擊面。完整的練習解決方案位於 %TrainingKitInstallDir% \BackgroundServices\Ex1-TriggerStartService\End 目錄中,根據不同的語言有不同的版 本。
小結
在本實驗中,您提高了 Windows 服務的性能和安全性,使其更加適 合操作系統。您使用觸發器 - 啟動服務機制,僅在真正需要執行任務時才啟動服務,此外, 還移除了處理令牌中不必要的權限,最大程度地減少了服務的攻擊面。
本 Windows 7 課程代碼示例中包含完整的 USB 復制服務演示和 Weather Updater 服務。