06
06
Note: In current Windows versions, each process also has other resources like GDI objects,
kernel objects (tokens, jobs), and security descriptors that weren’t enumerated here.
Author’s point: Threads are what the scheduler dispatches to CPUs. Threads within
one process share that process’s resources.
Key thread-specific elements:
1. Stack: For local variables, call frames, exception handling.
2. Thread-Local Storage (TLS): Allows per-thread globals—on Windows via TLS
APIs; on POSIX via pthread_key_t.
3. Start argument: A single pointer or integer passed to the thread’s entry
function.
4. Kernel context: The saved register state when the thread is preempted.
Multiprocessor Scheduling:
o Windows schedules threads across all available CPUs (SMP).
1
o On UNIX/Linux, the kernel’s scheduler does likewise; many implementations
allow thread-to-CPU affinity.
Author’s comparison:
o A UNIX process ≃ Windows process.
o UNIX “threads” via POSIX Pthreads roughly mirror Windows threads, though
Windows offers a richer, highly granular API.
Modern corrections:
o On Linux and modern UNIX-like OSes, lightweight processes (LWP) are often the
kernel-level threads that Pthreads maps onto.
o Windows’ thread API (e.g. CreateThread, SetThreadPriority, synchronization via
events/mutexes/semaphores) is broader than POSIX, but both offer the classic
primitives (mutex, condition variable, semaphore).
Although the excerpt didn’t include code, here’s a corrected, C11-style sketch for spawning a
child process on Windows and on UNIX.
int main(void) {
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
BOOL ok = CreateProcessA(
"C:\\Windows\\System32\\notepad.exe", // Application name
NULL, // Command line (can be modified)
NULL, // Process security attributes
NULL, // Thread security attributes
FALSE, // Inherit handles?
0, // Creation flags
NULL, // Environment (inherit)
NULL, // Current directory
&si, // Startup info
&pi // Process info (output)
);
if (!ok) {
fprintf(stderr, "CreateProcess failed: %lu\n", GetLastError());
return 1;
}
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void) {
3
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return EXIT_FAILURE;
}
if (pid == 0) {
// Child process: replace image with 'ls -l /'
char *argv[] = { "ls", "-l", "/", NULL };
execvp(argv[0], argv);
// Only reached on error:
perror("execvp");
exit(EXIT_FAILURE);
} else {
// Parent process: wait for child
int status;
waitpid(pid, &status, 0);
printf("Child exited with status %d\n", WEXITSTATUS(status));
}
return EXIT_SUCCESS;
}
</details>
Bottom Line:
The author rightly emphasizes the importance of isolating resources at the process level and
using threads for concurrent activities. Modern practice builds on these foundations but
often leans on higher-level abstractions (e.g. std::thread, thread pools) and more
standardized synchronization primitives (C11 atomics, POSIX/Rust channels, etc.).
Understanding these basics, however, remains essential whenever you need fine-grained
control or high performance.
-------------------------
4
6.2 Process Creation on Windows
Author’s point:
o CreateProcess is the fundamental Windows call to spawn a new process (with
one primary thread).
o It returns both a process handle and a thread handle (via a
PROCESS_INFORMATION struct), not a single handle.
Parameter breakdown & modern notes:
1. lpApplicationName vs. lpCommandLine
If you supply lpApplicationName (wide or ANSI), it must be an exact path
(no search).
Otherwise, Windows parses the first token of lpCommandLine and
searches in this order:
1. Parent’s executable directory
2. Current working directory
3. System directory (GetSystemDirectory)
4. Windows directory (GetWindowsDirectory)
5. Directories in PATH
Clarification: In modern code, it’s often simpler to pass a full path in lpApplicationName and
build lpCommandLine separately, to avoid quoting/parsing pitfalls.
CopyEdit
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
Author’s point:
o IDs (DWORD) are globally unique while the object exists, but have no rights
associated.
o Handles carry access rights and are required for handle-based APIs (e.g.,
WaitForSingleObject, TerminateProcess).
o A single object may have multiple handles with different access permissions.
3. No fork in Windows
6
Author’s point:
o Windows lacks a direct fork; instead CreateProcess is analogous to UNIX’s fork +
exec.
o fork itself is ill-suited to multithreaded parents (it would have to replicate all
threads and synchronization state).
o Windows process groups are not the same as UNIX groups, but support console
control propagation.
HANDLE GetCurrentProcess(void)
o Returns a pseudo-handle (value -1), which you can use like a real handle but do
not close.
DWORD GetCurrentProcessId(void)
CopyEdit
HANDLE OpenProcess(
BOOL bInheritHandle,
DWORD dwProcessId
);
7
o Always check for NULL (failure) and call GetLastError() for diagnostics.
Here’s a leaner, typo-free C11 sketch for launching a child process with I/O redirection:
CopyEdit
#include <windows.h>
#include <stdio.h>
int main(void) {
STARTUPINFO si = { .cb = sizeof(si), 0 };
PROCESS_INFORMATION pi = { 0 };
SECURITY_ATTRIBUTES sa = { sizeof(sa), NULL, TRUE };
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdOutput = hOut;
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
BOOL ok = CreateProcessA(
"C:\\Windows\\System32\\ipconfig.exe", // app name
NULL, // command line
NULL, // proc sec attr
NULL, // thread sec attr
TRUE, // inherit handles
0, // creation flags
8
NULL, // env
NULL, // cwd
&si, &pi
);
if (!ok) {
fprintf(stderr, "Error %lu\n", GetLastError());
return 1;
}
Note: Always call CloseHandle on any inheritable handle you no longer need.
Key Takeaways
This foundation will support everything from simple child-process launches to sophisticated
orchestration of console apps, services, or GUI programs.
-----------------------------------
9
1. Duplicating Handles (Section 6.4)
Why duplicate?
A child may need different access rights (or inheritability) than the original handle
provides. Likewise, you sometimes need a real handle (not the pseudohandle from
GetCurrentProcess) that another process can use.
Core API
Graceful exit
10
o Terminates the calling process and all its threads immediately. DLLs still receive
their DLL_PROCESS_DETACH notifications, but structured-exception handlers
(__finally/__except) in your code will not run.
Querying exit status
BOOL GetExitCodeProcess(
HANDLE hProcess, // must have PROCESS_QUERY_INFORMATION
LPDWORD lpExitCode // out: exit code or STILL_ACTIVE
);
Forcing termination
BOOL TerminateProcess(
HANDLE hProcess,
UINT uExitCode
);
o Dangerous! The target process gets no chance to clean up—no SEH cleanup, no
C-runtime on-exit handlers, not even DLL detach calls.
o The author warns especially against using this on processes that share
resources (mutexes, semaphores).
Cooperative shutdown
Rather than killing, consider sending a message or event and letting the target call
ExitProcess itself—this preserves orderly cleanup.
UNIX comparison
o exit() in C maps roughly to ExitProcess.
o To kill another process, UNIX uses signals (e.g. kill(pid, SIGKILL)).
o There’s no direct equivalent of Windows process handles or
GetExitCodeProcess.
c
11
CopyEdit
DWORD WaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds);
DWORD WaitForMultipleObjects(
DWORD nCount,
const HANDLE *lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds
);
Structure
The environment is a single block of consecutive, null-terminated NAME=VALUE
strings, ending in an extra \0. Internally it must be sorted alphabetically
(case-insensitive) to let the system look up variables efficiently.
APIs
DWORD GetEnvironmentVariable(
LPCTSTR lpName,
LPTSTR lpValue,
DWORD cchValue
);
BOOL SetEnvironmentVariable(
LPCTSTR lpName,
LPCTSTR lpValue // if NULL, the variable is deleted
);
12
oIf your buffer is too small, GetEnvironmentVariable returns the size required.
o Passing NULL for the lpEnvironment parameter of CreateProcess inherits the
caller’s entire block.
Best practice
Always check the return value and, if needed, reallocate your buffer. Remember that
neither setenv() nor putenv() on UNIX automatically sorts; that’s purely Windows’
internal requirement.
My take:
The author provides a clear, systematic tour of Windows process handling, contrasting
it with UNIX.
In modern C (C11 and later), you’d still use these same Win32 APIs, but wrap them in
safer RAII-style classes (e.g., smart handles that call CloseHandle) and always check
return codes.
Always prefer cooperative shutdown (events/messages) over TerminateProcess;
handle cleanup deterministically via C++ destructors or at least __try/__finally blocks.
For environments, treat the block as immutable once your process starts; avoid
reshuffling large blocks at runtime—set only what you need.
-----------------------------
13
6.8 Example: Parallel Pattern Searching (grepMP)
Overview
The author builds a “master” program, grepMP, which forks one child process per
input file to run the standard grep-style search. Each child writes its matches to a
distinct temporary file via redirected STDOUT.
Key Steps
1. Make handles inheritable:
CopyEdit
.lpSecurityDescriptor = NULL,
.bInheritHandle = TRUE };
FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
2. Spawn child:
Populate a STARTUPINFO with hStdOutput = hTemp, then call
CreateProcess(NULL, cmd, …, TRUE, …).
3. Collect handles:
Store each child’s hProcess in an array.
4. Wait for all:
Loop through in batches of up to MAXIMUM_WAIT_OBJECTS using
WaitForMultipleObjects(…, TRUE, INFINITE).
5. Report results:
For each process whose exit code is zero (pattern found), spawn a cat (or print
directly) to dump the temp file, then delete it.
Corrections & Modern Tips
o Always check return values of CreateFile, CreateProcess, and wait calls.
o Prefer GetTempFileNameA/GetTempFileNameW carefully: buffer sizes must
include the terminating NUL.
14
o Free resources promptly; consider wrapping handles in RAII wrappers (or use
C11 _cleanup_ tricks).
Automatic Parallelism
The author notes that, with no extra coding, Windows will schedule each child’s thread
on a different CPU core—so grepMP on a 4-core box runs ~4× faster than sequential
grep (15 s vs. 77 s in his tests).
Non-Linear Scaling
Overhead (process creation, I/O serialization) means you won’t get a perfect N-fold
speedup. But in embarrassingly parallel tasks (independent per-file searches), the
gains are worthwhile.
Affinity Control
If you need to reserve cores for other work, you can set a process’s processor affinity
mask (SetProcessAffinityMask) so that only specific CPUs are used.
Threads vs. Processes
The author foreshadows that you could also spawn multiple threads in one process;
Chapter 7 covers thread-level considerations. Threads share memory but still benefit
from multi-core scheduling.
CopyEdit
BOOL GetProcessTimes(
HANDLE hProcess,
LPFILETIME lpCreationTime,
LPFILETIME lpExitTime,
15
LPFILETIME lpKernelTime,
LPFILETIME lpUserTime
);
Purpose
A Windows analogue of UNIX time, printing:
o Real (elapsed) time
o User time
o System (kernel) time
Flow
1. Capture start wall-clock with GetSystemTime.
2. CreateProcess on the target command line.
3. WaitForSingleObject to block until it finishes.
4. Call GetProcessTimes for kernel/user; compute elapsed via the start/end times.
5. Print each with millisecond precision.
Modern Refinements
o Use GetTickCount64 or QueryPerformanceCounter for higher-precision
wall-clock timing.
o For robust parsing of the full command line, prefer CommandLineToArgvW in
Unicode builds.
16
6.12 Generating Console Control Events
Motivation
Abrupt termination (via TerminateProcess) skips cleanup. Instead, you can request
that a console-based child handle a clean shutdown by sending it a control event.
Process Groups
If you start a process with CREATE_NEW_PROCESS_GROUP, that process becomes the
leader of a new group. All its descendants without another such flag join the same
group.
Raising the Event
CopyEdit
BOOL GenerateConsoleCtrlEvent(
DWORD dwCtrlEvent, // CTRL_C_EVENT or CTRL_BREAK_EVENT
DWORD dwProcessGroupId // the leader process’s PID
);
All attached console processes in that group will receive the event, allowing them to run their
SetConsoleCtrlHandler handlers and exit gracefully.
Limitations
o The caller and target must share the same console.
o It only works for console apps. GUI processes aren’t affected.
o Handlers run in a separate thread context; you must guard shared data
appropriately.
In my view, this continuation solidifies how Windows processes can be orchestrated for
parallel work, profiled, and shut down cleanly. For production code, wrap raw handles in safe
abstractions, check every API return, and prefer cooperative shutdown (console events or
custom IPC) over forced kills.
----------------------------------
17
Here’s a concise synthesis of the “Simple Job Management” example, highlighting the key
ideas, correcting typos, and updating to a more modern C11 style where appropriate.
A minimal “job shell” that mimics UNIX shell job control on Windows, with three
commands:
1. jobbg – launch a process in the background (optionally in its own console or
completely detached).
2. jobs – list all active (or stopped) background jobs, showing job numbers, PIDs,
and status.
3. kill – terminate or signal a background job (via TerminateProcess or console
control events).
Persistent job list: Jobs are recorded in a user-specific file (%TMP%\UserName.JobMgt)
so that multiple shells share the same list and jobs survive a shell restart.
Concurrency safety: All accesses to the job-list file are protected by Win32 file locking
(LockFileEx / UnlockFileEx), preventing interleaved updates from multiple interactive
shells.
Note from the author: you could instead use the registry for storing job metadata (Exercise
6-9), and you can extend this basic framework with suspend/resume commands.
18
c
CopyEdit
| (giveConsole ? CREATE_NEW_CONSOLE : 0)
19
o If a console event flag is set, calls GenerateConsoleCtrlEvent().
o Else calls TerminateProcess(hProcess, code).
o Waits up to 5 s for termination, then closes handle and reports.
6. Finding a Job (FindProcessId)
o Seeks to the byte offset (jobNumber – 1)*sizeof(JM_JOB) in the file.
o Locks that single record, reads it, unlocks, closes.
o Returns the stored ProcessId.
20
int main(void) {
bool exitFlag = false;
char line[1024];
char* args[64];
int argc;
if (strcmp(args[0], "jobbg") == 0) {
Jobbg(argc, args, line);
} else if (strcmp(args[0], "jobs") == 0) {
Jobs(argc, args, line);
} else if (strcmp(args[0], "kill") == 0) {
Kill(argc, args, line);
} else if (strcmp(args[0], "quit") == 0) {
exitFlag = true;
} else {
printf("Illegal command. Try again.\n");
}
}
return 0;
}
This version remedies missing punctuation, adopts C11 types, and uses safer string handling.
By correcting minor typos, adopting C11 conventions, and adding more robust shutdown
logic, you can make this example both more maintainable and more production-ready.
------------------------------
Windows Job Objects let you group one or more processes into a single “job,” so you
can:
1. Control them together (e.g. terminate all at once).
2. Enforce resource limits across the group (CPU time, working‐set size, process
count, etc.).
3. Collect accounting data (total user & kernel time, number of processes, etc.).
This mechanism augments the earlier “job shell”–based approach, which managed
each process individually via a shared file.
22
o QueryInformationJobObject(hJob, JobObjectBasicAccountingInformation,
&JOBOBJECT_BASIC_ACCOUNTING_INFORMATION, size, NULL) returns totals:
TotalProcesses, ActiveProcesses, TotalTerminatedProcesses.
TotalUserTime (in 100 ns ticks), TotalKernelTime, TotalElapsedTime.
CopyEdit
// Convert seconds to 100 ns units:
basicLimits.PerProcessUserTimeLimit.QuadPart = userSeconds * 10’000’000;
SetInformationJobObject(hJobObject,
JobObjectBasicLimitInformation,
&basicLimits, sizeof basicLimits);
23
Name confusion: “job” refers both to your UNIX‐style background job and to the
Windows Job Object—keep them straight!
Exercise 6-12 invites you to investigate some discrepancies between MSDN examples
and this code, particularly around structure initialization and flags usage.
The author notes that console output can interleave messily when many processes
share the same window; recommending the -c (new‐console) option to isolate each
child.
1. Robust Initialization
o Rather than rely on C’s “partial brace” syntax, explicitly zero structures before
setting fields:
JOBOBJECT_BASIC_LIMIT_INFORMATION limits = {0};
limits.PerProcessUserTimeLimit.QuadPart = userLimit * 10'000'000;
limits.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_TIME |
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
2. Graceful Shutdown
o Instead of forcible termination, consider GenerateConsoleCtrlEvent or named‐
pipe signaling so processes can clean up.
3. Handle Leaks
o Always check return values from AssignProcessToJobObject,
SetInformationJobObject, etc., and call CloseHandle promptly on both process
and job handles.
4. High-Resolution Timing
o If you need sub-second limits, use the JobObjectExtendedLimitInformation
structure with PerJobUserTimeLimit or leverage Windows’s high-precision
timers.
5. Security
o Supply appropriate SECURITY_ATTRIBUTES when creating named job objects so
that only intended users/processes can join or query them.
Bottom line: Job Objects provide a powerful, centralized way to group processes for collective
control, limit enforcement, and aggregated accounting—far more robust than a simple
file-based tracker alone. By combining them with your existing job-shell framework, you get
both per‐process bookkeeping (from your shared file) and system-level resource governance
(from the job object).
24