printf() и stdio в среде выполнения Julia
Оболочки libuv для stdio
julia.h
определяет оболочки libuv для потоков stdio.h
:
uv_stream_t *JL_STDIN;
uv_stream_t *JL_STDOUT;
uv_stream_t *JL_STDERR;
и соответствующих выходных функций:
int jl_printf(uv_stream_t *s, const char *format, ...);
int jl_vprintf(uv_stream_t *s, const char *format, va_list args);
Эти функции printf
используются файлами .c
в каталогах src/
и cli/
везде, где требуется stdio, чтобы обеспечить единую обработку буферизации вывода.
В особых случаях, когда, например, применяются обработчики сигналов и когда полная инфраструктура libuv слишком объемна, функцию jl_safe_printf()
можно использовать для записи (write(2)
) непосредственно в STDERR_FILENO
.
void jl_safe_printf(const char *str, ...);
Интерфейс между JL_STD* и кодом Julia
Константы Base.stdin
, Base.stdout
и Base.stderr
привязаны к потокам JL_STD*
libuv, определенным в среде выполнения.
Функция Julia __init__()
(в base/sysimg.jl
) вызывает reinit_stdio()
(в base/stream.jl
) для создания объектов Julia для констант Base.stdin
, Base.stdout
и Base.stderr
.
Функция reinit_stdio()
использует ключевое слово ccall
для получения указателей на поток JL_STD*
и вызывает jl_uv_handle_type()
для проверки типа каждого потока. Затем она создает объект Julia Base.IOStream
, Base.TTY
или Base.PipeEndpoint
для представления каждого потока, например:
$ julia -e 'println(typeof((stdin, stdout, stderr)))'
Tuple{Base.TTY,Base.TTY,Base.TTY}
$ julia -e 'println(typeof((stdin, stdout, stderr)))' < /dev/null 2>/dev/null
Tuple{IOStream,Base.TTY,IOStream}
$ echo hello | julia -e 'println(typeof((stdin, stdout, stderr)))' | cat
Tuple{Base.PipeEndpoint,Base.PipeEndpoint,Base.TTY}
Методы Base.read
и Base.write
для этих потоков используют ключевое слово ccall
для вызова оболочек libuv в src/jl_uv.c
, например:
stream.jl: function write(s::IO, p::Ptr, nb::Integer) -> ccall(:jl_uv_write, ...) jl_uv.c: -> int jl_uv_write(uv_stream_t *stream, ...) -> uv_write(uvw, stream, buf, ...)
printf() во время инициализации
Потоки libuv, на которые полагается функция jl_printf()
и подобные, недоступны до середины инициализации среды выполнения (см. описание init.c
, init_stdio()
). Сообщения об ошибках или предупреждения, которые должны выводиться до достижения этого момента, направляются в функцию fwrite()
стандартной библиотеки C с помощью следующего механизма.
В sys.c
указатели потока JL_STD*
статически инициализируются в целочисленные константы: STD*_FILENO (0, 1 and 2)
. В jl_uv.c
функция jl_uv_puts()
проверяет свой аргумент uv_stream_t* stream
и вызывает fwrite()
, если поток имеет значение STDOUT_FILENO
или STDERR_FILENO
Это позволяет единообразно использовать функцию jl_printf()
в течение всего времени выполнения, независимо от доступности какого-либо конкретного фрагмента кода до завершения инициализации.
Устаревшая библиотека ios.c
Библиотека src/support/ios.c
унаследована от femtolisp. Он обеспечивает кросс-платформенный буферизованный файловый ввод-вывод и предоставляет временные буферы в памяти.
ios.c
по-прежнему используется следующими файлами.
-
src/flisp/*.c
-
src/dump.c
— для сериализации файлового ввода-вывода и буферов в памяти. -
src/staticdata.c
— для сериализации файлового ввода-вывода и буферов в памяти. -
base/iostream.jl
— для файлового ввода-вывода (см.base/fs.jl
по эквиваленту libuv).
Использование ios.c
в этих модулях в основном автономно и происходит отдельно от системы ввода-вывода libuv. Однако есть одно место, где femtolisp вызывает jl_printf()
с устаревшим потоком ios_t
.
В ios.h
есть средство, которое присоединяет поле ios_t.bm
к uv_stream_t.type
и гарантирует, что значения, используемые для ios_t.bm
, не будут пересекаться с допустимыми значениями UV_HANDLE_TYPE
. Это позволяет указателям uv_stream_t
указывать на потоки ios_t
.
Это необходимо, поскольку функция fl_print()
femtolisp передает поток ios_t
функции jl_printf()
, вызывающей jl_static_show()
. Функция jl_uv_puts()
в Julia обрабатывает этот момент особым образом.
if (stream->type > UV_HANDLE_TYPE_MAX) {
return ios_write((ios_t*)stream, str, n);
}