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);
}