Puertos
Los programas obtienen puertos tal como los define OTP, y la E/S externa pasa
por ellos. La tabla de puertos, las reglas de salida y las tareas de puerto
están en runtime/src/scheduler/ports.cpp, los builtins en
runtime/src/builtins/erlang/ports.cpp, y los drivers y el hilo de E/S en
runtime/src/ports/. Goldens de OTP: executables_port_identities,
executables_port_input, executables_port_spawn, executables_file_io,
executables_sockets, executables_many_ports, executables_port_fairness,
executables_busy_ports, executables_slow_owner; prueba del runtime
runtime_port_io.
Identidad
- Un puerto es una palabra inmediata: etiqueta
0x7(TermKind2::portbajo la etiqueta primariasee_termkind2, junto al0x3de los pids), con carga útil un número de puerto de una secuencia única para todo el proceso del sistema que nunca se reutiliza, como los números de pid (pids). Un runtime solo admite los números que ha emitido; las palabras de puerto falsificadas o ajenas se rechazan. - Impresión:
#Port<0.N>;port_to_list/1da ese texto ylist_to_port/1lo analiza (badarg para cualquier otra cosa). - Orden: números < átomos < referencias < funs < puertos < pids < tuplas, como en OTP; los puertos se comparan por número.
- La identidad de un puerto cerrado sigue siendo válida:
is_port/1sigue siendo true,port_info/1,2respondenundefinedy los envíos dirigidos a él se descartan. - Los puertos son inmediatos, así que la copia, la recolección y los mensajes no necesitan nada nuevo.
Tabla de puertos y propiedad
- Cada runtime mantiene una tabla de puertos (número → puerto) en su ejecutor, protegida por el mutex del ejecutor como los enlaces y los nombres de los procesos (hilos de trabajo).
- Un puerto registra su driver, su proceso conectado (quien lo abrió, cambiado
por
port_connect/2o{Pid, {connect, New}}), sus enlaces, los monitores que hay sobre él, un nombre registrado opcional, sus opciones y sus contadores de bytes de entrada/salida. open_port/2enlaza el puerto nuevo con quien lo abre. Cuando el proceso conectado termina, su puerto se cierra; un puerto que se cierra envía señales de salida a sus enlaces y mensajes'DOWN'a sus monitores con su razón (normalpara un cierre que no es un error; si no, un átomo POSIX comoepipe).- Una señal de salida que llega a un puerto lo cierra con su razón (
killdeexit/2comokilled), exceptonormala través de un enlace desde un proceso distinto del conectado, que solo elimina el enlace.exit(Port, normal)cierra el puerto. Un proceso conectado que se ha desenlazado deja su puerto abierto al terminar. - Un mensaje de petición de un proceso distinto del conectado, o un mensaje mal
formado, envía al proceso conectado una señal de salida
badsigdesde el puerto; el puerto sigue abierto. - Las señales de salida y los mensajes de un puerto dirigidos a un proceso que
se ejecuta en otro hilo de trabajo esperan a que termine la porción de tiempo
de ese proceso (solo contienen átomos, pids y puertos); un
exit(Port, Reason)con un término de razón en el heap del emisor hace que el builtin se vuelva a ejecutar hasta que ningún proceso enlazado o que monitoriza se ejecute en otro sitio, como con las señales de procesos (hilos de trabajo). - Los puertos admiten nombres registrados (
register/2) ymonitor(port, Port);link/1yunlink/1los aceptan.
Builtins y mensajes de puerto
is_port/1(true para puertos),port_to_list/1,list_to_port/1,ports/0.port_info/1,2:name,links,id,connected,input,output,os_pid,monitors,monitored_by,registered_name,queue_size(puertos ocupados).port_close/1,port_connect/2,port_command/2,3; los mensajesPort ! {Pid, {command, Data}},{Pid, close},{Pid, {connect, New}}.link/1,unlink/1,monitor(port, P),exit/2sobre puertos;register/2de un puerto.port_control/3,port_call/3:badargpara drivers sin control; los driversfiley de sockets se manejan medianteport_control/3.open_port({fd, In, Out}, Opts),open_port({spawn, Command}, Opts),open_port({spawn_executable, File}, Opts)y los drivers internos de la biblioteca del proyecto (file, sockets).
Los errores siguen a OTP: badarg para un puerto cerrado o no válido y para
argumentos incorrectos, y la razón POSIX (enoent, eacces) como error para
una apertura que falla.
Los mensajes de un puerto van a su proceso conectado: {Port, {data, Data}},
{Port, eof} (opción eof), {Port, {exit_status, Status}} (opción
exit_status), {Port, closed} (tras {Pid, close}), {Port, connected} (al
propietario anterior tras un connect).
Modos de datos y opciones
| Opción | Efecto |
|---|---|
stream (por defecto), {packet, N} (N = 1, 2, 4) | Los bytes según llegan, o mensajes delimitados por una longitud big-endian de N bytes que también recibe la salida |
{line, L} | {eol, Line} por línea, {noeol, Part} para las partes de más de L o un final sin terminador |
binary | Los datos como binaries en lugar de listas de bytes |
eof | {Port, eof} al final de la entrada; el puerto sigue abierto hasta que se cierra |
exit_status | {Port, {exit_status, S}} cuando el programa sale (puertos spawn) |
use_stdio (por defecto), nouse_stdio, stderr_to_stdout, in, out, hide | Como en OTP (hide no tiene efecto) |
{args, List}, {arg0, A}, {env, Env}, {cd, Dir} | Puertos spawn (subprocesos) |
{busy_limits_port, {Low, High} | disabled} | Bytes de salida encolados que hacen que el puerto esté ocupado (puertos ocupados) |
Sin eof, el final de la entrada cierra el puerto con la razón normal,
después del mensaje exit_status cuando se ha pedido. Las opciones desconocidas
son badarg.
Drivers
Un driver es un objeto C++ detrás de un puerto (runtime/src/ports/): abre el
recurso, acepta la salida (port_command), responde a port_control/3 cuando
admite control, informa de la entrada y de los errores como eventos y se cierra.
Drivers:
-
fd: descriptores existentes, salida escrita de forma síncrona, entrada a través del hilo de E/S. -
spawn: un programa hijo con su stdin y su stdout como tuberías (subprocesos). -
file: un archivo abierto del módulofilede la biblioteca del proyecto; sus operaciones son llamadas síncronas aport_control/3que pueden bloquear al hilo de trabajo que ejecuta al llamante mientras dura la E/S de disco, como hacen los planificadores de E/S sucia (dirty I/O) de OTP (archivos). -
tcp_inet,udp_inet: sockets degen_tcp,gen_udpeinetde la biblioteca del proyecto sobre Boost.Asio; las conexiones, las aceptaciones y las recepciones son asíncronas (sockets). -
clause_registry(Windows): el registro dewin32reg(el registro).
Los drivers internos de file, de los sockets y del registro se abren con
{spawn_driver, Name} bajo nombres de Clause, y sus operaciones de
port_control/3 son un protocolo de Clause: los programas usan los módulos de
la biblioteca, no los protocolos prim_inet/efile de OTP.
Hilo de E/S
Un hilo de E/S por runtime (detail::Reactor, runtime/src/ports/reactor.hpp)
ejecuta un io_context de Boost.Asio: un puerto de finalización de E/S (I/O
completion port) en Windows, epoll en Linux, kqueue en macOS. El primer
puerto que lo necesita lo arranca; sirve a todos los tipos de puerto, así que un
puerto no cuesta ningún hilo propio:
- Tuberías de los programas lanzados: tuberías con nombre superpuestas
(overlapped) en Windows (
windows::stream_handle; las tuberías anónimas no pueden ser superpuestas), tuberías normales en los demás sistemas (posix::stream_descriptor). La salida se encola y se escribe en orden mediante escrituras asíncronas. - Entrada
fden Linux y macOS: el hilo espera hasta que el descriptor es legible y después un únicoread()toma lo que haya, de modo que los descriptores propios del programa conservan su modo bloqueante; un archivo regular, que no se puede esperar, se lee de inmediato. - Salidas de programas: en Windows, el pool de hilos de espera del sistema
(
RegisterWaitForSingleObject, como usaobject_handlede Asio) espera al manejador del proceso y la salida se notifica en el hilo de E/S; en Linux y macOS, un manejador deSIGCHLD(signal_set) ywaitpid(WNOHANG)recogen cada programa vigilado, también después de que su puerto se haya cerrado. - Sockets (sockets).
- Excepción: un manejador de entrada
fden Windows (una consola o una tubería anónima heredada) no puede ser superpuesto, así que lo lee un hilo bloqueante propio, como hacen libuv y ERTS; cerrar el puerto cancela la lectura (CancelSynchronousIo) y deja terminar el hilo.
runtime/src/ports/io*.cpp contienen la E/S de puertos (detail::IoService):
sus métodos solo envían trabajo al hilo de E/S, donde vive todo el estado de
E/S. El hilo de E/S entrega la entrada tal como se leyó: bytes en bruto, el
final de la entrada, un error de lectura, el estado de salida de un programa.
Los eventos de sockets (mensajes, conexiones aceptadas) se entregan del mismo
modo.
El prototipo tests/prototypes/poller/ (run.py --wsl) muestra el despertar en
Windows (puerto de finalización) y en Linux bajo WSL (poll()): un hilo
planificador inactivo despierta 9–91 µs después de la entrada, y el apagado
detiene el hilo de E/S sin entrada.
Al final del programa se cierran todos los puertos (los programas hijos ven el
final de la entrada; no se matan, como en OTP) y el hilo de E/S se detiene: se
espera (join) fuera del mutex del ejecutor, porque una entrega en curso lo toma;
un lector fd de Windows bloqueado en una lectura que no se puede cancelar se
desacopla y ya no entrega nada más.
Tareas de puerto
Un puerto se planifica como un proceso: lo que entrega el hilo de E/S espera en el puerto, y el puerto espera en la cola de puertos del ejecutor hasta que un hilo de trabajo del planificador ejecuta su tarea.
- Los hilos de trabajo toman alternativamente una tarea de puerto y una porción de tiempo de proceso mientras ambas colas tienen trabajo, así que un puerto que inunda de entrada no puede dejar sin servicio a los procesos, ni los procesos a los puertos; un hilo de trabajo inactivo toma lo que esté encolado.
- Una tarea se ejecuta bajo el mutex del ejecutor durante
PORT_TASK_REDUCTIONS(las 4.000 de una porción de tiempo): cada mensaje que entrega cuesta 100 reducciones más una por cada 64 bytes que lleva. Un puerto al que le queda trabajo se vuelve a encolar al final. - La tarea delimita la entrada (
InputDecoder): la entrada de flujo llega en los trozos que devuelven las lecturas;{packet, N}retiene los bytes hasta que ha llegado un paquete completo (un paquete incompleto al final de la entrada se descarta, como en OTP);{line, L}divide en\n, envía una línea de más deLcomo trozos{noeol, Part}y un final sin terminador como{noeol, Rest}.port_info(P, input)cuenta cada byte leído, incluidos los saltos de línea y las cabeceras de paquete, en el momento en que se lee. - El final de la entrada envía
{Port, eof}con la opcióneof; si no, cierra el puerto con la razónnormal; un error de lectura lo cierra coneio. - Un mensaje se convierte en un mensaje a su proceso de inmediato cuando ese proceso no se está ejecutando en un hilo de trabajo (despertándolo como cualquier mensaje), y si no, cuando termina su porción de tiempo, así que otro hilo nunca toca el heap de un proceso en ejecución.
- Los comandos, cierres, conexiones y señales de salida enviados a un puerto
actúan de inmediato en el hilo de trabajo del emisor, como hace ERTS para un
puerto que no está ocupado: un puerto
fdescribe su salida de inmediato en el hilo de trabajo del llamante; los drivers de tuberías y de sockets encolan la salida y dejan que la escriba el hilo de E/S; un puerto ocupado suspende al emisor (puertos ocupados).
Puertos ocupados
La salida que un driver encola para el hilo de E/S (las tuberías de los
programas lanzados) cuenta para los límites de ocupación del puerto, el
busy_limits_port de OTP (por defecto: alto 8.192 bytes, bajo 4.096;
{busy_limits_port, {Low, High}} o disabled como opción de open_port/2,
límites de al menos 1, y un límite bajo superior al alto se rebaja a este):
- Una escritura que lleva la salida encolada hasta el límite alto o por encima sigue saliendo y deja el puerto ocupado; sigue ocupado hasta que el hilo de E/S ha escrito lo suficiente para que quede encolado menos del límite bajo.
port_command/2yPort ! {Pid, {command, Data}}dirigidos a un puerto ocupado suspenden al emisor, que no escribe nada; en cuanto el puerto deja de estar ocupado, o se cierra, el emisor vuelve a ejecutar su builtin (un puerto cerrado da entoncesbadarg, como en OTP). Un proceso suspendido sigue recibiendo señales de salida.port_command/3connosuspenddevuelvefalsepara un puerto ocupado y no escribe nada;forcelanzanotsup, ya que los drivers spawn yfdde OTP no lo permiten.port_info(P, queue_size)es la salida encolada y aún no escrita.- Los puertos
fdescriben de inmediato y la salida de los sockets pasa porport_control/3, así que ninguno de los dos está nunca ocupado.
Un puerto también acota la entrada que retiene:
- Cuando más de 64 KiB de entrada en bruto esperan a la tarea del puerto, el hilo de E/S deja de leer el puerto (un programa que escribe en él se bloquea entonces, como con una tubería llena) hasta que la tarea la ha reducido por debajo de 32 KiB.
- Mientras el proceso conectado puede ejecutarse (está en ejecución o encolado,
no esperando en un
receive, suspendido ni bloqueado) y ya tiene 1.024 mensajes (o tiene esa cantidad de señales de puerto esperando a que termine su porción de tiempo), la tarea del puerto no entrega nada más hasta que haya terminado la porción de ese proceso. Un proceso que espera en unreceivesiempre recibe la entrada, así que un receive de un mensaje posterior del puerto no puede esperar para siempre.
Subprocesos
open_port({spawn, Command}, Options) y
open_port({spawn_executable, File}, Options) arrancan un programa cuyos stdin
y stdout son tuberías del puerto:
{spawn, Command}: en Linux y macOS,/bin/sh -c Command; en Windows,CreateProcessWconCommandcomo línea de órdenes (encuentra el programa en la ruta de búsqueda).{spawn_executable, File}ejecutaFilesin shell, con argv[0]{arg0, A}(por defectoFile) y{args, List}; en Windows los argumentos se entrecomillan según las reglas deCommandLineToArgvW.{env, [{Name, Value | false}]}fija o elimina variables del hijo,{cd, Dir}su directorio,stderr_to_stdoutune stderr al puerto; en caso contrario, el hijo comparte el stderr del programa.insolo no abre tubería stdin youtsolo no abre tubería stdout (en su lugar, el dispositivo nulo); con ambas opciones o con ninguna, el puerto tiene las dos, como en OTP.hideyoverlapped_iono tienen efecto.- Un programa que no se puede arrancar lanza
error:Reasoncon la razón POSIX (enoent,eacces,enoexec); los nombres y las opciones incorrectos lanzanbadarg. - La salida se encola y la escribe el hilo de E/S, con los límites de ocupación del puerto (puertos ocupados); cerrar el puerto le deja terminar lo encolado y después cierra el stdin del programa. El programa nunca se mata; normalmente termina al final de la entrada.
- La opción
exit_statusenvía{Port, {exit_status, S}}una vez que el programa ha salido (su código de salida; en Linux y macOS, 128 más la señal para un programa terminado por una señal) y antes de{Port, eof}o del cierre al final de la entrada; un puerto que no lee nada lo notifica cuando el programa sale y después se cierra. port_info(P, os_pid)es el identificador de proceso del programa;namees el comando o el archivo.- El
os:cmd/1de la biblioteca ejecutaCommandconCOMSPEC /cen Windows (cmdsi no está definida) o con/bin/sh -c, recoge stdout y stderr hasta que el programa los cierra y devuelve los bytes como una lista;os:type/0es{win32, nt},{unix, linux}o{unix, darwin};os:getenv/1devuelve una cadena ofalse.
E/S estándar y archivos
io:format,io:put_charsyerlang:displayescriben directamente en la salida estándar (io). La entrada estándar la lee un único proceso servidor de la biblioteca, registrado comoclause_stdiny arrancado en el primer uso, que posee un puerto{fd, 0, 1}:io:get_line/1,2escribe el indicador (prompt) y después devuelve la siguiente línea con su salto de línea, el resto de la entrada sin él al final, y despuéseof;io:get_chars/2,3devuelve hastaNcaracteres y despuéseof. Las peticiones de varios procesos se responden en orden de llegada. La entrada se decodifica de UTF-8 a caracteres, como OTP lee su terminal; el servidor conserva las opciones destandard_iosin shell de OTP (binary,encoding,echo,log;io:getopts/0,1,io:setopts/1,2): el modo binario devuelve binaries,echoescribe la entrada consumida en la salida estándar yget_passwordes{error, enotsup}sin shell, como en OTP.- Los servidores de archivos responden a
getoptsysetoptscomo el servidor de E/S de archivos de OTP (binary/listyencoding;file:open/2acepta{encoding, E}): las peticiones de E/S (io:get_line,get_chars,put_chars,get_until) codifican y decodifican la codificación del dispositivo, yfile:read/2,read_line/1sobre un archivo codificado responden como peticiones latin1get_chars/get_line(no_translationpor encima de 255, que detiene el servidor). La salida deio:format/3,io:put_chars/2,io:nl/1a cualquier pid es una peticiónput_charsdel protocolo de E/S. - El módulo
filede la biblioteca maneja el driver de archivos del runtime,{spawn_driver, "clause_file"}, conport_control/3. Sus operaciones (ports/file.cpp: open, read, write, position, read_line, sync, close, y las operaciones sobre rutas read_file, write_file, delete, rename, list_dir, make_dir, del_dir y read_file_info,ports/file_info.cpp; get_cwd, set_cwd, read_link, read_link_info, make_link y make_symlink,ports/file_paths.cpp) son llamadas síncronas al sistema en el hilo de trabajo del llamante, fuera del mutex del ejecutor; sus respuestas empiezan con un byte de estado (0 ok, 1 error y su razón POSIX, 2 fin de archivo). El protocolo es propio de Clause. read_line lee 64 KB por adelantado y devuelve líneas de ese búfer; cualquier otra operación sobre el archivo retrocede primero sobre los bytes que no ha devuelto, de modo que las posiciones siguen siendo exactas. - Las operaciones sobre rutas hacen las llamadas al sistema de OTP y nada más:
el estado de una ruta se lee solo cuando falla una apertura o eliminación
(para responder
eisdiroeperm), delete desenlaza un archivo y nunca elimina un directorio, y read_file lee un archivo hasta el tamaño que indica su descriptor, como OTP, sin lecturas para sondear su final. port_control/3entrega un binary o[]al driver en su ubicación original (los demás iodata se aplanan primero), y la respuesta se convierte en el binary de resultado sin copiarse: las respuestas sonByteBuffers (runtime/include/byte_buffer.hpp), vectores de bytes que crecen sin ponerse a cero, de modo que una lectura rellena los bytes de su respuesta una sola vez.file:open/2devuelve el pid de un servidor de E/S que posee un puerto de archivo y se cierra cuando termina quien lo abre, como en OTP (en Windows el archivo se comparte para lectura, escritura y eliminación, como lo abre OTP, por lo que un archivo abierto se puede eliminar o renombrar) (modosread,write,append,exclusive,binary,raw; los demás modos se ignoran, como OTP ignora las opciones que no usa;writetrunca salvo que también se indiqueappend).file:write_file/3abre con sus modos másbinaryywrite;file:read_file_info/1,2devuelven el#file_info{}de OTP a partir del estado de la ruta (siguiendo los enlaces), con las horas como fecha y hora local o universal o como segundos POSIX ({time, local | universal | posix}; se aceptaraw). En Windows,inode,uidygidson 0 ymajor_devicees el número de unidad, como los informa OTP.read/2,read_line/1,write/2,position/2eio:get_line/2,io:get_chars/3sobre él son peticiones a ese servidor; después declose/1las peticiones devuelven{error, terminated}yclose/1sigue devolviendook.- E/S posicionada y cambios en archivos:
pread/2,3ypwrite/2,3posicionan y después leen o escriben en un servidor de archivos (curcontinúa leyendo, la posición se mueve), como hace el servidor de OTP; un archivorawsolo acepta desplazamientos enteros (badargen otro caso) y conserva su posición.truncate/1corta en la posición;advise/4acepta los átomos de consejo de OTP (posix_fadviseen Linux, ignorado en los demás sistemas, como en OTP);allocate/3reserva espacio (fallocate,F_PREALLOCATEen macOS,{error, enotsup}en Windows), tras comprobar sus argumentos (badargpara valores no numéricos,einvalpara negativos y flotantes). Como el servidor de archivos de OTP, un servidor cuya escritura, escritura posicionada, sincronización, truncamiento o consejo falla responde con el error y termina, por lo que las peticiones posteriores reciben{error, terminated}; un archivo raw continúa.write_file_info/2,3ychange_mode/2,change_owner/2,3,change_group/2,change_time/2,3cambian propietario, permisos y tiempos en el orden de prim_file (Windows: se ignoran los propietarios, solo se toma el atributo de solo lectura del bit de escritura del propietario, y también se fija la hora de creación).sendfile/2,5leen un archivo raw en bloques (como máximo 1 MiB,chunk_size) y los envían congen_tcp:send/2o la fun indicada, la alternativa de OTP: Clause no tiene sendfile nativo.format_error/1proporciona los textos de OTP, los POSIX medianteerl_posix_msg:message/1. - Líneas y lecturas fallidas:
read_line/1eio:get_lineleen un final CRLF como LF (la posición cuenta el CR), como OTP; un servidor de archivos cuya lectura falla responde con el error y termina, y uno cuya lectura de línea falla termina sin responder (el llamante recibe{error, terminated}), como el de OTP. compressedycompressed_one: la escritura pasa por un flujo gzip deflate (zlib, bits de ventana 31) que finaliza al cerrar, también cuando el servidor se detiene o termina quien lo abrió; la lectura descomprime un archivo que empieza con los bytes mágicos de gzip (compressedlee miembros concatenados como uno solo,compressed_onese detiene tras el primero) y lee cualquier otro archivo tal cual. Las posiciones cuentan bytes sin comprimir: se avanza leyendo o escribiendo ceros, se retrocede solo al leer (desde el principio otra vez),eofeseinval. Leer un archivo abierto para escritura o escribir uno abierto para lectura esebadf;allocate/3y, en archivos raw, la E/S posicionada sonenotsup; un servidor de archivos se detiene trastruncate,adviseysync(enotsup), como el de OTP. Leer y escribir a la vez o añadir al final es{error, einval}.{zstd, Options}: comocompressed, mediante un contexto zstd creado conOptions({error, badarg}cuando las rechaza): cada escritura es una trama propia y el cierre añade una vacía, como escribe la capa de OTP; la lectura descomprime las tramas leídas en bloques de 8 KiB.ram: el archivo es su contenido iodata en memoria, servido por un proceso como los demás archivos y cerrado con quien lo abre; solo admite los modosread,writeybinary(readcuando no se indica ninguno, cualquier otro esbadarg,rawconramlanzabadarg). Las lecturas, escrituras, posiciones,pread/pwrite(conservando la posición),truncate,sync,adviseyallocateresponden como el driver de archivos ram de OTP: las escrituras más allá del final rellenan el hueco con ceros, los desplazamientos y tamaños que exceden 32 bits con signo soneinval, escribir en un archivo de solo lectura esebadfy truncarlo,eacces.- El protocolo de E/S de OTP: el servidor de la entrada estándar y el servidor
de cada archivo abierto responden a
{io_request, From, ReplyAs, Request}con{io_reply, ReplyAs, Reply}, que envíanio:request/1,2eio:requests/1,2(standard_ioyuserdesignan el servidor de la entrada estándar, otro átomo un proceso registrado;{error, arguments}cuando no está registrado,{error, calling_self}para el propio llamante). Peticiones:get_until(M:F(Cont, Chars | eof, Extra...)desdeCont = [], alimentada con lo que hay en el búfer o con bloques de 4 KiB del archivo hasta{done, Result, Rest};Restlo vuelve a leer la siguiente petición: se conserva en el búfer o, para un archivo, la posición retrocede sobre él),get_line,get_chars,put_chars(caracteres oM:F(A)), las formas antiguas sin codificación (latin1) y{requests, List}(en orden, deteniéndose en el primer{error, _}). Los caracteres son bytes, como arriba; un archivo en modo binario devuelve como binary un resultado de lista deget_until. Las demás reciben{error, {request, Request}}. Una función deget_untiloput_charsque falla hace que el servidor del archivo responda{error, Function}y termine con el fallo, como hace el de OTP; quien abrió el archivo sigue vivo (el servidor de archivos de OTP no está enlazado con él). Las peticiones de entrada sobre la entrada estándar escriben su indicador. - Directorios, enlaces y copias:
get_cwd/0escribe el directorio actual como lo hace OTP (barras normales, letra de unidad en minúscula),get_cwd/1toma una unidad de Windows (enotsupen otros sistemas);set_cwd/1lo cambia para todo el programa;read_link/1,read_link_all/1(einvalpara una ruta que no es un enlace),read_link_info/1,2(el estado propio de un enlace final:lstatde POSIX, en Windows los campos del destino con tiposymlink),make_link/2,make_symlink/2(en Windows necesita un privilegio:eperm, como OTP),list_dir_all/1,del_dir_r/1,copy/2,3(nombres,{Name, Modes}o archivos abiertos;{ok, BytesCopied}),raw_read_file_info/1,read_file/2,delete/2,native_name_encoding/0(utf8).del_dir/1respondeenoentpara una ruta inexistente yenotdirpara un archivo. - El
filede Clause es la capa de archivos;filelibyfilenamede Clause se ejecutan sobre ella (filelib:fold_files/5usare);erl_prim_loaderse proporciona sobrefilecon las respuestaserrorescuetas de OTP y una ruta de carga["."](set_path/1), y sus funciones de carga y de archivos comprimidos se rechazan;prim_fileyprim_buffersiguen ausentes, por lo que los llamantes deraw_file_io*de OTP nunca entran en un programa. - Los errores siguen a OTP:
{error, enoent},eexist,eisdir(un directorio abierto o leído como archivo),einval(una posición negativa;badargpara un valor que no es una ubicación),ebadf(leer un archivo de solo escritura o escribir uno de solo lectura),badargpara nombres y datos incorrectos. Los nombres de archivo son cadenas, binaries o átomos, codificados como UTF-8.
Sockets
Un socket es un puerto, como con el backend inet_drv de OTP: is_port(Socket)
es true y los mensajes en modo activo son {tcp, Socket, Data},
{tcp_closed, Socket}, {tcp_error, Socket, Reason},
{udp, Socket, Address, Port, Data}. El subconjunto:
gen_tcp:connect/2,3,4 (un host y un puerto, o un map de dirección de socket),
listen/2, accept/1,2, send/2, recv/2,3, close/1,
controlling_process/2, shutdown/2; gen_udp:open/1,2, connect/2,3,
send/2,3,4,5 (formas de destino y datos auxiliares de OTP), recv/2,3,
close/1; inet:setopts/2, getopts/2, getstat/1,2, info/1, i/0,1,2,
monitor/1, cancel_monitor/1, port/1, peername/1, peernames/1,2,
sockname/1, socknames/1,2; los modos {active, true | false | once},
binary/list ({mode, M}), {packet, 0 | 1 | 2 | 4 | raw}, {backlog, N},
{ip, Address}/{ifaddr, Address}, inet/inet6;
direcciones IPv4 e IPv6 como tuplas, nombres de host como cadenas o átomos
(incluido loopback).
Las opciones aparte del modo de datos (ports/socket_options.cpp,
library/kernel/clause_socket_options.erl) son records del driver con un código
y un valor, indicados al abrir un socket (se fijan antes de vincularlo o
conectarlo) o mediante setopts/2; todas las opciones de una llamada a
setopts/2 se comprueban antes de fijar ninguna. Las opciones del sistema
(reuseaddr, keepalive, dontroute, linger, broadcast, sndbuf,
recbuf, nodelay, tos, tclass, ipv6_v6only, multicast_ttl,
multicast_loop, multicast_if, add_membership, drop_membership,
exclusiveaddruse en Windows, priority y reuseport donde el sistema las
ofrece) pasan a setsockopt y se consultan con getsockopt; las opciones que
el propio inet_drv de OTP conserva (buffer, header, deliver,
exit_on_close, send_timeout, marcas de agua, packet_size, ...) se
conservan con los valores predeterminados de inet_drv y no se aplican
(diferencias). Como en inet_drv, un búfer de recepción aumenta
buffer hasta que este se fija, y getopts/2 omite las opciones de las que
carece un tipo de socket (broadcast de un socket TCP, nodelay de uno UDP,
tclass de IPv4) y rechaza las desconocidas con {error, einval}. Una conexión
aceptada hereda las opciones de su socket de escucha. send/5 con datos
auxiliares fija tos, tclass o ttl solo para ese datagrama; en Windows es
{error, einval}, como en OTP.
Cada socket cuenta su tráfico como inet_drv para getstat/1,2: los paquetes
recibidos son lecturas (TCP) o datagramas, los enviados son llamadas a send
(con su cabecera de paquete) o datagramas, con las medias acumuladas de
inet_drv; send_pend es la salida encolada y aún no escrita. getstatus
proporciona los indicadores de estado de inet_drv (open, connected,
connecting, listen, accepting), que info/1 e i/0,1,2 imprimen con los
nombres y la columna de módulo de OTP (inet_tcp, inet6_udp, ...). Las
consultas a un socket cerrado responden {error, einval}, como OTP.
El módulo socket (library/kernel/socket.erl) usa los mismos puertos de
driver en modo binario pasivo. Un socket de flujo se puede vincular antes de
escuchar o conectarse (la operación 22 abre y vincula el socket del sistema,
que listen convierte en el receptor de conexiones o connect conecta); un
socket de datagramas se vincula mediante udp_open, implícitamente en su primer
envío. El final de una conexión responde a todos los recv en espera (también
a los que piden más bytes de los que quedan). socket conserva los bytes que
un recv no tomó en su propio búfer, por lo que recv/2,3,4 proporciona
{error, {timeout, Data}} y {error, {closed, Data}} de OTP.
Implementación (runtime/src/ports/sockets.cpp):
- El hilo de E/S del runtime (hilo de E/S) sirve a los sockets; el
estado de cada socket vive en ese hilo. La biblioteca abre un puerto con
{spawn_driver, "tcp_inet" | "udp_inet"}y lo maneja conport_control/3; el hilo de trabajo que llama envía la operación al hilo de E/S y espera su respuesta síncrona (byte de estado 0 y un resultado, o 1 y una razón POSIX). - Las operaciones que esperan (connect, accept, recv) responden más tarde con un
mensaje
{clause_socket, Socket, Reply}a su llamante, así que el llamante se bloquea en unreceiveordinario y los demás procesos siguen ejecutándose. Un tiempo de espera cancela la petición; la propia cancelación respondecancelleddespués de todo lo que el socket haya enviado antes, de modo que una respuesta que ganó la carrera se devuelve en lugar de perderse. - Los mensajes de socket se describen fuera del heap (
ports/value.hpp) y se construyen en el heap del receptor al entregarse (tareas de puerto). Una conexión aceptada se convierte en un puerto nuevo cuyo propietario es el llamante deaccept, enlazado con él, y con el modo del socket de escucha. controlling_process/2detiene la entrega activa, traslada al nuevo propietario los mensajes del socket ya enviados al antiguo y después reconecta el puerto, como haceinetde OTP; solo el propietario puede llamarlo ({error, not_owner}).- La salida se encola sin límite y se escribe en orden en el hilo de E/S. Cerrar
un puerto envía lo encolado y después cierra la conexión de forma ordenada
(FIN); un puerto cerrado responde
{error, closed}a cada llamante en espera. La resolución de nombres se ejecuta en el hilo de trabajo del llamante, ya que bloquea. - Los puertos de socket se planifican como los demás puertos (tareas de puerto): los mensajes de un socket esperan en su puerto hasta que la tarea del puerto los entrega.
- Direcciones y hosts:
inet:parse_address/1,2,parse_strict_address/1,2,parse_ipv4_address/1,parse_ipv4strict_address/1,parse_ipv6_address/1,parse_ipv6strict_address/1(las formas relajadas de OTP: campos IPv4 cortos y octales o hexadecimales,::, colas IPv4,%scopedefe80::yff02::),ntoa/1(la grafía de OTP: hexadecimal en minúsculas, la primera secuencia de ceros más larga como::),is_ipv4_address/1,is_ipv6_address/1,is_ip_address/1son Erlang;gethostname/0(una operación de socket),getaddr/2,getaddrs/2(el resolutor del sistema,getaddrinfo) ygethostbyname/1,2(h_namees el nombre consultado, sin alias) responden mediante un puerto de socket abierto para la consulta. - Los servicios, las consultas inversas y las interfaces (
inet:getservbyname/2,getservbyport/2,gethostbyaddr/1,getifaddrs/0,1) son builtins declause_netcompartidos connet(nombres de red). gen_tcp,gen_udp,inetynetde Clause son la capa de red; el módulosocketde OTP (la API NIF de sockets) no existe, como tampocoprim_socket,prim_net,prim_inetnisocket_registry.
El registro
win32reg abre {spawn_driver, "clause_registry"} en Windows
(runtime/src/ports/registry_windows.cpp). El driver conserva el estado del
driver del registro de OTP (el acceso solicitado, la clave actual y su raíz,
inicialmente HKEY_CLASSES_ROOT) y responde a los nueve comandos de OTP como
operaciones síncronas de port_control/3 con las respuestas que el driver de
OTP envía como datos de puerto: o, e y el nombre errno (los daños del
registro son eio, una clave eliminada einval, los demás errores de Windows
como los asigna el runtime de C), k y un nombre de clave, v, el tipo, el
nombre y los datos (cadenas sin su cero final, DWORDs big-endian), s y la clave
actual. Los listados devuelven todas las respuestas, cada una precedida por su
tamaño de 32 bits. Los nombres y las cadenas usan la página de códigos ANSI,
como el driver de OTP.
No proporcionado
Los drivers enlazados (linked-in drivers) y los NIF,
erlang:open_port({spawn_driver, Name}) para nombres de drivers de OTP, los
puertos de distribución, port_call/3 sobre los drivers proporcionados fuera
del protocolo propio de la biblioteca, los puertos de socket ocupados, y las
opciones overlapped_io, parallelism y busy_limits_msgq.
Clause