|
| 1 | +## FileHandle |
| 2 | + |
| 3 | +<span class="images"><span>FileHandle class hierarchy</span></span> |
| 4 | + |
| 5 | +For general information on [files](file.html) and [filing systems](filesystem.html) in persistent storage, see their documentation. This chapter covers the abstract API, with an emphasis on devices. |
| 6 | + |
| 7 | +`FileHandle` is an abstract class representing a device that supports file-like operations, such as `read` and `write`. This may be an actual `File` on a storage device provided by a `FileSystem` or a device such as `UARTSerial`. |
| 8 | + |
| 9 | +The `FileHandle` abstraction represents an already-opened file or device, so it has no `open` method of its own - the opening may take the form of a call to another class that returns a `FileHandle`, such as `FileSystem::open`, or it may be implicit in the construction of an object such as `UARTSerial`. |
| 10 | + |
| 11 | +The `FileHandle` abstraction permits stream-handling code to be device-independent, rather than tied to a specific device like a serial port. Examples of such code in Mbed OS are: |
| 12 | + |
| 13 | +- The console input and output streams (`stdin` and `stdout`). |
| 14 | +- The `ATCmdParser` helper. |
| 15 | +- The PPP connection to lwIP. |
| 16 | + |
| 17 | +Exactly which operations a `FileHandle` supports depends on the underlying device, and in turn restricts what applications it is suitable for. For example, a database application might require random-access and `seek`, but this may not be available on a limited file system, and certainly not on a stream device. Only a `File` on a full `FileSystem`, such as `FATFileSystem`, would generally implement the entire API. Specialized devices may have particular limitations or behavior, which limit their general utility. Devices that do not implement a particular call indicate it by an error return - often `ENOSYS`, but sometimes more specific errors, such as `ESPIPE` apply; please see the POSIX specifications for details. |
| 18 | + |
| 19 | +#### Relationship of FileHandle to other APIs |
| 20 | + |
| 21 | +You can use a `FileHandle` directly, or you can use standard POSIX or C/C++ APIs to manipulate it. Stdio calls taking `FILE *stream` call the POSIX APIs taking `int fd`, which call methods on `FileHandle` objects. |
| 22 | + |
| 23 | +<span class="images"></span> |
| 24 | + |
| 25 | +The `FileHandle` may be implicitly created by a higher layer, as in a call to `fopen`. In this case, the name lookup produces a `FileHandle` and POSIX file descriptor internally. |
| 26 | + |
| 27 | +The three APIs provide different levels of capability: |
| 28 | + |
| 29 | +API | C/C++ | POSIX | Mbed |
| 30 | +-------------------------------|-------------------------|------------------------|----------------------------- |
| 31 | +Headers | stdio.h, iostream | mbed_retarget.h | FileHandle.h, mbed_poll.h |
| 32 | +Main type | FILE * | int | FileHandle object |
| 33 | +Blocking I/O | Yes (always) | Yes (default) | Yes (default) |
| 34 | +Nonblocking I/O | No | Yes | Yes |
| 35 | +Poll | No | Yes (struct pollfd) | Yes (struct pollfh) |
| 36 | +Sigio | No | No | Yes |
| 37 | +Device-specific extensions | No | No | Possible using derived types |
| 38 | +Newline conversion | Yes (enabled with JSON) | No | No |
| 39 | +Error indications | EOF, ferror, set errno | Return -1, set errno | Return negative error code |
| 40 | +Portability | High | Medium | Low |
| 41 | + |
| 42 | +You can mix the APIs if you're careful, for example setting up a callback initially with `FileHandle::sigio` but performing all subsequent operations using POSIX. |
| 43 | + |
| 44 | +<span class="notes">**Note:** `errno` is not thread-local on all toolchains. This may cause problems with error handling if multiple threads are using POSIX or C file APIs simultaneously.</span> |
| 45 | + |
| 46 | +#### Mapping between APIs |
| 47 | + |
| 48 | +Calls are provided to attach already-opened lower levels to the higher levels: |
| 49 | + |
| 50 | +- `int mbed_bind_to_fd(FileHandle *)` bind a FileHandle to a POSIX file descriptor. |
| 51 | +- `FILE *fdopen(int fd, const char *mode)` bind a POSIX file descriptor to a stdio FILE. |
| 52 | +- `FILE *fdopen(FileHandle *fh, const char *mode)` bind a FileHandle to a stdio FILE. |
| 53 | + |
| 54 | +The standard POSIX function `int fileno(FILE *stream)` may be available to map from `FILE` to file descriptor, depending on the toolchain and C library in use - it is not usable in fully portable Mbed OS code. |
| 55 | + |
| 56 | +As it is not possible to map from higher levels to lower levels, if code needs to access the lower levels, use a lower-level open call, so the lower-level handle is known. Then, bind that to the higher level.. |
| 57 | + |
| 58 | +The POSIX file descriptors for the console are available as `STDIN_FILENO`, `STDOUT_FILENO` and `STDERR_FILENO`, permitting operations such as `fsync(STDERR_FILENO)`, which would for example drain `UARTSerial`s output buffer. |
| 59 | + |
| 60 | +#### Redirecting the console |
| 61 | + |
| 62 | +If a target has serial support, by default a serial port is used for the console. The pins and settings for the port selection come from target header files and JSON settings. This uses either an internal `DirectSerial` if unbuffered (for backwards compatibility) or `UARTSerial` if `platform.stdio-buffered-serial` is `true`. |
| 63 | + |
| 64 | +The target can override this by providing `mbed::mbed_target_override_console` to specify an alternative `FileHandle`. For example, a target using SWO might have: |
| 65 | + |
| 66 | +``` |
| 67 | + namespace mbed |
| 68 | + { |
| 69 | + FileHandle *mbed_target_override_console(int) |
| 70 | + { |
| 71 | + static SerialWireOutput swo; |
| 72 | + return &swo; |
| 73 | + } |
| 74 | + } |
| 75 | +``` |
| 76 | + |
| 77 | +Then any program using `printf` on that target sends its output over the SWO, rather than serial. |
| 78 | + |
| 79 | +Because targets can redirect the console in this way, portable applications should not use constructs like `Serial(USBTX, USBRX)`, assuming that this will access the console. Instead they should use `stdin`/`stdout`/`stderr` or `STDIN_FILENO`/`STDOUT_FILENO`/`STDERR_FILENO`. |
| 80 | + |
| 81 | +``` |
| 82 | + // Don't do: |
| 83 | + Serial serial(USBTX, USBRX); |
| 84 | + serial.printf("Hello!\r\n"); |
| 85 | + |
| 86 | + // Do do: |
| 87 | + printf("Hello!\n"); // assume platform.stdio-convert-newlines is true |
| 88 | +``` |
| 89 | + |
| 90 | +Beyond the target-specific override, an application can override the target's default behavior itself by providing `mbed::mbed_override_console`. |
| 91 | + |
| 92 | +Alternatively, an application could use the standard C `freopen` function to redirect `stdout` to a named file or device while running. However there is no `fdreopen` analogue to redirect to an unnamed device by file descriptor or `FileHandle` pointer. |
| 93 | + |
| 94 | +#### Polling and nonblocking |
| 95 | + |
| 96 | +By default `FileHandle`s conventionally block until a `read` or `write` operation completes. This is the only behavior supported by normal `File`s, and is expected by the C library's `stdio` functions. |
| 97 | + |
| 98 | +Device-type `FileHandle`s, such as `UARTSerial`, are expected to also support nonblocking operation, which permits the `read` and `write` calls to return immediately when unable to transfer data. Please see the API reference pages of these functions for more information. |
| 99 | + |
| 100 | +For a timed wait for data, or to monitor multiple `FileHandle`s, see [`poll`](poll.html) |
| 101 | + |
| 102 | +#### Event-driven I/O |
| 103 | + |
| 104 | +If using nonblocking I/O, you probably want to know when to next attempt a `read` or `write` if they indicate no data is available. `FileHandle::sigio` lets you attach a `Callback`, which is called whenever the `FileHandle` becomes readable or writable. |
| 105 | + |
| 106 | +Important notes on sigio: |
| 107 | + |
| 108 | +- The sigio may be issued from interrupt context. You cannot portably issue `read` or `write` calls directly from this callback, so you should queue an [`Event`](event.html) or wake a thread to perform the `read` or `write`. |
| 109 | +- The sigio callback is only guaranteed when a `FileHandle` _becomes_ readable or writable. If you do not fully drain the input or fully fill the output, no sigio may be generated. This is also important on start-up - don't wait for sigio before attempting to read or write for the first time, but only use it as a "try again" signal after seeing an `EAGAIN` error. |
| 110 | +- Spurious sigios are permitted - you can't assume data will be available after a sigio. |
| 111 | +- Given all the above, use of sigio normally implies use of nonblocking mode or possibly `poll`. |
| 112 | + |
| 113 | +Ordinary files do not generate sigio callbacks because they are always readable and writable. |
| 114 | + |
| 115 | +#### Stream-derived FileHandles |
| 116 | + |
| 117 | +`Stream` is a legacy class that provides an abstract interface for streams similar to the `FileHandle` class. The difference is that the `Stream` API is built around the `getc` and `putc` set of functions, whereas `FileHandle` is built around `read` and `write`. This makes implementations simpler but limits what is possible with the API. Because of this, implementing the `FileHandle` API directly is suggested API for new device drivers. |
| 118 | + |
| 119 | +Note that `FileHandle` implementations derived from `Stream`, such as `Serial`, have various limitations: |
| 120 | + |
| 121 | +- `Stream` does not support nonblocking I/O, poll or sigio. |
| 122 | +- `Stream` does not have correct `read` semantics for a device - it always waits for the entire input buffer to fill. |
| 123 | +- `Stream` returns 0 from `isatty`, which can slightly confuse the C library (for example defeating newline conversion and causing buffering). |
| 124 | + |
| 125 | +As such, you can only use `Stream`-based devices for blocking I/O, such as through the C library, so we don't recommend use of `Stream` to implement a `FileHandle` for more general use. |
| 126 | + |
| 127 | +### FileHandle class reference |
| 128 | + |
| 129 | +[](http://os.mbed.com/docs/v5.11/mbed-os-api-doxy/classmbed_1_1_file_handle.html) |
| 130 | + |
| 131 | +### FileHandle using C library example |
| 132 | + |
| 133 | +``` |
| 134 | +// Continuously monitor a serial device, and every time it outputs a |
| 135 | +// character, send it to the console and toggle LED2. Can use the C library |
| 136 | +// to access the device as only using blocking I/O. |
| 137 | +// |
| 138 | +// Note that the console is accessed using putchar - this will be accessing |
| 139 | +// a FileHandle-based device under the surface, but the particular device can be |
| 140 | +// target-dependent. This makes the program portable to different devices |
| 141 | +// with different console types, with the only target-dependence being |
| 142 | +// knowledge of which pins the serial device we're monitoring is attached to, |
| 143 | +// which can be configured using JSON. |
| 144 | +
|
| 145 | +static DigitalOut led2(LED2); |
| 146 | +
|
| 147 | +// UARTSerial derives from FileHandle |
| 148 | +static UARTSerial device(MBED_CONF_APP_DEVICE_TX, MBED_CONF_APP_DEVICE_RX); |
| 149 | +
|
| 150 | +int main() |
| 151 | +{ |
| 152 | + // Perform device-specific setup |
| 153 | + device.set_baud(19200); |
| 154 | + |
| 155 | + // Once set up, access through the C library |
| 156 | + FILE *devin = fdopen(&device, "r"); |
| 157 | + |
| 158 | + while (1) { |
| 159 | + putchar(fgetc(devin)); |
| 160 | + led2 = !led2; |
| 161 | + } |
| 162 | +} |
| 163 | +``` |
| 164 | + |
| 165 | +### FileHandle sigio example |
| 166 | + |
| 167 | +``` |
| 168 | +// Main thread flashes LED1, while we monitor a serial-attached device |
| 169 | +// in the background. Every time that device outputs a character, we echo |
| 170 | +// it to the console and toggle LED2. |
| 171 | +#include "mbed.h" |
| 172 | +
|
| 173 | +static DigitalOut led1(LED1); |
| 174 | +static DigitalOut led2(LED2); |
| 175 | +
|
| 176 | +static UARTSerial device(MBED_CONF_APP_DEVICE_TX, MBED_CONF_APP_DEVICE_RX); |
| 177 | +
|
| 178 | +static void callback_ex() |
| 179 | +{ |
| 180 | + // always read until data is exhausted - we may not get another |
| 181 | + // sigio otherwise |
| 182 | + while (1) { |
| 183 | + char c; |
| 184 | + if (device.read(&c, 1) != 1) { |
| 185 | + break; |
| 186 | + } |
| 187 | + putchar(c); |
| 188 | + led2 = !led2; |
| 189 | + } |
| 190 | +} |
| 191 | +
|
| 192 | +int main() |
| 193 | +{ |
| 194 | + // UARTSerial-specific method - all others are from FileHandle base class |
| 195 | + device.set_baud(19200); |
| 196 | + |
| 197 | + // Ensure that device.read() returns -EAGAIN when out of data |
| 198 | + device.set_blocking(false); |
| 199 | + |
| 200 | + // sigio callback is deferred to event queue, as we cannot in general |
| 201 | + // perform read() calls directly from the sigio() callback. |
| 202 | + device.sigio(mbed_event_queue()->event(callback_ex)); |
| 203 | +
|
| 204 | + while (1) { |
| 205 | + led1 = !led1; |
| 206 | + wait(0.5); |
| 207 | + } |
| 208 | +} |
| 209 | +
|
| 210 | +``` |
| 211 | + |
| 212 | +### Related content |
| 213 | + |
| 214 | +- [File](file.html). |
| 215 | +- [FileSystem](filesystem.html). |
| 216 | +- [Poll](poll.html). |
0 commit comments