Clause
← Gesamte Dokumentation

Das englische Original wurde seit dieser Übersetzung geändert, daher können Teile davon veraltet sein. Auf Englisch lesen

Übersetzt aus dem englischen Original · 1e3a39c8-dirty · 2026-10-26

Ports

Programme erhalten Ports so, wie OTP sie definiert, und externe I/O läuft über sie. Die Port-Tabelle, die Exit-Regeln und die Port-Tasks stehen in runtime/src/scheduler/ports.cpp, die Builtins in runtime/src/builtins/erlang/ports.cpp, die Treiber und der I/O-Thread in runtime/src/ports/. OTP-Goldens: 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; Runtime-Test runtime_port_io.

Identität

Port-Tabelle und Besitz

Builtins und Port-Nachrichten

Fehler folgen OTP: badarg für einen geschlossenen oder ungültigen Port und ungültige Argumente, der POSIX-Grund (enoent, eacces) als error für ein fehlgeschlagenes Öffnen.

Nachrichten eines Ports gehen an seinen verbundenen Prozess: {Port, {data, Data}}, {Port, eof} (Option eof), {Port, {exit_status, Status}} (Option exit_status), {Port, closed} (nach {Pid, close}), {Port, connected} (an den alten Besitzer nach einem Connect).

Datenmodi und Optionen

OptionWirkung
stream (Standard), {packet, N} (N = 1, 2, 4)Bytes, wie sie ankommen, oder Nachrichten, gerahmt durch eine N Byte lange Big-Endian-Länge, die auch die Ausgabe erhält
{line, L}{eol, Line} pro Zeile, {noeol, Part} für Teile länger als L oder ein nicht abgeschlossenes Ende
binaryDaten als Binaries statt Byte-Listen
eof{Port, eof} am Ende der Eingabe; der Port bleibt offen, bis er geschlossen wird
exit_status{Port, {exit_status, S}}, wenn das Programm endet (Spawn-Ports)
use_stdio (Standard), nouse_stdio, stderr_to_stdout, in, out, hideWie in OTP (hide hat keine Wirkung)
{args, List}, {arg0, A}, {env, Env}, {cd, Dir}Spawn-Ports (Unterprozesse)
{busy_limits_port, {Low, High} | disabled}Gepufferte Ausgabe-Bytes, die den Port auslasten (ausgelastete Ports)

Ohne eof schließt das Ende der Eingabe den Port mit Grund normal, nach der Nachricht exit_status, falls diese angefordert wurde. Unbekannte Optionen ergeben badarg.

Treiber

Ein Treiber ist ein C++-Objekt hinter einem Port (runtime/src/ports/): Er öffnet die Ressource, nimmt Ausgabe an (port_command), beantwortet port_control/3, wenn er Steuerung unterstützt, meldet Eingabe und Fehler als Ereignisse und schließt. Treiber:

Die internen Treiber von file, den Sockets und der Registry werden mit {spawn_driver, Name} unter Clause-Namen geöffnet, und ihre port_control/3-Operationen sind ein Clause-Protokoll: Programme nutzen die Bibliotheksmodule, nicht die Protokolle prim_inet/efile von OTP.

I/O-Thread

Ein I/O-Thread pro Runtime (detail::Reactor, runtime/src/ports/reactor.hpp) betreibt einen Boost.Asio-io_context: einen I/O Completion Port unter Windows, epoll unter Linux, kqueue unter macOS. Der erste Port, der ihn braucht, startet ihn; er bedient jede Port-Art, sodass ein Port keinen eigenen Thread kostet:

runtime/src/ports/io*.cpp enthalten die Port-I/O (detail::IoService): Ihre Methoden übergeben Arbeit nur an den I/O-Thread, wo der gesamte I/O-Zustand liegt. Der I/O-Thread übergibt Eingabe so, wie sie gelesen wurde: rohe Bytes, das Ende der Eingabe, einen Lesefehler, den Exit-Status eines Programms. Socket-Ereignisse (Nachrichten, angenommene Verbindungen) werden auf die gleiche Weise übergeben.

Der Prototyp tests/prototypes/poller/ (run.py --wsl) zeigt das Aufwecken unter Windows (Completion Port) und WSL Linux (poll()): Ein untätiger Scheduler-Thread wacht 9–91 µs nach der Eingabe auf, und das Herunterfahren stoppt den I/O-Thread ohne Eingabe.

Am Programmende wird jeder Port geschlossen (Kindprogramme sehen das Ende der Eingabe; sie werden nicht beendet, wie in OTP), und der I/O-Thread stoppt: Er wird außerhalb des Executor-Mutex gejoint, weil eine laufende Zustellung ihn nimmt; ein Windows-fd-Leser, der in einem nicht abbrechbaren Lesevorgang blockiert, wird abgekoppelt und stellt nichts mehr zu.

Port-Tasks

Ein Port wird wie ein Prozess eingeplant: Was der I/O-Thread übergibt, wartet im Port, und der Port wartet in der Port-Warteschlange des Executors, bis ein Scheduler-Worker seinen Task ausführt.

Ausgelastete Ports

Ausgabe, die ein Treiber für den I/O-Thread puffert (Pipes gestarteter Programme), zählt gegen die Auslastungsgrenzen des Ports, busy_limits_port von OTP (Standardwerte: hoch 8.192 Bytes, niedrig 4.096; {busy_limits_port, {Low, High}} oder disabled als Option von open_port/2, Grenzen mindestens 1, eine niedrige Grenze über der hohen wird auf diese gesenkt):

Ein Port begrenzt auch die Eingabe, die er hält:

Unterprozesse

open_port({spawn, Command}, Options) und open_port({spawn_executable, File}, Options) starten ein Programm, dessen stdin und stdout Pipes des Ports sind:

Standard-I/O und Dateien

Sockets

Ein Socket ist ein Port, wie beim inet_drv-Backend von OTP: is_port(Socket) ist wahr, und Nachrichten im aktiven Modus sind {tcp, Socket, Data}, {tcp_closed, Socket}, {tcp_error, Socket, Reason}, {udp, Socket, Address, Port, Data}. Die Teilmenge: gen_tcp:connect/2,3,4 (Host und Port oder eine Socket-Adress-map), 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 (die Zielformen und Zusatzdaten von 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; Modi {active, true | false | once}, binary/list ({mode, M}), {packet, 0 | 1 | 2 | 4 | raw}, {backlog, N}, {ip, Address}/{ifaddr, Address}, inet/inet6; IPv4- und IPv6-Adressen als Tupel, Hostnamen als Strings oder Atome (loopback eingeschlossen).

Optionen jenseits des Datenmodus (ports/socket_options.cpp, library/kernel/clause_socket_options.erl) sind Treiber-records aus einem Code und einem Wert, angegeben beim Öffnen eines Sockets (gesetzt vor dem Binden oder Verbindungsaufbau) oder durch setopts/2; jede Option eines setopts/2-Aufrufs wird geprüft, bevor eine gesetzt wird. Die Systemoptionen (reuseaddr, keepalive, dontroute, linger, broadcast, sndbuf, recbuf, nodelay, tos, tclass, ipv6_v6only, multicast_ttl, multicast_loop, multicast_if, add_membership, drop_membership, exclusiveaddruse unter Windows, priority und reuseport, wo das System sie anbietet) gehen an setsockopt und werden mit getsockopt zurückgelesen; die Optionen, die inet_drv von OTP selbst hält (buffer, header, deliver, exit_on_close, send_timeout, Wasserstandsmarken, packet_size, ...), werden mit den Standardwerten von inet_drv gehalten und nicht angewendet (Unterschiede). Wie bei inet_drv erhöht ein Empfangspuffer buffer, bis es gesetzt wird, und getopts/2 lässt Optionen aus, die einem Socket-Typ fehlen (broadcast bei einem TCP-Socket, nodelay bei einem UDP-Socket, tclass bei IPv4), und weist unbekannte mit {error, einval} ab. Eine angenommene Verbindung übernimmt die Optionen ihres lauschenden Sockets. send/5 mit Zusatzdaten setzt tos, tclass oder ttl nur für dieses Datagramm; unter Windows ergibt es {error, einval}, wie OTP.

Jeder Socket zählt seinen Datenverkehr für getstat/1,2 wie inet_drv: Empfangene Pakete sind Lesevorgänge (TCP) oder Datagramme, gesendete Pakete sind send-Aufrufe (mit ihrem Paket-Header) oder Datagramme, mit den gleitenden Durchschnittswerten von inet_drv; send_pend ist die gepufferte und noch nicht geschriebene Ausgabe. getstatus liefert die Status-Flags von inet_drv (open, connected, connecting, listen, accepting), die info/1 und i/0,1,2 mit den Namen und der Modulspalte von OTP (inet_tcp, inet6_udp, ...) ausgeben. Abfragen eines geschlossenen Sockets antworten {error, einval}, wie OTP.

Das Modul socket (library/kernel/socket.erl) nutzt dieselben Treiber-Ports im passiven binary-Modus. Ein Stream-Socket kann vor dem Lauschen oder Verbindungsaufbau gebunden werden (Operation 22 öffnet und bindet den System-Socket, den listen zum Acceptor macht oder connect verbindet); ein Datagramm-Socket wird durch udp_open gebunden, implizit bei seinem ersten Senden. Das Ende einer Verbindung beantwortet jedes wartende recv (auch eines, das mehr Bytes anfordert, als übrig sind). socket hält die Bytes, die ein recv nicht übernommen hat, in seinem eigenen Puffer, sodass recv/2,3,4 die Antworten {error, {timeout, Data}} und {error, {closed, Data}} von OTP liefert.

Implementierung (runtime/src/ports/sockets.cpp):

Die Registry

win32reg öffnet {spawn_driver, "clause_registry"} unter Windows (runtime/src/ports/registry_windows.cpp). Der Treiber hält den Zustand des Registry-Treibers von OTP (den angeforderten Zugriff, den aktuellen Schlüssel und seine Wurzel, anfangs HKEY_CLASSES_ROOT) und beantwortet die neun Befehle von OTP als synchrone port_control/3-Operationen mit den Antworten, die der Treiber von OTP als Port-Daten sendet: o, e und der errno-Name (Registry-Schäden ergeben eio, ein gelöschter Schlüssel einval, andere Windows-Fehler werden wie von der C-Runtime abgebildet), k und ein Schlüsselname, v, der Typ, Name und die Daten (Strings ohne ihre abschließende Null, DWORDs Big-Endian), s und der aktuelle Schlüssel. Auflistungen geben jede Antwort zurück, jeweils mit ihrer 32-Bit-Größe davor. Namen und Strings nutzen die ANSI-Codepage, wie beim Treiber von OTP.

Nicht bereitgestellt

Linked-in-Treiber und NIFs, erlang:open_port({spawn_driver, Name}) für OTP-Treibernamen, Distributions-Ports, port_call/3 auf den bereitgestellten Treibern außer dem eigenen Protokoll der Bibliothek, ausgelastete Socket-Ports sowie die Optionen overlapped_io, parallelism und busy_limits_msgq.