2.11.3. Free Space Management

Forward References

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.

Review

The example uses many C++ features introduced in the last chapter. Please review the following as needed.

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)
A series of rectangles representing the words in a bit map. Each word contains 32 bits. All the bits in the first three words and the first three bits in word 3 (the fourth word) are 0s. Bit 4 of the 4th word (word 3 because we begin numbering with 0) is the first 1 bit.
(d)
A close-up of the 4th word, word 3, shows that the offset to the first 1 bit is 3.
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.
  1. The relationship between n, the disk size, and b, an arbitrary disk block number.
  2. The meaning of the bits in the bit map.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
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):
  1. Finding a free disk block
    1. Searches through the bit map from the beginning to the end.
    2. 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.
    3. Searches all bits in wordi (i.e., map[i]).
    4. Creates a bitmask with one 1-bit at the offset position. The bit moves left one position with each (inner) loop iteration.
      1. 1U: Creates the initial bitmask as an unsigned integer with one 1-bit in the least significant position.
      2. 1U << offset: Shifts or moves the 1-bit left by offset positions; offset is the inner loop control variable.
    5. 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.
  2. Update the bit map, indicating the allocation of a disk block
    1. ~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.
    2. 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].
  3. Return the block number or failure indicator
    1. Returns the calculated block number.
    2. 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.
void deallocate(int b)
{
	map[b / WORDSZ] |= (1U << (b % WORDSZ));
}
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:
  1. b / WORDSZ: Integer division locates the bit map word containing the bit the function must change.
  2. b % WORDSZ: The mod operator calculates the offset position of the bit in the word.
  3. 1U: Creates a bitmask with 1 in the least significant position and 0s in the other positions.
  4. (1U << (b % WORDSZ)): The left shift operator moves the 1-bit left to the offset position.
  5. 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.