Home | Chi sono | Contattami

Progr. lineare

Miei articoli


Miei articoli 


Windows JOB Objects

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 application

Now 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 restrictions

I'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.


My 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




Your Ad Here