Operating Systems Concepts                      Tutorial exercises

 

Exercise 2: Creating processes in Windows NT

 

This exercise is designed to get you started as a Windows NT systems programmer.  As usual, it’s not a bad idea at all to go through this with a friend, provided that when you have finished you have both spent some time at the keyboard exploring.  You shouldn’t be able to break anything; if you manage it, blame Microsoft and come and see me to collect a small prize.

 

  1. Find a PC running Windows NT, with Microsoft Visual C++.  Any of the Windows NT lab machines will do. 
  2. Use Microsoft Visual C++ to create a new project called “ProcessCreation”.  Create a new source file called “ProcessCreator.cpp” .  Enter the following source code:

 (you don’t need to type this in; cut and paste it from the web: http://www.doc.ic.ac.uk/~phjk/OperatingSystemsConcepts/Exercises/Software/ProcessCreator.cpp)

 

// ProcessCreationExample.cpp - a simple example of process creation

// Paul Kelly, Imperial College, October 2000

//

#include <iostream.h>

#include <windows.h>

 

// ProcessFactory: allows us to set up the configuration options for a process then create it

//

class ProcessFactory {

private:

                STARTUPINFO startInfo;                                   // configuration options for new process

                PROCESS_INFORMATION processInfo;                // information about the created process

public:   

                ProcessFactory();                                                               // Constructor

                ~ProcessFactory();                                             // Destructor

                void doCreateProcess(char *CommandLine);

                // could add methods here to set configuration options

};

ProcessFactory::ProcessFactory()                                               // Constructor

{

                ZeroMemory(&startInfo, sizeof(startInfo));                // set all config options to default

                startInfo.cb = sizeof(startInfo);                   // tell OS how big it is

}

ProcessFactory::~ProcessFactory()                           // Destructor

{

                CloseHandle(&processInfo.hThread);                // Release our references to

                CloseHandle(&processInfo.hProcess);                //                information about the process

}

 

void ProcessFactory::doCreateProcess(char *CommandLine)

{

                int ret = CreateProcess(NULL, CommandLine,

                                NULL, NULL, FALSE, NULL, NULL, NULL, &startInfo, &processInfo);

                if (!ret) {

                                cerr << "CreateProcess failed on error " << GetLastError() << " \n";

                                ExitProcess(1);

                }

}

(Continued on next page)

int main()

{

                int NChildProcesses;

                cerr << "How many processes should be created? ";

                cin >> NChildProcesses;

                cerr << "No of child processes: " << NChildProcesses << "\n";

 

                for (int i=0; i<NChildProcesses && i<100; ++i) {

                                ProcessFactory PF;

                                PF.doCreateProcess("c:\\WINNT\\SYSTEM32\\CALC.EXE");

                }

                return 0;

}

 

 

 

 

  1. Build and run the ProcessCreation project.  It should ask you how many processes to create.  Type some small number, such as 2, then press return.  The program should create a number of instances of the CALC program, wait a while, then finish.  When it has finished, you need to close the CALC applications yourself (click on the X at the top right of each calculator).
  2. Read the source code and figure out how it works.
  3. Modify it so that instead of the “CALC” program, it runs the heartbeat program from the previous exercise.

 

 

 


Background information – Processes, Handles

 

The Windows NT kernel is the heart of the operating system.  When asked to run an application program, it creates a “virtual machine” for the program to run in – including a “virtual memory” for its code and data.  It then creates a process to execute this code.  Each process starts with just one “thread” of control; later, other threads can be created so that different activities within the same application can be interleaved.  All the threads within a process share access to the same code and data.

 

Constructors and destructors; building a factory

 

“ProcessFactory” is an example of the “factory”design pattern (Gamma et al).  A factory is a class whose purpose is to construct an object.  Of course, in many cases you can do this with the normal C++ constructor.  A factory abstracts the object creation process so we can control it while retaining a simple object creation interface.  I used a factory here to encapsulate the two data structures associated with creating a process, and to provide a convenient way of setting the configuration options (“startInfo”) before calling the operating system’s “CreateProcess” function.

Setting up the configuration options for a new process

 

The CreateProcess() function is part of Microsoft’s Win32 Application Programming Interface (“API”).  It is documented at http://msdn.microsoft.com/library/psdk/winbase/prothred_9dpv.htm - which is part of the Microsoft Developer’s Network website, http://msdn.microsoft.com/.  The function prototype for CreateProcess() is:

 

BOOL CreateProcess(

  LPCTSTR lpApplicationName,               // name of executable module

  LPTSTR lpCommandLine,                      // command line string

  LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD

  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // SD

  BOOL bInheritHandles,                          // handle inheritance option

  DWORD dwCreationFlags,                    // creation flags

  LPVOID lpEnvironment,                        // new environment block

  LPCTSTR lpCurrentDirectory,               // current directory name

  LPSTARTUPINFO lpStartupInfo,          // startup information

  LPPROCESS_INFORMATION lpProcessInformation   // process information

);

 

The field types (LPCTSTR, LPSECURITY_ATTRIBUTES etc etc) are #defined in the header file <windows.h>.  In most cases they are simple C/C++ data types, but Microsoft may change their definition at some point, so use these macros.  For this exercise, we don’t need most of these options, so most of the parameters to CreateProcess() are NULL.    LpStartupInfo is a pointer to a further block of configuration options with the following structure (see MSDN again for more information):

 

typedef struct _STARTUPINFO {

    DWORD   cb;

    LPTSTR  lpReserved;

    LPTSTR  lpDesktop;

    LPTSTR  lpTitle;

    DWORD   dwX;                      // specify position of the new process’s window

    DWORD   dwY;                       //   (only used if dwFlags specifies STARTF_USESIZE)

    DWORD   dwXSize;                               // specify size of the new process’s window

    DWORD   dwYSize;                                //   (only used if dwFlags specifies STARTF_USESIZE)

    DWORD   dwXCountChars;                 // specify no of columns in console window

    DWORD   dwYCountChars;                 //   (only used if dwFlags specifies STARTF_USECOUNTCHARS)

    DWORD   dwFillAttribute;

    DWORD   dwFlags;                                // bit field determines which fields used when process creates window.

    WORD    wShowWindow;

    WORD    cbReserved2;

    LPBYTE  lpReserved2;

    HANDLE  hStdInput;                             // a handle for manipulating process’s standard input stream

    HANDLE  hStdOutput;                          // a handle for manipulating process’s standard output stream

    HANDLE  hStdError;                              // a handle for manipulating process’s error output stream

} STARTUPINFO, *LPSTARTUPINFO;

 

Again, we don’t need to set any of these parameters in this simple example, so we set them all to zero (the ZeroMemory() call above). 

What you get back from the operating system

 

The PROCESS_INFORMATION structure is filled in by the CreateProcess function with information about a newly created process and its primary thread.  Here is its structure (see MSDN again for more information):

 

typedef struct _PROCESS_INFORMATION {

    HANDLE hProcess;

    HANDLE hThread;

    DWORD dwProcessId;

    DWORD dwThreadId;

} PROCESS_INFORMATION;

 

The CreateProcess() function gets the operating system to create a process (consisting of a virtual machine, with a virtual address space preconstructed to contain the program code you specified, together with its initialized data, and it working memory space).  hProcess is a handle to the newly created process. The handle is used to specify the process in all functions that perform operations on the process object.   The operating system also creates a thread to run in that process.  hThread  is a handle to the primary thread of the newly created process. The handle is used to specify the thread in all functions that perform operations on the thread object.   dwProcessId is a global process identifier number that can be used to identify a process (for example using taskmgr). dwThreadId is a global thread identifier number that can be used to identify the thread.

Background reading, finding out more:

 

Gary Nutt, Operating Systems Projects using Windows NT (Addison Wesley1999)

Gives brief and very useful intro to NT’s internal structure

David A Solomon, Inside Windows NT (Second edition, Microsoft Press 1998)

                The most widely-recommended source for understanding how NT works

Microsoft, Windows NT Workstation Resource Kit (Microsoft Press, 1996)

        Far from brief but often useful to fill in gaps and provide details.

Erich Gamma, Richard Helm,Ralph Johnson, and John Vlissides,

Design Patterns: Elements of Reusable Object-Oriented Software.  Addison Wesley. October 1994.  The definitive (“gang of four”) text on design patterns.

Microsoft Developers’ Network (MSDN), URL: http://msdn.microsoft.com/