Thread Scheduling, Priorities and Affinities

Windows is called a preemptive multithreaded operating system because a thread can be stopped at any time and another thread can be scheduled

Suspending and Resuming a Thread

Creating a thread in the suspended state allows you to alter the thread’s environment before the thread has a chance to execute any code. Once you alter the thread’s environment, you must make the thread schedulable. You do this by calling ResumeThread and passing it the thread handle returned by the call to CreateThread (or the thread handle from the structure pointed to by the ppiProcInfo parameter passed to CreateProcess):

DWORD ResumeThread(HANDLE hThread);

A single thread can be suspended several times. If a thread is suspended three times, it must be resumed three times before it is eligible for assignment to a CPU. In addition to using the CREATE_ SUSPENDED flag when you create a thread, you can suspend a thread by calling SuspendThread:

DWORD SuspendThread(HANDLE hThread);

SuspendThread is asynchronous with respect to kernel-mode execution, but user-mode execution does not occur until the thread is resumed.

In real life, an application must be careful when it calls SuspendThread because you have no idea what the thread might be doing when you attempt to suspend it.

SuspendThread is safe only if you know exactly what the target thread is (or might be doing) and you take extreme measures to avoid problems or deadlocks caused by suspending the thread.

Note: The concept of suspending or resuming a process doesn’t exist for Windows because processes are never scheduled CPU time.

Sleeping

A thread can also tell the system that it does not want to be schedulable for a certain amount of time. This is accomplished by calling Sleep:

VOID Sleep(DWORD dwMilliseconds);

There are a few important things to notice about Sleep:

  • Calling Sleep allows the thread to voluntarily give up the remainder of its time slice.
  • The system makes the thread not schedulable for approximately the number of milliseconds specified. That’s right—if you tell the system you want to sleep for 100 milliseconds, you will sleep approximately that long, but possibly several seconds or minutes more.
  • You can call Sleep and pass INFINITE for the dwMilliseconds parameter. This tells the system to never schedule the thread.
  • You can pass 0 to Sleep. This tells the system that the calling thread relinquishes the remainder of its time slice, and it forces the system to schedule another thread.

Windows is not a real-time operating system. Your thread will probably wake up at the right time, but whether it does depends on what else is going on in the system.

Switching to Another Thread

The system offers a function called SwitchToThread that allows another schedulable thread to run if one exists:

BOOL SwitchToThread();

When you call this function, the system checks to see whether there is a thread that is being starved of CPU time. If no thread is starving, SwitchToThread returns immediately. If there is a starving thread, SwitchToThread schedules that thread (which might have a lower priority than the thread calling SwitchToThread). The starving thread is allowed to run for one time quantum and then the system scheduler operates as usual.

This function allows a thread that wants a resource to force a lower-priority thread that might currently own the resource to relinquish the resource. If no other thread can run when SwitchToThread is called, the function returns FALSE; otherwise, it returns a nonzero value.

A Thread’s Execution Times

Sometimes you want to time how long it takes a thread to perform a particular task. What many people do is write code similar to the following, taking advantage of the new GetTickCount64 function:

// Get the current time (start time).
ULONGLONG qwStartTime = GetTickCount64();

// Perform complex algorithm here.

// Subtract start time from current time to get duration.
ULONGLONG qwElapsedTime = GetTickCount64() - qwStartTime;

This code makes a simple assumption: it won’t be interrupted. However, in a preemptive operating system, you never know when your thread will be scheduled CPU time. When CPU time is taken away from your thread, it becomes more difficult to time how long it takes your thread to perform various tasks. What we need is a function that returns the amount of CPU time that the thread has received. Fortunately, prior to Windows Vista, the operating system offers a function called GetThreadTimes that returns this information:

BOOL GetThreadTimes(
   HANDLE hThread,
   PFILETIME pftCreationTime,
   PFILETIME pftExitTime,
   PFILETIME pftKernelTime,
   PFILETIME pftUserTime);

Using this function, you can determine the amount of time needed to execute a complex algorithm by using code such as the following.

__int64 FileTimeToQuadWord (PFILETIME pft) {
   return(Int64ShllMod32(pft->dwHighDateTime, 32) | pft->dwLowDateTime);
}

void PerformLongOperation () {

   FILETIME ftKernelTimeStart, ftKernelTimeEnd;
   FILETIME ftUserTimeStart,   ftUserTimeEnd;
   FILETIME ftDummy;
   __int64 qwKernelTimeElapsed, qwUserTimeElapsed,
      qwTotalTimeElapsed;

   // Get starting times.
   GetThreadTimes(GetCurrentThread(), &ftDummy, &ftDummy,
      &ftKernelTimeStart, &ftUserTimeStart);

   // Perform complex algorithm here.

   // Get ending times.
   GetThreadTimes(GetCurrentThread(), &ftDummy, &ftDummy,
      &ftKernelTimeEnd, &ftUserTimeEnd);

   // Get the elapsed kernel and user times by converting the start
   // and end times from FILETIMEs to quad words, and then subtract
   // the start times from the end times.
   qwKernelTimeElapsed = FileTimeToQuadWord(&ftKernelTimeEnd) -
      FileTimeToQuadWord(&ftKernelTimeStart);

   qwUserTimeElapsed = FileTimeToQuadWord(&ftUserTimeEnd) -
      FileTimeToQuadWord(&ftUserTimeStart);

   // Get total time duration by adding the kernel and user times.
   qwTotalTimeElapsed = qwKernelTimeElapsed + qwUserTimeElapsed;

   // The total elapsed time is in qwTotalTimeElapsed.
}

Thread Context

The CONTEXT structure allows the system to remember a thread’s state so that the thread can pick up where it left off the next time it has a CPU to run on.

Windows actually lets you look inside a thread’s kernel object and grab its current set of CPU registers. To do this, you simply call GetThreadContext:

BOOL GetThreadContext(
   HANDLE hThread,
   PCONTEXT pContext);

You should call SuspendThread before calling GetThreadContext; otherwise, the thread might be scheduled and the thread’s context might be different from what you get back.

It’s amazing how much power Windows offers the developer! But, if you think that’s cool, you’re gonna love this: Windows lets you change the members in the CONTEXT structure and then place the new register values back into the thread’s kernel object by calling SetThreadContext:

BOOL SetThreadContext(
   HANDLE hThread,
   CONST CONTEXT *pContext);

Again, the thread whose context you’re changing should be suspended first or the results will be unpredictable.

Before calling SetThreadContext, you must initialize the ContextFlags member of CONTEXT again, as shown here:

CONTEXT Context;

// Stop the thread from running.
SuspendThread(hThread);

// Get the thread's context registers.
Context.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(hThread, &Context);

// Make the instruction pointer point to the address of your choice.
// Here I've arbitrarily set the address instruction pointer to
// 0x00010000.
Context.Eip = 0x00010000;

// Set the thread's registers to reflect the changed values.
// It's not really necessary to reset the ContextFlags member
// because it was set earlier.
Context.ContextFlags = CONTEXT_CONTROL;
SetThreadContext(hThread, &Context);

// Resuming the thread will cause it to begin execution
// at address 0x00010000.
ResumeThread(hThread);

This will probably cause an access violation in the remote thread; the unhandled exception message box will be presented to the user, and the remote process will be terminated. That’s right—the remote process will be terminated, not your process. You will have successfully crashed another process while yours continues to execute just fine!

Thread Priorities

Every thread is assigned a priority number ranging from 0 (the lowest) to 31 (the highest). When the system decides which thread to assign to a CPU, it examines the priority 31 threads first and schedules them in a round-robin fashion. If a priority 31 thread is schedulable, it is assigned to a CPU. At the end of this thread’s time slice, the system checks to see whether there is another priority 31 thread that can run; if so, it allows that thread to be assigned to a CPU.

Starvation occurs when higher-priority threads use so much CPU time that they prevent lower-priority threads from executing.

Higher-priority threads always preempt lower-priority threads, regardless of what the lower-priority threads are executing.

when the system boots, it creates a special thread called the zero page thread. This thread is assigned priority 0 and is the only thread in the entire system that runs at priority 0. The zero page thread is responsible for zeroing any free pages of RAM in the system when there are no other threads that need to perform work.

Application developers never work with priority levels. Instead, the system maps the process’ priority class and a thread’s relative priority to a priority level. It is precisely this mapping that Microsoft does not want to commit to. In fact, this mapping has changed between versions of the system.

image

image

image

In general, a thread with a high priority level should not be schedulable most of the time. When the thread has something to do, it quickly gets CPU time. At this point, the thread should execute as few CPU instructions as possible and go back to sleep, waiting to be schedulable again. In contrast, a thread with a low priority level can remain schedulable and execute a lot of CPU instructions to do its work. If you follow these rules, the entire operating system will be responsive to its users.

To create a thread with an idle relative thread priority, you execute code similar to the following:

DWORD dwThreadID;
HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, NULL,
   CREATE_SUSPENDED, &dwThreadID);
SetThreadPriority(hThread, THREAD_PRIORITY_IDLE);
ResumeThread(hThread);
CloseHandle(hThread);

Dynamically Boosting Thread Priority Levels

The system determines the thread’s priority level by combining a thread’s relative priority with the priority class of the thread’s process. This is sometimes referred to as the thread’s base priority level. Occasionally, the system boosts the priority level of a thread—usually in response to some I/O event such as a window message or a disk read.

For example, a thread with a normal thread priority in a high-priority class process has a base priority level of 13. If the user presses a key, the system places a WM_KEYDOWN message in the thread’s queue. Because a message has appeared in the thread’s queue, the thread is schedulable. In addition, the keyboard device driver can tell the system to temporarily boost the thread’s level. So the thread might be boosted by 2 and have a current priority level of 15.

The thread is scheduled for one time slice at priority 15. Once that time slice expires, the system drops the thread’s priority by 1 to 14 for the next time slice. The thread’s third time slice is executed with a priority level of 13. Any additional time slices required by the thread are executed at priority level 13, the thread’s base priority level.

Another situation causes the system to dynamically boost a thread’s priority level. Imagine a priority 4 thread that is ready to run but cannot because a priority 8 thread is constantly schedulable. In this scenario, the priority 4 thread is being starved of CPU time. When the system detects that a thread has been starved of CPU time for about three to four seconds, it dynamically boosts the starving thread’s priority to 15 and allows that thread to run for twice its time quantum. When the double time quantum expires, the thread’s priority immediately returns to its base priority.

API Table

Function

Description

DWORD ResumeThread(HANDLE hThread);
Resumes the specified thread (i.e making it schedulable)
DWORD SuspendThread(HANDLE hThread);
Suspends the specified thread.
VOID Sleep(DWORD dwMilliseconds);
thread can also tell the system that it does not want to be schedulable for a certain amount of time
BOOL SwitchToThread(); allows another schedulable thread to run if one exists
BOOL GetThreadTimes(
   HANDLE hThread,
   PFILETIME pftCreationTime,
   PFILETIME pftExitTime,
   PFILETIME pftKernelTime,
   PFILETIME pftUserTime);
returns the amount of CPU time that the thread has received
BOOL GetThreadContext(
   HANDLE hThread,
   PCONTEXT pContext);
lets you look inside a thread’s kernel object and grab its current set of CPU registers
BOOL SetPriorityClass(
   HANDLE hProcess,
   DWORD fdwPriority);
once the child process is running, it can change its own priority class
DWORD GetPriorityClass(HANDLE hProcess);
Query the priority class of a certain process
BOOL SetThreadPriority(
   HANDLE hThread,
   int nPriority);
To set a thread’s relative priority, you must call these functions:
int GetThreadPriority(HANDLE hThread);
To get a thread’s relative priority, you must call these functions:
BOOL SetProcessPriorityBoost(
   HANDLE hProcess,
   BOOL bDisablePriorityBoost);
tells the system to enable or disable priority boosting for all threads within a process
BOOL GetProcessPriorityBoost(
   HANDLE hProcess,
   PBOOL pbDisablePriorityBoost);
determine whether process priority boosting is enabled or disabled:
BOOL SetThreadPriorityBoost(
   HANDLE hThread,
   BOOL bDisablePriorityBoost);
you enable or disable priority boosting for individual threads
BOOL GetThreadPriorityBoost(
   HANDLE hThread,
   PBOOL pbDisablePriorityBoost);
determine whether thread priority boosting is enabled or disabled:
BOOL SetProcessAffinityMask(
   HANDLE hProcess,
   DWORD_PTR dwProcessAffinityMask);
To limit threads in a single process to run on a subset of the available CPUs
BOOL GetProcessAffinityMask(
   HANDLE hProcess,
   PDWORD_PTR pdwProcessAffinityMask,
   PDWORD_PTR pdwSystemAffinityMask);
returns a process’ affinity mask
DWORD_PTR SetThreadAffinityMask(
   HANDLE hThread,
   DWORD_PTR dwThreadAffinityMask);
set affinity masks for individual threads
DWORD SetThreadIdealProcessor(
   HANDLE hThread,
   DWORD dwIdealProcessor);
It would be better if you could tell the system that you want a thread to run on a particular CPU but allow the thread to migrate to another CPU if one is available.Use SetThreadIdealProcessor to set an ideal CPU for a thread us

References

Windows® via C/C++, Fifth Edition

How process can skip from its JOB – A taste of dirty application

I am writing this post while I can’t believe if what I did is normal or not, I will mention what I`ve did then I will pose some questions that really need answer.

Job Object is a very powerful concept in Windows, and it us used to impose limitations on process assigned to it, some of this limitations enhance process security. For more information about jobs refer Jobs post.

An important information you should know about Jobs:

Once a process is part of a job, it cannot be moved to another job and it cannot become jobless (so to speak). Also note that when a process that is part of a job spawns another process, the new process is automatically made part of the parent’s job. However, you can alter this behavior in the following ways

Let’s consider this scenario:

you write a Host application that runs other applications and manages them, this applications may have unknown implementation (you are not its author), and you create these application as a child process (Client process) of your Host process. Imagine that some of these client process are infected by some virus which requires to cross the limitations you imposed by your Job (e.g spawn other children processes, access USER objects outside the process, etc..). To ensure that your client processes run in a safe environment you impose the necessary restrictions.

Imagine if one application (infected one) was written to skip from the Job assigned to it and run as a jobless process with no restrictions! HOW will you control such thing? This is horrible and this is what I could do! I though that doing such thing is not doable as it is very dangerous, but I could do it.

Dirty application code

#include <iostream>
#include <Windows.h>
#include <tchar.h>
#include <strsafe.h>
#include "XWinAssist.h"
using namespace std;

#define HostJobName _T("XHostJob")
#define AppName _T("cmd.exe")
#define AppCount 5

void CreateJoblessSelf()
{
    const int cchSize = 128;
    DWORD dwSize = cchSize;
    TCHAR szProcessName[cchSize];
    QueryFullProcessImageName(GetCurrentProcess(), 0, szProcessName, &dwSize);

    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi;

    // Here I create myself again in the same console window
    // I specify that I want to create myself and skip the current job
    BOOL fCreate = CreateProcess(NULL, szProcessName, NULL, NULL, FALSE, CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &si, &pi);

    if(!fCreate)
    {
        _tprintf_s(_T("Failed to create jobless self\n"));
        PrintLastError();
    }

    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
}

void StartSomeProcess()
{
    const int iSize = 128;
    TCHAR szCommandLine[iSize];
    _tcscpy_s(szCommandLine, iSize, AppName);
   
    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi;

    // Start some cmd.exe prcess, for sure this can be any process you imagine
    BOOL fCreate = CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

    if(!fCreate)
    {   
        _tprintf_s(_T("Failed to start some process\n"));
        PrintLastError();
    }

    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
}

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
    BOOL bInJob = FALSE;
    PROCESS_INFORMATION pi =  { 0 };

    // find if we are already assigned to a job
    IsProcessInJob(GetCurrentProcess(), NULL, &bInJob);

    // I will try to skip from the job already assigned to me
    if (bInJob)
    {
        _tprintf_s(_T("\nProcess already in a job\n"));
        _tprintf_s(_T("Spawining JOBLESS SELF …\n"));

        // instantiating myself but with no restrictions
        CreateJoblessSelf();

        _tprintf_s(_T("\nRestarting …\n"));
        return 0;
    }
    else
    {
        // Reaching this line means that I am totally free and have no job restrictions
        _tprintf_s(_T("\nMUHAHAHAHA … I am JOBLESS Process (Evil Laugh)\n"));
    }

    // Spawn some process to mobilize system resources
    // this could be some other infectious job
    for(int i = 0; i < AppCount; ++i)
    {
        StartSomeProcess();
    }

    return(0);
}

Environment:

  • Microsoft Win7 Professional
  • Visual Studio 2008 Team Suite

Questions and Exclamations

I have some questions in my mind I need answer for, I tried to search online but with no useful information.

  1. Is there any permission or access rights required to allow a process to spawn other process outside its job? I think there should be some.
  2. Can this behavior be really dangerous and be used to make havoc in the system?
  3. Is this considered as a security flaw?

Jobs

You often need to treat a group of processes as a single entity. For example, when you tell Microsoft Visual Studio to build a C++ project, it spawns Cl.exe, which might have to spawn additional processes (such as the individual passes of the compiler). But if the user wants to prematurely stop the build, Visual Studio must somehow be able to terminate Cl.exe and all its child processes. Solving this simple (and common) problem in Microsoft Windows has been notoriously difficult because Windows doesn’t maintain a parent/child relationship between processes. In particular, child processes continue to execute even after their parent process has been terminated.

When you design a server, you must also treat a set of processes as a single group. For instance, a client might request that a server execute an application (which might spawn children of its own) and return the results back to the client. Because many clients might connect to this server, it would be nice if the server could somehow restrict what a client can request to prevent any single client from monopolizing all of its resources. These restrictions might include maximum CPU time that can be allocated to the client’s request, minimum and maximum working set sizes, preventing the client’s application from shutting down the computer, and security restrictions.

Microsoft Windows offers a job kernel object that lets you group processes together and create a "sandbox" that restricts what the processes can do. It is best to think of a job object as a container of processes. However, it is also useful to create jobs that contain a single process because you can place restrictions on that process that you normally cannot.

If the process is already associated with a job, there is no way to move away from it: both for the current process or any other spawn process. This is a security feature to ensure that you can’t escape from the restrictions set for you.

By default, when you start an application through Windows Explorer, the process gets automatically associated to a dedicated job, whose name is prefixed by the "PCA" string.

Placing Restrictions on a Job’s Processes

After creating a job, you will typically want to set up the sandbox (set restrictions) on what processes within the job can do. You can place several different types of restrictions on a job:

  • The basic limit and extended basic limit prevent processes within a job from monopolizing the system’s resources.
  • Basic UI restrictions prevent processes within a job from altering the user interface.
  • Security limits prevent processes within a job from accessing secure resources (files, registry subkeys, and so on).

Once a process is part of a job, it cannot be moved to another job and it cannot become jobless (so to speak). Also note that when a process that is part of a job spawns another process, the new process is automatically made part of the parent’s job. However, you can alter this behavior in the following ways:

  • Turn on the JOB_OBJECT_LIMIT_BREAKAWAY_OK flag in JOBOBJECT_BASIC_LIMIT_INFORMATION‘s LimitFlags member to tell the system that a newly spawned process can execute outside the job. To make this happen, you must call CreateProcess with the new CREATE_BREAKAWAY_FROM_JOB flag. If you call CreateProcess with the CREATE_BREAKAWAY_FROM_JOB flag but the job does not have the JOB_OBJECT_LIMIT_BREAKAWAY_OK limit flag turned on, CreateProcess fails. This mechanism is useful if the newly spawned process also controls jobs.

  • Turn on the JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK flag in the JOBOBJECT_BASIC_LIMIT_INFORMATION‘s LimitFlags member. This flag also tells the system that newly spawned processes should not be part of the job. However, there is no need to pass any additional flags to CreateProcess. In fact, this flag forces new processes to not be part of the job. This flag is useful for processes that were originally designed knowing nothing about job objects.

Jobs Sample

My StartRestrictedProcess function places a process in a job that restricts the process’ ability to do certain things:

void StartRestrictedProcess() {
   // Check if we are not already associated with a job.
   // If this is the case, there is no way to switch to
   // another job.
   BOOL bInJob = FALSE;
   IsProcessInJob(GetCurrentProcess(), NULL, &bInJob);
   if (bInJob) {
      MessageBox(NULL, TEXT("Process already in a job"),
         TEXT(""), MB_ICONINFORMATION | MB_OK);
      return;
   }

// Create a job kernel object.
HANDLE hjob = CreateJobObject(NULL,
   TEXT("Wintellect_RestrictedProcessJob"));

// Place some restrictions on processes in the job.

// First, set some basic restrictions.
JOBOBJECT_BASIC_LIMIT_INFORMATION jobli = { 0 };

// The process always runs in the idle priority class.
jobli.PriorityClass = IDLE_PRIORITY_CLASS;

// The job cannot use more than 1 second of CPU time.
jobli.PerJobUserTimeLimit.QuadPart = 10000; // 1 sec in 100-ns intervals

// These are the only 2 restrictions I want placed on the job (process).
jobli.LimitFlags = JOB_OBJECT_LIMIT_PRIORITY_CLASS
   | JOB_OBJECT_LIMIT_JOB_TIME;
SetInformationJobObject(hjob, JobObjectBasicLimitInformation, &jobli,
   sizeof(jobli));

// Second, set some UI restrictions.
JOBOBJECT_BASIC_UI_RESTRICTIONS jobuir;
jobuir.UIRestrictionsClass = JOB_OBJECT_UILIMIT_NONE;     // A fancy zero

// The process can't log off the system.
jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS;

// The process can't access USER objects (such as other windows)
// in the system.
jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_HANDLES;

SetInformationJobObject(hjob, JobObjectBasicUIRestrictions, &jobuir,
   sizeof(jobuir));

// Spawn the process that is to be in the job.
// Note: You must first spawn the process and then place the process in
//      the job. This means that the process' thread must be initially
//      suspended so that it can't execute any code outside of the job's
//      restrictions.
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
TCHAR szCmdLine[8];
_tcscpy_s(szCmdLine, _countof(szCmdLine), TEXT("CMD"));
BOOL bResult =
   CreateProcess(
      NULL, szCmdLine, NULL, NULL, FALSE,
      CREATE_SUSPENDED | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
// Place the process in the job.
// Note: If this process spawns any children, the children are
//      automatically part of the same job.
AssignProcessToJobObject(hjob, pi.hProcess);

   // Now we can allow the child process' thread to execute code.
   ResumeThread(pi.hThread);
   CloseHandle(pi.hThread);

   // Wait for the process to terminate or
   // for all the job's allotted CPU time to be used.
   HANDLE h[2];
   h[0] = pi.hProcess;
   h[1] = hjob;
   DWORD dw = WaitForMultipleObjects(2, h, FALSE, INFINITE);
   switch (dw - WAIT_OBJECT_0) {
      case 0:
         // The process has terminated...
         break;
      case 1:
         // All of the job's allotted CPU time was used...
         break;
   }

   FILETIME CreationTime;
   FILETIME ExitTime;
   FILETIME KernelTime;
   FILETIME UserTime;
   TCHAR szInfo[MAX_PATH];
   GetProcessTimes(pi.hProcess, &CreationTime, &ExitTime,
      &KernelTime, &UserTime);
   StringCchPrintf(szInfo, _countof(szInfo), TEXT("Kernel = %u  |  User = %u\n"),
      KernelTime.dwLowDateTime / 10000, UserTime.dwLowDateTime / 10000);
   MessageBox(GetActiveWindow(), szInfo, TEXT("Restricted Process times"),
      MB_ICONINFORMATION | MB_OK);

   // Clean up properly.
   CloseHandle(pi.hProcess);
   CloseHandle(hjob);
}

 

Jobs API Table

Function

Description

BOOL IsProcessInJob
   HANDLE hProcess,
   HANDLE hJob,
   PBOOL pbInJob);

check whether or not the current process is running under the control of an existing job by passing NULL as the second parameter

HANDLE CreateJobObject(
   PSECURITY_ATTRIBUTES psa,
   PCTSTR pszName);

create a new job kernel object

HANDLE OpenJobObject(
   DWORD dwDesiredAccess,
   BOOL bInheritHandle,
   PCTSTR pszName);
 
BOOL SetInformationJobObject(
   HANDLE hJob,
   JOBOBJECTINFOCLASS JobObjectInformationClass,
   PVOID pJobObjectInformation,
   DWORD cbJobObjectInformationSize);

place restrictions on a job

BOOL UserHandleGrantAccess(
   HANDLE hUserObj,
   HANDLE hJob,
   BOOL bGrant);

Grants or denies access to a handle to a User object to a job that has a user-interface restriction. When access is granted, all processes associated with the job can subsequently recognize and use the handle. When access is denied, the processes can no longer use the handle

BOOL QueryInformationJobObject(
   HANDLE hJob,
   JOBOBJECTINFOCLASS JobObjectInformationClass,
   PVOID pvJobObjectInformation,
   DWORD cbJobObjectInformationSize,
   PDWORD pdwReturnSize);

once you have placed restrictions on a job, you might want to query those restrictions.

 

A process in a job can call QueryInformationJobObject to obtain information about the job to which it belongs by passing NULL for the job handle parameter. This can be very useful because it allows a process to see what restrictions have been placed on it. However, the SetInformationJobObject function fails if you pass NULL for the job handle parameter because this allows a process to remove restrictions placed on it.

clip_image001

BOOL AssignProcessToJobObject(
   HANDLE hJob,
   HANDLE hProcess);

This function tells the system to treat the process (identified by hProcess) as part of an existing job (identified by hJob). Note that this function allows only a process that is not assigned to any job to be assigned to a job, and you can check this by using the already presented IsProcessInJob function.

BOOL TerminateJobObject(
   HANDLE hJob,
   UINT uExitCode);

kill all the processes within a job

 

References

Windows® via C/C++, Fifth Edition

Processes Cont`d–Process Termination

A process can be terminated in four ways:

  1. The primary thread’s entry-point function returns. (This is highly recommended.)
  2. One thread in the process calls the ExitProcess function. (Avoid this method.)
  3. A thread in another process calls the TerminateProcess function. (Avoid this method.)
  4. All the threads in the process just die on their own. (This hardly ever happens.)

This section discusses all four methods and describes what actually happens when a process ends.

The Primary Thread’s Entry-Point Function Returns

You should always design an application so that its process terminates only when your primary thread’s entry-point function returns. This is the only way to guarantee that all your primary thread’s resources are cleaned up properly.

Having your primary thread’s entry-point function return ensures the following:

  • Any C++ objects created by this thread will be destroyed properly using their destructors.
  • The operating system will properly free the memory used by the thread’s stack.
  • The system will set the process’ exit code (maintained in the process kernel object) to your entry-point function’s return value.
  • The system will decrement the process kernel object’s usage count.

The ExitProcess Function

A process terminates when one of the threads in the process calls ExitProcess:

VOID ExitProcess(UINT fuExitCode);

This function terminates the process and sets the exit code of the process to fuExitCode. ExitProcess doesn’t return a value because the process has terminated. If you include any code following the call to ExitProcess, that code will never execute.

When your primary thread’s entry-point function (WinMain, wWinMain, main, or wmain) returns, it returns to the C/C++ run-time startup code, which properly cleans up all the C run-time resources used by the process. After the C run-time resources have been freed, the C run-time startup code explicitly calls ExitProcess, passing it the value returned from your entry-point function.

By simply allowing the primary thread’s entry-point function to return, the C/C++ run time can perform its cleanup and properly destruct all C++ objects. By the way, this discussion does not apply only to C++ objects. The C/C++ run time does many things on behalf of your process; it is best to allow the run time to clean it up properly.

Making explicit calls to ExitProcess and ExitThread is a common problem that causes an application to not clean itself up properly. In the case of ExitThread, the process continues to run but can leak memory or other resources.

The TerminateProcess Function

A call to TerminateProcess also ends a process:

BOOL TerminateProcess(
   HANDLE hProcess,
   UINT fuExitCode);

This function is different from ExitProcess in one major way: any thread can call TerminateProcess to terminate another process or its own process. The hProcess parameter identifies the handle of the process to be terminated. When the process terminates, its exit code becomes the value you passed as the fuExitCode parameter.

You should use TerminateProcess only if you can’t force a process to exit by using another method. The process being terminated is given absolutely no notification that it is dying—the application cannot clean up properly and cannot prevent itself from being killed (except by normal security mechanisms). For example, the process cannot flush any information it might have in memory out to disk.

A process will leak absolutely nothing once it has terminated. I hope that this is clear.

The TerminateProcess function is asynchronous. So you might want to call WaitForSingleObject.

When All the Threads in the Process Die

If all the threads in a process die (either because they’ve all called ExitThread or because they’ve been terminated with TerminateThread), the operating system assumes that there is no reason to keep the process’ address space around. This is a fair assumption because there are no more threads executing any code in the address space. When the system detects that no threads are running any more, it terminates the process. When this happens, the process’ exit code is set to the same exit code as the last thread that died.

When a Process Terminates

When a process terminates, the following actions are set in motion:

  1. Any remaining threads in the process are terminated.
  2. All the User and GDI objects allocated by the process are freed, and all the kernel objects are closed. (These kernel objects are destroyed if no other process has open handles to them. However, the kernel objects are not destroyed if other processes do have open handles to them.)
  3. The process’ exit code changes from STILL_ACTIVE to the code passed to ExitProcess or TerminateProcess.
  4. The process kernel object’s status becomes signaled. This is why other threads in the system can suspend themselves until the process is terminated.
  5. The process kernel object’s usage count is decremented by 1.

Note that a process’ kernel object always lives at least as long as the process itself. However, the process kernel object might live well beyond its process. When a process terminates, the system automatically decrements the usage count of its kernel object. If the count goes to 0, no other process has an open handle to the object and the object is destroyed when the process is destroyed.

Once again, let me remind you that you should tell the system when you are no longer interested in a process’ statistical data by calling CloseHandle. If the process has already terminated, CloseHandle will decrement the count on the kernel object and free it.

References

Windows® via C/C++, Fifth Edition

Processes Cont`d–Beauty of CreateProcess Function

The CreateProcess Function

You create a process with the CreateProcess function:

BOOL CreateProcess(
   PCTSTR pszApplicationName,
   PTSTR pszCommandLine,
   PSECURITY_ATTRIBUTES psaProcess,
   PSECURITY_ATTRIBUTES psaThread,
   BOOL bInheritHandles,
   DWORD fdwCreate,
   PVOID pvEnvironment,
   PCTSTR pszCurDir,
   PSTARTUPINFO psiStartInfo,
   PPROCESS_INFORMATION ppiProcInfo);

When a thread calls CreateProcess:

  1. The system creates a process kernel object with an initial usage count of 1. This process kernel object is not the process itself but a small data structure that the operating system uses to manage the process.
  2. The system then creates a virtual address space for the new process and loads the code and data for the executable file and any required DLLs into the process’ address space.
  3. The system then creates a thread kernel object (with a usage count of 1) for the new process’ primary thread. Like the process kernel object, the thread kernel object is a small data structure that the operating system uses to manage the thread.
  4. This primary thread begins by executing the application entry point set by the linker as the C/C++ run-time startup code, which eventually calls your WinMain, wWinMain, main, or wmain function.
  5. If the system successfully creates the new process and primary thread, CreateProcess returns TRUE.

pszApplicationName and pszCommandLine

The pszApplicationName and pszCommandLine parameters specify the name of the executable file the new process will use and the command-line string that will be passed to the new process, respectively.

Notice that the pszCommandLine parameter is prototyped as a PTSTR. This means that CreateProcess expects that you are passing the address of a non-constant string. Internally, CreateProcess actually does modify the command-line string that you pass to it. But before CreateProcess returns, it restores the string to its original form.

CreateProcess also searches for the executable in the following order:

  1. The directory containing the .exe file of the calling process
  2. The current directory of the calling process
  3. The Windows system directory—that is, the System32 subfolder as returned by GetSystemDirectory
  4. The Windows directory
  5. The directories listed in the PATH environment variable

All of this happens as long as the pszApplicationName parameter is NULL (which should be the case more than 99 percent of the time). Instead of passing NULL, you can pass the address to a string containing the name of the executable file you want to run in the pszApplicationName parameter. Note that you must specify the file’s extension; the system will not automatically assume that the filename has an .exe extension. CreateProcess assumes that the file is in the current directory unless a path precedes the filename. If the file can’t be found in the current directory, CreateProcess doesn’t look for the file in any other directory—it simply fails.

psaProcess, psaThread, and bInheritHandles

To create a new process, the system must create a process kernel object and a thread kernel object (for the process’ primary thread). Because these are kernel objects, the parent process gets the opportunity to associate security attributes with these two objects. You use the psaProcess and psaThread parameters to specify the desired security for the process object and the thread object, respectively. You can pass NULL for these parameters, in which case the system gives these objects default security descriptors. Or you can allocate and initialize two SECURITY_ATTRIBUTES structures to create and assign your own security privileges to the process and thread objects.

Another reason to use SECURITY_ATTRIBUTES structures for the psaProcess and psaThread parameters is if you want either of these two object handles to be inheritable by any child processes spawned in the future by this parent process.

fdwCreate

The fdwCreate parameter identifies flags that affect how the new process is created. You can specify multiple flags if you combine them with the bitwise OR operator.

The fdwCreate parameter also allows you to specify a priority class. However, you don’t have to do this, and for most applications you shouldn’t—the system will assign a default priority class to the new process.

pvEnvironment

The pvEnvironment parameter points to a block of memory that contains environment strings that the new process will use. Most of the time, NULL is passed for this parameter, causing the child process to inherit the set of environment strings that its parent is using.

pszCurDir

The pszCurDir parameter allows the parent process to set the child process’ current drive and directory. If this parameter is NULL, the new process’ working directory will be the same as that of the application spawning the new process. If this parameter is not NULL, pszCurDir must point to a zero-terminated string containing the desired working drive and directory. Notice that you must specify a drive letter in the path.

psiStartInfo

The psiStartInfo parameter points either to a STARTUPINFO or STARTUPINFOEX structure:

typedef struct _STARTUPINFO {
   DWORD cb;
   PSTR lpReserved;
   PSTR lpDesktop;
   PSTR lpTitle;
   DWORD dwX;
   DWORD dwY;
   DWORD dwXSize;
   DWORD dwYSize;
   DWORD dwXCountChars;
   DWORD dwYCountChars;
   DWORD dwFillAttribute;
   DWORD dwFlags;
   WORD wShowWindow;
   WORD cbReserved2;
   PBYTE lpReserved2;
   HANDLE hStdInput;
   HANDLE hStdOutput;
   HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;

typedef struct _STARTUPINFOEX {
    STARTUPINFO StartupInfo;
    struct _PROC_THREAD_ATTRIBUTE_LIST *lpAttributeList;
} STARTUPINFOEX, *LPSTARTUPINFOEX;

Windows uses the members of this structure when it creates the new process. Most applications will want the spawned application simply to use default values. At a minimum, you should initialize all the members in this structure to zero and then set the cb member to the size of the structure:

STARTUPINFO si = { sizeof(si) };
CreateProcess(..., &si, ...);

If you fail to zero the contents of the structure, the members will contain whatever garbage is on the calling thread’s stack.

Now, if you want to initialize some of the members of the structure, you simply do so before the call to CreateProcess.

This dwFlags member contains a set of flags that modify how the child process is to be created. Most of the flags simply tell CreateProcess whether other members of the STARTUPINFO structure contain useful information or whether some of the members should be ignored

ppiProcInfo

The ppiProcInfo parameter points to a PROCESS_INFORMATION structure that you must allocate; CreateProcess initializes the members of this structure before it returns. The structure appears as follows:

typedef struct _PROCESS_INFORMATION {
   HANDLE hProcess;
   HANDLE hThread;
   DWORD dwProcessId;
   DWORD dwThreadId;
} PROCESS_INFORMATION;

As already mentioned, creating a new process causes the system to create a process kernel object and a thread kernel object. At creation time, the system gives each object an initial usage count of 1. Then, just before CreateProcess returns, the function opens with full access to the process object and the thread object, and it places the process-relative handles for each in the hProcess and hThread members of the PROCESS_INFORMATION structure. When CreateProcess opens these objects internally, the usage count for each becomes 2.

This means that before the system can free the process object, the process must terminate (decrementing the usage count by 1) and the parent process must call CloseHandle (decrementing the usage count again by 1, making it 0). Similarly, to free the thread object, the thread must terminate and the parent process must close the handle to the thread object.

When a process kernel object is created, the system assigns the object a unique identifier; no other process kernel object in the system will have the same ID number. The same is true for thread kernel objects. When a thread kernel object is created, the object is assigned a unique, systemwide ID number. Process IDs and thread IDs share the same number pool. This means that it is impossible for a process and a thread to have the same ID. In addition, an object is never assigned an ID of 0. Notice that Windows Task Manager associates a process ID of 0 to the "System Idle Process" as shown next. However, there is really no such thing as the "System Idle Process." Task Manager creates this fictitious process as a placeholder for the Idle thread that runs when nothing else is running. The number of threads in the System Idle Process is always equal to the number of CPUs in the machine. As such, it always represents the percentage of CPU usage that is not being used by real processes.

You can discover the ID of the current process by using GetCurrentProcessId and the ID of the running thread by calling GetCurrentThreadId. You can also get the ID of a process given its handle by using GetProcessId and the ID of a thread given its handle by using GetThreadId. Last but not least, from a thread handle, you can determine the ID of its owning process by calling GetProcessIdOfThread.

References

Windows® via C/C++, Fifth Edition

Processes

What is a process?

A process is usually defined as an instance of a running program and consists of two components:

  • A kernel object that the operating system uses to manage the process. The kernel object is also where the system keeps statistical information about the process.
  • An address space that contains all the executable or dynamic-link library (DLL) module’s code and data. It also contains dynamic memory allocations such as thread stacks and heap allocations.

For a process to accomplish anything, it must have a thread that runs in its context; this thread is responsible for executing the code contained in the process’ address space. In fact, a single process might contain several threads, all of them executing code "simultaneously" in the process’ address space. To do this, each thread has its own set of CPU registers and its own stack. Each process has at least one thread that executes code in the process’ address space. When a process is created, the system automatically creates its first thread, called the primary thread. This thread can then create additional threads, and these can in turn create even more threads. If there were no threads executing code in the process’ address space, there would be no reason for the process to continue to exist, and the system would automatically destroy the process and its address space.

For all of these threads to run, the operating system schedules some CPU time for each thread. It creates the illusion that all the threads run concurrently by offering time slices (called quantums) to the threads in a round-robin fashion.

Your First Windows Application

Windows supports two types of applications: those based on a graphical user interface (GUI) and those based on a console user interface (CUI). A GUI-based application has a graphical front end.

When you use Microsoft Visual Studio to create an application project, the integrated environment sets up various linker switches so that the linker embeds the proper type of subsystem in the resulting executable. This linker switch is /SUBSYSTEM:CONSOLE for CUI applications and /SUB-SYSTEM:WINDOWS for GUI applications. When the user runs an application, the operating system’s loader looks inside the executable image’s header and grabs this subsystem value

Your Windows application must have an entry-point function that is called when the application starts running. As a C/C++ developer, there are two possible entry-point functions you can use:

int WINAPI _tWinMain(
   HINSTANCE hInstanceExe,
   HINSTANCE,
   PTSTR pszCmdLine,
   int nCmdShow);

int _tmain(
   int argc,
   TCHAR *argv[],
   TCHAR *envp[]);

     

Notice that the exact symbol depends on whether you are using Unicode strings or not. The table below tells you which entry point to implement in your source code and when.

image

The linker is responsible for choosing the proper C/C++ run-time startup function when it links your executable.

All of the C/C++ run-time startup functions do basically the same thing. The difference is in whether they process ANSI or Unicode strings and which entry-point function they call after they initialize the C run-time library. Here’s a summary of what the startup functions found in the crtexe.c file do:

  1. Retrieve a pointer to the new process’ full command line.
  2. Retrieve a pointer to the new process’ environment variables.
  3. Initialize the C/C++ run time’s global variables. Your code can access these variables if you include StdLib.h.
  4. Initialize the heap used by the C run-time memory allocation functions (malloc and calloc) and other low-level input/output routines.
  5. Call constructors for all global and static C++ class objects.

After all of this initialization, the C/C++ startup function calls your application’s entry-point function. If you wrote a _tWinMain function with _UNICODE defined, it is called as follows:

GetStartupInfo(&StartupInfo);
int nMainRetVal = wWinMain((HINSTANCE)&__ImageBase, NULL, pszCommandLineUnicode,
   (StartupInfo.dwFlags & STARTF_USESHOWWINDOW)
      ? StartupInfo.wShowWindow : SW_SHOWDEFAULT);

And it is called as follows without _UNICODE defined:

GetStartupInfo(&StartupInfo);
int nMainRetVal = WinMain((HINSTANCE)&__ImageBase, NULL, pszCommandLineAnsi,
   (StartupInfo.dwFlags & STARTF_USESHOWWINDOW)
      ? StartupInfo.wShowWindow : SW_SHOWDEFAULT);

Notice that _ImageBase is a linker defined pseudo-variable that shows where the executable file is mapped into the application memory.

When your entry-point function returns, the startup function calls the C run-time exit function, passing it your return value (nMainRetVal). The exit function does the following:

  1. It calls any functions registered by calls to the _onexit function.
  2. It calls destructors for all global and static C++ class objects.
  3. In DEBUG builds, leaks in the C/C++ run-time memory management are listed by a call to the _CrtDumpMemoryLeaks function if the _CRTDBG_LEAK_CHECK_DF flag has been set.
  4. It calls the operating system’s ExitProcess function, passing it nMainRetVal. This causes the operating system to kill your process and set its exit code.

If you wrote a _tmain function, it is called as follows when _UNICODE is defined:

int nMainRetVal = wmain(argc, argv, envp);

And it is called as follows when _UNICODE is not defined:

int nMainRetVal = main(argc, argv, envp);

Process Entry-point Parameters

Process Handle

Every executable or DLL file loaded into a process’ address space is assigned a unique instance handle. Your executable file’s instance is passed as (w)WinMain‘s first parameter, hInstanceExe. The handle’s value is typically needed for calls that load resources.

The actual value of (w)WinMain‘s hInstanceExe parameter is the base memory address where the system loaded the executable file’s image into the process’ address space. For example, if the system opens the executable file and loads its contents at address 0×00400000, (w)WinMain‘s hInstanceExe parameter has a value of 0×00400000.

The GetModuleHandle function, shown next, returns the handle/base address where an executable or DLL file is loaded in the process’ address space:

HMODULE GetModuleHandle(PCTSTR pszModule);

A Process’ Previous Instance Handle

As noted earlier, the C/C++ run-time startup code always passes NULL to (w)WinMain‘s hPrevInstance parameter. This parameter was used in 16-bit Windows and remains a parameter to (w)WinMain solely to ease porting of 16-bit Windows applications. You should never reference this parameter inside your code.

A Process’ Command Line

When a new process is created, it is passed a command line. The command line is almost never blank; at the very least, the name of the executable file used to create the new process is the first token on the command line.

Following the example of the C run time, you can also obtain a pointer to your process’ complete command line by calling the GetCommandLine function:

PTSTR GetCommandLine();

This function returns a pointer to a buffer containing the full command line, including the full pathname of the executed file. Be aware that GetCommandLine always returns the address of the same buffer. This is another reason why you should not write into pszCmdLine: it points to the same buffer, and after you modify it, there is no way for you to know what the original command line was.

The following function declared in ShellAPI.h and exported by Shell32.dll, CommandLineToArgvW, separates any Unicode string into its separate tokens:

PWSTR* CommandLineToArgvW(
   PWSTR pszCmdLine,
   int* pNumArgs);

As the W at the end of the function name implies, this function exists in a Unicode version only

A Process’ Environment Variables

Every process has an environment block associated with it. An environment block is a block of memory allocated within the process’ address space that contains a set of strings with the following appearance, where ‘’ means null character:

=::=::\ ...
VarName1=VarValue1
VarName2=VarValue2
VarName3=VarValue3 ...
VarNameX=VarValueX

The first part of each string is the name of an environment variable. This is followed by an equal sign, which is followed by the value you want to assign to the variable.

When a user logs on to Windows, the system creates the shell process and associates a set of environment strings with it. The system obtains the initial set of environment strings by examining two keys in the registry.

The first key contains the list of all environment variables that apply to the system:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\
   Session Manager\Environment

The second key contains the list of all environment variables that apply to the user currently logged on:

HKEY_CURRENT_USER\Environment

Normally, a child process inherits a set of environment variables that are the same as those of its parent process. However, the parent process can control what environment variables a child inherits, as you’ll see later when we discuss the CreateProcess function. By inherit, I mean that the child process gets its own copy of the parent’s environment block; the child and parent do not share the same block. This means that a child process can add, delete, or modify a variable in its block and the change will not be reflected in the parent’s block.

If you still want to use environment variables, there are a few functions that your applications can call. The GetEnvironmentVariable function allows you to determine the existence and value of an environment variable:

DWORD GetEnvironmentVariable(
   PCTSTR pszName,
   PTSTR pszValue,
   DWORD cchValue);

Many strings contain replaceable strings within them. For example, you can this string somewhere in the registry:

%USERPROFILE%\Documents

The portion that appears in between percent signs (%) indicates a replaceable string. In this case, the value of the environment variable, USERPROFILE, should be placed in the string. On my machine, the value of my USERPROFILE environment variable is

C:\Users\jrichter

So, after performing the string replacement, the resulting string becomes

C:\Users\jrichter\Documents

Because this type of string replacement is common, Windows offers the ExpandEnvironmentStrings function:

DWORD ExpandEnvironmentStrings(
   PTCSTR pszSrc,
   PTSTR pszDst,
   DWORD chSize);

Finally, you can use the SetEnvironmentVariable function to add a variable, delete a variable, or modify a variable’s value:

BOOL SetEnvironmentVariable(
   PCTSTR pszName,
   PCTSTR pszValue);

You should always use these functions for manipulating your process’ environment block.

Process Related

A Process’ Error Mode

Associated with each process is a set of flags that tells the system how the process should respond to serious errors, which include disk media failures, unhandled exceptions, file-find failures, and data misalignment. A process can tell the system how to handle each of these errors by calling the SetErrorMode function:

UINT SetErrorMode(UINT fuErrorMode);

The fuErrorMode parameter is a combination of any of the flags shown in the below table bitwise ORed together.

image

A Process’ Current Drive and Directory

When full pathnames are not supplied, the various Windows functions look for files and directories in the current directory of the current drive. For example, if a thread in a process calls CreateFile to open a file (without specifying a full pathname), the system looks for the file in the current drive and directory.

The system keeps track of a process’ current drive and directory internally. Because this information is maintained on a per-process basis, a thread in the process that changes the current drive or directory changes this information for all the threads in the process.

A thread can obtain and set its process’ current drive and directory by calling the following two functions:

DWORD GetCurrentDirectory(
   DWORD cchCurDir,
   PTSTR pszCurDir);
BOOL SetCurrentDirectory(PCTSTR pszCurDir);

If the buffer you provide is not large enough, GetCurrentDirectory returns the number of characters required to store this folder, including the final ” character, and copies nothing into the provided buffer, which can be set to NULL in that case. When the call is successful, the length of the string in characters is returned, without counting the terminating ” character.

A Process’ Current Directories

The system keeps track of the process’ current drive and directory, but it does not keep track of the current directory for every drive. However, there is some operating system support for handling current directories for multiple drives. This support is offered via the process’ environment strings. For example, a process can have two environment variables, as shown here:

=C:=C:\Utility\Bin
=D:=D:\Program Files

These variables indicate that the process’ current directory for drive C is \Utility\Bin and that its current directory for drive D is \Program Files.

If you call a function, passing a drive-qualified name indicating a drive that is not the current drive, the system looks in the process’ environment block for the variable associated with the specified drive letter. If the variable for the drive exists, the system uses the variable’s value as the current directory. If the variable does not exist, the system assumes that the current directory for the specified drive is its root directory.

For example, if your process’ current directory is C:\Utility\Bin and you call CreateFile to open D:ReadMe.Txt, the system looks up the environment variable =D:. Because the =D: variable exists, the system attempts to open the ReadMe.Txt file from the D:\Program Files directory. If the =D: variable did not exist, the system would attempt to open the ReadMe.Txt file from the root directory of drive D. The Windows file functions never add or change a drive-letter environment variable—they only read the variables.

If a parent process creates an environment block that it wants to pass to a child process, the child’s environment block does not automatically inherit the parent process’ current directories. Instead, the child process’ current directories default to the root directory of every drive. If you want the child process to inherit the parent’s current directories, the parent process must create these drive-letter environment variables and add them to the environment block before spawning the child process. The parent process can obtain its current directories by calling GetFullPathName:

DWORD GetFullPathName(
   PCTSTR pszFile,
   DWORD cchPath,
   PTSTR pszPath,
   PTSTR *ppszFilePart);

For example, to get the current directory for drive C, you call GetFullPathName as follows:

TCHAR szCurDir[MAX_PATH];
DWORD cchLength = GetFullPathName(TEXT("C:"), MAX_PATH, szCurDir, NULL);

As a result, the drive-letter environment variables usually must be placed at the beginning of the environment block.

The System Version

Frequently, an application needs to determine which version of Windows the user is running.

BOOL GetVersionEx(POSVERSIONINFOEX pVersionInformation);

This function requires you to allocate an OSVERSIONINFOEX structure in your application and pass the structure’s address to GetVersionEx. The OSVERSIONINFOEX structure is shown here:

typedef struct {
   DWORD dwOSVersionInfoSize;
   DWORD dwMajorVersion;
   DWORD dwMinorVersion;
   DWORD dwBuildNumber;
   DWORD dwPlatformId;
   TCHAR szCSDVersion[128];
   WORD  wServicePackMajor;
   WORD  wServicePackMinor;
   WORD  wSuiteMask;
   BYTE  wProductType;
   BYTE  wReserved;
} OSVERSIONINFOEX, *POSVERSIONINFOEX;

The OSVERSIONINFOEX structure has been available since Windows 2000. Other versions of Windows use the older OSVERSIONINFO structure, which does not have the service pack, suite mask, product type, and reserved members.

References

Windows® via C/C++, Fifth Edition

Operating Systems Overview

I dealt with computers first when I was in the elementary school around 1998, where I used to play Sky Roads on DOS computers. 2000 was the year I convinced my father to buy a computer at home, Some guy came at home and installed Windows so that we can deal with the computer, This Windows was like a mysterious thing or like a ghost that controls my PC, Actually I didn’t pay attention to know what Windows is all about rather than it is a kind of “Operating System” ! but as an end-user I was very good at using it.

Now as a computer science student, I figured out what Operating System – abbreviated OS – is all about, this article intend to give a brief overview to Operating Systems.


What is OS?

  • A program that controls the execution of application programs, and act as an interface between the application and the computer hardware.
  • It has 3 objectives:
    • Convenience; makes computer more convenient to use.
    • Efficiency; allows us to use computer resources in an efficient manner.
    • Ability to evolve; allow for further development to allow to system functions.

OS as a user/computer interface

  • OS provides a variety of services in the following areas:
    1. Program development: editors, debuggers they are tools supplied with the OS.
    2. Program execution: automate a number of steps to execute a program.
    3. Access to I/O devices: act as a façade to I/O devices.
    4. Controlled access to files: provide protection mechanism to control access to files.
    5. System access: protect system data and resources from unauthorized users.
    6. Error detection and response: detect program and hardware errors so as to clear the error condition with the least impact on the running applications.
    7. Accounting: monitoring system resources and collect usage statistics, help in judging whether to upgrade the resources or it is efficient enough.

OS as a Resource Manager

  • Memory allocation is controlled by the OS and the MMU (Memory Management Unit).
  • The OS decides when I/O device can be used by a program in execution.
  • Control access to and use of files.
  • The processor operation itself is controlled by the OS, that OS decides how much time the processor can spend on a particular program.

What makes OS evolve ?

  1. Hardware upgrades and new types of hardware: New types of hardware require that the OS be able to deal with it, so OS should be built with support for that hardware.
  2. New services: OS offers new services demanded by the users or system managers.
  3. Bug Fixes: Bugs appear over time, and detected by users, so OS should be fixed for this bugs, and sometimes fixing a bug raise another bug.

OS Evolution

In the dark ages, when there was no OS from late 1940s to mid-1950s (I call this years: before OS, abbreviated BOS like BC) the programmer had to deal directly with the computer hardware, These computers were run from a console consisting of display lights, toggle switches, some form of input device.

  • Serial Processing:

    • This systems presented two main problems
      1. Scheduling: users had to sign-up sheet to reserve computer time, users couldn’t`t know precisely how long it will take to finish their program.
      2. Setup time: A single program called a job had to be installed before used with its compiler and code, saving the object program and linking and so on to run the program.
    • Users have access to computer in series.
    • Simple Batch Systems
      • Monitor: a software program that handle executing the jobs provided by the user on tapes or disks.
  • Multiprogrammed Batch Systems

    • The I/O devices are much slower than the processor, leaving the processor idle most of the time waiting for the I/O devices to finish their operations.
    • Uniprogramming: the processor starts executing a certain program and when it reaches an I/O instructions, it must wait until that I/O instructions is fully executed before proceeding.
    • Multiprogramming: in contrast to the uniprogramming, when a job needs to wait for an I/O instruction, the processor switches to another job executing it until the first job finishes its waiting I/O instructions, the processor continue to swap between jobs as it reaches an I/O operation.
    • Multiprogramming batch system must rely on certain hardware capabilities such as process switching when swapping between program execution.
    • Interrupt-driven I/O or DMA helps a lot in multiprogramming environments, allowing the processor to issues an I/O command and proceed executing another program.

  • Time-Sharing Systems

    • As multiprogramming allows processor to handle multiple batch jobs at time, it can allow the processor to handle multiple interactive jobs at time, through time sharing.
    • Time Slicing: there is a system clock that generates interrupts at a constant rate, allowing the OS regain control and assign the processor to another process.

Time sharing and multiprogramming raise a host for new problems

  • If multiple jobs are in memory they must be protected from interfering with each other.
  • File systems must be protected from access by unauthorized users.
  • The programs contention for resources (mass storage, printer, … ) must be handled by the OS.

Major Achievements in OS

  • The Process
    • Possible Definitions:
      • A program in execution.
      • An instance of a program running on a computer.
    • The interrupt helped programmers in developing early multiprogramming and multiuser interactive systems.
    • Errors caused by handling more than one process at time is:
      1. Improper synchronization
      2. Failed mutual exclusion: allow only one routine at a time to perform an update against the file.
      3. Non-determinate program operation: programs may interfere with each other when they share memory and their process is interleaved by the processor.
      4. Deadlocks: 2 programs hung up waiting for each others to release a resource.
    • The execution context or the process state is the internal data by which the OS is able to control the process.
    • The context contains the content of the processor registers, as with information to use by the OS as the priority of the process.
  • Memory Management
    • Process Isolation: OS should prevent independent processes from interfering with each other.
    • Automatic allocation and management: Programs should be dynamically allocated, and allocation should be transparent to the programmer.
    • Support of modular programming: Programmers should be able to write their own programs and create, destroy and alter the size of it dynamically.
    • Long-term storage: saving information for extended periods of time.
    • Protection and access control.
  • Information Protection and Security
    • the use of time-sharing systems, computer networks has brought concern for the protection of information.
    • We are concerned with the problem of controlling access to the computer system.
    • Work in this area can be grouped in:
      • Availability: protect the system against interruption.
      • Confidentiality: users cannot read data for which access is unauthorized.
      • Data integrity: protection of data from unauthorized modification.
      • Authenticity: verification of the identity of the users and validity messages.
  • Scheduling and Resource Management
    • Any resource allocation and scheduling policy must consider:
      1. Fairness: jobs of the same class competing for a resource are to be given equal and fair access to that resource.
      2. Differential responsiveness.
      3. Efficiency: OS should maximize processor utilization, minimize response time, and accommodate as many users as possible.
    • OS elements involved in the scheduling of process and the allocation of resources in a multiprogramming environment:
      • Short-term queue: contains processes in the main memory and are ready to run as soon as the processor is made available.
      • Short-term scheduler: decides which process in the short-term queue to use the processor, a common strategy is to give each process some time in term (round-ribbon) technique.
      • Long-term queue: list of all new jobs waiting to use the processor, the OS adds jobs to the system by transferring process from the long-term queue to the short-term queue.
      • I/O queues: each device has a queue for the processes waiting to use that device; it is the OS that decide which process to assign to an available I/O device.

References

Operating Systems: Internals and Design Principles (6th Edition), William Stallings

Follow

Get every new post delivered to your Inbox.