-
Notifications
You must be signed in to change notification settings - Fork 8k
zvfs: improve libc FILE to integer fd abstraction (v3) #96278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
424018e
to
42c0978
Compare
FYI @cfriedt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks very well done 👍
42c0978
to
a2037eb
Compare
CI issues are unrelated to the changes introduced here and is caused by #96787. |
Previously, there was an implicit assumption that Zephyr's internal struct fd_entry * was synonymous with FILE * from the C library. This is generally not the case and aliasing these two distinct types was preventing a fair bit of functionality from Just Working - namely stdio function calls like fgets() and fopen(). The problem count be seen directly when trying to use a function like zvfs_fdopen(). Instead of aliasing the two types, require that all Zephyr C libraries provide 1. FILE *z_libc_file_alloc(int fd, const char *mode) Allocate and populate the required fields of a FILE object 2. int z_libc_file_get_fd(FILE *fp) Convert a FILE* object to an integer file descriptor. For Picolibc and Newlib-based C libraries, these functions set and get the integer file descriptor from a field of the internal FILE object representation. For the minimal C library, these functions convert between array index and struct fd_entry pointers. Includes changes necessary for tests to still pass. Signed-off-by: Chris Friedt <[email protected]> Co-authored-by: Jakub Klimczak <[email protected]> Signed-off-by: Jakub Klimczak <[email protected]>
Add a testsuite for the POSIX_DEVICE_IO Option Group. Signed-off-by: Chris Friedt <[email protected]> Co-authored-by: Jakub Klimczak <[email protected]> Signed-off-by: Jakub Klimczak <[email protected]>
Add a test to the POSIX filesystem API suite that checks whether fdopen() returns a valid value. Signed-off-by: Jakub Klimczak <[email protected]>
Give the user the ability to interact with stdio using POSIX read() and write() using the tty.h API. This avoids depending on standard C functions and allows poll(), select() et al. to work for stdio thanks to the internal use of semaphores. Alternatively, should there be no UART console device, read() and write() will use stdio hooks (where available) and poll() will simply return POLLNVAL for fd's 0, 1 and 2. Tested using the posix/device_io test suite. Signed-off-by: Jakub Klimczak <[email protected]>
Update the device_io test suite so that it expects select(), pselect() and poll() to work on stdio. Signed-off-by: Jakub Klimczak <[email protected]>
a2037eb
to
6d6489c
Compare
|
In spite of the blanket generalization above, there are some who would like to see Zephyr become a POSIX conformant RTOS, and to be competitive in industries previously dominated by one or two companies. Keeping the POSIX layer as thin as possible is a goal of the subsystem, as is keeping it optional. That's why, for example, the network subsystem has been converted to use native API calls, and the related POSIX API functions are just a thin wrapper around native calls. It's also why all POSIX system interfaces are disabled by default, and can be enabled by Option Group. Historically, Zephyr has certainly blurred lines and so more of the POSIX API leaked into regular applications than necessary. However, that should be mostly fixed in #88547. The |
Also, IIRC, we should be ok to use picolibc instead of the native libc when building for |
About the However, I do share a concern about ZVFS being mandatory in the future for all file-level interactions @cfriedt. IIUC, today we have a lightweight file API that I think should remain available to smaller applications that do not require the full abstraction of file descriptors that comes with POSIX. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think we should not do things like this in Zephyr.
Here we are adding to a component that claims to be central and "must use" for all, yet it seems to set quite a lot of requirements on the C library integration.
And it seems to be done in a very fragile way: Knowing a lot about those libraries and expecting a lot from them.
Imho, basic Zephyr code should be nimble, as simple as possible, with as few dependencies as possible, work with any C library or target, and setting the minimal amount of requirements on other components. If that means we cannot provide some standard API that is provided in higher level systems, so be it.
If we really want to provide those APIs with tough requirements on other components, those must be optional and isolated from the core functionality.
Note: I have only done a quick very partial review, and stopped due to the fundamental issues.
|
||
/** @cond INTERNAL_HIDDEN */ | ||
|
||
#if !defined(_CLOCK_T_DECLARED) && !defined(__clock_t_defined) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changes to posix_clock seem unrelated to the commit and PR?
zvfs: improve libc FILE to integer fd abstraction
if ZVFS | ||
|
||
config ZVFS_LIBC_FILE_SIZE | ||
default 144 if 64BIT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Major: This seems a pretty tight assumption about the picolibc implementation (same for newlib)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also accurate. And also easily adjustable.
return flags; | ||
} | ||
|
||
void zvfs_libc_file_alloc_cb(int fd, const char *mode, FILE *fp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the commit message refers to z_libc_file_alloc & z_libc_file_get_fd
"CONFIG_ZVFS_LIBC_FILE_SIZE must match alignment of FILE object"); | ||
|
||
#define FDEV_SETUP_ZVFS(fd, buf, size, rwflags, bflags) \ | ||
FDEV_SETUP_BUFIO(fd, buf, size, zvfs_read_wrap, zvfs_write_wrap, zvfs_lseek, zvfs_close, \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fundamental: All these seems like a very tight coupling into the picolibc implementation, using its internals. Which is likely to force us to update both sides simultaneously.
Even though this is picolibc specific, having code in the zephyr tree that is very coupled to an external module creates a very tight dependency.
We have at the very least 2 versions of picolibc (the one in the module and the one in the SDK we point to). But users or CI may try to use other sdk versions (certainly newer versions in the future).
As such I would try to avoid having code like this in the tree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@keith-packard - is there a better method of initializing a suitable Picolibc FILE
object here?
FDEV_SETUP_BUFIO(fd, buf, size, zvfs_read_wrap, zvfs_write_wrap, zvfs_lseek, zvfs_close, \ | ||
rwflags, bflags) | ||
|
||
/* FIXME: do not use ssize_t or off_t */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should fixmes not be fixed before submitting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest no, since it's a far larger change. This is just left here as a reminder to do so.
FILE *const stderr = (FILE *)(&_stderr); | ||
|
||
#define table_stride ROUND_UP(CONFIG_ZVFS_LIBC_FILE_SIZE, CONFIG_ZVFS_LIBC_FILE_ALIGN) | ||
#define table_data ((uint8_t *)_k_mem_slab_buf_zvfs_libc_file_table) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Major: This assumes the internals of the implementation of K_MEM_SLAB_DEFINE_STATIC, assuming private implementation details of other components would be quite fragile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fkokosinski - I think what Alberto is hinting at is that there needs to be an non-fragile interface that supports the same functionality. Perhaps it would be a good idea to add a K_MEM_SLAB_BUFFER()
macro for that purpose.
tags: | ||
- posix | ||
- device_io | ||
# 1 tier0 platform per supported architecture |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this comment?
static LIBC_DATA int (*_stdout_hook)(int); | ||
static int _stdout_hook_default(int c) | ||
{ | ||
(void)(c); /* Prevent warning about unused argument */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is a macro for this
|
||
config ZVFS_LIBC_FILE_SIZE | ||
int | ||
default -1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Major: The size of structures tends to depends on target, compile options, and other configuration (not just kconfig).
Getting this kind of information thru kconfig seems like a quite manual and fragile way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is a better alternative, please feel free to suggest one.
# Note: the POSIX_API dependency is only necessary until common elements | ||
# of C11 threads and POSIX API can be abstracted out to a common library. | ||
depends on POSIX_API | ||
depends on POSIX_THREADS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unrelated change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In version 2 of this change set, it was required to avoid pulling in unnecessary dependencies which resulted in some issue on some platform.
In any case, it's a more accurate refinement of the dependency.
Translating to factually and historically correct with lack of argumentation to counter, so I will just discard them in bulk.
OK, we may have different priorities here.
That isn't the case when your file support, like in case of close implemented here, is not implemented on native Zephyr API, but instead on additional fd id based level? That is not how the libc file operations should work, these should be natively implemented on the system API. I do not know the best approach here, but imho that should be to keep the system working on the most resource constrain devices we can, if that lefts out some of the libc things, then let that be the case. |
if (!fd) { | ||
return EOF; | ||
} | ||
if (close(fd)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this would be more appropriate here, so that POSIX is not unintentionally pulled into the libc matters.
if (close(fd)) { | |
if (zvfs_close(fd)) { |
*/ | ||
|
||
#include <zephyr/sys/fdtable.h> | ||
#include <unistd.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#include <unistd.h> |
config POSIX_DEVICE_IO_STDIO_CONSOLE | ||
bool "read() and write() to/from stdio use console subsystem" | ||
default y | ||
depends on CONSOLE_SUBSYS | ||
depends on !(CONSOLE_GETCHAR || CONSOLE_GETLINE) | ||
help | ||
Enable to use UART console whenever read() or write() is called | ||
on stdin, stdout or stderr. | ||
|
||
if POSIX_DEVICE_IO_STDIO_CONSOLE | ||
|
||
config POSIX_DEVICE_IO_STDIN_BUFSIZE | ||
int "read() buffer size for stdin" | ||
default 16 | ||
help | ||
Size of internal buffer for read()-ing from stdin. Setting both this and | ||
POSIX_DEVICE_IO_STDOUT_BUFSIZE to 0 will replace interrupt-driven operation | ||
with busy-polling and stop poll(), select() and others from working with stdio. | ||
|
||
config POSIX_DEVICE_IO_STDOUT_BUFSIZE | ||
int "write() buffer size for stdout" | ||
default 16 | ||
help | ||
Size of internal buffer for write()-ing to stdout. Setting both this and | ||
POSIX_DEVICE_IO_STDIN_BUFSIZE to 0 will replace interrupt-driven operation | ||
with busy-polling and stop poll(), select() and others from working with stdio. | ||
|
||
endif # POSIX_DEVICE_IO_STDIO_CONSOLE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These might be better defined at the libc level. The files stdin
, stdout
, and stderr
are from C89 / ANSI.
@de-nordic - not really looking for an argument.
Exactly. Different users of Zephyr have different priorities.
Do you mean
When using both |
where is this claim being made, I can't find it, can you provide a link? |
@nashif "ZVFS is a central, Zephyr-native library that provides a common interoperable API for all |
With this implementation you always have a nix or POSIX layer under C lib. Whether you enable POSIX or not.Native implementation would basically mean that
The wrapper to system object, would have to provide the proper implementation of the callbacks that would work correctly with the native pointer, when given the But that is not happens here. Here we have implemented fd table, zephyr/lib/libc/common/source/stdio/fclose.c Lines 10 to 22 in 6d6489c
The What is being implemented here is this
Which means that fdtable is and entire What we should have instead is
where C lib is aware to register and hold POSIX type file ids, only when POSIX is enabled at the same time. Integer file descriptors are neither C not Zephyr thing. Additionally, the implementation here, and prior work, basically tries to mimic how nix type process env is created immediately creating always existing stdin, stder and stdout descriptors that may not even be used by anything. |
Yet it is the C library that ultimately defines what a
ISO C FILE objects are defined by the respective C library implementors. The filesystem objects that Zephyr provides are used (when dealing with filesystems). Similarly, the socket objects that Zephyr provides are used when dealing with sockets. zephyr/lib/libc/common/source/stdio/fclose.c Lines 10 to 22 in 6d6489c
I am in agreement (and have commented on exactly that already). That should not use fileno or close (which are POSIX).
Fully agreed. |
That, and we already have Native API, getting C lib f-functions implemented, there is also POSIX. What is the ZVFS thing for? Why is POSIX being implemented over ZVFS not native Zephyr services directly? ZFVS is already basically nix/POSIX. lseek->zvfs_lseek->(inline)zvfs_lseek_wrap-> fdtable[fd].vtable->ioctl->fs_seek So it is not like POSIX natively calls subsystems, there is ZVFS between it and the target. POSIX zephyr/lib/posix/options/fd_mgmt.c Lines 44 to 47 in 727c15a
There is no documentation on the ZVFS, it does not appear on POSIX page in Zephyr, it is marked EXPERIMENTAL in Kconfig, yet POSIX, that is not marked experimental, selects if for only existing POSIX implementation of device_io. Shouldn't we just rename the ZVFS posix and drop one thing? |
Follow-up to the work in #83386 and #90916.