Mike Forster

Windows 95/98
CS 450 Operating Systems
Section 002
Fall 2002
Steve Boyle
Mike Forster
Maggie Hamill
Nancy O’Brien
Table of Contents Introduction 3 History 3 Hardware Platform 4 User Interface 4 Plug & Play 6 Physical Organization of File System 6 File System 7 Memory Management 7
Threads 9
Scheduling 9
Synchronization 10
Process Control Block 13
Conclusion 14
Bibliography 15
Introduction
This paper discusses the various components that make up the Windows 95/98 operating system. The paper begins with the history of the operating system from Windows 1.0 up through and including Windows 98. Throughout the paper we discuss:
Hardware Platforms
User Interface
Plug and Play
Physical Organization of File Systems
File System
Memory Management
Threads
Scheduling
Synchronization
Process Control Block
History of Microsoft Windows
In September 1981, Microsoft began developing its first operating system, then called Interface Manager. Microsoft publicly announced the newly renamed Windows operating system in November 1983. They promised an “easy to use graphical user interface, device independent graphics, and multitasking support.” This product, Windows 1.0, was released two years later in November of 1985.
Windows 1.0 was followed by the release of Windows 2.0 in the fall of 1987. Windows 2.0 offered a “viable environment” for such programs as Word for Windows, Excel, and Corel Draw. It also included the addition of icons and overlapping windows. In late 1987, Windows/386 was released which added the capability of being able to run multiple programs.
In early 1990’s, Windows 3.0, 3.1, and 3.11 showed a more powerful user interface. Windows 3.1 for Workgroups and 3.11 for Workgroups integrated Windows and networking for the first time and enabled peer-to-peer file and printer sharing. Windows NT was released in 1994 and was intended for network servers.
August 1995 saw the release of Windows 95, a 32-bit system with full preemptive multitasking, advanced file system sharing, threading, and networking. Window 98 was released in June of 1998 with several improvements to Windows 95 such as a web browser-like interface, active desktop, better power management, FAT32, and disk defragmenter to name a few. The components of these two operating systems are discussed in full detail later in the paper.
Hardware Platform
Both Windows 95 and Windows 98 require:
386 CPU or faster
8 MB of RAM
30MB hard drive
User Interface
Almost anyone that has ever used a computer is familiar with the Windows graphical user interface (GUI). For most users, the GUI makes applications, services, files etc. easy to find and use. More importantly the GUI is a shell that shields the operating system from users. The idea is that the user communicates with the shell, and then the shell communicates with the OS. The shell provides a user-friendly interface for humans, and then translates instructions to machine language. The Windows 95 GUI is actually a protected-mode overlay of the DOS shell. The GUI architecture is shown below, and explained in the table.
Figure 1: GUI Architecture
Table 1: GUI architecture shown in above figure.
Component
|
GUI
|
DOS
|
Device Drivers
|
Protected mode drivers
|
Real mode drivers loaded at CONFIG.SYS or AUTOEXEC.BAT
|
Virtual Memory Manager
|
Creates virtual machines
|
Loads a simple DOS
|
Installable File System
|
Provides support for hard, CD-ROM, and network drives
Provides support for long filenames
|
Kernel, User, GDI
|
Handles the main functions of the OS
|
|
User Interface
|
Icons, windows, toolbars
|
Command prompt
|
Windows 95 is different from DOS. All portions of the DOS operating system were written in 16-bit code. Therefore, they only support 16-bit drivers and 16-bit application programs. Windows 95 still has a DOS-based core and still uses many 16-bit programs, but also introduces 32-bit programming. 32-bit programs may require more address space, but they generally run faster than programs written in 16-bit code. Windows 95 includes both 32-bit and 16-bit code making it backward-compatible with older software and hardware designed for use with DOS.
When the GUI is running, the main functions of the OS are handled by 3 core components.(as shown in the table below)
Table 2: these are the core components, which handle the OS functions when it is running in GUI Mode.
Component Name
|
Main Files Holding the Component
|
Functions
|
Kernel
|
Kernel32.dll, Krnl386.exe
|
Handles the basic OS functions such as memory management, file I/O, and loading and executing programs
|
User
|
User32.dll, User.exe
|
Controls the mouse, keyboard, ports, and the desktop, including position of windows, icons and dialog boxes
|
GDI
|
GDI32.dll, GDI.exe
|
Draws screens, graphics, and lines, and prints them
|
The kernel uses mostly 32-bit code. The small amount of 16-bit code is only needed for entry points to the kernel from 16-bit application programs. The user portion uses mostly 16-bit code. The equivalent 32-bit code would increase the memory needed and increase the speed, but there is no real need for the speed increase. The GDI (Graphics Device Interface) uses a mix of 16-bit and 32-bit code, so that it can maintain compatibility with 16-bit application programs.
Although Windows 95 supports both 16-bit and 32-bit drivers, it is preferable to use the 32-bit drivers. The main reasons for this are the drivers are much faster, they conserve memory, they can be stored in extended memory and they can be dynamically loaded as they are needed.
Plug and Play
Plug and Play was a new technology introduced in Windows 95 where the OS and the BIOS are designed to automatically configure new hardware devices to eliminate system resource conflicts, such as IRQ and port conflicts. For Plug and Play to work the system must meet the following criteria
The system BIOS must be Plug and Play (PnP).
All hardware devices and expansion cards must be PnP-compliant.
The OS must support PnP.
A 32-bit device driver must be available.
If a system meets the above criteria, installing new hardware simply consists of turning on your PC. A PnP OS provides 2 main services. One is resource management, which occurs at startup as system resources are allocated to devices. The other service is run-time configuration, which is an ongoing process during run time that monitors any changes in system devices. The four components that are used to implement PnP architecture are
Configuration Manager – controls the configuration of all devices and communicates these configuration with the device
Hardware Tree – is a database built every time the OS starts, which contains a list of all the installed components and the resources they can use
Bus Enumerator – locates all devices on a particular bus and inventories the resource requirements for these devices
Resource Arbitrator – decides which resources are assigned to which devices
The first step in the PnP process is when the OS starts up and the configuration manager receives a list of devices from the BIOS. The configuration manager then examines all the required resources until it determines a suitable configuration for all the devices. The resource arbitrator determines what resources were assigned by interacting with the configuration manager. The configuration manager then passes this information to the bus enumerator. The bus enumerator then builds the hardware tree, which contains the device drivers for each device and any user-defined settings. The hardware tree is built every time the OS starts, and dynamically changes as devices are added to or removed from the system. This process can make connecting I/O devices very simple for the user.
Physical Organization of the File System
The file management subsystem is made up of the following components:
The installable file system (IFS) manager; which communicates with permanent storage devices and manages various types of file systems available on the system. Each file system is implemented as a 32-bit protected mode virtual device driver. Also, if a user decides to attach other storage devices to an existing system, IFS can install and manage the new file systems needed to accommodate those devices.
The virtual file allocation table (VFAT) is the 32 bit FAT file system and manages all floppy and hard disks in the system. It runs in protected mode and maintains fast, direct access to a disk through the disk controller card.
The 32 bit CD-ROM file system, called CDFS, multitasks more efficiently and provides a high quantity of throughput.
Any network file system for which a user has installed the redirector (a loadable file system driver that is not dependent on the system’s hardware architecture. Its function is to direct an I/O request from a user or application to the remote server that has the appropriate file or resource needed to satisfy the request.)
In terms of file names, Windows 95 has both the DOS format (eight-character file name and three-character extension) and a long file name for each file. The VFAT and CDFS build special directory entries containing long file names that can have up to 255 characters.
Another part of the File Management Subsystem is the block input/output subsystem that handles access to the hard drive. The uppermost layer of the block I/O subsystem is called the input/output supervisor (IOS). This IOS manages access of the higher-layer components in the file management subsystem within the hard disk. Below the IOS layer are the components that allow direct access to the disk hardware, the miniport driver, and the small computer system interface (SCSI) layer. The SCSI layer contains the functions that are common to all SCSI devices. The miniport driver contains the functions that are specific to an individual SCSI device. Therefore, the miniport driver is the only component that is hardware dependent.
File System
Windows 95 was not designed specifically for distributed systems. The designers of Windows 95 assumed that most PC’s would be stand-alone, and therefore sharing capabilities were not needed. However the Windows 95 operating system can blend into several networking environments. The OS can be configured to perform peer services, but can only be implemented on a network where every workstation is running Windows 95. A Windows 95 workstation can be configured so it requires a user’s name and password to be passed to a Windows NT or Netware server. By relying on the security available on existing network servers, networked Windows 95 machine are more secure.
Memory Management
Windows 95 introduced a much more highly advanced system of memory management than existed in its predecessors. Introduced was the idea of protected mode memory, which is utilized by 32-bit code. Most of the operating system is written in 32-bit code for this reason, yet the user is still able to utilize older device drivers and software that has 16-bit code in it. The 16-bit code is able to run within a virtual machine created by the OS. There was a significant amount of thought that went into the design of the memory model to allow compatibility between systems as well as adding powerful features. One restriction in adopting Windows 95 and its new memory management model is that it requires new features of the 386 processor to function properly, and is one of the major underlying reasons why Windows 95 relies heavily on the Intel architecture.
In the past, 16-bit code was only allowed to run in what is called real mode. The process had access to the first megabyte of memory directly and only one process could run at a time. Once the 286 processor came out, the ability to switch between active processes was able to occur. This combined with the fact of programs becoming more complex arose the question of how to give more memory to each process. Enhancements were made to the system in new facilities such as HIMEM.SYS which allowed 16-bit processes to address more than 1 megabyte of memory. With the advent of the 386 processor, features such as virtual memory could be implemented as well.
The memory structure of Windows 95 is built as a demand-paged virtual memory system. The memory is structured in a linear fashion utilizing a 32-bit memory space which has a maximum addressable size of four gigabytes. The OS however allows applications to address up to two gigabytes and reserves the rest for itself. In previous systems, such as those based on 16-bit code, memory management was more complex and was broken up into segments. With the new virtual address space, each process thinks it has one contiguous area of allocated space and utilizes new hardware capabilities of the 386 processor to translate the virtual spaces into physical locations.
For compatibility, 16-bit code can also run on Windows 95 under virtual real mode. As far as the program knows, it is the only process running, has direct access to the first megabyte of memory, and can address this space with 16-bits. Windows 95 takes care of the management, translates the addresses to 32-bit and even uses virtual memory.
Windows 95 handles allocation and deallocation of memory space via the VM Manager. Among its responsibilities is protecting each processes memory space, sharing memory, kernel, swapping memory to and from disk, and eliminating memory fragmentation. In order to keep track of pages on disk it uses a page directory, page table, and page frames. Each process has a page table, which lists all of the frames allocated to it. The main page directory therefore keeps track of the addresses of all the page tables in the system. A page frame is addressed to by an entry in the page table, and refers to a physical memory location. The page table also holds information pertinent to each frame such as if its present in memory, when it was last accessed, its privileges etc. As stated earlier, the VM Manager depends on the 386 processor to function. This is due to a specific 64-bit hardware cache built on the chip to store the information about pages that is most likely to be hit the most, and thus gives the OS a significant performance gain when using the virtual memory system. The VM Manager uses the information stored in the page table to identify what each pages purpose is, when it was last used, and assigns each a priority according to the last time it was used. It therefore relies upon a least recently used algorithm for swapping pages to disk. In addition to simply checking on the last time a page was used, it also prefers to keep a page in physical memory if it has been modified. A page that has been in memory yet unmodified is preferably swapped out.
Threads
Windows 95 supports only uniprocessor systems. However, Windows 95 does make use of threads. Threads allow a program to be executing in more then one place of its code simultaneously. Since Windows 95 runs on a single processor system, the threads only appear to be executing simultaneously. Actually, the CPU is being switched between different threads in system. A built in timer notifies the operating system at regular intervals (time slices), and the operating system can then decided whether it should choose a different thread to run. There are two reasons the OS would want to switch threads. One possibility is if one thread creates another thread to accomplish some task, the CPU would be switched to the new thread. Another reason that a thread may be switched away is that it has executed long enough and another thread needs a chance to run. In Windows 95, the default time slice is 20 milliseconds. Theoretically the CPU can switch between 50 threads in 1 minute, which appears to be simultaneous.
Scheduling
The core scheduler in the Windows 95 Virtual Machine Manager (VMM) has no real knowledge of processes. Instead, it concentrates on scheduling the threads with the highest priority, in other words processes don’t really have a priority, the threads do. Each active thread is assigned a priority that is used to determine when it will be scheduled to run. Threads are scheduled for execution by two schedulers: the primary scheduler, which evaluates priorities assigned to threads and schedules them for execution based on priority order, and the time slice scheduler, which regularly adjusts priorities for each thread to ensure that time slices are allocated fairly to all threads. When it comes time to switch the CPU from one thread to another, the processor manager examines the queues containing the threads of equal priority. It looks at the highest priority queue and activates the next thread waiting there. The processor manager then goes through each waiting thread from queue to queue until all have been executed.
However, the schedulers can raise the priority of a thread when certain conditions have been met. When a thread enters a critical region, this upgrade is called dynamic priority boosting. Threads can also inherit priorities from other threads, which is called priority inheritance. This technique is implemented when a low-priority thread is holding resources needed by high-priority threads. In this case, the low-priority thread temporarily inherits the priority of the high-priority thread to speed up its completion and release the needed resources to the high-priority thread. Once it is finished executing its original priority is restored.
Within the VMM scheduler, there are 32 distinct priority levels. These 32 levels are broken into four groups, also known as priority classes. Each priority class is associated with a specific priority level that is the default priority for threads of that priority class. Within the priority class, threads can vary from two below the default priority to two above. A table of the four priority classes, their default priority values, and their range of priority values is as follows:
Table 3: Priority Classes of threads
Priority
|
Default Value
|
Range of priority level
|
Idle
|
4
|
2 - 6
|
Normal
|
9 or 7 (9 if foreground process, 7 otherwise)
|
6 – 10
|
High
|
13
|
11 – 15
|
Realtime
|
24
|
16 – 31
|
Notice how the number 1 isn’t listed. The thread priority of 1 is a special case and has to be specifically set to priority level 1 by the SetPriorityClass function.
A problem does arise, however, when separate threads are trying to access the same resource at the same time, this is called contention. This is solved in Windows 95 through different synchronization techniques. One such technique is that the thread must notify the system that it needs to use a certain shared resource and then goes into a waiting state. When the operating system discovers that the resource is available, it allocates that resource to the waiting thread.
Synchronization
Windows 95 provides several different ways of implementing synchronized threads/processes. Synchronization can be achieved through mutual exclusion of critical sections, usage of mutexes and semaphores. Our main focus will be on those implemented directly in the main kernel and therefore the low level details handled by the operating system itself. Mutual exclusion of critical sections of code is a useful and very efficient method, yet it is only effective in synchronizing threads that share data within one process. If you require synchronization between many processes a method of synchronization must be used that is implemented in the kernel.
Mutexes are handles that exist in the main kernel which have the status of being signaled or unsignaled. When in the signaled state, it lets the other processes that wish to access the part of code or data that the mutex represents, that it is safe to enter that section. When in the unsignaled state, the mutex is linked to the thread that has rights over the shared data, and all other threads that wish to use that shared code cannot and must wait for a signal to be raised and the kernel to wake the thread up for execution. In order for a mutex to be used, it must first be created. This is done by the CreateMutex function:
HANDLE CreateMutex(LPSECURITY_ATTRIBUTE lpsa, BOOL fOwner, LPTSTR lpszName);
The fOwner attribute is a Boolean value, true meaning that the mutex will initially be owned by the calling thread and be in the unsignaled state. A false value here would mean that the mutex will be created and whichever thread wants to use it first will be the first owner. The lpszName is passed the address of a null terminated string to identify the mutex between processes.
The CreateMutex function has another use as well. If a process makes a call to it specifying a mutex name that already exists in the kernel, it will simply return the handle of the existing mutex and not create a new one. This is good if the programmer expects the mutex to be shared between these processes, but in the case that they should be separate mutexes and the name being the same is incidental, one may make a call to GetLastError and check whether a new mutex was in fact created. One other function that may be used to attain an existing mutex handle is OpenMutex:
HANDLE OpenMutex(DWORD fdwAccess, BOOL fInherit, LPTSTR lpszName);
The programmer must specify an existing mutex name as lpszName, and the function will return a handle pointing to the location of the mutex in the kernel if it so exists.
After a thread has been executing, owns a mutex and is done with the shared data, it must now send a signal. This is done by use of the ReleaseMutex function:
BOOL ReleaseMutex(HANDLE hMutex);
The handle of the mutex to be released is passed, and a Boolean response is returned. The function will only succeed if the current owner of the mutex issues the command. No other thread may release someone else's ownership of a mutex. With the fact that a thread must release the mutex after it is done with it, what happens when the thread dies unexpectedly or is exited without releasing its mutex? For this situation there is the idea of abandoned mutexes. Mutexes, as was stated earlier, are associated with a specific thread. If the system senses that the thread that owns a mutex does not exist anymore, it signals the mutex but is sure to let the next thread to take ownership that it was abandoned. This is important because the data that is controlled by the mutex could be in an unstable state from the last thread and the program can make a choice of what to do next.
Regardless of whether a program is waiting for a semaphore or mutex signal, the same system calls are used to sleep the thread execution. Execution may be stopped until a specific signal is made or many objects are signaled. The functions WaitForSingleObject and WaitForMultipleObjects are the appropriate functions:
DWORD WaitForSingleObject(HANDLE hObject, DWORD dwTimeout);
DWORD WaitForMultipleObjects(DWORD cObjects, LPHANDLE lpHandles, BOOL bWaitAll, DWORD dwTimeout);
In the case of waiting for a single signal, the handle of the mutex/semaphore is given as hObject and an amount of time the thread is willing to wait in milliseconds is given as dwTimeout. If a value of zero is as the timeout tells the function that you do not plan on waiting at all and just want to know the signal status of the object. If however a value of 0xFFFFFFFF is passed, then the thread will wait indefinitely and possibly forever.
The WaitForMultipleObjects function adds the possibility of waiting for several conditions at once. The cObjects is the count of how many objects you are checking the status of, which are specified as an array in lpHandles. The maximum amount of objects to check is 64 and there may be no repetition. The only other new parameter is bWaitAll. This is a Boolean value telling the kernel whether all objects must be signaled or only one must signal to resume execution. The values returned from these functions can be seen in the following list:
WAIT_OBJECT_0 0x00000000 A signal has occurred
[+ cObjects - 1] [Has the index value of the object in the
array that signaled]
WAIT_TIMEOUT 0x00000102 The signal timed out
WAIT_ABANDONED 0x00000080 There is a signal because the mutex was
abandoned
[+ cObjects - 1] [Has the index value of the object in the
array that signaled due to abandonment]
WAIT_FAILED 0xFFFFFFFF Error
Semaphores are similar to mutexes but are not owned by any specific thread. Whereas mutexes are useful and mainly used in protection of shared data and critical sections of code, semaphores are more appropriate for resource counting. The reason that semaphores are not owned by any specific thread is that when a resource is being used by a thread many times another process, perhaps a system process, is handling I/O and will release the semaphore for its caller. A semaphore is given an initial value for the resource count when it is created. Like mutexes, the semaphore can be in either a signaled or unsignaled state. If the resource count of the semaphore is positive, it is signaled, otherwise it is unsignaled. Each time a thread requests a semaphore (with the WaitForSingleObject function), if it is in the signaled state, the resource count is decremented and the thread executes, if not, the thread sleeps until the semaphore is signaled and the kernel wakes it up.
The function to create a semaphore is named CreateSemaphore:
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTE lpsa, LONG cSemInitial, LONG cSemMax, LPTSTR lpszSemName);
The important parameters here are cSemInitial and cSemMax. They hold the number of resources available at creation time and the maximum number of resources respectively. When semaphores are to be referred to between separate processes, the same methods can be used as we talked about with mutexes. The existing semaphore name can be passed to either the OpenSemaphore function or CreateSemaphore function to return the valid handle. The OpenSemaphore function is identical to the functionality of the OpenMutex function discussed above.
When requesting a semaphore, you must use the WaitForSingleObject function. You are not able to request multiple semaphores in the same function as you are with mutexes. If several semaphores are needed, multiple calls to the WaitForSingleObject function are required. As for releasing semaphores, it is slightly different than mutexes. The ReleaseSemaphore function can be seen as:
HANDLE ReleaseSemaphore(HANDLE hSemaphore, LONG cRelease, LPLONG lplPrevious);
The main difference as stated above is that any thread may release a semaphore. New to this function is the cRelease parameter. This value indicates how many times the semaphore should be released. If your program had two instances of the semaphore you would simply pass the value two here to release both at the same time. Finally, the lplPrevious attribute returns the resource count of the semaphore back to the user before the semaphore is released.
Process Control Block
A process is defined as a unit of ownership. Each process is associated with a unique value in the system, a processID, and is divided further into threads (subprocesses). One process may have many threads and the threads represent execution, not the processes. When Windows 95 creates a new process, it also creates a new memory context for the process’s threads to execute in. In addition, Windows 95 creates an initial thread of execution for the process. (If needed, the process can create additional threads) The process control block, or process database as Microsoft refers to it, is a block of memory allocated from the KERNEL32 shared memory heap. The specific fields of the process database are shown in the table below:
Table 4: Fields of the Process Control Block
Field Name
|
Description
|
Type
|
The KERNEL32 object type for a process
|
cReference
|
The reference count for the process (the number of things that are currently using the process structure for something)
|
un1
|
Unknown – always is 0
|
pSomeEvent
|
Pointer to an event object
|
TerminationStatus
|
The value returned from the main or WinMain functions
|
un2
|
Unknown – always is 0
|
DefaultHeap
|
Contains the address of the default process heap
|
Memory Context
|
Pointer to the process’s memory context
|
flags
|
Contains bit value of flags
|
pPSP
|
Holds the linear address of the DOS PSP created for this process (field is set for both Win16 and Win32 processes)
|
PSPSelector
|
A selector that points to the DOS PSP for this process (both Win16 and Win32 applications have DOS PSP’s)
|
MTEIndex
|
Contains an index into the global module table
|
cThreads
|
Number of threads belonging to this process
|
cNotTermThreads
|
Holds the number of threads for this process that haven’t yet been terminated.
|
un3
|
Unknown – always is 0
|
cRingOThreads
|
Holds the number of ring O threads as managed by VMM.VXD (for normal applications this is the same number as cThreads)
|
HeapHandle
|
Holds the handle of the HEAP that handle tables belonging to this process should be allocated from
|
W16TDB
|
Holds the Win16 Task Database selector associated with this process
|
MemMapFiles
|
Pointer to the head node in the list of memory mapped files in use by this process
|
pEDB
|
Pointer to the environment database
|
pHandleTable
|
Pointer to a process handle table
|
ParentPDB
|
Pointer to the PROCESS_DATABASE for the process that created this process
|
MODREFlist
|
Points to the head of the process’s module list
|
ThreadList
|
Pointer to the lsit of threads owned by this process
|
DebuggeeCB
|
A debuggee context block
|
LocalHeapFreeHead
|
Points to the head of the free list in the default heap for the process
|
InitialRingOID
|
Unknown – always is 0
|
crst
|
Critical section used by various API functions for synchronizing threads within the same process
|
un4
|
Unknown – always is 0
|
pConsole
|
Points to the console object used for output
|
tlsInUseBits1
|
Represent the status of the lowest 32 Thread Local Storage (TLS) indexes
|
tlsInUseBits2
|
Represents the status of TLS indices 32 – 63
|
ProcessDWORD
|
Unknown
|
ProcessGroup
|
Either 0 or points to the master process ina process group
|
pExeMODREF
|
Points to EXE’s MODREF (module list entry)
|
TopExcFilter
|
Holds the “Top Execution Filter” for the process (called if no other exception handlers choose to handle an exception – kind of like the catch-all)
|
BasePriority
|
Holds the scheduling priority for this process
|
HeapOwnList
|
Points to the head of the linked list of heaps for the process
|
HeapHandleBlockList
|
Pointer to the head of the moveable handle table list within the default process heap
|
pSomeHeapPtr
|
Unknown – normally 0
|
pConsoleProvider
|
Either a 0 or a pointer to a KERNEL32 console object
|
EnvironSelector
|
Holds a selector that points to the process’s environment. (same value as the linear address)
|
ErrorMode
|
Contains the value set by the SetErrorMode function (reflects the Win16 error mode value for the process)
|
pevtLoadFinished
|
Points to a KERNEL32 Event object (signaled when the process has finished loading)
|
UTState
|
Unknown
|
Conclusion
Windows 95/98 is a significant improvement from the previous Windows operating systems. The advancements in Plug and Play, FAT32, 32-bit addressing, and the GUI interface makes Windows 95/98 stand out from other operating systems of that time. These enhancements came at a certain price, requiring at least a 386 processor, 4MB of RAM, and a 30MB hard drive. This system helped pave the way for even better implementations of Windows operating systems including Windows 2000 and Windows XP which incorporated advanced networking features and memory management.
Bibliography
Andrews, Jean, Ph.D. (2000). A+ Guide to Managing and Maintaining Your PC, Third Edition. Cambridge, Massachusetts: Course Technology. ISBN 0-619-00038-4.
Ethington, Brent (1995). Introducing Microsoft Windows 95: The Next Generation of Microsoft Windows. Redmond, Washington: Microsoft Press. ISBN 1-55615-860-2.
Flynn, Ida & McHoes, Ann (1997). Understanding Operating Systems, Second Edition. Boston, Massachusetts: PWS Publishing Company. ISBN 0-534-95093-0.
Meyers, Michael (1998). All-In-One A+ Certification Exam Guide, Second Edition. New York, NY: McGraw-Hill. ISBN 0-07-212266-8.
PCBibliography (2002) “History of Windows” http://members.fortunecity.com/pcmuseum/windows.htm
Pietrek, Matt (1995). Windows 95 System Programming SECRETS. Forest City, California: IDG Books Worldwide, Inc. ISBN 1-56884-318-6.
Richter, Jeffrey (1995). Advanced Windows: The Developers Guide to the Win32 API for Windows NT 3.5 and Windows 95. Redmond, Washington: Microsoft Press. ISBN 1-55615-677-4.
Woram, John (1999). The Windows 98 Registry: A Survival Guide for Users. Foster City, CA: MIS: Press. ISBN 1-55828-591-1.
|