The binary output example relies on several features covered in subsequent chapters, but most features are similar to those used in Java. Please review the following as needed.
In a C++ program, any value can be used to control an if-statement or loop. The program treats 0 as false and non-0 as true. See Boolean Type and Values.
An operating system's primary task is managing a computer's resources, such as the CPU, main memory, and disk drive. Operating systems often partition a drive's storage media into individually addressable "chunks" called blocks. Blocks are said to be allocated when they are in use - when they store data - and unallocated when not in use. A drive's free space consists of the unused blocks. An operating system must quickly allocate a free block when needed and quickly deallocate it (return it to the free space) when it is no longer needed.
As hardware evolves, operating systems implement and discard many free space management algorithms. One technique, no longer in general use, is based on a bit vector or bit map, which is a long string of bits. Keeping the bit map in main memory allows the operating system to allocate and deallocate disk blocks quickly.
The Bit Map Algorithm
Silberschatz, Galvin, & Gagne, 2011, pp. 441-442, describe the bit map free space allocation algorithm. Logically, we can view a disk drive as a sequence of blocks uniquely numbered from 0 to n-1, where n is disk size. Each bit in the bit map represents one disk block.
0 ≤ b < n
bit[b] == 1 ⇒ block[b] is free
bit[b] == 0 ⇒ block[b] is allocated or in use
(a)
(b)
b = (number of bits per word) * (number of 0-value words) + offset to first 1 bit
(c)
(d)
Number of bits per word = 32
number of 0-value words = 3
offset to the first 1 bit = 3
32 * 3 + 3 = 96 + 3 = 99
(e)
(f)
Managing free space with a bit map. The bit map consists of an array of words or unsigned integers. The bit map in this example has seven words, numbered 0 to 6, and assumes 32 bits per word, numbered 0 to 31 from right to left.
The relationship between n, the disk size, and b, an arbitrary disk block number.
The meaning of the bits in the bit map.
To find a free block, the algorithm searches the bit map from 0 to n-1, returning the block number, b, of the first free block found. In this formula, "word" is the CPU's native word size: 32- or 64-bits currently being the most common sizes. For speed and efficiency, the C++ int type and word size are the same.
In this example, the first three words (0, 1, and 2) consist of all 0s, meaning that the disk's first 32*8 = 96 blocks (blocks 0 through 95) are allocated and in use.
In word 3, the first 3 bits (numbered 0, 1, and 2) are also 0s, meaning that blocks 96, 97, and 98 are also allocated.
Plugging in the numbers, we calculate that block 99 is the first free block.
Program
#define MAPSZ ... // the number of words in the bit map
#define WORDSZ (8 * sizeof(int)) // number of bits in a word
unsigned map[MAPSZ]; // the bit map
Program setup. Before we can program the operations to find and allocate a free disk block, we need to complete some basic setup steps.
MAPSZ is a symbolic constant that represents the map size - specifically, the number of words in the map. As the actual size of the map is a function of the managed disk drive and doesn't affect the following functions, ellipses replace an exact size.
WORDSZ is the number of bits in a word. We use the sizeof operator, which returns the size of a data type measured in bytes. We assume a common 8 bits per byte and calculate the total number of bits. We use this technique because the number of bits in a word is a function of the underlying hardware. Unlike most operations, the preprocessor performs the calculation to find WORDSZ and passes a constant value to the compiler component.
map is the bit map or bit vector, which we implement as an array of unsigned integers. If we write in an object-oriented language, we can implement the array as a class member; otherwise, we must implement it as a global variable.
int allocate()
{
for (int i = 0; i < MAPSZ; i++) // 1.a
if (map[i]) // 1.b
for (int offset = 0; offset < WORDSZ; offset++) // 1.c
{
unsigned mask = 1U << offset; // 1.d
if (map[i] & mask) // 1.e
{
map[i] &= ~mask; // 2
return WORDSZ * i + offset; // 3.a
}
}
return -1; // 3.b
}
Finding and allocating a free disk block. Allocating a block requires three steps: (1) finding a free block, (2) updating the bit map, indicating that the block is now allocated, and (3) returning the block number or a failure indicator (i.e., the drive is full):
Finding a free disk block
Searches through the bit map from the beginning to the end.
Identifies the first non-0 word in the map - the first word with at least one 1-bit. C++ treats 0 as false and non-0 as true.
Searches all bits in wordi (i.e., map[i]).
Creates a bitmask with one 1-bit at the offset position. The bit moves left one position with each (inner) loop iteration.
1U: Creates the initial bitmask as an unsigned integer with one 1-bit in the least significant position.
1U << offset: Shifts or moves the 1-bit left by offset positions; offset is the inner loop control variable.
if (map[i] & (1U << offset)): Compares map[i] to bitmask. The bitwise-AND operation forms an expression zeroing out the bits in map[i] except the bit in the offset position, which "passes through" unchanged. If map[i] has a 1 at the offset position, the expression is true, and the algorithm allocates a free block.
Update the bit map, indicating the allocation of a disk block
~mask: The complement operator, ~, "flips" the mask bits, changing the 1s to 0s and the 0s to 1s, creating an expression consisting of all 1s except at the offset position.
map[i] &= ~mask: The bitwise-AND with assignment changes the offset bit in map[i] to 0 and saves the result bask to map[i].
Return the block number or failure indicator
Returns the calculated block number.
Returns an invalid block number, indicating the algorithm failed to find a free block - the disk is full!
The elaborating description is significantly longer than the code itself.
Deallocating a disk block. b is a previously allocated disk block. To deallocate the block, we must change the corresponding bit in the map from 0 (denoting an allocated block) to 1 (denoting a free block). It takes us several steps to deallocate the block, but the steps are small enough that we can do them in a single statement:
b / WORDSZ: Integer division locates the bit map word containing the bit the function must change.
b % WORDSZ: The mod operator calculates the offset position of the bit in the word.
1U: Creates a bitmask with 1 in the least significant position and 0s in the other positions.
(1U << (b % WORDSZ)): The left shift operator moves the 1-bit left to the offset position.
map[b / WORDSZ] |= (1U << (b % WORDSZ)): the bitwise-OR with assignment operator occurs between one word in the bit map and the bitmask. It changes the bit corresponding to the allocated block, b, from 0 to 1. The updated bit map word is stored back in the map.
Silberschatz, A., Galvin, B. G., & Gagne, G. (2011). Operating Systems Concepts Essentials. Hoboken, NJ: John Wiley & Sons, Inc.