The UNIX and Windows process models are very different, and the major difference lies in the creation of processes. UNIX uses fork to create a new copy of a running process and exec to replace a process s executable file with a new one. Windows does not have a fork function. Instead, Windows creates processes in one step by using CreateProcess . While there is no need in Win32 to execute the process after its creation (as it will already be executing the new code), the standard exec functions are still available in Win32.
These differences (and others) result in the need to convert the UNIX code beforeit can run on a Win32 platform.
The areas that you need to consider that are covered in this section are:
Process creation
Replacing a process s executable code
Spawning child processes and the process hierarchy
Waiting for a child process
Setting process resource limits
Windows jobs are also introduced. Jobs allow you to group processes together for management purposes. This functionality is not available in UNIX.
Note | There are a number of process management functions in the Win32 API. For detailsof these functions, see the Win32 API reference on the Microsoft Developer Network (MSDN) Web site. |
In UNIX, a developer creates a new process by using fork . The fork function creates a child process that is an almost exact copy of the parent process. The fact that the child is a copy of the parent ensures that the process environment is the same forthe child as it is for the parent.
In Windows, the CreateProcess function enables the parent process to create an operating environment for a new process. The environment includes the working directory, window attributes, environment variables , execution priority, and command line arguments. A handle is returned by the CreateProcess function, which enables the parent application to perform operations on the process and its environment while it is executing. Unlike UNIX, the executable file run by CreateProcess is not a copy of the parent process, and it has to be explicitly specified in the call to CreateProcess .
An alternative to using CreateProcess is to use one of the spawn functions that is present in the standard C runtime. There are 16 variations of the spawn function. Each spawn function creates and executes a new process. Many of these have the same functionality as the similarly named exec functions. The spawn functions include an additional argument that permits the new process to replace the current process, suspend the current process until the spawned process terminates, run asynchronously with the calling process, or run simultaneously and detach as a background process.
For a UNIX application to change the executable file run in the child process, the child process must explicitly call an exec function to overwrite the executable file with a new application. The combination of fork and exec is similar to, but not the same as, CreateProcess .
The following example shows a UNIX application that forks to create a child process and then runs the UNIX ps command by using execlp .
#include <unistd.h> #include <stdio.h> #include <sys/types.h> int main() { pid_t pid; printf("Running ps with fork and execlp\n"); pid = fork(); switch(pid) { case -1: perror("fork failed"); exit(1); case 0: if (execlp("ps", NULL) < 0) { perror("execlp failed"); exit(1); } break; default: break; } printf("Done.\n"); exit(0); }
You can port this code to Windows by using the Win32 CreateProcess function discussed earlier, or by using a spawn function from the standard C runtime library. In both cases, the old and new processes run parallel, asynchronously.
#include <windows.h> #include <process.h> #include <stdio.h> void main() { STARTUPINFO si; PROCESS_INFORMATION pi; GetStartupInfo(&si); printf("Running Notepad with CreateProcess\n"); CreateProcess(NULL, "notepad", // Name of app to launch NULL, // Default process security attributes NULL, // Default thread security attributes FALSE, // Dont inherit handles from the parent 0, // Normal priority NULL, // Use the same environment as the parent NULL, // Launch in the current directory &si, // Startup Information &pi); // Process information stored upon return printf("Done.\n"); exit(0); }
The arguments supported by CreateProcess (shown in the preceding example) give you a considerable degree of control over the newly created process. This contrasts with the spawn functions, which do not provide options to set process priority, security attributes, or the debug status.
The next example shows a port of the same code using the _spawnlp function.
#include <windows.h> #include <process.h> #include <stdio.h> void main() { printf("Running Notepad with spawnlp\n"); _spawnlp(_P_NOWAIT, "notepad", "notepad", NULL); printf("Done.\n"); exit(0); }
Running either of the preceding examples yields a console window similar to that shown here:
A UNIX application replaces the executing image with that of another applicationby using one of the exec functions. As mentioned previously, a fork followed by an exec is similar to CreateProcess .
Windows supports the six POSIX variants of the exec function plus two additional ones ( execlpe and execvpe ). The function signatures are identical, and come as part of the standard C runtime. Porting UNIX code that uses exec to Win32 is easy to understand. The following is a simple UNIX example showing the use of the execlp function.
Note | For more information about exec support on Win32, see the standard C runtime library documentation that comes with the Microsoft Visual Studio development system. |
#include <unistd.h> #include <stdio.h> int main() { printf("Running ps with execlp\n"); execlp("ps", "ps", "-l", 0); printf("Done.\n"); exit(0); }
The preceding example compiles and runs on Windows with only minor modifi-cations. It does, however, require an executable file called ps.exe to be available(one is included with the Interix product).
The <unistd.h> include file is not a valid header file when using Windows. To use this example when using Windows, you need to change the header file to <process.h>. Doing so allows you to compile, link, and run this simple application.
In the preceding section, the example showed how you can create an asynchronous process where the parent and child processes execute simultaneously. No synchronization is performed. This section describes how to modify the previous exampleto include functionality that enables the parent process to wait for the child process to complete or terminate before continuing.
To accomplish this in UNIX, a developer would use one of the wait functions to suspend the parent process until the child process terminates. The same semantics are available when using Windows. The functions used are different, but the results are the same.
When you view the examples, keep in mind that this is not an exhaustive comparison between the two platforms. A very simple scenario is described, but if you needto expand the scenario to include waiting for multiple child processes, the spawn example does not map adequately as it does not include support for this functionality. In this case, you need to consider the CreateProcess approach and WaitForMultipleObjects .
To see the code for this example, see Appendix 9.7: Waiting for a Spawned Process.
In the next example, the UNIX code is forking a process, but not executing a separate runtime image. This creates a separate execution path within the application. When using Windows, this is achieved by using threads rather than processes. If your UNIX application creates separate threads of execution in this manner, you should use the Win32 API CreateThread .
The process of creating threads is covered in the next section, Threads.
#include <unistd.h> #include <stdio.h> #include <sys/types.h> int main() { pid_t pid; int n; printf("fork program started\n"); pid = fork(); switch(pid) { case -1: perror("fork failed"); exit(1); case 0: puts("Im the child"); break; default: puts("Im the parent"); break; } exit(0); }
Developers often want to create processes that run with a specific set of resource restrictions. In some cases, they may impose limitations for the purposes of stress testing or forced failure condition testing. In other cases, however, the limitations may be imposed to restrict runaway processes from using up all available memory, CPU cycles, or disk space.
In UNIX, the getrlimit function retrieves resource limits for a process, the getrusage function retrieves current usage, and setrlimit function sets new limits. The common limit names and their meanings are described in Table 9.1 on the next page.
Limit | Description |
---|---|
RLIMIT_CORE | The maximum size , in bytes, of a core file created by this process. If the core file is larger than RLIMIT_CORE, the write is terminated at this value. If the limit is set to 0, then no core files are created. |
RLIMIT_CPU | The maximum time, in seconds, of CPU time a process can use. If the process exceeds this time, the system generates SIGXCPU for the process. |
RLIMIT_DATA | Maximum size, in bytes, of a process s data segment. If the data segment exceeds this value, the functions brk , malloc , and sbrk will fail. |
RLIMIT_FSIZE | The maximum size, in bytes, of a file created by a process. If the limit is 0, the process cannot create a file. If a write or truncation call exceeds the limit, further attempts will fail. |
RLIMIT_NOFILE | The highest possible value for a file descriptor, plus one. This limits the number of file descriptors a process may allocate. If more than RLIMIT_NOFILE files are allocated, functions allocating new file descriptors may fail with the error EMFILE. |
RLIMIT_STACK | The maximum size, in bytes, of a process s stack. The stack won t automatically exceed this limit; if a process tries to exceed the limit, the system generates SIGSEGV for the process. |
RLIMIT_AS | Maximum size, in bytes, of a process s total available memory. If this limit is exceeded, the memory functions brk , malloc , mmap , and sbrk will fail with errno set to ENOMEM, and automatic stack growth will fail as described for RLIMIT_STACK. |
Windows uses job objects to set job limits (rather than process limits). Unlike in UNIX, Windows job objects do not have File input/output (I/O) source restrictions. If you require File I/O limits in your application, you need to create your own code to handle this.
Windows supports the concept of job objects, which allows you to group one or more processes into a single entity. Once a job object has been populated with the desired processes, the entire group can be manipulated for various purposes ranging from termination to imposing resource restrictions.
The restrictions that job objects allow you to enforce are described in Table 9.2.
Member | Description | Notes |
---|---|---|
PerProcessUser-TimeLimit | Specifies the maximum user -mode time allotted to each process (in 100 ns intervals). | The system automatically terminates any process that uses more than its allotted time. To set this limit, specify the JOB_OBJECT_LIMIT_PROCESS_TIME flag in the LimitFlags member. |
PerJobUser-TimeLimit | Specifies how much more user-mode time the processes in this job can use (in 100 ns intervals). | By default, the system automatically terminates all processes when this time limit is reached. You can change this value periodically as the job runs. To set this limit, specify the JOB_OBJECT_LIMIT_JOB_TIME flag in the LimitFlags member. |
LimitFlags | Specifies which restrictions to apply to the job. | See the job objects API reference for more information. |
MinimumWorkingSetSize/ | Specifies the minimum and maximum working set size for each process (not for all processes within the job). | Normally, a process s working set cangrow above its maximum; setting MaximumWorkingSetSize forces a hard limit. Once the process s working set reaches this limit, the process pages against itself. Calls to SetProcessWorkingSetSize by an individual process are ignored unless the process is just trying to empty its working set. To set this limit, specify the JOB_OBJECT_ LIMIT_WORKINGSET flag in the LimitFlags member. |
ActiveProcessLimit | Specifies the maximum number of processes that can run concurrently in the job. | Any attempt to go over this limit causes the new process to be terminated with a not enough quota error. To set this limit, specify the JOB_OBJECT_ LIMIT_ACTIVE_PROCESS flag in the LimitFlags member. |
Affinity | Specifies the subset of the CPU(s) that can run the processes. | Individual processes can limit this even further. To set this limit, specify the JOB_OBJECT_ LIMIT_AFFINITY flag in the LimitFlags member. |
PriorityClass | Specifies the priority class that all processes use. | If a process calls SetPriorityClass , the call will return successfully even though it actually fails. If the process calls GetPriorityClass , the function returns what the process has set the priority class to even though this might not be process s actual priority class. In addition, SetThreadPriority fails to raise threads above normal priority but can be used to lower a thread s priority. To set this limit, specify the JOB_OBJECT_LIMIT_PRIORITY_CLASS flag in the LimitFlags member. |
SchedulingClass | Specifies a relative time quantum difference assigned to threads in the job. | Value can be from 0 to 9 inclusive; 5 is the default. See the text after this table for more information. To set this limit, specify the JOB_OBJECT_LIMIT_SCHEDULING_CLASS flag in the LimitFlags member. |
As you may have observed by reviewing the table for setrlimit and job objects, the restrictions offered by job objects are comparable except in one major area: File I/O.
When a process is created in UNIX, the Process Control Block (PCB) in kernel space contains an array of limits that is initialized with default values. In the case of the RLIMIT_FSIZE limit, the write procedures in the kernel are aware of the limit structure in the PCB, and these functions make checks to enforce the limits. The Windows operating system does not implement similar limits on files. To solve this problem, you must write your own solution and build it into your application.
This section presents a solution that you could use in your application. This solution emulates the UNIX file resource limits with:
An array of limits held as a static variable.
This is similar to how some of the C runtime functions use static variables.
Versions of the UNIX functions getrlimit() and setrlimit() .
These functions manipulate the limit array.
Wrappers for each of the disk write functions.
These wrappers are resource limit aware.
This solution is implemented as three files. Two of the files, resource.h and resource.c, implement the getrlimit() , setrlimit() , rfwrite() , and _rwrite() functions. Only fwrite() and _ write() are wrapped since they are the most common disk write functions encountered in the UNIX world. The third file is rlimit.c, which is a very simple test program used to confirm that rfwrite() will fail when the limit was reached.
For more information, see Appendix 9.2: Limiting File I/O.
The Win32 API has several functions for gathering process accounting information:
GetProcessShutdownParameters
GetProcessTimes
GetProcessWorkingSetSize
SetPriorityClass
SetProcessShutdownParameters
SetProcessWorkingSetSize
Alternatively, a better method of obtaining process information is through the Windows Management Instrumentation (WMI) API.
For more information on WMI, see http://msdn.microsoft.com/downloads/default.asp?URL=/code/sample.asp?url=/msdn-files/027/001/566/msdncompositedoc.xml .