Suppose we start with a very simple model of a computer. In this model the computer can run only one program at a time. It would seem that we do not even need an operating system since the single running program can have complete control of the whole machine. But there is still a good reason to have an operating system. We would like any program that we write to be able to use all of the features of our computer, i.e., the various disk drives, the network connection, the printer, the graphics hardware, etc. We could incorporate all of the code needed for every one of these devices into every one of our programs. But this would be a nightmare. For one thing, we would need to know a great deal of detailed information about every device in the computer and then write quite a bit of fairly complicated code for each device. And if we went out and bought a new video card or a new printer, then we would need to rewrite all of the video and printer code in every one of our programs. This would not be an acceptable situation.
A better idea would be to have an "operating system" that is a collection of code that takes care of all the hardware devices in the computer. For each hardware device in the computer this "operating system" would have a piece of code (called a "device driver") that knows all of the details of how to handle that particular hardware device. When we write a program and the program needs to use some physical device, say a CD-ROM drive, we write the program so that it "calls" the CD-ROM's device driver. Each device driver will present a standard "interface" to our application programs. The interface defines what "calls" are available from the device driver and what each call does (like find a certain block of data on the CD-ROM drive or read a block of data off the CD-ROM drive). We would write our application programs so that they use this standard interface and, therefore, our applications would not need to know the details of how this interface is actually implemented. If we went out and bought a new device, say a new video card, then we would have to write a new device driver for the new card, but the device driver would still provide the same interface to our application programs, so (and this is very important) our application programs would not have to be rewritten for the new video card.
To put it briefly, in our simplified model for a computer, the responsibility of our "operating system" is to provide a running program with an abstraction of the hardware devices in the machine that the program is running on (the operating system, in effect, creates a "virtual machine" that our application programs run on). This makes our programs easier to write and maintain.
So even with our simplified model of a computer, which can run only one program at a time, we need an operating system. When we run a program on our computer, in the computer's memory there will be our (single) program and the operating system (consisting of all the device drivers). Here is one way to organize this program/OS in the computer's memory. We can divide the computer memory in half and dedicate half the memory (say the lower half) to the single program and the other half to all of the operating system code. In this model, all of our program and all of the operating system are loaded into the computer's memory all of the time but the program and the operating system are kept separate from each other and the program "calls" the operating system whenever it needs some service done for it.
(Note: The above "simplified model" for an operating system is not all that unreasonable. It is essentially the model for the Microsoft DOS operating system, which is still used on some small, embedded, computer systems, and was used on a huge number of computers throughout the 1980's.)
Now here is another reason why we would want to have an operating system. Suppose that our computer has 512 MB of RAM. So, using our current model, half would be dedicated to the program and half to the operating system. But that leaves only 256MB of memory for our program. What if we need to run a program that needs more memory (like an image or movie editing program)? We could go out and buy more memory, but there should be a better way to allow us to run programs that require more memory than is actually in the computer. One idea would be to have our large program divided up into several segments (sometimes called "overlays"), each of which could fit into the available memory. When we run the program we start with one of the segments and that segment will, if needed, load one of the other segments on top of itself and let the other segment run. As our program runs, it would load segments (from the hard disk) and run one segment at a time. This way, our program could be as large as we want. But we would have to write the code that finds and loads the segments and we would need to put this code into every one of our programs. And if we go out and buy more memory, then we may not even need the segments anymore and they would just needlessly slow down our program. Even worse, if we try to run our program on a computer with less memory, then our segments may be too big for that computer and we would be out of luck.
A better idea than having to segment our programs into overlays would be to extend our "operating system" to include code that implements a "virtual memory system". A virtual memory system essentially automatically segments our program for us (the segments are called "pages") and the virtual memory system automatically loads these pages as they are needed. Our virtual memory system could be written so as to fool our program into thinking that it has a large amount of "memory", say 4GB (which is 2^32 and the 32 comes from the fact that a Pentium was a 32-bit CPU). Half of this memory could still be dedicated to our program and the other half to the operating system (which now includes both the device drivers and the virtual memory code).
So now we have two responsibilities for the operating system in our simplified model of a computer. To provide our single program with an abstraction of the hardware devices that the program has available to it and to provide an abstraction of the computer's physical memory so that the program can believe it has as much as 2GB of memory, even when the physical memory is less.
(Note: This simplified model of a computer is actually the model that the Microsoft Windows XP operating system created for every program. Every (32 bit) Windows XP program (i.e. process) had its own "virtual computer" with 4 GB of "virtual memory", half of which was dedicated to the single process running on this virtual computer and the other half was dedicated to the operating system. In effect, every process thought that it had a whole computer, a kind of "virtual computer", all to itself. Notice that every process also thought it had its own copy of the whole operating system code, but in fact, a single copy of the operating system code was shared between all of the running processes. Windows 7 and 8 are similar, but since they are 64 bit operating systems, things are a bit more complex.)
Now let us find another reason why we would want an operating system. Most computer users want to be able to run more than one program at a time. While one program is downloading email, another program could be streaming music, while another program is watching for updates on some social media site, and the TaskManager program is gathering statistics about all the other programs. Even if a modern computer has four or eight cores in it, a user may want to be running more programs than there are cores. How might we make this happen? One way is for every program to be aware that it is sharing a CPU with other programs. If a program is running on a CPU, the program should, at some point, "ask" if any other program "needs" the CPU and be willing to "yield" the CPU to the other program. But this requires that we write extra code into all our programs that make them run as a "cooperatively multitasked system". And besides having to write this extra code into all our programs, there is no assurance that it will work. One buggy program might refuse to yield the CPU and cause all the other programs to stop executing.
A better idea than having all our program try to cooperatively share the CPU would be to extend our "operating system" so that it controls the CPUs and grants them to programs for fixed amounts of time ("time slices"). Our operating system should create a "CPU abstraction" that makes each program run as if it had unlimited control of its own private CPU. Interestingly, if we combine this with the previous two features of our operating system, then we could write our programs to act exactly as if they were the programs of our first, simplified model of a computer; each program runs as if it had complete control of the entire (virtual) computer (devices, memory, and CPU).
Note: Having many programs run at once adds a new responsibility to the part of our OS that virtualizes memory. If we have more than one program running at a time, we would like for each program's memory to be protected from access by any other program. We certainly don't want a buggy program to write all over the memory of some other, well written, program.
So now we have three responsibilities for the operating system of a computer. Provide each process with an abstraction of the hardware devices available to the process, provide an abstraction of physical memory so that each process believes it has as much as 2GB of (private) memory, and provide an abstraction of the CPU so that each process thinks that it is continuously running on its own private processor.
Very briefly, here are two more reason why we need an operating system.
We want our computers to persistently store large amounts of information on storage devices. We need that information to be organized in a useful and efficient way. We do not want to implement, in every one of our programs, our own mechanisms for storing, naming, and finding information. And if we move a storage device from one computer to another, we want all the programs on the other computer to be able to find all the data stored on the device. So we want our operating system to implement standard, and well accepted, "file system" interfaces that all our programs can use for storing and retrieving long term data.
Our computers contain valuable information. Our computers also communicate with other computers from all over the world. We want our information to remain safe from those other computers. But ensuring the security of a program and its data is very difficult. We don't want to be responsible for having to write into every one of our programs standard security mechanisms (user accounts, authentication, authorization, access control lists, encryption, etc.). So we want our operating system to provide built in security mechanisms that apply to all running processes. Our operating system should provide an abstraction of a "secure virtual machine".
To be continued ...