microsoft/openvmm
Publicmirrored fromhttps://github.com/microsoft/openvmmAvailable
Guide/src/dev_guide/contrib/save-state.md
92lines · modecode
| 1 | # Save State |
| 2 | |
| 3 | OpenHCL supports the mechanism of saving & restoring state. This primitive can |
| 4 | be used for various VM operations, but a key use case is for updating OpenHCL at |
| 5 | runtime (a.k.a. "Servicing"). This save state can be stored in memory or on |
| 6 | durable media, read back at a later time. |
| 7 | |
| 8 | ## Save State First Principles |
| 9 | |
| 10 | Here are the principles you must maintain when adding new save & restore code: |
| 11 | |
| 12 | 1. **Save & Restore is Forward & Backwards Compatible**: A newer version of |
| 13 | OpenHCL must understand save state from a prior version, and an older version |
| 14 | must not crash when reading save state from a newer version. |
| 15 | 2. **Do not break save state after that state is in use**: Save state must be |
| 16 | compatible from *any* commit to any other commit, once the product has |
| 17 | shipped and started using that save state.[^1] |
| 18 | 3. **All Save State is Protocol Buffers**: All save state is encoded as |
| 19 | `ProtoBuf`, using `mesh`. |
| 20 | |
| 21 | ## Best practices |
| 22 | |
| 23 | 1. **Put save state in it's own module**: This makes PR reviews easier, to catch |
| 24 | any mistakes updating save state. |
| 25 | 2. **Create a unique package per crate**: A logical grouping of saved state |
| 26 | should have the same `package` |
| 27 | 3. **Avoid unsupported types, when possible**: These types don't support the |
| 28 | default values needed for safely extending save state: |
| 29 | * Arrays: if you need to add an array, consider a `vec` or `Option<[T; N]>` |
| 30 | instead. |
| 31 | * Enum: if you need to add an enum, add it as `Option<MyEnum>' instead. |
| 32 | 4. **Be particularly careful when updating saved state**: See below. |
| 33 | |
| 34 | ### Updating (Extending) Saved State |
| 35 | |
| 36 | Since saved state is just Protocol Buffers, use the [guide to updating Protocol |
| 37 | Buffers messages](https://protobuf.dev/programming-guides/proto3/#updating) as a |
| 38 | starting point, with the following caveats: |
| 39 | |
| 40 | 1. OpenVMM uses `sint32` to represent the `i32` native type. Therefore, changing |
| 41 | `i32` to `u32` is a breaking change, for example. |
| 42 | 2. The Protocol Buffers docs mention what happens for newly added fields, but it |
| 43 | bears adding some nuance here: |
| 44 | 1. Plain `arrays` and `enums` are not supported. Reading newer save state |
| 45 | with an `array` or `enum` will fail on an older build. Instead, wrap |
| 46 | these in an `Option`. |
| 47 | 2. Old -> New Save State: Save state from a prior revision will not contain |
| 48 | some newly added fields. Those fields will get the [default |
| 49 | values](https://protobuf.dev/programming-guides/proto3/#default). This is |
| 50 | how that breaks down for the rust types: |
| 51 | * `Option<T>` => `None` |
| 52 | * Structs => each field gets that field's default value |
| 53 | * Vecs => empty vec |
| 54 | * Numbers => 0 |
| 55 | * Strings => `""` |
| 56 | 3. New -> Old Save State: Unknown fields are ignored. |
| 57 | 3. Ensure that default values for new saved state fields make semantic sense. |
| 58 | Here is the heuristic: if you need a conditional to check if the restored |
| 59 | value is the default because it was not included in save state, you should |
| 60 | use an `Option`. If you wouldn't otherwise need a conditional, you should not |
| 61 | use an `Option`. |
| 62 | 4. Define your saved state so that the natural default of `bool` types is |
| 63 | `false`. |
| 64 | |
| 65 | ## Defining Saved State |
| 66 | |
| 67 | Saved state is defined as a `struct` that has `#[derive(Protobuf)]` and |
| 68 | `#[mesh(package = "package_name")]` attributes. Here is an example, taken from |
| 69 | the `nvme_driver`: |
| 70 | |
| 71 | ```rust |
| 72 | pub mod save_restore { |
| 73 | use super::*; |
| 74 | |
| 75 | /// Save/restore state for IoQueue. |
| 76 | #[derive(Protobuf, Clone, Debug)] |
| 77 | #[mesh(package = "nvme_driver")] |
| 78 | pub struct IoQueueSavedState { |
| 79 | #[mesh(1)] |
| 80 | /// Which CPU handles requests. |
| 81 | pub cpu: u32, |
| 82 | #[mesh(2)] |
| 83 | /// Interrupt vector (MSI-X) |
| 84 | pub iv: u32, |
| 85 | #[mesh(3)] |
| 86 | pub queue_data: QueuePairSavedState, |
| 87 | } |
| 88 | } |
| 89 | ``` |
| 90 | |
| 91 | [^1]: Saved state is in use when it reaches a release branch that is in tell |
| 92 | mode. See [release management](./release.md) for details. |