|
Nel seguito ho raccolto un pò di documentazione ed esempi relativi al concetto
di oggetto JOB. Gli oggetti JOB sono kernel objects che possono essere gestiti
(creati, modificati, eliminati, ...) tramite specifiche api win32. Un oggetto
JOB è fondamentalmente un gestore di processi: vado ad assegnare i processi
che mi interessano all'oggetto JOB in questione (tramite le opportune api Win32)
ed a quel punto tutti i processi assegnati a quel JOB avranno le caratteristiche
che gli sono state imposte dal JOB in questione. Un oggetto JOB consente
fondamentalmente di definire specifiche limitazioni: assegnando uno o più
processi ad un oggetto JOB (tramite le opportune api win32), tali processi
saranno sottoposti alle limitazioni imposte dall'oggetto JOB (limitazioni di
memoria, di utilizzo della CPU, di accesso a specifiche informazioni, etc...). Per tutto ciò che
concerne gli oggetti JOB (api win32 dedicate e documentazione in genere) la
fonte principale è sicuramente il Platform SDK.
Job Objects
Nel seguito ho inserito un articolo datato 2001 tratto dall'ormai defunto
sito "DelphiDevelopersNewletter". Allegati i sorgenti relativi. A seguire i
sorgenti di un esempio relativo ad un articolo di
www.thedelphimagazine.com
Taming Wild Processes with Windows 2000 Job Objects
Author: Jani Järvinen
When programming in Windows, your applications can easily hog all available
CPU resources and consume all available memory. This might be okay in some cases,
but if you're building server applications that process requests from many
clients, you want to make sure that your application doesn't eventually bring
the server down to its knees. If you're using Windows 2000 as your platform, you
can solve the problem with a few API calls. Jani Järvinen explains.
Many Delphi programmers have moved to Windows 2000. Although it provides many
exiting new features, Borland doesn't officially support Delphi 5 on this
platform.
Even so, aside from some minor problems with localized versions of the operating
system and glitches caused by certain display drivers, Delphi 5 appears to work
fine on Windows 2000. At the time of this writing, Delphi 6 hasn't yet been
officially released. But once it is, it will certainly support Windows 2000.
Since Delphi 5 (and even older versions) works with Windows 2000, you can start
using the new operating system features in your current development projects
even before the release of Delphi 6. In this article, I'll provide the necessary
code to use Windows 2000 job objects with Delphi 5. Even though I can't
guarantee that the sample code will compile with Delphi 6, it will likely do so
without any problems.
Introducing job objects
When an application is running on a Windows 2000 box, the application is free
to consume the resources it needs to perform its tasks. For example, to perform
heavy calculations, your application might spawn new threads to use the CPU more
efficiently. If your application needs to share information with other
applications, it might simply use the clipboard, and if it needs to modify
system settings, it can do so. Furthermore, if your application needs to
allocate memory, the operating system won't object (up to a point). On modern
systems, allocating 50MB or even 100MB of memory isn't a problem.
Applications can operate in a rather unrestricted environment in Windows 2000.
Certainly, the system administrator can specify what an ordinary user can do on
the system, but the administrator can't control applications individually. For
instance, the administrator can specify that a user (and applications that the
user runs) can't access certain folders or computers on the network, but the
memory a particular application consumes can't be limited.
This is where Windows 2000 job objects come into play. Job objects are new to
Windows 2000, and they can be used to restrict many parameters, such as memory
consumption, CPU time, clipboard access, number of processes, and more. Job
objects are Windows 2000 kernel objects, meaning that support for them is
implemented directly in the basic subsystems of the operating system.
The word "job" might sound like a mainframe-era batch processing application,
but job objects in Windows 2000 aren't historic relics—quite the opposite. Also,
don't confuse job objects with print jobs, which are an entirely different thing.
When you want to use job objects, you need to first create one. I'll soon
provide the details, but for now you should think of a job object as being
created just like any other system object, like a file, pipe, or thread. Once
you've created a job object, it's initially empty. To make it useful, you need
to tweak its settings.
The most important thing you need to do with a newly created job object is
associate one or more processes with it. By default, a process isn't associated
with any job object, but you can associate it with exactly one job object if you
wish. The association is permanent, so you can't break it once it's done.
After you've assigned one or more processes to a job object, you can start to
set limits for the object. For example, you could limit the amount of memory
that a single process assigned to the job object can consume. As an example,
this limit could be 5MB or 30MB of memory. Depending on the setting, you can
apply it to a single process or to the job object as a whole.
Preparing to consume
By now, you probably see many uses for job objects. For example, if you run
applications that respond to client requests, you might want to associate these
processes with a restricting job object. Or, if you feel that (Java!)
applications you must run are too eager to consume all available resources,
associate the appropriate processes with a job object. You can probably think of
many other uses as well.
To start using Windows 2000 job objects, you need to find converted versions of
the appropriate C language header files. To get these original header files and
associated API documentation, you need to download the latest Windows Platform
SDK package. It's available from the Microsoft Developer's Network (MSDN)
Downloads site at http://msdn.microsoft.com/downloads. The .H files you need to
look at are named WINBASE.H and WINNT.H.
Since translating C header files is somewhat time-consuming, I've done a simple
translation to an Object Pascal unit named JOBS.PAS. You can use this header
translation freely in your own projects, but presumably Delphi 6 will contain
all of the necessary translations out of the box. However, since Delphi 6 hasn't
been released yet, I can't say for sure.
In my opinion, the best way to learn new operating system features is to see
them in action. Since Windows 2000 job objects are about restricting
applications to behave correctly, I've written two very simple applications that
consume system resources.
These two applications are simply named CPUHog and MemoryHog. As you can guess
from the application names, these two applications efficiently consume CPU time
and global memory. These guns might not exactly be real-world applications, but
basically they behave just the same.
To see how these applications work, check out Listing 1 and Listing 2.
Before you study the code listings further, I should warn you that I've written
the tools with an eye toward simplicity, and thus you probably shouldn't copy
the code into your own applications as is. Usually, Delphi Developer shows you
how to write code, but in this case, perhaps I've shown you how not to.
Listing 1. Wasting CPU cycles in a While loop.
procedure TCPUHogForm.StartClick(Sender: TObject);
begin
Hogging := True;
Start.Enabled := False;
Stop.Enabled := True;
While Hogging do
Begin
Application.ProcessMessages;
End;
end;
procedure TCPUHogForm.StopClick(Sender: TObject);
begin
Hogging := False;
Start.Enabled := True;
Stop.Enabled := False;
end;
Listing 2. Allocating memory with GetMem on megabyte
granularity.
procedure TMemoryHogForm.AllocateClick(Sender: TObject);
Var
P : PChar;
begin
GetMem(P,StrToInt(Edit1.Text)*1024*1024);
{ just to store something there }
StrCopy(P,PChar(DateTimeToStr(Now)));
end;
The sample applicationNow that you've seen how the two hog applications work, it's time to show them
some discipline. This is where the Job Objects sample application comes into play. Remember that because job objects were introduced in Windows
2000, you'll need at least Windows 2000 to run the sample application presented
here.
When a process is assigned to a job object, Windows 2000 collects statistics
about the processes. And if a certain threshold (such as memory consumption) is
met, Windows 2000 will terminate the offending process. This might sound like a
rude process, but it works pretty well if applications were written to be aware
of such things. Optionally, Windows 2000 can just note the fact and continue
running the processes normally.
The sample application creates a kernel job object at startup (in main form
OnCreate), and then constantly monitors its statistics using a timer component.
You can choose whether to display the statistics in decimal or hexadecimal
numbers.
The sample application has three buttons: Run CPUHog, Run MemoryHog, and Set
Restrictions. The first two buttons allow you to execute either of the two hog
applications, and the third button is used to apply predefined job object
limitations. Next, I'm going to show you how all of this was done.
Letting Windows do the work As noted earlier, the sample application will create a kernel job object during
startup. The code in Listing 3 shows how to call the CreateJobObject Win32 API
function. The two parameters for this function, lpJobAttributes and lpName, can
both be set to nil to use default settings.
Listing 3. Creating a job object with a call to CreateJobObject.
procedure TJobMainForm.FormCreate(Sender: TObject);
begin
JobObj := CreateJobObject(nil,nil);
If (JobObj = 0) Then
ShowMessage(SysErrorMessage(GetLastError));
end;
...
Function CreateJobObject(
lpJobAttributes: PSecurityAttributes;
lpName : PAnsiChar
) : THandle; StdCall;
External Kernel32 Name 'CreateJobObjectA';
If the call to CreateJobObject succeeds, the return value is a handle to the
created object. If the call fails, the returned handle value is zero, and the
code shows an error message on-screen. Note how the code uses the
SysErrorMessage function declared in SYSUTILS.PAS to construct the error message.
After the job object has been created, the code that's executed next will be the
event handler for the TTimer component that handles the display of job object
statistics. The code executed is shown in Listing 4. The API function used to
query job object information is named QueryInformationJobObject. Note how the
SysUtils.Win32Check function is used to raise an exception if the API function
call fails. The two conversion functions, I2S32 and I2S64, are used to convert
the integer results to strings in base 10 or 16 numbers.
Listing 4. Calling QueryInformationJobObject to retrieve job object statistics.
procedure TJobMainForm.Timer1Timer(Sender: TObject);
Var
Info : TJobObjectBasicAndIOAccountingInformation;
Len : Cardinal;
begin
{ retrieve basic and IO information
about the job object }
Win32Check(QueryInformationJobObject(
JobObj,
JobObjectBasicAndIoAccountingInformation,
@Info,
SizeOf(Info),
@Len
)
);
{ show the results on screen }
With Info do
Begin
Edit1.Text := I2S64(BasicInfo.TotalUserTime);
Edit2.Text := I2S64(BasicInfo.TotalKernelTime);
Edit3.Text := I2S64(BasicInfo.ThisPeriodTotalUserTime);
Edit4.Text := I2S64(BasicInfo.ThisPeriodTotalKernelTime);
Edit5.Text := I2S32(BasicInfo.TotalPageFaultCount);
Edit6.Text := I2S32(BasicInfo.ActiveProcesses);
Edit7.Text := I2S32(BasicInfo.TotalTerminatedProcesses);
Edit8.Text := I2S64(IoInfo.ReadOperationCount);
Edit9.Text := I2S64(IoInfo.WriteOperationCount);
Edit10.Text := I2S64(IoInfo.ReadTransferCount);
Edit11.Text := I2S64(IoInfo.WriteTransferCount);
End;
end;
Since a job object doesn't do anything by itself, you need to associate processes with it. The two buttons I already mentioned can be used to run the two hog applications. The buttons work exactly the same except that they execute different applications. Listing 5 shows the code for the OnClick event handlers,
and Listing 6 shows the ExecuteProcess helper function.
Listing 5. Executing a process and optionally associating it with the job object.
procedure TJobMainForm.RunCPUHogClick( Sender: TObject );
Var
Proc: THandle;
begin
Proc := ExecuteProcess('cpuhog.exe');
If AssociateWithJob.Checked Then
Win32Check(AssignProcessToJobObject(JobObj,Proc));
CloseHandle(Proc);
end;
procedure TJobMainForm.RunMemoryHogClick( Sender: TObject );
Var
Proc: THandle;
begin
Proc := ExecuteProcess('memoryhog.exe');
If AssociateWithJob.Checked Then
Win32Check(AssignProcessToJobObject(JobObj,Proc));
CloseHandle(Proc);
end;
Listing 6. Executing a process using the CreateProcess API function.
Function TJobMainForm.ExecuteProcess(
EXE : String
) : THandle;
Var
SI : TStartupInfo;
PI : TProcessInformation;
Begin
Result := INVALID_HANDLE_VALUE;
FillChar(SI,SizeOf(SI),0);
SI.cb := SizeOf(SI);
If CreateProcess(
nil,
PChar('.\'+EXE),
nil,
nil,
False,
0,
nil,
nil,
SI,
PI
) Then
Begin
{ close thread handle }
CloseHandle(PI.hThread);
Result := PI.hProcess;
End
Else
ShowMessage('CreateProcess call failed: '+
SysErrorMessage(GetLastError));
End;
As you can see from these listings, the code associates the newly executed
process with the job object if the "Associate process with job object" check box
is checked on the main form. Note that since the ExecuteProcess function is
called without a path, you need to make sure that the files CPUHOG.EXE and
MEMORYHOG.EXE are in the same directory as the sample application JOBOBJECTS.EXE.
The API function AssignProcessToJobObject is used to associate a process with a
job.
By default, a job object doesn't set any restrictions to the processes assigned
to it. Instead, Windows 2000 just collects statistics about the processes,
letting you do accounting, reporting, and other related things. The real fun
begins when you set the restrictions. This is done with the
SetInformationJobObject API function.
Enforcing restrictionsI've written the example application so that it restricts the amount of CPU time
to 10 seconds per process, and 20 seconds in total for the job. Also, the number
of active (running) processes is limited to five, and all processes belonging to
the job can consume 20MB of memory each.
When you click the Set Restrictions button, the code shown in Listing 7 is
executed.
Listing 7. Applying restrictions with a call to SetInformationJobObject.
procedure TJobMainForm.SetRestrictionsClick(
Sender: TObject);
Const
MegaByte = 1024*1024;
Var
Limits : TJobObjectExtendedLimitInformation;
begin
{ fill structure }
FillChar(Limits,SizeOf(Limits),0);
With Limits,BasicLimitInformation do
Begin
PerProcessUserTimeLimit := SecondsTo100NSTicks(10);
PerJobUserTimeLimit := SecondsTo100NSTicks(20);
ActiveProcessLimit := 5;
ProcessMemoryLimit := 20*MegaByte;
{ set flags }
LimitFlags := JOB_OBJECT_LIMIT_PROCESS_TIME Or
JOB_OBJECT_LIMIT_JOB_TIME Or
JOB_OBJECT_LIMIT_ACTIVE_PROCESS Or
JOB_OBJECT_LIMIT_PROCESS_MEMORY;
End;
{ enforce limits }
Win32Check(SetInformationJobObject(
JobObj,
JobObjectExtendedLimitInformation,
@Limits,
SizeOf(Limits)
)
);
ShowMessage('Limits set!'#13#13+
'User time per process: 10 seconds'#13+
'User time per job: 20 seconds'#13+
'Active process limit: 5 processes'#13+
'Per process memory allocation limit: 20 MB');
SetRestrictions.Enabled := False;
end;
After you've enforced the limits, go ahead and execute, for instance, MemoryHog.
The application starts normally, and you can allocate 10MB of memory by clicking
the Allocate button. But try to click the button a second time. This time, the
allocation fails even though your system probably has lots of memory available (verify
this by using the Task Manager; activate it by pressing Ctrl-Shift-Esc anywhere).
If you've never seen such an error message (this message is rare nowadays), see
Figure 4.
Next, probe CPUHog. As I said, the default restrictions are 10 seconds of user
time per process and 20 seconds per job object. To test the first limit, click
the Run CPUHog button, and note the counter values on the screen. Then, click
the Start button on the CPUHog, and wait approximately 10-12 seconds. After the
time has elapsed, CPUHog should simply vanish from the screen. Again, job
objects are at work.
Up to now, you've tested per-process limits, but as you saw in Listing 7, I've
also declared the sample application to use per-job CPU time limits. To test
this limit, restart Job Objects, start one or two MemoryHogs, and then start
exactly two CPUHogs.
You don't need to touch the MemoryHog application(s), but instead click the
Start button on one of the CPUHogs. Wait the previously mentioned 10-12 seconds,
and that instance of CPUHog will be terminated. So far, everything is happening
just as it did previously.
Now, click the Start button on the remaining CPUHog. Wait again, and the CPUHog
disappears. But so have the instances of the MemoryHogs! This might not seem
fair, but this is how the job objects work by default. When a per-job CPU time
restriction is met, there's no point in running any processes anymore, since
every process will consume some amounts of CPU time.
ConclusionMy aim with this article was to show you how Windows 2000 job objects can be
used in your own applications. As you can see from the code listings, this isn't
too difficult. Actually, it's quite trivial when compared to many other
API-level techniques.
Since using job objects is so easy, I suggest that you take advantage of this
great feature in many situations where appropriate. For example, if you must run
applications that don't behave as nicely as you'd like them to, you could write
simple "starter" applications like the following:
C:\>limitmem 25000 "C:\Program Files\SomeApp\App.exe"
In addition to the CPU usage and memory limits presented in this article, job
objects can be used to limit many other aspects in running applications.
Describing them all would probably require two or three articles, and I've
decided to keep this article short. However, if you're interested in these
features, I suggest downloading the Platform SDK documentation. Of course, you
can also look at the documentation online at http://msdn.microsoft.com/library.
Here's one last tip for job objects: Use them in application testing. For
example, how many times have you really tested your application in situations
where the memory runs out? Until I found out about Windows 2000 job objects, my
story was, "Hmm, it's too difficult..."
Catch you next time!
Download sample JobObjects
Sidebar: Understanding CPU Consumption When you think about job objects, it would be really nice to be able to restrict
processes to use only 80 percent of the CPU. For instance, if you've been
developing Web applications on Windows 2000, you've no doubt seen the Web Site
Properties dialog box with the "Enable process throttling" check box and the
associated "Maximum CPU Use %" setting.
How can IIS (Internet Information Server) limit CPU usage percentage-wise even
though job objects don't allow that? IIS does use job objects, however, so this
kind of setting must be possible somehow.
To solve the puzzle, you need to understand CPU consumption on Windows 2000 (or
on any true multitasking operating system, for that matter). The system is
constantly running different applications (actually threads in those processes),
and is actively switching between them. So, on a single-CPU system, only one
application is running at a time. When the application is running, it uses the
CPU to its fullest potential, not 50 percent or 75 percent.
Because of multitasking, Windows 2000 will only run a single application for a
very brief moment, typically 20 milliseconds or so, and then switch to another
application. This gives the illusion of being able to run multiple applications
simultaneously. Given this situation, you must understand that CPU consumption
is a function of time.
Let's take a look at a concrete example: If an application is using the CPU for
5 seconds on a 10-second interval, it's using 50 percent of the CPU on average.
For a 24-hour period, this same 50 percent would be 12 hours, and so on.
So you can easily limit CPU usage percentage-wise. The trick is to only select
the appropriate time interval at the same time!
<<< Fine Articolo originale>>> A questo punto allego l'esempio ancora più dettagliato sui Job Objects fornito da www.thedelphimagazine.com JobObjects_TDM.7z
| |
| |
|