Chapter 3: Developing Phase: Process and Thread Management

This chapter discusses process and thread management in Microsoft® Interix and compares the implementation of these mechanisms in the UNIX and the Interix environment models. The chapter also provides workarounds for areas that are significantly different for both models. These areas include:

  • Process management:

    • Creating a new process

    • Replacing a process image

    • Maintaining process hierarchy

    • Waiting for a child process

    • Managing process resource limits

    • Supporting process groups

    • Managing and scheduling the processes

  • Thread management:

    • Creating new threads

    • Detaching a thread

    • Terminating a thread

    • Synchronizing threads

    • Associating thread attributes

    • Scheduling and prioritizing threads

After comparing these environments, you will be able to identify the core changes required in these areas during the migration of UNIX applications to the Interix environment. You will also learn about the equivalent Interix system calls for the UNIX system APIs.

*

On This Page

Process Management Process Management
Thread Management Thread Management

Process Management

The process models for the UNIX and Microsoft Windows® operating systems are very different. However, because these differences are hidden by the Interix subsystem, it is possible to migrate UNIX code to Interix with a few modifications in the process code.

The following sections discuss the similarities between UNIX and Interix process functions and highlight the modifications that must be made to the code for migration.

Creating a New Process

In a UNIX environment, you create a new process using the fork function. The fork function creates a child process that is almost an exact copy of the parent process, thereby ensuring that the process environment for the child is the same as that for the parent.

Interix supports the UNIX application programming interfaces (APIs) for process creation—fork(), and vfork(). A code that uses these calls does not require any modifications to compile under Interix.

Replacing a Process Image

A UNIX application replaces the executing image with that of another application using one of the exec functions.

Because Interix supports all six exec calls that are collectively known as exec(), execl(), execle(), execlp(), execv(), execve(), and execvp(), code that uses these calls does not need to be modified.

Interix supports the family of setuid and setgid APIs. The Interix setuser() API call is a faster and more secure replacement for setuid/setgid API calls. The exec*_asuser() APIs are deprecated and are currently wrappers to setuser().

Maintaining Process Hierarchy

In UNIX, processes have a parent-child relationship. This hierarchical arrangement is used to manage processes within applications. Interix maintains this process hierarchy and even tracks the parent-child relationship of Microsoft Win32® processes on the same system. The Interix ps –efi command displays processes and their relationship to each other. (The -i option is specific to Interix and shows the process hierarchy.) The init is the first process that runs when the Interix subsystem starts. It is similar to /etc/init on traditional UNIX systems, except that the Interix version does not use /etc/inittab because Interix runs only at level 2. The init process runs as process identifier 1 and always remains in the background when the system is running.

The following sample output of the ps –efi command shows Interix processes followed by Win32 processes:

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

UID PID PPID STIME TTY TIME CMD
...
joeuser 2049 1 Jun 4 n00 0:00.59 /bin/csh –l
joeuser 9545 2049 06:31:58 n00 0:00.01 ps –ef
...
+SYSTEM 8 0 Jun 4 S00 1:14.33 SystemProcess
+SYSTEM 152 8 Jun 4 S00 0:00.86 \SystemRoot\System32\
smss.exe
+SYSTEM 176 152 Jun 4 S00 3:53.04 C:\WINNT\system32\
csrss.exe C:
+SYSTEM 1040 152 Jun 4 S00 0:23.39 C:\WINNT\system32\
psxss.exe C:
+SYSTEM 196 152 Jun 4 S00 0:24.47 C:\WINNT\system32\
winlogon.exe
+SYSTEM 236 196 Jun 4 S00 0:48.41 C:\WINNT\system32\
lsass.exe
+SYSTEM 224 196 Jun 4 S00 0:47.00 C:\WINNT\system32\
services.ex

In some versions of UNIX, applications provide for automatic cleanup of child processes by calling signal as follows:

signal(SIGCHLD, SIG_IGN);

However, in the Portable Operating System Interface (POSIX) standard, the result of setting SIG_IGN as the signal handler for SIGCHLD is undefined. Therefore, you cannot use this method to provide for automatic cleanup of child processes in portable applications.

To clean up child processes in an Interix application, you need to define a signal handler for SIGCHLD and call waitpid() within that signal handler. The prototype of the waitpid function is describes as follows:

pid_t waitpid (pid_t wpid, int *status, int options)

The waitpid function suspends execution of its calling process until status information is available for any terminated child process. This function takes the following parameters:

  • Status. Status of the child process.

  • Pid. Specifies which child process to wait for.

  • Options. A bitwise OR of flags to control the behavior of waitpid. The possible values of flags are WNOHANG and WUNTRACED.

A detailed explanation for the preceding is available in the Help manual of Windows Services for UNIX 3.5.

To avoid blocking within the signal handler, call waitpid with the first and third arguments set   to -1 and WNOHANG, respectively. Because stopped child processes are not important in this case, add SA_NOCLDSTOP to sa_flags for the SIGCHLD signal.

Waiting for a Child Process

Interix supports most of the wait-for-process-termination calls, including wait() and waitpid(). However, Interix does not support calls in the style of Berkley Software Distribution (BSD). When BSD-style wait calls are used, modify the code to use the suggested equivalents in Interix, which are listed in Table 3.1.

Table 3.1. BSD-Style Wait Calls and Interix Equivalents

Function

Description    

Suggested Interix Replacement

pid_t wait3(int *status, int options, struct rusage *rusage)

Waits for process termination.

pid_t waitpid (pid_t wpid, int *status, int options)

pid_t wait3(int *status, int options, struct rusage *rusage)

Waits for process termination.

pid_t cpid = waitpid (-1, *nstatus, options)

getrusage (cpid, *r_usage)

waitpid (pid, *status, options)

pid_t wait4(int *status, int *statusp,int options, struct rusage *rusage)

Waits for process termination.

pid_t waitpid (pid_t wpid, int *status, int options)

pid_t wait4(int *status, int *statusp, int options, struct rusage *rusage)

Waits for process termination.

int getrusage (int pid, struct rusage *r_usage)

Functions supported by Interix are defined by the POSIX and the UNIX standards and are more portable than the forms that they replace.

Without requiring some additional steps in the ported application, combining waitpid() with getrusage() does not produce the same results as wait3() or wait4(). The idea is to capture getrusage(RUSAGE_CHILDREN, ...) information before and after the child process is terminated and to compute the difference between the data contained in the two structures. To achieve accuracy, a child process can use getrusage() and communicate the same to the parent process. This method is more appropriate when there are multiple child processes.

When a program forks and the child finishes before the parent, the kernel still keeps the child process information; then the child process is said to be in a zombie state. It remains in a zombie state until it is cleaned up by its parent. In this state, the only resource it holds is a proc structure, which retains its exit status and resource usage information. The parent retrieves this information by calling wait, which also frees the proc structure.

Managing Process Resource Limits

Interix supports the following three functions in UNIX:

  • getrlimit. Returns the process resource limits.

  • getrusage. Returns current usage.

  • setrlimit. Sets new limits.

In addition, Interix supports the common limit names in UNIX listed in Table 3.2.

Table 3.2. Process Resource Limit Names

Limit

Description

RLIMIT_CORE

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, no core files are created.

RLIMIT_CPU

Maximum CPU time (in seconds) that 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 data segment. If the data segment grows larger than this value, the functions brk, malloc, and sbrk fail.

RLIMIT_FSIZE

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 truncate call exceeds the limit, further attempts fail.

RLIMIT_NOFILE

Maximum value for a file descriptor, plus one. This limits the number of file descriptors a process can allocate. If more than RLIMIT_NOFILE files are allocated, functions allocating new file descriptors can fail and generate the error EMFILE.

RLIMIT_STACK

Maximum size (in bytes) of a process stack. The stack does not automatically grow past this limit. If a process tries to exceed the limit, the system generates the SIGSEGV error for the process.

RLIMIT_AS

Maximum size (in bytes) of total available memory for a process. If this limit is exceeded, the memory functions brk, malloc, mmap, and sbrk fail with errno set to ENOMEM, and automatic stack growth fails as described for RLIMIT_STACK.

The other resource limit names listed in Table 3.3 are sometimes used in UNIX code and are unavailable in Interix. For these names, you need to modify the code to use the replacements that are also suggested in Table 3.3.

Table 3.3. Process Resource Limit Names Not Available in Interix

Limit        

Description

Suggested Interix Replacement

RLIMIT_MEMLOCK.

Maximum locked-in-memory address space (in bytes).

Interix has no mechanism to determine or enforce limits on this resource.

RLIMIT_NPROC

Maximum number of processes.

sysconf(_SC_CHILD_MAX) is the only Interix equivalent that provides programmatic information on process limits, but this is not an exact equivalent.

RLIMIT_RSS

Maximum resident set size (in bytes) of address space in a process's address space (in bytes).

Interix has no mechanism to determine or enforce limits on this resource.

RLIMIT_VMEM

Maximum size (in bytes) of mapped address space in a process’s mapped address space (in bytes). If this limit is exceeded, the brk and mmap functions fail with errno set to ENOMEM. In addition, the automatic stack growth fails as described for RLIMIT_STACK.

Interix has no mechanism to determine or enforce limits on this resource.

Supporting Process Groups

Functions in this category provide support for the management of processes as a group. Because the functions in this group are not supported by Interix, code must be modified to use the recommended replacement functions listed in Table 3.4.

Table 3.4. Process Group Functions Not Supported by Interix

Function Name

Description

Suggested Interix Replacement

pid_t getpgid(0) 

Gets process group ID of the calling process.

pid_t getpgrp (void)

pid_t getpgid(pid_t pid) 

Gets process group ID for process PID.

No support or equivalent in Interix. It can be replaced with user-defined function getpgid. (See the description paragraph that follows the table.)

Setpgrp()

Sets process group ID of the calling process.

setpgid(0 , 0)

tcgetsid

Gets process group ID for session leader for the terminal indicated by the file descriptor.

struct utmpx *getutxid

(const struct utmpx *id)

As mentioned in the preceding table, Interix does not support the getpgid(pid) function, which returns the process group ID for a given process. You can obtain this information using /proc mechanism, which allows a program to retrieve a variety of information about any running process. An implementation of such a function is as follows:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
extern int errno;
pid_t getpgid(pid_t pid)
{
char procfile[25];
char stat_rec[40];
char inbuf[110];
char field1[10], field2[100];
FILE *in;
sprintf(procfile, "/proc/%d/stat", pid);
in = fopen(procfile, "r");
if (in == NULL)
{
errno = ESRCH; /* No such process */
return(-1);
    }
//Scan file for "pgid" entry
while(fgets(inbuf, sizeof(inbuf), in))
{
sscanf(inbuf, "%s\t%s\n", field1, field2);
if (strcmp(field1,"pgid") == 0)
    return( (pid_t) atoi(field2));
}
errno = ENOSYS; /* Function not implemented */
return(-1);
}

Managing and Scheduling the Processes

The getpriority(), setpriority(), and nice() functions provide support for the scheduling and priority management of processes. These functions operate on a nice value, which is an integer in the range -20 to +19 where a nice of -20 means that the process has the highest priority.

Interix maps nice values to Windows process scheduling priorities according to the following rules:

  • A nice value of 0 in Interix corresponds to the default Windows scheduling priority of 10.

  • Positive nice values in Interix are applied as a reduction in Windows scheduling priority. For example, assigning a nice value of +4 to a process in Interix would result in the process being given a Windows scheduling priority of 6.

  • Negative nice values in Interix are applied as an increase in Windows scheduling priority; a process nice value of -4 in Interix would cause the process to have a Windows scheduling priority of 14.

Regardless of the nice value, the lowest Windows priority applied by Interix to a process is 1, whereas the highest is 30. Microsoft recommends that no process be assigned a priority higher than 15—that is, a nice value of -5. The Interix subsystem itself runs at a Windows priority of 15. Setting a higher priority on any application yields unpredictable results. The preceding mapping of nice values with the Windows scheduling priority is again illustrated in Table 3.5.

Table 3.5. Nice Values and Windows Scheduling Priority Mapping

Nice Value

Windows Scheduling Priority

-20 <= n <= -1

10 – n or 10 + abs(n)

0

10

1 <= n <= 9

10 – n

10 <= n <= 20

1

Any Interix process can lower the Windows priority of any process owned by the same user (increase its nice value). The effective user of a process must have the SE_INC_BASE_PRIORITY_NAME Windows privilege to increase the Windows scheduling priority of any process owned by the same user (decrease its nice value). In addition, the effective user of a process must have the SE_TCB_NAME Windows privilege to affect any process owned by any other user.

Terminating the Processes

Interix supports most of the process-termination calls, including exit(), _exit(), and kill().

The exit() function terminates the process. Before terminating the process, it calls the functions registered with the atexit() function, flushes the stream buffers, closes streams, and unlinks the temporary files.

The _exit() function also terminates the process, but it only performs the kernel cleanup of the process. The difference between them is that exit() also performs the cleanup of the user-mode constructs, whereas _exit() performs only kernel level cleanup.

The kill() function is used to send a termination signal to the process. This can be used to terminate a process or a group of processes. In addition to these functions, Interix also supports the kill command.

Thread Management

The Interix subsystem supports much of the Pthread model and Pthread interfaces necessary for conformity to the IEEE Std1003.1-2001 standard as well as many of the Pthread, semaphore, mutex, and scheduling-specific APIs. The Interix environment also comes with updated libraries that support the thread-safe and new reentrant functions that this standard requires.

Although the Windows kernel provides much of the thread functionality and semantics required by Interix Pthreads, the function calls and syntax between the two threading models are very different.

Interix threads are implemented using the same mechanisms and functionality as Windows threads. This implies that Interix threads will share the same characteristics as Windows threads. These characteristics are listed as follows:

  • Scheduling Priorities. Each thread can be assigned a scheduling priority in the range 1 to 31. The range 1 to 15 is known as the dynamic range and the range 16 to 31 is known as the real-time range.

  • Scheduling Policies and Algorithms. The thread with the highest priority value is executed first.

  • System-Level Scoping. All threads in the system are scheduled relative to each other.

Compiling Pthread programs on Interix is similar to any other UNIX platform. You must use the    –D_REENTRANT option to ensure that all the Pthread definitions are visible at the preprocessing stage. You can use -lpthread as well, but that is unnecessary because all the Pthread support is incorporated into the default libraries (libc.a and libpsxdll.a).

Note The Interix version of Perl distributed with Windows Services for UNIX 3.5 was inadvertently built with an inappropriate implementation of Pthread support. For appropriate Pthread support, download a newer version of Perl from https://www.interopsystems.com/tools/warehouse.htm.

Creating New Threads

Interix supports the pthread_create function of UNIX to create a new thread. This function has the following three arguments:

  • A pointer to a data structure that describes the thread.

  • An argument specifying the attributes of the thread (usually set to NULL indicating the default settings).

  • The function that the thread will run.

The thread finishes execution with a pthread_exit, where it returns a string. The process can wait for the thread to complete using the pthread_join function. The simple UNIX example that follows creates a thread and waits for it to finish execution.

Code that uses these calls can be directly ported to Interix without any modification.

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
char message[] = "Hello World";
void *thread_function(void *arg) 
{
printf("thread_function started. Arg was %s\n", (char *)
arg);
sleep(3);
strcpy(message, "Bye!");
pthread_exit("See Ya");
}
int main()
{
int res;
pthread_t a_thread;
void *thread_result;
res = pthread_create(&a_thread, NULL, thread_function, 
            (void *)message);
if (res != 0) 
{
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
printf("Waiting for thread to finish...\n");
res = pthread_join(a_thread, &thread_result);
if (res != 0) 
{
perror("Thread join failed");
exit(EXIT_FAILURE);
}
printf("Thread joined, it returned %s\n", (char *)
thread_result);
printf("Message is now %s\n", message);
exit(EXIT_SUCCESS);
}

Detaching a Thread

Interix supports the pthread_detach function of UNIX. A code that uses these calls does not require any modifications to compile under Interix.

Terminating a Thread

Threads can terminate themselves by either returning from their thread start function or by explicitly calling the pthread_exit() routine. Usually, when a thread terminates, all other threads in the process continue to run. However, there is a special case— when the initial thread returns from the main() routine, the entire process terminates. To avoid this behavior, the initial thread can either avoid returning from the main() routine or call pthread_exit().

A thread can also be cancelled by any other thread (including itself) with the pthread_cancel() routine, as long as the caller knows the identity of the thread that is going to be cancelled. Each thread controls its “cancelability” state and type. The thread must cooperate and allow itself to be cancelled.

Interix supports the pthread_exit() and pthread_cancel() routines of UNIX. Code that uses these calls does not require any modifications to compile under Interix.

Synchronizing Threads

Programs that use threads usually need to share data between the threads and need to perform various actions in a particular and coherent order across multiple threads. These operations require a mechanism to synchronize the activity of the threads. This synchronization is used to avoid race conditions and to wait for or signal when resources are available.

The Pthread model uses objects called mutexes, condition variables, and semaphores as the interthread synchronization mechanism.

The Interix Pthread implementation is built on the thread functionality already provided by the Windows kernel. Table 3.6 lists the Windows resources that are used in Interix.

Table 3.6. Windows Resources Used in Interix

Pthread Object    

Windows Kernel Object

Thread

Kernel thread object

Mutex

Kernel mutex object

Condition variable

Kernel event object

Semaphore

Kernel semaphore object

Read-write lock (rwlock)

Kernel rtl_resource object

Synchronization Using Mutexes

A Pthread mutex is an object that can be created, initialized, locked, unlocked, or destroyed. Interix supports the following UNIX routines for mutexes:

  • pthread_mutex_init(). Use this function to initialize the mutex object.

  • pthread_mutex_lock(). Use this function to lock the mutex object.

  • pthread_mutex_trylock(). This function is identical to pthread_mutex_lock() except that if the mutex is already locked by the calling thread or any other thread, then this function returns immediately.

  • pthread_mutex_unlock(). Use this function to unlock the mutex object.

  • pthread_mutex_destroy(). Use this function to destroy the mutex object.

Code that uses these calls does not require any modifications to compile under Interix.

Synchronization Using Condition Variables

A condition variable is used to communicate information about the state of shared data. It provides a mechanism to signal and wait for specific events related to data in a critical section being protected by a mutex.

Interix supports the following UNIX functions for condition variables:

  • pthread_cond_signal. This function unblocks one or more threads that are blocked on the condition variable specified by condition, if any.

  • pthread_cond_broadcast. This function unblocks all threads that are blocked on the condition variable specified by the condition argument.

  • pthread_cond_wait. This function unlocks the mutex referenced by the mutex and blocks the calling thread on the condition variable.

  • pthread_cond_timedwait. This function unlocks the mutex and blocks the calling thread on condition or until the system clock reaches or passes the absolute time.

  • pthread_cond_destroy. This function destroys (uninitializes) the condition variable.

Code that uses these calls does not require any modifications to compile under Interix.

Synchronization Using Semaphores

Interix supports the following UNIX functions for semaphores:

  • sem_init. This function initializes the unnamed semaphore.

  • sem_destroy. This function destroys the unnamed semaphore.

  • sem_post. This function unlocks the semaphore.

  • sem_timedwait. This function locks a semaphore with expiration time.

  • sem_trywait. This function locks the semaphore only if the semaphore is not currently locked.

  • sem_wait. This function locks the semaphore object.

Code that uses these calls does not require any modifications to compile under Interix.

Associating Thread Attributes

A number of attributes are associated with threads in UNIX. Table 3.7 lists these threads along with their default values supported by Interix.

Table 3.7. Thread Attributes and Default Values

fThread Attribute

Default Value Supported by Interix

detachstate

PTHREAD_CREATE_JOINABLE, PTHREAD_CREATE_DETACHED.

inheritsched

PTHREAD_INHERIT_SCHED, PTHREAD_EXPLICIT_SCHED.

schedparam

No default value.

schedpolicy

Although POSIX_THREAD_PRIORITY_SCHEDULING is defined, Interix only supports the SCHED_OTHER scheduling policy. It does not support the SCHED_RR or SCHED_FIFO policies.

Scope

PTHREAD_SCOPE_SYSTEM, PTHREAD_SCOPE_PROCESS

The only scheduling contention scope that Interix provides is the PTHREAD_SCOPE_SYSTEM. This means that all threads on the system are scheduled with respect to each other, regardless of the process that owns the thread.

Stackaddr

No default value.

Stacksize

No default value.

Scheduling and Prioritizing Threads

Interix threads are implemented using the same mechanisms and functionality as Windows threads. By relying on the Windows thread and process implementation, the Interix Pthread implementation has the following characteristics:

  • Scheduling contention scope is system wide (PTHREAD_SCOPE_SYSTEM) because scheduling is per thread and not per process.

  • The existing functions nice(), getpriority(), and setpriority() continue to handle nice values. These nice values have a range -20 to 19 and map directly to the Windows scheduling priorities in the range 30 to 1 as described in Table 3.5: Nice Values and Windows Scheduling Priority Mapping. Therefore, higher positive nice values result in less favorable scheduling priorities. The Windows scheduling priority represented by the UNIX process nice value is used as the value in the Windows process absolute priority.

  • To access the Windows thread base-priority value, Interix uses the sched_priority member in the newly defined sched_param structure found in the file sched.h. The current Interix implementation maps the Pthread definition of priority to the Windows thread base-priority. Therefore, this member is only allowed the values in the range -2 to +2 when used with the following Pthread-specific functions:

    • pthread_getschedparam()

    • pthread_setschedparam()

    • pthread_attr_setschedparam()

    • pthread_attr_getschedparam()

  • There are other Pthread-specific functions that deal with thread priority values. Functions such as pthread_setschedprio(), sched_get_priority_max(), and sched_get_priority_min() accept a priority argument. This argument is limited to the range of values between PRIO_MIN and PRIO_MAX. You can use the following statements at the beginning of the code to define the values of PRIO_MIN and PRIO_MAX:

    #define PRIO_MIN -2
    

#define PRIO_MAX 2

  • The combination of the Interix priority value of the thread with the Interix process nice value is used as the Windows thread current scheduling priority, which determines the actual scheduling priority.

  • Changing the nice value of the process will also change the current scheduling priority in all the threads associated with that process.

Note The Interix implementation in Windows Services for UNIX 3.5 introduces an inconsistency because Pthread scheduling priority values are supposed to be used consistently for both threads and processes. The Windows scheduling algorithm uses more than five priority values. Programmers migrating Pthread applications must be wary of this in case the application makes assumptions between processes and thread priorities that may not be true in Interix.

Download

Get the UNIX Custom Application Migration Guide

Update Notifications

Sign up to learn about updates and new releases

Feedback

Send us your comments or suggestions