Ajuster la fréquence de votre processeur avec l'API Windows Power Management

Cet article a été initialement publié dans Programmez! 138

Les sources complets sont disponibles en fin d'article.

Sans doute avez-vous déjà visité les options d'alimentation dans le panneau de configuration de Windows ? Rien de très folichon là-dedans pour les ordinateurs de bureau. C'est un peu plus intéressant pour les ordinateurs portables qui disposent de deux modes d'alimentation, secteur et batterie. Ce panneau de configuration est toutefois bien loin de laisser entrevoir la richesse et la puissance de l'API qui se cache derrière. Avec cette API, il est possible d'ajuster de très nombreux paramètres inédits. Pour le découvrir, nous imaginons que nous possédons un ordinateur portable que nous devons utiliser pendant un temps très long, mais dans une situation demandant très peu de puissance, comme par exemple, la simple prise de notes au cours d'une conférence interminable. Il serait intéressant d'avoir dans cette situation une petite application de type tableau de bord, qui informe de l'autonomie de la batterie, ce qui reste classique, mais qui permette aussi d'ajuster la fréquence du processeur, ou de fixer à cette fréquence un seuil maximum, afin d'économiser la batterie. Ce dernier point est beaucoup plus geek, et cet article va s'efforcer de donner tous les éléments nécessaires à la réalisation d'une telle application.

 

Avec l'API Power Management

Tout programme C ou C++ qui travaille avec cette API doit voir son édition de liens faite avec PowrProf.lib. Pour l'état du système, nous utilisons cette fois une de ces fonctions "couteau suisse" dont Microsoft a le secret. Il s'agit de CallNtPowerInformation qui, comme le nom le suggère, est antérieure à Windows Vista. Cette API renvoie des résultats qui ne sont pas toujours très fiables. Ainsi, si pour nous faire la main, nous lui demandons si la cadence de notre processeur est ajustable (Programme DemoCallNtPowerInformation):

 

Le matériel

Il va de soi qu'il est nécessaire de posséder une machine récente qui supporte l'underclocking, ou sous-cadencement du processeur par programmation. Les exemples qui accompagnent cet article ont été essayés sur un ordinateur portable ASUS X71SL. Le code C++ a été écrit sous Visual Studio 2008. Tout autre compilateur C++ conviendra. Le système d'exploitation est Windows 7. Les exemples devraient fonctionner également avec Windows Vista. Ils ne fonctionneront pas sur des systèmes antérieurs. Enfin, signalons que l'API Power Management est constituée de deux jeux de fonctions pêle-mêle, pré Vista et post Vista. Le lecteur qui voudrait aller plus loin et enrichir les exemples de cet article devra être très attentif à n'utiliser que des fonctions Vista ou supérieur. Sauf erreur de ma part, les fonctions pour les systèmes antérieurs ne fonctionnent pas très bien sous Vista et Windows 7. Enfin on ne peut se passer de la documentation Microsoft, MSDN en ligne ou SDK 7, même si cette documentation manque cruellement de clarté, ce à quoi cet article va tenter de remédier.

 

Savoir observer le système

Si l'on souhaite ajuster la consommation énergétique d'un système, il faut commencer par être capable de savoir à tout moment ce qui s'y passe. Sous Windows, il y a une API pour cela, la WMI, qui est l'implémentation des spécifications CIM et WBEM définies par la Distributed Management Task Force. Celle-ci est une organisation qui développe et maintient des standards pour l'administration de systèmes informatiques. Dit très succinctement, CIM est un jeu de classes, chacune d'entre elles décrivant un élément d'un système, et la WBEM est un jeu de fonctions pour manipuler ces classes. Le sujet de la WMI a été traité par exemple dans l'article "Observez la charge de votre processeur multi-coeurs sous Windows" de Programmez! n° 110 et nous invitons le lecteur qui voudrait approfondir la question à s'y reporter. Nous donnons simplement ici un programme en C# qui affiche la cadence de processeur, la nature de l'alimentation (secteur ou batterie) et l'état de charge de la batterie ainsi que son autonomie estimée. Il est nécessaire que le programme fonctionne quelques secondes pour que la valeur de l'autonomie soit ajustée par le système:

 

using System; using System.Management; using System.Threading; namespace CSharpWatchProcessor { class Program { void Go() { ManagementClass proc = new ManagementClass("Win32_Processor"); ManagementClass batt = new ManagementClass("Win32_Battery"); while (true) { ManagementObjectCollection cpus = proc.GetInstances(); foreach (ManagementObject cpu in cpus) { Console.WriteLine("Processeur"); UInt32 maxclockspeed = (UInt32)cpu.GetPropertyValue("MaxClockSpeed"); UInt32 currentclockspeed = (UInt32)cpu.GetPropertyValue("CurrentClockSpeed"); Console.WriteLine("CurrentClockSpeed: {0} Max: {1}", currentclockspeed, maxclockspeed); try { UInt32 voltagecaps = (UInt32)cpu.GetPropertyValue("VoltageCaps"); Console.WriteLine("VoltageCaps: {0}", voltagecaps); } catch (SystemException) { Console.WriteLine("VoltageCaps inconnu"); } UInt16 currentvoltage = (UInt16)cpu.GetPropertyValue("CurrentVoltage"); Console.WriteLine("CurrentVoltage: {0}V", currentvoltage / 10.0); } Console.WriteLine(); Console.WriteLine("Batterie"); ManagementObjectCollection batteries = batt.GetInstances(); foreach (ManagementObject batterie in batteries) { UInt16 status = (UInt16)batterie.GetPropertyValue("BatteryStatus"); Console.WriteLine("Status: " + BatterieStatus(status)); UInt16 charge_percent = (UInt16)batterie.GetPropertyValue("EstimatedChargeRemaining"); Console.WriteLine("Charge: {0} %", charge_percent); if (status != 2) // si alimentation secteur, pas de problème d'autonomie :-) { UInt32 autonomie = (UInt32)batterie.GetPropertyValue("EstimatedRunTime"); Console.WriteLine("Autonomie estimee: {0} minutes", autonomie); } } Console.WriteLine(); Console.WriteLine(); Thread.Sleep(3000); } } static void Main(string[] args) { Program p = new Program(); p.Go(); } String BatterieStatus(UInt16 status) { String s; switch (status) { case 1: s = "en decharge"; break; case 2: s = "alimentation secteur"; break; case 3: s = "pleine charge"; break; case 4: s = "faible"; break; case 5: s = "critique"; break; case 6: s = "en charge"; break; case 7: s = "en charge et haute"; break; case 8: s = "en charge et faible"; break; case 9: s = "en charge et critique"; break; case 10: s = "indefini"; break; case 11: s = "charge partielle"; break; default: s = "inconnu"; break; } return s; } } }

Quelques petites remarques à propos de ce code. Tout d'abord ne nous laissons pas abuser par la terminologie: des termes comme ManagementClass concerne la WMI et sont sans rapport avec l'API Power Management qui est au centre de cet article. Sur ma machine, il s'est avéré impossible de lire la propriété VoltageCaps de la classe WIN32_Processor. La lecture provoque la levée d'une exception, et c'est pourquoi le morceau de code correspondant est placé dans une construction try/catch. Bien entendu, il est possible d'interroger la WMI avec C++, mais cela fait appel à des notions qui sortent du cadre de cet article, nous renvoyons pour cela le lecteur à Programmez! 110. Le code en C# est également plus concis. Pourquoi avoir mentionné WMI dans un article dédié à l'API Power Management ? Parce que WMI présente un énorme avantage: elle est unifiée. Quand on sait l'utiliser pour surveiller un élément du système, on sait faire de même pour n'importe quel élément. En outre les données observées avec elle permettent de vérifier qu'un programme écrit avec d'autres API se comporte correctement. Nous abordons maintenant l'API Power Management.

#include using namespace std; #include #include int main(int argc, char* argv[]) { SYSTEM_POWER_CAPABILITIES spc; NTSTATUS result; result = CallNtPowerInformation(SystemPowerCapabilities, NULL, 0, &spc, sizeof(SYSTEM_POWER_CAPABILITIES)); if(result) cout << "Buffer insuffisant ou droits insuffisants pour acceder aux informations" << endl; if(spc.SystemBatteriesPresent) cout << "Une batterie est presente sur le systeme" << endl; else cout << "Pas de batterie presente" << endl; if(spc.ProcessorThrottle) cout << "La cadence du processeur est ajustable" << endl; else { cout << "La cadence du processeur n'est pas ajustable" << endl; return 0; } cout << "Seuil d'ajustement mini: " << (int)spc.ProcessorMinThrottle << "%" << endl; cout << "Seuil d'ajustement maxi: " << (int)spc.ProcessorMaxThrottle << "%" << endl; return 0; }

Suite de l'article