Hubbry Logo
Execution (computing)Execution (computing)Main
Open search
Execution (computing)
Community hub
Execution (computing)
logo
7 pages, 0 posts
0 subscribers
Be the first to start a discussion here.
Be the first to start a discussion here.
Execution (computing)
Execution (computing)
from Wikipedia

Execution in computer and software engineering is the process by which a computer or virtual machine interprets and acts on the instructions of a computer program. Each instruction of a program is a description of a particular action which must be carried out, in order for a specific problem to be solved. Execution involves repeatedly following a "fetch–decode–execute" cycle for each instruction done by the control unit. As the executing machine follows the instructions, specific effects are produced in accordance with the semantics of those instructions.

Programs for a computer may be executed in a batch process without human interaction or a user may type commands in an interactive session of an interpreter. In this case, the "commands" are simply program instructions, whose execution is chained together.

The term run is used almost synonymously. A related meaning of both "to run" and "to execute" refers to the specific action of a user starting (or launching or invoking) a program, as in "Please run the application."

Process

[edit]

Prior to execution, a program must first be written. This is generally done in source code, which is then compiled at compile time (and statically linked at link time) to produce an executable. This executable is then invoked, most often by an operating system, which loads the program into memory (load time), possibly performs dynamic linking, and then begins execution by moving control to the entry point of the program; all these steps depend on the Application Binary Interface of the operating system. At this point execution begins and the program enters run time. The program then runs until it ends, either in a normal termination or a crash.

Executable

[edit]

Executable code, an executable file, or an executable program, sometimes simply referred to as an executable or binary, is a list of instructions and data to cause a computer "to perform indicated tasks according to encoded instructions",[1] as opposed to a data file that must be interpreted (parsed) by a program to be meaningful.

The exact interpretation depends upon the use. "Instructions" is traditionally taken to mean machine code instructions for a physical CPU.[2] In some contexts, a file containing scripting instructions (such as bytecode) may also be considered executable.

Context of execution

[edit]

The context in which execution takes place is crucial. Very few programs execute on a bare machine. Programs usually contain implicit and explicit assumptions about resources available at the time of execution. Most programs execute within multitasking operating system and run-time libraries specific to the source language that provide crucial services not supplied directly by the computer itself. This supportive environment, for instance, usually decouples a program from direct manipulation of the computer peripherals, providing more general, abstract services instead.

Context switching

[edit]

In order for programs and interrupt handlers to work without interference and share the same hardware memory and access to the I/O system, in a multitasking operating system running on a digital system with a single CPU/MCU, it is required to have some sort of software and hardware facilities to keep track of an executing process's data (memory page addresses, registers etc.) and to save and recover them back to the state they were in before they were suspended. This is achieved by a context switching.[3]: 3.3 [4] The running programs are often assigned a Process Context IDentifiers (PCID).

In Linux-based operating systems, a set of data stored in registers is usually saved into a process descriptor in memory to implement switching of context.[3] PCIDs are also used.

Runtime

[edit]

Runtime, run time, or execution time is the final phase of a computer program's life cycle, in which the code is being executed on the computer's central processing unit (CPU) as machine code. In other words, "runtime" is the running phase of a program.

A runtime error is detected after or during the execution (running state) of a program, whereas a compile-time error is detected by the compiler before the program is ever executed. Type checking, register allocation, code generation, and code optimization are typically done at compile time, but may be done at runtime depending on the particular language and compiler. Many other runtime errors exist and are handled differently by different programming languages, such as division by zero errors, domain errors, array subscript out of bounds errors, arithmetic underflow errors, several types of underflow and overflow errors, and many other runtime errors generally considered as software bugs which may or may not be caught and handled by any particular computer language.

Implementation details

[edit]

When a program is to be executed, a loader first performs the necessary memory setup and links the program with any dynamically linked libraries it needs, and then the execution begins starting from the program's entry point. In some cases, a language or implementation will have these tasks done by the language runtime instead, though this is unusual in mainstream languages on common consumer operating systems.

Some program debugging can only be performed (or is more efficient or accurate when performed) at runtime. Logic errors and array bounds checking are examples. For this reason, some programming bugs are not discovered until the program is tested in a production environment with real data, despite sophisticated compile-time checking and pre-release testing. In this case, the end-user may encounter a "runtime error" message.

Application errors (exceptions)

[edit]

Exception handling is one language feature designed to handle runtime errors, providing a structured way to catch completely unexpected situations as well as predictable errors or unusual results without the amount of inline error checking required of languages without it. More recent advancements in runtime engines enable automated exception handling which provides "root-cause" debug information for every exception of interest and is implemented independent of the source code, by attaching a special software product to the runtime engine.

Runtime system

[edit]

A runtime system, also called runtime environment, primarily implements portions of an execution model.[clarification needed] This is not to be confused with the runtime lifecycle phase of a program, during which the runtime system is in operation. When treating the runtime system as distinct from the runtime environment (RTE), the first may be defined as a specific part of the application software (IDE) used for programming, a piece of software that provides the programmer a more convenient environment for running programs during their production (testing and similar), while the second (RTE) would be the very instance of an execution model being applied to the developed program which is itself then run in the aforementioned runtime system.

Most programming languages have some form of runtime system that provides an environment in which programs run. This environment may address a number of issues including the management of application memory, how the program accesses variables, mechanisms for passing parameters between procedures, interfacing with the operating system, and otherwise. The compiler makes assumptions depending on the specific runtime system to generate correct code. Typically the runtime system will have some responsibility for setting up and managing the stack and heap, and may include features such as garbage collection, threads or other dynamic features built into the language.[5]

Instruction cycle

[edit]

The instruction cycle (also known as the fetch–decode–execute cycle, or simply the fetch-execute cycle) is the cycle that the central processing unit (CPU) follows from boot-up until the computer has shut down in order to process instructions. It is composed of three main stages: the fetch stage, the decode stage, and the execute stage.

This is a simple diagram illustrating the individual stages of the fetch-decode-execute cycle.

In simpler CPUs, the instruction cycle is executed sequentially, each instruction being processed before the next one is started. In most modern CPUs, the instruction cycles are instead executed concurrently, and often in parallel, through an instruction pipeline: the next instruction starts being processed before the previous instruction has finished, which is possible because the cycle is broken up into separate steps.[6]

Interpreter

[edit]

A system that executes a program is called an interpreter of the program. Loosely speaking, an interpreter directly executes a program. This contrasts with a language translator that converts a program from one language to another before it is executed.

Virtual machine

[edit]

A virtual machine (VM) is the virtualization/emulation of a computer system. Virtual machines are based on computer architectures and provide functionality of a physical computer. Their implementations may involve specialized hardware, software, or a combination.

Virtual machines differ and are organized by their function, shown here:

Some virtual machine emulators, such as QEMU and video game console emulators, are designed to also emulate (or "virtually imitate") different system architectures thus allowing execution of software applications and operating systems written for another CPU or architecture. OS-level virtualization allows the resources of a computer to be partitioned via the kernel. The terms are not universally interchangeable.

See also

[edit]

References

[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
In computing, execution is the process by which a computer or carries out the instructions of a , typically involving the retrieval, decoding, and performance of operations stored in . This core activity is managed primarily by the (CPU), which interacts with , input/output devices, and system buses to read instructions, process data, and produce results. The plays a crucial role in directing this flow by holding the memory address of the next instruction and updating it through increments or jumps during subroutine calls and control transfers. The foundational mechanism of execution is the fetch-decode-execute cycle, a repetitive in the CPU where: the address from the is sent to to fetch the instruction via the data bus; the instruction is loaded into the and decoded to determine the operation; the specified action is performed (such as arithmetic, data movement, or changes); and the is advanced to point to the subsequent instruction. This cycle enables sequential processing in architectures like the von Neumann model, where instructions and data share the same space, though modern s often incorporate pipelining and parallelism to enhance throughput. Before execution begins, programs are loaded from storage (e.g., disk) into by the operating , which also handles file preparation through compilation, assembly, and linking to generate executable machine code. In virtualized settings, execution occurs within a , a software-emulated environment that abstracts the underlying hardware to provide isolated, platform-independent runtimes for programs. VMs are implemented by adding a software layer—such as a or virtual machine monitor—over a physical machine, enabling features like resource control, fault isolation, and the ability to run legacy or incompatible software without direct hardware access. Operating systems further orchestrate execution by managing processes through creation, scheduling, termination, and context switching, using structures like process control blocks to support concurrent multitasking while ensuring efficient . Execution models vary, including for native speed, interpretation for flexibility, and for optimized performance in managed environments like the .

Basic Principles

Instruction Cycle

The instruction cycle, also known as the fetch-decode-execute cycle, is the fundamental process by which a central processing unit (CPU) executes machine instructions in a stored-program computer. This cycle forms the core of the von Neumann architecture, proposed by John von Neumann in 1945, which introduced the concept of storing both instructions and data in the same memory, allowing sequential execution under program control. The first practical implementation of a stored-program machine supporting this cycle appeared in the Manchester Baby computer in 1948, marking the transition from wired-program designs to programmable ones. Commercial formalization came with the IBM System/360 in 1964, which standardized a compatible instruction set across models, emphasizing efficient cycle execution for general-purpose computing. The cycle consists of three primary phases, with an optional fourth in some architectures: fetch, decode, execute, and write-back. In the fetch phase, the CPU retrieves the next instruction from main memory using the address stored in the program counter (PC), a special register that holds the memory location of the instruction to be executed; the fetched instruction is then loaded into the instruction register. The decode phase interprets the instruction by examining its opcode (operation code) to determine the required action and identifying operands, such as registers or immediate values, often using a control unit to generate signals for the datapath. During the execute phase, the CPU performs the specified operation, such as arithmetic on the arithmetic logic unit (ALU), data movement, or control flow changes like branching, which may update the PC non-sequentially. If needed, a write-back phase stores the results back to registers or memory, ensuring data persistence beyond the cycle; this phase is integrated into execute in simpler designs but separated in pipelined processors to improve throughput. The plays a critical role in sequencing instructions, incrementing automatically after each fetch to point to the next sequential address, unless altered by branches or jumps during execution. In modern superscalar processors, which execute multiple , the basic cycle is pipelined across stages to overlap phases of different instructions, but this introduces hazards: data hazards occur when an instruction depends on unready results from a prior one (e.g., read-after-write); control hazards arise from branches that disrupt sequential fetching; and structural hazards stem from resource conflicts, such as multiple instructions accessing the same port simultaneously. These are mitigated through techniques like forwarding, branch prediction, and , but they increase average (CPI) if unresolved. For a simple reduced instruction set computing (RISC) example, consider a processor executing a load instruction like lw $t0, 4($s0), which loads a word from at $s0 + 4 into register $t0. The cycle can be represented in as follows:

initialize PC to program start [address](/page/Address) repeat until halt: fetch: instruction ← [memory](/page/Memory)[PC] PC ← PC + 4 // assuming 32-bit instructions decode: [opcode](/page/Opcode), rs, rt, rd, imm ← parse(instruction) if [opcode](/page/Opcode) == LOAD: execute: [address](/page/Address) ← register[rs] + imm data ← [memory](/page/Memory)[[address](/page/Address)] register[rt] ← data // write-back integrated // handle other opcodes similarly

initialize PC to program start [address](/page/Address) repeat until halt: fetch: instruction ← [memory](/page/Memory)[PC] PC ← PC + 4 // assuming 32-bit instructions decode: [opcode](/page/Opcode), rs, rt, rd, imm ← parse(instruction) if [opcode](/page/Opcode) == LOAD: execute: [address](/page/Address) ← register[rs] + imm data ← [memory](/page/Memory)[[address](/page/Address)] register[rt] ← data // write-back integrated // handle other opcodes similarly

This illustrates sequential execution, with total execution time determined by the formula: total clock cycles = (number of instructions) × CPI, where CPI represents the average cycles required per instruction, typically 1 in an ideal non-pipelined RISC but higher (e.g., 1.5–2) in pipelined designs due to hazards.

Machine Code Execution

Machine code consists of platform-specific binary instructions, comprising opcodes and operands, that are native to a particular CPU architecture such as x86 or . These instructions encode low-level operations that manipulate data, manage , and control directly within the processor. The execution process involves the hardware directly interpreting these binary sequences without intermediate translation layers, where the CPU fetches, decodes, and executes each instruction to perform arithmetic, logical, or control operations. This direct interpretation occurs through the , which processes each step via fetch-decode-execute phases. serves as the human-readable equivalent of , using mnemonic symbols for opcodes and labels for operands, which an assembler translates into . The earliest machine code programs emerged in the 1940s with vacuum-tube computers, exemplified by the Manchester Baby's first stored-program execution in 1948, which ran a 17-instruction sequence on binary data. In modern systems, such as Intel's architecture, instructions like MOV (move data between registers or ) and ADD (add operands in the ALU) form the core of binaries, enabling operations like MOV EAX, EBX to copy values or ADD EAX, 5 to perform arithmetic. Key concepts in machine code include endianness, which specifies byte order in multi-byte data: big-endian stores the most significant byte first (as in network protocols), while little-endian stores the least significant byte first (as in x86 systems). Instruction set architectures (ISAs) further classify designs as CISC, which uses complex, variable-length instructions to minimize program size (e.g., x86), or RISC, which employs simpler, fixed-length instructions for faster execution (e.g., ARM). The basic execution flow transforms binary opcodes into ALU operations, such as loading operands, computing results in the arithmetic logic unit, and storing outcomes, ensuring efficient hardware-level computation across architectures.

Executables and Loading

Executable File Formats

Executable file formats provide a standardized structure for packaging , data, symbols, and metadata, enabling operating systems to load and execute programs efficiently across diverse hardware architectures. These formats emerged to address the limitations of early binary representations, evolving from simple layouts to complex, extensible designs that support features like dynamic linking and . Common formats include the (ELF) for systems, the (PE) for Windows, and Mach-O for macOS and iOS, each tailored to their respective ecosystems while emphasizing portability and modularity. The historical evolution of executable formats began in the 1970s with the a.out format in early Unix systems, which featured a basic structure consisting of a header, text (code) segment, , and , but lacked support for advanced features like dynamic linking and was limited in extensibility. By the late , as Unix variants grew more complex, the Common Object File Format (COFF) introduced improvements such as multiple sections and better symbol handling, serving as a precursor to modern standards. The ELF format, developed by Unix System Laboratories in collaboration with for System V Release 4 around 1989 and first deployed in Solaris 2.0 in 1992, replaced a.out and COFF in most environments by the mid-1990s, offering enhanced flexibility for 32-bit and later 64-bit architectures, cross-platform compatibility, and support for shared libraries. Similarly, PE was introduced by in the early 1990s with to provide a portable structure derived from COFF, while debuted in the mid-1990s with and was adopted by Apple for macOS and iOS to optimize for memory mapping and . These evolutions prioritized , scalability, and the separation of code, data, and metadata to facilitate and optimization. The format, prevalent in and other Unix derivatives since the , begins with a fixed 52-byte (32-bit) or 64-byte (64-bit) ELF header identified by the magic number bytes 0x7F 'E' 'L' 'F', which specifies the file class, encoding (), and version. Key header fields include e_type, which indicates the file type (e.g., ET_EXEC = 2 for files ready for loading), and e_entry, the virtual address of the program's where execution begins. Following the header are program headers (an array of segment descriptors for loadable portions like and ) and section headers (detailing granular components), with sections such as .text for , .data for initialized variables, .symtab for symbol tables (containing names, types, and bindings), and relocation tables (e.g., .rel.text with entries specifying offsets and types like R_386_PC32 for position-independent adjustments). ELF supports both static and dynamic executables, where dynamic ones include a .dynamic section with tags like DT_NEEDED for dependencies. The PE format, used in Windows executables (.exe and .dll files) since the 1990s, starts with a 64-byte stub header for compatibility, including the magic number MZ (0x5A4D) and an offset (e_lfanew) to the PE signature "PE\0\0". This is followed by the COFF file header (specifying machine type and section count) and a required optional header for images, which includes the AddressOfEntryPoint (relative virtual address for startup) and fields like ImageBase for preferred loading address. Sections, defined in a trailing table, encompass .text for code, .data for variables, .rdata for read-only data, and metadata such as .reloc tables (base relocation blocks with additive deltas for address fixes) and .idata for import tables listing DLL functions. PE files are inherently designed for dynamic linking, with export tables in .edata enabling usage, though static variants embed dependencies directly. Mach-O, the native format for Apple platforms since the 1990s, features a compact header with (e.g., MH_MAGIC = 0xFEEDFACE for 32-bit executables), CPU type, file type (MH_EXECUTE for executables), and the offset. Load commands follow, providing directives like LC_SEGMENT for mapping segments into memory and LC_MAIN for the . The file is divided into segments such as __TEXT (read-only, containing __text for code, __const for constants, and __cstring for strings) and __DATA (writable, with __data for variables and __bss for uninitialized storage), each comprising multiple sections for fine-grained organization. Metadata includes dynamic linking info via load commands like LC_LOAD_DYLIB for dependencies and symbol tables for relocations, supporting both static self-contained binaries and dynamic ones that leverage shared frameworks for reduced disk usage and improved update efficiency. A core distinction in executable formats lies between static and dynamic variants: static executables incorporate all necessary code directly into the file during compilation, resulting in larger but fully portable binaries that require no external dependencies at runtime, as seen in or PE files without dynamic sections. Dynamic executables, conversely, reference shared libraries via metadata like ELF's .dynamic or PE's import tables, yielding smaller files that promote system-wide and easier updates but depend on runtime resolution for addresses and symbols, enhancing portability across compatible systems while minimizing redundancy. This design choice, embedded in formats like ELF since its , balances size, security, and maintainability in modern computing environments.

Loading and Linking

In , loading refers to the operating system's of reading an file from disk and preparing it in for execution, while linking resolves references to external code or data, either at or during loading. The OS loader, such as the kernel in systems, handles this by parsing the executable's structure—typically formats like on —to allocate appropriate regions. For instance, in , the system call invokes the kernel to load an , mapping its segments into using mechanisms like for efficient on-demand paging. This includes allocating read-only space for the text () segment, read-write space for initialized data, zero-initialized space for the segment, and dynamic regions for the stack and heap. Linking can be static or dynamic, determining when and how dependencies on libraries are resolved. Static linking occurs at compile time, where the linker embeds all required library code directly into the executable, resulting in a self-contained binary that does not rely on external files at runtime. This approach simplifies execution but increases file size and can lead to code duplication across programs. In contrast, dynamic linking defers resolution to load time or runtime, allowing shared libraries (e.g., .so files on Unix-like systems or .dll files on Windows) to be loaded separately and reused by multiple processes, conserving memory. Tools like LD_PRELOAD in Linux enable runtime overrides by preloading custom libraries before standard ones. During loading, key steps ensure the is correctly positioned in , including relocation and measures. Relocation adjusts absolute es in the and to match the actual load address, using tables in the (e.g., .rel.dyn in ) to patch references if the program is not loaded at its preferred base. (ASLR), introduced in 3.4 in November 2003, randomizes the base addresses of key segments like the stack, heap, libraries, and mmap regions to thwart exploits by making addresses unpredictable. This feature became widespread in the 2010s, adopted in (from kernel 2.6.12 in 2005) and other systems, providing entropy against attacks without significantly impacting performance. Once loaded and linked, execution begins at the program's , specified in the ELF header's e_entry field, which typically points to the _start symbol provided by the runtime startup code. This routine initializes the environment (e.g., setting up argc/argv and calling constructors) before transferring control to the user's main function. In dynamic linking, the loader (e.g., ld-linux.so on ) may use lazy binding by default, resolving symbols only on first use via the Procedure Linkage Table (PLT) and Global Offset Table (GOT), which delays overhead for unused functions and improves startup time. Eager binding, forced via LD_BIND_NOW, resolves all symbols at load time for predictability, though at higher initial cost.

Processes and Execution Context

Process Lifecycle

The process lifecycle in computing refers to the sequence of states and transitions that a process undergoes from its to completion within an operating system. This lifecycle serves as the foundational mechanism for managing execution, enabling the OS to allocate resources, schedule activities, and ensure orderly termination. Introduced in early systems, the concept allows multiple programs to share system resources efficiently without direct interference.

Creation

Process creation initiates the lifecycle, where the operating system allocates resources and sets up the initial state for a new execution unit. In Unix-like systems, this typically involves the fork() system call, which duplicates the calling process to create a child process sharing the same code, data, and open files, followed by exec() to replace the child's image with a new program. The parent process then uses wait() to monitor and reap the child upon completion, preventing resource leaks. In contrast, Windows employs the CreateProcess API, which directly specifies the executable and creates a new process with its primary thread, inheriting security context from the parent but operating independently. Upon creation, each process receives a unique Process ID (PID), a numerical identifier used for tracking and signaling throughout its lifecycle. The operating system maintains a (PCB) for every , a kernel that stores essential state information, including the PID, CPU registers, , details (such as page tables and pointers), open file descriptors, and accounting data like CPU usage. This PCB is allocated during creation and updated across state transitions to preserve the process's context.

Running

Once created and loaded, a process enters the running state, where it actively executes instructions on the CPU. In this phase, the process consumes computational resources, performing tasks like or I/O operations until interrupted by a timer, signal, or voluntary yield. The OS scheduler selects processes for the CPU based on algorithms that balance responsiveness and throughput.

Waiting or Blocked

A process transitions to a waiting or blocked state when it cannot proceed immediately, often due to awaiting external events such as I/O completion (e.g., disk read) or resource availability. In this state, the process is removed from the CPU but remains in memory, with its PCB updated to reflect the blocking condition and associated event. This allows the OS to allocate the CPU to other ready processes, improving system utilization. Common triggers include system calls for I/O or synchronization primitives like semaphores.

Terminated

Termination marks the end of the lifecycle, occurring when a process completes its task via an exit system call or is forcibly ended by the OS or a signal. Upon exit, the process releases its resources, and its PCB is marked for cleanup; the parent (or init process in Unix) reaps the exit status to finalize termination. If the parent does not reap promptly, the process becomes a zombie—a defunct entry in the process table holding minimal PCB data (like PID and exit code) until collected, consuming negligible resources but potentially leading to table exhaustion if numerous. An orphan process arises if the parent terminates first; the OS reparents it to the init process (PID 1), which automatically reaps it upon completion. Forced termination can occur via signals, such as SIGKILL in Unix, which unconditionally ends the process without cleanup.

Historical Development

The process concept originated in the operating system during the 1960s, developed as a joint project by MIT, , and starting in 1965, to support multi-user with dynamic and process hierarchies like user-process-groups. It was popularized in Unix in the 1970s, with early implementations on the PDP-11 introducing for efficient process creation, evolving from Multics' interactive model but simplified for portability. Modern extensions include process groups (collections of related processes for signaling) and sessions (groups for terminal management), standardized in for systems.

Management

Process lifecycle management involves scheduling to determine execution order, using priority queues to organize processes by urgency or fairness. Scheduling can be preemptive, where the OS interrupts a running (e.g., via timers) to switch to a higher-priority one, or , relying on voluntary yields, with preemption dominant in modern systems for responsiveness. Transitions between states, such as from running to waiting, often trigger context switching to save and restore PCB contents for the next process. Priority adjustments and signals facilitate dynamic control, ensuring efficient resource use across the lifecycle.

Context Switching

Context switching is the operating system mechanism that enables multitasking by suspending the execution of one and resuming another, allowing multiple processes to share a single CPU core. This involves saving the current process's execution state to its (PCB) and loading the state of the next process from its PCB into the CPU. The saved state typically includes CPU registers, the (which points to the next instruction), stack pointer, and memory management details such as page table base registers for mappings. The overhead of context switching stems from the time and resources needed to perform these save and restore operations, as well as indirect costs like flushing translation lookaside buffers (TLBs) and reloading caches. On modern CPUs, this overhead generally ranges from 1 to 5 microseconds, varying with hardware architecture, kernel implementation, and whether the switch involves full or lightweight thread state changes. Context switches frequently occur via mode transitions from user space to kernel space, often triggered by hardware interrupts that require kernel intervention. Techniques such as reduce the frequency of switches by allowing threads to maintain private data without shared global structures that might necessitate and preemption. Key concepts in context switching include its interrupt-driven nature and the distinction between voluntary and involuntary types. Interrupt-driven switches are prompted by events like timer interrupts for time-slicing or I/O completion signals, ensuring fair CPU allocation among processes. Voluntary switches happen when a process explicitly relinquishes the CPU, such as through a yield or blocking system call, while involuntary switches occur via OS preemption to enforce scheduling policies. A historical milestone was the 1968 , which pioneered efficient context switching through a structured, layered approach to process management, influencing subsequent OS designs. In modern systems, introduces hypervisor-level context switches, which became prevalent in the with the rise of platforms like and , adding overhead from managing guest-to-host state transitions beyond standard OS switches. serves as a by enabling involuntary switches even in kernel mode, reducing latency spikes and allowing finer-grained control over switching frequency to balance responsiveness and efficiency.

Runtime Environments

Runtime System

A is a software layer that supports the execution of programs by providing beyond those offered by the operating system, such as memory allocation, enforcement, and abstracted operations. It acts as an intermediary between the application code and the underlying OS, managing language-specific runtime behaviors to ensure portability and efficiency across different hardware platforms. For instance, in managed languages, the handles automatic memory reclamation to prevent leaks, while in systems languages, it supplies low-level utilities for manual resource control. Key components of a runtime system include , type checking, and I/O abstraction. often involves garbage collection in environments like the (JVM), where the HotSpot runtime uses generational collectors to automatically identify and free unreferenced objects, optimizing for throughput and latency. In contrast, the C runtime library (libc) provides manual functions such as malloc for dynamic allocation and free for deallocation, allowing fine-grained control over heap memory. Type checking ensures runtime safety, as seen in the JVM's bytecode verifier, which performs stack-based to prevent invalid operations like type mismatches. I/O abstraction simplifies interactions with the OS; for example, libc offers standardized functions like and fopen to handle file and stream operations portably across Unix-like systems. Runtime systems originated in the 1950s with early high-level languages, evolving from runtime libraries in that supported mathematical computations and I/O on machines like the , where the compiler-generated code relied on a supporting library for non-intrinsic operations. By the , these libraries had become integral for handling and error conditions in scientific . Modern runtime systems, such as the .NET Common Language Runtime (CLR) introduced in 2002, extend this foundation with comprehensive services including cross-language type compatibility and automatic via a mark-and-sweep garbage collector. Similarly, the JVM's runtime, formalized in the Java specification since 1999, integrates these elements within a architecture. A prominent key concept in contemporary runtime systems is the integration of just-in-time (JIT) compilation, which dynamically optimizes code for performance; the V8 JavaScript engine, released in 2008 and updated since 2017, employs a pipeline of Ignition (interpreter to bytecode) followed by optimizing JIT compilers like TurboFan to achieve near-native speeds for web applications. Security features, such as sandboxing, further enhance runtime isolation—for example, the JVM's security manager (deprecated since Java 17 in 2021 and permanently disabled as of JDK 24 in 2024) formerly restricted untrusted code from accessing sensitive resources like the file system, with modern alternatives including the Java Platform Module System for encapsulation and access control. These mechanisms collectively enable robust, secure execution while abstracting OS complexities.

Exception Handling

Exception handling in computing refers to the mechanisms provided by programming languages and runtime systems to detect, respond to, and recover from anomalous conditions during program execution, such as errors or unexpected events that disrupt normal flow. These mechanisms allow programs to maintain robustness by transferring control to dedicated handlers, preventing abrupt termination and enabling graceful degradation or recovery. The runtime system typically provides the underlying infrastructure for propagating and resolving these events, ensuring that resources are properly managed even under failure conditions. Exceptions are broadly categorized into hardware and software types. Hardware exceptions arise from processor-detected faults, such as divide-by-zero errors or page faults due to invalid memory access, which trigger immediate interruption of the executing instruction. Software exceptions, in contrast, are explicitly raised by the program code in response to logical errors, like a NullPointerException in when attempting to dereference a null reference. These distinctions ensure that both low-level hardware anomalies and high-level application errors are addressed uniformly within the execution model. The handling process involves detecting an exception, propagating it through a chain of potential handlers, and executing recovery code if available. In structured exception handling, prevalent in modern languages, exceptions are managed using try-catch blocks that delimit protected code regions; upon detection, the stack unwinds by destroying local objects in reverse order of construction (via destructors in C++), searching for a matching catch handler up the call stack. If no handler is found, the exception propagates further until resolved or the program terminates. Unstructured approaches, such as setjmp/longjmp in C, bypass this stack discipline by saving and restoring execution context non-locally, which can lead to resource leaks or undefined behavior if not carefully managed. Exception handling originated in the 1960s with early implementations in , which introduced ON-units for condition handling, and Lisp variants that supported error recovery through mechanisms like the ERROR pseudo-function in Lisp 1.5. Structured exception handling was formalized in the seminal work by Goodenough, who outlined requirements for language features to support reliable error propagation and recovery. Standardization occurred in C++ with the introduction of try-throw-catch in the Annotated C++ Reference Manual in 1990, and in upon its release in 1995, where exceptions are integral to the language specification with checked and unchecked variants. Asynchronous exceptions, which can interrupt execution at arbitrary points, are handled via signals, such as SIGFPE for floating-point errors, providing a system-level mechanism for non-synchronous error notification. Key concepts in exception handling include exception safety guarantees, which ensure program invariants are preserved post-exception. The basic guarantee requires that no resources leak and the program remains in a valid state, while the strong guarantee restores the pre-exception state as if the operation never occurred, as detailed in foundational C++ design principles. Performance impacts are mitigated through zero-cost abstractions, where normal execution incurs no overhead from exception machinery; for instance, Rust's panic mechanism, introduced in version 1.0 in , uses unwind or abort strategies that avoid runtime checks unless an error occurs, aligning with the language's emphasis on efficient error handling.

Alternative Execution Models

Interpreters

In computing, an interpreter executes instructions directly without first translating the entire program into , typically processing code line-by-line or statement-by-statement during runtime. This approach contrasts with compilation by enabling immediate execution from or an intermediate form like , often resulting in slower overall runtime performance due to repeated translation but faster startup times and greater ease of modification. The core mechanism of an interpreter involves parsing the input code—either directly from source text or from a platform-independent representation—and then evaluating it through an iterative loop. For , the interpreter may first construct an (AST) to represent the program's structure, then traverse this tree to execute operations sequentially. In languages like , this evaluation occurs via a read-eval-print loop (REPL), where the interpreter reads user input, evaluates it in the current environment, and prints the result, facilitating interactive development. interpreters, such as those in Python, first compile source to a compact intermediate form before interpreting it, balancing readability with efficiency. Historically, interpreters gained prominence in the with systems like the UCSD p-System, which used a p-code interpreter to run Pascal programs portably across diverse hardware without native compilation, emphasizing cross-platform scripting and . This portability advantage made interpreters ideal for environments where hardware varied widely, allowing code to execute via a single interpreter implementation per platform. Key concepts in interpreter design include AST traversal for structured execution, which simplifies semantic analysis, and inherent ease, as errors can be identified and corrected interactively without recompilation cycles. Hybrid approaches, such as just-in-time () interpretation, further optimize performance by dynamically compiling hot code paths during execution while retaining interpretive flexibility. Prominent examples include , the of Python released in 1991, which interprets generated from to support versatile scripting applications. Similarly, , the original JavaScript engine developed by at in 1995, interprets scripts directly in web browsers, enabling dynamic client-side behavior. These interpreters often operate within virtual machines to abstract underlying hardware, enhancing portability across execution environments.

Virtual Machines

Virtual machines (VMs) provide abstracted hardware environments that emulate complete systems, allowing software to execute in isolated, portable settings independent of the underlying physical hardware. This abstraction enables multiple operating systems or applications to run concurrently on a single host machine, optimizing resource utilization and facilitating development, testing, and deployment across diverse platforms. VMs are categorized into two primary types: system virtual machines and process virtual machines. System VMs, such as those managed by or , emulate an entire physical computer, including hardware components like processors, memory, and I/O devices, to run a full guest operating system and its applications. These were pioneered for mainframe environments and later adapted for x86 architectures, with introducing its product in 1999 following the company's founding in 1998 to support full OS emulation on commodity hardware. In contrast, process VMs, exemplified by the (JVM), focus on executing a single application or process by interpreting or translating platform-specific into host instructions, without emulating a complete hardware stack; the JVM, specified by , enables programs to run portably across operating systems by managing execution within a host process. Another prominent process VM is (Wasm), a binary instruction format for a stack-based that enables high-performance execution of code compiled from various languages in web browsers and standalone runtimes; first released in March 2017 and updated to version 2.0 in March 2025, Wasm provides a secure, portable alternative for compute-intensive tasks. The execution model of a VM relies on a or host runtime to mediate access to physical resources. In system VMs, a Type-1 (bare-metal) like KVM runs directly on hardware and partitions resources such as , memory, and I/O among guests, translating or trapping guest instructions to the host CPU for execution. KVM, integrated into the in 2007, leverages hardware virtualization extensions (e.g., Intel VT-x) to efficiently virtualize these resources while minimizing overhead through direct device passthrough or emulated interfaces. Process VMs employ a runtime environment, such as the JVM's interpreter or just-in-time , to virtualize resources at the application level, mapping abstract instructions to native code on the host OS. Resource virtualization ensures isolation, with mechanisms like shadow page tables for memory and virtual network interfaces for I/O, allowing guests to operate as if on dedicated hardware. Historically, virtual machines trace their origins to IBM's CP/CMS system in 1967, which introduced and on the System/360 Model 67, enabling multiple interactive user sessions on a mainframe. This foundational work evolved into VM/370 in 1972 and influenced modern implementations, including Microsoft's , released in 2008 as a Type-1 for to support server consolidation and workload mobility. Containerization emerged as a lightweight VM variant with Docker in 2013, using to package applications with dependencies while sharing the host kernel, reducing overhead compared to full emulation. Key concepts in VM technology include paravirtualization, which modifies guest OS code to communicate directly with the , improving by reducing trapping overhead in I/O and scheduling operations, as demonstrated in Xen-based systems where it achieves near-native throughput for workloads. allows a running VM to transfer between physical hosts with minimal downtime, suspending execution briefly to copy state over the network, enhancing in clustered environments like those using KVM or . Security isolation is paramount, with VMs providing strong boundaries via hardware-enforced and ring-based privilege separation to prevent guest escapes or interference, though vulnerabilities like side-channel attacks necessitate ongoing mitigations.

References

Add your contribution
Related Hubs
User Avatar
No comments yet.