Ich schreib noch etwas, vielleicht bringt Dich das ein Stückchen weiter - aber kann auch sein das ich an Deinen Fragezeichen vorbeierzähle, da es nicht direkt mit Deinem Problem zusammenhängt. Hole auch soviele weiter Meinungen(z.B. bei stackoverflow oder das TASM / MASM / NASM manual) wie möglich ein um Dich vor unabsichtlichen Fehlern zu schützen.
Segment / "assume"
Bezüglich der segmente - die Direktiven im assembler wie z.B. "assume" klären das die Segmente gültig sind, sprich code, daten, stack sind korrekt initialisiert und das Programm findet somit gültige Bedingungen für eine hoffentlich korrekte Ausführung vor. Wenn Dein assembler listing nicht an ein anderes Objekt, welches das Hauptprogramm stellt, andockt, dann musst Du das im assembler mittels Direktive, wie z.B. assume, regeln.
Segment Override
Der segment-override wurde früher oft benutzt wenn jemand, ein einem Speichermodell, wo nur ein Datensegment voranden ist(z.B. tiny / small), in ein anderes(z.B. dem VGA Speicher) schreiben wollte. Da die "lineare", eingeblendete, Adresse des VGA(0xA0000) grösser als 16 bit ist, passt sie nicht in ein Standardregister wie ax, bx etc. sondern ergibt erst im Tandem mit einem Segment die Möglichkeit dort zu lesen / schreiben. Das war der grosse Trick des PCs die magischen 64KiB im "real mode" zu umgehen - Segment:Offset Adressen.
Es wurde also ein linearer 1MiB grosser Addressraum mittels eines Hilfskonstrukt aus zwei 16 bit Registern etwas "umständlich" zugänglich gemacht.
Das Segmentregister "es" wurde hier z.B. mit dem Wert A000(Segmenteil der linearen Adresse A0000(-> A0000) des VGA Bereichs) geladen und dann mittels
"mov byte ptr es:[di], byte_value"
z.B. ein Pixel gesetzt. Denn die effektive Adresse ist hier "es * 16 + di". Nun können die hinteren 16 bit der Adresse(A0000) mittels "di" ohne Probleme angesprochen werden und man "wandert" somit über den Schirm.
Zum Lernen
Vielleicht wäre es einfacher wenn Du die ganz alten, oder systemnahen, DOS "Hindernisparcours" vorerst wegglässt, und einfach Assembler(hilfsroutinen) im 32bit protected mode benutzt, wobei Du hier an einen geeigneten Compiler "andockst".
So ein Compiler, wie z.B. WatcomC, hat bereits alles korrekt aufgesetzt sodas Du auf einen 64+MiB grossen, linearen Addressraum ohne Segmentierung (dank DPMI) zugreifen kannst. Einziger Nachteil ist, dass eine Interaktion mit dem System(z.B. BIOS / SVGA VBE1.2, etc.) welches realen Dosspeicher braucht, ein wenig aufwendiger ist.
Nachdem Du hier Erfahrung gesammelt hast, gehst Du wieder zurück zum 16bit mode und erledigst Probleme die eine umständlichere Herangehensweise erfordern.
Du darfst die Programmierung selbst nicht mit den Eigenheiten bezüglich der Ansprache von Geräten verwechseln. Komplexitäten ergeben sich oft durch das Prozedere etwas "fachgerecht" zu erledigen und nicht durch die formale Sprache mittels Du der dies erledigen willst. ModeX z.B. ist, meiner Meinung nach, umständlich da die hardware die diesen bereithält relativ komplex ist. Die vom user benutzte Sprache ist hier eher unschuldig.
Das mit dem bios ist extrem interessant, aber zum Lernen eher schlecht da Du von Details die jenseits von assembler liegen abgelenkt wirst - Syntax- und hardware-probleme verschmelzen dann zu einem tödlichen Brei. Zudem ist das 16bit Programmiermodel relativ komplex durch z.B. Speicher-Segmentierung und Einschränkung(en) beim Registergebrauch.
Wichtig wäre eher mal "x86", "real mode", "segment", "interrupts - software / hardware" zu recherchieren.