NVRAM Module
Logo Image
Phase 1 Rewrite

1 Overview

In the beginning nvram was intended to be used for router configuration info only, and the data was allowed to grow until all the memory was used up. Over time other uses for nvram were found and implemented without a full review of the current design. This has lead to an nvram implementation that is full of hacks and patches that the current design cannot handle reliably, yet the number of requirements and feature requests continue to grow. To meet these demands the typical response has been to increase the amount of nvram memory. This approach simply does not scale and the current implementation has reach it's limit.

This specification proposes to re-implement nvram as a well defined module with managed dependencies and a clean and rigidly enforced api. The design will be robust enough to accommodate the current requirements even as the amount of memory used continues to increase, and will be versatile enough to accommodate new uses as they arise.

More precisely, the new implementation will simulate a real file system with individual files and protected partitions. It will handle the special needs of nvram internally, giving the developer the tools to implement any number of algorithms to meet various design requirements without the need to know any details of the nvram implementation.

The longer range goal is to componentize the nvram module. This will put the implementation out of reach and present just the api to the rest of IOS. Only then can the nvram module be protected from feature creep and code evolution. Thus nvram would be expected to stabilize and become more robust over time.

1.1 Problem Analysis

The current implementation of nvram allocates a contiguous block of memory, and it is up to the developer to parse the memory into it's constituent parts. For historical reasons the main part of nvram is the startup configuration. At some point this "public" configuration data was extended to include some "private" configurations, but the memory space was not divided into sections to accommodate this new type of data. Instead the private configs were simply appended to the end of the startup configs, meaning that the calling process must keep track of the configuration boundaries.

More recently a third type of data was added, called persistent data, which was not specific to router configuration, but which needed to be stored securely and locally on the router. Again, no memory space was craved out for this new type of data but instead it was simply added to the very end of nvram memory. 

One of the characteristics of nvram is that it's actually a serial storage device mapped as an ifs file. The "ram" part of nvram is a misnomer, and serial access means that all of the configuration data must be read into a buffer where it can be modified and then written out as a contiguous block, overwriting all of the previous data. Thus, adding data to the startup config requires shifting some part of the buffered data to the right in order to make room, and deleting means shifting the buffered data to the left in order to overwrite the old data. All of this is done in the system memory buffer and not in nvram.

Another characteristic of nvram is that it is non-removable. The nvram memory hardware is part of the router, and that fact has certain security implications. Thus nvram has become the preferred place to store "persistent" data that remains securely with the router at all times and across router reloads.

In order to avoid complicating the process for storing configuration data, the persistent data is stored in blocks starting at the end of nvram memory space and working backwards. This obviously does not scale, and the inevitable question becomes which is more "persistent" when nvram is full ?? The fact is that the nvram memory space is not controlled and the configuration data is not prevented from using all of nvram memory, and it will simply overwrite the persistent data if the memory is needed.

Thus the current implementation of nvram is a programming challenge because it leads to conflicts when memory is at a premium, and the calling process must detect a conflict in order to issue a message if the persistent data is about to be overwritten. It is also up to the calling process to deal with the fact that the configuration data is actually two sets of data, public and private, and must keep track of that boundary when making modifications.

Another complication is that because nvram is a serial device a read or write operation must be allowed to suspend in order to prevent potentially large data transfers form hogging the cpu. During that suspend the nvram ifs file is held open, and if another process tries to access any of the config or persistent data it fails with a "busy" error. It's then up to the calling process to either suspend and try again later or just give up.

Finally, the checksum calculation has created a performance problem. The checksum is primarily used to validate nvram and verify that it has not been corrupted between writes and between router reloads. In order to accomplish this, all of nvram must be checksum'd after every write operation because there are no stable boundaries within it's memory space and so no way to tell if only a portion needs to be verified. On platforms with large amounts of nvram this can mean that saving even a simple configuration change can take an unacceptable amount of time to complete.

1.2 Modular Solution

The nvram module partitions its memory space into file-blocks and protects those file's boundaries. A file is then accessed using nvram api functions that open, read, write, seek, configure and close the file. What a calling process gets is a buffer of data of a specified size from the specified nvram file. The buffer can be searched, modified, extended, deleted, etc, and then written back to nvram.

There are three major differences between this and the old implementation:

1. calling process does not have to consider the boundary between startup configs and private configs.
2. calling process does not have to deal with conflicts between config and persistent data.
3. calling process can request any portion of the file data instead of the whole thing.

For example, a process could request all of the persistent data, or set up a loop to grab some buffer size one at a time, then search, modify, extend and delete various sections of the buffer, and then write only the buffers that were modified back to the persistent file. The nvram algorithms will figure out what data blocks need to be inserted, what blocks need to be deleted and what blocks to be modified. In any case, the data will not be written outside of the file's boundaries.

These changes are also useful for accessing the startup config file, which is uploaded into system memory at runtime, and is passed back to the nvram code as a contiguous buffer at "savetime". The nvram module will parse the buffered data into blocks, and only those blocks that have changed will be written back to nvram and have it's checksum re-generated.

The new nvram module does not hold the nvram ifs file open between operations. Calling the "open" api function creates and initializes a file access node but does not actually open the nvram ifs file. A "read" or "write" function call opens, reads and closes the nvram ifs file before returning. This allows the calling process to suspend for any amount of time, for example waiting on "auto-more", and yet not block other processes from accessing nvram. Although a large data transfer may still generate a suspend within the nvram module, it's guaranteed an nvram suspend will to be scheduled to run again as soon as possible.

1.3 New Characteristics

The new nvram module has the following new characteristics:
  1. Assumes that a write starts where the last read started, unless there was a seek. Thus a read-modify-write operation will not require a seek to reposition the file pointer.
  2. Doesn't start writing until the first modification is detected. If the length changes, in "legacy" mode the rest of the file is shifted accordingly. When in "file" mode (i.e. not supporting the old memory map) the module can insert non-contiguous blocks of nvram memory where needed. In this way the rest of the file data doesn't have to be shifted to make room.   
  3. Generates a checksum for each individual memory block instead of for all of nvram. 
The following are some examples on using the api functions. Note that section 4.3 lists the set of possible error codes returned by the nvram module, and section 4.4 gives a detailed description of the api functions and the error codes each can return.

1.3 Compatibility Issues

It is not realistic to expect to re-implement all of nvram across all platforms and then require all the processes that use nvram to update their code. Nor is it realistic to expect all router customers to update their images for a feature enhancement that may not be useful to some. It's more likely that over time developers and customers will use the new nvram api when the current limitations become an obstacle.

This means that the new nvram module must exist alongside the old implementation, although this is problematic because the new nvram module changes the way data is stored. In theory the new code could just map the files to match the old memory space, except that the old private config file boundary changes with the size of the startup config. In otherwords, the location of the private config file keeps moving.

The nvram module must also provide the tools to migrate to the new paradigm when all of IOS has made the conversion. This would be version specific and would necessitate the option to convert back to the old memory layout before an older IOS version is loaded.

This approach would require little if any change to the old code base, and puts the burden on the new implementation.







[ Home Page
[ Contents ]
[ Next Section : 2 ]