Il firmware ora avvia NATALIZIOOS. Catena verificata: firmware → kernel → login grafico → menu ambienti → desktop.
db mancanti e un operando di dimensione errataadd bx, 512 con BX a 16 bit: dopo 128 settori il kernel si sovrascriveva. Corretto avanzando il segmentojmp wait_for_boot riazzerava il contatore del timeout: restava sullo splash all'infinitodd gdt_start era un offset, non l'indirizzo lineare: stage2 sta a 0x8000jmp CODE_SEG:protected_mode non rilocato: saltava a 0x1bf, dentro la tabella dei vettori di interruptcall KERNEL_TARGET con una costante viene assemblato da NASM come call RELATIVO. Saltava a 0x6790 invece che a 0x100000. Corretto con mov eax, KERNEL_TARGET + call eax-d int,cpu_reset, -d exec e xp /8xb 0x100000 dal monitorSequenza intera verificata dall'accensione al desktop.
Catena completa funzionante: NATALIZIOFIR 6.10 (POST, Setup F2, Boot Menu F8, Diagnostica F9/F10) → Stage2 NATALIZIOOS → boot selector → login → menu ambienti → Desktop. Un comando: make run-firmware
Otto app distribuite nelle cartelle per argomento, non ammucchiate in una sola.
Diagnostica
Sistema
File
DT_FOLDER_MAX_APPS alzato da 14 a 16: Diagnostica e Sistema erano a 13 e le nuove non ci stavano.
Tre interventi distinti.
Data della boot animation
08/2026 → 09/2026, corretta in drivers/boot_anim.c (due punti) e kernel/gfxboot.c.
Cruscotto in Desktop 2
Accanto al pulsante DEBUG, che resta intatto, c'e' ora CRUSCOTTO: apre una sovrapposizione a schermo intero con cinque pannelli sullo stato del sistema. Il desktop sotto non viene toccato.
Tutti i valori sono letti dal sistema, non scritti a mano. Si naviga con i tasti 1-5 o cliccando la barra laterale; ESC chiude.
Nota: Desktop 2 ha sotto quasi 13.000 righe e 28 applicazioni in desktop2_core/. Il file kernel/desktop2.c e' solo il ponte verso mouse, tastiera e framebuffer.
Sfondo : cielo al tramonto e catena montuosa. Disegnato, non fotografato: una foto da 1024×768 occuperebbe megabyte dentro il kernel, qui il paesaggio viene calcolato a ogni disegno e costa qualche centinaio di byte.
Il profilo nasce da una funzione pseudo-casuale deterministica: stesso profilo a ogni avvio, nessuna tabella da memorizzare.
Due correzioni durante la lavorazione. Le onde da 16 pixel facevano sembrare i monti una lama di sega: portate a 190, con interpolazione morbida al posto di quella lineare. E la luce del tramonto misurava la pendenza su un pixel solo, lasciando strisce verticali sulle pareti: ora si calcola su una finestra di venti pixel.
Nella versione precedente era cambiato solo lo sfondo. Qui cambia l'interfaccia.
Barra superiore
Da gradiente beige con testo nero a barra scura traslucida con testo chiaro: si intuisce il paesaggio dietro. La trasparenza e' calcolata a mano mescolando ogni pixel col nero al 72%, perche' il framebuffer non ha un canale alfa.
Dock
Da pannello crema a scuro traslucido, stessa tecnica. Gli angoli arrotondati sono rispettati nella fusione, altrimenti la trasparenza sbordava. Icone da 40 a 52 pixel, angoli da 8 a 12.
Finestre
Barra del titolo da 22 a 28 pixel, gradiente quasi piatto, pallini da raggio 5 a 6 e piu' distanziati.
Icone con volume
Le piastrelle erano quadrati di tinta piatta con un lucido in cima. Ora hanno una sfumatura verticale su tutta l'altezza e un filo bianco sul bordo superiore. La sfumatura rispetta gli angoli arrotondati sia sopra che sotto.
Etichette leggibili
Con lo sfondo a montagne il testo bianco spariva sulle creste innevate e sul cielo chiaro, e l'ombra da sola non bastava. Ogni nome sta ora su una pastiglia semitrasparente con angoli smussati: non copre il paesaggio, ma si legge ovunque capiti l'icona.
Sul testo
L'antialiasing non e' stato fatto: il font e' bitmap 8×16, ogni carattere e' una griglia di punti accesi o spenti. Per ammorbidirlo servirebbe un font nuovo con livelli di grigio e un motore di disegno che li usi, un lavoro a se'. Qui e' stato risolto il problema vero, che era il contrasto.
Segnalato: su Desktop 2 il cruscotto e il pulsante DEBUG tremolano.
La causa
A ogni fotogramma desktop2_core disegnava tutto in un buffer, d2_blit lo copiava sullo schermo, e poi cruscotto e pulsanti venivano disegnati direttamente sulla superficie visibile. Al giro dopo la copia li cancellava e venivano ridisegnati: quella finestra fra cancellazione e ridisegno si vedeva a occhio.
La correzione
Non serve una GPU virtuale, ma l'idea di fondo era giusta: mancava il doppio buffer per le sovrapposizioni. In drivers/fb.c la destinazione di disegno e' ora reindirizzabile; tutte le primitive passano da fb_put_pixel, quindi e' bastato aggiungere il reindirizzamento li'.
fb_set_target(buf, w, h, pitch) disegna nel buffer, fb_reset_target() torna allo schermo. Desktop 2 compone cruscotto e pulsanti dentro il buffer prima della copia: lo schermo riceve un fotogramma gia' finito e non vede mai uno stato intermedio.
Verifica
Quattro fotogrammi consecutivi, impronta della barra laterale del cruscotto: c7d3e08bc1 quattro volte, identiche. Nella zona dei pulsanti 658 pixel chiari in tutti i fotogrammi; prima il conteggio variava.
Il meccanismo non e' specifico di Desktop 2: qualunque parte del sistema puo' comporre fuori campo e riversare in un colpo solo.
Antialiasing: font con livelli di copertura
Il font resta 6×8, ma ogni pixel ha ora una copertura da 0 a 255 invece che acceso/spento. Ogni glifo viene ingrandito tre volte con scale3x, che sui contorni obliqui inserisce i pixel intermedi; il risultato a 18×24 viene ridotto contando quanti dei nove sotto-pixel sono accesi.
.###.. 099900 #...#. 930390 #####. -> 999990 #...#. 930390 #...#. 900090
Le aste restano piene, solo i bordi obliqui prendono valori intermedi. Sfocare e basta non andava bene: la sfocatura ammorbidisce tutto, aste comprese, e a questa dimensione il testo diventa illeggibile. Costo: 6 KB di tabella.
Adattatore video virtuale
Nuovo drivers/vgpu.c, nello spirito del Basic Display Adapter: chi disegna non parla piu' con lo schermo ma con un adattatore che gli presta una superficie e decide quando mandarla a video. Niente stati intermedi, copia della sola zona cambiata, e misure leggibili. Nuovo pannello Video nel cruscotto (tasto 6).
Due difetti trovati collegandolo
fb_clear e fb_fill_rect scrivevano diritte in memoria video saltando fb_put_pixel: il riempimento finiva sullo schermo mentre il testo finiva nel buffer, e la sovrapposizione appariva trasparente.
Rosso e blu risultavano invertiti: la superficie di Desktop 2 tiene il rosso nel byte basso, fb_put_pixel in alto. Aggiunto fb_set_target_ex(..., swap_rb), con conversione in scrittura e in lettura cosi' anche la mescolanza dell'antialiasing resta corretta.
Nella 0.2.4.9 l'adattatore serviva solo a Desktop 2. Ora e' un servizio comune.
Il driver video aveva gia' un doppio buffer (fb_set_double_buffer e fb_present), ma veniva acceso a mano in punti sparsi e non misurava nulla. Nessuno lo accendeva per il menu di avvio o per Desktop 1.
vgpu_sys_enable(); vgpu_sys_begin(); ...disegno... vgpu_sys_present();
Acceso all'ingresso di Desktop 1, Desktop 2 e del menu di avvio. Tutte le chiamate sparse a fb_present() passano ora dall'adattatore. Chi non lo usa continua a funzionare come prima: l'adattatore non si impone a nessuno.
Misure comuni
Il pannello Video distingue fotogrammi di Desktop 2 e fotogrammi totali. Nella prova: 302 totali contro 152 del solo Desktop 2 — la differenza e' il resto del sistema, che prima non veniva contato.
L'adattatore video non decide piu' solo come il fotogramma arriva a video, ma anche con quale resa viene disegnato.
Uno solo per tutto il sistema
Cambiando stile cambiano insieme Desktop 1 (sfondo con caduta di luce, ombre piu' profonde), Desktop 2 (tramonto piu' caldo, barre del titolo sfumate), il login (sfondo sfumato invece di tinta unica, in 5 punti) e le finestre.
Si sceglie dal cruscotto, pannello Video, con due riquadri cliccabili, oppure col tasto S.
Aiuti per chi vuole adeguarsi
vgpu_shade(rgb, delta) vgpu_panel(x, y, w, h, rgb) vgpu_shadow(x, y, w, h, profondita)
In stile base non fanno nulla e costano un confronto. vgpu_shadow legge il pixel e lo scurisce invece di scriverci sopra un grigio: cosi' l'ombra segue il colore di cio' su cui cade.
Onesta' sulla copertura: boot animation e shell includono l'adattatore ma non lo interrogano ancora. Sono collegate per essere pronte, ma la resa realistica li' e' ancora da fare.
Lo stesso binario funziona con 32, 64, 128, 256 MB e 1 GB.
Due limiti, non uno
Il gestore della memoria fisica aveva un tetto di 128 MB: alzarlo costa solo la mappa dei frame liberi, 32 KB per 1 GB. Ma la paginazione ne mappava 64, cioe' meno di quanto il gestore dichiarasse gia' di reggere. Portata da 16 a 256 tabelle: statiche, perche' la paginazione si accende prima che il gestore sia utilizzabile, e in memoria non inizializzata, quindi il binario non cresce.
Il difetto scoperto alzando il tetto
total_frames valeva sempre il tetto gestibile, non la memoria installata. Con il tetto a 128 MB l'errore passava inosservato; a 1 GB una macchina con 32 MB dichiarava 1 GB totali e zero liberi.
prima dopo -m 32M 1024 MB / 0 liberi 31 MB -m 256M 1024 MB / 221 liberi 255 MB / 221 liberi -m 1024M 1024 MB / 999 liberi 1029 MB / 999 liberi
Verifica: avvio completo fino al login con tutte e cinque le dimensioni, pannello Memoria controllato su 32, 64, 256 e 1024 MB.
Segnalato: la modalita' realistica non era realistica. Era vero — qualche sfumatura e un'ombra, differenze troppo sottili per meritare quel nome.
Sei effetti nuovi nell'adattatore
Dove sono applicati
Sfondo di Desktop 2 (grana e vignettatura — senza queste il paesaggio resta "disegnato" per quanto siano curati i monti), barre del titolo (smusso e riflesso), dock (riflesso, occlusione e ombra: diventa un pannello di vetro appoggiato sopra il desktop), icone (smusso, lucido, ombra).
Nota tecnica: la grana usa una funzione pseudo-casuale deterministica — stesso punto, stesso valore — altrimenti il fondo tremolerebbe a ogni fotogramma. Il riflesso ricava l'inclinazione dalla somma di riga e colonna, e la banda si assottiglia scendendo.
drivers/cpu.c — identificazione del processore
La schermata di avvio dichiarava "QEMU Virtual CPU", "version 2.5+", "1 vCPU" scritti a mano nel sorgente: restavano quelli su qualunque macchina, esattamente come la memoria che diceva sempre 128 MB. Ora produttore, nome commerciale, famiglia, modello e sette capacita' (FPU, TSC, MSR, PAE, APIC, SSE, SSE2) vengono letti via CPUID.
Frequenza misurata, non dichiarata: si contano i cicli del processore in un decimo di secondo scandito dal timer. Sulla macchina di prova: 2099 MHz. Prima di usare CPUID il driver verifica che esista, provando a cambiare il bit 21 di EFLAGS — sui 386 quel bit e' fisso, ed e' cosi' che si distinguono.
drivers/rtc.c — orologio di sistema
La lettura dell'orologio era duplicata in quattro punti (commands.c, desktop.c, gfxshell_main.c, dos.c), con piccole differenze fra le copie. Qui ce n'e' una sola, che gestisce le due insidie del CMOS: l'orologio puo' essere in mezzo a un aggiornamento mentre lo si interroga, e restituisce i valori in BCD, dove 0x25 significa 25 e non 37.
drivers/speaker.c — altoparlante di sistema
Tre segnali riconoscibili: due note ascendenti per l'esito positivo, due discendenti per l'errore, tre brevi per l'attenzione. Non serve a fare musica: un errore di avvio che si sente e' piu' utile di uno che scorre via.
Nuovo pannello Processore nel cruscotto (tasto 7): mostra quello che i driver leggono, cosi' i dati sono verificabili invece che dichiarati.
Due compilatori diversi
Qui si compila con gcc, sulla macchina di sviluppo con clang. kernel/desktop2.c usava pmm_used_frames, task_count, heap_total, heap_used e ata_read_sector senza averne la dichiarazione: per gcc e' un avviso e la build passa, per clang e' un errore e si ferma. Ecco perche' ogni pacchetto arrivava da riparare a mano.
Aggiunti gli include mancanti, e soprattutto -Werror=implicit-function-declaration nel Makefile: ora gcc e' severo quanto clang. La seconda correzione conta piu' della prima — impedisce che se ne ripresenti un altro dello stesso tipo.
Modalita' realistica: era inusabile
Vignettatura e grana chiamavano fb_get_pixel e fb_put_pixel per ogni pixel: a 1024×768 sono oltre un milione e mezzo di chiamate a funzione per fotogramma. Riscritte con accesso diretto alla superficie tramite puntatore.
stile base 302 fotogrammi stile realistico 304 fotogrammi
Errore in modalita' testo: non riprodotto. Il page fault durante "Paging (4 KB)" qui non si verifica, provato con 64 MB sia dall'immagine testuale sia da quella grafica. Queste sono pero' le prime build compilate con i controlli severi attivi: e' possibile che il guasto dipendesse da come erano state sistemate a mano le dichiarazioni mancanti.
L'archivio di NATALIZIOOS(2023) contiene 1.558.070 righe di C e Objective-C. AppKit — la parte che serve per il terzo desktop — poggia su quattro strati che NATALIZIOOS(2026) non ha: runtime a oggetti, Foundation, Mach e Display PostScript. Il runtime e' il primo: senza, non si compila una sola riga del resto.
Strutture identiche all'originale
Stessi campi, stesso ordine, stessi nomi di src/objc-1/objc.h. Non e' pignoleria: il codice di AppKit accede a questi campi direttamente, e un ordine diverso lo farebbe leggere nel posto sbagliato.
Tre meccanismi
La prova
Il pannello non mostra numeri dichiarati: crea davvero una classe Punto, sottoclasse di Object, ne fa un'istanza e le manda messaggi. La riga che conta e' "ereditato da Object": name non e' definito in Punto, viene trovato risalendo la catena. E' l'ereditarieta' che funziona.
Cosa manca
Categorie, protocolli, posing, caricamento dinamico, eccezioni, conteggio dei riferimenti. E objc_msgSend e' scritto in C: passa bene i primi due argomenti, mentre l'originale e' in assembly e li passa tutti senza toccarli. Serve la versione in assembly prima di Foundation.
Perche' si parte da qui
L'archivio contiene **1.558.070 righe** di C e Objective-C. AppKit -
la parte che serve per il terzo desktop - e' scritta in
Objective-C e poggia su quattro strati che NATALIZIOOS(2026) non ha:
runtime a oggetti, Foundation, Mach e Display PostScript.
Il runtime e' il primo: senza, non si compila una sola riga del
resto. E' l'unico punto di partenza possibile.
Cosa c'e' in questo passo
`objc/objc.h` e `objc/runtime.c` — **le strutture rispettano
esattamente quelle originali** (`src/objc-1/objc.h` e `objc-class.h`):
stessi campi, stesso ordine, stessi nomi. Non e' pignoleria: il
codice di AppKit accede a questi campi direttamente, e un ordine
diverso lo farebbe leggere nel posto sbagliato.
**Tre meccanismi, in ordine di dipendenza:**
1. **Selettori** — un nome di metodo ridotto a un numero:
`"setTitle:"` diventa sempre lo stesso puntatore in tutto il
sistema, e confrontare due metodi costa un confronto di puntatori
invece di scorrere due stringhe.
2. **Registro delle classi** — ogni classe si ritrova dal nome, come
serve a `objc_getClass`.
3. **Invio dei messaggi** — dato un oggetto e un selettore, trovare
il codice: si guarda nella classe, poi nella superclasse, fino
alla radice. Con una cache a 64 posizioni, perche' senza sarebbe
lentissimo.
`objc/Object.c` — la classe radice, come nell'originale: `alloc`,
`init`, `free`, `self`, `class`, `name`, `respondsTo:`.
La prova
Nuovo pannello **Objective-C** nel cruscotto (tasto 8). Non mostra
numeri dichiarati: **crea davvero una classe** (`Punto`, sottoclasse
di `Object`), ne fa un'istanza e le manda messaggi.
Classi registrate 2
Selettori 9
Messaggi inviati 705
Trovati in cache 700
[Punto alloc] init Punto
ereditato da Object Punto <- name non esiste in Punto
risponde a init si
risponde a volare no
La riga che conta e' la seconda: `name` non e' definito in `Punto`,
viene trovato risalendo la catena delle superclassi. E' l'ereditarieta'
che funziona.
Cosa manca, detto subito
Categorie, protocolli, posing, caricamento dinamico, eccezioni,
conteggio dei riferimenti. E `objc_msgSend` e' scritto in C: passa
bene i primi due argomenti, mentre l'originale e' in assembly e li
passa tutti senza toccarli. Serve la versione in assembly prima di
arrivare a Foundation.
La strada davanti
Il difetto che bloccava tutto
La versione in C del passo 1 chiamava il metodo passandogli solo
`self` e `_cmd`. Qualunque metodo con argomenti veri - e in AppKit
sono la maggioranza - riceveva spazzatura.
La soluzione, la stessa dell'originale
`objc_msgSend` **non deve copiare gli argomenti: deve lasciarli dove
sono e saltare al metodo**. Il metodo trova la pila esattamente come
l'ha preparata il chiamante, e quando finisce torna direttamente a
lui.
Dal C non si puo' esprimere "salta a questa funzione lasciando
intatti gli argomenti". Per questo l'originale
(`src/objc-1/objc-msg-i386-nolock.s`) e' in assembly, e per questo lo
e' anche il nostro `objc/msgsend.asm`.
Il dettaglio che fa tutto e' l'ultima istruzione: **`jmp`, non
`call`**. La pila resta quella del chiamante.
Divisione dei compiti
Separare le due cose permette di scrivere in C la parte complicata e
in assembly solo la parte che il C non sa esprimere.
`objc_msgSendSuper`
Con `super` la ricerca non parte dalla classe del ricevente ma da
quella indicata, e il metodo deve ricevere **l'oggetto vero, non la
struttura**: il primo argomento sulla pila viene sostituito prima del
salto. E' cio' che fa funzionare `[super init]`.
La prova
Nel pannello Objective-C due righe nuove, con metodi che prendono
argomenti:
[p setX:30 y:12] somma 42
[p moltiplica:2 per:3 per:7] 42
Entrambe in verde. Il secondo caso ha **tre** argomenti: il terzo era
quello che con la versione in C si perdeva sempre.
Cosa manca ancora del runtime
Inoltro dei messaggi (`forward::`), categorie, protocolli, posing,
caricamento dinamico, eccezioni, conteggio dei riferimenti. Quando un
metodo non esiste si restituisce `nil` invece di invocare l'inoltro.
Lo strato su cui AppKit poggia direttamente. Tre classi, nell'ordine
in cui dipendono l'una dall'altra.
NSObject
Ciclo di vita, identita', interrogazione. I nomi dei metodi sono
quelli originali (`src/Kits/Foundation/NSObject.h`): `retain`,
`release`, `dealloc`, `retainCount`, `isEqual:`, `hash`,
`isKindOfClass:`, `isMemberOfClass:`, `respondsToSelector:`,
`description`. AppKit li chiama cosi', e cambiarli significherebbe
riscrivere AppKit.
**Conteggio dei riferimenti**, come nell'originale: ogni oggetto sa
da quanti e' trattenuto e si libera quando nessuno lo trattiene piu'.
Il contatore sta subito dopo `isa`, quindi tutte le sottoclassi lo
ereditano e `retain`/`release` funzionano su qualunque oggetto senza
che debbano occuparsene.
`isKindOfClass:` risponde vero anche per le superclassi,
`isMemberOfClass:` solo per la classe esatta: la differenza conta e
viene verificata.
NSString
Testo immutabile. **Nota onesta**: l'originale usa `unichar` a 16
bit, qui il testo e' a 8 bit. Il passaggio a Unicode e' rimandato
perche' AppKit funziona anche cosi', e la conversione va fatta una
volta sola quando servira' davvero.
NSArray
Elenco a capacita' crescente. L'originale distingue `NSArray` da
`NSMutableArray`; qui c'e' una classe sola con i metodi di entrambe.
**L'elenco trattiene cio' che contiene e lo rilascia quando viene
distrutto**: e' la regola di Foundation, e AppKit ci conta.
La prova
Nuovo pannello **Foundation** (tasto 9). Crea oggetti veri e verifica
ogni comportamento:
testo NATALIZIOOS
length 11
isEqualToString: si
isKindOfClass: NSObject si
count dopo due add 2
objectAtIndex:1 NATALIZIOOS
retainCount dopo l'add 2 <- l'array trattiene
dopo release dell'array 1 <- e rilascia
Oggetti creati 348
Ancora vivi 0 <- nessuna perdita
Tutte le righe in verde. Quella che conta di piu' e' l'ultima: 348
oggetti creati e tutti distrutti, nessuna perdita di memoria.
Dove siamo
Come richiesto: **entrambi, non uno al posto dell'altro.**
PostScript
Il modello ispirato a NeXT: si costruisce un tracciato (`PSmoveto`,
`PSlineto`, `PScurveto`), si imposta lo stato grafico (colore,
spessore, trasformazione) e poi si riempie o si contorna.
I nomi sono identici a quelli di `src/Kits/AppKit/psops.h`, cosi' il
codice di AppKit compila senza modifiche.
intersezioni col tracciato, si ordinano e si riempie fra coppie
consecutive. Gestisce anche le forme concave.
distinguerebbero, di meno si vedrebbero gli spigoli.
meccanismo su cui AppKit costruisce il disegno annidato delle
viste.
Diretto
Rettangoli e linee scritti subito nel framebuffer, senza tracciato
ne' trasformazioni. Non sa fare curve ne' rotazioni, ma dove serve un
rettangolo pieno costa una frazione.
Perche' entrambi
Il PostScript serve a far girare AppKit senza toccarlo; il diretto
serve dove la velocita' conta piu' della fedelta'. **Il terzo desktop
potra' scegliere per ogni elemento**, invece di subire un compromesso
unico.
Sotto, i due condividono lo stesso rasterizzatore e la stessa
superficie: cambiare motore non cambia dove si disegna.
Un difetto trovato disegnando
I colori uscivano invertiti: il blu diventava arancione. La
superficie di lavoro tiene il rosso nel byte basso, mentre `RGB()` lo
mette in alto. Lo scambio ora avviene **una volta sola dentro il
motore**, invece di lasciarlo da ricordare a ogni chiamata.
Verificato campionando i pixel: poligono PostScript e rettangolo
diretto danno entrambi `(0, 140, 216)`, il blu atteso.
Il controllo severo ha gia' pagato
`-Werror=implicit-function-declaration`, aggiunto nella 0.2.5.1, ha
fermato la compilazione su `RGB` non dichiarata in `gfx/render.c`.
Con gcc permissivo sarebbe passata, e sarebbe arrivata rotta.
Dove siamo
Le tre classi che reggono un'interfaccia.
L'idea che tiene insieme tutto
**La gerarchia delle viste.** Ogni vista ha coordinate relative al
genitore, e disegnandosi sposta l'origine per i figli. Cosi' una
vista **non deve sapere dove si trova sullo schermo**: disegna sempre
come se fosse a zero.
E' qui che serve `PSgsave`/`PSgrestore` del passo 4: la traslazione
si impila scendendo nell'albero e si toglie risalendo. Senza quella
coppia, il disegno annidato non sarebbe esprimibile.
NSView
`initWithFrame:`, `addSubview:`, `frame`, `drawRect:`, `display`.
`drawRect:` disegna la vista in coordinate proprie. `display` disegna
se stessa e poi scende nei figli, salvando e ripristinando lo stato
grafico a ogni livello.
NSWindow
`initWithContentRect:`, `setTitle:`, `setContentView:`,
`contentView`, `display`.
La finestra disegna la propria cornice e la barra del titolo, poi
lascia fare alla vista di contenuto - che non sa nulla della cornice.
NSApplication
`sharedApplication`, `addWindow:`, `windows`, `run`.
**Nota onesta**: nell'originale `run` e' il ciclo degli eventi e non
ritorna. Qui disegna tutte le finestre e ritorna, perche' il ciclo
degli eventi appartiene al desktop che ospitera' AppKit, non ad
AppKit stesso finche' non c'e' un sistema di eventi.
La prova
Nuovo pannello **AppKit** (tasto A). Costruisce una finestra vera con
viste annidate su tre livelli e la fa disegnare da `NSApplication`:
Viste create 180
drawRect: chiamate 4140
Profondita' massima 4
A schermo si vede la finestra con barra del titolo, vista di
contenuto, un pannello blu **con dentro** un bottone verde, e una
spia rossa accanto.
Il bottone verde e' figlio del pannello blu, che e' figlio del
contenuto: **tre traslazioni annidate**, e compare nel posto giusto.
E' la verifica che conta.
Cosa manca di AppKit
Eventi (clic, tastiera), `NSResponder` e la catena dei responsabili,
`NSControl` e `NSButton`, testo, ridisegno selettivo delle sole zone
sporche. Per ora ogni `display` ridisegna tutto.
Dove siamo
L'ultimo passo. Nuova voce **DESKTOP 3** nel menu di avvio.
Non imita AppKit: e' costruito su AppKit
Ogni elemento che si vede e' un `NSView`, ogni finestra e' una
`NSWindow`, e il disegno passa dal motore PostScript. Il codice non
disegna rettangoli: compone un albero di viste e chiede a
`NSApplication` di disegnarlo.
id menu = NSWindow_new(NSMakeRect(20, 20, 170, 200), "NATALIZIOOS");
id menu_v = NSView_new(NSMakeRect(0, 0, 170, 178), NX_PANNELLO);
NSView_setDrawer(menu_v, nx_pannello);
NSWindow_setContentView(menu, menu_v);
NSApp_addWindow(g_app, menu);
L'aspetto
Grigi ispirato a NeXT, bordi in rilievo ottenuti con due fili - uno chiaro
sopra e a sinistra, uno scuro sotto e a destra. Menu staccato in alto
a sinistra, non una barra in cima allo schermo: e' cosi' che
funzionava Rhapsody. Dock verticale a destra.
Tre finestre: il menu, un Workspace a tre colonne e un Inspector con
campi e pulsanti.
Un difetto trovato all'ultimo
Le finestre non comparivano: il motore era attaccato alla superficie
di Desktop 2, che qui non esiste. Con il doppio buffer attivo
`fb_get_address()` restituisce il buffer nascosto, ed e' li' che deve
finire il disegno. Attaccandolo altrove, AppKit componeva
correttamente in una memoria che nessuno mostrava.
Cosa manca
Il testo non passa ancora da AppKit - manca `NSFont` - quindi le
etichette si scrivono sopra. E non ci sono eventi: le finestre si
vedono ma non si cliccano.
bilancio onesto
I file di AppKit contengono solo `// TODO: Implement this method`:
Apple apri' il kernel e gli strumenti, non i framework applicativi.
Nei Kits ci sono **1.355 metodi** da implementare.
Dove il codice esisteva l'ho seguito; dove non esisteva ho
implementato le firme esatte delle intestazioni. Se un domani salta
fuori l'implementazione vera, entra al posto della nostra senza che
nulla intorno cambi.
Avevo guardato solo i Kits e concluso troppo in fretta. Censendo
**tutto** l'archivio, il quadro e' opposto: i segnaposto sono
l'eccezione.
Su 85 cartelle, solo i Kits sono vuoti. Tutto il resto - kernel,
driver, comandi, compilatore, librerie - e' codice vero.
`wc` portato, non riscritto
`ported/wc.c` viene da
La logica di conteggio e' quella dell'originale, riga per riga. Sono
cambiate due cose sole, e solo perche' qui non esistono:
open/read -> vfs_finddir/vfs_read
stdout -> la funzione di stampa del chiamante
**I commenti dell'autore originale sono rimasti**, compreso quello
che spiega perche' il conteggio delle righe e' separato da quello
delle parole:
*line counting is split out because it's a lot faster to get lines
than to get words, since the word count requires some logic.*
Senza quel commento sembrerebbe una duplicazione inutile.
Nel Desktop 3
Nuova finestra AppKit che mostra l'esito:
$ wc readme
righe 10
parole 44
caratteri 307
Numeri veri, calcolati dal codice originale sul filesystem di
NATALIZIOOS.
La strada che si apre
Con Commands, Libc e i driver tutti veri, c'e' molto da portare: gli
altri comandi di testo, le funzioni di libreria che ci mancano, i
driver per dispositivi che non gestiamo. `wc` e' il primo, e serve a
dimostrare che il percorso funziona.
Cosa puo' entrare e cosa no
Da `src/Libc-1/string.subproj` — sette funzioni
`strstr`, `strspn`, `strcspn`, `strpbrk`, `memchr`, `strcasecmp`,
`strsep`.
Il corpo di ciascuna e' quello dell'originale. Le modifiche sono tre,
tutte obbligate:
accetta piu' la vecchia
Da `src/Commands` — quattro comandi
`basename`, `dirname`, `rev`, `head`, `cksum`.
**I commenti degli autori sono rimasti.** In `basename` e `dirname` i
numeri fra parentesi sono i passi della specifica POSIX, come li
aveva annotati chi scrisse il codice:
*(3) If there are any trailing slash characters in string, they
shall be removed.*
Sono quelli che spiegano il perche' delle scelte. Riscriverli sarebbe
perdere il senso del port.
Una scelta dichiarata
`cksum` nell'originale usa una tabella di 256 voci precalcolate. Qui
il CRC si calcola al volo: 8 KB di tabella per una funzione usata di
rado non valgono lo spazio. E' l'unica divergenza logica, ed e'
segnata nel codice.
Nel Desktop 3
La finestra "Strumenti" mostra gli esiti veri:
$ wc readme
righe 10 parole 44 caratteri 307
$ basename /usr/local/bin/natalizioos
natalizioos
$ dirname /usr/local/bin/natalizioos
/usr/local/bin
$ cksum readme -> 3568399731
strstr/strspn/strcasecmp: S 6 =
Tutti calcolati dal codice di Berkeley sul filesystem di NATALIZIOOS.
Il problema, e perche' non si risolve portando il kernel
Il codice di NATALIZIOOS(2023) chiama Mach: `vm_allocate`, `task_self`,
`msg_send`. Il kernel Mach non puo' entrare in NATALIZIOOS, perche'
due kernel non convivono - portarlo significherebbe sostituire
NATALIZIOOS, non aggiungergli qualcosa.
**La soluzione non e' portare il kernel: e' portare la sua
interfaccia.**
`mach/mach_bridge.c` offre le chiamate Mach con i nomi e le firme
originali, e le serve con quello che NATALIZIOOS(2026) ha gia':
**NATALIZIOOS(2026) non e' stato modificato di una riga.** Questo e' uno
strato sopra, che traduce.
Un conflitto che il ponte ha dovuto risolvere
NATALIZIOOS(2026) ha gia' un `task_t`: e' la sua struttura di processo.
Mach ha un `task_t` che e' un numero di porta. Stesso nome, due
significati.
Il ponte usa `mach_task_port_t` internamente, cosi' i due vocabolari
convivono senza che nessuno dei due debba cambiare. E' esattamente il
lavoro che un ponte deve fare.
Due differenze dichiarate
**`vm_allocate`**: Mach alloca a pagine e puo' collocare la regione
dove vuole; qui la richiesta va all'heap, che lavora a byte. Per il
codice che chiama `vm_allocate` per avere memoria - cioe' quasi tutto
**`msg_receive`**: in Mach puo' attendere finche' un messaggio
arriva. Qui ritorna subito se la coda e' vuota, perche' senza
processi non ci sarebbe nessuno a riempirla mentre si aspetta:
attendere bloccherebbe il sistema per sempre.
La prova, nel Desktop 3
Ponte Mach (codice Mach, kernel nostro)
vm_allocate ok msg_send/receive ok
host_info -> 255 MB
Il messaggio inviato con `msg_send` viene riletto da `msg_receive`
con lo stesso identificativo. E `host_info`, interrogato in
linguaggio Mach, risponde con la memoria reale della macchina letta
dal nostro `pmm`.
Sul compilatore e i driver
`cc-1` richiede processi e memoria virtuale per processo: prima
servono quelli. I driver sono scritti per DriverKit, il modello a
oggetti ispirati a NeXT: ora che c'e' il runtime Objective-C, DriverKit
diventa costruibile - e' il passo che apre quella strada.
Perche' serviva
I 226.014 righe di `src/drivers-i386` sono scritte per DriverKit, il
modello a oggetti con cui NeXT scriveva i driver. Senza, quel codice
non ha su cosa poggiare. Ora che c'e' il runtime Objective-C,
DriverKit e' costruibile - ed e' la porta d'ingresso a quella parte
dell'archivio.
L'idea di DriverKit
Un driver e' un **oggetto** che discende da `IODevice`. Il sistema lo
interroga con `+probe` per sapere se il dispositivo c'e'; se
risponde di si', ne crea un'istanza e la registra.
Cosi' **aggiungere un driver non significa modificare il kernel:
significa aggiungere una classe.**
Gerarchia dall'originale (`src/driverkit-3/driverkit/`):
Object
IODevice base di tutti
IODirectDevice accesso diretto all'hardware
IOAudio, IONetwork, IODisk...
Metodi da `IODevice.h`: `+probe`, `+deviceStyle`, `-setName:`,
`-name`, `-setUnit:`, `-unit`.
Tre driver veri
Non sono finti: ciascuno interroga l'hardware attraverso i driver di
NATALIZIOOS e risponde di conseguenza.
Nel Desktop 3:
DriverKit: dispositivi rilevati
AuthenticAMD
ATA hd0
Memoria fisica
Il primo nome arriva dal driver CPU, che l'ha letto dal processore:
non e' scritto da nessuna parte nel codice di DriverKit.
Cosa apre
Ora i driver dell'archivio hanno la base su cui poggiano. Portarli
diventa un lavoro meccanico: sostituire le chiamate all'hardware di
NeXT con le nostre, lasciando la struttura a oggetti intatta.
Da `src/Commands/text_cmds` e `file_cmds`:
`cmp` confrontato con se stesso deve dare 0: e' la verifica che il
confronto funzioni davvero, non che si limiti a dire sempre "uguali".
**Commenti originali conservati.** In `uniq`, "If different, print;
set previous to new value" e' dell'autore e descrive esattamente il
cuore del ciclo.
Stato del port
Da `src/Libc-1/stdlib.subproj/`: `strtol`, `atoi`, `atol`, `abs`,
`labs`, `div`, `bsearch`, `qsort`.
Sono le funzioni che il resto dell'archivio usa di continuo: senza,
portare altri comandi significa riscriverle ogni volta.
`strtol` e il traboccamento
Il meccanismo di `cutoff`/`cutlim` e' dell'originale, ed e' il pezzo
piu' interessante del file: invece di moltiplicare e poi accorgersi
del traboccamento, si calcola **prima** il valore oltre il quale
moltiplicare traborderebbe.
E' il modo corretto di farlo quando non esiste un tipo piu' grande in
cui rifugiarsi. Il commento dell'autore lo spiega ed e' rimasto.
Una divergenza dichiarata
`qsort` nell'originale e' l'ordinamento di Bentley e McIlroy, con
gestione speciale degli elementi uguali e dei vettori quasi
ordinati. Qui c'e' lo stesso algoritmo - mediana di tre come perno,
partizione, ricorsione - senza quelle ottimizzazioni: contano su
vettori da milioni di elementi, e qui non ne esistono.
La mediana di tre invece l'ho tenuta, perche' serve a evitare il caso
peggiore su un vettore gia' ordinato, che capita spesso.
La prova
strtol/atoi/qsort/bsearch: 255 493 -42 S S
Le due basi dedotte sono la verifica che conta: dimostrano che la
logica dello zero iniziale funziona, non solo la conversione.
Stato
Da `src/Commands/text_cmds/`.
cut/tr/col/comm: 10 71 0 10
L'ultimo numero e' la verifica che conta: confrontando un file con se
stesso, tutte e dieci le righe devono risultare in comune e nessuna
esclusiva. Se `comm` sbagliasse il confronto, quel numero non
tornerebbe.
Due dettagli dagli originali
**`tr`** usa una tabella di 256 voci: si prepara una volta e poi la
sostituzione costa una lettura per carattere, invece di cercare ogni
volta dentro la stringa. E se `string2` e' piu' corta, l'ultimo
carattere si ripete - comportamento dell'originale, non una
semplificazione.
**`cut`** scorre carattere per carattere contando la colonna, come
nella funzione `c_cut` originale, invece di spezzare la riga in
campi: e' piu' semplice e non alloca nulla.
Una divergenza dichiarata
**`banner`** nell'originale ha una tabella col disegno di ogni
lettera. Qui c'e' un tratto semplice: quella tabella e' 4 KB per una
funzione di puro ornamento. Il disegno non e' quello dell'originale,
ed e' segnato nel codice.
Totale portato finora
Da `src/Libc-1/gen.subproj/fnmatch.c`, piu' `difftime` e le funzioni
di classificazione dei caratteri.
Perche' fnmatch conta
E' la corrispondenza con caratteri jolly: e' cosi' che la shell
capisce cosa significa `*.txt`. Serve a qualunque comando debba
selezionare file per modello - `ls` con argomenti, `find`,
l'espansione della riga di comando.
Otto prove, otto corrette
fnmatch (jolly, intervalli, negazione): 8/8
Le due righe con `[a-n]` e `[a-m]` sono la verifica che conta: la
stessa stringa deve dare esiti opposti, e la differenza sta in un
solo carattere dell'intervallo.
Il pezzo interessante dell'originale
Il caso di `*` non prova tutte le posizioni alla cieca: prima
accorpa gli asterischi consecutivi, poi tratta a parte il caso in cui
l'asterisco sia in fondo, dove la risposta e' immediata. Solo se
nessuna di queste scorciatoie vale, ricorre.
E' rimasta anche l'attribuzione originale nel commento di
`rangematch`, dove J.T. Conklin spiega perche' `^` viene trattato
come `!` nonostante la specifica lo lasci indefinito.
Una divergenza dichiarata
Le funzioni di classificazione (`isalnum`, `ispunct`, `isprint`,
`iscntrl`, `isxdigit`) nell'originale sono una tabella di 256 voci
coi bit delle classi. Qui sono confronti diretti: la tabella serve a
gestire le codifiche locali, che NATALIZIOOS non ha.
Totale portato
Da `src/Libc-1/string.subproj/`: `memcmp`, `memccpy`, `memmove`,
`strdup`, `strmode`, `strerror`.
memcmp/memmove/strdup: ok strmode 0644 -> -rw-r--r--
Le verifiche scelte apposta
**`memcmp` deve ordinare, non solo dire uguale o diverso.** Le tre
prove controllano che `"abc" < "abd"`, che `"abd" > "abc"` e che
`"abc" == "abc"`: e' la differenza fra un confronto usabile per
ordinare e uno che serve solo a sapere se due blocchi coincidono.
**`memmove` con aree sovrapposte.** E' il caso che distingue
`memmove` da `memcpy`: copiando in avanti si sovrascrive cio' che non
e' ancora stato letto. La prova sposta un blocco su se stesso di due
posizioni e verifica che il contenuto sia arrivato intatto.
**`strmode 0644`** deve dare esattamente `-rw-r--r--`: e' la forma
che si vede in `ls -l`, con le undici posizioni fisse - tipo, tre
terne di permessi, e lo spazio finale che l'originale lascia.
Una divergenza dichiarata
`strerror` nell'originale legge da una tabella di messaggi di
sistema. Qui la tabella e' ridotta agli errori che NATALIZIOOS puo'
davvero produrre: elencare errori che non capitano mai sarebbe solo
spazio sprecato.
Totale portato
Da `src/Libc-1/stdio.subproj/vfprintf.c`.
Cosa e' stato portato, e cosa no
L'originale e' **857 righe** e poggia sui flussi (`FILE *`), che qui
non esistono. Ho portato il **cuore**: l'analisi del formato e la
conversione, che sono la parte riusabile e il grosso della logica.
Scrive in un buffer invece che su un flusso.
Resta fuori: i numeri in virgola mobile (senza FPU nel kernel non
avrebbero senso), la larghezza presa da argomento con `*`, le
conversioni di tipo lungo lungo.
Il pezzo interessante: `__ultoa`
Il numero si costruisce **all'indietro**, partendo dalla fine del
buffer. Cosi' non serve sapere prima quante cifre verranno.
E per la base 10 l'originale fa una cosa che sembra strana finche'
non si legge il commento:
*On many machines, unsigned arithmetic is harder than signed
arithmetic, so we do at most one unsigned mod and divide; this is
sufficient to reduce the range of the value to LONG_MAX before
using signed arithmetic.*
Una sola divisione senza segno per scendere sotto `LONG_MAX`, poi
tutto il resto con segno. E' un'ottimizzazione che oggi conta meno,
ma il commento spiega perche' c'e', e l'ho tenuta.
Sei verifiche, sei corrette
snprintf: 6/6 DEADBEEF -17 ab |
La seconda e' quella che smaschera le versioni scritte in fretta:
con il riempimento a zeri il segno deve uscire **prima**, altrimenti
si ottiene `000-42`.
Totale portato
Segnalato: la finestra "Strumenti portati" sfora, e le ultime righe
finiscono sopra la barra in fondo allo schermo.
La causa
Ogni riga veniva stampata a una **coordinata fissa**, calcolata a
mano: `fb_puts(250, 470 + i * 18, ...)`. Con diciotto stampe sparse,
ogni lotto nuovo spostava tutto e prima o poi il contenuto doveva
uscire dalla finestra. Non era un caso: era inevitabile.
La correzione
Le righe si raccolgono ora in un elenco, e se ne disegnano solo
quelle che ci stanno, a partire dalla posizione di scorrimento.
d3_riga_add("$ wc readme", RGB(0x11,0x11,0x11));
d3_riga_num(" righe ", g_wc.linee, ...);
**Aggiungere un lotto non richiede piu' di ricalcolare nulla**: si
accoda e basta. E' la ragione vera per cui valeva la pena
rifarlo, non solo il difetto visibile.
Cosa si vede ora
Ventotto righe di contenuto in una finestra che ne mostra sedici:
scorrendo compaiono DriverKit e il ponte Mach.
Nota
Le righe si costruiscono con `nat_snprintf`, la stampa formattata
portata nella versione precedente. Il codice portato dall'archivio
ha iniziato a servire al sistema stesso, non solo alle prove.
Da src/drivers-i386/sound/drvBeepSound/Beep.drvproj/Beep.lksproj/Beep.m: 481 righe, zero segnaposto.
Perche' proprio questo
Usa la porta 0x61 dell'altoparlante, la stessa del nostro drivers/speaker.c. Il ponte fra i due mondi qui e' minimo: serve a dimostrare che il percorso funziona prima di affrontarne uno complicato.
Cosa e' stato mantenuto
Le cinque sequenze coi loro rapporti di frequenza, che sono la parte caratteristica del driver:
Plain 1 nota, rapporto 1:1 Blip 2 note, rapporto 3:4 quarta giusta discendente Up 8 note, rapporto 17:16 leggermente crescente Down 8 note, rapporto 15:16 leggermente calante Octave 2 note, rapporto 2:1 salto di ottava
Quei rapporti sono di NeXT, e i commenti che spiegano che intervallo musicale rappresentano sono rimasti: senza, sarebbero numeri arbitrari.
Mantenuti anche stringToStyle, i limiti 20 Hz - 20 kHz, e la struttura a classe DriverKit.
Cosa e' cambiato, e perche'
L'attesa fra le note. L'originale usa thread_set_timeout e thread_block, cioe' sospende il thread. Senza processi qui si attende sul timer di sistema.
Il pilotaggio della porta passa da drivers/speaker.c invece che da outb diretto: cosi' il driver rispetta l'interruttore del silenzio che il sistema ha gia'. E' una divergenza che migliora il comportamento, non un compromesso.
Nel Desktop 3
DriverKit rileva ora quattro dispositivi:
AuthenticAMD ATA hd0 Memoria fisica Beep (altoparlante) <- portato dall'archivio
E le cinque sequenze coi rapporti originali.
Cosa dimostra
I 226.014 righe di drivers-i386 non sono piu' teoricamente portabili: una l'abbiamo portata. Il metodo e' quello - mantenere la struttura a oggetti e le costanti, sostituire solo le chiamate all'hardware di NeXT con le nostre.
Da src/Kits/SoundKit/SoundMeter.m: 440 righe, zero segnaposto.
I Kits non sono tutti vuoti
AppKit e Foundation sono scheletri, ma SoundKit ha codice vero: 13 file su 178. E' l'unica parte dei Kits portabile, ed e' quella da cui si comincia.
Perche' si e' potuto portare adesso
SoundMeter e' una NSView, e NSView c'e' dal passo 5. Disegna con PSrectfill, e il motore PostScript c'e' dal passo 4.
Tutto cio' su cui poggia esisteva gia'. E' il momento in cui il lavoro fatto prima comincia a rendere: il primo file dei Kits si e' innestato senza dover costruire nulla di nuovo.
Mantenuto dall'originale
smoothValue — il valore sale subito ma scende con calma: la media pesata sugli ultimi tre campioni vale solo quando il livello CALA. Cosi' l'indicatore segue i picchi senza tremolare sulle pause. E' una riga sola, ma e' la differenza fra un indicatore usabile e uno che balla.
prepareValueForDisplay con la scala mu-law. L'orecchio percepisce il volume in modo logaritmico: un raddoppio di potenza non suona "doppio". La mu-law comprime la scala seguendo quella percezione. Nel pannello si vede il risultato:
scala mu-law: 0.25 -> 62%, 0.50 -> 78%, 1.00 -> 100%
Non lineare: meta' potenza da' il 78%, non il 50%. E' cosi' che l'orecchio sente.
Mantenuti anche PEAK_WIDTH = 3.0, i margini di 5 pixel e la struttura del disegno.
Cambiato
NSColor a valori diretti, perche' NSColor non e' ancora portatoNel Desktop 3
Nella finestra Inspector c'e' l'indicatore vero: barra verde del livello, cursore giallo del picco, e si muove. Verificato su due fotogrammi a tre secondi di distanza: 532 e 511 pixel accesi.
Priorita' Commands, come richiesto.
expr — la precedenza sta nella struttura
L'originale usa un analizzatore generato da yacc; qui c'e' la discesa ricorsiva scritta a mano, con la stessa grammatica:
espressione := termine { + | - termine }
termine := fattore { * | / | % fattore }
fattore := numero | ( espressione )
La precedenza non e' scritta da nessuna parte: nasce dal fatto che il termine si valuta piu' in basso nella discesa. E' lo stesso principio della grammatica originale.
Le verifiche lo dimostrano:
expr: 2+3*4=14 (2+3)*4=20 100%7--3=5
Il primo risultato sarebbe 20 se la precedenza non funzionasse. Il secondo mostra che le parentesi la scavalcano. Il terzo prova modulo e numeri negativi insieme.
look — ricerca binaria su file ordinato
E' il comando dietro al controllo ortografico: su un dizionario di centomila parole, la ricerca binaria costa circa diciassette confronti invece di centomila.
Struttura dell'originale conservata: prima la binaria per avvicinarsi, poi la lineare per trovare esattamente. La binaria da sola non basta perche' possono esserci piu' righe corrispondenti.
jot e du
jot 6 10 5: 10 15 20 25 30 35 du readme: 1 blocchi da 512
du arrotonda al blocco superiore, come l'originale.
Un intoppo
Le righe nuove non comparivano: erano state accodate dopo DriverKit, cioe' oltre la parte visibile senza scorrere. Spostate sopra. Il difetto era nella posizione, non nel codice - ma senza guardare lo schermo avrei detto che non funzionavano.
Totale
CommandsLibcDue pezzi portati che lavorano insieme
sort usa qsort, portato dal lotto stdlib. E' la prima volta che un comando dell'archivio chiama una funzione di libreria dell'archivio, come faceva nell'originale.
sort (con qsort portato): in ordine
La verifica non si fida dell'esito: riordina e poi ricontrolla riga per riga che ogni elemento sia minore o uguale al successivo. Se qsort sbagliasse, non basterebbe che il comando finisca senza errori.
sum — la rotazione che conta la posizione
E' la somma di controllo storica di BSD, piu' semplice di cksum e usata prima che esistesse.
Il punto interessante: prima di sommare ogni byte, si ruota il totale di un bit a destra. Senza quella rotazione "ab" e "ba" darebbero lo stesso risultato: e' cosi' che la posizione entra nel conto.
nl e paste
nl numera le righe non vuote, allineate a destra in sei colonne, come il comportamento predefinito dell'originale. paste affianca le righe di due file separandole con un carattere.
Di nl l'originale ha molte opzioni - quali righe numerare, il formato, la larghezza. Qui c'e' il comportamento senza argomenti: e' dichiarato nel codice.
Totale
CommandsLibcstrings: 8 yes: 3 true/false: 0/1 ok
strings
Estrae le sequenze di caratteri stampabili da un file. E' il modo piu' rapido di capire cosa fa un eseguibile senza disassemblarlo: i messaggi, i nomi dei file, le stringhe di formato sono tutte li'.
La lunghezza minima predefinita e' 4: sotto quella soglia uscirebbe troppo rumore, ed e' la scelta dell'originale.
od
Mostra il file in esadecimale, con l'offset a sette cifre davanti e i caratteri a fianco - il punto per quelli non stampabili. L'originale supporta molti formati; qui c'e' quello che si usa davvero per guardare dentro un file.
true e false
I due comandi piu' brevi di UNIX: non fanno nulla, uno riesce e l'altro fallisce. La verifica controlla che diano 0 e 1: se fossero uguali, gli script non potrebbero distinguerli, ed e' tutto cio' che serve loro.
tee e yes
tee prende il nome dal raccordo a T dell'idraulica, e fa la stessa cosa: il flusso prosegue e intanto una copia va altrove.
yes ripete una stringa - sembra inutile finche' non serve rispondere "y" a un comando che fa cento domande.
Copia di sicurezza
Da questa versione il pacchetto viene salvato negli output a ogni giro, non solo in /tmp: quella cartella si perde a ogni reset dell'ambiente, gli output no.
Totale
CommandsLibcgrep: 1 trovate, -v 9, jolly 0 (somma 10 = righe)
grep costruito su due pezzi gia' portati
L'originale usa il motore di espressioni regolari completo (src/Libc-1/regex.subproj, 3.207 righe). Qui ci sono due modi, e entrambi poggiano su codice gia' portato dall'archivio:
| Modo | Usa |
|---|---|
| testo semplice | nat_strstr, dal lotto stringhe |
| con jolly | nat_fnmatch, dal lotto gen |
Le espressioni regolari vere arriveranno quando il motore sara' portato: e' una divergenza dichiarata, non una svista.
La verifica scelta apposta
grep trova 1 riga, grep -v ne trova 9, e la somma fa esattamente le 10 righe del file. E' la prova che -v sia il complemento esatto: un conteggio sbagliato da una parte o dall'altra romperebbe la somma.
cat, split, echo
cat -n -s numera le righe e accorpa le vuote consecutive: sono le due opzioni dell'originale che cambiano davvero l'uscita.
echo -n non mette la nuova riga in fondo. E' l'unica opzione dell'originale, ed e' la differenza fra un prompt che resta sulla stessa riga e uno che va a capo.
split e' dichiarato incompleto: conta in quante parti andrebbe diviso il file e quanto e' grande l'ultima, ma non scrive xaa, xab, xac come l'originale, perche' il VFS non ha ancora la creazione di file.
Totale
CommandsLibcdate (dal driver RTC): Sab 19 Set 2026 11:27:30 test: 6 relazioni numeriche + 2 su stringa -> tutte corrette cal (mese corrente): Settembre 2026
Tre pezzi portati che collaborano
date e cal usano il driver rtc, portato nella v0.2.5.0. Non ricevono l'ora come argomento: la chiedono al sistema, come fanno nell'originale.
E cal usa rtc_weekday, che implementa Zeller: il lavoro vero di cal e' sapere in che giorno cade il primo del mese, perche' da li' tutto il resto e' conseguenza. L'originale ha la sua day_in_week; qui la funzione c'era gia' portata.
Il giorno della settimana nella data - Sab per il 19 settembre 2026 - e' la verifica che Zeller funzioni: un errore di un giorno sarebbe subito visibile.
test e lo zero che significa vero
E' il comando dietro alle condizioni della shell: [ 5 -gt 3 ].
Ritorna 0 se vero e 1 se falso, al contrario di quanto verrebbe naturale: negli script lo zero significa riuscita, e test deve parlare quella lingua.
Otto verifiche: le sei relazioni numeriche (-eq -ne -gt -ge -lt -le) e due su stringa (= e !=), ciascuna controllata in entrambi i versi - che 5 -gt 3 dia 0 e che 3 -gt 5 dia 1. Un confronto sempre vero passerebbe meta' delle prove.
Totale
CommandsLibcfmt -40: 10 righe, nessuna parola spezzata: si colrm 1-5: 40 colonne tolte unexpand: 0 tab pr -5: 2 pagine
fmt e la differenza con fold
Entrambi spezzano le righe lunghe, ma:
fold taglia dove capita, anche in mezzo a una parolafmt riempie fino alla larghezza rispettando le paroleLa verifica non si fida del conteggio: scorre il risultato carattere per carattere e controlla che nessuna riga superi le 40 colonne. Un fmt che spezzasse le parole produrrebbe lo stesso numero di righe, quindi contarle non basterebbe.
Mantenuti dall'originale anche la conservazione delle righe vuote come separatori di paragrafo.
colrm e unexpand
colrm e' il complemento di cut: cut tiene le colonne scelte, colrm le elimina.
unexpand e' il contrario di expand, ma con un limite preciso dell'originale: tocca solo gli spazi in testa alla riga, non quelli in mezzo. Cambiare anche quelli spezzerebbe l'allineamento delle tabelle.
pr usa date
pr prepara il file per la stampa con intestazione e numero di pagina. La data nell'intestazione arriva da nat_date, portato nel lotto precedente, che a sua volta legge dal driver rtc.
Tre pezzi portati in fila: pr chiama date, che chiama il driver.
Totale
CommandsLibcjoin: 10 unite column 3x20: 4 righe ul: 0 lam: 10
join e il perche' dei file ordinati
Unisce le righe di due file che hanno lo stesso primo campo: e' l'operazione che nei database si chiama giunzione.
L'originale pretende che i file siano ordinati, e non e' un capriccio: e' quello che permette di scorrerli una volta sola, avanzando nell'uno o nell'altro a seconda del confronto. Senza l'ordinamento servirebbe rileggere un file da capo per ogni riga dell'altro.
La verifica unisce il file con se stesso: ogni riga ha il primo campo uguale al proprio, quindi tutte e dieci devono unirsi. Se il confronto sbagliasse, il numero non tornerebbe.
ul e le telescriventi
Interpreta le sottolineature fatte con ritorno a capo e sovrascrittura: a\bA significa A sottolineata.
Nasce dalle telescriventi, dove si sottolineava tornando indietro e battendo l'underscore sopra il carattere. Quel \b e' proprio il ritorno del carrello. Sul file di prova ne trova zero, ed e' corretto: non ne contiene.
column e lam
column dispone le righe su piu' colonne riempiendo per righe: dieci righe in tre colonne danno quattro righe di uscita.
lam affianca due file senza separatori - e' la differenza con paste, che il separatore lo mette.
Totale
CommandsLibcls: 5 voci find *.txt: 3 file readme: testo ASCII
drw-r-xr-x 0 disk
drw-r-xr-x 0 docs
I primi comandi che lavorano sulle directory, non sul contenuto dei file. E usano due pezzi gia' portati.
ls -l usa strmode
I permessi nel formato drw-r-xr-x arrivano da strmode, portato nel lotto memoria. E' la stessa funzione che usa ls nell'originale, non una riscrittura: era gia' li' perche' serviva a questo.
find usa fnmatch
Il confronto *.txt passa da fnmatch, portato nel lotto gen. Anche qui, e' la funzione che usa find nell'originale.
L'originale scende ricorsivamente nell'albero e valuta un'espressione di condizioni; qui c'e' la ricerca per nome, che e' l'uso piu' frequente, con la discesa ricorsiva limitata a otto livelli.
file e l'ordine delle prove
Indovina il tipo guardando l'inizio del file. L'originale consulta /etc/magic, un elenco di firme; qui ci sono quelle essenziali - ELF, MZ, settore di avvio, Mach-O.
La regola di fondo e' dell'originale: prima le firme note, poi si guarda se il contenuto e' tutto stampabile, e solo alla fine si dichiara "dati". Invertire l'ordine farebbe passare per testo un eseguibile che comincia con caratteri leggibili.
pwd risale la catena
Il nodo sa chi e' il padre, non i figli del percorso: si risale fino alla radice raccogliendo i nomi, poi si scrive al contrario. E' il modo dell'originale.
Totale
CommandsLibcuname -a: NATALIZIOOS 0.2.5.28-DEV i386 AuthenticAMD 255 MB id: uid=0(root) gid=0(wheel) acceso da 0:00:47 env: 4 variabili
uname mette insieme tre strati
Una riga sola, e viene da tre posti diversi - tutti portati:
| Pezzo | Da dove |
|---|---|
| versione | il sistema |
AuthenticAMD | driver cpu (v0.2.5.0) |
255 MB | host_info del ponte Mach (v0.2.5.10) |
Nell'originale uname interroga il kernel Mach. Qui fa la stessa strada, con i nostri strati sotto.
env e la verifica che smaschera
L'ambiente regge scrittura e rilettura. La prova non si limita a scrivere e rileggere: scrive due volte la stessa variabile e controlla che la seconda sostituisca invece di aggiungere un doppione. Poi verifica che una variabile inesistente dia zero, perche' un getenv che restituisse sempre qualcosa passerebbe la prima meta' della prova.
Nota dichiarata: senza processi non c'e' un ambiente ereditato, quindi la tabella e' del sistema. L'interfaccia pero' e' quella giusta, e quando i processi ci saranno ogni processo avra' la sua.
Totale
CommandsLibcsed s/e/E/: 7 senza g, 13 con g tsort: libA libB libC libD (ciclo rilevato: si)
sed e il modificatore che cambia tutto
L'originale ha un linguaggio intero: indirizzi, cicli, buffer di appoggio, espressioni regolari. Qui c'e' il comando s, la sostituzione, che e' quello per cui sed si usa quasi sempre.
Con g sostituisce tutte le occorrenze della riga, senza solo la prima. La verifica lo misura: 7 senza, 13 con. Se i due numeri coincidessero, il modificatore non funzionerebbe - e un test che guardasse solo "ha sostituito qualcosa" non se ne accorgerebbe.
La ricerca usa nat_strstr, portato dal lotto stringhe. Le espressioni regolari arriveranno col motore.
tsort e il ciclo
Ordinamento topologico: date coppie "A viene prima di B", produce un ordine che le rispetta tutte. In UNIX serve a decidere l'ordine di collegamento delle librerie - se libA usa libB, libB va dopo.
L'algoritmo e' quello dell'originale: si prende ripetutamente un elemento senza predecessori, lo si emette, si tolgono i suoi archi.
Il caso che conta e' il ciclo. Se a un certo punto nessun elemento ha zero predecessori ma ne restano, il grafo ha un ciclo e un ordine valido non esiste. L'originale lo segnala invece di produrne uno falso, e la verifica prova entrambi i casi: un grafo sano che deve dare libA libB libC libD, e uno con X->Y->Z->X che deve essere riconosciuto come ciclico.
Totale
CommandsLibcdc: "3 4 + 2 *"=14 "2 10 ^"=1024 div/0 segnalata: si factor 360: 2 2 2 3 3 5 (6 fattori) primes 2-50: 15 trovati
dc e il perche' della notazione polacca inversa
Si scrivono prima gli operandi e poi l'operatore: 3 4 + fa 7.
Sembra scomodo, ma non serve nessuna regola di precedenza e nessuna parentesi: l'ordine e' gia' nella scrittura. E' il motivo per cui dc e' piu' vecchio di bc, che al confronto e' solo un traduttore verso dc.
Una pila e un ciclo: il programma piu' semplice che calcoli espressioni qualsiasi. La verifica prova anche la divisione per zero, che deve essere segnalata invece di produrre un numero a caso.
factor e il perche' ci si ferma alla radice
Il ciclo si arresta quando il divisore supera la radice, ed e' il punto dell'originale che vale la pena capire:
> se n non ha divisori fino alla sua radice, allora e' primo - > perche' se avesse un divisore piu' grande della radice, il > quoziente sarebbe piu' piccolo e l'avremmo gia' trovato.
factor 360 da' 2 2 2 3 3 5: e 2³ x 3² x 5 fa esattamente 360.
primes e il crivello
Invece di provare se ogni numero e' primo, si cancellano i multipli di quelli che si trovano.
Il crivello parte da d*d, non da 2*d: i multipli piu' piccoli sono gia' stati cancellati da divisori minori. E' la stessa ragione per cui factor si ferma alla radice - lo stesso principio, applicato al contrario.
Totale
CommandsLibcSegnalato: le scritture escono dalle finestre.
La causa, in numeri
L'area interna della finestra e' larga 424 pixel e il font e' 8 pixel: ci stanno 53 caratteri. Le righe erano lunghe fino a 72.
Ogni riga oltre i 53 caratteri sforava di 19, cioe' 152 pixel che finivano sopra il desktop.
Perche' non ho troncato
Troncare sarebbe stato piu' semplice, ma le righe finiscono quasi tutte con il risultato della verifica: nl: 8 righe numerate, div/0 segnalata: si, echo -n: 5. Tagliare la coda avrebbe fatto sparire proprio il numero che conta.
Come vanno a capo
Le righe troppo lunghe si spezzano su uno spazio, per non tagliare le parole a meta', e la continuazione rientra di due spazi cosi' si vede che appartiene alla riga sopra.
Se nei primi caratteri non c'e' nessuno spazio si taglia comunque: meglio una parola spezzata che una riga fuori dal bordo.
Verifica
Contati i pixel di testo oltre il bordo destro della finestra: zero. Prima erano centinaia.
L'elenco e' passato da 64 a 96 righe di capienza, perche' andando a capo il numero di righe cresce.
Correzione dopo la prima stesura: il README era stato riscritto da capo invece che aggiornato, perdendo le sezioni precedenti. Ripristinato dall'originale: restano "Quick Start", "Comandi utili" e tutta la cronologia di "Aggiornamenti recenti" fino alla v0.2.2.4. Le sezioni nuove sono state aggiunte, non sostituite.
BUILD_STATUS.md generato, non scritto
Nuovo scripts/stato_build.py e passo make stato.
Il problema dei documenti di stato e' che si scrivono una volta e poi nessuno li aggiorna: dopo tre versioni raccontano un sistema che non esiste piu'. Questo conta i file: righe di codice, funzioni portate, esito di make graphic e make text eseguiti sul momento.
Include anche cosa non e' portabile e perche' - Mach, il compilatore, i Kits vuoti - cosi' chi legge non se lo chiede.
README.md riscritto
Era fermo alla v0.2.2.5.2, trenta versioni fa: descriveva un sistema senza port, senza Desktop 3, senza driver nuovi.
Ora racconta i tre ambienti, con quello che e' entrato e quello che non puo' entrare, e le licenze - BSD per Libc e Commands, Apple Public Source License per runtime e DriverKit, con le note di copyright conservate nei file.
Quattro comandi
cmp -l: 0 differenze touch: esiste wc -L: 44 colonne su 10 righe comm -3 (solo comuni): 10 righe
cmp -l elenca ogni differenza coi due byte in ottale, non solo dove il confronto fallisce: e' la versione che serve a capire *cosa* e' cambiato.
comm a colonne restituisce le righe invece di contarle, con le tre opzioni -1 -2 -3 che si sopprimono a vicenda come nell'originale.
touch e' dichiarato incompleto: il VFS non conserva le date ne' permette la creazione, quindi riferisce solo se il file esiste. Il comando e' pronto per quando il VFS crescera'.
Totale
CommandsLibctr -d: 71 tolti, con -s 71 rs 4col: 2 righe xargs -n4: 3 blocchi apply: 3
xargs e apply, opposti
xargs raggruppa: prende un elenco e ne fa blocchi di n elementi. Nell'originale ogni blocco diventa una riga di comando, ed e' il motivo per cui esiste - le righe di comando hanno una lunghezza massima, e passare diecimila nomi di file in una volta sola non funziona.
apply separa: costruisce una riga per ogni elemento, sostituendo un segnaposto. E' il predecessore di xargs, e fa la cosa opposta.
Nove elementi con -n4 danno tre blocchi: 4+4+1.
Divergenza dichiarata: senza processi qui non si esegue nulla, si raggruppa e basta. Il raggruppamento pero' e' quello dell'originale, ed e' la parte che conta.
tr -d e -s insieme
Il tr del primo lotto sostituiva; questo cancella. Con -s accorpa anche le ripetizioni che restano: e' la pulizia tipica di un testo, togliere i caratteri indesiderati e comprimere gli spazi multipli.
rs
Rimodella un flusso di valori in righe e colonne - il nome sta per "reshape". Otto valori in quattro colonne danno due righe.
Totale
CommandsLibcultimo bit, cambi su 1000: rand 100%, random 49%
Portati entrambi da src/Libc-1/stdlib.subproj/, e la differenza fra loro e' il motivo per cui il secondo esiste.
rand e il suo difetto
Tre righe: una moltiplicazione, una somma, un resto.
Il problema e' noto e serio: i bit bassi hanno periodo cortissimo. L'ultimo si alterna 0,1,0,1; il penultimo ha periodo quattro.
Chi scrive rand() % 2 per tirare una moneta ottiene testa e croce alternate, mai due teste di fila. La misura lo mostra: cambia il 100% delle volte.
E' rimasto nella libreria per compatibilita', non perche' vada bene.
random e la retroazione additiva
Invece di un solo valore tiene uno stato di 31 numeri, e ogni volta somma due elementi distanti fra loro:
stato[f] += stato[r]; risultato = (stato[f] >> 1); /* chucking least random bit */
Quel >> 1 e' commentato cosi' dall'autore originale, e scarta proprio il bit che in rand aveva periodo due.
Il polinomio x^31 + x^3 + 1 da' un periodo di circa 2^69, contro i 2^31 di rand. Misurato: 49% di cambi, che e' quello che ci si aspetta da una sequenza casuale.
srandom scarta i primi 310 valori, come l'originale: serve a far dimenticare allo stato la regolarita' del riempimento iniziale.
Il controllo severo ha colto un altro errore
nat_random veniva usata da srandom prima di essere definita. Con gcc permissivo sarebbe passata come avviso; -Werror=implicit-function-declaration ha fermato la build.
E' la terza volta che quel controllo intercetta qualcosa che sarebbe arrivato rotto.
Totale
CommandsLibcgetopt "-a -bc -f nome resto": a b c f=nome optind fermo sul primo non-opzione: si
Perche' conta
E' la funzione che analizza le opzioni della riga di comando, e la ragione per cui tutti i comandi UNIX si comportano allo stesso modo: -a, -bc raggruppate, -f con argomento attaccato o staccato, e -- che chiude le opzioni.
Nei lotti precedenti ogni comando riceveva le opzioni come parametri separati, perche' getopt non c'era. Ora c'e', e i comandi possono analizzare una riga vera.
La prova sceglie il caso difficile
-bc raggruppate sono quello che distingue un getopt vero da un confronto carattere per carattere: devono diventare due opzioni, b e c, non una sola.
E optind deve fermarsi sul primo argomento che non e' un'opzione - resto, in posizione 5 - perche' e' da li' che il comando riprende a leggere i suoi argomenti veri.
Le variabili globali, coi nomi originali
optind, optarg, optopt, opterr, optreset: sono quelle che ogni comando UNIX si aspetta di trovare.
Unica modifica: i messaggi d'errore non vanno su stderr, che qui non esiste, ma in un buffer che il chiamante puo' leggere.
Totale
CommandsLibc%F %T -> 2026-09-20 17:12:35 %A %e %B %Y -> Domenica 20 Settembre 2026 %I:%M %p (%j) -> 05:12 PM (263)
La funzione dietro a ogni data leggibile in un sistema UNIX. I dati arrivano dal driver rtc portato nella v0.2.5.0.
Le verifiche scelte
Tre formati che controllano cose diverse:
%F %T — il formato ISO, cifre e separatori%A %e %B %Y — i nomi, e soprattutto il giorno della settimana, che viene calcolato con Zeller e sarebbe subito sbagliato di un giorno se la formula avesse un errore%I:%M %p (%j) — le ore in formato 12, dove mezzanotte e' 12 e non 0, e il giorno dell'annoLe 17:12 diventano 05:12 PM, e il 263esimo giorno del 2026 torna.
Cosa manca, e perche'
L'originale ha 33 specificatori. Qui ci sono quelli che hanno senso senza tabelle di localizzazione. Non gestiti:
| Motivo | |
|---|---|
%c %x %X | dipendono dalla localizzazione |
%Z %z | richiedono il fuso orario |
%U %W | numero della settimana, servono le regole ISO |
%s | secondi dall'epoca, serve una data di riferimento |
Quelli non gestiti vengono lasciati passare inalterati invece di produrre un valore sbagliato: chi legge l'uscita vede %Z e capisce che manca, invece di fidarsi di un fuso inventato.
Un errore colto dal controllo severo
desktop3.c usava rtc_time_t senza includere l'header del driver. Quarta volta che -Werror=implicit-function-declaration ferma qualcosa prima che arrivi a destinazione.
Totale
CommandsLibcdd bs=128 conv=ucase: 2+1 blocchi "NATALIZIOOS RAMFS - " cksum System V: 24523 (cksum CRC: 3568399731, sum BSD: 57676)
dd e il rapporto "n+m"
La sintassi strana - if=, of=, bs= invece delle opzioni con trattino - viene da JCL, il linguaggio di controllo dei mainframe IBM: dd fu scritto per leggere nastri che arrivavano da quelle macchine, e ne ereditò il modo di parlare.
Il rapporto finale ha un significato preciso: n blocchi letti interi, m parziali. Un file da 307 byte in blocchi da 128 ne da' due interi e uno parziale — ed e' quello che si legge.
conv=swab scambia i byte a coppie: serviva a leggere nastri scritti da macchine con l'ordine inverso. Oggi sembra esotico, ma e' la ragione per cui dd esiste.
Tre somme di controllo, tre epoche
Lo stesso file, tre algoritmi diversi:
| Comando | Risultato | Metodo |
|---|---|---|
sum (BSD) | 57676 | rotazione e somma a 16 bit |
cksum (POSIX) | 3568399731 | CRC a 32 bit |
cksum -o1 (System V) | 24523 | somma con ripiegamento del riporto |
Ognuno risolve lo stesso problema con i mezzi della sua epoca, e averli affiancati mostra l'evoluzione meglio di qualunque spiegazione.
hexdump -C
I sedici byte sono divisi in due gruppi da otto, e non e' estetica: rende possibile contarli a colpo d'occhio senza seguirli uno per uno.
Totale
CommandsLibctac 10 nl -ba 10 head -c20 20 tail -c20 20 grep -c 1 sort -r 10 sort -u 9 wc -w 44 tr -c: 267 sostituiti seq 1 3 10: 4 valori basename -s .txt: programma
Dodici in un colpo invece di tre o quattro: sono tutti brevi e si somigliano nella struttura - leggono, trasformano, scrivono - quindi ha senso portarli insieme.
| Comando | Cosa aggiunge |
|---|---|
tac | cat al contrario, dall'ultima riga |
nl -ba | numera tutte le righe, anche le vuote |
head -c / tail -c | i primi e gli ultimi byte, non righe |
grep -c / grep -l | conta soltanto, o dice solo il nome del file |
sort -r / sort -u | ordine inverso, e senza doppioni |
wc -w | solo le parole |
seq | come jot, con la sintassi GNU |
basename -s | toglie anche il suffisso |
tr -c | complemento: agisce sui caratteri non elencati |
Due numeri che raccontano qualcosa
sort -r 10 contro sort -u 9: c'e' una riga duplicata nel file, e -u la toglie. Se i due numeri fossero uguali, -u non starebbe facendo nulla.
tr -c con le sole maiuscole: 267 sostituzioni su 307 caratteri. Il complemento agisce su tutto il resto - minuscole, spazi, punteggiatura - ed e' la logica opposta a quella di tr normale.
Totale
CommandsLibcgrep -n 1 grep -i 1 wc -m 307 head -q 3 cut -f1 10 uniq -d / uniq -u find -type d/f ls -aR du -s df
| Comando | Cosa aggiunge |
|---|---|
grep -n | il numero di riga davanti |
grep -i | senza distinguere maiuscole |
wc -m | i caratteri |
head -q | senza intestazione col nome file |
cut -f | per campi delimitati, non per colonne fisse |
uniq -d | solo le righe duplicate |
uniq -u | solo quelle che non si ripetono |
find -type | filtra file o directory |
find -size | filtra per dimensione |
ls -aR | nascosti, e discesa ricorsiva |
du -s | il totale invece del dettaglio |
df | spazio del filesystem |
cut -c e cut -f, due modi diversi
-c taglia posizioni fisse, -f taglia campi delimitati. Su un file con colonne di larghezza diversa solo -f funziona, ed e' il motivo per cui esistono entrambi.
uniq -d e uniq -u sono complementari
Uno mostra solo cio' che si ripete, l'altro solo cio' che e' unico. Insieme coprono tutte le righe senza sovrapporsi - ed e' la verifica: la loro somma deve tornare.
grep -i verificato al contrario
La ricerca usa "natalizioos" tutto minuscolo contro un file che contiene NATALIZIOOS maiuscolo: se la conversione non funzionasse, il conteggio sarebbe zero invece di uno.
Totale
CommandsLibcsed -n 1p: NATALIZIOOS RAMFS - filesystem RAM (root /). sed 1d 9 sed 2,5p 4 awk $1 8 awk NF max 7 tail -3 3 grep -w file 0 contro grep file 1 wc totale 20 righe 614 byte rev NATALIZIOOS -> SOOIZILATAN printf \t\n: 4 byte
Verifica: 21 righe verdi, 0 rosse.
grep -w, la verifica piu' chiara del lotto
La parola file nel testo compare solo dentro filesystem.
grep file la trova: 1grep -w file no: 0E' esattamente la differenza che -w deve fare. Cercare la con grep normale trova anche parlare; con -w i bordi devono essere non alfanumerici, e filesystem non passa.
sed e awk, le espressioni che si usano davvero
Sono entrambi linguaggi interi. Qui ci sono le forme che coprono quasi tutto l'uso reale:
| Forma | Cosa fa |
|---|---|
sed -n Np | solo la riga N |
sed Nd | tutte tranne la N |
sed A,Bp | le righe fra A e B |
awk '{print $N}' | il campo N di ogni riga |
awk NF | quanti campi ha una riga |
awk separa sui gruppi di spazi, non su ogni spazio: due spazi consecutivi non producono un campo vuoto. E' il comportamento dell'originale, e cambia il risultato su qualunque testo allineato.
Gli altri
grep -x (la riga intera deve corrispondere), wc su piu' file col totale, expand -t con passo scelto, rev su stringa, e il printf della shell con le sequenze \t \n \r.
Totale
CommandsLibcUna correzione sui numeri
Le versioni precedenti dichiaravano 107 comandi. Il numero contava le opzioni come comandi separati: grep, grep -c, grep -n, grep -i, grep -w, grep -x risultavano sei.
I comandi distinti portati sono 77 con questo lotto.
Quanti ne restano
Nell'archivio ci sono 334 comandi con codice C. Ne restano 257, ma non sono tutti uguali:
| Categoria | Quanti | Cosa li blocca |
|---|---|---|
| Portabili ora | ~152 | niente |
| Rete (AppleTalk, TCP/IP) | 53 | manca lo stack di rete |
| Scrittura su disco | 27 | il VFS non scrive |
| Processi e demoni | 19 | mancano i processi |
| Utenti e password | 9 | manca un sistema di permessi |
| Shell complete | 6 | centinaia di migliaia di righe, e vogliono i processi |
La divisione e' approssimata: e' fatta per parole chiave nel nome, e qualcuno fra i "portabili" si rivelera' bloccato aprendolo.
Il dato che conta: i ~114 bloccati non si sbloccano portando comandi, ma aggiungendo al sistema scrittura nel VFS e processi. Con quelle due cose ne entrerebbero una cinquantina di colpo.
I sei comandi
arch: i386 domainname: natalizioos.local checknr: bilanciato 0 errori, lasciato aperto 1
checknr controlla che i cambi di carattere nroff siano bilanciati: ogni \fB deve tornare a \fR. Nei manuali, dimenticarne uno trasformava in grassetto tutto il resto. Verificato nei due versi: zero errori sul testo corretto, uno su quello lasciato aperto.
asa interpreta i caratteri di controllo carrello di FORTRAN - il primo carattere di ogni riga diceva alla stampante a catena cosa fare prima di stamparla. colcrt toglie le sovrapposizioni che la stampante usava per sottolineare. calendar mostra le righe di un file di promemoria con la data di oggi, letta dal driver rtc.
Detto chiaramente: asa, colcrt e calendar sono compilati ma non ancora verificati a schermo. arch, domainname e checknr si'.
Totale
Commands (su 334)LibcUn blocco che non esisteva
Nella v0.2.5.42 ventisette comandi erano classificati come "bloccati: il VFS non scrive". Era sbagliato. vfs_write, vfs_create e vfs_unlink esistevano gia' nel ramfs - non erano stati verificati prima di dichiarare il blocco.
Otto comandi che modificano il filesystem
mkdir, touch (ora completo: crea il file), cp, rm, rmdir, mv, chmod, chown, ln.
SCRITTURA: voci 6 -> 9 dopo mkdir/touch/cp -> 6 dopo rm cp 307 byte cp su se stesso: rifiutato rmdir non vuota: rifiutato mv: 307 byte, sorgente sparita: si chmod: ok
La verifica guarda lo stato del filesystem, non si fida dei valori di ritorno: il numero di voci deve salire di tre e tornare esattamente al punto di partenza.
Comportamenti dell'originale mantenuti
cp rifiuta di copiare un file su se stesso: aprirebbe la destinazione troncandola, e a quel punto la sorgente sarebbe gia' vuotarm senza -r non tocca le directoryrmdir rifiuta le directory non vuotemv cancella la sorgente solo se la copia e' riuscita, per non perdere il file a meta'Un errore nella prova, trovato dalla prova
La prima versione verificava il rifiuto di rmdir usando la directory di sistema disk, data per piena. Era vuota - e' un punto di montaggio - e rmdir l'ha cancellata correttamente. Il conteggio finale e' sceso a 5 invece di tornare a 6, e la riga e' uscita rossa.
Il codice di scrittura era giusto; era sbagliata la prova. Ora si crea da sola la sua directory piena. La regola: mai usare directory di sistema come materiale di prova.
Divergenze dichiarate
mv fa copia piu' cancellazione: il ramfs non ha il renameln fa una copia: il ramfs non ha i collegamenti, quindi modificando uno dei due l'altro non cambiaStato del conteggio
| Comandi distinti portati | 85 su 334 |
| Categoria "scrittura su disco" | sbloccata - era un falso blocco |
| Ancora bloccati davvero | rete, processi, utenti, shell |
UTENTI: root gruppi: wheel users registrati: 2 id: uid=0(root) gid=0(wheel) groups=0(wheel),100(users) sudo permesso: si su root: ok passwd con vecchia errata: rifiutata
Un altro blocco che non esisteva
Nove comandi erano classificati come "bloccati: manca un sistema di permessi". Falso. kernel/users.c esisteva gia', con:
uid e flag di amministratoreusers_login, users_add, users_remove, users_passwdpassword_validate per i requisiti minimisudoers_check per i permessi elevaticurrent_user, l'utente connessoE' il secondo falso blocco dopo la scrittura nel VFS. In entrambi i casi avevo dichiarato mancante qualcosa senza aprire il file - e in entrambi i casi il codice c'era gia'.
Otto comandi
whoami, id (ora coi dati veri), groups, users, passwd, su, logname, sudo.
Una sola via per l'autenticazione. su e passwd verificano passando da users_login, la stessa funzione della schermata di accesso: se ce ne fossero due, prima o poi divergerebbero e una delle due sarebbe piu' debole.
Le verifiche scelte
passwd con la vecchia password sbagliata deve fallire con -1, non accettare in silenzio. E' il controllo che distingue un passwd vero da uno che cambia e bastasu verso se stessi da root passa senza password, come nell'originale: root non deve dimostrare nullaid mostra i gruppi multipli solo per gli amministratoriDivergenza dichiarata
sudo risponde se il permesso ci sarebbe, ma non esegue nulla: senza processi non c'e' cosa eseguire.
Stato dei blocchi
| Blocco | Stato |
|---|---|
| Scrittura su disco | falso, sbloccato nella 0.2.5.43 |
| Utenti e permessi | falso, sbloccato qui |
| Processi (19 + compilatore + shell) | reale |
| Rete (53) | reale |
Comandi distinti portati: 93 su 334.
ESECUZIONE: file su testo -> non e' un ELF which: /readme nice 99 limitato a 19 printenv PATH: /bin:/usr/bin
Terzo falso blocco: i processi
Diciannove comandi erano classificati come "bloccati: mancano i processi". kernel/elf.c sa gia' caricare ed eseguire binari ELF - elf_load, elf_execute, elf_unload - e la shell li usa gia' in commands.c.
Terzo caso su tre in cui avevo dichiarato mancante qualcosa senza aprire il file. Non e' una svista isolata: e' un modo di procedere sbagliato, e da qui in avanti verifico prima di classificare.
Ma qui il blocco esiste davvero, ed e' piu' preciso
Non manca l'esecuzione: manca la concorrenza.
Un programma eseguito qui gira fino alla fine e restituisce un valore. Non c'e' un secondo programma che gira insieme, ne' un identificativo di processo da elencare o terminare.
Quindi la divisione vera e':
| Comandi | Stato |
|---|---|
time, which, nice, nohup, file su ELF | entrano: serve eseguire, e si puo' |
ps, kill, top, renice | restano fuori: servono processi concorrenti |
E' una distinzione che prima non avevo, e che vale piu' di un conteggio: dice esattamente cosa costruire per sbloccare il resto.
Sei comandi
time, which, file sull'ELF, nice, printenv con nome, basename -a.
file sull'ELF legge l'intestazione e dice per quale macchina e' fatto il binario. Su un sistema a 32 bit, provare a eseguire un ELF a 64 bit e' l'errore piu' frequente: meglio accorgersene prima.
Divergenze dichiarate
time riporta solo il tempo reale. L'originale ne da' tre - reale, utente, sistema - ma senza contabilita' separata fra modo utente e kernel gli altri due sarebbero inventatinice registra la priorita' e la limita fra -20 e 19, ma senza concorrenza non ha effetto sull'esecuzionewhich cerca nella radice: il VFS non ha ancora /binStato dei blocchi
| Blocco | Verdetto |
|---|---|
| Scrittura su disco | falso |
| Utenti e permessi | falso |
| Esecuzione di programmi | falso |
| Concorrenza (ps, kill, top) | reale |
| Rete (53 comandi) | reale |
Comandi distinti portati: 99 su 334.
PID STATO NOME
0 in corso main
2 pronto prova_due
1 pronto prova_uno
kill: sistema rifiutato, task vero ok, due volte rifiutato
top: 3 task totali, 2 pronti, 1 in corso, 0 finiti
Quarto falso blocco
Li avevo dichiarati impossibili per mancanza di concorrenza. kernel/task.c ha gia' una tabella di task con identificativo, nome, stato e lista concatenata, piu' task_current e task_list_head per scorrerla.
Quattro volte su quattro ho classificato come mancante qualcosa che c'era: scrittura nel VFS, utenti, esecuzione di programmi, concorrenza. La regola che ne esce: aprire il file prima di dichiarare il blocco.
Quello che manca davvero e' la preemption
I task cedono il controllo con task_yield, non vengono interrotti dal timer. Per questi quattro comandi non cambia nulla - elencare, terminare e misurare funzionano uguale - ma cambia per la priorita': senza preemption renice non influenza l'ordine di esecuzione.
Le regole dell'originale mantenute
kill rifiuta il task 0: e' quello di sistema, terminarlo fermerebbe la macchinakill rifiuta un task gia' finito, invece di dire che e' andato benerenice limita fra -20 e 19La verifica prova kill in tutti e tre i casi: sul task di sistema, su uno vero, e sullo stesso due volte. Un kill che dicesse sempre "ok" passerebbe solo il secondo.
Un difetto trovato dalla prova
ps e top non mostravano nulla: nel contesto del Desktop 3 la lista dei task risultava vuota, anche se task_init viene chiamato nella sequenza di avvio. Ora si inizializza se serve, invece di dare per scontato che sia pronta.
Divergenze dichiarate
top non mostra la percentuale di processore per task: senza preemption un task che non cede il controllo risulterebbe al 100%, e sarebbe un numero senza sensorenice tiene la priorita' in una tabella affiancata, perche' task_t non ha quel campo e aggiungerlo significherebbe modificare il nucleo. Quando lo scheduler diventera' a priorita', il valore sara' gia' li'Comandi distinti portati: 103 su 334.
RETE: natalizioos.natalizioos.local dispositivi PCI: 6 ifconfig -a: eth0: 8086:100e Intel 82540EM (e1000) lo0: 127.0.0.1 anello locale ping 127.0.0.1: 64 byte da 127.0.0.1: tempo=0.0 ms ping 8.8.8.8: irraggiungibile (manca lo stack TCP/IP)
Questa volta il blocco c'e' davvero
Dopo quattro falsi blocchi ho guardato prima di dichiarare. Qui non c'e' ne' uno stack TCP/IP ne' un driver Ethernet: i comandi che scambiano pacchetti - telnet, ftp, rlogin - restano fuori e non si possono fingere.
Ma due cose ci sono
drivers/restapi.c parla gia' un protocollo su serialeBastano per i comandi che descrivono la rete invece di usarla.
Cinque comandi
ifconfig -a, lspci, hostname -f, ping, netstat -i.
ifconfig legge davvero dal bus: 8086:100e e' l'identificativo della scheda Intel e1000 emulata da QEMU, non un valore scritto a mano. Cambiando scheda nella riga di comando di QEMU, cambia qui.
ping distingue i due casi: verso 127.0.0.1 risponde, perche' l'anello locale non lascia mai la macchina e la risposta e' per definizione immediata - ed e' quello che fa anche l'originale, che sull'anello locale funziona pure senza schede. Verso qualunque altro indirizzo fallisce e dice perche', invece di inventare un tempo di risposta.
La riga che conta di piu'
ping 8.8.8.8: irraggiungibile (manca lo stack TCP/IP)
Sarebbe stato facile far rispondere tutto. Un ping che risponde sempre e' peggio di uno che manca: nasconde il problema invece di segnalarlo.
Cosa servirebbe per il resto
Un driver per la e1000 - che il PCI gia' vede - e uno stack TCP/IP. Con quelli entrerebbero i 48 comandi di rete rimasti.
Comandi distinti portati: 108 su 334.
Questo pacchetto non e' completo. Viene consegnato solo per non perdere il lavoro: tutto stava in /tmp, che si azzera a ogni reset dell'ambiente.
Cosa funziona
| Kernel 1 | 1151 settori, intatto, arriva al login |
| Kernel 2 | ~323 settori, Desktop 3 a 1024x768 |
| Boot selector | premi 3 -> carica dal settore 2048 |
| Driver e1000 | trasmette (provato con cattura pacchetti) |
| Spazio libero | 828 settori = 414 KB |
Il muro che non c'e' piu'
Il kernel 1 era a 1151 settori su 1151 utilizzabili: la BIOS lo carica in modo reale fra 0x10000 e 0xA0000, e li' finisce lo spazio. Non poteva crescere di un byte, e qualunque aggiunta rompeva l'avvio senza un messaggio.
La soluzione e' un secondo kernel caricato dal bootloader da un altro settore, con dentro solo cio' che serve al Desktop 3. Nasce mezzo vuoto, e la rete ha finalmente dove stare.
settore 9 kernel 1 shell + Desktop 1 + Desktop 2 settore 2048 kernel 2 Desktop 3 + rete
Quattro difetti veri trovati e corretti
pci_write_config_dword non esisteva nel driver PCI. Senza, la scheda non puo' leggere la memoria: consumava i descrittori e sul cavo non usciva nullavolatile - li scrive la scheda in DMA, e il compilatore teneva lo stato in un registroRDLEN tornava 384 invece di 512Il terzo l'ho trovato solo rileggendo ogni registro dopo averlo scritto. E' il metodo giusto per questo codice, ed e' annotato nei commenti.
Cosa NON funziona
La ricezione. RDH resta a zero: la scheda non scrive nell'anello, anche con configurazione valida, bus mastering attivo, filtri disattivati e modalita' promiscua.
Provato e scartato, ognuno verificato: volatile, bit di validita' del filtro, tabella multicast, ordine RCTL/RDT in entrambi i versi, ripristino con CTRL.RST, anelli da 8 e da 32 descrittori, riscrittura completa della sequenza.
Il punto da cui ripartire: scriviamo RCTL = 0x0400801A e rileggendo sembra tornare 0x0400601A. Va verificato in DECIMALE, perche' a schermo i caratteri esadecimali si possono confondere. Se i due numeri sono davvero diversi, la scheda interpreta la scrittura in modo inatteso - ed e' li' la risposta.
Cosa manca per i 48 comandi
Ricezione funzionante, poi ARP completo, IPv4, ICMP (ping vero), UDP e TCP. Sono migliaia di righe, e vanno scritte con calma: lo spazio adesso c'e'.
Lavoro in corso. La ricezione e' ancora ferma, ma tutto cio' che le sta sopra e' costruito e provato con risposte vere dall'altro capo.
Il cambio di strategia che ha sbloccato tutto
La ricezione era bloccata e insistevo su quella. Ma la costruzione di un pacchetto si verifica senza riceverne indietro: se il nostro e' ben formato, l'altro capo risponde - e la cattura del traffico lo mostra.
Nessuno risponde a un pacchetto con l'intestazione sbagliata o il checksum errato. Quindi la risposta e' una prova.
Cosa e' stato costruito e verificato
5: USCITA IPv4 42B ICMP richiesta eco 6: ENTRATA IPv4 60B ICMP RISPOSTA ECO 9: USCITA IPv4 71B UDP 40000->53 DNS 12: ENTRATA IPv4 103B UDP 53->40000 DNS 10: USCITA TCP 45000->80 SYN 11: ENTRATA TCP 80->45000 ACK+RST
| Livello | Verifica |
|---|---|
| IPv4 | il gateway risponde al ping: intestazione e checksum corretti |
| ICMP | richiesta di eco accettata, risposta ricevuta |
| UDP | il risolutore DNS risponde con 103 byte |
| TCP | il SYN riceve RST+ACK: "capito, porta chiusa" - prova che il segmento e' valido |
Tre cose imparate, scritte nei commenti
Il checksum di Internet ha una proprieta' comoda: sommando anche il checksum stesso, un pacchetto integro da' sempre zero. Il ricevente non ricalcola e confronta - somma tutto e guarda se viene zero.
UDP e TCP sommano anche gli indirizzi IP, che non stanno nel loro pacchetto. Serve a scoprire un pacchetto consegnato al destinatario sbagliato. Quei campi formano una pseudo-intestazione che esiste solo per il calcolo e non viene mai spedita.
La lunghezza dell'intestazione TCP si misura in parole da 32 bit, non in byte, e sta nei quattro bit alti: venti byte si scrivono 0x50. Metterci 20 e' l'errore classico, e il pacchetto viene scartato senza un messaggio.
La ricezione: diagnosi chiusa
I byte grezzi del primo descrittore, dopo che le risposte sono arrivate sul cavo:
desc0 grezzo: 0 0 0 0 0 0 0 0
Tutti zeri. Non e' un campo letto male: la scheda non scrive proprio. Con:
RDLEN=512 accettato, RDT=31, RCTL esatto al bitDieci ipotesi provate, nessuna ha cambiato il comportamento. Il prossimo passo e' leggere il sorgente della e1000 dell'emulatore e vedere quale controllo fallisce, invece di indovinarlo.
Stato
| Kernel 1 | 1151 settori, intatto |
| Kernel 2 | 328 settori, Desktop 3 a 1024x768 |
| Spazio libero | 823 settori = 411 KB |
| Passi fatti | 3, 4, 5 su 6 |
| Passo aperto | 1 (ricezione), e con esso il 2 |
ported/rete_rx.c: dato un fotogramma grezzo, dice se e' ARP, eco ICMP, UDP, TCP o da scartare. I controlli vengono prima delle letture: una trama troncata e' il caso normale di un attacco. Autoprova 8/8, con casi negativi (trama corta, checksum rotto, tipo ignoto).
ported/rete_comandi.c: ping, arping, nslookup, traceroute, netstat -s, nc, in sola trasmissione - dicevano "risposte non ancora leggibili" invece di inventarle. Interprete DNS con la compressione dei nomi (0xC0): provato su una risposta vera.
ported/rete_comandi2.c: tabella delle rotte, vince il prefisso piu' lungo e la predefinita /0 perde sempre. route, telnet, ftp, whois, ntpdate, tftp (in sola trasmissione). Autoprova 4/4.
ported/rete_inet.c: inet_aton accetta le forme abbreviate BSD ("127.1" = 127.0.0.1), rifiuta 256 e i caratteri in coda. ipcalc: rete, broadcast, maschera, ospiti (/31 e /32 ne hanno zero). Autoprova 9/9.
ported/rete_libc.c: ether_aton/ether_ntoa, getservbyname/ getservbyport (22 servizi), gethostbyname/gethostbyaddr (file locale prima del DNS), htons/htonl. Autoprova 12/12: l'unico errore trovato era nell'indice della prova, non nella funzione.
ported/rete_tcp_stati.c: gli undici stati di RFC 793 e le transizioni guidate dai fanioni. RST ha la precedenza su tutto; TIME_WAIT dura 60 s. Autoprova 14/14, compreso cio' che NON deve succedere (un ACK in SYN_SENT non apre la connessione). netstat -a.
ported/rete_socket.c: descrittori di socket (il descrittore 0 e' valido), connect, send, close. I numeri di sequenza TCP ripartono da zero dopo 2^32: si confronta la differenza con segno, non i valori. Un send TCP senza handshake completato e' rifiutato (-3), non finto. Autoprova 15/15.
*Nota: questa voce e le sei seguenti sono state scritte in v0.2.5.59. Il lavoro dalla 0.2.5.52 alla 0.2.5.58 stava in /tmp e si e' perso con un azzeramento dell'ambiente; i file sono stati riscritti identici e le autoprove ripassate tutte.*
Lavoro in corso verso i 48 comandi di rete, ma il muro e' caduto: la scheda e1000 riceve, e sopra la ricezione ora c'e' una pila vera.
RICEZIONE: dopo 1100 ms, prima trama 64 B: risposta ARP ARP 10.0.2.2 -> 52:55:0a:00:02:02 (1 giri) $ ping -c 3 10.0.2.2 64 byte da 10.0.2.2: icmp_seq=0 ttl=255 tempo=0.457 ms 64 byte da 10.0.2.2: icmp_seq=1 ttl=255 tempo=0.085 ms 64 byte da 10.0.2.2: icmp_seq=2 ttl=255 tempo=0.075 ms 3 trasmessi, 3 ricevuti, 0% persi $ nslookup example.com Nome: example.com Indirizzo: 172.66.147.243 $ traceroute -m 3 10.0.2.2 1 10.0.2.2 0.658 ms 2 127.0.0.1 0.310 ms $ nc -z 10.0.2.2 80 10.0.2.2:80 - rifiutata: RST in 0.461 ms (porta chiusa) $ dhclient eth0 DHCP: offerta 10.0.2.15 da 10.0.2.2 DHCP: confermato 10.0.2.15/255.255.255.0 router 10.0.2.2 DHCP: DNS 10.0.2.3 durata 86400 s
La causa del silenzio: un timer di un secondo
Dieci ipotesi sui registri, tutte scartate. La risposta era nel sorgente dell'emulatore (hw/net/e1000.c di QEMU 8.2), letto invece che indovinato:
set_rx_control(): timer_mod(s->flush_queue_timer, adesso + 1000 ms); e1000_can_receive(): return ... && !timer_pending(s->flush_queue_timer);
Ogni scrittura di RCTL arma un timer di un secondo, e finche' corre la scheda dice "non posso ricevere": i pacchetti restano in coda nell'emulatore e arrivano tutti insieme allo scadere. Noi leggevamo entro pochi millisecondi dall'abilitazione, e l'elenco del Desktop 3 si costruisce una volta sola: guardavamo sempre dentro quel secondo. I registri erano giusti da tre versioni.
Ora e1000_attendi_rx() aspetta che la finestra si chiuda, con un tetto: se il tempo non scorresse, esce invece di bloccare.
Il timer era fermo: le interruzioni spente
Aspettare un secondo richiede un orologio che cammini. Il kernel 2 non accendeva mai le interruzioni: il kernel 1 lo fa subito dopo timer_init, il kernel 2 no. Si accendevano per caso alla prima creazione di un task (task_trampoline esegue sti). Ora sti e' esplicito, dopo fb_init, perche' il gestore del mouse disegna.
La pila: rete_pila.c
Ogni trama passa da rete_elabora(), che la riconosce con rx_interpreta() (provato 8/8 in v0.2.5.52) e la smista:
| Arriva | Cosa succede |
|---|---|
| ARP | la tabella impara (regola "merge" di RFC 826); se chiedono di noi, rispondiamo |
| ICMP eco | rispondiamo: NATALIZIOOS ora si puo' "pingare" |
| ICMP risposta | annotata con l'ora di partenza scritta nei dati, come fa ping.c |
| ICMP errore | irraggiungibile / TTL scaduto, per traceroute |
| UDP | in coda sulla porta in ascolto |
| TCP | alla connessione giusta: SYN, SYN+ACK, ACK, dati, FIN, RST |
La macchina a stati TCP e' la stessa tcp_transizione() provata 14/14 in v0.2.5.57, e l'aritmetica di sequenza e' quella provata 15/15 in v0.2.5.58.
ARP vero, e il MAC provvisorio va in pensione
ip_mac_per() sceglie il prossimo salto con la tabella delle rotte (prefisso piu' lungo), poi chiede ad ARP. UDP, TCP e ICMP la usano tutti: se ARP non risponde il pacchetto non parte (-4), invece di partire verso un indirizzo inventato. ip_mac_provvisorio() resta, solo per costruire il pacchetto di prova dell'interprete.
Comandi con le risposte (rete_comandi3.c)
ping, arping, nslookup, traceroute, nc -z, una GET HTTP, dhclient e netstat -s fanno il loro lavoro per intero. Il ping misura il tempo come l'originale: l'ora di partenza sta nei 56 byte di dati, l'altro capo la rimanda, all'arrivo si fa la differenza. traceroute usa sonde UDP verso porte alte a TTL crescente, come traceroute.tproj.
Due difetti che nessuno vedeva
Il Desktop 3 aveva 196 righe su un massimo di 96: le ultime 100 - fra cui DriverKit, SoundMeter, Beep, Mach e gli utenti - venivano scartate senza avviso. Nel kernel 2 il limite ora e' 400 (il kernel 1 resta a 96) e l'elenco si copia sulla seriale, per verificarlo parola per parola invece che con l'OCR.
Gli utenti del kernel 2 erano vuoti: nessuno chiamava users_init(). Non si vedeva perche' quelle righe erano oltre la 96esima.
Il file system scriveva sopra il kernel 1
Scoperto controllando dove si scrive sul disco:
| Area | Prima | Dentro il kernel 1? | Ora |
|---|---|---|---|
| N.A.T. FS | settore 100 | si' | 8192 |
| sudoers | settore 98 | si' | 4095 |
| patch (Makefile) | settore 200 | si' | 4200 |
Il kernel 1 sta sul disco dal settore 9 e occupa oltre 1050 settori. Al primo avvio natfs_init() trovava codice al posto del superblocco, formattava, e scriveva sopra il kernel. Verificato: dopo un avvio l'immagine differiva da kernel.bin proprio al byte (100-9)*512. Quando il file system fu scritto il kernel era piccolo; e' il kernel che gli e' cresciuto addosso. Ora, dopo un avvio del kernel 1 e uno del kernel 2 sulla stessa immagine, entrambi i kernel sul disco sono identici byte per byte ai file compilati.
I file salvati nel vecchio N.A.T. FS non si recuperano: stavano sopra il kernel e venivano cancellati a ogni ricompilazione.
Altro
0040:006C)make ora mette nell'immagine anche il kernel 2: prima serviva make -f Makefile.k2 a parte, e senza, il tasto 3 caricava settori vuoti. make graphic comincia con clean, che cancellava anche il kernel 2: ora viene ricompilato in codagcc -m32, che sulla macchina di sviluppo non c'e'): provato con entrambi, 340 settori con clang e 382 con gccrete.h: dichiarazioni comuni dei moduli di reteI 48 comandi, contati per nome
Il 53 delle versioni passate era la categoria "rete" del conteggio dell'archivio. Contati uno per uno: in network_cmds e at_cmds ci sono 51 comandi di rete con codice C - 42 TCP/IP, 7 AppleTalk, bpwhoami, tn3270 - piu' lspci e hostname -f fuori da quelle cartelle. Tolti i 5 descrittivi della v0.2.5.47, restano esattamente 48:
TCP/IP arp domainname ftp logger makedbm newclient nfsstat nslookup
rcp rdist revnetgroup rlogin route rpcinfo rsh rstat ruptime
rwho slattach sliplogin spray startslip stdethers stdhosts talk
tcpdump telnet tftp traceroute trpt trsp wall ypcat ypmatch
yppoll yppush ypset ypwhich ypxfr
AppleTalk appleping appletalk at_cho_prn atlookup atprint atstatus aurpd
altri bpwhoami tn3270
Con risposte vere da questa versione: arp, nslookup, traceroute. route ha la tabella ma non ancora i sottocomandi.
Stato
| Kernel 1 | 1060 settori (clang), parte, login verificato |
| Kernel 2 | 382 settori su 952 utilizzabili (a 3000 inizia il backup) |
| Ricezione | funziona |
| Comandi di rete | 3 su 48 completi, piu' ping/netstat/dhclient |
Nel Desktop 3 del kernel 2, in fondo alla finestra degli strumenti, c'e' una riga di comando. Si scrive, Invio esegue, l'uscita si accoda all'elenco:
rete> ping -c 2 gateway
64 byte da 10.0.2.2: icmp_seq=0 ttl=255 tempo=0.210 ms
64 byte da 10.0.2.2: icmp_seq=1 ttl=255 tempo=0.080 ms
rete> route add 192.168.5.0/24 10.0.2.2
add net 192.168.5.0/24: gateway 10.0.2.2
rete> route get 192.168.5.7
route to: 192.168.5.7
gateway: 10.0.2.2
rete> tcpdump -c 4 icmp
41.380633 10.0.2.15 > 10.0.2.2: icmp: echo request
41.380655 10.0.2.2 > 10.0.2.15: icmp: echo reply
44.117833 10.0.2.2 > 10.0.2.15: icmp: time exceeded in-transit
44.118028 127.0.0.1 > 10.0.2.15: icmp: 10.0.2.2 udp port 33436 unreachable
aiuto elenca i comandi. I nomi si risolvono come fa gethostbyname(): numerici, poi la tabella locale (localhost, gateway, dns, natalizioos), poi il DNS.
telnet: una sessione vera
telnet host porta apre una sessione: le righe scritte vanno all'altro capo, cio' che arriva si stampa in blu. Le opzioni si negoziano come vuole RFC 854 - si accetta che sia il server a fare l'eco e a sopprimere il "go ahead", si rifiuta il resto. Provato contro un server che chiede ECHO, SGA e TERMINAL-TYPE:
dal server: IAC WILL ECHO, IAC WILL SGA, IAC DO TTYPE risposta: IAC DO ECHO, IAC DO SGA, IAC WONT TTYPE
e poi una conversazione intera, fino alla chiusura dall'altro capo: SYN, SYN+ACK, ACK, dati nei due versi, FIN, ACK, FIN. ESC chiude la sessione senza uscire dal desktop.
tcpdump
Porta il formato delle righe di print-arp.c, print-icmp.c, print-udp.c, print-tcp.c e print-domain.c dell'archivio. Le trame vengono da un registratore sempre acceso nella pila (le ultime 48, in entrata e in uscita, 320 byte ciascuna): mostra cio' che e' gia' successo. Filtro per protocollo: arp, icmp, udp, tcp.
Gli altri comandi di questo lotto
| Comando | Cosa fa |
|---|---|
arp -a / -d / -s | tabella nel formato dell'originale, ? (ip) at mac |
route add/delete/get | sulla tabella a prefisso piu' lungo |
netstat -a/-s/-r/-i | connessioni TCP vere, porte UDP in ascolto, contatori |
ifconfig | flag, indirizzo, MAC, contatori della scheda |
logger -p fac.liv -t tag | registro locale + datagramma syslog UDP 514 |
domainname | legge e imposta il dominio NIS |
wall | intestazione dell'originale, a schermo e sulla seriale |
nc, http, dhclient | sessione TCP grezza, GET, DHCP |
La finestra si allarga
A 52 colonne le righe di tcpdump si spezzavano in due o finivano sotto la barra di scorrimento. Nel kernel 2 la finestra degli strumenti e' larga 720 pixel (84 colonne); nel kernel 1 resta com'era.
I 48: a che punto siamo
Completi: arp, domainname, logger, nslookup, route, tcpdump, telnet, traceroute, wall - 9 su 48. Parziali: ftp, tftp. Il conto esatto sta in BUILD_STATUS.md, letto dai marcatori COMANDO_RETE: nei sorgenti.
rete> tftp gateway get grande.bin Received 3000 bytes in 0.001215 seconds -> grande.bin rete> sum grande.bin 25221 3 grande.bin <- identico al file originale rete> ftp gateway 2121 220 Server FTP di prova pronto 230 Login successful. ftp> get dati.bin 227 Entering Passive Mode (127,0,0,1,215,153). 150 Opening BINARY mode data connection for dati.bin (10000 bytes). 226 Transfer complete. 10000 bytes received in 0.000389 seconds rete> rcp gateway:leggimi.txt copia.txt gateway:leggimi.txt -> copia.txt (44 byte) rete> rsh gateway -l ospite whoami ospite
I file ricevuti si scrivono davvero nel file system (VFS) e si spediscono leggendoli da li'. Ogni trasferimento e' stato verificato con sum sui due lati: stesse somme, byte per byte.
tftp (tftp.tproj, RFC 1350)
get e put. Il server risponde da una porta nuova (la "TID") e da li' si parla con quella; un pacchetto da un'altra porta riceve l'errore 5, come vuole il protocollo. Blocchi da 512, l'ultimo corto; ritrasmissione dopo 1 s, 5 tentativi; errori del server stampati col loro codice (Error code 1: File not found).
make run-gui avvia ora QEMU con il server TFTP interno, che serve la cartella tftpboot/ del progetto: tftp gateway get benvenuto.txt funziona subito. Il flag si aggiunge solo se QEMU ha la rete "user".
ftp (ftp.tproj, RFC 959)
Sessione con il prompt ftp>: ls, dir, get, put, cd, cdup, pwd, ascii, binary, system, status, user, quote, quit. Accesso anonimo come ftp -a. Dati in modo passivo; se il server indica un indirizzo che da qui non ha senso (127.x dietro la traduzione dell'emulatore) si usa quello della connessione di controllo, come fanno i client moderni.
rsh, rcp, rlogin (i comandi "r" di Berkeley)
La fiducia di questi protocolli sta nella porta privilegiata: il server crede al nome utente se la connessione viene da sotto la 1024. Le porte si prendono dalla 1023 in giu', come rresvport(). rcp e' rsh che lancia rcp -f o rcp -t sull'altra macchina, poi le righe C0644 lunghezza nome e gli zeri di conferma. rlogin apre una sessione come telnet.
TCP: ritrasmissione, finestra, traccia
Per trasferire file non bastava piu' il TCP "da conversazione":
trpt (trpt.tproj)
La pila tiene una traccia degli ultimi 40 passi TCP, e trpt la stampa nel formato dell'originale:
818 SYN_SENT:output c739dd2e@0(win=2000)<SYN> -> SYN_SENT 818 SYN_SENT:input 19d1601@c739dd2f(win=ffff)<SYN,ACK> -> ESTABLISHED 818 ESTABLISHED:output [c739dd2f..c739dd47)@19d1602(win=2000)<ACK,PUSH> -> ESTABLISHED 839 ESTABLISHED:input 19d164d@c739dd53(win=ffff)<ACK,FIN> -> CLOSE_WAIT
Anche nel terminale
ls, cat, sum: per vedere e verificare cio' che si e' scaricato.
I 48
Completi: arp, domainname, ftp, logger, nslookup, rcp, rlogin, route, rsh, tcpdump, telnet, tftp, traceroute, trpt, wall - 15 su 48.
Come e' stato provato
Contro server veri sulla macchina che ospita l'emulatore: il server TFTP interno di QEMU, e server FTP, rshd e rlogind minimi scritti per la prova. Sulla tua macchina ftp, rsh, rlogin risponderanno "Connection refused" finche' non ci gira un server: e' la risposta vera, non un difetto.
rete> rpcinfo -p
program vers proto port
100000 2 udp 111 portmapper
100001 3 udp 1002 rstatd
100012 1 udp 1003 sprayd
100026 1 udp 1004 bootparam
100004 2 udp 1005 ypserv
100007 2 udp 1006 ypbind
rete> ypcat hosts
127.0.0.1 localhost
10.0.2.15 natalizioos
10.0.2.2 gateway
10.0.2.3 dns
rete> ypmatch gateway dns hosts
10.0.2.2 gateway
10.0.2.3 dns
rete> yppoll hosts
Map hosts.byname has order number 1790435514.
The master server is natalizioos.
rete> bpwhoami
host_name: natalizioos
domain_name: natalizioos
router: 10.0.2.2
Sun RPC (rete_rpc.c)
Mezza rete Unix degli anni '80 e '90 - NFS, NIS, rstat, spray, bootparam - parla RPC di Sun (RFC 1057) con argomenti in XDR (RFC 1014). Il client ritrasmette ogni secondo come clntudp_call, riconosce la "porta irraggiungibile" e la riporta come clnt_perror. Le strutture seguono campo per campo le definizioni dell'archivio, Librpcsvc-1/rstat.x, spray.x, bootparam_prot.x, yp.x.
NATALIZIOOS e' anche server: portmapper (con SET, UNSET, GETPORT, DUMP e CALLIT, che inoltra le chiamate in broadcast), rstatd, sprayd, bootparamd, ypserv, ypbind. Rispondono a questa macchina e a chiunque sulla rete.
lo0: la macchina parla con se' stessa
Un pacchetto per 127.x o per il nostro indirizzo non esce dalla scheda: entra in una coda e rientra dallo smistamento, come if_loop.c nel kernel BSD. ping localhost, rpcinfo -p localhost, ypcat verso il ypserv locale. E, come un sistema vero, a un datagramma per una porta dove nessuno ascolta si risponde "porta irraggiungibile".
NIS, le "pagine gialle"
Dominio natalizioos, servito da questa macchina. Le mappe si costruiscono dai file di testo con la catena del Makefile di /var/yp, fatta dagli strumenti di questo stesso lotto: stdhosts e stdethers normalizzano /etc/hosts e /etc/ethers, revnetgroup inverte /etc/netgroup (seguendo i gruppi annidati), makedbm scrive le mappe. I file in /etc si creano al primo avvio della rete, solo se mancano.
Client: ypwhich (-m, -x), ypcat (-k, soprannomi come hosts = hosts.byname), ypmatch, yppoll, ypset, ypxfr (confronta i numeri d'ordine, -f forza), yppush (legge ypservers).
Un dettaglio trovato nell'archivio: yp.x dichiara la risposta come *stato, chiave, valore*, ma ypserv_xdr.c - e ogni server Sun - mette prima il valore e poi la chiave. FreeBSD lo chiama "STUPID_SUN_BUG". Vale cio' che viaggia sul filo, e cosi' e' fatto qui.
Un difetto trovato provando
ypset andava in "Timed out" dopo un centinaio di chiamate riuscite. Le porte dei client RPC salivano fino alla 1020 e arrivavano alla 1002 di rstatd: la risposta finiva al servizio invece che al client. Ora i client usano 700-899, lontano dai servizi.
Anche
rstat (ora, tempo acceso e carico nel formato dell'originale), spray (raffica senza risposta, poi il conteggio del server), rpcinfo -u e -b (broadcast via CALLIT), bpwhoami, nfsstat (contatori RPC veri; NFS a zero, perche' nulla e' montato via NFS).
I 48
31 su 48 completi. Restano: newclient, rdist, ruptime, rwho, slattach, sliplogin, startslip, talk, trsp, tn3270 e i 7 di AppleTalk.
rete> talk mario@gateway [Checking for invitation on caller's machine] [Connection established] ciao root, sono mario talk> ciao mario, qui NATALIZIOOS mario ha letto: ciao mario, qui NATALIZIOOS rete> ruptime natalizioos up 0:01, 1 user, load 0.00, 0.00, 0.00 rete> rwho root natalizioos:console Sep 26 15:26 rete> rdist -c benvenuto.txt gateway:/tmp/copia.txt updating host gateway installing: benvenuto.txt rete> newclient pippo pluto Installing /private/tftpboot/mach Creating /clients/pippo...[OK] Customizing /clients/pippo/etc/fstab...[OK]
talk e talkd (talk.tproj, talkd.tproj, protocols/talkd.h)
Il protocollo "ntalk" di 4.3BSD in due tempi: i demoni si scambiano messaggi di controllo da 84 byte (LOOK_UP, LEAVE_INVITE, ANNOUNCE, DELETE, con gli indirizzi nel vecchio osockaddr), poi chi e' invitato si collega in TCP e ciascuno manda i suoi tre tasti di modifica. Provato in tutti i casi: invito trovato e conversazione, "Your party is not logged on", annuncio e attesa con ritiro dell'invito (DELETE) quando si rinuncia.
NATALIZIOOS ha il suo talkd: un annuncio arriva sul terminale del Desktop 3 con le parole di announce.c - "Message from Talk_Daemon@natalizioos ... talk: respond with: talk root@...".
rwho, ruptime, rwhod (protocols/rwhod.h)
Ogni tre minuti la macchina grida in broadcast sulla porta 513 chi e', da quando e' accesa e chi e' collegato; rwhod raccoglie le grida degli altri in /var/rwho/whod.<macchina>, e rwho e ruptime leggono quei file nel formato dell'originale. Una macchina che tace da 11 minuti e' "down".
rdist (rdist.tproj)
rdist -c file ... host:destinazione lancia rdist -Server sull'altra macchina con rsh e le parla nel protocollo versione 3: V3, t bersaglio, Q domanda, R file, conferme a zero. Il secondo invio dello stesso file risponde "gia' aggiornato", come l'originale che confronta lunghezza e data.
newclient (newclient.tproj)
Lo script csh di NeXT (1988) che prepara un client NetBoot, rifatto: la radice in /clients/<nome>, fstab con SERVER e CLIENT sostituiti, file di swap, e in /private/tftpboot il kernel e il caricatore veri - il kernel 2 letto dal disco dal settore 2048 e lo stage2 dai settori 1-8. Verificato con sum: identici ai file dell'immagine. Il VFS non ha collegamenti simbolici: dove l'originale ne fa uno, si scrive il percorso a cui punterebbe, e lo si dice.
TCP dalla parte del server
Una porta in ascolto accetta un SYN, risponde SYN+ACK (ritrasmesso se serve) e diventa ESTABLISHED col terzo ACK; tcp_accetta() consegna la connessione. Serve a talk, e servira' ai demoni.
La rete lavora sempre
Il Desktop 3 chiama la pila a ogni giro, anche quando nessuno scrive: e' cosi' che la macchina risponde a ping, RPC, talk e rwho degli altri, e che gli annunci arrivano da soli sul terminale.
Un limite messo in chiaro
Il bss del kernel 2 era arrivato a 3,3 MB, e l'heap comincia a 4 MB. Cinque buffer da 64 KB per i trasferimenti sono diventati uno solo (un trasferimento alla volta), e all'avvio il kernel 2 controlla il margine e si ferma con un messaggio se il bss arrivasse all'heap. Oggi: 958 KB di margine.
I 48
36 su 48. Restano: slattach, startslip, sliplogin, trsp, tn3270 e i 7 di AppleTalk.
rete> startslip -s ATDT5551234 tty01 natalizio segreto
Sent login: natalizio
Sent password: ********
sl0 in linea: configura con "ifconfig sl0 locale remoto"
rete> ifconfig sl0 10.1.0.1 10.1.0.2
sl0: flags=8011<UP,POINTOPOINT,MULTICAST> mtu 296
inet 10.1.0.1 --> 10.1.0.2 netmask 0xffffff00
rete> ping -c 2 10.1.0.2
64 byte da 10.1.0.2: icmp_seq=0 ttl=64 tempo=42.705 ms
rete> tcpdump -c 4 -i sl0
O 89.232984 10.1.0.1 > 10.1.0.2: icmp: echo request
I 89.276828 10.1.0.2 > 10.1.0.1: icmp: echo reply
rete> sliplogin Snatalizioos
starting slip login for Snatalizioos
/etc/slip.login: sl0 10.1.0.1 --> 10.1.0.2 netmask 0xffffff00
SLIP (RFC 1055) e l'interfaccia sl0 (ported/rete_slip.c)
IP su una linea seriale, come ci si collegava con un modem: i pacchetti IP cosi' come sono, separati dal byte END (0xC0), con ESC (0xDB) davanti ai byte che somigliano ai separatori. La linea e' la seconda porta seriale (COM2, 0x2F8, tty01 per BSD); la prima resta la console di diagnostica. Dal kernel dell'archivio (if_sl.c): mtu 296 (SLMTU), pacchetti in arrivo fino a 1900 byte (SLMAX), e link1 che non lascia passare ICMP sulla linea (SC_NOICMP).
tty01 [baud]: la linea in modo SLIP, velocita' della tabella dell'originale; tty00 e' rifiutata (e' la console).-d -b -s -p -A -F), la stringa di chiamata al modem, poi il dialogo col terminal server letto con la stessa getline(): una riga finisce a capo o ai due punti, e si confronta dal secondo carattere (ogin:, assword:, con -A il prompt dell'Annex e sername:). Con -d si vede tutto il dialogo, come nell'originale; la password resta coperta.[nome]: cerca il nome in /etc/slip.hosts e esegue lo script /etc/slip.login con i parametri dell'originale (unita', velocita', nome, indirizzi, maschera, opzioni): la riga /sbin/ifconfig sl$1 inet $4 $5 netmask $6 configura davvero sl0. I due file, se mancano, nascono da quelli della distribuzione (slip.hosts con una voce per questa macchina). Nel registro di sistema: "attaching slip unit 0 for ...", "SLIP access denied for ...".[inet] locale remoto [netmask m] [up|down] [link1|-link1]; la maschera anche esadecimale (0xffffff00), come in slip.hosts. ifconfig senza argomenti mostra anche sl0.sl_if_print() dell'originale (-i eth0, -i lo0 anche).La pila sceglie l'uscita dalla tabella delle rotte: verso l'altro capo di sl0 il pacchetto esce dalla linea con l'indirizzo di sl0 come mittente (come in_pcbconnect()), e TCP taglia i segmenti a 256 byte di dati (mtu 296 - 40). I pacchetti che arrivano dalla linea entrano nello smistamento di sempre: ping, UDP, TCP, servizi, tcpdump.
Provato con un altro capo vero su COM2 (tools/slip_peer.py: modem Hayes, terminal server, poi SLIP): ping 3/3 e i ping dell'altro capo verso di noi 3/3 con dentro 0xC0 e 0xDB (le sostituzioni nei due sensi), traceroute a un salto, nc 10.1.0.2 13 (TCP daytime), rcp /etc/slip.login 10.1.0.2:/tmp/sl spezzato in segmenti da 256 + 70 byte e sum uguale ai due capi (11047 1). Nel Desktop 3 una riga nuova prova lo stesso codice senza la linea: SLIP (RFC 1055): 9/9.
make run-slip collega la COM2 a tools/slip_peer.py 5555.
Differenze dichiarate: niente compressione delle intestazioni TCP (Van Jacobson, link0/link2: i pacchetti compressi in arrivo si scartano); niente frammentazione IP; startslip fa un tentativo solo, con 10 secondi per ogni domanda del server, invece di sei tentativi con attese che raddoppiano (un'ora): qui e' un comando del terminale, non un demone.
TCP: due correzioni trovate provando SLIP
tcp_invia() rifiutava tutto oltre 500 byte. Ogni invio piu' lungo di 500 byte si fermava al primo segmento: ftp put, rcp e rdist verso un server funzionavano solo con file piccoli (quelli provati finora erano da 77 byte). Ora il limite e' 546: put dati.bin da 10000 byte arriva identico (cmp sull'ospite).tcp_libera() cancellava la connessione subito dopo il nostro FIN, e l'ACK finale dell'altro capo trovava una connessione sparita: partiva un RST. Ora una connessione chiusa dall'applicazione resta "orfana" finche' la chiusura non finisce, come il PCB di BSD dopo close(). In tcpdump la chiusura e' pulita: FIN, FIN, ACK.nc/telnet dicevano "nessuna connessione libera" anche quando il SYN non poteva partire; ora "connect: No route to host". ping distingue "sendto: No route to host (sl0 senza linea)".Compilazione
Makefile.k2 scrive il kernel 2 nell'immagine sempre, non solo quando lo ricollega: un'immagine appena rifatta da make con il kernel 2 gia' compilato restava senza kernel 2 (settori vuoti al tasto 3).Comandi di rete completi: 39 su 48. Restano trsp, tn3270 e i sette di AppleTalk.
rete> tn3270 10.0.2.2 3270 [3270: IBM-3278-2 negoziato - BINARY ed EOR nei due sensi] +--------------------------------------------------------------------------------+ | NATALIZIO MVS/ESA - SISTEMA DI PROVA PER TN3270 | | USERID ===> ________ | | PASSWORD ===> | | IKJ56700A ENTER USERID - | +--------------------------------------------------------------------------------+ 4B cursore 10,17 | = TAB :pf1-24 :pa1-3 :clear ESC esce tn3270> ibmuser|sys1 | IKJ56455I IBMUSER LOGON IN PROGRESS AT 18:27:57 ON SEPTEMBER 26, 2026 | | READY | tn3270> listcat | IN CATALOG:CATALOG.MASTER | | IBMUSER.NATALIZ.DATI |
tn3270 (ported/rete_tn3270.c, da network_cmds/tn3270)
Un mainframe IBM non manda righe di testo ma schermate intere, con campi protetti e campi da riempire; il terminale le disegna, si scrive nei campi e si preme un tasto di attenzione, e all'host torna solo cio' che e' cambiato. Dall'archivio (Berkeley, 1988-93), portati con la loro logica:
ctlr/hostctlr.h e ctlr/screen.h: ordini, attributi, comandi, codici AID, le macro dello schermo (campi, indirizzi a 12 bit con la tabella CIA, il bit MDT dei campi modificati);ctlr/outbound.c: DataFromNetwork(), il flusso dati dall'host - Write, Erase/Write, Erase All Unprotected, Read Modified, Read Buffer; gli ordini SF, SBA, IC, PT, RA, EUA, GE;ctlr/inbound.c: OneCharacter() (con il salto automatico dei campi "autoskip" e il blocco dei campi numerici), Tab(), DoReadModified() con SendField(), DoReadBuffer();api/ebc_disp.c, api/asc_ebc.c: le tabelle EBCDIC vere;telnet.tproj/tn3270.c: il tipo di terminale IBM-3278-2, poi BINARY ed EOR nei due sensi; i record finiscono con IAC EOR.La schermata 24x80 si disegna nel terminale di rete, dentro una cornice, con la riga di stato del 3278 (4B, X SYSTEM quando la tastiera e' bloccata in attesa dell'host, la posizione del cursore). I campi da riempire si vedono come ____, quelli "non display" (la password) restano vuoti. La tastiera, al posto di map3270: la riga scritta va nel campo del cursore, | e' TAB, Invio e' ENTER, :pf1 ... :pf24, :pa1 ... :pa3, :clear, :tab, :home. Se l'host non parla 3270 si resta in telnet semplice, come l'originale.
Provato contro un piccolo mainframe (tools/mainframe_3270.py: logon IBMUSER/SYS1, poi TSO con TIME, LISTCAT, LOGOFF): negoziazione, password sbagliata e giusta, tre comandi, uscita. Il mainframe riceve i record decodificati come li manderebbe un 3278 vero - AID, cursore, SBA e campo per ogni campo modificato. Un dettaglio vero: scrivere nove lettere in un campo di otto fa saltare il cursore al campo dopo (e, all'ultimo campo, di nuovo al primo), come su un 3278.
Nel Desktop 3 una riga nuova prova il controllore senza rete: 3270 (tn3270): 8/8 - Erase/Write, SF, SBA, IC, RA, la tastiera e la risposta Read Modified byte per byte.
Differenze dichiarate: solo il modello 2 (24x80); gli ordini SFE, SA e MF, che l'originale rifiuta, qui si accettano tenendo solo l'attributo base del campo (niente colori); gli indirizzi a 14 bit si capiscono oltre a quelli a 12; niente modo "trasparente" e niente API per i programmi. Corretto un ciclo senza fine dell'originale in Erase All Unprotected (il for con ScreenInc non arrivava mai oltre l'ultima posizione).
Comandi di rete completi: 40 su 48. Restano trsp e i sette di AppleTalk.
rete> spp -e 02:00:00:00:00:02
ECHO 0.20000000002H.2H: 42 byte di dati
74 byte da 0.20000000002H.2H: seq=0 tempo=0.898 ms
rete> spp 02:00:00:00:00:02
Connected to 0.20000000002H.400H (sid 1, did 431f).
spp> ciao xerox
CIAO XEROX
rete> trsp
spp_debx=41
32abcc:
890 LISTEN:user req=CONNECT -> SYN_SENT
890 SYN_SENT:output
seq = 0, ack = 0, alo = 4, len = 2a, flags=c0<SP_SP,SP_SA>, sid = 1, did = 0, dt = 0,
sna=<net:00,host:525400123456,port:bb8> dna=<net:00,host:020000000002,port:400> -> SYN_SENT
890 SYN_SENT:input
seq = 0, ack = 0, alo = 3, len = 2a, flags=c0<SP_SP,SP_SA>, sid = 431f, did = 1, dt = 0, ...
XNS nel kernel 2 (ported/rete_xns.c, da kernel-7/bsd/netns)
Prima di TCP/IP negli uffici c'era XNS, la rete della Xerox (1980): da li' vengono IPX di Novell e l'idea stessa di "socket" numerati. Il kernel dell'archivio ha ancora la pila XNS di 4.4BSD, e trsp e' il suo trpt: stampa la traccia delle connessioni SPP. Per avere una traccia vera serviva la pila vera, e c'e':
ns_input.c): 30 byte, indirizzi rete + host (l'indirizzo della scheda: niente ARP) + socket, trama Ethernet di tipo 0x0600. La somma di controllo XNS di ns_cksum.c - somma e rotazione di un bit a ogni parola, cosi' l'ordine delle parole conta - verificata in entrata e messa in uscita.ns_error.c): l'eco sul socket 2 risponde anche quando nessuno ascolta ("Yes, this is a wart!" dice il sorgente); un pacchetto per un socket che non c'e' torna indietro come errore NOSOCK con l'inizio del colpevole.spp_usrreq.c): connessioni affidabili che numerano i pacchetti invece dei byte. spp_input() con LISTEN, SYN_RECEIVED, SYN_SENT; spp_reass() con le conferme, la finestra ("alo") e le conferme doppie; spp_output() con la coda di spedizione, la finestra di congestione e i pacchetti di sistema; i timer lenti (ritrasmissione, persistenza, keep-alive) con il loro raddoppio; il controllo di spp_hardnosed.spp_debug.c): il registro circolare di 100 voci, con l'indirizzo vero del blocco di controllo, come nel kernel.trsp (trsp.c) legge quel registro e lo stampa come l'originale: -a gli indirizzi, -s lo stato di sequenza, -t i timer, -j solo gli indirizzi dei blocchi, -p un blocco solo, -z azzera.
Per avere connessioni da tracciare: spp host [socket], una sessione come nc (riga spedita, risposta stampata, ESC chiude con la convenzione Xerox end/end reply, tipi 0xfe/0xff), e spp -e host, l'eco XNS. La macchina ha un servizio di eco SPP sul socket 1024. spp non e' nella lista dei 48: e' l'aggiunta che serve a trsp. Gli indirizzi si scrivono come ns_ntoa() dell'archivio (0.20000000002H.400H) e si leggono anche nella forma della scheda (02:00:00:00:00:02).
Provato con un'altra macchina sullo stesso cavo. QEMU mette la scheda su un hub con due porte: la rete di sempre e un socket UDP che porta le trame Ethernet grezze a tools/xns_peer.py, una macchina XNS scritta a parte. Eco 3/3 (la somma verificata dall'altra parte), connessione SPP in tre tempi, dati avanti e indietro, chiusura end/end reply, "Connection refused" su un socket vuoto, e la ritrasmissione: con --perdi l'altra macchina butta il primo pacchetto di dati e SPP lo ripete un secondo dopo. Anche in locale (spp natalizioos, attraverso la coda di anello XNS) contro il nostro servizio di eco. make run-xns prepara il cavo in piu'.
tcpdump mostra le trame XNS come l'originale, che per IDP non ha un decodificatore: indirizzi, "ns", lunghezza, parole esadecimali.
Nel Desktop 3 una riga nuova: XNS (IDP/SPP): 6/6 - la somma a rotazione (un bit cambiato si vede, due parole scambiate anche) e ns_ntoa() con il ritorno.
Differenze dichiarate: niente inoltro (non siamo un router XNS) e niente RIP; i pacchetti SPP fuori ordine si scartano invece di aspettare in coda (l'altro capo li ripete); la chiusura end/end reply, che BSD lasciava ai programmi, la fa spp.
Altro
trsp e le schermate di tn3270 ne vogliono di piu'.Comandi di rete completi: 41 su 48. Restano i sette di AppleTalk.
rete> appletalk -u -q
AppleTalk now online: 100.115 (0x0064.73), zona NATALIZIO
rete> atstatus 'LaserWriter Ufficio'
Default printer is:LaserWriter Ufficio:LaserWriter@NATALIZIO
status: idle
rete> atprint < /etc/hosts
atprint: printing on LaserWriter Ufficio:LaserWriter@NATALIZIO.
rete> aurpd
aurpd: capo locale 10.0.2.15, porta UDP 387
esportate: 100-101 (NATALIZIO, Ufficio, Laboratorio)
tunnel 1 con 10.0.2.2: aperto; di la': 200 (Remoto), 300-301 (Remoto, Magazzino)
rete> appletalk -t
start-stop net:node d p PBTZ GSBU zones
100-101 0:0 0 0 1001 1000 1,2,3
0-200 0:1 1 1 0001 1000 4
300-301 0:1 2 1 0001 1000 4,5
rete> appleping c8.5 599 2
599 bytes from 00c8.05.04: hop= 1, time= 0. ms
rete> atlookup "=:=@Remoto"
Found 2 entries in zone Remoto
00c8.05.fa Mac Remoto:Macintosh
012d.09.fb Server Remoto:AFPServer
AppleTalk nel kernel 2 (ported/rete_atalk.c, ported/rete_atcmd.c)
La rete dei Macintosh (1985): niente configurazione, i nodi si scelgono l'indirizzo da soli, le stampanti si trovano per nome. Dal kernel AppleTalk di Rhapsody (kernel-7/bsd/netat) e dalla sua libreria (LibcAT-1), con i loro formati sul filo:
ddp_aarp.c): l'indirizzo si sceglie a caso e si sonda 20 volte ogni 1/30 di secondo; se qualcuno risponde e' occupato e se ne prova un altro. Prima nell'intervallo di avvio (0xFF00-0xFFFE), poi, saputo il cavo dal router, di nuovo nel cavo vero.ddp.c): l'intestazione lunga di 13 byte, la somma a rotazione verificata in entrata, i socket.ddp_rtmp.c, ddp_r_zip.c, ddp_nbp.c, ddp_aep.c): il router del cavo, GetNetInfo per la zona, le ricerche per nome (BrRq al router o LkUp in broadcast, i jolly = e ≈), il nome di questa macchina (natalizioos:NATALIZIOOS), l'eco.atp_*.c, pap_*.c): transazioni con fino a 8 risposte, e sopra il protocollo delle LaserWriter: OpenConn, SendData, dati da 512 byte, EOF, CloseConn, SendStatus.I sei comandi, dai sorgenti di Commands/at_cmds:
| Comando | Cosa fa |
|---|---|
appletalk | -u accende la pila, -d la spegne, -n -s -p -a -z come l'originale; -t la tabella delle rotte con aurpd acceso |
appleping | l'eco AEP verso net.node (in esadecimale, come l'originale) o verso un nome NBP, con tempi e salti |
atlookup | le ricerche NBP, i jolly, -z le zone (ZIP GetZoneList via ATP) |
at_cho_prn | sceglie la stampante predefinita: zone, stampanti della zona, scelta in /etc/atalk/atprint.dat |
atstatus | lo stato di una stampante (PAP SendStatus) |
atprint | stampa cio' che arriva con < file (PAP, pacchetti da 512 byte) |
aurpd: AppleTalk attraverso Internet (ported/rete_aurp.c)
AURP (RFC 1504, 1993) unisce due reti AppleTalk lontane: i router ai due capi di un tunnel UDP (porta 387) si passano i pacchetti DDP chiusi in un'intestazione di dominio di 22 byte, e si dicono quali reti e quali zone stanno dalla loro parte - una volta sola, poi soltanto i cambiamenti. Portato da aurpd.c (il comando) e dal protocollo del kernel (aurp_open.c, aurp_ri.c, aurp_zi.c, aurp_tickle.c, aurp_rd.c, aurp_misc.c, aurp_tx.c, aurp_rx.c):
/etc/aurp_tunnel.cfg letto come l'originale: port, local, remote (piu' d'uno), hide, export, anche abbreviate (strpfx(): basta r per remote); gli errori con il loro testo ("unknown keyword", "Hiding/Export conflict", "no remote' keyword"). La prima volta il file d'esempio di aurp_tunnel.cfg(5) con gli indirizzi di questa rete. Opzioni -d (ferma, con Router-Down), -q, -l log`.appletalk -t e appletalk -z mostrano la tabella delle rotte e delle zone come showRoutes() e showZones() dell'originale; le zone di la' compaiono anche in atlookup -z e in at_cho_prn.Dove l'originale ha un errore evidente c'e' la correzione, dichiarata nel sorgente: il controllo di local che non scattava mai (guardava una variabile che nessuno impostava), le reti nuove a distanza 0 in un RI-Upd scartate, l'evento nullo letto come evento.
Provato con tre macchine intorno. tools/at_peer.py e' la rete AppleTalk sul cavo in piu' (router 100.1, zone NATALIZIO, Ufficio e Laboratorio, una LaserWriter, due Mac) e, come un router vero, impara le rotte dagli RTMP degli altri; tools/aurp_peer.py e' il capo del tunnel di la' (reti 200 e 300-301, un Mac, un server, una LaserWriter). Dal nostro lato: tunnel aperto, rotte e zone nei due sensi, eco anche con pacchetti interi da 599 byte, ricerca NBP in una zona di la', stato della stampante di la', la rete 102 comparsa sul cavo mandata di la' con un RI-Upd e la sua zona, il Tickle dopo 60 secondi di silenzio, aurpd -d e appletalk -d con il Router-Down. Dal lato di la' (--prove, 18 prove su 18): la ricerca dei nomi del nostro cavo, l'eco verso di noi e verso la LaserWriter del cavo (inoltrata da noi), la rete 400 aggiunta e poi tolta. E un Mac del cavo che ci usa come router per cercare i nomi di la' (--brrq): le risposte gli arrivano con due salti. make run-at prepara tutto.
tcpdump e il terminale
tcpdump stampa AppleTalk come print-atalk.c (RTMP, ZIP, NBP, ATP, AEP, AARP) e ha i filtri atalk, aarp e ns."..." e '...' tengono insieme gli argomenti con gli spazi (i nomi NBP ne sono pieni), e < file manda un file all'ingresso del comando (atprint < /etc/hosts).Correzioni
make ricompila il kernel 2 anche quando cambiano solo i suoi file (il bersaglio kernel2): prima si poteva provare un binario vecchio.tftp put di un file oltre i 396 byte non partiva (i blocchi sono di 516). Ora il limite e' quello dell'interfaccia: 1472 byte su eth0, 268 su sl0. Provato: 650 byte in due blocchi, da 516 e 142.tftp get, ftp get e rcp con un percorso (/etc/file) mettevano il file nella radice con quel nome intero, e nessuno lo ritrovava: ora va nella sua cartella.udp_servizio_togli): dopo aurpd -d la porta 387 risponde di nuovo "porta irraggiungibile".atprint senza una stampante scelta diceva atstatus: No default printer selected...: ora, come get_chooserPrinter() di chooser.c, il messaggio porta il nome di chi chiama.Comandi di rete completi: 48 su 48. Il kernel 2 e' a 698 settori su 952, con 674 KB fra la fine dei suoi dati e l'heap. Il kernel 1 non e' toccato e parte come prima.
NATALIZIOOS v0.2.5.68 - Stage1
LBA ok, loading Stage2...
NATALIZIOOS v0.2.5.68 - Stage2 active (real mode)
... (niente piu' "[1] completo [3] Desktop 3")
login root / root -> menu -> 5 DESKTOP 3
Avvio del kernel 2: Desktop 3 con la rete...
[k2] avviato dal menu del kernel 1
[k2] utente: root
rete> ifconfig
eth0: flags=863<UP,BROADCAST,RUNNING,SIMPLEX> mtu 1500
inet 10.0.2.15 netmask 0xffffff00 broadcast 10.0.2.255
ESC -> [k2] ESC: ritorno al menu del kernel 1 -> login -> menu
Il kernel 2 si avvia dal menu, non all'accensione
Fino alla 0.2.5.67 il kernel 2 si sceglieva al prompt di stage2, all'accensione ("[1] completo [3] Desktop 3 + rete"). Ora si parte sempre dal kernel 1, e il kernel 2 si apre dal menu, alla voce DESKTOP 3, insieme a Desktop 1, Desktop 2 e alle due shell.
boot/stage2.asm): il codice resta, dentro %ifdef SCELTA_KERNEL_ALL_AVVIO (con nasm -DSCELTA_KERNEL_ALL_AVVIO torna). Senza, stage2 carica il kernel 1 senza aspettare.cksum) e la versione (kernel/descrittore_kernel.asm). Settore 2046 per il kernel 1, 2047 per il kernel 2.kernel/salto_kernel.c, kernel/trampolino.asm): il kernel 1 legge il kernel 2 dal disco in memoria bassa (0x10000, come stage2), controlla il CRC, poi copia un pezzetto di codice a 0x9000 e lo chiama. Quel codice spegne la paginazione, carica una GDT sua e una IDT vuota, copia il kernel 2 a 1 MB e lo chiama esattamente come stage2 (push mappa E820; call 0x100000): il kernel 2 non si accorge di non essere partito da li'.id -> uid=0(root)). Stage2 la azzera a ogni accensione, cosi' un riavvio non trova messaggi vecchi.e1000_ferma(): la e1000 scrive da sola in memoria i pacchetti in arrivo, e quella memoria sta per diventare del kernel 1), rilegge il kernel 1, controlla il CRC e salta. Il kernel 1 capisce dalla casella che si torna dal Desktop 3 e va dritto al login e al menu, senza scelta testo/grafica e senza animazione.make fa il kernel 1, il kernel 2 e i due descrittori, e li scrive nella stessa immagine. Makefile.k2 legge la versione con sed invece di grep -oP (le espressioni Perl non ci sono dappertutto).La shell testuale: lo stack stava dentro le tabelle di pagina
Scegliendo la shell testuale il sistema si fermava con un errore di pagina, EIP=0x1C7E7003. La causa:
initial_page_tables di paging.c (da 0x18E000 a 0x28E000);paging_init(), che riempie quelle tabelle: la voce della pagina 0x1C7E7000 (valore 0x1C7E7003) finiva sopra un indirizzo di ritorno, e al ret la CPU saltava li'.E' anche la causa del vecchio errore del kernel 2 con la paginazione (EIP=0x327FFFFF, commento in kernel2/main.c). Ora kernel/entry.asm, dopo aver azzerato la bss, passa a uno stack tutto suo (256 KB nella bss; la funzione piu' pesante, misurata con -fstack-usage, ne usa 8,4). Vale per tutti e tre i binari: kernel 1, kernel 2 e immagine solo testo.
16, 32, 64 MB (e oltre)
Il kernel 1 occupava 30,5 MB di RAM: 0,5 di codice e 30 di bss, e quasi tutta era del Desktop 2 (un'arena da 16 MB, un buffer video da 9 MB per 1920x1200, 1,8 MB di immagini). Con 16 MB non ci stava, e con 32 MB c'era un secondo guaio: l'heap del kernel, fisso a 4 MB, cadeva in mezzo all'arena del Desktop 2.
desktop2_core/memory.c, framebuffer.c, img.c): il buffer per 1024x768, il modo piu' grande che il kernel sa impostare (3 MB invece di 9); l'arena da 16 MB come prima quando sta nella meta' dell'heap libero, altrimenti 8 o 4 MB (64, 32, 16 MB di RAM); le immagini solo quando qualcuno le carica davvero. Se l'heap non ha abbastanza, il Desktop 2 lo dice e torna al menu; se l'arena si esaurisce durante la sessione, la finestra nuova non si apre (prima un'app sarebbe partita scrivendo dall'indirizzo 0).kernel/heap.c, kernel/pmm.c): comincia dopo la fine del kernel e prende la RAM libera e contigua, fino a 64 MB, lasciando 512 KB al gestore della memoria fisica; e' segnato come occupato, quindi nessuno ci finisce sopra. Con 16 MB installati: 11,2 MB di heap nel kernel 1, 11,8 nel kernel 2; con 64 MB, 59 MB. Richiamare heap_init (lo fa bootanim-text, che rifa' i passi dell'avvio) non azzera piu' un heap in uso.natmemory non scrive piu' a 8-16 MB: quella zona "sicura" ora e' heap, e i motivi di prova avrebbero distrutto i blocchi in uso. La prova si fa su un blocco chiesto all'heap (fino a 8 MB, al massimo meta' del libero), restituito alla fine.drivers/fb.c): con 16 MB di RAM li' non c'era memoria (Desktop 1 e Desktop 3 disegnavano nel vuoto), e con l'heap dinamico sarebbe caduto in mezzo all'heap. Si prende dall'heap la prima volta che serve. Il Desktop 2, che non lo usa, non lo fa piu' allocare (vgpu_sys_enable_diretto), e dopo il Desktop 2 il Desktop 1 e il Desktop 3 ritrovano il doppio buffer: prima ripartivano senza, con lo sfarfallio.make run-gui RAM=16 (o 32, 128...): la RAM dell'emulatore si sceglie da riga di comando. Predefinito 64, come prima.Tastiera e mouse dopo il riavvio
"Solo la prima volta funzionano tastiera e mouse": dipendeva da quando si alzava il dito. mouse_init() chiedeva al controller PS/2 il suo byte di configurazione e leggeva il primo byte che trovava. Se nel controller c'era gia' un byte della tastiera - il rilascio del tasto premuto all'avvio, arrivato dopo il cli del bootloader - leggeva quello: il rilascio di "1" e' 0x82, quello di "3" e' 0x84, e riscritti come configurazione spengono l'interruzione della tastiera (bit 0) e la traduzione dei codici (bit 6).
Ora (drivers/mouse.c): porte spente, controller svuotato, poi la configurazione, con i bit giusti forzati invece che ricopiati; le risposte del mouse si accettano solo se vengono dal mouse (bit 5 dello stato); un mouse che non risponde ha la porta spenta, cosi' non tiene occupato il controller; il gestore del mouse legge sempre il byte, anche col mouse disabilitato. I gestori di tastiera e mouse ignorano le interruzioni senza un byte pronto (le lascia in attesa l'avvio a interruzioni spente: rileggere 0x60 dava un tasto o un clic fantasma). Se il controller non risponde ai comandi nuovi, la vecchia sequenza riaccende comunque la tastiera. Provato con tasti tenuti premuti fino a 2 secondi durante l'avvio e durante i salti fra i kernel, con i riavvii di QEMU e con tre accensioni di fila sulla stessa immagine.
In piu', la rotella: il mouse si accende in modo IntelliMouse (frequenze 200, 100, 80, poi "chi sei?": un mouse con la rotella risponde 3 e manda pacchetti da 4 byte; se la prova si interrompe a meta', un reset lo riporta a mouse normale). Nel Desktop 3 la rotella scorre la finestra dei comandi (3 righe per scatto); nel Desktop 2 arriva al core, che la usava gia' per testi, Writer, Sheets e Spotlight ma dal kernel riceveva sempre 0.
Le versioni
NATALIZIOOS v0.2.5.68 (prima stage1 diceva ancora v0.2.0.1.1.1): la versione arriva dal Makefile con nasm -DNAT_VERSIONE.uname), scritto a mano in natalizioos.h e rimasto fermo per venti versioni. Ora arriva dal Makefile (-DNAT_VERSIONE_MAKEFILE), in entrambi i kernel. La versione e' anche nella schermata di scelta testo/grafica.Anche
settori * 512, che con un numero assurdo traboccava; il Makefile avvisa se il kernel 1 supera 1150 settori (oltre, il ritorno dal Desktop 3 riavvia la macchina). Trovati da una rilettura indipendente del codice, insieme a natmemory, all'arena esaurita e al doppio buffer sprecato qui sopra.make firmware-img, KERNEL_LBA = 15000) dalla 0.2.5.48 caricava il kernel dal settore 9, scritto a mano; ora usa KERNEL_LBA.tools/) dicono DESKTOP 3 nel menu, tasto 5.Le prove
| 16 MB | 32 MB | 64 MB | 128 MB | 512 MB | |
|---|---|---|---|---|---|
Shell testuale (scelta 1 all'avvio), login, mem | OK | OK, anche dopo riavvio | OK | ||
| Menu: SHELL TESTUALE | OK | OK | OK, tre accensioni | ||
| Menu: SHELL GRAFICA | OK | OK | OK | ||
| Menu: Desktop 1 | OK | OK | OK | ||
| Menu: Desktop 2 | OK | OK | OK | OK | OK |
| Menu: Desktop 3 -> kernel 2, rete, ESC -> menu | OK | OK | OK, tre giri di fila | OK | OK |
| Immagine solo testo | OK | ||||
natmemory test (su un blocco dell'heap), poi mem | OK, 5,6 MB | OK, 8 MB |
Nel kernel 2 aperto dal menu passano tutte le prove automatiche della rete (socket 15/15, SLIP 9/9, 3270 8/8, XNS 6/6, AppleTalk 7/7, AURP 6/6, libc 12/12...) anche con 16 MB; ping, arp, ifconfig, netstat a mano. Con il kernel 2 assente o con un byte cambiato compaiono i messaggi giusti e si apre il Desktop 3 interno; senza il descrittore del kernel 1 il ritorno riavvia la macchina.
Call of Duty Multiplayer - Release COD_T6_R MP build 1.0.4 CL(1802446) CODPCAB2 Sun Jul 13 11:42:18 2014 win-x86 Ice9be6c NATALIZIOOS Server - Dev NAT_V2_D K1 build 0.2.5.69_WATERMAKER CL(170) VM Sun Sep 27 20:37:20 2026 bare-x86 01df0e4b
La prima riga e' quella in fondo allo schermo di Call of Duty: Black Ops II. La seconda e' il watermark di NATALIZIOOS da questa versione: una riga sola, centrata in basso, azzurro acciaio, con i dati veri della build. Fino alla 0.2.5.68 c'era un pannello di tre righe grigie in basso a destra (NATALIZIOOS_SERVER_EDITION_DEV / For testing purposes only / Build v...).
Il nome della build
La versione e' 0.2.5.69 e la build si chiama _WATERMAKER. Nel Makefile, accanto a NATALIZIOOS_VERSION, c'e' una riga nuova:
NATALIZIOOS_VERSION := 0.2.5.69 NATALIZIOOS_SUFFISSO := _WATERMAKER
Il nome arriva dappertutto dove prima c'era -DEV: stage1 e stage2, la scelta testo/grafica, il banner delle shell, i descrittori dei due kernel (settori 2046 e 2047) e il watermark. In natalizioos.h -DEV resta il valore predefinito, se si compila senza Makefile. Anche il "timbro" della versione contiene il nome: cambiandolo si ricompila tutto. Per la build dopo basta cambiare quella riga, o lasciarla vuota.
La riga, campo per campo
| Call of Duty | NATALIZIOOS | Che cos'e' |
|---|---|---|
Call of Duty Multiplayer | NATALIZIOOS Server | il prodotto |
Release | Dev | il tipo di build |
COD_T6_R | NAT_V2_D | la serie (0.2) e D di dev |
MP | K1, K2, TX | quale eseguibile: kernel 1, kernel 2 (Desktop 3), immagine solo testo |
build 1.0.4 | build 0.2.5.69_WATERMAKER | versione e nome della build |
CL(1802446) | CL(170) | il numero della build |
CODPCAB2 | VM | il computer che ha compilato |
Sun Jul 13 11:42:18 2014 | Sun Sep 27 20:37:20 2026 | data e ora della build |
win-x86 | bare-x86 | la piattaforma: x86 senza un sistema sotto |
Ice9be6c | 01df0e4b | l'impronta dei sorgenti |
Nel kernel 1 la versione e' quella che l'aggiornamento software tiene sul disco (settore 2000), come prima; su un disco nuovo e' quella di compilazione.
Da dove vengono i dati
scripts/costruzione.sh (nuovo) scrive un piccolo file C, costruzione.c, con i cinque dati: kernel, numero, computer, data, impronta. Lo lanciano i Makefile subito prima di collegare un kernel, quindi data e ora sono quelle della build. Prima la data era __DATE__ __TIME__ di gfx_watermark.c, cioe' dell'ultima volta che si era ricompilato quel file. Usa solo comandi POSIX (date, find, sort, cksum...): gira uguale in MSYS2, in WSL e su Linux.costruzione.numero, un file nuovo nella cartella principale (partito da 163). Sale di uno ogni volta che il kernel 1, o l'immagine solo testo, si ricollega. Il kernel 2 prende il numero del kernel 1: make glielo passa, e make -f Makefile.k2 da solo lo legge da build-graphic/costruzione.c. Cosi' la stessa build ha lo stesso numero nei due kernel. Un file cambiato solo nel kernel 2 (tutta la rete) ricollega anche il kernel 1, e tutti e due prendono il numero nuovo; un make senza modifiche non cambia niente. Il numero non torna mai indietro.uname -n in maiuscolo, al massimo 12 caratteri: cosi' la riga sta in 1024 punti (126 caratteri su 128 colonne). In MSYS2 e in WSL e' il nome del PC; si sceglie a mano con make NAT_MACCHINA=NATPCAB1. Le build di questa versione dicono VM, il nome della macchina dove sono state compilate.cksum) di tutti i .c, .h e .asm dei due kernel, in ordine fisso: due build dello stesso sorgente hanno la stessa impronta, un carattere cambiato la cambia.Dove si vede
#8EB4E8) con un'ombra scura spostata di un punto, come in Call of Duty. Su uno schermo stretto (640x480) va su due righe, ciascuna centrata.K2.Il vecchio pannello resta
Il pannello a tre righe non e' stato tolto: sta in gfx_watermark.c dentro #ifdef NAT_WATERMARK_PANNELLO_VECCHIO, per la grafica e per il testo. Aggiungendo -DNAT_WATERMARK_PANNELLO_VECCHIO alle CFLAGS del Makefile torna quello di prima.
Anche
BUILD_STATUS.md ha una sezione nuova, "Numero di build e watermark": numero, computer, data e impronta di ciascun kernel, la riga come appare sullo schermo e il controllo che kernel 1 e kernel 2 abbiano lo stesso numero. Legge i dati da costruzione.c.gfx_watermark_text_width() e' segnata unused: nella build solo testo nessuno la usa e dava un avviso.Le prove
| Dove | Cosa si vede | Esito |
|---|---|---|
| Scelta testo/grafica all'avvio | v0.2.5.69_WATERMAKER | OK |
| Menu 1, SHELL GRAFICA | la riga K1 ... CL(170) in basso | OK |
| Menu 2, SHELL TESTUALE | la riga sulla penultima riga | OK |
| Menu 3, Desktop 1 | sopra la barra | OK |
| Menu 4, Desktop 2 | sopra il dock, in tre fotogrammi di fila | OK |
| Menu 5, Desktop 3 -> kernel 2 | K2 ... CL(170), ESC -> menu | OK, anche con 16 MB |
| Shell testuale VGA (1 all'avvio) | due righe, K1 ... CL(170) | OK |
| Immagine solo testo | due righe, TX ... CL(171) | OK |
E la build: make da pulito da' lo stesso numero ai due kernel; un secondo make non cambia niente; toccando solo un file della rete (ported/rete_udp.c) si ricollegano tutti e due col numero dopo; make -f Makefile.k2 da solo tiene il numero del kernel 1; make stato (grafica, testo, kernel 2) finisce con kernel 1 = kernel 2 = 170 e l'immagine solo testo a 171. Il pannello vecchio compila, in grafica e in testo. Kernel 1: 1070 settori su 1150; kernel 2: 710 su 952.
N A T A L I Z I O O S
Server Edition
v0.2.5.70-DEV
Seleziona la modalita' di avvio
[1] SHELL TESTUALE VGA 80x25
[2] MODALITA' GRAFICA < 1920x1080 >
frecce sinistra/destra: risoluzione (resta salvata)
Fino alla 0.2.5.69 tutto il sistema grafico girava a 1024x768, scritto nel codice (fb_init(FB_MODE_1024x768) e fb_set_mode(FB_MODE_1024x768) all'ingresso di ogni ambiente). La risoluzione nuova e' 1920x1080 (Full HD, 16:9), e non e' l'unica: si sceglie all'avvio con le frecce fra 1024x768, 1280x800, 1600x900 e 1920x1080, e la scelta resta. 1024x768 non se ne va: e' uno dei quattro modi, ed e' quello che il sistema usa da solo con 16 MB di RAM.
Il nome della build torna "-DEV" (la 0.2.5.69 era "_WATERMAKER"): la riga NATALIZIOOS_SUFFISSO del Makefile.
I modi, e dove si sceglie
drivers/fb.c, fb.h) conosce tre modi nuovi, FB_MODE_1280x800, FB_MODE_1600x900, FB_MODE_1920x1080, oltre a 640x480, 800x600 e 1024x768. Il BGA di QEMU accetta qualunque larghezza multipla di 8 finche' l'immagine sta nei 16 MB di memoria video: 1920x1080x4 = 7,9 MB. Le misure di un modo le dicono fb_modo_larghezza() e fb_modo_altezza(), il modo attuale fb_get_mode().NATVIDEO, il modo, un numero di controllo e il nome ("1920x1080", si legge anche a occhio). Vale per i prossimi avvii e anche per il kernel 2. Il modulo nuovo e' kernel/schermo.c.resolution conosce i modi nuovi (resolution 1920x1080, resolution 1600, resolution fullhd, resolution list) e salva la scelta se e' uno dei quattro; 640x480 e 800x600 restano come prima, solo per la sessione. resolution da solo dice quella attiva, quella salvata e la massima per la RAM.modo_video): il Desktop 3 si apre nello stesso modo del menu. Al ritorno il kernel 1 rilegge la scelta dal disco.Con poca RAM si scende da soli
Nel caso peggiore servono due immagini dello schermo in memoria: il doppio buffer (Desktop 1, Desktop 3, shell) e il buffer del Desktop 2. Piu' circa 10 MB per il resto: il kernel (4,2 MB), la riserva del gestore della memoria fisica, un'arena minima del Desktop 2 e un margine. schermo_modo_massimo() fa il conto sulla RAM che c'e', e la scelta (o il predefinito) si riduce a quello:
| RAM | Risoluzione massima |
|---|---|
| 16 MB | 1024x768, come fino alla 0.2.5.69 |
| da circa 19 MB | 1280x800 |
| da circa 22 MB | 1600x900 |
| da circa 27 MB | 1920x1080 |
Con make run-gui (64 MB) si parte a 1920x1080. Nel selettore d'avvio, con poca RAM, le frecce non vanno oltre il massimo e la riga sotto dice perche': "con 16 MB di RAM al massimo 1024x768".
Gli ambienti a tutto schermo
Tutto era disegnato per 1024x768. Dove le coordinate dipendevano gia' da fb_get_width()/fb_get_height() bastava lo schermo nuovo; dove erano scritte nel codice:
kernel/gfxboot.c): la colonna di destra (logo, server, caselle) si aggancia al bordo destro, le righe iLO e la barra dei tasti al fondo, e i quattro tasti U, P, B, N si distribuiscono su tutta la larghezza.kernel/gfxlogin.c): il blocco centrale (logo, sottotitolo, riquadro del login, finestra Admin) si centra, le righe di aiuto restano in fondo e le intestazioni restano larghe quanto lo schermo. Il visualizzatore dei README prende lo schermo che c'e' (a 1920x1080 42 righe invece di 25). Le sei card del menu si centrano fra intestazione e piede.GCONSOLE_COLS/GCONSOLE_ROWS), e l'animazione d'avvio, disegnata per 80x25, si centra li'.kernel/desktop.c): DT_W e DT_H sono lo schermo vero. Sfondo, barra, menu Start, finestre e trascinamento passavano gia' tutti da li'. Le finestre "Sistema" e "Risoluzione" dicono la risoluzione vera (erano scritte "1024x768") e la seconda elenca i sei modi, con quello attivo.kernel/desktop2.c, desktop2_core/framebuffer.c): il core riceve lo schermo vero in init() e dispone da se' dock, barra e finestre. Il suo buffer si prende grande quanto lo schermo (3 MB a 1024x768, 7,9 MB a 1920x1080) e si rifa' se la risoluzione sale; l'adattatore video si ricollega a ogni apertura, con le misure nuove. I pulsanti CRUSCOTTO e DEBUG stanno in basso a destra alla stessa distanza di prima. Se l'heap non basta, prima si restituisce il doppio buffer degli altri ambienti, poi si scende di risoluzione fino a 1024x768.Le schermate disegnate a mano per 1024x768
ServerCfg, SeaOfSens (seaof gui), DynPower (dynpower gui), NATTour (tour), l'editor (edit, e il Notepad del Desktop 1), il display di NATMemory e le schermate dei tasti U, P, B, N dell'avvio hanno le coordinate nel codice, centinaia. Invece di riscriverle, il driver ha una vista (fb_set_vista, fb_vista_centrata): tutto il disegno si sposta in un riquadro 1024x768 al centro dello schermo e ce lo tiene, e fb_get_width/height rispondono con le misure del riquadro. Si vedono uguali a prima, con il nero intorno. A 1024x768 la vista non si accende e non cambia niente. Anche lo scorrimento della console (gconsole_scroll, che scrive diritto in memoria video) rispetta la vista.
La memoria del doppio buffer
Il doppio buffer si prendeva dall'heap sempre grande 3 MB (FB_BACKBUFFER_MAX, 1024x768). Ora e' grande quanto il modo attuale, e se ne serve uno piu' grande quello vecchio torna all'heap (kfree) e se ne prende uno nuovo. fb_libera_doppio_buffer() lo restituisce quando nessuno lo usa.
Trovati da una rilettura indipendente
Il codice di questa versione l'ha riletto un secondo agente, che l'ha anche compilato con gcc e provato in QEMU:
.eh_frame (circa 62 KB), che nel binario piatto spingevano il kernel 1 oltre i 1152 settori che stage2 carica: le variabili inizializzate (system_version, lo stato del PostScript...) sarebbero partite a zero, senza nessun avviso. Con gcc e il linker GNU era gia' oltre il limite nella 0.2.5.69 (1164 settori). Ora kernel/linker.ld scarta quelle sezioni e i due Makefile passano a gcc -fno-asynchronous-unwind-tables -fno-unwind-tables: con gcc il kernel 1 fa 1036 settori. Con clang non cambia niente. E oltre 1152 settori la build si ferma con un errore invece di scrivere un'immagine che non parte intera.Anche
-MMD -MP): un make dopo aver cambiato solo un header, o solo kernel/graphic/natalizioos.c (che e' incluso dentro natalizioos_graphic.c), teneva gli oggetti vecchi e il kernel non cambiava. Ora il compilatore scrive i file .d accanto agli oggetti e il Makefile li legge.BUILD_STATUS.md ha una sezione "Risoluzione": i modi, la memoria di un'immagine, la RAM minima e il settore 2001.Le prove
| Risoluzione | Esito | |
|---|---|---|
| Avvio (64 MB): selettore, avvio grafico, login, menu | 1920x1080 | OK |
| Shell grafica, ServerCfg nel riquadro al centro | 1920x1080 | OK |
SHELL TESTUALE: animazione centrata, 240x67, resolution | 1920x1080 | OK |
| Desktop 1, poi ESC: si vede il login | 1920x1080 | OK |
| Desktop 2 | 1920x1080 | OK |
| Desktop 3 -> kernel 2 -> ESC -> menu | 1920x1080 | OK |
| SeaOfSens, DynPower, NATTour, editor, tasto U all'avvio | nel riquadro 1024x768 | OK |
| Informazioni (I), README, gestione utenti (M), finestra Admin | 1920x1080 | OK |
| Frecce all'avvio -> 1600x900; riavvio: la scelta resta; -> 1024x768 | 1600x900, 1024x768 | OK |
resolution 1920x1080 dopo un Desktop 2 a 1024x768 (il buffer si rifa'), e il contrario | tutte e due | OK |
| 32 MB: Desktop 1, Desktop 2, kernel 2 | 1920x1080 | OK |
| 24 MB: Desktop 1, Desktop 2, kernel 2 | 1600x900 | OK |
| 16 MB: selettore limitato, Desktop 2, kernel 2 e ritorno | 1024x768 | OK |
| Shell testuale VGA (1 all'avvio), immagine solo testo | 80x25 | OK |
| Build con gcc: kernel 1 di 1036 settori, kernel 2 e ritorno | 1920x1080 | OK |
Kernel 1: 1084 settori su 1150 (con clang); kernel 2: 717 su 952. Build 180 (kernel 1 e kernel 2), 181 l'immagine solo testo.