Re: Kein Reboot am Ende des Flash Vorgangs (Nevis Nightly 07.05.2022)
Verfasst: So 22. Mai 2022, 12:09
Ich schreib jetzt mal, warum das grundsätzlich technisch nicht funktionieren kann (und wenn es das doch tut, dann ist es Glück / Zufall).
Was hier gemacht ist, daß das gemountete rootfilesystem "unter dem Hintern weg" ausgetauscht wird, ohne es vorher zu unmounten. Wenn jetzt irgendetwas auf dem System versucht, auf das Dateisystem zuzugreifen, und der Kernel bemerkt, daß da was nicht stimmt, dann crashed er höchstwahrscheinlich (kernel panic).
Nun ist der Neutrino-Code (wahrscheinlich) so gemacht, daß er nach dem starten des Flashprozesses nicht mehr bewusst auf das Filesystem zugreift. Ich gehe mal davon aus, daß das Image im tmpfs abgelegt wird etc.
Allerdings ist es, gerade auf Maschinen die nicht überragend viel Speicher haben oft so, daß teile des Programms (z.b. bestimmte Library-Teile), wenn der Speicher gerade für was anderes sinnvoller nutzbar zu sein scheint (das kann auch mal Dateisystem-Cache sein...), aus dem Speicher gehauen werden und dann, falls sie doch noch benötigt werden, über den Paging-Mechanismus des Kernels wieder geladen werden. Wenn jetzt aber zwischenzeitlich das Dateisystem von dem sie nachgeladen werden verändert wurde => Bumm. Klassischerweise segfaulted dann die Applikation.
Ein Beispiel was da schief gehen könnte ist (im NI-Neutrino) in der src/gui/update.cpp von Zeile 1106 bis 1110: da wird nochmal der statusbalken updated, eine Erfolgsnachricht gemalt, 2 Sekunden gewartet (das wird eher harmlos sein), der Bildschirm gelöscht, eine Hintbox gezeigt und dann endlich rebootet.
Jedes Malen einer Statusnachricht kann potenziell dazu führen, daß der Font vom Filesystem gelesen werden muss. => BUMM.
Wenn andersrum irgendetwas geschrieben werden soll auf das Filesystem (irgendein Hintergrunddienst, ...), dann kann man davon ausgehen, daß das soeben geflashte image kaputt ist, weil der schreibende Kernel ja vom alten Filesystemlayout ausgeht und nicht damit rechnet, daß das jemand hinter seinem Rücken ausgetauscht hat.
Warum funktioniert das manchmal trotzdem? Nun, bei Maschinen mit ausreichend Arbeitsspeicher passiert es einfach seltener, daß Sachen aus dem Speicher wieder rausgeworfen werden müssen.
Das Problem wurde übrigens schon früh erkannt und zumindest versucht besser zu lösen: im enigma (nicht enigma2): https://sourceforge.net/p/tuxbox-cvs/ap ... pgrade.cpp ab zeile 580, wenn flashext==1: da werden vom enigma die möglichen Statusscreens komplet all raw FB data vorgerendert, nach /tmp/ geschrieben, dann beendet sich enigma mit exit(3). Im Startskript https://sourceforge.net/p/tuxbox-cvs/cd ... init.d/rcS wird dann, wenn exit-code 3 ist, alles gekillt was sonst noch so läuft, einiges unmounted und dann das (kleine) flashtool https://sourceforge.net/p/tuxbox-cvs/ap ... lashtool.c aufgerufen. Das schreibt die vorbereiteten screens in den Framebuffer und malt während dem löschen und dem flashen jeweils eine primitive progressbar dazu, am Ende rebootet es hart.
Theoretisch funktioniert das auch nicht immer 100%, aber da so ziemlich alles vorher angehalten werden kann (ich hätte wahrscheinlich auch noch versucht, alle Dateiysteme außer /dev readonly zu remounten...) und das flashtool statisch kompiliert werden kann (es könnte sich auch noch mittels mlock() in den Speicher festnageln...), ist die Chance daß das funktioniert wesentlich größer.
Dieses generelle Problem ist übrigens auch der Grund dafür, warum die Android-Handys früher ihr update erst runtergeladen haben, und dann ins recovery booteten von wo aus das update dann installiert wurde. Danach wurde ins installierte system gebootet. Heute lösen sie das mittels a/b update, was noch andere Vorteile bringt.
Die ganzen DSL-Router etc. machen das übrigens auch so, also zumindest vor ca 12 Jahren, als ich mich da professionell mit beschäftigt habe (und die Kosten für FLASH Speicher in den Kisten noch wirklich relevant waren, da geht es um centbeträge was die Hardware kosten darf), da hatten die 3 Partitionen im FLASH und das system war zweigeteilt: "Basissystem" und "GUI komponenten. Eine Partition war immer frei. In die wurde dann beim Update das Basissytem geschrieben, dann rebooted. Wenn's wieder hochkam dann wurde in die alte Basis-partition das GUI update geschrieben. Reboot. Wenns wieder hochkam: gut. Beim nächsten update wurde dann in die alte GUI-Partition das Basis-update geschrieben etc.pp. Wenn das system nicht wieder erfolgreich startete dann wurde beim nächsten booten einfach wieder das vorhergehende System gebootet. Wichtig war, daß die dinger nie beim update gebrickt wurden sondern immer vom Kunden wieder gängig gemacht werden konnten.
Für solche Setups fehlt in den Nevis kisten aber ausreichend FLASH. Insofern würde ich, wenn ich so eine Kiste noch im ernsthaften Einsatz hätte und nicht sowieso die opkg-update-Methode viel besser fände, wohl am Konzept "minimales Flashtool" arbeiten. Oder alternativ am "die update-funktion schreibt einen bootfähigen usb-stick, der beim nächsten booten das system von außen updated und sich dann selber "ent-bootfähigt" so daß die kiste wieder vom FLASH bootet.
Was hier gemacht ist, daß das gemountete rootfilesystem "unter dem Hintern weg" ausgetauscht wird, ohne es vorher zu unmounten. Wenn jetzt irgendetwas auf dem System versucht, auf das Dateisystem zuzugreifen, und der Kernel bemerkt, daß da was nicht stimmt, dann crashed er höchstwahrscheinlich (kernel panic).
Nun ist der Neutrino-Code (wahrscheinlich) so gemacht, daß er nach dem starten des Flashprozesses nicht mehr bewusst auf das Filesystem zugreift. Ich gehe mal davon aus, daß das Image im tmpfs abgelegt wird etc.
Allerdings ist es, gerade auf Maschinen die nicht überragend viel Speicher haben oft so, daß teile des Programms (z.b. bestimmte Library-Teile), wenn der Speicher gerade für was anderes sinnvoller nutzbar zu sein scheint (das kann auch mal Dateisystem-Cache sein...), aus dem Speicher gehauen werden und dann, falls sie doch noch benötigt werden, über den Paging-Mechanismus des Kernels wieder geladen werden. Wenn jetzt aber zwischenzeitlich das Dateisystem von dem sie nachgeladen werden verändert wurde => Bumm. Klassischerweise segfaulted dann die Applikation.
Ein Beispiel was da schief gehen könnte ist (im NI-Neutrino) in der src/gui/update.cpp von Zeile 1106 bis 1110: da wird nochmal der statusbalken updated, eine Erfolgsnachricht gemalt, 2 Sekunden gewartet (das wird eher harmlos sein), der Bildschirm gelöscht, eine Hintbox gezeigt und dann endlich rebootet.
Jedes Malen einer Statusnachricht kann potenziell dazu führen, daß der Font vom Filesystem gelesen werden muss. => BUMM.
Wenn andersrum irgendetwas geschrieben werden soll auf das Filesystem (irgendein Hintergrunddienst, ...), dann kann man davon ausgehen, daß das soeben geflashte image kaputt ist, weil der schreibende Kernel ja vom alten Filesystemlayout ausgeht und nicht damit rechnet, daß das jemand hinter seinem Rücken ausgetauscht hat.
Warum funktioniert das manchmal trotzdem? Nun, bei Maschinen mit ausreichend Arbeitsspeicher passiert es einfach seltener, daß Sachen aus dem Speicher wieder rausgeworfen werden müssen.
Das Problem wurde übrigens schon früh erkannt und zumindest versucht besser zu lösen: im enigma (nicht enigma2): https://sourceforge.net/p/tuxbox-cvs/ap ... pgrade.cpp ab zeile 580, wenn flashext==1: da werden vom enigma die möglichen Statusscreens komplet all raw FB data vorgerendert, nach /tmp/ geschrieben, dann beendet sich enigma mit exit(3). Im Startskript https://sourceforge.net/p/tuxbox-cvs/cd ... init.d/rcS wird dann, wenn exit-code 3 ist, alles gekillt was sonst noch so läuft, einiges unmounted und dann das (kleine) flashtool https://sourceforge.net/p/tuxbox-cvs/ap ... lashtool.c aufgerufen. Das schreibt die vorbereiteten screens in den Framebuffer und malt während dem löschen und dem flashen jeweils eine primitive progressbar dazu, am Ende rebootet es hart.
Theoretisch funktioniert das auch nicht immer 100%, aber da so ziemlich alles vorher angehalten werden kann (ich hätte wahrscheinlich auch noch versucht, alle Dateiysteme außer /dev readonly zu remounten...) und das flashtool statisch kompiliert werden kann (es könnte sich auch noch mittels mlock() in den Speicher festnageln...), ist die Chance daß das funktioniert wesentlich größer.
Dieses generelle Problem ist übrigens auch der Grund dafür, warum die Android-Handys früher ihr update erst runtergeladen haben, und dann ins recovery booteten von wo aus das update dann installiert wurde. Danach wurde ins installierte system gebootet. Heute lösen sie das mittels a/b update, was noch andere Vorteile bringt.
Die ganzen DSL-Router etc. machen das übrigens auch so, also zumindest vor ca 12 Jahren, als ich mich da professionell mit beschäftigt habe (und die Kosten für FLASH Speicher in den Kisten noch wirklich relevant waren, da geht es um centbeträge was die Hardware kosten darf), da hatten die 3 Partitionen im FLASH und das system war zweigeteilt: "Basissystem" und "GUI komponenten. Eine Partition war immer frei. In die wurde dann beim Update das Basissytem geschrieben, dann rebooted. Wenn's wieder hochkam dann wurde in die alte Basis-partition das GUI update geschrieben. Reboot. Wenns wieder hochkam: gut. Beim nächsten update wurde dann in die alte GUI-Partition das Basis-update geschrieben etc.pp. Wenn das system nicht wieder erfolgreich startete dann wurde beim nächsten booten einfach wieder das vorhergehende System gebootet. Wichtig war, daß die dinger nie beim update gebrickt wurden sondern immer vom Kunden wieder gängig gemacht werden konnten.
Für solche Setups fehlt in den Nevis kisten aber ausreichend FLASH. Insofern würde ich, wenn ich so eine Kiste noch im ernsthaften Einsatz hätte und nicht sowieso die opkg-update-Methode viel besser fände, wohl am Konzept "minimales Flashtool" arbeiten. Oder alternativ am "die update-funktion schreibt einen bootfähigen usb-stick, der beim nächsten booten das system von außen updated und sich dann selber "ent-bootfähigt" so daß die kiste wieder vom FLASH bootet.