Portar
Beslut i plan 11 step 57A (2026-10-08). Det ersätter beslutet från step 53 (inga portar, processer): program får portar så som OTP definierar dem, och extern I/O går genom dem. Steps 57B–57F implementerar det; varje avsnitt anger sitt step. Detta kontrakt fastställer representationen, drivrutinsmodellen och I/O-tråden innan någon källkod kan öppna en port.
Implementerat: identiteter, porttabellen, portarnas inbyggda funktioner och
meddelanden, länkar, monitorer, namn och exitsignaler för portar samt
fd-portar som endast skriver utdata (step 57B,
runtime/src/scheduler/ports.cpp, runtime/src/builtins/ports.cpp,
runtime/src/ports/; OTP-referenstest (golden) executables_port_identities);
I/O-tråden och indata till fd-portar med ström-, paket- och radindelning
(step 57C, runtime/src/ports/io*.cpp; OTP-referenstest
executables_port_input); delprocessportar, os:type/0, os:getenv/1 och
os:cmd/1 (step 57D, runtime/src/ports/spawn*.cpp,
library/stdlib/os.erl; OTP-referenstest executables_port_spawn);
fildrivrutinen, delmängden av biblioteksmodulen file och standard-indata
genom io:get_line/io:get_chars (step 57E, runtime/src/ports/file.cpp,
library/stdlib/{file,io}.erl; OTP-referenstest executables_file_io);
socketar (step 57F, OTP-referenstest executables_sockets); en händelsestyrd
I/O-tråd för alla sorters portar (step 57G1, runtime/src/ports/reactor.cpp;
OTP-referenstest executables_many_ports, runtime-test runtime_port_io);
portuppgifter på schemaläggarens arbetstrådar (step 57G2,
runtime/src/scheduler/ports.cpp; OTP-referenstest
executables_port_fairness); upptagna portar och begränsad indata (step
57G3; OTP-referenstester executables_busy_ports, executables_slow_owner).
Identitet
- En port är ett omedelbart ord (immediate): tagg
0x7(TermKind2::portunder den primära taggensee_termkind2, bredvid pid:ars0x3), med ett portnummer som nyttolast, hämtat ur en processgemensam sekvens som aldrig återanvänds, precis som pid-nummer (pid:ar). En runtime godtar endast de nummer den själv har utfärdat; förfalskade och främmande portord avvisas. - Utskrift:
#Port<0.N>;port_to_list/1ger den texten ochlist_to_port/1tolkar den (badarg för allt annat). - Ordning: tal < atomer < referenser < funs < portar < pid:ar < tupler, som i OTP; portar jämförs efter nummer.
- En stängd ports identitet förblir giltig:
is_port/1förblir sant,port_info/1,2svararundefined, och sändningar till den kastas. - Portar är omedelbara värden, så kopiering, skräpsamling och meddelanden behöver inget nytt.
Porttabell och ägarskap
- Varje runtime har en porttabell (nummer → port) i sin exekutor, skyddad av exekutorns mutex, liksom processernas länkar och namn (arbetstrådar).
- En port registrerar sin drivrutin, sin anslutna process (den som öppnade
porten, ändras av
port_connect/2eller{Pid, {connect, New}}), sina länkar, de monitorer som hålls på den, ett valfritt registrerat namn, sina alternativ samt sina bytesräknare för indata och utdata. open_port/2länkar den nya porten till den som öppnade den. När den anslutna processen avslutas stängs dess port; en port som stängs skickar exitsignaler till sina länkar och'DOWN'-meddelanden till sina monitorer med sin orsak (normalför en stängning som inte är ett fel, annars en POSIX-atom somepipe).- En exitsignal som når en port stänger den med signalens orsak (
killfrånexit/2somkilled), utomnormalgenom en länk från en annan process än den anslutna, vilket endast tar bort länken.exit(Port, normal)stänger porten. En ansluten process som har tagit bort länken lämnar sin port öppen när den avslutas. - Ett begäransmeddelande från en annan process än den anslutna, eller ett
felformat meddelande, skickar exitsignalen
badsigfrån porten till den anslutna processen; porten förblir öppen. - En ports exitsignaler och meddelanden till en process som körs på en annan
arbetstråd väntar tills den processens tidsskiva tar slut (de innehåller
endast atomer, pid:ar och portar); ett
exit(Port, Reason)med en orsaksterm i avsändarens heap gör att den inbyggda funktionen körs igen tills ingen länkad eller övervakande process körs någon annanstans, som för processignaler (arbetstrådar). - Portar kan få registrerade namn (
register/2) ochmonitor(port, Port);link/1ochunlink/1accepterar dem.
Inbyggda funktioner och portmeddelanden
| Inbyggd funktion eller meddelande | Step |
|---|---|
is_port/1 sant för portar, port_to_list/1, list_to_port/1, ports/0 | 57B |
port_info/1,2 (name, links, id, connected, input, output, os_pid, monitors, monitored_by, registered_name) | 57B |
port_close/1, port_connect/2, port_command/2,3 | 57B |
Port ! {Pid, {command, Data}}, {Pid, close}, {Pid, {connect, New}} | 57B |
link/1, unlink/1, monitor(port, P), exit/2 på portar; register/2 av en port | 57B |
port_control/3, port_call/3 | 57B (badarg för drivrutiner utan styrning); används av drivrutinerna i 57E/57F |
open_port({fd, In, Out}, Opts) | 57B utdata; indata från 57C |
open_port({spawn, Command} | {spawn_executable, File}, Opts) | 57D |
Interna drivrutiner i projektets bibliotek (file, socketar) | 57E, 57F |
De inbyggda funktionerna ersätter diagnostikerna [ports] notimpl från
step 53; funktionen ports blir implementerad i 57B. Fel följer OTP: badarg
för en stängd eller ogiltig port och felaktiga argument, POSIX-orsaken
(enoent, eacces) som ett error för en öppning som misslyckas.
Meddelanden från en port går till dess anslutna process:
{Port, {data, Data}}, {Port, eof} (alternativet eof),
{Port, {exit_status, Status}} (alternativet exit_status),
{Port, closed} (efter {Pid, close}), {Port, connected} (till den
tidigare ägaren efter en anslutning).
Datalägen och alternativ
| Alternativ | Effekt |
|---|---|
stream (standard), {packet, N} (N = 1, 2, 4) | Bytes allteftersom de anländer, eller meddelanden inramade av en N-bytes längd i big-endian som även utdata får |
{line, L} | {eol, Line} per rad, {noeol, Part} för delar längre än L eller ett oavslutat slut |
binary | Data som binaries i stället för bytelistor |
eof | {Port, eof} vid slutet av indata; porten förblir öppen tills den stängs |
exit_status | {Port, {exit_status, S}} när programmet avslutas (spawn-portar) |
use_stdio (standard), nouse_stdio, stderr_to_stdout, in, out, hide | Som i OTP (hide har ingen effekt) |
{args, List}, {arg0, A}, {env, Env}, {cd, Dir} | Spawn-portar (57D) |
{busy_limits_port, {Low, High} | disabled} | Köade utdatabytes som gör porten upptagen (upptagna portar, 57G3) |
Utan eof stänger slutet av indata porten med orsaken normal, efter
meddelandet exit_status om ett sådant begärdes. Okända alternativ ger
badarg.
Drivrutiner
En drivrutin är ett C++-objekt bakom en port (runtime/src/ports/): den
öppnar resursen, tar emot utdata (port_command), svarar på
port_control/3 när den stöder styrning, rapporterar indata och fel som
händelser och stänger. Drivrutiner:
fd: befintliga deskriptorer, utdata skrivs synkront (57B), indata genom I/O-tråden (57C).spawn: ett barnprogram med sin stdin och stdout som rör (pipes) (57D).file: en öppen fil i projektbibliotekets modulfile; dess operationer är synkronaport_control/3-anrop som kan blockera arbetstråden som kör anroparen under diskoperationens varaktighet, som OTP:s smutsiga I/O-schemaläggare (dirty I/O schedulers) gör (57E).tcp_inet,udp_inet: socketar för projektbiblioteketsgen_tcp,gen_udpochinetovanpå Boost.Asio; anslutningar, accepteranden och mottagningar är asynkrona (57F, socketar).
De interna drivrutinerna för file och socketarna öppnas med
{spawn_driver, Name} under Clause-namn, och deras port_control/3-operationer
är ett Clause-protokoll: program använder biblioteksmodulerna, inte OTP:s
protokoll prim_inet/efile.
I/O-tråd
En I/O-tråd per runtime (detail::Reactor, runtime/src/ports/reactor.hpp,
step 57G1) kör en Boost.Asio-io_context: en I/O-kompletteringsport (I/O
completion port) på Windows, epoll på Linux, kqueue på macOS. Den första
porten som behöver den startar den; den betjänar alla sorters portar, så en
port kostar ingen egen tråd:
- Rör för startade program: överlappade namngivna rör på Windows
(
windows::stream_handle; anonyma rör kan inte överlappas), vanliga rör annars (posix::stream_descriptor). Utdata köas och skrivs i ordning med asynkrona skrivningar. fd-indata på Linux och macOS: tråden väntar tills deskriptorn är läsbar, sedan tar ettread()det som finns, så att programmets egna deskriptorer behåller sitt blockerande läge; en vanlig fil, som inte går att vänta på, läses direkt.- Programavslut: på Windows väntar systemets trådpool för väntan
(
RegisterWaitForSingleObject, som Asiosobject_handleanvänder) på processhandtaget och avslutet rapporteras på I/O-tråden; på Linux och macOS tar enSIGCHLD-hanterare (signal_set) ochwaitpid(WNOHANG)hand om varje bevakat program, även efter att dess port har stängts. - Socketar (socketar).
- Undantag: ett
fd-indatahandtag på Windows (en konsol eller ett ärvt anonymt rör) kan inte överlappas, så det läses av en egen blockerande tråd, som libuv och ERTS gör; att stänga porten avbryter läsningen (CancelSynchronousIo) och släpper tråden.
runtime/src/ports/io*.cpp innehåller port-I/O:n (detail::IoService): dess
metoder lägger endast upp arbete på I/O-tråden, där allt I/O-tillstånd finns.
- I/O-tråden lämnar över indata så som den lästes: råa bytes, slutet av indata, ett läsfel, ett programs avslutsstatus. Sockethändelser (meddelanden, accepterade anslutningar) lämnas över på samma sätt.
Portuppgifter
Step 57G2. En port schemaläggs som en process: det som I/O-tråden lämnar över väntar i porten, och porten väntar i exekutorns portkö tills en arbetstråd i schemaläggaren kör dess uppgift.
- Arbetstrådar tar växelvis en portuppgift och en processtidsskiva medan båda köerna har arbete, så en port som översvämmas av indata kan inte svälta processer, och processer kan inte svälta portar; en ledig arbetstråd tar det som står i kö.
- En uppgift körs under exekutorns mutex i
PORT_TASK_REDUCTIONS(en tidsskivas 4 000): varje meddelande den levererar kostar 100 reduktioner plus en per 64 bytes som det bär. En port med arbete kvar köas igen längst bak. - Uppgiften ramar in indata (
InputDecoder): strömindata kommer i de bitar som läsningar returnerar;{packet, N}håller kvar bytes tills ett helt paket har anlänt (ett ofullständigt paket vid slutet av indata kastas, som i OTP);{line, L}delar vid\n, skickar en rad längre änLsom{noeol, Part}-delar och ett oavslutat slut som{noeol, Rest}.port_info(P, input)räknar varje läst byte, inklusive radbrytningar och pakethuvuden, när den läses. - Slutet av indata skickar
{Port, eof}med alternativeteof, annars stängs porten med orsakennormal; ett läsfel stänger den medeio. - Ett meddelande blir ett meddelande till dess process direkt när den processen inte körs på en arbetstråd (och väcker den som vilket meddelande som helst), annars när dess tidsskiva tar slut, så att heapen hos en körande process aldrig berörs av en annan tråd.
- Kommandon, stängningar, anslutningar och exitsignaler som skickas till en
port verkar direkt på avsändarens arbetstråd, som ERTS gör för en port som
inte är upptagen: en
fd-port skriver sin utdata direkt på anroparens arbetstråd; rör- och socketdrivrutinerna köar utdata och låter I/O-tråden skriva den (57D, 57F, 57G1); en upptagen port suspenderar avsändaren (upptagna portar).
Prototypen tests/prototypes/poller/ (run.py --wsl) visar väckningen på
Windows (kompletteringsport) och WSL Linux (poll()): en ledig
schemaläggartråd vaknar 9–91 µs efter indata, och nedstängning stoppar
I/O-tråden utan indata.
Vid programmets slut stängs varje port (barnprogram ser slutet av indata; de
dödas inte, som i OTP) och I/O-tråden stoppas: den förenas (join) utanför
exekutorns mutex, eftersom en pågående leverans tar mutexen; en Windows-läsare
för fd som är blockerad i en läsning som inte kan avbrytas kopplas loss och
levererar inget mer.
Upptagna portar
Step 57G3. Utdata som en drivrutin köar för I/O-tråden (rör för startade
program) räknas mot portens gränser för upptagenhet, OTP:s
busy_limits_port (standardvärden: hög 8 192 bytes, låg 4 096;
{busy_limits_port, {Low, High}} eller disabled som ett alternativ till
open_port/2, gränser minst 1, en låg gräns över den höga sänks till den):
- En skrivning som tar den köade utdatan till den höga gränsen eller över skickas ändå och gör porten upptagen; den förblir upptagen tills I/O-tråden har skrivit så mycket att mindre än den låga gränsen står i kö.
port_command/2ochPort ! {Pid, {command, Data}}till en upptagen port suspenderar avsändaren, som inte skriver något; när porten inte längre är upptagen, eller stängs, kör avsändaren sin inbyggda funktion igen (en stängd port ger dåbadarg, som i OTP). En suspenderad process tar fortfarande emot exitsignaler.port_command/3mednosuspendreturnerarfalseför en upptagen port och skriver inget;forcekastarnotsup, eftersom OTP:s spawn- ochfd-drivrutiner inte tillåter det.port_info(P, queue_size)är den utdata som står i kö och ännu inte har skrivits.fd-portar skriver direkt och socketutdata går genomport_control/3, så ingen av dem är någonsin upptagen.
En port begränsar även den indata den håller:
- När mer än 64 KiB rå indata väntar på portens uppgift slutar I/O-tråden att läsa porten (ett program som skriver till den blockeras då, som vid ett fullt rör) tills uppgiften har tagit den under 32 KiB.
- Medan den anslutna processen kan köras (den körs eller står i kö, väntar
inte i en
receive, är inte suspenderad eller blockerad) och redan håller 1 024 meddelanden (eller har lika många portsignaler som väntar på att dess tidsskiva ska ta slut), levererar portens uppgift inget mer förrän den processens tidsskiva har tagit slut. En process som väntar i enreceivefår alltid indata, så en receive efter ett senare meddelande från porten kan inte vänta för evigt.
Delprocesser
Step 57D. open_port({spawn, Command}, Options) och
open_port({spawn_executable, File}, Options) startar ett program vars stdin
och stdout är portens rör:
{spawn, Command}: på Linux och macOS/bin/sh -c Command; på WindowsCreateProcessWmedCommandsom kommandorad (den hittar programmet i sökvägen).{spawn_executable, File}körFileutan skal, med argv[0]{arg0, A}(standardFile) och{args, List}; på Windows citeras argument enligt reglerna förCommandLineToArgvW.{env, [{Name, Value | false}]}sätter eller tar bort variabler för barnet,{cd, Dir}dess katalog,stderr_to_stdoutslår ihop stderr med porten; annars delar barnet programmets stderr.inöppnar inget stdin-rör ochoutinget stdout-rör (null-enheten i stället).hideochoverlapped_iohar ingen effekt.- Ett program som inte kan startas kastar
error:Reasonmed POSIX-orsaken (enoent,eacces,enoexec); felaktiga namn och alternativ kastarbadarg. - Utdata köas och skrivs av I/O-tråden, med portens gränser för upptagenhet (upptagna portar); att stänga porten låter den slutföra det som står i kö och stänger sedan programmets stdin. Programmet dödas aldrig; det avslutas vanligtvis vid slutet av indata.
- Alternativet
exit_statusskickar{Port, {exit_status, S}}när programmet har avslutats (dess avslutskod; på Linux och macOS 128 plus signalen för ett program som en signal avslutade) och före{Port, eof}eller stängningen vid slutet av indata; en port som inte läser något rapporterar det när programmet avslutas och stängs sedan. port_info(P, os_pid)är programmets process-id;nameär kommandot eller filen.- Bibliotekets
os:cmd/1körCommandmedCOMSPEC /cpå Windows (cmdom den inte är satt) eller/bin/sh -c, samlar in stdout och stderr tills programmet stänger dem och returnerar bytes som en lista;os:type/0är{win32, nt},{unix, linux}eller{unix, darwin};os:getenv/1returnerar en sträng ellerfalse.
Standard-I/O och filer
Step 57E.
io:format,io:put_charsocherlang:displayskriver fortfarande direkt till standard-utdata (io). Standard-indata läses av en serverprocess i biblioteket, registrerad somclause_stdinoch startad vid första användning, som äger en{fd, 0, 1}-port:io:get_line/1,2skriver prompten och returnerar sedan nästa rad med dess radbrytning, resten av indata utan radbrytning i slutet, och sedaneof;io:get_chars/2,3returnerar upp tillNtecken, sedaneof. Begäranden från flera processer besvaras i ankomstordning. Indata returneras som bytes (ett listelement per byte).- Bibliotekets modul
filedriver runtimens fildrivrutin,{spawn_driver, "clause_file"}, medport_control/3. Dess operationer (ports/file.cpp: open, read, write, position, read_line, close och sökvägsoperationerna read_file, write_file, delete, rename, list_dir, make_dir, del_dir) är synkrona systemanrop på anroparens arbetstråd, utanför exekutorns mutex; deras svar börjar med en statusbyte (0 ok, 1 fel och dess POSIX-orsak, 2 filslut). Protokollet är Clauses eget. file:open/2returnerar en I/O-servers pid som äger en filport och är länkad till den som öppnade (lägenaread,write,append,exclusive,binary; andra lägen ignoreras, som OTP ignorerar alternativ den inte använder).read/2,read_line/1,write/2,position/2ochio:get_line/2,io:get_chars/3på den är begäranden till den servern; efterclose/1returnerar begäranden{error, terminated}ochclose/1förblirok.- Fel följer OTP:
{error, enoent},eexist,eisdir(en katalog som öppnas eller läses som en fil),einval(en negativ position),ebadf(läsning av en fil som bara är skrivbar eller skrivning till en som bara är läsbar),badargför felaktiga namn och data. Filnamn är strängar, binaries eller atomer, kodade som UTF-8. - En modul som programmet refererar till och som är en inbyggd funktion i
katalogen (
io:format) drar inte längre in biblioteksmodulen med det namnet i programmet; endast ett anrop av en av dess Erlang-funktioner gör det.
Socketar (57F)
En socket är en port, som med OTP:s backend inet_drv: is_port(Socket) är
sant och meddelanden i aktivt läge är {tcp, Socket, Data},
{tcp_closed, Socket}, {tcp_error, Socket, Reason},
{udp, Socket, Address, Port, Data}. Delmängden: gen_tcp:connect/3,4,
listen/2, accept/1,2, send/2, recv/2,3, close/1,
controlling_process/2, shutdown/2; gen_udp:open/1,2, send/4,
recv/2,3, close/1; inet:setopts/2, inet:port/1, inet:peername/1,
inet:sockname/1; lägena {active, true | false | once}, binary/list,
{packet, 0 | 1 | 2 | 4 | raw}, {reuseaddr, Bool}, {backlog, N},
{ip, Address}/{ifaddr, Address}, inet/inet6; IPv4- och
IPv6-adresser som tupler, värdnamn som strängar eller atomer (loopback
inräknat). Justeringsalternativen nodelay, keepalive, send_timeout,
send_timeout_close, delay_send och exit_on_close accepteras men
tillämpas inte; alla andra alternativ ger exit(badarg), som för ett
ogiltigt alternativ i OTP.
Implementation (runtime/src/ports/sockets.cpp):
- Runtimens I/O-tråd (I/O-tråd) betjänar socketarna; varje
sockets tillstånd finns på den tråden. Biblioteket öppnar en port med
{spawn_driver, "tcp_inet" | "udp_inet"}och driver den medport_control/3; den anropande arbetstråden lägger upp operationen på I/O-tråden och väntar på dess synkrona svar (statusbyte 0 och ett resultat, eller 1 och en POSIX-orsak). - Operationer som väntar (connect, accept, recv) svarar senare med ett
meddelande
{clause_socket, Socket, Reply}till sin anropare, så att anroparen blockeras i en vanligreceiveoch andra processer fortsätter att köras. En tidsgräns avbryter begäran; själva avbrottet svararcancelledefter allt som socketen skickat dessförinnan, så att ett svar som vann kapplöpningen returneras i stället för att gå förlorat. - Socketmeddelanden beskrivs utanför heapen (
ports/value.hpp) och byggs i mottagarens heap när de levereras, under exekutorns regler i I/O-tråd. En accepterad anslutning blir en ny port som ägs av och är länkad till den som anropadeaccept, med den lyssnande socketens läge. controlling_process/2stoppar aktiv leverans, flyttar socketens meddelanden som redan skickats till den tidigare ägaren till den nya, och ansluter sedan om porten, som OTP:sinetgör; endast ägaren får anropa den ({error, not_owner}).- Utdata köas utan tak och skrivs i ordning på I/O-tråden. Att stänga en port
skickar det som står i kö och stänger sedan anslutningen på ett ordnat sätt
(FIN); en stängd port svarar varje väntande anropare
{error, closed}. Namnuppslagning körs på anroparens arbetstråd, eftersom den blockerar. - Socketportar schemaläggs som andra portar (portuppgifter): en sockets meddelanden väntar i dess port tills portens uppgift levererar dem.
Tillhandahålls inte
Inlänkade drivrutiner (linked-in drivers) och NIF:ar,
erlang:open_port({spawn_driver, Name}) för OTP-drivrutinsnamn,
distributionsportar, port_call/3 på de tillhandahållna drivrutinerna utöver
bibliotekets eget protokoll, upptagna socketportar samt alternativen
overlapped_io, parallelism och busy_limits_msgq.
Clause