28. Virtual Machines

Part of CS:2820 Object Oriented Software Development Notes, Spring 2016
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

 

A Problem

When object-oriented programs get big, how can we organize them so that we can make better sense of them. There are at least two parts to this, first, when classes get big, how can we organize their interior details to make things easier, and second, when there are large numbers of classes, how can we make sense of their relationships.

Consider the problem of building an epidemic simulator. In simulating the social network of a community, in our initial problem statement, we mentioned workplaces, schools and churches, but in our initial solution to the problem, we only implemented workplaces.

Each workplace is characterized by a subset of the population that goes there at the opening of business and goes home at the close of business.

Were we to add schools to our model, each school would be characterized by a subset of the population that goet to school at the start of the school day and goes home at the close of the school day. The code for schools and workplaces would be nearly identical.

Adding churches would be quite similar, once we added a weekly structure to the time model in the simulation. Schools and workplaces convene Monday to Friday, each with its own business hours. A first approximatio of churches would convene on Sunday, but aside from differences in the days on which they meet and their business hours on those days, the code for churches would be just like the code for schools and workplaces.

Can we build a general mechanism that allows us to create a multitude of places with people who are predisposed (or perhaps programmed) to go to those places?

One proposal for a general mechanism would rest on something like the following:

This proposal has difficulty extending to people with multiple sets of obligations: Students with after-school jobs, people who have jobs and also go to church on Sundays, people who go to lodge meetings on Wednesday night, or people who have second jobs. It also doesn't deal with un-scheduled activity. People who go shopping on the way home after work, or people who decide to go out to dinner.

A second proposal would be to have "bolt on" features for places and people. First, we would create all the places and all the people, and then we would create linkages between people and the places they frequent. This requires:

All of these models conform to the hierarchy of our current simulation. They involve adding complexity, but not adding new layers to our model. We have the following basic layers:

In talking about systems composed of multiple layers, where each layer has multiple classes there are some useful (and 50 year old) ideas for talking about what we have.

O-functions and V-functions

When a class gets large, with a large collection of methods, it is useful to find some way of organizing those methods to help readers understand the class. David Parnas, one of the early developers of ideas relating to object-oriented programming, proposed the following general classification system for methods:

O-functions
Methods that Operate on the value of an object.

V-functions
Methods that work in terms of the Value of an object without changing it.

Note: Parnas used the term function, in part, because the term method was not yet in general use. Today, he might have called them methods.

Today, some people refer to setter and getter methods, where a setters methods is used to set the value of a field or fields of an object, and a getter method is used to inspect the value of that field.

Regardless of what you call them, the broad idea remains useful. Consider this simple class:

class C {
    private int f; // some field of the object
    public void set( int v ) { // a simple o-function or setter
        f = v;
    }
    public int get() { // a simple v-function or getter
        return f;
    }
}

The combination of a private field with a getter and a setter method illustrated above offers is rather pointless. We could have simply declared the field f to be public. Getters and setters make sense when they are used to constrain the use of a field. For example, a private field plus just a getter makes that field read-only from the outside of the class. A setter method can enforce constraints of a field, for example, forcing it to be even, forcing it to inside some range, or forcing it to be non null.

Java has only limited access control. Variables can be public or private, default (package-level access) or protected, but you can't declare a variable to be, for example, "public read-only, private read-write." If that is the access rule you want to enforce, and you are stuck using Java, you can enforce this by creating a public getter for a private variable.

Parnas's notion of o and v-functions was more general than getters and setters, since any function that changed the values of any fields but did not return a value was defined as an o-function, while any function that returned something about an object without changing it was defined as a v-function. On a stack, for example, the push operation is a pure o-function, while pop does not fit the o-v classification since it both changes the value of the stack and returns the former top element. If we replace the pop operation with top for inspecting the top of the stack and discard for discarding the top item of the stack, then top is a well defined v-function and discard is an o-function.

When a class gets large, separating the o-functions from the v-functions can be one useful way of organizing the class definition to make it easier to read.

Note that you might imagine that the o-functions doing all the complex work of the function, while the v-functions trivially inspect and return fields, but this is not the case. Consider the problem of computing perspective projections from some viewpoint:

class Perspective {
    // fields describing the viewpoint

    public void set( Viewpoint v ) { // an o-function
        // do whatever is needed
    }

    public point transform( point p ) { // a v-function
        return perspective transformation of point p;
    }
}

Here, it turns out, the fields describing the perspective transformation from a particular viewpoint are essentially a matrix. When the viewpoint is set (by providing the coordinates of a point and the direction of the view from that point), the code in our O-function must compute this matrix.

When the coordinates of a point are transformed into the coordinates as seen from that viewpoint, the essential computation involves multiplying the coordinate vector times the perspective transformation matrix. Matrix multiplication is an expensive operation, so our v-function does quite a bit of work. Of course, we can also construct alternative problems where the O-functions naturally do all the work while the v-functions do very little.

Virtual Machines

The term virtual machine is used fairly frequently these days, and in its broadest sense, it is very relevant to the problem of organizing large programs. First, however, let's look at its primary meanings.

software-defined virtual machines
When you have executable code in the instruction set of one machine, but the hardware you have executes a different instruction set, you can still run that code if you have a virtual (software) implementation of the instruction set you want. Such a virtual implementation is also referred to as an instruction-set emulator.

For example, many computer architecture courses are taught using the ARM and MIPS instruction sets, yet most of the students taking those courses only have access to computers with Intel x86-family processors. So, to run their programming assignments, they use virtual MIPS or ARM processors implemented in software by an appropriate emulator.

For a second example, most Java compilers produce output in a machine language called J-code, and the simplest Java run-time systems use a virtual J machine, or a J-code emulator to run the code. Briefly, Rockwell Collins Corp. of Cedar Rapids, Iowa, offered a hardware J-machine for sale. They did this because they had already developed, in house, a micrprocessor called the AAMP that was so similar to the J-machine that converting the AAMP to run J-code was fairly easy.

virtual machine monitors
A virtual machine monitor is an operating system that creates one or more virtual machines running on one physical machine. The virtual machines are typically identical in their instruction set to the physical machine, except that each has access to only a subset of the physical resources.

A well-written virtual machine monitor on a computer designed to be virtualizable will typically run user code at a speed very close to the actual hardware speed. Most machine instructions on a virtual machine will be executed by the physical hardware. The only instructions that are executed in software are the instructions that control access to physical resources such as I/O devices and the memory management unit.

For example, VMware (a product built by the company of the same name) tries to virtualize the x86 family instruction set. Their software is widely used on server farms that provide cloud computing services in order to prevent users of cloud services from interfering with other users. An unfortunate problem with the Intel x86 family is that it was not originally designed to be virtualizable, and attempts to find all of the parts of the instruction set that need virtualization have proven to be very difficult.

The VM operating system from IBM that runs on their enterprise server architecture is a far better example, as well as being the first fully developed example in this category.

virtual machines (in general)
The most general definition of a virtual machine is that it is any set of hardware and software resources that, taken together, create an environment in which applications can be developed. So:

An important point to note, here, is that most applications are developed in the context of a hierarchy of virtual machines. Recognizing that many of the components of large programs are actually defining new virtual machine layers can help make sense of large programs that would otherwise be difficult to digest.

The Origin of the Term

The term virtual machine is, in part, a borrowing by computer scientists from the field of optics. In classical optics, when discussing lenses, the key concept is that of an image. You have an object, a lens, and the image of that object cast by the lens. This works so long as you are describing converging lenses (like magnifying glasses). The key defining characteristic of an image is that, if you put a projection screen or a sheet of white paper in the plane of an image, you will see that image projected on the paper.

Many optical systems have numerous image planes in them. For example, in an astronomical refracting telescope, the objective lens projects an image of a patch of sky on the primary image plane. There is no sheet of paper there, but the image exists regardless of whether you put a projection screen there so you can see it. The eyepiece of the telescope is, essentially, a magnifying glass allowing you to look at a small patch of the primary image. The combination of the eyepiece lens and the lens in your eye then projects an image on your retina. That is what you actually see.

When some of the lenses are diverging lenses, we need to modify our terms. When you look through a diverging lens, you see something in the distance through the lens. From your human perspective, it looks like you are looking at an image or at a real object, but if you reach behind the lens to grab something you see, your hand will miss the real object.

The problem with diverging lenses is that they create what is referred to, in classical optics, as virtual images. A virtual image is something you see when you look through a diverging lens. There is either an object or a real image behind the diverging lens, but it is somewhere closer to you than the virtual image you see.

An Example

Consider an application running on a server in the cloud (Amazon, Google, Microsoft, it hardly matters). This is probably running on a multi-layered stack of virtual machines:

  1. On top of the physical machine, probably a high-end Intel processor, the cloud computing service is running something like VMware in order to safely isolate their customes from each other.

  2. On top of VMware (or equivalent secure environment), the customer is running some operating system, for example, Linux. Linux extends the bare machine it thinks it is running on with a number of resources created by software such as files, timers, and processes.

  3. On top of Linux, the customer might be running a language like Java that relies on a virtual machine such as JVM.

  4. On top of the bare JVM environment, the customer is almost certainly using some subset of the Java standard library, for example, classes String and File. These library classes are implemented in Java, so the user could have ignored the library and directly used the resources of the underlying operating system.

  5. On top of the Java library, if the customer is running our neuron network simulation, there is class ErrorMessage and Simulator. Both of these use classes from the Java library as well as pure Java code.

  6. On top of the those classes, our code included class ScanSupport that rested on top of class ErrorMessage, so this can be viewed as yet another layer.

The actual neuron-network simulation code we wrote can be considered to rest on top of this stack of virtual machine layers. Almost all large software systems involve such stacks of virtual machines. Many of the layers described above can be further broken down into layers. For example, the internal code of many operating systems contains a kernel that is used to implement services that are used to implement other services that sit under application programs. From the outside, we consider the operating system to be a single layer, but to a system programmer working on developing the system, it has many layers.

Differences Between Virtual Machines

The virtual machine layers in the above hierarchy differ in some important ways. Software implementations of instruction sets have a significant performance penalty because it typically takes from 10 to 100 physical machine instructions to implement each virtual machine instruction. At the same time, a software implementation of an instruction set can completely isolate the user from knowing anything about the physical machine; this creates portability, and it also has the potential to be very secure.

In contrast, with a virtual machine consisting of a software library plus some lower level tools, the user is not obligated to use the library, so the user has full access to the lower level machine at its native speed. With full access comes danger, if some of the features of that machine are insecure or unsafe.

Transparency

Virtual machines in a hierarchy may be transparent or opaque; these terms were originally defined by Parnas in the early 1970s.

Transparent virtual machines
A virtual machine is transparent if it does not prevent its user from inspecting and making arbitrary changes to the state of underlying virtual (or physical) machines.

Opaque virtual machines
A virtual machine is opaque if it prevents its user from inspecting or making changes to the underlying state.

In general, opaque virtual machines offer security benefits, preventing user code from accessing or manipulating things that are dangerous, while transparent virtual machines offer greater flexibility and potentially greater transparency.

Parnas's original illustration for this concept was a 4-wheeled vehicle, perhaps an automobile. The low level virtual machine has 2 fixed wheels at the rear end, and two steerable wheels at the front. The front wheels can be steered independently (imagine two steering levers, one you can hold in each hand).

The low level vehicle is very flexible. It can follow any path a vehicle might want to follow, and it can even turn on a dime. Just position the rear axle over the dime, and then turn the two front wheels so that the lines of their axles also pass over the dime. This would make parallel parking incredibly easy, but this vehicle is extremely unsafe. If you are driving at any significant speed and you turn the front wheels so they are not parallel, you risk tumbling the vehicle tail over head. I would hate to drive this vehicle on I-80.

So, we build a higher level virtual machine on top of the low level one by linking the two front wheels to a steering wheel so that they always turn (approximately) in parallel. Our new vehicle is incredibly safer, but it is less flexible because our new virtual machine is opaque. Parallel parking is now a difficult skill involving much reversing and rocking of the vehicle.

Most virtual machines are a mix of transparent and opaque parts. Security problems such as the ability to install rootkits in operating systems tend to involve virtual machines that were intended to be opaque but had small (and typically difficult to find) transparent spots.

In Java, the primary tool we have to control the transparency of virtual machine layers in large programming projects are the ability to declare components of objects private. If we are careless in designing the methods of an abstraction, though, we can end up providing the user with a backdoor that allows some method or combination of methods to set a private field to an arbitrary and unsafe value.