Patent Publication Number: US-11656878-B2

Title: Fast boot

Description:
CROSS REFERENCE TO OTHER APPLICATIONS 
     This application is a continuation of U.S. patent application Ser. No. 16/186,335, entitled FAST BOOT filed Nov. 9, 2018, which is incorporated herein by reference for all purposes, which claims priority to U.S. Provisional Patent Application No. 62/586,052 entitled FAST BOOT filed Nov. 14, 2017 which is incorporated herein by reference for all purposes. 
    
    
     BACKGROUND OF THE INVENTION 
     The time to perform memory testing and initialization is proportional to the amount of memory presented to the operating system. On conventional servers with large memories, the booting up of such large memory systems may take a substantial amount of time. Further, if the operating system is performing the initialization, then normal applications are typically not run until this initialization is complete. Improvements to memory initialization of large memory systems are needed. 
    
    
     
       BRIEF DESCRIPTION OF THE DRAWINGS 
       Various embodiments of the invention are disclosed in the following detailed description and the accompanying drawings. 
         FIG.  1    illustrates an embodiment of a computer system. 
         FIG.  2    illustrates the physical structure of the computer system as a hierarchy. 
         FIG.  3 A  depicts a virtualized computing environment in which multiple virtual machines (with respective multiple guest operating systems) run on a single physical machine. 
         FIG.  3 B  depicts a virtualized computing environment in which multiple physical machines collectively run a single virtual operating system. 
         FIG.  4 A  depicts an example of a software stack. 
         FIG.  4 B  depicts an example of a software stack. 
         FIG.  5    depicts an example of an operating system&#39;s view of hardware on an example system. 
         FIG.  6 A  depicts an example of a hyperthread&#39;s view of hardware on a single node. 
         FIG.  6 B  depicts an example of a hyper-kernel&#39;s view of hardware on an example system. 
         FIG.  7    depicts an example of an operating system&#39;s view of hardware on an example of an enterprise supercomputer system. 
         FIG.  8    illustrates an embodiment of a process for selectively migrating resources. 
         FIG.  9    illustrates an embodiment of a process for performing hierarchical dynamic scheduling. 
         FIG.  10    illustrates an example of an initial memory assignment and processor assignment. 
         FIG.  11    illustrates an updated view of the memory assignment and an unchanged view of the processor assignment. 
         FIG.  12    illustrates a memory assignment and an updated view of the processor assignment. 
         FIG.  13 A  illustrates an example embodiment of a system in which a network attached memory is used in selective resource migration. 
         FIG.  13 B  illustrates an example embodiment of a system in which a network-attached memory is used in selective resource migration. 
         FIG.  14    illustrates an example embodiment of a network-attached memory appliance. 
         FIG.  15    is a diagram illustrating an embodiment of allocating a page. 
         FIG.  16    illustrates an example embodiment of internode communication according to a cache coherency protocol. 
         FIG.  17    is a flow diagram illustrating an embodiment of a process for maintaining cache coherency in the presence of a network attached memory. 
         FIG.  18    is a flow diagram illustrating an embodiment of a process for zeroing pages. 
         FIG.  19    illustrates an embodiment of initializing a multi-server system. 
         FIG.  20    is a flow diagram illustrating an embodiment of a process for initializing a system using dormant pages. 
         FIG.  21    illustrates an embodiment of converting a dormant page. 
         FIG.  22    is a flow diagram illustrating an embodiment of a process for converting a dormant page. 
         FIG.  23    illustrates an embodiment of re-dormanting pages. 
         FIG.  24    is a flow diagram illustrating an embodiment of a process for converting a dormant page. 
     
    
    
     DETAILED DESCRIPTION 
     The invention can be implemented in numerous ways, including as a process; an apparatus; a system; a composition of matter; a computer program product embodied on a computer readable storage medium; and/or a processor, such as a processor configured to execute instructions stored on and/or provided by a memory coupled to the processor. In this specification, these implementations, or any other form that the invention may take, may be referred to as techniques. In general, the order of the steps of disclosed processes may be altered within the scope of the invention. Unless stated otherwise, a component such as a processor or a memory described as being configured to perform a task may be implemented as a general component that is temporarily configured to perform the task at a given time or a specific component that is manufactured to perform the task. As used herein, the term ‘processor’ refers to one or more devices, circuits, and/or processing cores configured to process data, such as computer program instructions. 
     A detailed description of one or more embodiments of the invention is provided below along with accompanying figures that illustrate the principles of the invention. The invention is described in connection with such embodiments, but the invention is not limited to any embodiment. The scope of the invention is limited only by the claims and the invention encompasses numerous alternatives, modifications and equivalents. Numerous specific details are set forth in the following description in order to provide a thorough understanding of the invention. These details are provided for the purpose of example and the invention may be practiced according to the claims without some or all of these specific details. For the purpose of clarity, technical material that is known in the technical fields related to the invention has not been described in detail so that the invention is not unnecessarily obscured. 
     As used herein, a software defined server refers to an assembly or collection or cluster of hardware modules (e.g., computing nodes or individual computers) that run a distributed virtual machine manager referred to herein as a “hyper-kernel.” 
     Compared to existing distributed multi-server architectures, on which a single physical node or server supports many virtual machines, in a software defined server, a single virtual machine that spans many physical nodes/servers is supported. 
     Details regarding an example implementation of a software defined server are described below. 
       FIG.  1    illustrates an embodiment of a computer system. System  100  is also referred to herein as a “software defined server,” an “enterprise supercomputer,” and a “mainframe.” In the example shown, system  100  includes a plurality of nodes (e.g., nodes  102 - 108 ) located in close proximity (e.g., located within the same rack or nearby racks of servers). In other embodiments, multiple racks of nodes (e.g., located within the same facility) can be used in the system. Further, the techniques described herein can also be used in conjunction with distributed systems. 
     The nodes are interconnected with an interconnect ( 110 ) such as 10-gigabit or 40-gigabit Ethernet, direct PCI-to-PCI, and/or InfiniBand. Each node comprises commodity server-class hardware components (e.g., a blade in a rack with its attached or contained peripherals). The interconnect may be dedicated, but need not be. The interconnect may also be high-speed, but need not be. In the example shown in  FIG.  1   , each node includes multiple physical processor chips. Each physical processor chip (also referred to as a “socket”) includes multiple cores, and each core has multiple hyperthreads. 
     As illustrated in  FIG.  2   , the physical structure of system  100  forms a hierarchy ( 230 ) (from the bottom) of hyperthreads ( 232 ), cores ( 210 - 224 ), physical processor chips ( 202 - 208 ), and nodes ( 102 - 108  (with nodes  104 ,  106 , etc. omitted from the figure and represented as ellipses)). The tree depicted in  FIG.  2    is of a fixed size, defined by the hardware configuration. 
     As will be described in more detail below, each enterprise supercomputer (e.g., system  100 ) runs a single instance of an operating system. Both the operating system, and any applications, can be standard commercially available software and can run on system  100 . In the examples described herein, the operating system is Linux, however other operating systems can also be used, such as Microsoft Windows, Mac OS X, or FreeBSD. 
     In a traditional virtualized computing environment, multiple virtual machines may run on a single physical machine. This scenario is depicted in  FIG.  3 A . In particular, three virtual machines ( 302 - 306 ) are running three guest operating systems on a single physical machine ( 308 ), which has its own host operating system. In contrast, using the techniques described herein, multiple physical machines ( 354 - 358 ) collectively run a single virtual operating system ( 352 ), as depicted in  FIG.  3 B . 
     One example of a software stack is depicted in  FIG.  4 A . Such a stack may typically be used in traditional computing environments. In the stack shown in  FIG.  4 A , an application ( 402 ) sits above a database engine ( 404 ), which in turn sits upon an operating system ( 406 ), underneath which lies hardware ( 408 ).  FIG.  4 B  depicts a software stack used in some embodiments. As with the stack shown in  FIG.  4 A , an application ( 452 ) sits above a database engine ( 454 ), which in turn sits upon an operating system ( 456 ). However, underneath the operating system and above the hardware is a layer of software (referred to herein as a hyper-kernel) that observes the system running in real time and optimizes the system resources to match the needs of the system as it operates. The hyper-kernel (one example of a virtual machine manager) conceptually unifies the RAM, processors, and I/O (Input Output resources for example Storage, Networking resources) of a set of commodity servers, and presents that unified set to the operating system. Because of this abstraction, the operating system will have the view of a single large computer, containing an aggregated set of processors, memory, and I/O. As will be described in more detail below, the hyper-kernel optimizes use of resources. The hyper-kernel can also help optimize other I/O system resources such as networks and storage. In some embodiments, based on observations and profiles of running software, performance indicators (hints) are provided to upper layers (e.g., database management systems) about the dynamic performance of the system that can further improve overall system performance. 
     The hyper-kernel can be ported to all major microprocessors, memory, interconnect, persistent storage, and networking architectures. Further, as hardware technology evolves (e.g., with new processors, new memory technology, new interconnects, and so forth), the hyper-kernel can be modified as needed to take advantage of industry evolution. 
     As shown in  FIG.  4 B , operating system  456  is running collectively across a series of nodes ( 458 - 462 ), each of which has a hyper-kernel running on server hardware. Specifically, the operating system is running on a virtual environment that is defined by the collection of hyper-kernels. As will be described in more detail below, the view for operating system  456  is that it is running on a single hardware platform that includes all of the hardware resources of the individual nodes  458 - 462 . Thus, if each of the nodes includes 1 TB of RAM, the operating system will have as a view that it is running on a hardware platform that includes 3 TB of RAM. Other resources, such as processing power, and I/O resources can similarly be collectively made available to the operating system&#39;s view. 
       FIG.  5    depicts an example of an operating system&#39;s view of hardware on an example system. Specifically, operating system ( 502 ) runs on top of processors  504 - 508  and physical shared memory  510 . As explained above, an operating system can run on either a traditional computing system or on an enterprise supercomputer such as is shown in  FIG.  1   . In either case, the view of the operating system will be that it has access to processors  504 - 508  and physical shared memory  510 . 
       FIG.  6 A  depicts an example of a hyperthread&#39;s view of hardware on a single node. In this example, a node has four hyperthreads denoted H1 ( 602 ) through H4 ( 608 ). Each hyperthread can access all portions of physical shared memory  612 . Physical shared memory  612  is linear, labeled location 0 through a maximum amount, “max.” The node also includes three levels of cache ( 610 ). 
       FIG.  6 B  depicts an example of a hyper-kernel&#39;s view of hardware on an example system. In this example, three nodes ( 652 - 656 ) are included in an enterprise supercomputer. Each of the three nodes has four hyperthreads, a physical shared memory, and cache (i.e., each node is an embodiment of node  600  shown in  FIG.  6 A ). A hyperthread on a given node (e.g., node  652 ) has a view that is the same as that shown in  FIG.  6 A . However, the hyper-kernel is aware of all of the resources on all of the nodes (i.e., the hyper-kernel sees twelve hyperthreads, and all of the physical shared memory). In the example shown in  FIG.  6 B , a given hyperthread (e.g., hyperthread  658 , “H1-4”) is labeled with its node number (e.g., “1”) followed by a hyperthread number (e.g., “4”). 
       FIG.  7    depicts an example of an operating system&#39;s view of hardware on an example of an enterprise supercomputer system. The operating system sees a plurality of “virtualized processors” (also referred to herein as “virtual processors” and “VCPUs”) denoted in  FIG.  7    as P1 through Pmax ( 702 ). The virtualized processors correspond to the total number of hyperthreads across all nodes included in the enterprise supercomputer. Thus, using the example of  FIG.  6 B , if a total of twelve hyperthreads are present across three nodes, a total of twelve virtualized processors would be visible to an operating system running on the enterprise supercomputer. The operating system also sees “virtualized physical memory” ( 704 ) that appears to be a large, physical, linear memory of a size equal to the total amount of physical memory across all nodes. 
     As will be described in more detail below, the hyper-kernel dynamically optimizes the use of cache memory and virtual processor placement based on its observations of the system as it is running. A “virtual processor” is a computing engine known to its guest operating system, i.e., one that has some operating system context or state. As will be described in more detail below, the hyper-kernel dynamically optimizes the use of cache memory and virtual processor placement based on its observations of the system as it is running. A “virtual processor” is a computing engine known to its guest operating system, i.e., one that has some operating system context or state. 
     Resource Virtualization 
     Memory Virtualization 
     As explained above, in the physical configuration, each node has an array of memory addresses representing locations in memory. As such, in a physical configuration with three nodes (e.g., as depicted in  FIG.  6 B ), there are three memory locations each of which has address 0x123456. In contrast, in the virtual configuration, all memory addresses are unique and represent the sum total of all memory contained in those three nodes. In the virtual configuration, all memory is shared, and all memory caches are coherent. In some embodiments, memory is further subdivided into a series of contiguous blocks, with monotonically increasing memory addresses. In the examples described herein, each page has 4K bytes of memory, however, other subdivisions can also be used, as applicable. The term “blocks” is used herein to describe contiguous arrays of memory locations. In some embodiments, the “blocks” are “pages.” 
     Processor Virtualization 
     A virtual processor (e.g., virtual processor  706  of  FIG.  7   ), as seen by the operating system, is implemented on a hyperthread in the physical configuration, but can be location independent. Thus, while the operating system thinks it has 500 processors running on a single physical server, in actuality it might have 5 nodes of 100 processors each. (Or, as is shown in  FIG.  6 B , the operating system will think it has twelve processors running on a single physical server.) The computation running on a virtual processor is described either by the physical configuration on a hyperthread when the computation is running, or in a “continuation,” when the virtual processor is not running (i.e., the state of an interrupted or stalled computation). 
     As used herein, a “continuation” represents the state of a virtual processor. Each continuation:
         Has processor state (i.e., saved registers, etc.).   Has a set of performance indicators that guide a scheduler object with information about how to intelligently assign continuations to leaf nodes (e.g., hyperthreads) for execution.   Has a virtual-processor identifier that indicates the processor the operating system thinks is the physical processor to which this continuation is assigned.   Has an event on which this continuation is waiting (possibly empty).   Has a state which includes: “waiting-for-event” or “ready.”       

     I/O Virtualization 
     I/O systems observe a similar paradigm to processors and memory. Devices have a physical address in the physical configuration and virtual addresses in the virtual configuration. When migrating computations (described in more detail below), if for example, there are memory buffers associated with I/O operations, the I/O devices used will likely perform better if they are co-located with the memory with which they are associated, and can be moved accordingly. 
     Resource Maps 
     Resource maps are used to translate between virtual and physical configurations. The following are three types of resource maps used by enterprise supercomputers in various embodiments. 
     A “physical resource map” is a table that describes the physical resources that are available on each node. It contains, for example, the number and type of the processors on each node, the devices, the memory available and its range of physical addresses, etc. In some embodiments, this table is read-only and is fixed at boot time. 
     An “initial virtual resource map” is fixed prior to the booting of the operating system and describes the virtual resources that are available from the point of view of the operating system. The configuration is readable by the operating system. In some cases, it may be desirable to configure a system (from the viewpoint of the operating system) that does not match, one-to-one, with the underlying hardware resources. As one example, it may be desirable for the operating system to have more memory and fewer cores. This can be accomplished by changing the ratio of memory to cores, i.e., by modifying the initial virtual resource map. 
     A “current resource map” is created and maintained by each hyper-kernel instance. This map describes the current mapping between the virtual resource map and the physical resource map from the point of view of each node. For each entry in the virtual resource map, a definition of the physical resources currently assigned to the virtual resources is maintained. Initially (e.g., at boot time), the current resource map is a copy of the initial virtual resource map. The hyper-kernel modifies the current resource map over time as it observes the characteristics of the resource load and dynamically changes the mapping of physical resources to virtual resources (and vice-versa). For example, the definition of the location of the Ethernet controller eth27 in the virtualized machine may at different times refer to different hardware controllers. The current resource map is used by the hyper-kernel to dynamically modify the virtual hardware resource mappings, such as the virtual memory subsystem, as required. 
     Resource Migration Overview 
     Using the techniques described herein, virtualized resources, such as virtual general-purpose processors, virtual memory, virtual networks, and virtual disks can be migrated between physical locations. Virtual interrupts may also be remotely delivered. As explained above, the operating system is provided with information about the virtualized system, but that information need not agree with the physical system. As long as the basic hardware abstractions expected by the operating system are not violated, a virtual machine may look to the operating system just like a physical machine. 
     As described above, in the example software defined server described herein, there is an identical instance of the hyper-kernel running on each node of the cluster that the virtual machine spans. 
     Due to physical hardware boundaries, a physical processor cannot directly address every guest physical address. When a guest physical address needs to be read or written, it is translated into a physical address that the physical processor can access. 
     In one embodiment, this is handled through the physical processor&#39;s second level page tables. When software makes a reference to a guest (virtual) physical address, if the page of memory containing that address is resident on the node that has the processor that generated that address, the address is represented in the second level page table. Automatic address translation hardware will then translate that address to a guest physical address and then to a real physical address as it normally does by using the first and second level page tables, with no performance degradation. However, if the memory address is not present in the second level page table, the hardware cannot completely translate that guest address to a real physical address, and the processor generates an interrupt. The hyper-kernel fields that interrupt and analyzes the request (similar to what an operating system might do when it needs to copy a page that is not memory-resident but is only resident on a backing store). This analysis may result in a request for that page to be sent (migrated) to the node from a different node, or it may result in a decision to migrate that virtual processor to the node that has that page of memory. Page reads and writes may be handled differently. For example, readable pages may be replicated, but a writable page requires additional overhead to remove that page from the L4 cache of other nodes that may have a copy (i.e., invalidation). 
     In one embodiment, to migrate a virtual processor, the hyper-kernel takes a snapshot of the state of the processor (e.g., a continuation, using 6400 bytes of data, or any other amount as appropriate) and sends it in a message over the dedicated interconnect (e.g., Ethernet) to the chosen destination, where the suspended virtual processor may be restored onto another physical processor (e.g., implemented as a hyperthread of a processor core) at the destination node. Saving and restoring processor state may be implemented using mechanisms provided for processors supporting virtualization. Since the program counter has not advanced, the instruction is then restarted. Since the page and the virtual processor are now co-resident, the virtual processor continues running. It is possible that in some cases the instruction generates additional interrupts to access different non-resident pages, but the mechanism that is used may be the same. When the virtual processor migrates, its updated location is recorded (e.g., in the resource maps described above). However, for reliability, perfect location knowledge is not assumed, as the virtual processor may have subsequently re-migrated. 
     In the following example of resource migration, suppose an enterprise supercomputer holds a large in-memory database, larger than can fit into a single node. Part of the database is in a first node, “node1.” Suppose one of the cores on a different node, “node2,” is trying to access data that is owned by node1 and that does not reside locally in a cache on node2. The core on node2 will receive a memory access violation because it is trying to access data that it believes it should be able to access (but cannot). As will be described in more detail below, the exception is handled in the hyper-kernel. 
     One way that the situation can be resolved is by moving the needed area of memory to node2, and then returning control back to the operating system (which, in turn, returns it back to the database system). The software can then proceed as intended (i.e., as if the access violation never occurred). 
     In many cases, there may be one or more other cores in other nodes (e.g., “node3”) that are also trying to access the same area block of memory as needed by node2 above. Node3 might be attempting to access the same data, or it might be accessing different data contained in the memory that was moved (also referred to as “false sharing”). The data could be moved to node3, but if the core on node2 asks for the data a second time, the data would need to be moved back to node2 (i.e., potentially moving the data back and forth repeatedly), which can be slow and wasteful. One way to avoid moving data back and forth between cores is to recognize that both cores and the associated block of data should be co-located. Using the techniques described herein, the memory and the computation can be migrated so that they reside on the same node. Doing so will result in a higher likelihood of faster access to data, and a higher probability of sharing data stored in local caches. 
     When the access violation occurs, an event is triggered (in a system dependent way) to which the hyper-kernel responds. One example of how such an event can be handled is by the invocation of a panic routine. Other approaches can also be used, as applicable. As will be described in more detail below, the hyper-kernel examines the cause of the event and determines an appropriate strategy (e.g., low level transaction) for handling the event. As explained above, one way to handle the event is for one or more blocks of hyper-kernel virtualized memory to be transferred from one node&#39;s memory to another node&#39;s memory. The transfer would then be initiated, and the corresponding resource maps would be updated. A continuation would be built poised to be placed in a local table in shared memory, referred to herein as an “event table,” so that the next thing the continuation does when it is resumed would be to return control to the operating system after the transfer is completed. Alternatively, a decision could be made to move the virtual processor to the node that contains the memory being requested or to move the virtualized memory (and its virtualized memory address) from one node to another. Different decisions for how to handle the stall may be based on the characteristics or context or cause of the stalling event. In various embodiments, the hyper-kernel makes three decisions when handling an event: which (virtual) resources should move, when to move them, and to where (in terms of physical locations) they should move. 
     TidalTree 
     The physical hierarchical structure depicted in  FIG.  2    has an analogous software hierarchy comprising a set of “scheduler objects” (i.e., data structures), each of which has a set of characteristics described below. The scheduler objects form a “TidalTree,” which is an in-memory tree data structure in which each node of the tree is a scheduler object. Each scheduler object corresponds to an element of the physical structure of the supercomputer (but not necessarily vice versa), so there is one node for the entire machine (e.g., node  100  as shown in  FIG.  2   ), one node for each physical node of the system (e.g., node  102  as shown in  FIG.  2   ), one node for each multicore socket on the physical nodes that comprise the entire machine (e.g., node  202  as shown in  FIG.  2   ), one node for each core of each socket (e.g., node  210  as shown in  FIG.  2   ), and one node for each hyperthread on that core (e.g., node  232  as shown in  FIG.  2   ). 
     Each scheduler object s:
         Is associated with a physical component (e.g., rack, blade, socket, core, hyperthread).   Except for the root of the tree, has a parent scheduler object which is partly responsible for directing its operations (as explained in more detail below).   Has a set of children each of which is a scheduler object. This is the null set for a leaf (e.g., hyperthread) node. As explained in more detail below, it is the responsibility of a scheduler object s to manage and assign (or re-assign) work to its children, and indirectly to its grandchildren, etc. (i.e., s manages all nodes in the subtree rooted at s).   Has a work queue, which is a set of continuations (as described earlier).   Has a (possibly empty) set of I/O devices that it also has the responsibility to manage and assign (or re-assign) work.       

     Each node can potentially be associated with a layer of some form of cache memory. Cache hierarchy follows the hierarchy of the tree in the sense that the higher the scheduler object is, the slower it will usually be for computations to efficiently utilize caches at the corresponding level of hierarchy. The cache of a scheduler object corresponding to a physical node can be a cache of memory corresponding to that node. The memory on the physical node can be thought of as a cache of the memory of the virtual machine. 
     Resource Migration—Additional Information 
     The hyper-kernel simulates part of the virtual hardware on which the virtual configuration resides. It is an event-driven architecture, fielding not only translated physical hardware events, but soft events, such as receipt of inter-node hyper-kernel messages generated by hyper-kernel code running on other nodes. 
     As explained above, when an interrupt event significant to the hyper-kernel occurs, the hyper-kernel makes a decision of how to respond to the interrupt. Before control is returned to the operating system, any higher priority interrupts are recognized and appropriate actions are taken. Also as explained above, the hyper-kernel can make three separate decisions: (1) which resources to migrate upon certain events, (2) when to migrate them, and (3) to where those resources should move. 
     In the following example, suppose a scheduler object “s” in a virtual machine is in steady state. Each scheduler object corresponding to a physical node has a set of physical processor sockets assigned to it. Hyperthreads in these sockets may or may not be busy. The physical node also has some fixed amount of main memory and a set of I/O devices, including some network devices. Scheduler object s, when corresponding to a node, is also responsible for managing the networks and other I/O devices assigned to nodes in the subtree rooted at s. The following is a description of how resources can migrate upon either synchronous or asynchronous events. 
     Migrations Triggered by Synchronous Events 
     In the following example, suppose there exists a leaf node scheduler object s, and virtual processor p assigned to s. Leaf node schedule object s is assumed to be executing an application or operating system code on behalf of an application. Assuming the leaf node is not in an infinite loop, p will eventually run out of work to do (i.e., stall) for some reason (e.g., waiting for completion of an I/O operation, page fault, etc.). Instead of allowing p to actually stall, the hyper-kernel decides whether to move the information about the stalled computation to some other node, making one of that other node&#39;s processors “responsible” for the stalled continuation, or to keep the “responsibility” of the stalled computation on the node and instead move the relevant resources to the current node. 
     The stall is thus handled in either of two ways: either the computation is moved to the physical node that currently has the resource, or else the resource is moved to the physical node that has requested the resource. Example pseudo code for the handling of a stall is provided below (as the “OnStall” routine) in the “EXAMPLE ROUTINES” section below. 
     Decisions such as how to handle a stall can be dependent on many things, such as the order of arrival of events, the state of the computation running on the virtual machine, the state of the caches, the load on the system or node, and many other things. Decisions are made dynamically, i.e., based on the best information available at any given point in time. 
     Recording Stalled Computations 
     Stalled computations are recorded in a data structure referred to as a “continuation.” A continuation has a status that can be, for example, “waiting-for-event” or “ready.” A stalled computation gets recorded as a newly created continuation with status “waiting-for-event.” Once the reason for stalling is satisfied (e.g., due to detection of the event), the status of the corresponding continuation is changed to “ready.” Each continuation with status “ready” is stored in a “wait queue” of a scheduler object so that eventually it gets scheduled for execution. In contrast, any continuation with status “waiting-for-event” will not be stored in any scheduler object&#39;s wait queue. Instead, it is stored in the local shared memory of the physical node where the hardware event that stalled the corresponding computation is expected to occur, such as receipt of a missing resource. 
     Additionally, the newly created continuation is associated with the stalling event that caused its creation. This mapping between (stalling) events and continuations awaiting these events permits fast dispatch of asynchronous events (see the “handleEvent” described below). The mapping between continuations and events is stored in a table called “event table” and is kept in the shared memory of the corresponding physical node. Each physical node has its own event table, and an event table of a physical node is directly addressable by every core on that physical node. All anticipated events recorded in an event table of a physical node correspond to hardware events that can occur on that physical node. The scheduler object s mapped to a physical node n represents n, and the event table of n is associated with s. In some cases, several continuations may be waiting on the same event, and so some disambiguation may be required when the event is triggered. 
     Continuations are built using the “InitContinuation” routine. If a decision is made to move the computation, the remote physical node holding the resource will build a continuation that corresponds to the stalled computation and will store it in the remote physical node&#39;s event table. When that continuation resumes, the resource will be available. In effect, the hyper-kernel has transferred the virtual processor to a different node. 
     In the case where a decision is made to move the resource, the node that has experienced the stall requests the transfer of the resource and builds a continuation using InitContinuation and stores it in the local event table. Upon receipt of the resource, the continuation is attached to an appropriate node in the TidalTree, and when that continuation is resumed, the resource will be generally be available and visible. In effect, the virtual resource has been transferred to the node that requested it. 
     Note that by placing continuations in event tables, it is guaranteed that the processor that receives the event will quickly find the related continuations in its local event table. The reason for the stall in the computation will have been satisfied. 
     Having dealt with the stall, the virtual-processor p will in effect be suspended. In between processing the stall and finding a new continuation to resume, p becomes an “anonymous shadow processor,” i.e., a processor with no identity known to the operating system. This shadow processor then looks for a new continuation to resume. An example of this is shown below in the “assignProcessor” routine described in more detail below. 
     Notation 
     Let e be the event that stalled virtual processor p. Assume that e is triggered by local hardware of some physical node n. In particular, assume r is the resource, which caused the stalling event to occur. Resource r could be a block of memory, or an I/O operation, or a network operation. Assume that p is assigned to scheduler object s, which belongs to the subtree rooted at the scheduler object that represents physical node n. 
     On-Stall 
     The following are example details regarding an “on-stall” routine. The migration-continuation function returns true if and only if processor p in node n decides that the resource should not move, i.e., the computation should move. This can be determined by a number of factors such as history and frequency of movement of r between nodes, the type of r, the cost of movement, the number of events in n&#39;s local event table waiting for r, system load, etc. For example, it may not be desirable to move a resource if there is a continuation stored in n&#39;s local event table that is waiting for it. 
     A variety of patterns of events that would benefit from migrations exist. One approach to describing these patterns of events, like access violations, is in formal language theory. Regular (i.e., Chomsky type-3) languages can be recognized by finite state automata. In addition, using a compact and flexible notation, a description of the events that are observed can be made as sentences (or Chomsky sequences) in the regular language, and the recognition modeled as state transitions in the corresponding finite state automaton. When the full Chomsky sequence of events is seen, migration-continuation gets evaluated accordingly: if the finite state automaton accepts the Chomsky sequence, the condition is met, otherwise, it is not met. The length of the minimized finite state machine defines the amount of history that needs to be kept. 
     In various embodiments, all events happen locally, and the hyper-kernel on the physical node receiving the event must handle it—truly synchronous events are not assumed to occur between physical nodes. To coordinate migration strategy between nodes, “messages” are used. Message “sends” are synchronous from a node&#39;s point of view, but message “receives” are asynchronous, in that a processor or shadow processor, in general, does not wait for receipt of a message. When messages arrive, they are dealt with by the hyper-kernel as a virtual interrupt. In one embodiment, the hyper-kernel will not allow a processor to resume a continuation while there are messages waiting to be handled. Therefore, before control is transferred back to the operating system, the queue is checked, and any messages are dealt with prior to the transfer of control back to the operating system. 
     For scheduler object s and continuation c, a cost function cost(s,c) can be used to guide the search up the tree. If multiple ancestors of p have non-empty queues, then p may not want to stop its search at the first ancestor found with a nonempty wait queue. Depending on the metrics used in the optimizing strategy, p&#39;s choice may not only depend on the distance between p and its chosen ancestor but on other parameters such as length of the wait queues. 
     A function, find-best-within(s), can be used to return the “best-fit” continuation in a (non-empty) wait queue of a scheduler object. Examples of parameters that can be considered include: 
     1. Position in the queue 
     2. The relationship between p and the last location recorded in the continuation (the closer those locations are the better it may be for reusing cache entries). 
     3. Performance indicators recorded in the continuations in the queue. 
     The cost and find-best-within functions can be customized as applicable within a given system. 
     Migrations Triggered by Asynchronous Events 
     Examples of asynchronous events include: receipt of a packet, completion of an I/O transfer, receipt of a resource, receipt of a message requesting a resource, etc. Generally, a hyper-kernel that receives an event corresponding to a hardware device managed by the operating system needs to deliver a continuation associated with that event to a scheduler object s. By doing so, s will make this continuation available to an appropriate scheduler object and then ultimately to the computation managed by the operating system represented by that continuation. If, on the other hand, the event is the receipt of a message from a hyper-kernel on another physical node, the hyper-kernel can handle it directly. 
     To simplify explanation, in the examples described herein, an assumption is made that there is only one continuation associated with an event. The procedures described herein can be generalized for the case where multiple continuations are associated with the same event, as needed. 
     In some embodiments, the search for a scheduler object on which to place the continuation starts at the leaf of the tree that built the continuation and then proceeds upward (if the computation previously executed on this node). By doing so, the likelihood of reusing cache entries is increased. 
     Handle-Event 
     Pseudo code for an example handle-event routine is provided below in the “EXAMPLE ROUTINES” section. The cost function, cost(s,c), is a function that helps determine the suitability of assigning c to scheduling object s. The cost function can depend on a variety of parameters such as the size of the wait queues, the node traversal distance between s and the original scheduling node for c (to increase the probability that cache entries will be reused), and the history of the virtual processor, the physical-processor, and the continuation. If the wait queues of the scheduler objects close to s already contain too many continuations, then it may take a relatively longer time until any newly added continuation is scheduled for execution. Example conditions contributing to cost(s,c) are described below, and the conditions can be customized as applicable. 
     Costs 
     Cost functions are used to evaluate options when selecting continuations and scheduling objects. Cost functions can be expressed as the summation of a sum of weighted factors:
 
cost= w   1   f   1   x   1   +w   2   f   2   x   2   + . . . +w   n   f   n   x   n ,
 
     where w i  indicates the importance of the corresponding factor and x i  indicates an exponential. 
     Examples of factors f i  are listed for each of the costs below. Weights w i  and exponents x i  can be determined in a variety of ways, such as empirically and by simulation. Initial weights and exponents can be tuned to various application needs, and can be adjusted by an administrator to increase performance. The weights can be adjusted while the system is active, and changing weights does not change the semantics of the hyper-kernel, only the operational performance characteristics. 
     Examples of the factors that can be considered include:
         Length of time since the last processor evacuated this scheduler object.   Height of the scheduler object in the TidalTree.   Length of the work queue.   Reservation status (i.e., it may be the case that some application has reserved this resource for a specific reason).   Node specification (i.e., the node itself might have been taken out of service, or is problematic, has in some way a specialized function, etc.).   Age of the continuation in the queue.   Last physical processor to run this continuation.   Last virtual processor to run this continuation.   Node on which this continuation was last executing.   The “temperature” of the cache. (A cache is “warm” when it has entries that are likely to be reused. A cache is “cold” when it is unlikely to have reusable cache entries.)   Group membership of the continuation (i.e., the continuation may be part of a computation group, each element of which has some affinity for other members of the group).   Performance Indicators (Hints) and special requirements.       

     EXAMPLES 
     “OnStall” and “assignProcessor” 
       FIG.  8    illustrates an embodiment of a process for selectively migrating resources. In some embodiments, process  800  is performed by a hyper-kernel, such as in conjunction with the OnStall routine. The process begins at  802  when an indication is received that a core (or hyperthread included in a core, depending on whether the processor chip supports hyperthreads) is blocked. As one example, suppose a hyperthread receives a request, directly or indirectly, for a resource that the hyperthread is not able to access (e.g., RAM that is located on a different node than the node which holds the hyperthread). When the hyperthread fails to access the resource (i.e., an access violation occurs), an interrupt occurs, which is intercepted, caught, or otherwise received by the hyper-kernel at  802 . In particular, the hyper-kernel receives an indication at  802  that the hyperthread is blocked (because it cannot access a resource that it has been instructed to provide). In addition to reporting its blocked state, the hyperthread provides information such as the memory address it was instructed to access and what type of access was attempted (e.g., read, write, or modify). 
     At  804 , the hyper-kernel determines whether the needed memory should be moved (e.g., to the node on which the blocked hyperthread is located), or whether the requesting process should be remapped (i.e., the virtual processor should be transferred to a different node). The decision can be based on a variety of factors, such as where the needed memory is located, the temperature of the cache, the workload on the node holding the hyperthread, and the workload on the node holding the needed memory (e.g., overworked or underworked). In some embodiments, the workload of a node is determined based at least in part on the average queue length in the TidalTree. 
     If the hyper-kernel determines that the memory should be moved, the hyper-kernel uses its current resource map to determine which node is likely to hold the needed memory and sends a message to that node, requesting the resource. The hyper-kernel also creates a continuation and places it in its event table. The hyperthread that was blocked at  802  is thus freed to take on other work, and can be assigned to another virtual processor using the assignProcessor routine. 
     The hyper-kernel checks its message queue on a high-priority basis. When the hyper-kernel receives a message from the node it contacted (i.e., the “first contacted node”), in some embodiments, one of two responses will be received. The response might indicate that the first contacted node has the needed resource (and provide the resource). Alternatively, the message might indicate that the contacted node no longer has the resource (e.g., because the node provided the resource to a different node). In the latter situation, the first contacted node will provide the identity of the node to which it sent the resource (i.e., the “second node”), and the hyper-kernel can send a second message requesting the resource—this time to the second node. In various embodiments, if the second node reports to the hyper-kernel that it too no longer has the resource (e.g., has provided it to a third node), the hyper-kernel may opt to send the continuation to the third node, rather than continuing to request the resource. Other thresholds can be used in determining whether to send the continuation or continuing the resource (e.g., four attempts). Further, a variety of criteria can be used in determining whether to request the resource or send the continuation (e.g., in accordance with a cost function). 
     In the event the hyper-kernel determines that the continuation should be transferred (i.e., that the computation should be sent to another node rather than receiving the resource locally), the hyper-kernel provides the remote node (i.e., the one with the needed resource) with information that the remote node can use to build a continuation in its own physical address space. If the remote node (i.e., the one receiving the continuation) has all of the resources it needs (i.e., is in possession of the resource that caused the initial access violation), the continuation need not be placed into the remote node&#39;s event table, but can instead be placed in its TidalTree. If the remote node needs additional resources to handle the continuation, the received continuation is placed in the remote node&#39;s event table. 
       FIG.  9    illustrates an embodiment of a process for performing hierarchical dynamic scheduling. In some embodiments, process  900  is performed by a hyper-kernel, such as in conjunction with the assignProcessor routine. The process begins at  902  when an indication is received that a hyperthread should be assigned. Process  900  can be invoked in multiple ways. As one example, process  900  can be invoked when a hyperthread is available (i.e., has no current work to do). This can occur, for example, when the hyper-kernel determines (e.g., at  804 ) that a continuation should be made. The previously blocked hyperthread will become available because it is no longer responsible for handling the computation on which it blocked (i.e., the hyperthread becomes an “anonymous shadow processor”). As a second example, process  900  can be invoked when a message is received (e.g., by the hyper-kernel) that a previously unavailable resource is now available. The hyper-kernel will need to locate a hyperthread to resume the computation that needed the resource. Note that the hyperthread that was originally blocked by the lack of a resource need not be the one that resumes the computation once the resource is received. 
     At  904 , the TidalTree is searched for continuations that are ready to run, and one is selected for the hyperthread to resume. In various embodiments, the TidalTree is searched from the leaf-level, upward, and a cost function is used to determine which continuation to assign to the hyperthread. As one example, when a hyperthread becomes available, the continuation that has been queued for the longest amount of time could be assigned. If no continuations are waiting at the leaf level, or are outside a threshold specified by a cost function, a search will be performed up the TidalTree (e.g., the core level, then the socket level, and then the node level) for an appropriate continuation to assign to the hyperthread. If no appropriate continuations are found for the hyperthread to resume at the node level, the hyper-kernel for that node contacts the root. One typical reason for no continuations to be found at the node level is that there is not enough work for that node to be fully utilized. In some embodiments, the node or a subset of the node can enter an energy conserving state. 
     Time Sequence 
     For expository purposes, in the example, a “swapping” operation is used to transfer continuations and memory, but in fact that&#39;s not necessary in all embodiments. 
       FIG.  10    illustrates an example of an initial memory assignment and processor assignment. Specifically, region  1002  of  FIG.  10    depicts a hyper-kernel&#39;s mapping between physical blocks of memory (on the left hand side) and the current owner of the memory (the center column). The right column shows the previous owner of the memory. As this is the initial memory assignment, the current and last owner columns hold the same values. Region  1004  of  FIG.  10    depicts a hyper-kernel&#39;s mapping between system virtual processors (on the left hand side) and the physical nodes (center column)/core numbers (right column). 
     Suppose virtual processor P00 makes a memory request to read location 8FFFF and that the hyper-kernel decides to move one or more memory blocks containing 8FFFF to the same node as P00 (i.e., node 0). Block 8FFFF is located on node 2. Accordingly, the blocks containing 8FFFF are transferred to node 0, and another block is swapped out (if evacuation is required and the block is valid), as shown in  FIG.  11   . 
     Next, suppose virtual processor P06 makes a memory request to read location 81FFF. The contents of this block have been moved (as shown in  FIG.  11   ) to node 0. The hyper-kernel may determine that, rather than moving the memory again, the computation should be moved. Accordingly, virtual processor P06 is moved to node 0, and may be swapped with virtual processor P01, as shown in  FIG.  12   . 
     Performance Information 
     Locks and Other Synchronizers 
     In various embodiments, the use of synchronization mechanisms like locks is minimal. Locks are used, for example, to insert queue and remove queue continuations on scheduler objects and to maintain the event table. 
     Code Path Lengths 
     In some embodiments, the (maximum) length of all code paths is determined through a static code analysis, resulting in estimable and bounded amounts of time spent in the hyper-kernel itself. All data structures can be pre-allocated, for example, as indexed arrays. The nodes of the TidalTree are determined at boot time and are invariant, as are the number of steps in their traversal. One variable length computation has to do with the length of the work queues, but even that can be bounded, and a worst-case estimate computed. In other embodiments, other variable length computations are used. 
     Static Storage 
     In various embodiments, all data structures needed in the hyper-kernel are static, and determined at boot time, so there is no need for dynamic memory allocation or garbage collection. 
     Physical Memory 
     All memory used by the hyper-kernel is physical memory, so no page tables or virtual memory is required for its internal operations (except, e.g., to manage the virtual resources it is managing), further helping the hyper-kernel to co-exist with an operating system. 
     Sharing Data and Maintaining Consistency 
     In some cases, e.g., to preserve the conceptual integrity of the virtual machine being presented to the operating system, changes in one node&#39;s data structures are coordinated with corresponding ones in a different node. Many of the data structures described herein are “node local,” and either will not need to move, or are constant and replicated. The data structures that are node local are visible to and addressable by all hyperthreads on the node. Examples of data structures that are not node local (and thus require coordination) include the current resource map (or portions thereof), the root of the TidalTree, and migratory continuations (i.e., continuations that might have to logically move from one node to another). 
     A variety of techniques can be used to maintain a sufficient degree of consistency. Some are synchronous and assume all changes are visible at the same time to all nodes (i.e., “immediate consistency”). Others allow a more relaxed solution and strive for “eventual consistency.” As mentioned above, physical nodes of an enterprise supercomputer are connected via one or more high speed interconnects. Multiple instances of hyper-kernels are interconnected to pass messages and resources back and forth between physical nodes. 
     Updating the Current Resource Map 
     Each physical node n starts off (e.g., at boot time) with the same copy of the physical resource map, the initial virtual resource map, and the current resource map. Each node maintains its own copy of the current resource map. 
     In some embodiments, each entry for resource r in the current resource map has the following: 
     1. A local lock, so that multiple hyperthreads on a physical-node cannot modify r at the same time. 
     2. A node number specifying the node that currently owns the resource. 
     3. A count k of the number of times n has requested r since the last time it owned r. 
     4. A boolean which when set signifies that this node n wants r. 
     5. A boolean which when set signifies that this node has r but is in the process of transferring it, in which case the node number specifies the new owner. 
     In some embodiments, the count k is used to deal with unbounded chasing of resources. If k exceeds a threshold, a determination is made that it is better to move the newly built continuation rather than chasing the resource around the system. 
     The following is an example of a mechanism for initiating migration of resources and receiving resources. Key transactions include the following: 
     1. Node n sends a request for resource r to n′. 
     2. Node n′ receives a request for resource r from n. 
     3. Node n′ may send a “deny” message to n under certain circumstances, otherwise it can “accept” and will send the resource r. 
     4. Node n will receive a “deny” message from n′ if the resource r cannot be sent by n′ at this point in time. It may be that r is needed by n′, or it may be that r is being transferred somewhere else at the arrival of the request. If the request is denied, it can send a “forwarding” address of the node to which it&#39;s transferring the resource. It may be that the forwarding address is n′ itself, which is the equivalent of “try again later.” When node n receives the deny message, it can resend the request to the node suggested by n′, often the new owner of the resource. To avoid n chasing the resource around the system, it can keep track of the number of attempts to get the resource, and switches strategy if the number of attempts exceeds a threshold. 
     5. Node n will receive the resource r if n′ can send the resource. In this case, n needs to schedule the continuation c that was awaiting r, so that c can be resumed. 
     TidalTree Root 
     In some embodiments, one physical node of the set of nodes in the system is designated as a “master node.” This node has the responsibility at boot time for building the initial virtual resource map and other data structures, replicating them to the other nodes, and booting the operating system (e.g., Linux). The master node can be just like any other node after the system is booted up, with one exception. At least one physical node needs to store the root of the TidalTree. The master node is one example of a place where the root can be placed. Updates to the event queue of the TidalTree root scheduling object are handled in each node by sending a message to the master node to perform the update. 
     Over time, the hyper-kernel will adapt and locality will continually improve if resource access patterns of the operating system and the application permit. 
     Continuations 
     As explained above, physical memory addresses across all nodes are not unique. In some embodiments, the inclusion of physical memory addresses in continuations can be avoided by using partitioned integer indices to designate important data structures in the hyper-kernel. In the event an addresses needs to be put into a continuation, care is taken in the move, since the address is a physical address of the source, and bears no relationship with the physical address in the destination. Moving a continuation means copying its contents to the destination node as discussed above, and remapping any physical addresses from the source to the target. 
     Timestamps 
     In some embodiments, access to a free-running counter is visible to all of the nodes. In the absence of this, free-running counters on each node can also be used. Counters in continuations are mapped between the source and destination. 
     Handling of Disks and Persistent Flash 
     Where a needed resource is on disk (or persistent flash), in some embodiments, such resources are treated as having a heavier gravitational field than a resource such as RAM. Accordingly, disk/flash resources will tend to not migrate very often. Instead, continuations will more frequently migrate to the physical nodes containing the required persistent storage, or to buffers associated with persistent storage, on a demand basis. 
     Operating System Configuration 
     There are many ways to configure an operating system. For servers, an assumption can be made that its operating system is configured to only require a small set of resource types from the virtual machine implemented by the hyper-kernel: storage that includes linear block arrays, networks, processors, memory, and internode interconnects. As a result, the complexity of the operating system installation can be reduced. 
     Example Data Structures and Functions 
     The following section provides a list of examples of data structures and functions used in various embodiments. 
     init-continuation: Initializes a continuation when a computation is stalled. 
     assignProcessor: Routine that assigns a new continuation to a shadow processor (if possible). 
     on-stall(r): Stalling event occurs for resource r. 
     migrate-computation(computational-state,r,n): Message to request migration of a computational state to another node n which you hope has resource r. 
     on-interrupt(i): Software interrupt i occurs. 
     handle-event(e): Routine executed when the hyper-kernel is called on to handle an asynchronous event. 
     request-resource(r,n): Request transfer of resource r from node n. 
     initiate-send-resource(r,n): Start sending resource r to node n. 
     on-request-transfer-response(r,n,b): The requested transfer of r from n was accepted or rejected. b is true if rejected. 
     on-transfer-requested (r,m): Receive a request from m for resource r. 
     on-resource-transferred(r,n): Ack of resource r has been received from n. 
     on-receive-resource (r,n): Resource r has been received from n. 
     migration-continuation(r): True if and only if it is better to migrate a continuation than move a resource. 
     parent(s): Returns the parent scheduler-object of scheduler object s. 
     cost(s,c): Used to evaluate placement of continuation c in the wait-queue of scheduler-object s. 
     find-best-within(s): A cost function that returns a continuation stored in the wait-queue of scheduler-object s. 
     conserve-energy: Enter low power mode. 
     resume-continuation(c): Resume the computation represented by c in the processor executing this function at the point. 
     valid(i): Boolean function that returns true if and only if interrupt i is still valid. 
     initialize(best-guess): Initializes cost variable best-guess. 
     insert-queue(s,c): Insert continuation c into the wait-queue of scheduler-object s. 
     return-from-virtual-interrupt: Resume execution that was temporarily paused due to the interrupt. 
     r.owner: Returns the node where resource r is local. 
     r.e: Resource r is awaiting this event. 
     e.r: This event is for resource r. 
     e.continuation: When this event occurs, need to resume continuation. 
     get-state( ): Returns processor&#39;s state. 
     scheduler-object(p): Returns scheduler-object currently associated with processor p. 
     on-request-transfer-response(r,m, response): Response to request of transferring resource r from node m. Response can be either true if “rejected” or false if “accepted.” 
     Example Routines 
     The following are pseudo-code examples of routines used in various embodiments. In the following, functions that start with “on-” are asynchronous events or messages coming in. 
     
       
         
           
               
             
               
                   
               
             
            
               
                  ========================== 
               
               
                  init-continuation(computational-state) 
               
               
                  ========================== 
               
               
                  /* InitContinuation by processor p awaiting resource r with hints h */ 
               
               
                  c = allocate continuation 
               
               
                  c.state = computational-state 
               
               
                  c.last = scheduler-object(p) 
               
               
                  c.state = waiting-for-event 
               
               
                  c.hints = h 
               
               
                  e = allocate event in event-table 
               
               
                  e.resource = r 
               
               
                  e.continuation = c 
               
               
                  return c 
               
               
                  end InitContinuation 
               
               
                  ========================== 
               
               
                  assignProcessor 
               
               
                  ========================== 
               
               
                  /* Once processor p in physical node n becomes a shadow processor, it gives up 
               
               
                 its O/S identity and starts looking for a continuation with which to resume execution. p will look 
               
               
                 for such a continuation in wait-queues as follows: */ 
               
               
                  s = scheduler-object (p) 
               
               
                  initialize (best-guess) 
               
               
                  best-s = nil 
               
               
                  /* traverse upwards, keeping track of best candidate */ 
               
               
                  /* assume there is a locally cached copy of the root */ 
               
               
                  repeat 
               
               
                   guess = cost (s) 
               
               
                   if guess &gt; best-guess 
               
               
                   then 
               
               
                    best-guess = guess 
               
               
                    best-s = s 
               
               
                   s = parent (s) 
               
               
                  until s = nil 
               
               
                  if best-s &lt;&gt; nil 
               
               
                  then 
               
               
                   c = find-best-within (best-s) 
               
               
                   resume-continuation (c) 
               
               
                  else conserve-energy 
               
               
                  end assignProcessor 
               
               
                  ========================== 
               
               
                  on-stall(r) 
               
               
                  ========================== 
               
               
                  /* OnStall is invoked when the hardware detects an inconsistency between the 
               
               
                 virtual and physical configurations. More specifically, node n requests resource r which the 
               
               
                 hardware cannot find on node n. */ 
               
               
                  if migration-continuation (r) 
               
               
                  then 
               
               
                   /* send the computation to node n */ 
               
               
                   nn = owner(r) 
               
               
                   /* node n believes resource is probably at node nn */ 
               
               
                   migrate-computation (r,nn) 
               
               
                  else 
               
               
                   /* request the resource r */ 
               
               
                   c = init-continuation(get-state( )) 
               
               
                   /* insert code here to insert c into the local event-table */ 
               
               
                   request-resource(r, owner(r)) 
               
               
                  assignProcessor  /* At this point, p is an anonymous shadow processor */ 
               
               
                          /* p needs to find some work to do */ 
               
               
                  end OnStall 
               
               
                  ========================== 
               
               
                  on-migrate-computation(computational-state, r,n) 
               
               
                  ========================== 
               
               
                   /* the remote node gets the message from n to receive a continuation. 
               
               
                 Note: c in this case is the contents of the continuation, not the continuation itself */ 
               
               
                  c = InitContinuation /* with the information in the request */ 
               
               
                  c.state = computational-state 
               
               
                  e = insert c into the local event-table 
               
               
                  handle-event (e) 
               
               
                  end on-migrate-computation 
               
               
                  ========================== 
               
               
                  on-interrupt(i) 
               
               
                  ========================== 
               
               
                  /*When a processor p (in subtree of physical node n) is interrupted by i (using a 
               
               
                 very low level mechanism specific to the particular hardware design), p does the following: */ 
               
               
                  while valid (i) 
               
               
                   e = event-table (i)      /* find the event corresponding to i */ 
               
               
                   handle-event (e) 
               
               
                   i = next-queued-interrupt 
               
               
                  end while 
               
               
                  /* resume prior execution */ 
               
               
                  return-from-virtual-interrupt 
               
               
                  end on-interrupt 
               
               
                  ========================== 
               
               
                  handle-event(e) 
               
               
                  ========================== 
               
               
                  /* An event occurred. Move it from the event table to the best scheduler-object. */ 
               
               
                  c = e.continuation      /* find the continuation for event e */ 
               
               
                  event-table (i).clear = true  /* remove the event from the table */ 
               
               
                  e.complete = true      /* mark e as completed */ 
               
               
                  c.state = ready 
               
               
                  /* now find out the best place to put c */ 
               
               
                  s = c.last 
               
               
                  initialize (best-guess) 
               
               
                  /* look for best choice */ 
               
               
                  /* assume there is a locally cached copy of the root */ 
               
               
                  repeat 
               
               
                   guess = cost (s,c) 
               
               
                   if guess &gt; best-guess 
               
               
                   then 
               
               
                    best-guess = guess 
               
               
                    best-s = s 
               
               
                   s = parent (s) 
               
               
                  until s = nil 
               
               
                  insert-queue (best-s,c)/* queue up c in the wait-queue of best-s */ 
               
               
                  end handle-event 
               
               
                  ========================== 
               
               
                  request-resource (r,n) 
               
               
                  ========================== 
               
               
                  /* When a node n needs a resource r owned by node n&#39; the resource is requested, 
               
               
                 but the request may not be satisfied because someone else might have beaten you to request it or 
               
               
                 n&#39; is currently using it. */ 
               
               
                   current-resource-map(r).wanted = true 
               
               
                   request-transfer(owner(r),r)  /* send a request to the owner of r */ 
               
               
                                /* requesting r&#39;s transfer */ 
               
               
                  return 
               
               
                  ========================== 
               
               
                  on-request-transfer-response (r, m, is-rejected) 
               
               
                  ========================== 
               
               
                  /* Now, consider that you are a node getting a response from a previous request to 
               
               
                 a node for a resource r. When the response to this request comes in, it can be accepted or 
               
               
                 rejected. */ 
               
               
                  if is-rejected 
               
               
                  then /* resource has been transferred to m */ 
               
               
                  increment k 
               
               
                  if k &gt; threshold 
               
               
                    then 
               
               
                     /* you don&#39;t want to go chasing around forever*/ 
               
               
                     /* trying to get the resource. Give up */ 
               
               
                     migrate-computation(r,m) 
               
               
                     return 
               
               
                    else 
               
               
                     request-transfer(r,m) /* try again */ 
               
               
                     return 
               
               
                   else 
               
               
                    /* request was not rejected and r is the resource */ 
               
               
                    r.k = 0 
               
               
                    r.wanted = false  /* resource has been moved */ 
               
               
                    r.owner = me   /* set the owner to n (i.e., “me”) */ 
               
               
                    if the resource is memory, 
               
               
                     update the hardware memory map with the new memory 
               
               
                   return 
               
               
                  ========================== 
               
               
                  on-transfer-requested (r,n) 
               
               
                  ========================== 
               
               
                  /* When a resource request for r comes from node n, if transfer in progress to 
               
               
                 owner(r), deny the request */ 
               
               
                  if r.being-transferred 
               
               
                  then 
               
               
                   send-request-response (r, owner(r), true) 
               
               
                  else 
               
               
                   /* transfer of resource is accepted */ 
               
               
                   r.transferring = true 
               
               
                   initiate-send-resource(r) 
               
               
                   if type(r) = memory 
               
               
                   then update local memory map 
               
               
                   send-request-response (r, owner(r), false) 
               
               
                  return 
               
               
                  ========================== 
               
               
                  on-resource-transferred (r,n) 
               
               
                  ========================== 
               
               
                  /* When an acknowledgement comes in that the transfer is complete */ 
               
               
                  r.owner = n 
               
               
                  r.transferring = false 
               
               
                  return 
               
               
                  ========================== 
               
               
                  on-receive-resource(r,n) 
               
               
                  ========================== 
               
               
                  /* Now we receive a message with the requested resource r from n*/ 
               
               
                  r.k = 0 
               
               
                  r.wanted = false/* clear the bit saying that it&#39;s wanted */ 
               
               
                  r.owner = me /* set the owner to n (i.e., “me”) */ 
               
               
                  if the resource is memory, 
               
               
                   update the memory map with the new memory 
               
               
                  send-resource-transferred(r,n) 
               
               
                  handle-event(r.e)  /* the event we&#39;ve been waiting for has occurred */ 
               
               
                  return 
               
               
                   
               
            
           
         
       
     
     Network Attached Memory Using Selective Resource Migration 
     Described above are example embodiments of a hardware and software architecture in which a series of tightly coupled cluster of servers (e.g., the software defined server described herein, also referred to herein as a “TidalPod”) share an aggregated set of resources. These resources include several types, such as processors, dynamic memories, storage, and networks. The aggregation of these resources by a hyper-kernel in such a system allows for the construction of a virtual machine that spans the set of nodes and appears to an operating system and to applications as a single large server. 
     Described herein are techniques for extending aggregation of resources by a hyper-kernel to include memory technologies such as flash memory, PCM (phase change memory), 3D-XPoint, hard disk drives, etc. While example embodiments involving flash memory are described below, the techniques described herein can variously be adapted to accommodate any type of memory technology, as applicable. 
     In one example embodiment, flash memory is organized as a physical array of bytes. Each byte of this array corresponds to a physical memory address in the virtual machine created by the set of hyper-kernels running on each server (also referred to herein as “nodes”) of the cluster. 
     Typically, flash memory is used in two main ways: as solid-state disks (SSD) or as persistent memory in portable electronic devices such as mobile phones and tablets. One example primary usage for flash in servers is to simulate disks. Described herein are techniques that provide additional ways of using flash, for example, as a persistent backup memory to (a) make large memories available for use by applications and databases by extending the size of the memory as perceived by a guest operating system beyond that of the sum of the available dynamic memory of the cluster and (b) as a way to make systems more resilient in the presence of errors. 
     Unfortunately, most system software for servers typically have no mechanism to effectively deal with flash as a first class element of a memory hierarchy, and as a result, this software resorts to using it in a way that it understands well, namely disk storage. 
     Example Use Cases 
     The following are several example use cases supported by the techniques described herein: 
     1. Consider, for example, a software defined server with 5 TB of main memory, and consider a flash memory array of 10 TB. In one example embodiment, the server is configured to have 10 TB of main memory, and to the 5 TB of main memory as a new cache of the 10 TB of flash memory. The use of such a cache accelerates the system. 
     2. In an alternative embodiment, the flash memory array may be used as if it were additional main memory for a total of 15 TB of main memory. 
     3. In another example embodiment, the flash memory can be used to implement a persistent reference memory. For example, if a system detects an impending failure, the contents of the flash memory may be updated with the most recent pages of main memory that it does not already have. In this way, when the system restarts, the persistent memory can be used to help restore the system to the previously saved state (e.g., analogous to what is done when a laptop suspends/resumes). Thus, the main memory can be backed up to persistent storage so that the system can be restarted easily in the event of a crash, error, or other failure. 
     For example, typically, in the event of a failure (e.g., detection of a power failure or system crash), a snapshot of dynamic memory such as DRAM (dynamic random access memory), which is volatile (e.g., the contents of the volatile memory are lost when power is removed), is taken and is written to disk. This can be avoided using the techniques described herein, as a reference copy can be stored or flushed to a network attached memory that includes persistent (e.g., static, non-volatile) memory such as flash memory. Details regarding a network-attached memory will be described in further detail below. 
     4. In another example embodiment, the techniques described herein may be used to save the state of a node and replace it with a different one. This may be done for various reasons, such as upgrading of a node, replacing a failing node, etc. 
     Memory Hierarchy 
     In the example architecture presented above, a large “physical” address space is presented to a guest operating system. From the point of view of each node in the cluster, there are addresses in that “guest physical” address space as seen by the operating system whose guest physical addresses are directly addressed by the processors on that node. In some embodiments, if the guest physical address requested by a processor is not present on that node, a memory access fault is generated by the hardware, and either (a) the memory that contains that address is moved or copied to the node where the processor is located, or (b) the computation the guest processor was executing (i.e., the virtual processor representing the guest processor) is moved to the node where the guest physical memory is located. Examples of resource migration mechanisms and techniques using a hyper-kernel are described above. Either of these two strategies, once completed, enables the computation to be made available to be scheduled and, once scheduled, ultimately begins to run again, as if the memory access fault never happened. 
     In some embodiments, in this model, at any given time, the “guest physical” memory is owned by at most one node in the system. Other nodes may have copies of this memory, but, in some embodiments, in order to maintain strong cache coherency, when a guest processor modifies the page, either all the other copies must be marked as invalid, or otherwise forgotten. 
     In some embodiments, each page of dynamic memory in the system can be a local copy of some other memory, i.e., an alternate flash memory or a networked attached flash memory. This flash memory may be centrally located in a flash appliance accessible to all of the nodes in the cluster, or it may be distributed in parts throughout the cluster, on one or more nodes (e.g., on PCI cards including flash memory on one or more nodes). 
     Such an array of flash memory is referred to herein, without loss of generality, as a “network-attached memory.” The network-attached memory (also referred to herein as a “NAM”) may be composed itself of one more banks of memory distributed amongst the nodes of the cluster. In various embodiments, the network-attached memory can be implemented using memory technologies, such as PCM, 3D-XPoint, hard disk drives, etc., as described above, with the techniques described herein variously adapted, as applicable. An example of a network-attached memory is described in further detail below. 
     The network-attached memory can be used as an additional layer of memory in a TidalPod. In one example embodiment, the network-attached memory can be considered as the “real” home of all memory in the system (e.g., TidalPod). When the network-attached memory is considered or used or configured in this manner, then parts of the network-attached memory may temporarily reside on each node, for example, in the dynamic memory of the node. When considered in this way, the memory in each node can, in some embodiments, be used as a cache of the network-attached memory. 
     An example of a network-attached memory appliance is described in further detail below in conjunction with  FIG.  14   . 
     In various embodiments, the normal memory hierarchy is thus extended from only having cache levels on each node to having cache levels across tightly coupled nodes. 
     One example result of the techniques described herein in utilizing a network-attached memory is that the memory of the network-attached storage can greatly extend the available dynamic memory of the cluster, for example, due to an example requirement that the amount dynamic memory of the cluster be in 1:1 correspondence with the virtual machine being presented to the operating system, not necessarily the sum of the all the physical primary memory on each node. 
     Strong Cache Coherency 
     In some embodiments, in order for the operating system and applications to properly function, the entire memory hierarchy must be strongly cache coherent. In typical hardware, processors maintain strong cache coherency synchronized using one or more levels of hardware caches. When more than one processor exists on a node, using existing hardware, cache coherency is maintained via an on-board interconnect such as AMD&#39;s Hypertransport™ or Intel&#39;s QuickPath Interconnect™. 
     However, those schemes do not extend beyond a single node (i.e., a node having processors and memory). Described herein is a software version of a strong coherency protocol that uses the mechanisms described above to achieve a similar effect. 
     In some embodiments, when a processor running under the direction of an operating system writes to a location in its local memory, if a copy of that memory is stored in a hardware cache of a processor on that node, the processors must cooperate to maintain the strong consistency of the local caches. 
     Using the techniques described above (e.g., the hyper-kernel described above), a similar action can be taken. For example, when a guest virtual processor running under the direction of a guest operating system writes to a location in what it believes to be its local memory, if that memory is in the dynamic memory of the node, care must be taken to make sure that any other node having a local copy of that memory invalidates it&#39;s local copy, so that one true copy is maintained in only one place in the guest virtual machine. When memory is extended to a network attached memory, the same write should have the same effect. 
     Migration to and from Network-Attached Memory 
     Described herein is an overview of how to maintain strong cache coherency in the presence of networked attached memory. Additional details are described below. 
     In some embodiments, when a computation on a node attempts to read from a location that is stored and owned on another node, the resource migration algorithms such as those described above are executed to make sure that the computation and the memory are co-resident on the same node, either by moving the computation, or by moving the memory. 
     Techniques for extending resource migration to incorporate a network-attached memory flash appliance are described below. 
     In some embodiments, the set of nodes in a TidalPod that contain memory is extended to include the flash appliance. The flash appliance is treated is another distinct resource type in the system. The flash appliance can be thought of as similar to the other nodes except that, in some embodiments, it does not have any virtual processors on which computations can be scheduled. In some embodiments, the network-attached memory appliance does not start off with any virtual processors, and never accepts any virtual processors from other nodes in the TidalPod. 
       FIG.  13 A  illustrates an example embodiment of a system in which a network attached memory is used in selective resource migration. In the example shown, nodes  1304  (examples of nodes  458 - 462 ) of TidalPod  1302  communicate with a persistent memory array  1306  (example of a network-attached memory). In some embodiments, the nodes and NAM together form the TidalPod (where the NAM is a specialized node in the TidalPod). In some embodiments, the nodes and the NAM of the TidalPod communicate with each other over an interconnect ( 1308 ). 
     In one example embodiment, each of nodes  1304  includes a motherboard ( 1310 ), where the motherboard may have a number of processors, where each processor may have a number of cores, and each core may have a number of hyperthreads, as described above. In some embodiments, the guest operating system running on the TidalPod views each hyperthread as a processor. 
     In some embodiments, the network-attached memory  1306  is an array of memory ( 1312 ) (e.g., bytes of flash memory). The NAM also includes a processor ( 1314 ) configured to implement a cache coherency protocol, as described herein. Multiple NAMs may be used for redundancy and/or resiliency. In this example, the network-attached memory appliance  1306  is centrally located in a flash appliance accessible to all of the nodes in the cluster. In other embodiments, the network attached memory may be distributed in parts throughout the cluster, on one or more nodes (where examples of portions of the NAM distributed across nodes  1304  are shown at  1316 - 1322 ). 
     Using the techniques described herein, pages of memory may be placed in the NAM, just as pages of memory can be put on any node in the system, as described above (e.g., when performing memory migration). In some embodiments, the network-attached memory appliance communicates with the other nodes in the TidalPod over the interconnect using a cache coherency protocol, which will be described in further detail below. 
       FIG.  13 B  illustrates an example embodiment of a system in which a network-attached memory is used in selective resource migration. In this example, nodes  1352  and  1354  are examples of nodes  1304  and  458 - 462 . As shown in this example, each node has a hyper-kernel. Also shown, is an example embodiment of a memory or cache hierarchy on each node, which include L1, L2, and L3 caches. Each node also includes DRAM, used as an L4 cache. 
     As shown in this example, nodes  1352  and  1354  communicate with each other (e.g., over an interconnect), for example, migrating resources between each other. In this example, the nodes are also configured to communicate with persistent memory array  1356 , which is an example of a network-attached memory. The NAM and the nodes of the TidalPod communicate using a cache coherency protocol, described in further detail herein. 
       FIG.  14    illustrates an example embodiment of a network-attached memory appliance. One example implementation of a NAM is as follows. The NAM ( 1402 ) includes a number of flash memory chips (e.g., memory chip  1404 ) arranged in a 2D (two-dimensional) array on a board. In this example, the memory chips are arranged in banks and rows. The memory chips are connected on a memory bus ( 1406 ). The memory bus allows a processor ( 1408 ) to put out addresses to a memory controller  1410  (e.g., address specifying bank X, chip Y, page Z), which is then configured to return the appropriate page from the specified combination of bank/chip. For example, the memory controller takes the total number of chips, divides that by the number of pages on a chip, divided by the number of rows, etc. to return the appropriate page. 
     In this example, the processor included in the NAM is a coordinator, implemented, for example, as a special limited processor, a network processor, or a protocol processor that is configured or programmed to communicate with other nodes in the TidalPod, for example, receiving messages and providing responses as part of a cache coherency protocol, which will be described in further detail below. In some embodiments, the messages include acknowledgments, retries, etc. that are communicated between nodes of a TidalPod. One example of a message is a message to migrate a vcpu on node n to a node m. Example pseudocode for such a message is provided below: 
     Migrate this VCPU “V” to node M
         Snapshot the state of V (typically a small number of pages of memory (e.g., ˜6400 bytes))   Send “migrate” command with the appropriate number of bytes to contain the state V to M (e.g., 6400 bytes)   Wait for acknowledgment       

     Additional examples of messages and an example message structure will be described in further detail below. In some embodiments, the processor included in the NAM is invisible to the guest operating system. 
     In this example, the NAM also includes metadata  1412 . In some embodiments, the metadata includes page state data used to keep track of which pages are in what state. In some embodiments, the page state data indicates the type of the page (e.g., secondary). In some embodiments, the NAM includes metadata that indicates the state of the page on various nodes throughout the TidalPod. For example, the state of the copy of the page on the NAM is typically a secondary. Copies of the page on other nodes may be secondary, prime, or exclusive. This information can be used, for example, when performing a recovery in a fail over scenario, to restore the state of the TidalPod (e.g., to indicate that a copy of the page on a first node in the TidalPod should be marked as primary (also referred to herein as “prime”), while another copy of the page on a second node should be marked as secondary). 
     In some embodiments, all pages of the NAM are initially invalid (e.g., on startup of a TidalPod). When a page is written into on a node, a copy of the page is sent to the NAM according to a page/cache coherency protocol described in further detail below. Thus, over time, as pages are created and written on the TidalPod, the NAM maintains a recent copy of all those pages that are created and written (e.g., a copy of the dynamic memory). 
     In some embodiments, when a large memory system is started up (e.g., large memory Linux or FreeBSD system), typically a zeroing of the pages is performed. This process may take a long amount of time for large memory systems. In some embodiments, using the techniques described herein, the zeroing of pages can be performed more quickly and more efficiently. For example, the zeroing can be performed in parallel, and in a “lazy” fashion. In some embodiments, pages in the system that have not yet been zeroed are indicated as “dormant” pages. In some embodiments, these dormant pages are not zeroed until the first time that they are used (e.g., allocated and written into or created). A TidalPod system can boot up quickly by using this “parallel lazy zeroing” technique. 
     In some embodiments when the TidalPod system starts off, there are no entries in a second level page table (described in further detail below) for dormant pages, as they have not yet been created, and therefore do not yet exist (i.e., dormant pages do not have any physical representation until they are allocated). In some embodiments, when a dormant page is allocated, it is zeroed. An entry is then placed in the second level page table, which makes the page valid and non-dormant. 
       FIG.  15    is a diagram illustrating an embodiment of allocating a page. The diagram variably applies to dynamic memory and a network attached memory. In the example shown are dormant pages ( 1502  and  1504 ). When a dormant page is allocated, it is zeroed out (e.g., as shown at  1506 ). An entry is then placed in a second level page table, as described above. When the entry is made, the physical address of the page is entered. In some embodiments, mode bits are also entered for the page. The mode bits can indicate that the page is valid, whether the page is writable or read-only, etc. 
     In some embodiments, when a system such as a TidalPod starts up, all pages are empty (e.g., dormant). The guest operating system then begins allocating pages. For example, when a page is allocated, a page number (e.g., 4567) is allocated. Upon allocation, the page is zeroed out, and then its address is put in the second level page table (the page is not made visible until after it has been zeroed out). Now, if an attempt is made to address some pages that are known to the hyper-kernel as 4567, a page of zeroes will be seen. 
     In some embodiments, until allocation, the page does not exist. When a TidalPod with a number of nodes and processors is started up, a majority of the pages are dormant at startup time. As the pages are used, they become valid. This holds true for pages in dynamic memory as well as in the NAM. 
     In some embodiments, the number of pages in the NAM agrees with the number of pages that the guest operating system observes. Other copies of pages (e.g., shadow copies) of the page may exist (e.g., in a page cache), but, in some embodiments, they are not made visible to the guest operating system. There need not be a one-to-one correspondence between the hyper-kernel(s) and the NAM. However, in some embodiments, there is a one-to-one correspondence as seen by the guest operating system. 
     In a typical operating system, an operating system runs on a bare metal machine. As described above, using the techniques described herein, the bare metal machine is replaced with a distributed hyper-kernel, which gives the operating system the impression that it is running on a bare metal piece of hardware. This is supported by second level page tables, which exist in processors on the nodes in the TidalPod. If there is not an entry in the second level page table for a page of memory, a fault will occur when performing hardware address translation. As described above, based, for example, on cost functions, the virtual processor can be moved to the page with the memory, or the memory can be moved to where the virtual processor resides. In some embodiments, when the memory is moved, not only are the contents of the memory copied over, but the second level page table at the destination (node) is also updated. Thus, when a process on the node attempts to access the page, another fault will not occur. This provides, to the guest operating system, the appearance of a single large address space, which does not exist in hardware, but is defined and supported in software using the techniques described herein. If the request for a page is rejected (e.g., based on a message that comes into the cost function), then the processor moves to the location of the page (or both the virtual process and the page are moved to another node—i.e., post-condition, they are co-resident). 
     In some embodiments, when a page is requested, the page is requested from its primary location. That page is then marked as secondary, while the page sent to the requesting node is marked as exclusive or prime on the requesting node. 
     Consider the following example. Suppose that a TidalPod includes, for illustrative purposes, a centralized network-attached memory, and two nodes, node1 and node2. In some embodiments, when a strategic decision is made to have node1 request the transfer of memory (i.e., page of memory) whose latest copy is on node2, node2 first sends an invalidation of that memory to the network-attached memory, along with an indication that node1 is most likely the place or location that the latest copy can be found, and then satisfies node1&#39;s request by sending a copy of that memory to node1. In some embodiments, the memory now on node1 is updated as dictated by the various computations resident on node1, and then when node1 must transfer that memory somewhere else, node 1 may also update the network-attached memory to where the current version of that memory may be found. In some embodiments, the system (TidalPod) continues to work or function whether or not the update is made to the NAM, since knowledge of the system may not be perfect. In some cases, due to asynchronicity, memory pages may need to be “chased down” and searched for, because they may not be in a location where they were expected to be (e.g., according to a resource map). For example, at one point in time, the page may have been at a given location, but may no longer be there when the search for the page is made. 
     If another node needs a copy of that memory (page), a copy is sent to the requesting node, and a copy of that memory is also sent to the networked attached memory. 
     In some embodiments, when memory is modified on a node, the memory becomes exclusive to that node. In some embodiments, all other copies must become invalid, including any copy on the networked attached memory. 
     In some embodiments, on a periodic basis, the network-attached memory may request a secondary copy of every memory page. On planned or unplanned shutdown, if there is sufficient time, the network-attached memory may request exclusive ownership of all pages, after guest virtual processors are stopped. 
     In this way, the network-attached memory always contains a sufficiently recent copy to preserve the semantics of strong coherency. 
     Description of State Transitions and Cache Coherency Protocol 
     As described above, the network-attached memory (“NAM”) may be another node in a TidalScale Pod (centralized node or distributed across other nodes in the pod), with the exception that no virtual processors (also referred to herein as “vcpu&#39;s”) can be started on it, and to which no vcpu&#39;s can ever migrate. 
     In this example, the NAM is transactional in nature, given that no vcpu&#39;s are present on the NAM. There is no requirement or need to keep the contents of the NAM up to date in real time, except, in some embodiments, for well-defined synchronization points defined by the execution of the guest software (e.g., “I need this page in order to progress the computation and until I get it I cannot progress the computation”). Thus, reads and writes to the NAM can be performed in a “lazy” manner. As long as requests for pages and requests to update pages are satisfied, in order, consistent von Neumann semantics can be maintained or preserved, without having to perform then in real-time. It may also be the case that some processor families have additional constraints that must be satisfied, for example, Intel&#39;s “memory storage order” constraints. 
     In some embodiments, the logic to request a page, update a page, migrate a processor, invalidate read-only copies of pages, etc. is handled by vcpu&#39;s or housekeeping threads. Because the NAM does not have those, the NAM does not have to worry about these operations. 
     An example embodiment of a transactional structure is now described. 
     Pages on any node can be valid, or invalid. The validity/invalidity of a page refers to whether an entry to that page exists in some page table on a node. If they are valid, they correspond to an address that the guest operating system perceives to be a physical address, but is in reality a guest virtual address when viewed by the hyper-kernel. 
     A valid page p resident on a node n may be in one of several states: Prime (or primary), or Exclusive or Secondary. 
     1. If p is marked Prime, it is “read-only” and n is said to “own” p. 
     2. Nodes other than n may have copies of p, which are marked or called Secondaries. In some embodiments, if there is a secondary, it can be validly assumed that there is a prime somewhere else in the TidalPod. Similarly, if a node has a prime page, then it can be assumed that there secondaries of the page elsewhere in the TidalPod. In some embodiments, the number of secondaries is maximized so that when attempts to read those pages are made, the page data is already resident on the requesting node, minimizing time spent accessing the page data. 
     One example of where a secondary may be desired is when handling a set of pages that hold the code of an operating system (e.g., guest operating system). Because the code for the operating system is constant and does not change, it would be inefficient if the processors running the operating system were to fetch pages for the operating system (as this may result in a stall while the processor is waiting for the requested page). Instead, to improve efficiency and reduce stalls, secondaries can be used, where as many pages of the operating system are replicated as possible. By reducing stalls, overhead in the system is also reduced, leading to increased efficiency of the system. 
     Similar optimizations and efficiencies can be performed for other kinds of programs, such as those with read-only data (where pages of the read-only data are copied as secondaries to read only nodes). In some embodiments, no distinction is made between code pages of an operating system or an application that are read-only pages of memory. 
     As another example, secondaries can be used that have large amounts of data that do not change very often. If the memory is available to do so, as much of the read-only data can be replicated as possible to improve efficiency and reduce stalls. 
     3. If p is marked Exclusive on n, the page can only exist on n, there can be no other copies, and the page can be read and written into (“read-write”). In this case, there are no secondaries for p. 
     In some embodiments, before a page is made exclusive, an invalidation operation is performed to invalidate all other existing copies of the page. This can be used to guarantee evaluation order in existing architectures. The invalidation operation can be performed by sending out messages to all other nodes, requesting that they invalidate their copy of the page. When responses (e.g., acknowledgments) to those requests are received, the receipt of all those responses indicates that there are no other locations at which the page exists. The guest operating system can then start up again and write to the page. When the write is completed, other pages may want to have a copy of the page, and in some embodiments, a snapshot of the page can be taken and used to create new secondaries that have the updated information for the page. Thus, through the use of secondaries, it will appear to the guest operating system that the pages are local. 
     When a vcpu on a node m (m≠n) requests access to p from n, if that page is Prime or Exclusive, the page p currently on n is marked Invalid, and a copy of the page is then sent to m which marks p as Prime. In some embodiments, as an optimization, the vcpu on node m may mark the page p as Exclusive if the vcpu on node m knows that is what is needed. 
     In some embodiments, if a node has a page that is prime or exclusive, then when it receives a request to send prime or exclusive writes to that page it is converted on that node to a secondary. The right to write into that page is then transferred to the node that is requesting the page. This is an optimization that may be performed based on an assumption that the node would not be requesting the page unless the node were going to write to the page. This saves a transaction in the protocol having to be performed, increasing efficiency. 
     In some embodiments, if node m requests access to p from n, then node n marks its copy of p as secondary. The page p is then sent to node m. If node m marks its copy of p as exclusive or prime, then node n&#39;s copy of page p is invalidated. 
     In some embodiments, if a page p on node n is Prime, and is to be written into, all secondary copies must be made invalid, and only after receiving an acknowledgement that this has been accomplished, n marks p as Exclusive. For example, in some embodiments, the page cannot be written into until it is known that node n is the sole writer—that is, the page cannot be written into until the page is in the exclusive state, where the page cannot be in the exclusive state until all acknowledgments have been received indicating that there are no other secondaries (i.e., all secondaries have been invalidated). In some example implementations, this can be optimized. For example, the node that is primary for this page can initiate the invalidations. In some embodiments, the invalidations include instructions that the acknowledgements should be sent to the requestor (who is about to become primary) NOT to the current primary. In some embodiments, the requester must collect all the acknowledgements before the page can be accessed. In this way, the invalidations can proceed safely in parallel with the transfer of the page. In summary, in this example optimization, the primary initiates the invalidations, but the requestor completes the invalidation process. 
     The following are additional details regarding invalidations. In some embodiments, the TidalPod includes first level page tables, which perform hardware translation from the user space (e.g., guest program run in user space) to what the guest operating system believes is its physical space (i.e., the first level page table mapping translates virtual addresses into what the guest OS believes to be physical addresses). As described above, what the guest OS believes to be physical addresses are guest physical addresses managed by the hyper-kernel (e.g., hyper-kernel host addresses), which then go through another level of page address translation, in hardware (e.g., via a second level page table), where the guest physical addresses are converted or translated into true physical addresses of the pages. In some embodiments, a page is invalidated by erasing it from the second level page table. Garbage collection can then be run, or memory can be returned to a free pool, etc., as the nodes can no longer access the invalidated page of memory. 
     After this, all write operations to a page marked Exclusive will not generate any stalls, since they can be locally read and written into on the node, and no other copies exist (e.g., pages invalidated by erasing them from the second level page table, as described above). 
     In some embodiments, the NAM abides by the same protocol described above. As with any other node in the TidalPod, the NAM also has valid and invalid pages. For example: 
     1. All pages in the NAM start off as invalid. In some embodiments, if a page becomes valid, it is marked as secondary, because the page on the NAM cannot be written into (only read from). 
     2. Before a vcpu v on a node n writes to a page it must invalidate all copies of P elsewhere, including the NAM. Therefore, if the NAM has a secondary for p, p must be made invalid on the NAM, and send an acknowledgement that it has been made invalid before p can be updated, just as any other node that has a copy of p must do. 
     3. If a node n responds to a read request coming from a node m, m n, for a page p, where p is marked Primary or Exclusive, n marks p as secondary, and sends the page p to m, and also at (roughly) the same time, n also sends it to the NAM, which marks it as valid and secondary. Thus, in some embodiments, if a node marks a page as exclusive or primary, a copy of the page is sent to the network attached memory (and marked as secondary) so that the network attached memory has a valid copy of that page (i.e., if a node has a prime copy of the page (e.g., after writing to the page), the NAM will have a secondary copy that is valid after the write). After the page is received by m, m marks the page as Primary. As before, if the page transitions from Primary to Exclusive as would be the case for a remote write request, the Secondary copy on the NAM must be made invalid. If it is known ahead of time that the page will be marked Exclusive, the step of sending the page to the NAM can be skipped (as it would ultimately be invalidated anyway). 
     In this way, the NAM becomes a collection over time, of copies of all valid pages in the TidalPod. In some embodiments, each time a page is updated, an update is made to the network attached memory. Thus, over time, after a period of quiescence, the network attached memory will have a valid copy of every page in the TidalPod. Thus, even if power to the system goes out, an image of the memory will reside in the NAM. As another example, when booting, if a clean shutdown was not performed, then snapshot of the memory may be used to help restore the previous state of the system prior to the shutdown. 
     Finally, there is no restriction on the number of NAM&#39;s in the TidalPod. For example, there may be multiple NAMs in the system (e.g., for resiliency and/or redundancy). In some embodiments, several network attached memories can be shared between different TidalPods. As one example, a NAM appliance can be unplugged and replicated for the purposes of resiliency and/or redundancy. 
     For example, when data is pulled off of storage (e.g., extracted from files), it may be transformed into a local representation usable by an application or database that will operate on the data. In one example use case, once the data has been transformed and is replicated to a NAM, the NAM can be unplugged and moved to another TidalPod. Thus, the transformed data can be immediately used by the other TidalPod, saving the expensive cost of transforming the raw data first. For example, the initial load of data (e.g., from a database, streamed over the internet, read from a disk, etc.) that must then be transformed to be usable by an application running on a TidalPod that needs the data can be expensive, especially for very large files. This initial load/transformation can be skipped using the steps described above (e.g., needs only to be performed once, where the transformed data can be replicated or moved to other systems by moving the NAM). 
     Example State Transitions 
     The following is a table of example state transitions: 
     
       
         
           
               
               
               
               
             
               
                 TABLE 1 
               
               
                   
               
               
                   
                 Old page 
                   
                   
               
               
                 Operation 
                 state 
                 Action 
                 New Page state 
               
               
                   
               
             
            
               
                 local read 
                 invalid 
                 request page from primary 
                 primary 
               
               
                   
                   
                 or exclusive 
                   
               
               
                 local read 
                 secondary 
                 perform the read 
                 secondary 
               
               
                 local read 
                 primary 
                 perform the read 
                 primary 
               
               
                 local read 
                 exclusive 
                 perform the read 
                 exclusive 
               
               
                 local write 
                 invalid 
                 request write from primary 
                 exclusive 
               
               
                 local write 
                 secondary 
                 request write from primary; 
                 exclusive 
               
               
                   
                   
                 invalidate 
                   
               
               
                 local write 
                 primary 
                 invalidate; write 
                 exclusive 
               
               
                 local write 
                 exclusive 
                 perform the write 
                 exclusive 
               
               
                 receive read 
                 invalid 
                 forward to primary 
                 invalid 
               
               
                 receive read 
                 secondary 
                 forward to primary 
                 secondary 
               
               
                 receive read 
                 primary 
                 send the page 
                 secondary 
               
               
                 receive read 
                 exclusive 
                 send the page 
                 invalid 
               
               
                 receive write 
                 invalid 
                 forward to primary 
                 invalid 
               
               
                 receive write 
                 secondary 
                 forward to primary 
                 invalid 
               
               
                 receive write 
                 primary 
                 send the page 
                 invalid 
               
               
                 receive write 
                 exclusive 
                 send the page 
                 invalid 
               
               
                 invalidate 
                 invalid 
                 Ack 
                 invalid 
               
               
                 invalidate 
                 secondary 
                 invalidate; ack 
                 invalid 
               
               
                 invalidate 
                 primary 
                 N/A 
                 invalid 
               
               
                 invalidate 
                 exclusive 
                 N/A 
                 invalid 
               
               
                   
               
            
           
         
       
     
     In the above example of Table 1, when a read request for an exclusive page is received, the page is sent to the requestor and the stage of the page (on the node that receives the read request) is transitioned to invalidate. In some embodiments, the transition to invalid takes into account an optimization, as described above, in which an assumption is made that the page that has been requested will be written into, and the page on the node that received the request will ultimately need to be invalidated anyway. In other embodiments, in response to a receiving a read request, the node that receives the read request transitions the state of the page from exclusive to secondary. 
     In some embodiments, all nodes of a TidalPod, including the NAM, abide by the above example state transition diagram for the cache coherency protocol described herein. In the case of the NAM, which does not perform computations (e.g., writes on the pages that it holds), the NAM abides by the subset of transitions associated with passive operations. 
     Example Messages 
     The following are example of messages used in the internode cache coherency protocol described above: 
     In some embodiments, each subsystem has its own message types. The following are examples of subsystems in a TidalPod (e.g., scheduler subsystem, I/O subsystem, migration subsystem, etc.):
         TS_NET_SCHEDULER,   TS_NET_MEMORY,   TS_NET_IO,   TS_NET_MIGRATION,   TS_NET_REMOTE,   TS_NET_REMOTE_INIT,   TS_NET_IOAPIC,   TS_NET_CONTROL,   TS_NET_BENCHMARK,   TS_PAGE_BENCHMARK,   TS_SYNC_CLOCK,   TS_NET_MMIO,       

     In some embodiments, the TS_NET_MEMORY subsystem has the following example message types:
         VMEM_UNKNOWN=0,   VMEM_BUILD_GCPP=1, // message to node to build its cpp (coherency page protocol).   VMEM_BUILD_COMPLETE=2, // message that the build is complete   VMEM_MOVE_OWNER=3, // message carrying page from owner to owner   VMEM_COMPLETE_PAGE=4, // message used to signal page state change complete   VMEM_OK2SEND=5, // message used to broadcast available room for pages   VMEM_COLLECT_PAGE_BCST=6, // message used in isolating protocol (path where the collect message is broadcast).   GTHREAD_COLLECT_BCST=7, // message used to collect guest thread metadata   GTHREAD_COLLECT_BCST_ACK=8, // message used to respond to a guest thread collect message       

     Example Scenario 
       FIG.  16    illustrates an example embodiment of internode communication according to a cache coherency protocol such as that described above (e.g., according to the state transitions in Table 1, above). 
     In this example, suppose that a TidalPod includes nodes N 0  ( 1602 ), N 1  ( 1604 ), and N 2  ( 1606 ), as well as NAM  1608 . 
     In an initial state  1610 , node N 2  has the primary version of page p (indicated with a “P”). Node N 1  includes a secondary copy of page p (indicated with an “S”). Node N 0  does not have a copy of page p (e.g., invalid, as indicated by “X”). NAM  1608  may include either a secondary copy of page p (e.g., the system has been in use and the NAM has a reference copy of page p), or the page may be invalid on the NAM (e.g., at startup). 
     In a next step  1612 , node N 0  wishes to perform a local read of page p. Because it does not have a valid copy of page p, it performs a remote read request for the page from the node that has the primary copy of page p, in this case, node N 2  (e.g., based on resource maps). 
     In response to the remote read request, node N 2  marks its copy of page p as secondary (transition from primary to secondary), and sends the page p to node N 0 . Node N 2  also sends page p to the NAM, which marks its received copy (e.g., snapshot) of page p as valid and secondary. After node N 0  receives page p, node N 0  marks its copy of page p as primary. 
     In some embodiments, node N 0  receives a copy of page p that is marked as secondary. In other embodiments, and as shown in this example, in order to minimize the number of transactions (e.g., messages that are being communicated among nodes), node N 0  marks its copy of p directly as prime, under the assumption that node N 0  is requesting the page with the intention of writing into it. The page p on node N 2  is then marked as secondary. If the page p was invalid on the NAM, the page p is also sent to the NAM, where it is marked as a secondary. If the page exists on the NAM, it remains secondary. The copy of page p on node N 1  remains secondary. 
     In a next step  1614 , node N 0  performs a local write into its primary copy of page p. Before node N 0  can write into or update its copy of page p, all secondary copies of page p are made invalid. For example, invalidation messages are sent to N 1 , N 2 , and the NAM (i.e., other nodes in the TidalPod that have secondaries of p are requested to invalidate their copies of page p). After node N 0  receives acknowledgments from N 1 , N 2 , and the NAM indicating that they have invalidated their copies of p (i.e., no other copies of p exist in the TidalPod), where the invalidation on those nodes is indicated by the symbol “X”), node N 0  can then mark its copy of page p as exclusive (e.g., transition from primary to exclusive) and write into its copy of page p. 
     In the above example, node N 0  first performed a remote read request at  1612  and then subsequently performed a local write at  1614 . If it is known ahead of time that the page on node N 0  was to be marked exclusive (e.g., similar to a remote write request), the step of sending the page p to the NAM from node N 2  in  1612  may be skipped. 
     Now suppose at  1616  that N 1  attempts to perform a local read of page p. However, its copy of page p is invalid. Node N 1  then requests the page p from node N 0 . In response to the remote read request, node N 0  transitions its copy of page p to secondary (or in some embodiments invalidates its exclusive copy of page p if the requestor is expected to write into p), and sends page p to node N 1 . Node N 1  marks its received copy of page p as primary. Node N 0  also sends a copy of page p to the NAM, which marks its copy of page p as valid and secondary. 
     As shown in the example above, a page may be in different states on different nodes of a TidalPod. In some embodiments, a parallel data structure is used to maintain the metadata about the state of pages on various nodes in the TidalPod. In some embodiments, the structure is maintained such that re-computation of the state of pages across nodes in the TidalPod need not be performed. 
     In this example embodiment, the NAM is passive, and is not configured to request pages and write into them. The NAM, over time, builds up a set of secondary pages. If a node attempts to write into a page, the copy on the NAM becomes invalid. 
     In some embodiments, the NAM is notified, using the cache coherency protocol, of any transition from primary or exclusive to secondary (i.e., the NAM is copied on those transitions). Thus, the NAM is updated any time a page transitions from being exclusive or prime to secondary. If a page p on a node N becomes secondary, then the page on the NAM must also be updated to become a secondary. Thus, the NAM preserves the state of a set of pages that it believes are valid, and, in some embodiments, the NAM has the “ultimate” reference copy (or an approximation) of the official memory of the TidalPod. 
     In some embodiments, if it is detected that the TidalPod is failing or coming down, then the NAM makes read requests for copies of all primary pages in the TidalPod, which the NAM does not already have, which will be marked as secondary on the NAM. Given sufficient time, this will result in the NAM having all secondary copies of the pages on the nodes in the TidalPod (and becoming, for example, an “ultimate reference copy”). As one example, suppose that a failure or error is detected. The NAM searches through all of its pages to determine which pages are invalid. The NAM attempts to make them valid by making a read request for copies of those pages from other nodes in the TidalPod. If copies are obtained from other nodes, then the NAM updates its invalid page with the obtained copy of the page, which is then made valid and secondary on the NAM. 
     In some embodiments, the NAM includes backup systems, such as batteries on the motherboard that allow the NAM to continue to function, for example, during a system shutdown or loss of power. This provides the NAM with time to request secondary copies of pages on other nodes in the TidalPod (to transition from an invalid page to a valid secondary). This allows the NAM to complete itself in a failure situation. 
       FIG.  17    is a flow diagram illustrating an embodiment of a process for maintaining cache coherency in the presence of a network attached memory. In some embodiments, process  1700  is executed by a network attached memory such as network attached memory appliance  1306 . In some embodiments, process  1700  is performed according to a cache coherency protocol such as that described above. The process begins at  1702 , when a message is received at a network attached memory. The message is received based on an operation to be performed with respect to a page on a node (e.g., a TidalPod). In some embodiments, the node is included in a plurality of nodes with which the network attached memory communicates. In some embodiments, a virtual machine is created by a set of hyper-kernels running on each node in the TidalPod. An operating system may then be collectively run on the plurality of physical nodes. In some embodiments, the (guest) operating system is transparently run. For example, as described above, by using hyper-kernels, the operating system is given the impression that it is running on a bare metal piece of hardware (while being collectively run on a plurality of physical nodes as in a TidalPod), where modifications to the operating system need not be made. 
     As described above, in some embodiments, the network attached memory is organized as a physical array of bytes. In some embodiments, each byte of the array corresponds to a physical memory address in the virtual machine. 
     In some embodiments, the network attached memory is a centralized appliance. In other embodiments, the network attached memory is distributed across the nodes in the TidalPod. At  1704 , an action is performed by the network attached memory based on the received message. 
     As described above, in various embodiments, operations that can be performed with respect to pages of memory on nodes include reads and writes (which may be local or remote). Based on the type of operation to be performed, different types of messages can be sent out. 
     For example, if the operation is a request to write into the page, invalidation requests (examples of messages at  1702 ) are sent to and received by other nodes as well as the network attached memory to invalidate their copies of the page to be written into. The network attached memory is configured to then invalidate its copy of the page. The network attached memory is also configured to return an acknowledgment indicating that its copy of the page is now invalid. The write operation can then proceed after receiving acknowledgments to the invalidation requests. 
     If the operation is, for example, a remote request from a node to read a page, the page (e.g., copy or snapshot of the page, where the message at  1702  includes the copy of the page) is sent to the requesting node as well as the network attached memory. The network attached memory is then updated with the copy of the page (e.g., the network attached memory stores the copy of the page at  1704 ). 
     Other examples of operations, actions, and page state transitions are described above in conjunction with Table 1. 
       FIG.  18    is a flow diagram illustrating an embodiment of a process for zeroing pages. In various embodiments, process  1800  is performed by a node and/or a network attached memory included in a TidalPod. The process begins at  1802  when an indication to allocate a dormant page is received. In some embodiments, a dormant page is a page of memory that has not yet been zeroed. In various embodiments, the page of memory includes a page in DRAM (e.g., on a node in a TidalPod), a page in a flash memory (e.g., non-volatile memory in a network attached memory), or any other appropriate page of memory. At  1804 , the dormant page is allocated. At  1806 , the allocated dormant page is then zeroed. In some embodiments, the zeroing of the dormant page is performed in parallel with the allocation of the dormant page. An entry may then be placed in a second level page table, where the page becomes valid and non-dormant. As described above, by using dormant pages (where pages can be tagged as dormant) performing zeroing after (or at the time of/in parallel with) allocation increases the boot up speed of a system, as the zeroing is not done at startup, but in a “lazy” fashion at the time that the page is used. 
     Described above are techniques for attaching a network-attached memory (e.g., flash appliance) to a tightly coupled cluster of nodes in which the total memory of the cluster represents the memory of a virtual machine supporting strong memory coherency. In some embodiments, as described above, the network-attached memory within the cluster is the reference memory of the virtual machine. 
     Fast Boot 
     Further details regarding parallel zeroing (e.g., boot time initialization) and dormant pages are described below. 
     A server operating system, such as Linux, needs to assume the server on which its running is operating correctly. Sometimes this means that memory needs to be tested, and is usually initialized to a known state. On conventional servers, when there is a large amount of memory, this may take a substantial amount of time. For example, for a terabyte of DRAM, this initialization may take on the order of 30-45 minutes or more. If the operating system is performing that initialization, the operating system will typically not run normal applications until this initialization is complete. 
     The time to do such a memory test is directly proportional to the amount of memory as seen by the operating system. Further, it is usually the case that the test and initialization is single-threaded, and is performed by a single processor. Yet most servers today have multiple processors, and it would be beneficial for memory testing to be performed in parallel. 
     Consider the situation where the operating system is not running directly on hardware, but within a virtual machine such as a software defined server as described herein. As described herein, TidalScale&#39;s hyper-kernel is able to present a massive amount of memory to an operating system, and so booting up that operating system could potentially take a lot of time unless it is performed differently as compared to existing bootup techniques. 
     As described herein, the distributed hyper-kernel runs below a conventional operating system (i.e., the operating system runs in a virtual machine built by a cooperating set of hyper-kernels), so there is a place to optimize this initialization without modifying the operating system, namely the virtual machine. Further, as described herein, while the operating system relies on a first level of dynamic address translation, the hyper-kernel makes use of a second level of dynamic address translation provided on modern microprocessors with hardware support for virtualization. This second level may be used to help speed up the initialization process. 
     Dynamic Address Translation 
     Additional details regarding dynamic address translation are described. Virtualization may be supported in hardware. Described below is an example process of an application accessing memory. An application typically references memory in a virtual address space. When a virtual address is referenced by a processor running the application, the processor translates that virtual address to a physical address by consulting a page table that maps application virtual addresses to physical addresses. Page tables are maintained by an operating system. An operating system running on a virtual machine is referred to herein as a guest operating system, and the page tables the guest manages become what is referred to herein as first-level page tables. 
     In a virtual machine, there may be multiple levels of virtual to physical address translation. Just as an operating system takes an application virtual address and translates it to what it believes is a physical address, a virtualization system may take that guest physical address and convert it to a real physical address. In existing systems, two levels of address translation are typically supported (e.g., via the first and second level page tables described above), but may be extendable in that stacked virtual machines using similar techniques may be implemented. 
     Parallel Memory Initialization 
     At node initialization time, multiple physical CPUs on each node can begin zeroing all the pages in a node. This may be independent of the guest operating system which spans all the nodes taken together. In some embodiments, each node&#39;s hyper-kernel code does not need to wait for the completion of this activity. Once a page is zeroed it may be put into a queue (referred to herein as the “pre-zero” list) of already zeroed pages. Thus, the hyper-kernel on each node can start working “instantly.” 
     In some embodiments, parallel zeroing uses “unused” physical cpu capacity on each node, and is especially useful because the guest OS typically does not use all the VCPUs as it starts up. Furthermore, due to the design of chip architectures such as the Intel Xeon, an individual processor core cannot zero memory at full memory speed due to potential conflicts with multiple cores trying to write to the same bank of memory. Typically, between two and four cores on a chip are required to saturate the memory write access on a processor. Thus, memory initialization within a node can proceed in parallel with operating system initialization. In addition to using multiple processors on a node to initialize memory, processors on other nodes proceed in parallel as well, such that there is parallel processing in two dimensions. Further details regarding parallel memory initialization are described below. 
     Additional Details Regarding Dormant Pages 
     In some embodiments a special type of entry is created in the second level page tables that corresponds to pages that have not been made manifest (i.e., they don&#39;t really exist). The pages represented by these page table entries are referred to herein as “dormant” pages. That there is no memory actually allocated behind these pages speeds up the memory initialization process. 
     Dormant pages may be referenced by an operating system and application software in the same way as normal pages. However, once referenced, the dormant page may then be converted to a real page of memory via the stall process described above that is used to detect references to pages that are not local to a processor on a node making the reference. 
     In some embodiments, at hyper-kernel initialization time, the hyper-kernel marks all guest pages as dormant in its data structures managing and tracking placement of pages. The page tables in the hyper-kernel corresponding to dormant pages are empty in that they do not point at real, physical memory. 
     In some embodiments, conversion of dormant pages to “real” pages is done when the page is first read or written. In some embodiments, this conversion first looks to the pre-zero list to find a page that is pre-zeroed on the node where the conversion needs to take place. During guest OS initialization, this will usually succeed, but if there are no remaining pre-zeroed pages, in some embodiments, the hyper-kernel, acting on behalf of a vcpu, must wait until the node has one. Further details regarding obtaining of zeroed-pages will be described below. Other mechanisms in the hyper-kernel, such as the TidalTree (as described in further detail above), which may invoke memory capacity maintenance services, ensure that memory usage never reaches 100% of node capacity. 
     It may not be possible to know where a vcpu will be when it stalls for a missing page and discovers that the page is dormant. This means that it may not be possible to know a priori which nodes will provide pages for the guest from their pre-zeroed list, since it may not be possible to know on which node a vcpu will be when it needs that page. 
     Dormant pages may provide additional capabilities, because they act as placeholders in the guest physical address space for guest virtual memory that has not yet been touched, or, and in a special case, pages that the guest has zeroed out. Dormant pages actually occupy no physical space, and can also be transmitted from one node to another in batches because they take up zero space in the messages as well (since, for example, there are no contents of a page to be sent over). 
     Thus, during boot time, the existence of dormant pages indicates that pages do not actually need to be transferred prior to their first access by the guest because they do not reference any actual physical pages. This avoids the expense of page moves when the guest&#39;s initialization (e.g., boot and setting up its initial workload) touches all of the nodes of the system. 
     Thus, the example fast boot technique described herein saves in three ways: 
     1. Since many processors are operating in parallel, and since the stalls are happening concurrently, memory initialization is actually operating in parallel, and is no longer single-threaded, speeding up the boot process. Therefore, there is no need to wait for zeroing of all the pages in the system. 
     2. There is no need to decide where all the initial pages need to be placed on all the nodes (because dormant pages move to where they are needed at practically no cost). 
     3. Dormant pages, since they occupy no space, can be inexpensively transferred in batches. 
     The use of dormant pages and guest physical addresses provides a naming abstraction that is independent of the physical allocation of memory, where there is a separation between the address of pages that should be zeroed and real pages of physical memory that are actually zeroed. As described above, the representation of dormancy is efficiently transferrable, as guest physical addresses that are marked as dormant do not have an actual real physical page of memory allocated to them, and migrating a dormant page therefore does not require the transmission of the contents of a page of physical memory, but rather the migration of the dormant status of the guest physical address. 
     The implementation of dormancy has various benefits (such as those described above) in computing systems, such as the software defined server described herein. While examples of page dormancy in a software defined server are described herein for illustrative purposes, the techniques described herein may be variously adapted to accommodate any type of single server or multi-server distributed computing architecture, as appropriate. 
     Further examples of page dormancy in a software defined server, such as at system initialization and system steady state, are described below. 
     System Initialization 
       FIG.  19    illustrates an embodiment of initializing a multi-server system. A two-node software defined server containing node_0 ( 1902 ) and node_1 ( 1904 ) is shown in this example. In this example, at boot time/initialization, all guest physical addresses are marked as dormant. Further, all guest physical addresses are owned by a single node of the system, node_0 in this example (in some embodiments, all guest resources, such as VCPUs are also initially located on the same, single node at start up). As shown in this example, the definition of the guest physical addresses on node_0 and the indication of dormancy of the guest physical addresses are implemented using an internal page table  1906  (e.g., a second level page table or other data structure managing and tracking placement of pages). As one example implementation, the page table includes metadata information for guest physical addresses. The metadata includes a dormant bit, which is set if the page is dormant. 
     Since all of the guest physical addresses are dormant, they are not mapped to any real physical memory, and thus may be owned by a single node, even though if they were expanded (i.e., mapped to real physical memory), the node could not contain all of the physical memory. This provides a convenient and efficient initialization state, because a large amount of real physical memory is not required to boot the guest operating system—that is, even though the guest system is virtually large, its physical footprint is small, which allows for fast booting of the system. 
     With all guest physical addresses marked as dormant, the guest operating system is presented the illusion that all pages in the system are initialized and zeroed. That is, from the guest operating system&#39;s perspective, its requests to zero all of the guest physical addresses were satisfied when the hyper-kernel made all of the guest physical addresses dormant, which is an inexpensive task relative to writing zeroes to all pages of real physical memory. However, the real pages of physical memory have yet to actually be zeroed. As described herein, zeroing of the pages of real physical memory across the nodes may be performed in parallel. The zeroing may also be performed in a “lazy” or deferred manner. 
     At the startup of the system, there are relatively few processes that are running. Thus, guest VCPUs running on physical CPUs may be available to perform housekeeping such as zeroing the pages of real physical memory across the nodes. In some embodiments, each node is directed (by respective hyper-kernels) to zero its respective real physical memory in parallel with the other nodes. 
     As shown in this example, each node includes a “to be zeroed” list ( 1908  of node_0 and  1910  of node_1) that includes real physical addresses on the node that are to be zeroed. After a real physical address is zeroed, it is removed from the “to be zeroed list” and added to the “zeroed list” ( 1912  of node_0 and  1914  of node_1) (also referred to above as a “pre-zero” list), which indicates what pages of physical memory on the node have been zeroed and are free/available for use (e.g., to be mapped to a guest physical address so that its contents may be accessed). 
     The zeroing of pages of physical memory may be done in parallel, thereby increasing the speed at which memory initialization may be performed. Since a zeroed page is separated from the address of the zeroed page, there is no need to predict which nodes these zeroed pages will reside. That is, there is no need to define, at boot time, where these zeroed pages are. At boot time, all the physical memory on all the nodes may be zeroed, without considering where they might be mapped in the future. 
     This is in contrast to existing systems, where, for example, the code to perform the zeroing may be single threaded code (such as the test and initialization code described above with respect to memory tests), which would require pages to be brought one at a time to the processor running the thread to be written into with zeroes. This contributes to the slow bootup times of existing large memory systems. In contrast, using the techniques described herein, all pages may be zeroed on all nodes in parallel, without being restricted or limited by the location(s) at which the guest operating system calls the zeroing code. 
     Further, as described above, real pages of physical memory are zeroed in the background, and may be zeroed in a deferred manner—that is, not necessarily when/synchronously with the operating system booting up. For example, pages may be zeroed on an ongoing basis, as the need arises. This is in contrast to how existing operating systems are written, which typically do not defer the performing of the zeroing work. 
     Thus, through the separation provided by the use of dormancy, and the virtualization techniques described herein, the initialization of pages may be deferred, or time-shifted, as well as performed in parallel, which allows for fast system boot up. 
       FIG.  20    is a flow diagram illustrating an embodiment of a process for initializing a system using dormant pages. In some embodiments, process  2000  is executed by a distributed hyper-kernel (e.g., at system initialization time). The process begins at  2002  when a set of guest physical addresses is marked as dormant. At  2004 , a set of real physical addresses is designated to be zeroed. The real physical addresses are zeroed on an ongoing basis. 
     Converting Dormant Pages 
     Suppose that a software defined server has been initialized and booted up, as described above, and is now operating in steady state. There are guest physical addresses in the system that are marked as dormant that have not yet been mapped to any page of real physical memory. 
     In some embodiments, dormant pages/guest physical addresses are converted (mapped to real physical pages of memory) on an on-demand basis. Suppose that a VCPU accesses a guest physical address that the VCPU believes to be full of zeroes, but is actually dormant. The dormant guest physical address will need to be mapped to an actual page of physical memory. Described below are details regarding converting or materializing dormant pages. 
       FIG.  21    illustrates an embodiment of converting a dormant page. In this example, suppose that a thread running in a VCPU implemented on a physical CPU on node_0 ( 2102 ) requests to write a set of non-zero values (e.g., a set of Is) into a guest physical address 6 (GPA 6 ( 2104 ) in node_0&#39;s page table). 
     In this example, GPA 6 is owned by node_0 and is in node_0&#39;s internal page table. When the VCPU attempts to access GPA 6, the computation will stall on GPA 6 because it is dormant and is not actually mapped to a real page of zeroes (there is nothing mapped to GPA 6 in the second level page tables). Based on determining that GPA 6 is currently marked as dormant (as indicated in the page table), in order to handle the stall, a page of zeroes that is available or free to be used is obtained and mapped to GPA 6. 
     In this example, node_0&#39;s hyper-kernel checks its local “Zeroed” list ( 2106 ) to determine if there are any free pages of memory that are already zeroed and are ready to be mapped to GPA 6 and then written into by the VCPU. 
     In this example, the real page at real physical address 3 on node_0 has already been zeroed and is free for use. Real physical address 3 is then mapped to GPA 3. The internal page table of node_0 is updated to reflect that GPA 3 now points to a real physical page (where the real physical address 3 is mapped to GPA 3 in the second level page tables). The contents of real physical address 3 are overwritten with the 1&#39;s. Real physical address 3, having been used, is removed from the “zeroed” list. GPA 3 has thus been converted from a dormant “page” and mapped to a real physical page (which stores the contents of the guest physical page at GPA 3). 
     In the above example, there was a free page on node_0 that had already been zeroed that was available to be mapped to GPA 3. In some embodiments, if there are no free zeroed pages in the “zeroed” list, then the hyper-kernel determines whether there are any real physical pages on node_0 that are to be zeroed (and are listed in the “to be zeroed” list  2108 ). If so, then a real physical page in the “to be zeroed” list is allocated on demand and has zeroes written into it. The newly allocated page with zeroes written into it may then be used and mapped to GPA 3 for overwriting with 1&#39;s. The real physical page is removed from the “to be zeroed” list. In some embodiments, prior to being mapped, the zeroed page is then placed in the zeroed list, where it is then removed from the zeroed list. In other embodiments, the page that was allocated and zeroed on demand is directly mapped to the dormant guest physical address, without being placed on the zeroed list. 
     In some cases, there may be no free real physical pages in the “Zeroed” list on node_0, as well as no real physical pages on the “to be zeroed” list, where the node_0 has no free space on the first node (all of its real pages of physical memory are in use). There are various strategies for handling the stall, where the selected strategy may depend on the evaluation of cost functions and machine learning observations of the system. 
     One option or strategy is to migrate the VCPU that is stalled, as well as the dormant page, to another node that does have already zeroed pages that are free (or pages that are to be zeroed, or at least free space for free pages). In this case, ownership of the dormant page (guest physical address) is transferred to the remote node, where the dormant page is then converted as described above (e.g., by mapping the transferred dormant page to an already zeroed page at the new node, or to a free zeroed page that is allocated on demand off of the new node&#39;s list of real physical pages to be zeroed). 
     Another strategy to handle the stall and obtain a free page to map to the dormant guest physical address is for node_0 to migrate a non-dormant page (with contents) that it owns to another node, thereby freeing space on node_0. This leaves a free page of physical memory that may then be zeroed and mapped to the guest physical address. 
     Another option is to swap a page of contents on node_0 with a page of zeroes from another node. The stall may then be satisfied on node_0. 
     In the above example of  FIG.  21   , the GPA referred to by the VCPU on node_0 that stalled was also locally defined on node_0. In some cases, the referred-to GPA may be defined on a node that is remote with respect to the VCPU on which the stall occurred. In some embodiments, to collocate the ownership of the GPA and the VCPU, ownership of the GPA is transferred to the node with the stalled VCPU. In another embodiment, the stalled VCPU is migrated to the node that owns the GPA. With the VCPU and GPA collocated, the dormant GPA is then mapped to a real physical page of zeroes, as described above, and then written into by the now collocated VCPU. Further details regarding migration of dormant pages will be described below. The determination of what resources should be migrated may be based on cost functions. As one example, the strategy cost functions may evaluate conditions such as memory pressure. For example, if the node with the VCPU is under memory pressure, then ownership of the page is not transferred, unless, for example, there are other VCPUs on the first node that share a common working set with the VCPU on the first node that took the stall. The cost functions may, for example, decide to push pages out from the node with the VCPU (e.g., to free space and relieve memory pressure so that the dormant page may be migrated to the node with the VCPU), or migrate the VCPU. 
     In the example of  FIG.  21   , a non-zero value was written into the guest physical address. In some embodiments, the hyper-kernel detects (e.g., during the stall due to the attempt to access a dormant page) whether the instruction on which the stall occurred is to write a page of zeroes to the dormant page. If the hyper-kernel identifies that the instruction being executed by the VCPU has the effect of zeroing the page, then the hyper-kernel leaves the page dormant, and skips or otherwise advances past the instruction. This saves resources from writing pages full of zeroes. 
       FIG.  22    is a flow diagram illustrating an embodiment of a process for converting a dormant page. In some embodiments, process  2200  is executed by a set of hyper-kernels. The process begins at  2202  when an indication of a stall on a dormant guest physical address is received. For example, because there is no actual page of physical memory mapped to the guest physical address, there is also no entry in the second level page table for the guest physical address. A stall (page fault) will then occur. An internal page table of a node may be evaluated, as described above, to determine that the dormant bit for the guest physical address is set (indicating that the guest physical address is a dormant page). At  2204 , an available portion of physical memory is obtained. Various techniques for obtaining a free portion of physical memory are described above. At  2206 , the guest physical address is mapped to the obtained available portion of physical memory. 
     Converting a Page to Dormant (“Re-Dormanting” Pages) 
     When a user or a process executes commands, a process is created. Oftentimes, the process, by definition, is initially full of zeroes, no matter how large the process is. This allows the guest operating system to provide a guaranteed initial state (where the process is zeroed). In order to meet this guarantee, the operating system, at a time typically no later than when it starts this process, begins zeroing pages by executing a routine such as bzero of the libc library (where bzero performs fast zeroing of memory). The pages that are zeroed may include remnants of processes that no longer exist. 
     In one embodiment, rather than letting the system zero the pages, which would involve locating the page, potentially transferring it to the node with the VCPU implementing the bzero command (if the page is remote), and then having the page overwritten with zeroes, instead, the page (at a guest physical address) to be zeroed is converted to a dormant page (e.g., by marking its dormant bit in the internal page table for the record associated with the guest physical address). The portion of the instruction that zeroes the page is then skipped or advanced over. This provides the benefit that a page does not need to be allocated or made real until an attempt to write a non-zero value into a guest physical address is made. For example, at a later time, when the guest writes non-zero content to the guest physical address, it may, at that point, be converted from a dormant page to one that is mapped to a real physical page, as described above. This is performed transparently to the guest operating system. For example, the next time that the guest tries to access the page (e.g., to fill up the page with some non-zero value), a fault on a dormant page occurs, causing a stall, which is then handled as described above. 
       FIG.  23    illustrates an embodiment of re-dormanting pages. In this example, suppose that a VCPU on node_0 ( 2302 ) of the software defined server is directed by a guest thread to write zeroes (e.g., via the bzero command from libc, as described above) into the guest physical addresses GPA_27 and GPA_34. 
     Suppose that node_0 owns GPA_27 (where GPA_27 is defined on node_0) and GPA_27 is non-dormant (as indicated at  2306 ) and is mapped to some page of real physical memory local to node_0 that is at real physical address 196 (RPA_196). Suppose that GPA_34 is non-dormant (as indicated at  2310 ) and is defined on node_1 ( 2304 ) of the software defined server and is mapped to real physical address 734 (RPA_734) of node_1&#39;s local physical memory. 
     In this example, because GPA_27 is on the same node as the VCPU and is currently mapped to real physical memory at the time of the instruction, no stall occurs, and RPA_196, mapped to GPA_27, is overwritten with zeroes. 
     Now consider GPA_34. In this example, GPA_34 is on a remote node relative to the VCPU on node_0. When the hyper-kernel of node_0 checks its internal page tables, it will determine that it does not own GPA 34, and that GPA_34 is on a remote node. A stall then occurs (since the page of memory and the VCPU are not collocated on the same node). In this example, when the stall occurs, the hyper-kernel executing on node_0 evaluates the instruction to be executed and detects that the instruction to be executed by the VCPU on node_0 has the effect of zeroing the page at GPA_34. Based on this determination that the page is to be overwritten with zeroes, rather than overwriting the page with zeroes, GPA_34 is marked as dormant. 
     In one embodiment, node_1 is instructed by node_0 (e.g., via a sent message) to set the dormant bit for GPA_34, thereby creating a dormant page (if GPA_34 is to be zeroed, then its contents are irrelevant). In one embodiment, creating the dormant includes updating the record in the internal page table for GPA_34, and marking the dormant bit (e.g., as true, indicating that the guest physical address is dormant, or using any other appropriate marker/indicator). The page of real physical memory that GPA_34 previously pointed to now becomes a free page that may be used for some other purpose. For example, the real address RPA_734 is placed on node_1&#39;s to be zeroed list ( 2312 ) (where its zeroing may be deferred). Between the time that the real physical page is unmapped from the guest physical addresses and the time that another dormant page uses the real page of physical memory, the real page of physical memory may be zeroed when there are spare processor cycles (e.g., as part of housekeeping performed by the hyper-kernel). The page may also be zeroed directly and added to a list of already zeroed pages. Another dormant page may use the real page, for example, when a non-zero value is being written into, and be converted/materialized, as described above. 
     Thus, the remote page on node_1 being zeroed on node_0 (where the zeroing instruction originated) may be remotely converted to dormant on node_1. The converted page may be left on node_1 or may be moved to node_0 after becoming dormant. 
     In another embodiment, node_0 performs the conversion of GPA_34 to a dormant page. For example, node_1 sends a message back to node_0, indicating that node_0 may proceed from its stall by creating a dormant. As one example, ownership of GPA_34 is transferred to node_0. The hyper-kernel of node_0 marks GPA_34, which it now owns, as a dormant page in its internal page tables. The hyper-kernel on node_0 then sends a message to the node_1 indicating that the ownership of GPA_34 has been transferred and that the page is now dormant. Node_1 may then place the real physical page previously mapped to GPA_34 on its to be zeroed list. Further, node_1 is also instructed to unmap GPA_34 from node_1. 
     Thus, the distributed hyper-kernels have coordinated with each other to convert GPA_34 to dormant, which may be performed independently of where they are originally located or defined. 
     Moving GPA_34&#39;s dormancy to node_0 may improve locality and pre-emptively reduce future stalls, as it is likely to be accessed again by a VCPU on the node_0, from which the zeroing was requested (e.g., because node_0 is likely to write to the zeroed page next). 
     In the above example, GPA_27 was directly overwritten because no stall occurred since GPA_27 was mapped to a portion of physical memory local to node_0. However, there may be cases in which GPA_27 is owned by node_0 and is non-zero, but at the time of the zeroing instruction, is unmapped to real physical memory, in which case a reference to GPA_27 would stall (stalls are generated when a guest physical address is not mapped to a real physical address). There are various reasons why a guest physical address may be unmapped. For example, un-mapping could occur if the hyper-kernel is in the middle of an accounting procedure referred to herein as “sampling,” in which pages that have not been recently used are identified. 
     If GPA_27 had been unmapped at the time of the zeroing instruction, reference to it would cause a stall. At that point, when the stall occurs, the hyper-kernel on node_0 may, as similarly described with respect to GPA_34 above, determine that the instruction that caused the stall was trying to zero the page. In response to detecting that the page is to be overwritten with zeroes, the hyper-kernel on node_0, rather than, for example, mapping a page of physical memory (e.g., obtained from the zeroed list) to GPA_27, marks GPA_27 as dormant and then returns. 
     Thus, as shown above, in some cases, if the page is on node_0 and is non-zero, the stall may or may not occur, depending on other mechanisms that defer mapping of the page. For example, the hyper-kernel may be aware that a page is present, but it may not be mapped for other reasons (e.g., to facilitate the sampling described above to detect less frequently referenced pages, which may be used to decide which pages may be moved off of the node). 
     The guest OS or application software frequently zeros pages that may not be required after the zeroing for some time. If the page is needed for reading or writing, there may be fully zeroed pages already available in physical memory (e.g., in the zeroed list), where mapping one of these zeroed pages may be significantly faster than zeroing it. This has the effect of moving the zeroing of the actual page to CPUs that are not otherwise in use. 
     At some point in the future, another VCPU may access the guest physical addresses that were to be zeroed (but have been set as dormant instead of being overwritten with zeroes). For example, the VCPU may know that for a process being created, and all the pages associated with the process should be zero. The VCPU will write into the page. If the VCPU attempts to write into a page that is dormant, a stall will occur, as described above. The page may be dormant on the same node or dormant on another node. In one embodiment, if the page is dormant on another node, then the other node sends back, to the first node with the VCPU, the page in its dormant form. 
     When the instruction is restarted, the dormant page is converted into a page full of zeroes, for example, using the techniques described above (where, for example, any page full of zeroes that happens to be on the local node may be used). 
       FIG.  24    is a flow diagram illustrating an embodiment of a process for converting a dormant page. In some embodiments, process  2400  is executed by a set of hyper-kernels. The process begins at  2402  when an instruction to zero at least a portion of memory associated with a guest physical address is detected. At  2404 , the guest physical address is marked as dormant. At  2406 , the instruction to zero the portion of memory is skipped. 
     Migration of Dormant Pages 
     Because dormant pages are representations of pages, and do not have any memory allocated behind them, the dormant pages may be migrated quickly and efficiently (e.g., in large batches). That is, it may be much less expensive to migrate a dormant page than an actual page of memory with contents (since for a dormant page, there are no contents or data of a page of physical memory to be transmitted between nodes over the software defined server&#39;s interconnect). Thus, whenever a page is moved, where the page is either dormant or could be dormant (e.g., because the page will be overwritten with zeroes), the page is moved in the dormant mode. 
     In one embodiment, migrating a dormant page includes sending a message indicating that a particular GPA is being moved to a certain node and that the GPA is dormant. The message does not contain a page—there are not contents of a page that are sent over. Instead, the guest physical address (along with its marked dormant bit) is moved. 
     Further, in some embodiments, zero page detection is performed when migrating pages. For example, suppose that a page is to be transferred from one node to another. The hyper-kernel determines whether the page is a page of zeroes (or is a page that the guest has requested to overwrite with zeroes). When it is detected that the page is a page of zeroes, rather than migrating the page and its content of zeroes, the page is converted to a dormant page, where the page is then transferred as a dormant page. In this way, the contents of the page, containing zeroes, need not be transmitted over the network, saving network resources and facilitating efficient page migration. This reduces the cost of transmitting pages full of zeroes (which may represent a fairly large percentage of pages in some implementations). 
     Replicating Dormant Pages 
     In one embodiment, a dormant page, when converted, is converted into a primary copy, or into an exclusive copy that is in only one location in the system. After a period of time, the exclusive copy may be replicated, resulting in the exclusive copy becoming a primary copy, and a number of secondaries to be created throughout the system. This may occur, for example, when a zero is written into a page (even if not all of the page is written to zeroes). The page is turned from dormant into a real page, as it is often likely that the page will not be overwritten with all zeroes. However, as described above, there are cases in which an entire page is overwritten with zeroes. 
     As described above, pages with contents that are read-only may be replicated. In one embodiment, the page replication mechanism described herein is extended to replicating dormant pages, which have no contents, allowing, for example, primary dormant copies and secondary dormant copies, since a zero-page is read-only. 
     In one embodiment, a secondary copy that is dormant is converted into a secondary copy that has zeroes in it. This allows a word in it to be accessed at hardware speed, without taking a stall. For example, suppose that a file is made up of 20 pages, and one of the pages in the middle of the file happens to be full of zeroes. Suppose also that there is code being executed that reads through the file and adds up all the bytes of the file, where the code does not know which pages are zeroes and which are not (although, in some embodiments, the hyper-kernel may be configured to do so). A number of threads may execute this code at the same time. The zeroed pages may be replicated so that they may be read at hardware speed. In one example implementation, once a page has been materialized (e.g., a dormant page is converted so that it maps to a real page of physical memory), even if the page is full of zeroes, it may be replicated. Thus, in situations where a page of zeroes is to be read, the page of zeroes may be replicated so that a stall does not occur, and it is not required that a page of zeroes be obtained and mapped on-demand to a dormant GPA. 
     Dormant pages and their uses in improving the resource efficiency of a computing architecture have been described, from the initial condition, to consumption of memory as it is consumed by the guest (conversion of dormant pages), to re-dormanting of pages. 
     Using the techniques described herein, the cost of zeroing pages, and the cost of moving zeroed pages, are shifted to physical processor time that would be unused by the guest OS or application. This dramatically speeds up many performance-limiting behaviors of the guest operating system and applications. In particular, booting up. 
     Although the foregoing embodiments have been described in some detail for purposes of clarity of understanding, the invention is not limited to the details provided. There are many alternative ways of implementing the invention. The disclosed embodiments are illustrative and not restrictive.