Skip to content

Commit b129a76

Browse files
committed
Add FileHandle/poll documentation
1 parent effe8d0 commit b129a76

File tree

1 file changed

+274
-0
lines changed

1 file changed

+274
-0
lines changed
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
## FileHandle
2+
3+
For general information on [files](file.html) and [filing systems](filesystem.html), see the respective documentation. This chapter covers the abstract API, with an emphasis on devices, as they're less straightforward.
4+
5+
`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`.
6+
7+
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`.
8+
9+
The `FileHandle` abstraction permits stream-handling code to be device-independent. For example the console input and output streams used for C's `stdin` and `stdout` can be retargeted to something other than the default serial port via the `FileHandle` API, and `ATCmdParser` and the PPP connection to lwIP work on abstract `FileHandle` pointers.
10+
11+
Exactly which operations a `FileHandle` supports will depend on the underlying device, and will in turn restrict 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 filesystem, and certainly not on a stream device. Only a `File` on a full `FileSystem` such as `FATFileSystem` would generally implement the entire API. Specialised devices may have particular limitations or behavior, which they should document, and which would limit their general utility.
12+
13+
### Relationship of FileHandle to other APIs
14+
15+
Mbed `FileHandle`s can be used directly, but they are often manipulated via POSIX or C/C++ APIs. The layering is that stdio calls taking `FILE *stream` call the POSIX APIs taking `int fd`, which call methods on `FileHandle` objects.
16+
17+
The `FileHandle` may be implicitly created by a higher layer, as in a call to `fopen`. In this case, the name lookup will produce a `FileHandle` and POSIX file descriptor internally.
18+
19+
The three APIs provide different levels of capability:
20+
21+
API | C/C++ | POSIX | Mbed
22+
-------------------------------|-------------------------|------------------------|-----------------------------
23+
Headers | stdio.h, iostream | mbed_retarget.h | FileHandle.h, mbed_poll.h
24+
Main type | FILE * | int | FileHandle object
25+
Blocking I/O | Yes (always) | Yes (default) | Yes (default)
26+
Non-blocking I/O | No | Yes | Yes
27+
Poll | No | Yes (struct pollfd) | Yes (struct pollfh)
28+
Sigio | No | No | Yes
29+
Device-specific extensions | No | No | Possible via derived types
30+
Newline conversion | Yes (enabled via JSON) | No | No
31+
Error indications | EOF, ferror, set errno | Return -1, set errno | Return negative error code
32+
Portability | High | Medium | Low
33+
34+
The APIs can be mixed with care, for example setting up a callback initially with `FileHandle::sigio`, but performing all subsequent operations using POSIX.
35+
36+
* Note: `errno` is not currently thread-safe on all toolchains. This may cause problems with error handling if multiple threads are using POSIX or C file APIs simultaneously.
37+
38+
### Mapping between APIs
39+
40+
Calls are provided to attach already-opened lower levels to the higher levels:
41+
42+
* `int mbed_bind_to_fd(FileHandle *)` bind a FileHandle to a POSIX file descriptor
43+
* `FILE *fdopen(int fd, const char *mode)` bind a POSIX file descriptor to a stdio FILE
44+
* `FILE *fdopen(FileHandle *fh, const char *mode)` bind a FileHandle to a stdio FILE
45+
46+
There is currently no call to map from POSIX file descriptor to `FileHandle`; this may be added in future.
47+
48+
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 useable in fully-portable Mbed OS code.
49+
50+
Given those limitations on mapping, if code needs to access the lower levels, a lower-level open call should be used, so the lower-level handle is known, then that is bound to the higher level(s).
51+
52+
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.
53+
54+
### Redirecting the console
55+
56+
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`.
57+
58+
This can be overridden by the target providing `mbed::mbed_target_override_console` to specify an alternative `FileHandle`. For example, a target using SWO might have:
59+
60+
namespace mbed
61+
{
62+
FileHandle *mbed_target_override_console(int)
63+
{
64+
static SerialWireOutput swo;
65+
return &swo;
66+
}
67+
}
68+
69+
Then any program using simple `printf` on that target will send its output over the SWO, rather than serial.
70+
71+
Because the console can be redirected in this way by targets, 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`.
72+
73+
// Don't do:
74+
Serial serial(USBTX, USBRX);
75+
serial.printf("Hello!\r\n");
76+
77+
// Do do:
78+
printf("Hello!\n"); // assume platform.stdio-convert-newlines is true
79+
80+
81+
Beyond the target-specific override, an application can override the target's default behaviour itself by providing `mbed::mbed_override_console`.
82+
83+
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.
84+
85+
### Polling and non-blocking
86+
87+
By default `FileHandle`s conventionally block until a `read` or `write` operation completes. This is the only behaviour supported by normal `File`s, and is expected by the C library's `stdio` functions.
88+
89+
Device-type `FileHandle`s such as `UARTSerial` are expected to also support non-blocking operation, which permits the `read` and `write` calls to return immediately when unable to transfer data. See reference of these functions for more information.
90+
91+
For a timed wait for data, or to monitor multiple `FileHandle`s, `poll()` is provided in two forms:
92+
93+
* `int poll(struct pollfd fds[], nfds_t nfds, int timeout)` standard POSIX form taking array of integer file descriptors
94+
* `int mbed::poll(pollfh fhs[], unsigned nfhs, int timeout)` variant taking an array of `FileHandle *`
95+
96+
See the POSIX specification of `poll` for full details of the API. Mbed OS provides the `POLLIN`, `POLLOUT`, `POLLERR` and `POLLHUP` event flags, but implementation is dependent on device.
97+
98+
As per POSIX, ordinary files always indicate readable and writable via poll.
99+
100+
101+
### Event-driven I/O
102+
103+
If using non-blocking I/O, you will probably want to be know when to next attempt a `read` or `write` if they indicate no data is available. `FileHandle::sigio` lets you attach a `Callback` which will be called whenever the `FileHandle` becomes readable or writable.
104+
105+
Important notes on sigio:
106+
107+
* 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` or wake a thread to perform the `read` or `write`.
108+
* The sigio callback is only guaranteed when a `FileHandle` _becomes_ readable or writable. So 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/write for the first time, but only use it as a "try again" signal after seeing an `EAGAIN` error.
109+
* Spurious sigios are permitted - you can't assume data will be available after a sigio.
110+
* Given all the above, use of sigio normally implies use of non-blocking mode, or possibly `poll`.
111+
112+
Ordinary files do not generate sigio callbacks, because they are always readable and writable, so never become readable or writable.
113+
114+
### Stream-derived FileHandles
115+
116+
Note that `FileHandle` implementations derived from `Stream`, such as `Serial`, have various limitations:
117+
118+
* `Stream` does not support non-blocking I/O, poll or sigio.
119+
* `Stream` does not have correct `read` semantics for a device - it always waits for the entire input buffer to be filled.
120+
* `Stream` returns 0 from `isatty`, which can slightly confuse the C library (eg defeating newline conversion, and causing buffering).
121+
122+
As such, `Stream`-based devices can only be used for blocking I/O, such as through the C library, so use of `Stream` to implement a `FileHandle` for more general use is not recommended.
123+
124+
### FileHandle class reference
125+
[![View code](https://www.mbed.com/embed/?type=library)](http://os-doc-builder.test.mbed.com/docs/development/mbed-os-api-doxy/classmbed_1_1_file_handle.html)
126+
127+
### poll reference
128+
[![View code](https://www.mbed.com/embed/?type=library)](https://os-doc-builder.test.mbed.com/docs/development/mbed-os-api-doxy/group__platform__poll.html)
129+
130+
### FileHandle via C library example
131+
132+
```
133+
// Continuously monitor a serial device, and every time it outputs a
134+
// character, send it to the console and toggle LED2. Can use the C library
135+
// to access the device as only using blocking I/O.
136+
//
137+
// Note that the console is accessed via putchar - this will be accessing
138+
// a FileHandle-based device under the surface, but the particular device can be
139+
// target-dependent. This makes the program portable to different devices
140+
// with different console types, with the only target-dependence being
141+
// knowledge of which pins the serial device we're monitoring is attached to,
142+
// which can be configured via JSON.
143+
144+
static DigitalOut led2(LED2);
145+
146+
// UARTSerial derives from FileHandle
147+
static UARTSerial device(MBED_CONF_APP_DEVICE_TX, MBED_CONF_APP_DEVICE_RX);
148+
149+
int main()
150+
{
151+
// Perform device-specific setup
152+
device.set_baud(19200);
153+
154+
// Once set up, access via C library
155+
FILE *devin = fdopen(&device, "r");
156+
157+
while (1) {
158+
putchar(fgetc(devin));
159+
led2 = !led2;
160+
}
161+
}
162+
```
163+
//
164+
### FileHandle sigio example
165+
166+
```
167+
// Main thread flashes LED1, while we monitor a serial-attached device
168+
// in the background. Every time that device outputs a character, we echo
169+
// it to the console and toggle LED2.
170+
#include "mbed.h"
171+
172+
static DigitalOut led1(LED1);
173+
static DigitalOut led2(LED2);
174+
175+
static UARTSerial device(MBED_CONF_APP_DEVICE_TX, MBED_CONF_APP_DEVICE_RX);
176+
177+
static void callback_ex()
178+
{
179+
// always read until data is exhausted - we may not get another
180+
// sigio otherwise
181+
while (1) {
182+
char c;
183+
if (device.read(&c, 1) != 1) {
184+
break;
185+
}
186+
putchar(c);
187+
led2 = !led2;
188+
}
189+
}
190+
191+
int main()
192+
{
193+
// UARTSerial-specific method - all others are from FileHandle base class
194+
device.set_baud(19200);
195+
196+
// Ensure that device.read() returns -EAGAIN when out of data
197+
device.set_blocking(false);
198+
199+
// sigio callback is deferred to event queue, as we cannot in general
200+
// perform read() calls directly from the sigio() callback.
201+
device.sigio(mbed_event_queue()->event(callback_ex));
202+
203+
while (1) {
204+
led1 = !led1;
205+
wait(0.5);
206+
}
207+
}
208+
209+
```
210+
211+
### Poll example
212+
```
213+
// Transfer bidirectional data between two ports, acting as a virtual link.
214+
// poll() is used to monitor both ports for input.
215+
#include "mbed.h"
216+
217+
// Pins for each port are specified via mbed_app.json. We assume no flow control.
218+
// (If there was flow control, being blocked by it could break the implementation,
219+
// as we would stop reading input in the opposite direction).
220+
UARTSerial device1(MBED_CONF_APP_UART1_TX, MBED_CONF_APP_UART1_RX);
221+
UARTSerial device2(MBED_CONF_APP_UART2_TX, MBED_CONF_APP_UART2_RX);
222+
223+
// Precondition: "in" is readable
224+
static void copy_some(FileHandle *out, FileHandle *in)
225+
{
226+
// To ensure performance, we try to read multiple bytes at once, although
227+
// we don't expect to read many in practice.
228+
char buffer[32];
229+
230+
// Despite the FileHandle being in its default blocking mode,
231+
// read() must return immediately with between 1-32 bytes, as
232+
// we've already checked that `in` is ready with poll()
233+
ssize_t read = in->read(buffer, sizeof buffer);
234+
if (read <= 0) {
235+
error("Input error");
236+
}
237+
238+
// Write everything out. Assuming output port is same speed as input,
239+
// and no flow control, this may block briefly, but not significantly.
240+
ssize_t written = out->write(buffer, read);
241+
if (written < read) {
242+
error("Output error");
243+
}
244+
}
245+
246+
int main()
247+
{
248+
char buffer[32];
249+
pollfh fds[2];
250+
251+
fds[0].fh = &device1;
252+
fds[0].events = POLLIN;
253+
fds[1].fh = &device2;
254+
fds[1].events = POLLIN;
255+
256+
while (1) {
257+
// Block indefinitely until either of the 2 ports is readable (or has an error)
258+
poll(fds, 2, -1);
259+
260+
// Transfer some data in either or both directions
261+
if (fds[0].revents) {
262+
copy_some(fds[1].fh, fds[0].fh);
263+
}
264+
if (fds[1].revents) {
265+
copy_some(fds[0].fh, fds[1].fh);
266+
}
267+
}
268+
}
269+
```
270+
271+
### Related content
272+
273+
- [File](file.html).
274+
- [FileSystem](filesystem.html).

0 commit comments

Comments
 (0)