segmentaion fault
-
MatheStein schrieb:
Was könnte man den schon großartig böses anstellen, wenn jede Adresse sowieso nur auf den eigenen virtuellen Adressraum verweist?
Wenn die Maschine Abstraktionen anbietet, hat es das Betriebssystem leichter, den Speicher zu überwachen, sonst müsste es jeden einzelnen Zeigerzugriff per Software checken. Aber C soll auf allen Maschinen laufen, auch auf den einfachsten.
-
noobLolo schrieb:
der virtulle Adressraum kommt doch eher vom os oder?
Ja. Aber man kann sowas auch in die Hardware verlegen.
-
Speicher wird seitenweise vom OS vergeben. Eine gebräuchliche Seitengröße ist 4 KB.
Wenn du versuchst, auf den Speicher nach einem Objekt zuzugreifen, kann es gut sein, dass der Speicher dort auch noch zu einer reservierten Seite gehört. Deswegen kann man sich leider nicht auf ein segmentation fault bei ungültigen Speicherzugriffen verlassen.
-
Athar schrieb:
Speicher wird seitenweise vom OS vergeben. Eine gebräuchliche Seitengröße ist 4 KB.
Ich hab diese Frickeleien noch nie wirklich durchschaut. Meine Adressen haben 64 Bit, also nehm ich für die virtuellen Adressen, nun ja, sagen wir mal... 48 Bit.
-
danke für die antworten
verstehe ich es also richtig, dass die "segmentations faults" von der Hardware bzw bei gängigen Computern vom Betriebssystem ausgelöst werden?
Falls ja und wir das Beispiel mit dem Computer betrachten, wie arbeitet das Betriebssystem da in etwa?
Ich hab das bisher so verstanden, dass jede Page, die initial keine Daten von meinem Programm enthält(Stack etc) vorerst einen Zugriff nicht gestattet.
Allokieren ich nun einen Byte Speicher mit "malloc(1)", wird die komplette Page auf der sich mein Byte befindet für mein Programm verwendbar.Ist dieses so richtig?
gruß
-
In der Regel gibt es einen Memory Controller, wo du (als OS) den Speicher verwalten kannst. Da kannst du z.B. virtuelle Adressen zu reellen Mappen, Zugriffkontrolle einstellen, usw. Normalerweise kann man die Rechte Blockweise (bsp 1MB Block) vergeben, sprich sowas in der Art:
Der reelle 1MB großer Bereich ab 0xa000000 wird auf 0x10000000 gemappt und kann von jedem gelesen und geschrieben werden
Wenn ein Programm nun folgendes macht:
int *ptr = (int*) 0x100000c0; *ptr = 888;
dann geht alles glatt. Stell dir vor, das OS lässt diesen Bereich nur für priviligierte CPU-Modi schreiben, d.h nicht-priviligierte Modi haben keinen Schreibzugriff (also user-space). Dann würde der Memory-Controller beim Schreibversuch im User-Space eine Exception auslösen. Das OS behandelt die Exception und merkt, dass der Prozess xyz versucht hat, irgendwo zu schreiben, wo er keinen Schreibrecht hat. Dann kann das OS diesen Prozess einen SEGFAULT Signal schicken und ihn damit "vernichten" (OS können ganz schön unbarmherzig sein, wenn man ihre Gebote nicht folgt, so wie Götter der monotheistischen Religionen). So in etwa funktioniert es z.B. bei der ARM Architektur (die Exception heißt 'Data Abort Exception'). Bei x68 Architekturen wird das sehr ähnlich sein (ich kenne mich mit dieser Arch. nicht aus).
-
Bei Intel wird das über Global Descriptor Table und Local Descriptor Table realisiert. Man trägt quasi die Segmente ein die man fürs Lesen, Schreiben und/oder Ausführen "freischalten" will. Bei jedem Zugriff überprüft die Hardware passiv ob es für den Zugriff einen passenden Eintrag in der Tabelle gibt. Wenn nicht wird ein Segmentation Fault erzeugt und eine Funktion aufgerufen die diese Ausnahme behandelt. Das Betriebssystem legt nur die Tabellen an und stellt eine Funktion zur Verfügung die die Ausnahme behandelt, den Rest macht der Prozessor.
Die Tabelleneinträge beschreiben den Speicher Seitenweise, man kann auch ein bisschen einstellen wie groß die sind, aber so 1-4kb steht nur zur Auswahl. Malloc nimmt sich fürs erste Aufrufen eine neue Seite und gibt einen Pointer darauf zurück. Für jede folgenden Aufrufe kuckt malloc ob noch Platz auf der alten Seite ist oder ob eine neue Seite benötigt wird. Deswegen kann man manchmal hinter den Pointer den man per malloc bekommen hat noch etwas weiterschreiben, bis man auf der nächsten Seite landet, dann gibt es einen Segfault, es sei denn diese Seite wurde auch schon reserviert.
Wenn man mehr Speicher will als die Seiten groß sind muss man entsprechend mehrere Seiten anfordern. Die Fragmentierung des echten Speichers ist dabei egal, wenn du dir 20 Seiten auf einmal reservierst, dann sind die möglicherweise überall über den RAM verstreut. Allerdings gibt es ein Problem wenn der virtuelle Speicher fragmentiert ist, wenn du Speicher anforderst, der nicht mehr zwischen die Speicherblöcke die du schon hast, passt, dann kann dir malloc keinen Speicher geben, obwohl genug physischer Speicher da ist.
Übrigens sind Segmentation Faults nicht immer böse, es ist üblich RAM auf die Festplatte auszuswappen. Das heißt man nimmt eine nicht benutzte aber reservierte Seite des Speichers und schreibt sie auf die Festplatte und entfernt den entsprechenden Eintrag aus der Descriptortabelle. Wenn nun der Prozess doch mal auf den Speicher zugreift gibt es einen Segmentation Fault. Die entsprechende Funktion stellt dann fest, dass der Speicher eigentlich hätte da sein müssen, kopiert die Seite wieder von der Festplatte zurück in den RAM (und swapt eventuelle eine andere Seite aus um Platz zu schaffen) und lässt das Programm weitermachen, jetzt ist der Speicher da.
Du könntest dir das Minixbuch ansehen. http://www.pearsonhighered.com/educator/academic/product/0,,0131429388,00%2ben-USS_01DBC.html. Dort wird am praktischen Beispiel von Minix erklärt wie das alles funktioniert.
Boah, hab ich schon wieder nen Roman geschrieben. Egal, reicht jetzt.
-
vielen dank für die antwort
Was mir immer noch unklar ist:
Der virtuelle Adressraum eines Prozesses ist doch nur für diesen Prozess gedacht und somit sollten in ihm doch kein priviliegierter Code etc liegen oder?
Woher/Wie kommt Code in den Adressraum eines Prozesses, der eigentlich gar nicht von diesem Prozess verwendet werden darf?
Das Ganze irritiert mich leider noch ein wenig
-
Ich weiß nicht genau was du meinst, aber ich rate einfach mal.
Wir gehen mal davon aus wir haben die Standardbibliotheken, strcpy zum Beispiel. Alle möglichen Programme benutzen strcpy, also wird die Standardbibliothek inklusive strcpy einmal in den RAM kopiert und alle Prozesse erhalten einen entsprechenden Eintrage der es erlaubt diesen Speicherteil auszuführen.
Wenn Programm A ausgeführt wird, dann sind die Tabellen für Programm A geladen, also darf strcpy nur dort lesen und schreiben wo Programm A lesen und schreiben darf. Der Kernel benutzt auch strcpy und hat die entsprechenden Tabelleneinträge dass in den Kernelspeicher geschrieben werden kann, was Programm A nicht konnte.Übrigens hat natürlich nur der Kernel die entsprechenden Lese/Schreibrechte um die Tabelle zu bearbeiten, sonst wäre es ja sinnlos.
Ich glaube ich weiß jetzt was du meinst. Der Speicher den du nicht reserviert hast, existiert nicht. Es existiert nur die Adresse. Sobald du darauf zugreifen willst wird die virtuelle Adresse in eine physische Adresse umgerechnet, und genau da passiert der Fehler, diese virtuelle Adresse ist garnicht definiert und es gibt einen Segmentation Fault.
Es gibt also keinen Code für Speicher den du nicht reserviert hast, du kannst also auch nicht drauf zugreifen.
-
Woher/Wie kommt Code in den Adressraum eines Prozesses, der eigentlich gar nicht von diesem Prozess verwendet werden darf?
ich dachte spontan an sowas wie Pufferüberläufe alternativ einen Heap-Überlauf um nicht zu viel zu schreiben kopier ich ein bischen aus wiki..."Im Wesentlichen werden bei einem Pufferüberlauf durch Fehler im Programm zu große Datenmengen in einen dafür zu kleinen reservierten Speicherbereich, den Puffer, geschrieben, wodurch dem Ziel-Speicherbereich nachfolgende Informationen im Speicher überschrieben werden."
so hatte ich mir das vorgestellt
-
ich glaube so langsam komme ich dahinter
Was genau passiert denn im dem kernel-space des Prozess-Adressraums?
Liegen dort nur dynmaische bibs rum?gruß
-
sind die libs überhaupt im kernel space? könnten doch auch im user space liegen, das wär doch nur eine unnötige sicherheits lücke oder brauchen die unbedingt den kernel space? der kernel hat doch seinen eigenen adress raum und hat mit dem prozess raum nichts am hut außer das er ihn zuweist? find das thema auch spannend deswegen *bump*
-
hey
Dynamische bibs müssen ja irgendwo für jeden Prozess frei zugänglich sein. Würde jeder Prozess seine eigene bib bekommen, wären das ja statische bibs, wenn ich mich jetzt nicht irre
gruß
-
MatheStein schrieb:
Dynamische bibs müssen ja irgendwo für jeden Prozess frei zugänglich sein
das wären sie doch auch wenn sie im user-space sind? der user-space kann doch nicht auf der kernel-space zugreifen? sonst müßt ich doch nen kernel treiber coden hatt ich ja schon immer mal vor;)
-
Was ist denn der kernel-space des Prozess-Adressraums?
Die libs haben die Rechte des ausführenden Programms. Wenn ein Programm die Standardlibs benutzt haben die Standardlibs die Rechte dieses Programms, wenn der Kernel sie benutzt haben sie die Rechte des Kernels. Das hat mit Userspace und Kernelspace nichts zu tun, Standardlibs tun nur Dinge die im Userspace erlaubt sind. strcpy funktioniert für jedes Programm wie Kernel gleich, jeweils mit dem Unterschied dass entsprechend verschiedener Speicher ansprechbar ist.
Kernelspace und Userspace wird erst dann interessant wenn ein Programm etwas tun will was man ohne Kernelrechte nicht machen kann, hauptsächlich auf die Hardware zugreifen.
malloc ist ein gutes Beispiel, man sagt malloc(irgendwas), malloc kuckt ob noch Platz auf der aktuellen Seite ist, wenn ja wird der Platz als belegt markiert und ein Pointer darauf zurückgegeben, alles komplett ohne Kernel.
Aber wenn die Seite nicht mehr reicht muss malloc eine neue anfordern und dann muss im Descriptor Table ein Eintrag hinzugefügt werden, dass das Programm auf die neue Seite auch zugreifen darf. Dazu braucht man den Kernel. Man legt auf den Stack oder in Register einen Code für "Gib mir eine Seite" und erzeugt einen Interrupt. Der Interrupt setzt die Rechte auf Kernelrechte und springt in den Interrupthandler. Der kuckt dann in Register oder den Stack was das Programm will, handelt entsprechend und springt zurück ins Programm. (naja, meistens schlägt der Scheduler zu und springt in ein anderes Programm weil die Kosten gering sind weil das Programm eh schon unterbrochen ist.)Edit:
noobLolo schrieb:
der user-space kann doch nicht auf der kernel-space zugreifen? sonst müßt ich doch nen kernel treiber coden hatt ich ja schon immer mal vor;)
Klar kann ein normales Programm auf "Kernelspace" zugreifen, vorausgesetzt der entsprechende Tabelleneintrag ist gesetzt. Das ist sogar sinnvoll, man denke da an eine Funktion "GetFreeDiskSpace". Diese Funktion liegt in einer allgemein Zugänglichen Bibliothek und gibt einfach eine lokale statische Variable zurück. Jedes mal wenn sich der Speicherplatz ändert passt der Kernel die Variable entsprechend an. Damit liegt die Funktion und die Variable sowohl im Userspace als auch im Kernelspace, obwohl das eigentlich die falschen Begriffe dafür sind.
-
nwp2 schrieb:
malloc ist ein gutes Beispiel, man sagt malloc(irgendwas), malloc kuckt ob noch Platz auf der aktuellen Seite ist, wenn ja wird der Platz als belegt markiert und ein Pointer darauf zurückgegeben, alles komplett ohne Kernel.
ruft malloc nicht den kernel auf und sagt hey hier porcess xy gib mal speicher?
bzw. natürlich müssen user-space programme mit dem kernel interagieren also hat der kernel eine schnittstelle über die der user mit dem kernel interagieren kann denke das sind die syscalls?
GetFreeDiskSpace wird doch sicher auch nur einen syscall aufrufen und nicht irgendwo im kernel space rum pfuschen?
-
nwp2 schrieb:
strcpy funktioniert für jedes Programm wie Kernel gleich,
kann sein, aber nehmen wir mal als beispiel malloc, da nimmt der kernel nicht malloc sonder kmalloc() oder so also sind die libs nicht zwingend identisch...
ich denke eher die libs die wir in unsere programme einbinden wurden speziell für uns erstellt und haben mit den libs die der kernel verwendet nicht all zu viel zu tun
aber das ist alles halb wissen wär schön wenn uns da jemand aufklärt:)
-
noobLolo schrieb:
nwp2 schrieb:
malloc ist ein gutes Beispiel, man sagt malloc(irgendwas), malloc kuckt ob noch Platz auf der aktuellen Seite ist, wenn ja wird der Platz als belegt markiert und ein Pointer darauf zurückgegeben, alles komplett ohne Kernel.
ruft malloc nicht den kernel auf und sagt hey hier porcess xy gib mal speicher?
ja, das tut er, aber nur wenn der Speicher knapp ist. Wenn du malloc(100) machst, werden nicht 100 bytes reserviert sondern ein Block, indem die 100 Bytes sich befinden. Dieser Block (oder Seite/Page) kann eine feste größe haben, z.b. 4Kb. Ein erneuter malloc(100); müsste nicht nochmal nach eine Seite auffordern, da auf der letzten Seite noch genug Platz für weiter 100 Bytes gibt. Mache ich aber malloc(1024 * 5), dann muss malloc wieder dem kernel mitteilen, dass er einer weitere Seite braucht.
noobLolo schrieb:
bzw. natürlich müssen user-space programme mit dem kernel interagieren also hat der kernel eine schnittstelle über die der user mit dem kernel interagieren kann denke das sind die syscalls?
richtig, das sind die syscalls.
Ich kann nur dieses Buch http://books.google.de/books?id=vdk4ZGRqMskC&pg=PA383&lpg=PA383&dq=Andrew+SLOS+ARM&source=bl&ots=UJGhxRoQdH&sig=nrycPbArXBrS6QXauNLFtsDVmtY&hl=de&ei=lSRGS92MEpiwnQOtqdT1Ag&sa=X&oi=book_result&ct=result&resnum=5&ved=0CBgQ6AEwBA#v=onepage&q=&f=false
empfehlen: ARM system developer's guide: designing and optimizing system software Von Andrew N. Sloss,Dominic Symes,Chris Wright.ARM System Developer's Guide | ISBN: 1558608745
Die Bibel für ARM Programmier, ein wunderschönes Buch, welches viele Einblicke in genau diese Sachen gibt. Da die ARM Architektur einfach ist (im Vergleich zu x86), kann man schnell die Ideen lernen und auf andere Architekturen übertragen. Selbst wenn man keine ARM hardware hat, ist das Buch empfehlenswert. Aber die hardware ist nicht teuer, ein gumstix kostet keine 200 Dollar, zum Spielen & Lernen ist das sicher eine gute Investition.
-
Wie gesagt, manchmal braucht malloc den Kernel, manchmal auch nicht, je nachdem ob eine neue Seite benötigt wird oder nicht. So ein Kontextswitch (Sprung von Programm in Kernel und zurück) ist ziemlich langsam, das versucht man so weit es geht zu vermeiden.
Die Libs sind in sofern für den Kernel verschieden, dass er direkt machen kann was er will. malloc würde per Interrupt eine Seite anfordern während die Kernelversion einfach direkt eine Seite holt und für sich einen Tabelleneintrag einsetzt, ansonsten ist das gleich.
Syscalls sind auch nur Interrupts, mehr ist da nicht dahinter. Als Kernel braucht man keine Interrupts/Syscalls, alles was die tun ist in den Kernelmode springen, aber da ist der Kernel eh schon. Der ruft einfach die Funktion direkt auf die eigentlich der Interrupthandler aufrufen würde. string.h ist zum Beispiel komplett identisch für Programme und Kernel weil dort keine Syscalls vorkommen. Wenn man sich die stdlib.h mal hier ankuckt: http://www.cplusplus.com/reference/clibrary/cstdlib/ stellt man fest, dass nur Funktionen unter Dynamic memory management und Environment was mit Syscalls zu tun haben, der Rest ist gleich.GetFreeDiskSpace pfuscht nicht rum sondern könnte ganz einfach so implementiert sein:
int GetFreeDiskSpace(){ volatile static int discspace; return discspace; }
discspace ist in einer Seite wo shared Variablen rumliegen wo jeder lesen darf. Schreiben darf nur der Kernel. Als Programm kann man da nichts manipulieren weil alles mit Programmrechten läuft. Wenn man die Adresse von discspace errät kann man sie direkt lesen, das kann niemand verhindern. Schreiben allerdings geht nicht, da gibt es einen Segmentation Fault.