printf() and stdio in the Julia runtime
Libuv wrappers for 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, чтобы гарантировать, что буферизация вывода обрабатывается единообразно.
In special cases, like signal handlers, where the full libuv infrastructure is too heavy, jl_safe_printf() can be used to write(2) directly to STDERR_FILENO:
void jl_safe_printf(const char *str, ...);Interface between JL_STD* and Julia code
Base.stdin, Base.stdout и Base.stderr связаны с библиотекой JL_STD* потоков libuv, определенных в среде выполнения.
Функция __init__() Julia (в 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() during initialization
Потоки libuv, на которые полагается jl_printf() и т.д., недоступны до середины инициализации времени выполнения (см. init.c, init_stdio()). Сообщения об ошибках или предупреждения, которые необходимо напечатать до этого, перенаправляются в стандартную библиотеку C fwrite() с помощью следующего механизма:
В sys.c указатели потока JL_STD* статически инициализируются целочисленными константами: STD*_FILENO (0, 1 и 2). В jl_uv.c функция jl_uv_puts() проверяет свой аргумент uv_stream_t* stream и вызывает fwrite(), если поток установлен на STDOUT_FILENO или STDERR_FILENO.
Это позволяет единообразно использовать jl_printf() на протяжении всего времени выполнения, независимо от того, доступен ли какой-либо конкретный фрагмент кода до завершения инициализации.
Legacy ios.c library
Библиотека src/support/ios.c унаследована от femtolisp. Она предоставляет кроссплатформенный буферизованный ввод-вывод файлов и временные буферы в памяти.
ios.c все еще используется:
src/flisp/*.csrc/dump.c– для сериализации файлового ввода-вывода и для буферов памяти.src/staticdata.c– для сериализации файлового ввода-вывода и для буферов памяти.base/iostream.jl– для ввода-вывода файлов (см.base/fs.jlдля эквивалента libuv).
Использование ios.c в этих модулях в основном является самодостаточным и отделено от системы ввода-вывода libuv. Однако есть one place, где 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.
Это необходимо, потому что вызывающему jl_printf() jl_static_show() передается поток ios_t функцией fl_print() из femtolisp. Функция Julia jl_uv_puts() имеет специальную обработку для этого:
if (stream->type > UV_HANDLE_TYPE_MAX) {
return ios_write((ios_t*)stream, str, n);
}