Betriebssysteme - Einleitung
Betriebssysteme - Einleitung
Betriebssysteme - Einleitung
Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong><br />
... alois.schuette@h-da.de<br />
Alois Schütte<br />
23. Oktober 2013<br />
1 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Inhaltsverzeichnis<br />
In diesem Teil wird in die Thematik der <strong>Betriebssysteme</strong> eingeführt.<br />
1 Überblick<br />
2 Was ist ein Betriebssystem<br />
3 Historische Betrachtung<br />
4 Grundlegende Konzepte<br />
5 Systemaufrufe<br />
6 Betriebssystemstrukturen<br />
2 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Überblick<br />
Überblick<br />
Software kann grob in zwei Arten eingeteilt werden:<br />
• Systemsoftware, die den Rechner selbst steuert und<br />
• Anwenderprogramme, die die Aufgaben der Anwender lösen.<br />
Das wichtigste Systemprogramm ist das Betriebssystem, es<br />
• kontrolliert die Ressourcen des Rechners und<br />
• ist Basis für die Entwicklung der Anwenderprogramme.<br />
3 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Überblick<br />
Ein moderner Rechner besteht u.a. aus den Komponenten:<br />
• ein oder mehrere Prozessoren,<br />
• Hauptspeicher,<br />
• Taktgeneratoren,<br />
• Datenendgeräte,<br />
• Plattenlaufwerke,<br />
• Netzwerkkomponenten und<br />
• DFÜ Einrichtungen.<br />
Insgesamt also eine komplexe Hardware.<br />
4 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Überblick<br />
Schichtenmodell<br />
• Ziel der Systemprogrammierung<br />
ist es, die Anwender vor der<br />
Komplexität der Hardware zu<br />
bewahren.<br />
• Um der Komplexität Herr zu<br />
werden, kann man Rechner in<br />
Schichten einteilen.<br />
• Das Betriebssystem ist eine<br />
Softwareschicht oberhalb der<br />
Hardware, bedient alle Teile des<br />
Systems und stellt dem<br />
Anwender eine virtuelle<br />
Maschine bereit, die einfach zu<br />
verstehen und zu bedienen ist.<br />
SAP Office ...<br />
Compiler Editoren Shells<br />
Betriebssystem<br />
Maschinensprache<br />
Mikroprogrammierung<br />
physikalische Geräte<br />
Systemprogramme<br />
Anwendungsprogramme<br />
Hardware<br />
5 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Was ist ein Betriebssystem<br />
Betriebssystem als virtuelle Maschine<br />
• Rechner sind auf Maschinenebene umständlich zu programmieren.<br />
Soll ein Text auf Diskette gespeichert werden, muss man auf<br />
Maschinenebene die Struktur des Speichermediums (Spuren,<br />
Sektoren, ...) und die Maschinenbefehle zum Positionieren des<br />
Lese/Schreibkopfes kennen.<br />
• Ein Anwendungsprogrammierer soll sich mit solchen Details der<br />
physischen Geräte nicht kümmern. Er erwartet von einem<br />
Betriebssystem eine ”<br />
virtuelle Maschine“, auf der er dateiorientiert<br />
Lesen und Schreiben kann.<br />
6 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Was ist ein Betriebssystem<br />
Betriebssystem als Ressourcenverwalter<br />
• Heutige Rechner sind leistungsfähig genug, um mehreren<br />
Benutzern Zugriff auf angeschlossene Drucker, Modems oder<br />
Prozessoren zu geben.<br />
Damit unterschiedliche Benutzer die selben Ressourcen<br />
gleichzeitig“ nutzen können, sind Verwaltungsaufgaben<br />
”<br />
erforderlich.<br />
• Ein Betriebssystem übernimmt die Verwaltung der unterschiedlichen<br />
an den Rechner angeschlossenen Geräte und der Bestandteile des<br />
Rechners selbst, wie Hauptspeicher oder Prozessoren.<br />
7 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Historische Betrachtung<br />
1 Überblick<br />
2 Was ist ein Betriebssystem<br />
3 Historische Betrachtung<br />
Erste Generation: Röhrenrechner (1945-1955)<br />
Zweite Generation: Transistoren und Stapelsysteme (1955-1965)<br />
Dritte Generation: Multiprogrammierung (1965-1980)<br />
Vierte Generation: Personalcomputer (seit 1980)<br />
Netzwerk-<strong>Betriebssysteme</strong> und Verteilte Systeme<br />
4 Grundlegende Konzepte<br />
5 Systemaufrufe<br />
6 Betriebssystemstrukturen<br />
8 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
analytical engine<br />
Der erste digitale Rechner wurde von dem<br />
englischen Mathematiker Charles Babbage (1792-<br />
1871) entworfen.<br />
• Seine ”<br />
analytical engine“ funktionierte nie,<br />
denn ihr rein mechanischer Aufbau ließen die<br />
Fertigung mit der erforderlichen Präzision<br />
damals nicht zu.<br />
• Programmiert wurde in Assembler; es war<br />
die erste Turing-vollständige<br />
Programmiersprache.<br />
• Dennoch, dieser Rechner wäre ohne<br />
Betriebssystem ausgekommen.<br />
Abbildung : analytical engine<br />
9 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Erste Generation: Röhrenrechner (1945-1955)<br />
Erste Generation: Röhrenrechner (1945-1955)<br />
Der erste brauchbare digitale Rechner wurde im zweiten Weltkrieg<br />
entwickelt, die trotz ihre Größe (ein ganzer Raum voll mit Rohren,<br />
Kühlungen und E/A Geräten) weniger Rechenleistung vollbrachten als ein<br />
heutiger moderner PDA.<br />
• Diese Röhrenrechner wurden von wenigen<br />
Ingenieuren konzipiert, gebaut, programmiert,<br />
betrieben und gewartet. Die Programmierung<br />
erfolgte im Binärcode. Programmiersprachen<br />
und <strong>Betriebssysteme</strong> waren noch<br />
unbekannt. Die ersten Programme waren<br />
Berechnungen von Sinus- und Kosinustabellen.<br />
• Anfang der fünfziger Jahre begann man, die<br />
Programme im Binärcode auf Lochkarten zu<br />
schreiben, damit sie wieder verwendbar waren.<br />
Abbildung : Zuse Z22<br />
10 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Zweite Generation: Transistoren und Stapelsysteme (1955-1965)<br />
Zweite Generation: Transistoren und Stapelsysteme<br />
(1955-1965)<br />
Die Erfindung des Transistors führte dazu, dass Rechner zuverlässiger<br />
arbeiteten. Ab diesem Zeitpunkt wurde zwischen Entwicklern, Herstellern,<br />
Betreibern und Wartungspersonal unterschieden.<br />
• Die Maschinen wurden in gesicherten Räumen<br />
aufgestellt, die von Bedienern (operator) betreut<br />
wurden.<br />
• Wenn ein Rechenauftrag (job) ausgeführt werden<br />
sollte, musste ein Programmierer die Befehlsfolge<br />
auf Lochkarten stanzen und dem Operator<br />
übergeben. Der legte die Lochkarten in den<br />
Kartenleser und startete den Übersetzungslauf<br />
und anschliessend das Programm. Das Ergebnis<br />
(Ausdruck auf Papier) brachte der Operator in<br />
den Ausgaberaum, wo der Programmierer das<br />
Resultat abholen konnte.<br />
11 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Zweite Generation: Transistoren und Stapelsysteme (1955-1965)<br />
• Die hohen Investitionskosten zwangen die Entwickler dazu, nach<br />
effizienteren Wegen zu suchen. Der Stapelbetrieb (batch system)<br />
sollte die Systeme besser auslasten. Zuerst wurden die Aufträge im<br />
Eingaberaum gesammelt und dort auf einem Magnetband mit einem<br />
kleineren Rechner übertragen, der teilweise auch die Übersetzung<br />
(Fortran-Maschinenkode) vornahm. Diese Prozedur wurde wiederholt<br />
(etwa eine Stunde) bis genügend viele Aufträge auf dem<br />
Magnetband waren.<br />
• Das erzeugte Eingabeband wurde dann zum eigentlichen Rechner<br />
gebracht. Dort wurde ein spezielles Programm gestartet (eigentlich<br />
der Vorfahre eines Betriebssystems), das das Eingabeband einlas, die<br />
Programme ausführte und das Ergebnis auf ein Ausgabeband<br />
schrieb. Der Operator brachte das Band dann zu einem weiteren<br />
Rechner, der die Ergebnisse ausdruckte.<br />
12 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Zweite Generation: Transistoren und Stapelsysteme (1955-1965)<br />
• Diese Abarbeitungsmethodik von<br />
Kontrollkarten war der Vorgänger heutiger<br />
Kommandointerpreter (Shells).<br />
• Großrechner der zweiten Generation wurden<br />
weitgehend in Forschungseinrichtungen, etwa<br />
zur Lösung von Differentialgleichungen<br />
eingesetzt. Programmiert wurde in Fortran<br />
oder Assembler. In Banken und<br />
Versicherungen begann die EDV eine wichtige<br />
Rolle zu spielen – der ”<br />
closed job“ Betrieb<br />
wurde etabliert.<br />
• Typische <strong>Betriebssysteme</strong> waren FMS (Fortran<br />
Monitor System) und IBSYS, das IBM<br />
Betriebssystem des Großrechners 7094.<br />
Abbildung : Lochkartenstapel<br />
13 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Dritte Generation: Multiprogrammierung (1965-1980)<br />
Dritte Generation: Multiprogrammierung (1965-1980)<br />
Anfang der sechziger Jahre hatten die meisten Computerhersteller zwei<br />
unterschiedliche und gegenseitig inkompatible Produktlinien:<br />
• wortorientierte Grossrechner für den<br />
technisch-wissenschaftlichen Bereich und<br />
• zeichenorientierte Rechner für den<br />
kommerziellen Bereich, die hauptsächlich<br />
für Bandsortierungen und zum Ausdrucken<br />
(Auszüge, Fälligkeitslisten) bei Banken.<br />
• IBM versuchte daraufhin mit einem<br />
einheitlichen Konzept, dem System /360,<br />
beide Anwendungsbereiche durch eine<br />
Systemfamilie mit softwarekompatiblen<br />
Abbildung : IBM 360<br />
Rechnern abzudecken.<br />
Dieses Konzept war sehr erfolgreich. Nachfolger dieser Familie (z.B. 370,<br />
3090) sind z.T. noch heute bei Banken im Einsatz.<br />
14 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Dritte Generation: Multiprogrammierung (1965-1980)<br />
Der Vorteil, ein Betriebssystem für alle Bereiche und Programme, das auf<br />
allen Rechnern der Familie lauffähig war, erwies sich im Laufe der Zeit als<br />
die größte Schachstelle:<br />
es entstand ein sehr großes, kaum mehr zu wartendes<br />
Betriebssystem.<br />
Die wichtigsten Schlüsseltechniken der <strong>Betriebssysteme</strong> dieser<br />
Generation waren/sind:<br />
1 Mehrprogrammbetrieb,<br />
2 Spooling und<br />
3 Timesharing Betrieb.<br />
15 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Dritte Generation: Multiprogrammierung (1965-1980)<br />
Mehrprogrammbetrieb<br />
Der Mehrprogrammbetrieb (multiprogramming) wurde entwickelt,<br />
nachdem festgestellt wurde, dass gerade bei E/A intensiven<br />
Anwendungen die CPU die meiste Zeit nicht ausgelastet war. Z.B.<br />
wartete die CPU auf das Terminieren eines Bandkopierens, bevor sie den<br />
nächsten Auf- trag ausführte.<br />
Job5<br />
• Die Lösung bestand in der Aufteilung<br />
des Hauptspeichers in<br />
Speicherabschnitte auf Hardwareebene<br />
(jeweils mit Auftrag und<br />
Schutzmechanismen), die sicherstellen,<br />
dass Aufträge sich nicht gegenseitig<br />
beeinträchtigen.<br />
Job4<br />
Job3<br />
Job2<br />
Job 1<br />
Betriebssystem<br />
Hauptspeicher<br />
Abbildung : Mehrprogrammbetrieb mit<br />
5 Jobs im Speicher<br />
16 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Dritte Generation: Multiprogrammierung (1965-1980)<br />
Spooling<br />
Spooling (Simultaneous Peripheral Operation On Line) bezeichnet<br />
ursprünglich die unmittelbare Zwischenspeicherung von Aufträgen,<br />
die vom Kartenleser direkt auf Platten übertragen wurden.<br />
• Wenn ein Auftrag terminierte, konnte das Betriebssystem einen<br />
neuen Auftrag sofort in den freien Speicherbereich laden und<br />
bearbeiten.<br />
• Heute wird diese Technik noch im Bereich Ausgabe angewendet<br />
(Drucker Spooling).<br />
Die Rechner der dritten Generation waren zwar sehr leistungsfähig, sowohl<br />
im kaufmännischen als auch im technisch wissenschaftlichen Bereich.<br />
Die Verarbeitung war aber hauptsächlich Stapelverarbeitung: ein Fehler,<br />
der in einer Ausgabe (Liste) bemerkt wird, muss korrigiert werden und<br />
ein neuer Compile Auftrag muss eingeplant werden, dann erst kann die<br />
korrigierte Version in einem neuen Auftrag laufen.<br />
17 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Dritte Generation: Multiprogrammierung (1965-1980)<br />
Dialogsysteme<br />
Der Wunsch nach schnelleren Reaktions- und Antwortzeiten brachte<br />
Dialogsystem (timesharing) hervor: jeder Benutzer ist online mit dem<br />
Rechner über ein eigenes Terminal verbunden.<br />
MULTICS Das MIT, Bell Labs und General Electric wollten eine<br />
Maschine entwickeln, mit der gleichzeitig mehrere<br />
hundert Benutzer arbeiten können sollten. Vorbild war<br />
das elektrische Versorgungssystem: ein elektrisches Gerät<br />
wird einfach an eine Steckdose angeschlossen. Obwohl das<br />
Projekt MULTICS (MULTiplexed Information and<br />
Computing Service) nie abgeschlossen wurde, sind aus ihm<br />
viele wichtige Ideen in die Informatik eingeflossen und<br />
Nachfolgeprojekte stark beeinflusst worden.<br />
18 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Dritte Generation: Multiprogrammierung (1965-1980)<br />
Minicomputer<br />
• Ein weiterer wesentlicher Faktor der<br />
Entwicklung der Rechner der 3. Generation<br />
bestand im Wachstum der Minicomputer,<br />
beginnend mit der DEC PDP-1 im Jahre 1961.<br />
Sie verfügte über 4k 18 Bit Worte, kostete nur<br />
120.000 USD (ca. 5% einer IBM 7094) und<br />
war im Bereich numerische Berechnungen mit<br />
einer IBM 7094 vergleichbar, aber zu IBM<br />
inkompatibel.<br />
• Die leistungsstärksten PDP Rechner (PDP-11)<br />
waren noch Ende der 80er Jahre in vielen<br />
Forschungsinstituten zu finden.<br />
Abbildung : Ken Thompson<br />
und Dennis Ritchie 1972 vor<br />
einer PDP-11<br />
19 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Dritte Generation: Multiprogrammierung (1965-1980)<br />
UNICS, C<br />
• Ein Mitarbeiter des MULTICS Projektes bei Bell Labs, Ken<br />
Thompson, schrieb eine abgemagerte Version als<br />
Einbenutzersystem. Brian Kernighan bezeichnete dieses System als<br />
UNICS (UNiplexed Information and Computing Service), später<br />
wurde es dann UNIX genannt.<br />
• Ein anderer Informatiker bei Bell, Dennis Ritchi entwickelte die<br />
Programmiersprache C, um damit Unix neu zu schreiben. Ziel war<br />
es, ein Betriebssystem für den Mehrbenutzerbetrieb zu realisieren,<br />
das leicht auf unterschiedliche Rechner portierbar war. Bell Labs<br />
vergab Unix Lizenzen kostenlos an Hochschulen. Dadurch wurde es<br />
auf viele Plattformen portiert. Unix und die freie Version Linux ist<br />
das Betriebssystem, das auf den meisten Plattformen portiert wurde.<br />
Die Vorlesung wird sich stark an Unix orientieren, um Konzepte von<br />
<strong>Betriebssysteme</strong>n zu erklären.<br />
20 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Vierte Generation: Personalcomputer (seit 1980)<br />
Vierte Generation: Personalcomputer (seit 1980)<br />
Durch Fortschritte in der Entwicklung hochintegrierter Schaltkreise<br />
konnten Rechner konstruiert werden, die auch für Einzelpersonen<br />
erschwinglich waren.<br />
Der PC wurde mit <strong>Betriebssysteme</strong>n und Oberflächen ausgestattet, die es<br />
auch ”<br />
Normalbenutzern“ ermöglichten, damit zu arbeiten.<br />
Zwei <strong>Betriebssysteme</strong> hatten sich für PCs durchgesetzt:<br />
• MS-DOS von Microsoft für Intel-basierte Hardware und<br />
• Unix für Systeme, die auf Motorolas 68000er Familie beruhten.<br />
MS-DOS entwickelte sich bezogen auf die Funktionalität immer mehr in<br />
Richtung Unix (Microsoft war zu dieser Zeit der führende Unix Lieferant<br />
[XENIX]).<br />
21 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Netzwerk-<strong>Betriebssysteme</strong> und Verteilte Systeme<br />
Netzwerk-<strong>Betriebssysteme</strong> und Verteilte Systeme<br />
Seit Mitte der 80er Jahre wurden Rechner mehr und mehr vernetzt.<br />
Dadurch wurden Betriebssystemkonzepte erforderlich, die mit der<br />
Problematik<br />
• des Datenaustausches,<br />
• dem Verteilen von Aufgaben und<br />
• der Kommunikation zwischen Prozessen,<br />
die auf unterschiedliche Knoten im Netz ablaufen, erforderlich.<br />
22 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Netzwerk-<strong>Betriebssysteme</strong> und Verteilte Systeme<br />
Netzwerkbetriebssysteme<br />
Netzwerkbetriebssysteme erlauben einem Benutzer, Daten von anderen<br />
bekannten Rechnern zu verwenden und sich auf anderen bekannten<br />
Rechnern anzumelden.<br />
• Dabei können die verschiedenen Rechner unterschiedliche<br />
<strong>Betriebssysteme</strong> haben. Die Transparenz beim Kopieren von Daten<br />
geschieht durch die Verwendung von Standardprotokollen (z.B. ftp).<br />
• Die Netzwerkbetriebssysteme unterscheiden sich nicht von den<br />
Einprozessor <strong>Betriebssysteme</strong>n; auf unterster Ebene ist lediglich eine<br />
Schicht, die über einen Netzwerkkontroller auf Netzressourcen<br />
zugreift.<br />
• Weiterhin sind Programme erforderlich, um Remoterechner<br />
ansprechen zu können (telnet, rsh, ssh, sftp, scp).<br />
23 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Historische Betrachtung<br />
Netzwerk-<strong>Betriebssysteme</strong> und Verteilte Systeme<br />
Verteilte <strong>Betriebssysteme</strong><br />
Verteilte <strong>Betriebssysteme</strong> spiegeln dem Benutzer ein Einprozessorsystem<br />
vor:<br />
der Benutzer weiß nicht, auf welchem Knoten im verteilten<br />
System sein Programm abläuft.<br />
• Auf der Betriebssystemseite sind (im Gegensatz zu<br />
Netzwerkbetriebssystemen) hier neue komplexere Algorithmen<br />
erforderlich, die die Verteilung der Programmstücke auf verschiedene<br />
Prozessoren vornehmen, um viel paralleles Arbeiten zu ermöglichen.<br />
• Die Problematik resultiert im Prinzip daraus, dass es keine zentrale<br />
Haltung von Zustandsinformation gibt. Der Entwurf eines verteilten<br />
Systems hat darüber hinaus zum Ziel, die Ausfallsicherheit und<br />
Fehlertoleranz des Gesamtsystems zu erhöhen: der Ausfall von<br />
Komponenten soll ohne Beeinträchtigung der Funktionalität<br />
gewährleistet werden können.<br />
Diese Verteilten <strong>Betriebssysteme</strong> werden hier nicht behandelt !<br />
24 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Grundlegende Konzepte<br />
Grundlegende Konzepte<br />
1 Überblick<br />
2 Was ist ein Betriebssystem<br />
3 Historische Betrachtung<br />
4 Grundlegende Konzepte<br />
Prozesse<br />
Dateien<br />
Kommandointerpreter<br />
5 Systemaufrufe<br />
6 Betriebssystemstrukturen<br />
25 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Grundlegende Konzepte<br />
Prozesse<br />
Prozesse<br />
Ein Prozess ist ein Programm, das sich in der Ausführung befindet.<br />
Während ein Programm nur einmal auf der Festplatte gespeichert ist,<br />
dann dieses Programm mehrmals aufgerufen sein, also können von einem<br />
Programm mehrere Prozesse existieren.<br />
Ein Prozess besteht aus:<br />
• dem Programmcode,<br />
• den Programmdaten,<br />
• dem Programmstack,<br />
• dem Befehlszähler,<br />
• dem Stackzeiger und<br />
• Speicherinformationen, die zum Ablauf erforderlich sind.<br />
26 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Grundlegende Konzepte<br />
Prozesse<br />
Der Prozessbegriff orientiert sich an den Erfordernissen von Timesharing<br />
Systemen, d.h.<br />
das Betriebssystem muss periodisch entscheiden,<br />
• ob der aktuelle Prozess weiterlaufen darf, oder<br />
• ob seine Zeitscheibe schon abgelaufen ist, er also suspendiert werden<br />
muss, um einem neuen Prozess die CPU zuteilen zu können.<br />
Wird ein Prozess suspendiert, so muss er bei der Reaktivierung im selben<br />
Zustand weitermachen können.<br />
Deshalb ist es erforderlich, sich den zuletzt ausgeführten Befehl vor der<br />
Suspendierung mit allen Umgebungsmerkmalen zu merken, z.B. die<br />
aktuell geöffneten Dateien, pro Datei den Stand des Lese/Schreib-Kopfes<br />
usw.<br />
27 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Grundlegende Konzepte<br />
Prozesse<br />
Prozesstabelle<br />
Diese Informationen werden i.A. in einer so genannten Prozesstabelle<br />
gespeichert. Sie besteht aus einem Feld von Strukturen, die die o.a.<br />
Informationen pro suspendiertem Prozess enthält. Damit besteht ein<br />
suspendierter Prozess aus:<br />
• seinem Prozessadressraum<br />
(dem Speicher in dem Programm und Daten liegen) und<br />
• seinem Eintrag in der Prozesstabelle.<br />
28 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Grundlegende Konzepte<br />
Prozesse<br />
Systemaufrufe<br />
Systemaufrufe zur Prozessverwaltung sind u.a. dafür verantwortlich, dass<br />
• Prozesse erzeugt und terminiert werden,<br />
• Speicher für einen Prozess angefordert und freigegeben wird,<br />
• ein Prozess sich mit anderen ”<br />
unterhalten kann“ und<br />
• die Ablaufumgebung von Prozessen definiert (Größe des Speichers,<br />
Anzahl max. geöffneter Dateien, ...) wird.<br />
29 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Grundlegende Konzepte<br />
Prozesse<br />
Prozessbaum<br />
• Wenn ein Benutzer mit dem Betriebssystem<br />
arbeitet, so werden seine Anfragen von der Shell<br />
bearbeitet.<br />
• Dies ist ein Prozess, der die Kommandos des<br />
Benutzers liest, interpretiert und ausführt.<br />
• Nehmen wir an, der Benutzer gibt ein Kommando<br />
zum Suchen einer Datei ein. Dann muss die Shell<br />
einen Prozess erzeugen, ihm den Code des<br />
Such-Kommandos überlagern, ihn ausführen und<br />
dann beenden.<br />
• Durch das Erzeugen von Prozessen (Kindern),<br />
ausgehend von einem Erzeuger-Prozess (Vater),<br />
entsteht eine Baumstruktur von Prozessen.<br />
Abbildung :<br />
Prozessbaum<br />
30 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Grundlegende Konzepte<br />
Dateien<br />
Dateien<br />
• Dateien werden in Betriebssystem<br />
systematisch auf Plattenbereiche verteilt.<br />
• Systemaufrufe existieren zum Öffnen, Lesen,<br />
Schreiben und Schließen einer Datei.<br />
• Dateien in Linux werden organisiert, indem<br />
man Verzeichnisse anlegen kann. Ein<br />
Verzeichnis ist ein Katalog mit Inhalten, die<br />
aus Dateien und selbst wieder Verzeichnissen<br />
bestehen können. Somit hat auch das<br />
Dateisystem eine baumartige Struktur.<br />
• Wenn ein Benutzer sich in Linux anmeldet, so<br />
ist er automatisch in seinem Heimatverzeichnis<br />
(home directory). Die Daten kann er sich dort<br />
selbst organisieren.<br />
Abbildung : Dateibaum<br />
31 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Grundlegende Konzepte<br />
Dateien<br />
Zugriffsrechte<br />
Der Zugriff auf Dateien ist in Linux durch einen sechsstelligen binären<br />
Zugriffscode, der jeder Datei zugeordnet ist, geschützt.<br />
• Dieser Code besteht aus jeweils 3 Bit für den Eigentümer, die<br />
Gruppe zu der der Eigentümer gehört und für alle anderen.<br />
• Rechte können pro Kategorie vergeben werden: Lesen (r), Schreiben<br />
(w) und Ausführen (x).<br />
• Somit hat eine Datei, die nur der Benutzer Lesen, Schreiben und<br />
Ausführen darf, die Zugriffsmaske "<br />
rwx------ "<br />
.<br />
• Für Verzeichnisse bedeutet das x-Bit, dass im Katalog gesucht<br />
werden darf.<br />
(sticky bit und set user id bit später)<br />
32 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Grundlegende Konzepte<br />
Dateien<br />
mounten<br />
Wenn ein Betriebssystem hochgefahren wird, so wird i.A. zuerst das<br />
Dateisystem, auf dem sich das Betriebssystem selbst befindet, verfügbar<br />
gemacht. Es befindet sich normalerweise auf der Festplatte.<br />
• Daneben existieren auswechselbare<br />
Speichermedien z.B.<br />
USB-Laufwerke oder CD<br />
Laufwerke.<br />
• Auf solchen Medien kann man ein<br />
auswechselbares Dateisystem<br />
(mounted file system) anlegen. Es<br />
wird in das Hauptdateisystem<br />
eingehängt und ist ab diesem<br />
Zeitpunkt ”<br />
normal“ über den<br />
Pfadnamen erreichbar.<br />
Abbildung : mounten<br />
33 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Grundlegende Konzepte<br />
Kommandointerpreter<br />
Kommandointerpreter<br />
Das Betriebssystem ist der für die Ausführung von Systemaufrufen<br />
verantwortliche Teil der Systemsoftware. Wie im Schichtenmodell gezeigt,<br />
ist der Kommandointerpreter nicht Bestandteil des Betriebssystems.<br />
• Jedes Kommando, das der Benutzer<br />
absendet, bewirkt, dass die Shell einen<br />
Kindprozess erzeugt, der das<br />
Kommando ausführt.<br />
• Die Shell wartet, bis der Kindprozess<br />
terminiert ist.<br />
• Dann wird wieder ein Prompt<br />
geschrieben.<br />
• Soll ein Programm im Hintergrund<br />
ablaufen, so wird das Kommando mit<br />
dem Zeichen ’&’ abgeschlossen.<br />
34 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Systemaufrufe<br />
1 Überblick<br />
2 Was ist ein Betriebssystem<br />
3 Historische Betrachtung<br />
4 Grundlegende Konzepte<br />
5 Systemaufrufe<br />
Prozessverwaltung<br />
Signale<br />
Dateiverwaltung<br />
Katalogverwaltung<br />
Schutzmechanismen<br />
Zeitverwaltung<br />
Ablaufumgebung von Prozessen<br />
35 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Systemaufrufe<br />
Die vorgestellten Systemaufrufe sind an der Implementierung in Unix<br />
orientiert. In anderen <strong>Betriebssysteme</strong>n sind die Umsetzungen zwar<br />
teilweise anders, das Prinzip ist aber das gleiche.<br />
• Systemaufrufe bilden die Schnittstelle zur Hardware, auf dem das<br />
Betriebssystem abläuft.<br />
• Deshalb sind grosse Teile eines Systemaufrufs in Assembler<br />
programmiert.<br />
• Um sie für Programmierer nutzbar zu machen, wird oft eine<br />
C-Bibliothek bereitgestellt.<br />
• Systemaufruf zum Lesen einer Datei ist ”<br />
read“.<br />
count = read(file, buffer, nbytes);<br />
Mit drei Parametern von ”<br />
read“ wird beschrieben, welche Datei<br />
gelesen werden soll, wohin die Leseoperation das Ergebnis ablegen<br />
soll und wieviele Bytes aus der Datei gelesen werden sollen.<br />
36 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Beispiel<br />
simpleCat.c<br />
1 # define BUFSIZE 512<br />
2 int main (){<br />
3 char buf [ BUFSIZE ];<br />
4 int n;<br />
5 while ((n = read (0 , buf , BUFSIZE )) > 0)
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
Prozessverwaltung<br />
Ein Prozess besitzt einen Adressraum, in dem er abläuft. Der<br />
Prozessraum ist in mehrere Teile (Segmente) aufgeteilt.<br />
• Der Programmcode befindet sich im<br />
Textsegment.<br />
• Im Datensegment sind globale Objekte<br />
abgelegt,<br />
• dann folgt der Heap für dynamische<br />
Objekte.<br />
• Der Stack ist zur Speicherung lokaler<br />
Objekte und für Rücksprungadressen<br />
bei Rekursionen nötig.<br />
• Stack und Heap wachsen aufeinander<br />
zu.<br />
Stack<br />
Heap<br />
Daten<br />
Textsegment<br />
FFFF<br />
0000<br />
38 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
fork<br />
Ein Prozess wird erzeugt, in dem ein Eltern Prozess durch den<br />
Systemaufruf ”<br />
fork“ einen Kind Prozess erzeugt.<br />
• Der Aufruf erzeugt eine exakte Kopie<br />
des Originalprozesses (Kind=Clone des<br />
Vaters), einschließlich aller<br />
Dateideskriptoren, Register usw.<br />
Stack<br />
CC00<br />
Stack<br />
DD00<br />
• Nach dem fork werden beide Prozesse<br />
unterschiedliche Aktivitäten<br />
übernehmen.<br />
• Zum Zeitpunkt des fork haben alle<br />
Variablen die gleichen Werte, nach<br />
dem fork wirken sich Änderungen der<br />
Variablen nur noch im jeweiligen<br />
Prozess aus.<br />
Heap<br />
Daten<br />
pid=<br />
fork()<br />
Vater<br />
AA01<br />
Heap<br />
Daten<br />
pid=<br />
fork()<br />
Kind<br />
CC01<br />
39 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
fork<br />
Der fork Aufruf gibt einen Wert zurück, durch den im Programm<br />
unterschieden werden kann, ob der Code des Kindes oder des Vaters<br />
gemeint ist.<br />
0 ist der Kindprozess,<br />
>0 der Wert ist die<br />
Prozessidentifikation (pid)<br />
des Kindprozesses für den<br />
Eltern Prozess.<br />
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
Beispiel<br />
fork.c<br />
1 # include <br />
2 int main (){<br />
3 int childPid ;<br />
4 if (( childPid = fork ()) == -1) {
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
In welcher Reihenfolge erscheinen die Ausgaben ?<br />
42 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Shell<br />
Prozessverwaltung<br />
Ein reales Beispiel, bei dem ein Prozess erzeugt wird, ist die Shell.<br />
• Für jedes Kommando, das aus der<br />
Shell heraus ausgeführt wird, wird<br />
von der Shell ein eigener Prozess<br />
erzeugt.<br />
• Dabei dupliziert sich die Shell<br />
durch fork.<br />
• Im Kind (subshell) wird der Shell<br />
Code mit dem Code des<br />
auszuführenden Kommandos<br />
überlagert (exec).<br />
• Der Vater wartet (wait) bis der so<br />
erzeugte Kind-Prozess terminiert<br />
(exit).<br />
Somit kann in der Umgebung des Kindprozesses, das echo Kommando<br />
ausgeführt werden.<br />
43 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
Shell-Programmgerüst<br />
1 void read_command ( char *com , char ** par ){<br />
2 fprintf ( stdout , "$ ");<br />
3 .....<br />
4 return ;<br />
5 }<br />
7 int main (){<br />
8 int childPid ;<br />
9 int status ;<br />
10 char command [20];<br />
11 char * parameters [60];<br />
12 while (1) {<br />
13 read_command ( command , parameters );<br />
14 if (( childPid = fork ()) == -1) {
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
execvp<br />
Der execvp-Systemaufruf bewirkt, dass das Textsegment des Prozesses<br />
mit dem Kode des auszuführenden Programms überlagert wird.<br />
• Der erste Parameter ist das<br />
auszuführende Programm.<br />
• Der zweite Parameter ist ein<br />
Array mit Zeigern auf das<br />
auszuführende Programm und<br />
die Programmargumente.<br />
• Dabei muss der letzte Eintrag<br />
des Arrays NULL sein.<br />
parameters<br />
echo<br />
Hallo<br />
Jenni<br />
NULL<br />
execvp("echo", "Hallo", Jenni")<br />
Stack<br />
Heap<br />
Daten<br />
Kode von echo<br />
Kind<br />
DD00<br />
CC01<br />
Abbildung : execvp<br />
45 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
exit - wait<br />
Mittels wait und exit können Vater und Kind ’kommunizieren’.<br />
• ”<br />
exit“ hat einen Parameter, den so genannten Exitstatus.<br />
• Dies ist ein Integerwert zwischen 0 und 255.<br />
• Konvention in Unix ist, dass ein Exitstatus von Null bedeutet, dass<br />
die Aktion erfolgreich ausgeführt werden konnte. Jeder andere Wert<br />
wird als Fehler angesehen.<br />
• Dieser Status wird dem Elternprozess in der Variablen ”<br />
status“ des<br />
wait Aufrufs mitgegeben.<br />
Soll z.B. eine Kommunikation zwischen Kind und Eltern stattfinden, so<br />
kann das Kind z.B. durch<br />
exit(4);<br />
dem Elternprozess die Nachricht ’4’ übergeben.<br />
Der Elternprozess wird durch<br />
child-pid = wait(&status);<br />
dann die Information in der Variablen status sehen.<br />
46 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
Im Wert von Status sind unterschiedliche Informationen kodiert. Neben<br />
dem exit-Wert des Kindes, sind Informationen über die Terminierung des<br />
Kindes abgelegt. Um diese Informationen auszulesen existieren C-Macros.<br />
Macro<br />
WIFEXITED(status)<br />
WIFSGNALES(status)<br />
WIFSTOPPED(status)<br />
Beschreibung<br />
true, wenn status vom Kind gesetzt und das Kind<br />
normal beendet wurde. Dann kann man durch WE-<br />
XITSTATUS(status) die niederwertigen 8 Bit auslesen,<br />
die den Exit-Wert des Kindes beinhalten.<br />
true, wenn status vom Kind gesetzt und das Kind<br />
abnormal beendet wurde, durch signal. Dann kann<br />
man durch WTERMSIG(status) die Signalnummer,<br />
die das Kind beendet hat, abfragen.<br />
true, wenn status vom Kind gesetzt und das Kind<br />
gerade gestopped wurde. Dann kann man durch<br />
WSTOPSIG(status) die Signalnummer, die das Kind<br />
gestopped hat, abfragen.<br />
Der Aufruf von wait() bewirkt, dass der Vaterprozess blockiert, bis irgend<br />
ein Kinder beendet sind (waitpid später).<br />
47 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
Beispiel I<br />
status.c<br />
1 # include <br />
2 # include <br />
3 main ()<br />
4 {<br />
5 int childPid ;<br />
6 int status ;<br />
7 if (( childPid = fork ()) == -1) {<br />
8 fprintf ( stderr ," can 't fork \n");<br />
9 exit (1);<br />
10 } else if ( childPid == 0) { /* child process */<br />
11 fprintf ( stdout , " child : exit 4\n");<br />
12 sleep (20);
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
Beispiel II<br />
status.c<br />
23 WEXITSTATUS ( status ) );<br />
24 } else if ( WIFSIGNALED ( status )) {<br />
25 fprintf ( stdout , " parent : abnormal term . of child , status %d\n",<br />
26 WEXITSTATUS ( status ) );<br />
27 fprintf ( stdout , " parent : abnormal term . of child , signal number %d\n"<br />
28 WTERMSIG ( status ) );<br />
29 } else if ( WIFSTOPPED ( status )) {<br />
30 fprintf ( stdout , " parent : child stopped , status %d\n",<br />
31 WEXITSTATUS ( status ) );<br />
32 fprintf ( stdout , " parent : child stopped , signal number %d\n",<br />
33 WSTOPSIG ( status ) );<br />
34 }<br />
35 exit (0);<br />
36 }<br />
37 }<br />
> ulab<br />
49 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
Hörsaalübung<br />
Welche Ausgabe erzeugt das u.a. Programm?<br />
fork.c<br />
1 # include <br />
2 # include < unistd .h> // wg. getpid<br />
3 int x = 0;<br />
4 int main () {<br />
5 fork ();<br />
6 fork ();<br />
7 fork ();<br />
8 printf (" pid =%d x=%d\n", getpid () , x ++);<br />
9 }<br />
→ ulab<br />
50 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
waitpid<br />
Mit wait() kann der Elternprozess nur auf die Beendigung irgend eines<br />
Kindes warten.<br />
Soll der Vater hingegen auf die Beendigung eines bestimmten Prozesses<br />
warten, dann ist waitpid() zu verwenden.<br />
Mit dem ersten Argument von<br />
waitpid(pid wpid, int *status, int options) legt man fest,<br />
worauf gewartet werden soll:<br />
pid Beschreibung<br />
-1 auf beliebigen Prozess warten – äquivalent zu wait()<br />
pid auf die Beendigung von pid warten<br />
0 auf Beendigung warten, dessen Prozessgruppen-ID gleich<br />
der Prozessgruppen-ID des aufrufenden Prozesses ist.<br />
< −1 auf Beendigung warten, dessen Prozessgruppen-ID gleich<br />
der Prozessgruppen-ID des aufrufenden Prozesses ist.<br />
51 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
waitpid<br />
Das dritte Argument ”<br />
options“ von<br />
waitpid(pid wpid, int *status, int options) bestimmt das<br />
Verhalten von waitpid(). Folgende Konstanten können dabei verwendet<br />
werden<br />
Konstante<br />
WNOHANG<br />
WUNTRACED<br />
WIFSTOPPED<br />
Beschreibung<br />
Der aufrufende Prozess wird nicht blockiert, wenn der<br />
Prozess ppidnnoch nicht beendet wurde bzw. noch nicht<br />
im Zombie-Status ist. (waitpid() liefert in diesem Fall 0<br />
zurück).<br />
Status des angehaltenen Kindprozesses, der mit pid spezifiziert<br />
wurde.<br />
liefert 1 zurück, wenn es sich beim Rückgabewert um die<br />
PID des angehaltenen Kindprozesses handelt.<br />
Im folgenden Beispiel wird nicht auf die Terminierung des 1. Kindes<br />
gewartet, es wird ein zweites Kind erzeugt, auf dessen Beendigung<br />
gewartet wird.<br />
52 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
Beispiel I<br />
waitpid.c<br />
1 # include < unistd .h><br />
2 # include ...<br />
4 int main ( void ) {<br />
5 pid_t pid1 , pid2 ;<br />
6 int status ;<br />
7 switch ( pid1 = fork ()) {<br />
8 case -1:<br />
9 perror (" fork ()");<br />
10 return EXIT_FAILURE ;<br />
11 case 0: // child 1<br />
12 printf (" child 1: %d\n", getpid ());<br />
13 sleep (10);<br />
14 printf (" child 1: %d terminated \n", getpid ());<br />
15 break ;<br />
16 default : // parent code<br />
17 if ( waitpid (pid1 , NULL , WNOHANG ) != 0) {
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Prozessverwaltung<br />
Beispiel II<br />
waitpid.c<br />
23 perror (" fork ()");<br />
24 return EXIT_FAILURE ;<br />
25 case 0: // child 2<br />
26 printf (" child 2: %d\n", getpid ());<br />
27 sleep (5);<br />
28 printf (" child 2: %d terminated \n", getpid ());<br />
29 break ;<br />
30 default : // parent code<br />
31 if ( wait ( NULL ) != pid2 ) {
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Signale<br />
Signale<br />
Signale sind das Äquivalent im Bereich Software zu Interrupts im Bereich<br />
Hardware.<br />
• Wenn ein Signal zu einem Prozess gesendet wird und der Prozess<br />
das Signal nicht annimmt, dann wird der Prozess vom<br />
Betriebssystem automatisch entfernt.<br />
• Um sich vor diesem Automatismus schützen zu können, kann sich<br />
ein Prozess durch den Systemaufruf ”<br />
signal“ auf das Eintreffen von<br />
Signalen vorbereiten.<br />
• Dazu muss er eine Signalbehandlungroutine bereitstellen.<br />
• In einem Betriebssystem gibt es mehrere Signalarten. Die meisten<br />
Signale werden durch Ereignisse, die von der Hardware ausgelöst<br />
werden, erzeugt.<br />
55 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Signale<br />
Signale<br />
Die nachfolgende Tabelle listet die wichtigsten Signalarten auf.<br />
Nummer Konstante Bedeutung<br />
1 SIGHUP Hang up, Modemunterbrechung<br />
2 SIGINT DEL Taste<br />
3 SIGQUIT Quit Signal von Tastatur<br />
4 SIGILL Nicht erlaubte Instruktion<br />
5 SIGTRAP Unterbrechung für Testzwecke<br />
8 SIGFPE Gleitkommaüberlauf<br />
9 SIGKILL Abbruch<br />
10 SIBBUS Busfehler<br />
11 SIGSEGV Segmentfehler<br />
12 SIGSYS Ungültige Argumente bei Systemaufruf<br />
13 SIGPIPE Ausgabe auf Pipe ohne Leser<br />
14 SIGALARM Alarm<br />
15 SIGTERM Softwareerzeugtes Endesignal<br />
16 frei frei<br />
56 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Signale<br />
Beispiel<br />
Eine Endlosschleife wird auf das Eintreffen vom Signal ”<br />
SIGINT“,<br />
ausgelöst durch ”<br />
CTR-C“ reagieren, indem es einen Text ausgibt. signal.c<br />
1 # include <br />
2 # include < signal .h><br />
4 void handler ( int ) {<br />
5 printf (" handler \n");<br />
6 return ;<br />
7 }<br />
9 int main () {<br />
10 signal ( SIGINT , handler ); /* CTR -C handled */<br />
11 while (1) {<br />
12 printf (" main \n");<br />
13 sleep (2);<br />
14 }<br />
15 }<br />
Wenn ein Programm so geschrieben ist, dass es alle Signale ignoriert,<br />
könnte es nie abgebrochen werden. Deshalb gibt es das Signal ”<br />
SIGKILL“.<br />
Dieses Signal kann nicht per Signalhandler abgefangen werden.<br />
→ ulab: kill -9<br />
57 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Signale<br />
alarm<br />
Im Bereich Echtzeitanwendungen muss ein Betriebssystem in der Lage<br />
sein, Prozesse nach einer gewissen Zeit zu informieren, dass bestimmte<br />
Dinge zu erledigen sind.<br />
Der Systemaufruf ”<br />
alarm“ hat einen Parameter, der die Anzahl Sekunden<br />
angibt, nach denen das Signal ”<br />
SIGALARM“ erzeugt werden soll.<br />
timer.c<br />
1 void handler ( int ) {<br />
2 printf (" Bye \n");<br />
3 exit (0);<br />
4 }<br />
6 main () {<br />
7 char str [80];<br />
8 signal ( SIGALRM , handler ); ←<br />
9 while (1) {<br />
10 alarm (5); /* start timer */ ←<br />
11 printf ("> ");<br />
12 fgets (str , 80 , stdin );<br />
13 printf ("%s\n", str );<br />
14 }<br />
15 }<br />
58 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
Dateiverwaltung - create<br />
Das nachfolgende Beispielprogramm ”<br />
simpleTouch.c“ demonstriert den<br />
Systemaufruf ”<br />
creat“. Das Programm legt die als Aufrufparameter<br />
anzugebende Datei mit den Zugriffsrechten ”<br />
0640“ an.<br />
simpleTouch.c<br />
1 # include <br />
2 main ( int argc , char * argv []) {<br />
3 int fd;<br />
4 if ( argc != 2) {<br />
5 fprintf ( stderr , " usage : %s file \n", argv [0]);<br />
6 exit (1);<br />
7 }<br />
8 if (( fd= creat ( argv [1] ,0640)) < 0) { ←<br />
9 fprintf ( stderr , " create error \n");<br />
10 exit (2);<br />
11 }<br />
12 }<br />
Achtung: wenn die Datei bereits existiert, wird sie überschrieben!<br />
59 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
open<br />
Bevor eine Datei bearbeitet werden kann, muss sie geöffnet werden. Dazu<br />
existiert der Systemaufruf ”<br />
open“, der neben dem Namen noch die Art<br />
des Zugriffs (Lesen, Schreiben oder beides) benötigt.<br />
Das folgende Programm liest die als Parameter anzugebende Datei und<br />
schreibt den Inhalt auf die Standardausgabe.<br />
cat.c<br />
1 # define BUFSIZE 512<br />
2 main ( int argc , char ** argv ) {<br />
3 int fd; ←<br />
4 int n;<br />
5 char buf [ BUFSIZE ];<br />
6 if ( argc != 2) { /* check usage */<br />
7 fprintf ( stderr , " usage %s file \n", argv [0]);<br />
8 exit (1);<br />
9 }<br />
10 if (( fd= open ( argv [1] , O_RDWR )) < 0) { /* open file */ ←<br />
11 fprintf ( stderr , " open error \n");<br />
12 exit (2);<br />
13 }<br />
14 while ((n = read (fd , buf , BUFSIZE )) > 0) /* read and write file */<br />
15 write (1 , buf , n); ←<br />
16 }<br />
Achtung: wenn die Datei bereits existiert, wird sie uberschrieben!<br />
60 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
Wahlfreier Zugriff - fseek<br />
Der wahlfreie Zugriff auf Dateien wird durch den Systemaufruf lseek“ ”<br />
realisiert.<br />
fseek(file, no-bytes, start); hat drei Parameter:<br />
1 einen Filedescriptor, der die zu bearbeitende Datei definiert,<br />
2 den Offset , der die Position des Lese/Schreibkopfes in Byte relativ<br />
zum Ausgangspunkt definiert und<br />
3 den Ausgangspunkt für die Positionierung des Lese/Schreibkopfes<br />
• SEEK SET=Dateianfang<br />
• SEEK END=Dateiende<br />
• SEEK CUR=aktuelle Position<br />
Damit kann man ein Programm, das eine Datei (angefangen am<br />
Dateiende, d.h. rekursiv) liest einfach programmieren.<br />
61 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
fseek<br />
revCat.c<br />
Dateiverwaltung<br />
1 main ( int argc , char ** argv ) {<br />
2 int fd;<br />
3 int pos ;<br />
4 char buf [1];<br />
6 if ( argc != 2) { /* check usage */<br />
7 fprintf ( stderr , " usage %s file \n", argv [0]);<br />
8 exit (1);<br />
9 }<br />
10 if (( fd= open ( argv [1] , O_RDWR )) < 0) { /* open file */<br />
11 fprintf ( stderr , " open error \n");<br />
12 exit (2);<br />
13 }<br />
14 if (( pos = lseek (fd , -1, SEEK_END )) == -1) { /* pos == # bytes in file */<br />
15 fprintf ( stderr , " lssek error \n");<br />
16 exit (1);<br />
17 }<br />
18 while (pos >=0) { /* read and write file */<br />
19 read (fd , buf , 1);<br />
20 write (1 , buf , 1);<br />
21 lseek (fd , --pos , SEEK_SET );
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
fseek<br />
Im folgenden Programm ”<br />
hole.c“ wird der Systemaufruf ”<br />
lseek“<br />
verwendet, um ein Loch in einer Datei zu erzeugen.<br />
hole.c<br />
1 int main () {<br />
2 int fd;<br />
3 char buf1 [] = " abcdefghijklmnop ";<br />
4 char buf2 [] = " ABCDEFGHIJKLMNOP ";<br />
6 if (( fd= creat (" file . hole " ,0640)) < 0) {<br />
7 fprintf ( stderr , " create error \n"); exit (1);<br />
8 }<br />
9 if (( write (fd , buf1 , 16)) != 16) { /* offset now 16 */<br />
10 fprintf ( stderr , " buf1 write error \n"); exit (2);<br />
11 }<br />
12 if (( lseek (fd , 32 , SEEK_SET )) == -1) { /* offset now 32 */ ←<br />
13 fprintf ( stderr , " lseek error \n"); exit (3);<br />
14 }<br />
15 if (( write (fd , buf2 , 16)) != 16) { /* offset now 48 */<br />
16 fprintf ( stderr , " buf2 write error \n");<br />
17 exit (4);<br />
18 }<br />
19 exit (0);<br />
20 }<br />
63 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
fseek<br />
Ein Aufruf bewirkt:<br />
$ hole file . hole<br />
$ ls -l fil *<br />
-rw -r----- 1 as users 48 Nov 1 19:11 file . hole<br />
$ od -c file . hole<br />
00 a b c d e f g h i j k l m n o p<br />
20 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 Loch<br />
40 A B C D E F G H I J K L M N O P<br />
60<br />
$<br />
64 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
Dateistatus - stat<br />
Die Informationen über eine Datei, der so genannte Dateistatus, kann<br />
durch die Systemaufrufe abgefragt werden.<br />
• ”<br />
stat“ und ”<br />
fstat“ liefern Information über Dateien,<br />
• ”<br />
lstat“ über Links.<br />
Alle Aufrufe geben eine Struktur vom Typ ”<br />
stat“ zurück.<br />
1 struct stat {<br />
2 dev_t st_dev ; /* ID of device containing file */<br />
3 ino_t st_ino ; /* inode number */<br />
4 mode_t st_mode ; /* protection */<br />
5 nlink_t st_nlink ; /* number of hard links */<br />
6 uid_t st_uid ; /* user ID of owner */<br />
7 gid_t st_gid ; /* group ID of owner */<br />
8 dev_t st_rdev ; /* device ID (if special file ) */<br />
9 off_t st_size ; /* total size , in bytes */<br />
10 blksize_t st_blksize ; /* blocksize for file system I/O */<br />
11 blkcnt_t st_blocks ; /* number of 512 B blocks allocated */<br />
12 time_t st_atime ; /* time of last access */<br />
13 time_t st_mtime ; /* time of last modification */<br />
14 time_t st_ctime ; /* time of last status change */<br />
15 };<br />
65 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
Beispiel - lstat<br />
simpleFile.c<br />
1 int main ( int argc , char ** argv ) {<br />
2 int i;<br />
3 struct stat buf ;<br />
4 char * ptr ;<br />
5 if ( argc != 2) { /* check usage */<br />
6 fprintf ( stderr , " usage %s file \n", argv [0]); exit (1);<br />
7 }<br />
8 if ( lstat ( argv [1] , & buf ) < 0) { /* get file info */ ←<br />
9 fprintf ( stderr , " lstat error \n"); exit (2);<br />
10 }<br />
11 /* print file info */<br />
12 if ( S_ISREG ( buf . st_mode )) ptr = " regular ";<br />
13 else if ( S_ISDIR ( buf . st_mode )) ptr = " directory ";<br />
14 else if ( S_ISCHR ( buf . st_mode )) ptr = " character special ";<br />
15 else if ( S_ISBLK ( buf . st_mode )) ptr = " block special ";<br />
16 else if ( S_ISFIFO ( buf . st_mode )) ptr = " fifo ";<br />
17 else if ( S_ISLNK ( buf . st_mode )) ptr = " link ";<br />
18 else ptr = " unknown mode !";<br />
19 printf ("%s: %s\n", argv [1] , ptr );<br />
20 }<br />
66 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
Pipes<br />
Pipes sind ein Kommunikationsmedium, das es erlaubt, dass Prozesse in<br />
FIFO Manier kommunizieren. Eine Pipe ist dabei eine Pseudodatei, die<br />
die Kommunikationsdaten temporär beinhaltet.<br />
• In Unix können durch folgende<br />
Kommandofolge zwei Dateien sortiert und in<br />
einer Datei zusammengeführt werden:<br />
cat file2 file2 | sort<br />
• Hierbei werden zwei Prozesse erzeugt: der<br />
erste (cat) schreibt seine Ausgabe in die Pipe,<br />
der zweite (sort) liest die Standardeingabe aus<br />
der Pipe.<br />
cat datei<br />
sort<br />
67 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
pipe<br />
Der Systemaufruf ”<br />
pipe“ erzeugt eine Pipe und gibt zwei<br />
Dateideskriptoren zurück, einen zum Lesen und einen zum Schreiben.<br />
Der Mechanismus wird nun am Beispiel eines Programmes gezeigt, bei<br />
dem ein Vaterprozess einem Kind Informationen über eine Pipe sendet.<br />
int p[2];<br />
pipe(p); /* p[1]=Schreibende, p[0]=Leseende */<br />
68 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
Beispiel - pipe I<br />
pipe.c<br />
1 /* Example : Child | Father */<br />
2 # define BUFSIZE 20<br />
3 main () {<br />
4 int pid , status ;<br />
5 int p [2]; ←<br />
6 char buf [ BUFSIZE ];<br />
7 if ( pipe (p) != 0) {
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
Beispiel - pipe II<br />
pipe.c<br />
22 sleep (2); /* just to have more time to see it with "ps -el"<br />
23 }<br />
24 close (p [1]);<br />
25 exit (0);<br />
26 }<br />
27 default : /* father : read from pipe */<br />
28 { int length ;<br />
29 close (p [1]); /* close write end of pipe */
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
named pipes<br />
Sollen zwei unabhängige Prozesse über eine Pipe kommunizieren, muss<br />
die Pipe außerhalb des Adressraums der beiden Prozesse abgebildet<br />
werden.<br />
Eine named Pipe (FIFO) wird erzeugt durch den Systemaufruf:<br />
int mknod(char *pathname, int mode, int dev);<br />
Dabei ist pathname, ein Dateiname von Unix, der Name des FIFO. Das<br />
Argument mode definiert den Zugriff (read, write mit Rechten für owner,<br />
group, others). mode wird logisch OR verknüpft mit dem Flag S IFIFO<br />
(aus < sys/stat.h >), um auszudrücken, dass mknode einen FIFO<br />
erzeugen soll. dev wird für FIFOs ignoriert (relevant für andere Objekte).<br />
FIFOs können auch durch das Unix Kommando mknod erzeugt werden:<br />
/etc/mknod name p<br />
71 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
named pipe - Beispiel Client/Server cat<br />
Eine Client-Server Anwendung, bei der ein Client einen Dateinamen von<br />
stdin einliest und den Namen auf einen Kommunikationskanal schreibt.<br />
Der Server liest den Dateinamen vom Kommunikationskanal, öffnet die<br />
Datei, liest den Inhalt und schreibt ihn über einen Kommunikationskanal<br />
zurück. Der Client erwartet den Inhalt der Datei vom<br />
Kommunikationskanal (FIFO) und gibt ihn über stdout aus.<br />
72 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
named pipe - Beispiel Client/Server cat (Server)<br />
server.c fifo.h<br />
1 # define MAXBUFF 1024<br />
2 main () {<br />
3 int readfd , writefd ;<br />
4 /* Create the FIFOs , then open them - one for<br />
5 reading and one for writing . */<br />
7 if ( ( mknod ( FIFO1 , S_IFIFO | PERMS , 0) < 0)<br />
8 && ( errno != EEXIST ))<br />
9 perror (" can 't create fifo : FIFO1 ");<br />
10 if ( ( mknod ( FIFO2 , S_IFIFO | PERMS , 0) < 0)<br />
11 && ( errno != EEXIST )) {<br />
12 unlink ( FIFO1 );<br />
13 perror (" can 't create fifo : FIFO2 ");<br />
14 }<br />
15 if ( ( readfd = open ( FIFO1 , 0)) < 0)<br />
16 perror (" server : can 't open read fifo : FIFO1 ");<br />
17 if ( ( writefd = open ( FIFO2 , 1)) < 0)<br />
18 perror (" server : can 't open write fifo : FIFO2 ");<br />
20 server ( readfd , writefd );<br />
21 close ( readfd );<br />
22 close ( writefd );<br />
23 exit (0);<br />
24 }<br />
73 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
named pipe - Beispiel Client/Server cat (Server)<br />
1 server ( int readfd , int writefd ) {<br />
2 char buff [ MAXBUFF ];<br />
3 char errmesg [256];<br />
4 int n, fd; extern int errno ;<br />
6 /* Read filename from the IPC descriptor . */<br />
7 if ( (n = read ( readfd , buff , MAXBUFF )) 0)<br />
22 if ( write ( writefd , buff , n) != n)<br />
23 perror (" server : data write error ");<br />
24 if (n < 0) perror (" server : read error ");<br />
25 }<br />
26 }<br />
74 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
named pipe - Beispiel Client/Server cat (Client)<br />
client.c fifo.h<br />
1 main () {<br />
2 int readfd , writefd ;<br />
4 /* Open the FIFOs . We assume the server<br />
5 has already created them . */<br />
6 if ( ( writefd = open ( FIFO1 , 1)) < 0)<br />
7 perror (" client : can 't open write fifo : FIFO1 ");<br />
8 if ( ( readfd = open ( FIFO2 , 0)) < 0)<br />
9 perror (" client : can 't open read fifo : FIFO2 ");<br />
11 client ( readfd , writefd );<br />
12 close ( readfd );<br />
13 close ( writefd );<br />
15 /* Delete the FIFOs , now that we 're finished .<br />
16 */<br />
17 if ( unlink ( FIFO1 ) < 0)<br />
18 perror (" client : can 't unlink FIFO1 ");<br />
19 if ( unlink ( FIFO2 ) < 0)<br />
20 perror (" client : can 't unlink FIFO2 ");<br />
21 exit (0);<br />
22 }<br />
75 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Dateiverwaltung<br />
named pipe - Beispiel Client/Server cat (Client)<br />
1 client ( int readfd , int writefd ) {<br />
2 char buff [ MAXBUFF ];<br />
3 int n;<br />
5 /* Read the filename from standard input ,<br />
6 write it to the IPC descriptor . */<br />
8 printf (" File to print : ");<br />
9 if ( fgets (buff , MAXBUFF , stdin ) == NULL )<br />
10 perror (" client : filename read error ");<br />
12 n = strlen ( buff );<br />
13 if ( buff [n -1] == '\n')<br />
14 n - -; /* ignore newline from fgets () */<br />
15 if ( write ( writefd , buff , n) != n)<br />
16 perror (" client : filename write error " );;<br />
18 /* Read the data from the IPC descriptor and<br />
19 write to standard output . */<br />
20 while ( (n = read ( readfd , buff , MAXBUFF )) > 0)<br />
21 if ( write (1 , buff , n) != n) /* fd 1 = stdout */<br />
22 perror (" client : data write error ");<br />
23 if (n < 0)<br />
24 perror (" client : data read error ");<br />
25 }<br />
76 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Katalogverwaltung<br />
Katalogverwaltung<br />
• Durch den Systemaufruf link kann eine physische<br />
Datei unter mehreren Namen, auch in<br />
unterschiedlichen Verzeichnissen, erscheinen.<br />
• Eine Änderung über den Zugriff mit einem Namen<br />
wirkt sich immer auf das reale Dateiobjekt aus.<br />
• Um dies zu realisieren, wird eine Datei in Unix<br />
eindeutig identifiziert durch ihre inode-Zahl.<br />
• Ein Verzeichnis ist eine Datei, die eine Anzahl von<br />
Paaren (inode, Name im ASCII Code) enthält.<br />
• Durch einen Link wird dann ein neuer Eintrag im<br />
Verzeichnis erzeugt, der zum selben inode einen<br />
zusätzlichen Namen einführt.<br />
• Durch unlink kann man Links wieder löschen.<br />
Dabei wird nur der Eintrag aus dem Verzeichnis<br />
entfernt; die Datei bleibt erhalten.<br />
77 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Katalogverwaltung<br />
sync<br />
In Unix werden die zuletzt benutzten Blöcke in einem Cache<br />
(Zwischenspeicher) abgespeichert.<br />
• Dadurch wird ein erneutes Zugreifen schneller, da dann nicht mehr<br />
auf die Festplatte zugegriffen werden muss.<br />
• Wenn ein Fehler auftaucht, bevor die Platte wirklich beschrieben<br />
(durch write Systemaufruf) wird, so gehen Daten verloren und ein<br />
inkonsistentes Dateisystem wäre die Folge.<br />
• Deshalb wird periodisch vom Betriebssystem der sync Systemaufruf<br />
ausgeführt; er schreibt die Blöcke im Cache auf die Platte.<br />
• Beim Hochfahren eines Unix Systems wird ein Programm ”<br />
update“<br />
als Hintergrundprozess gestartet, der alle 30 Sekunden einen ”<br />
sync“<br />
Aufruf durchführt.<br />
78 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
fsync<br />
Katalogverwaltung<br />
Während ”<br />
sync“ den gesamten Cache synchronisiert, kann man mit dem<br />
Systemaufruf ”<br />
fsync“ die Blöcke einer Datei im Cache synchronisieren<br />
(z.B. bei Datenbanksystemen bei ”<br />
commit“).<br />
fsync.c<br />
1 int main ( int argc , char ** argv ) {<br />
2 int fd;<br />
3 if ( argc != 2) {<br />
4 fprintf ( stderr , " usage %s file \n", argv [0]); exit (1);<br />
5 }<br />
6 if (( fd= open ( argv [1] , O_RDWR )) < 0) {<br />
7 fprintf ( stderr , " open error \n"); exit (2);<br />
8 }<br />
10 /* manipulation of file ... */<br />
12 if ( fsync (fd) < 0) { ←<br />
13 fprintf ( stderr , " sync error \n"); exit (3);<br />
14 }<br />
15 }<br />
79 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Katalogverwaltung<br />
chdir, chroot<br />
• Um vom aktuellen Verzeichnis in ein anderes Verzeichnis zu<br />
wechseln, existiert der Systemaufruf chdir.<br />
Nachdem das aktuelle Verzeichnis mit dem Systemaufruf<br />
chdir( ”<br />
/usr/schuette/tmp“) geändert wurde, werden die<br />
folgenden Aufrufe (create, open), bei denen kein vollständiger<br />
Pfadname angegeben ist, immer im tmp-Verzeichnis Dateien<br />
angelegt, bzw. geöffnet.<br />
• Ein Systemaufruf, mit dem das Root-Verzeichnis bestimmt werden<br />
kann, ist ”<br />
chroot“.<br />
Er wird verwendet, um etwa auf Webservern virtuelle<br />
Root-Verzeichnisse für unterschiedliche Benutzergruppen definieren<br />
zu können.<br />
80 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Schutzmechanismen<br />
Schutzmechanismen<br />
In Linux besitzt eine Datei einen Besitzer und 12 Schutzbits in 4 Gruppen.<br />
Ausführungsmodi Benutzer Gruppe Rest<br />
[sgd] rwx rwx rwx<br />
s=setuid r=read<br />
g=setgpid w=write<br />
d=directory x=execute<br />
Durch den Systemaufruf chmod kann die Zugriffsmaske für eine Datei<br />
gesetzt werden. Die Maske wird dabei oktal angegeben.<br />
So bedeutet 0644 z.B.:<br />
0 = 000 Programmaufruf mit Rechten des Aufrufers<br />
6 = 110 rw-<br />
4 = 100 r–<br />
4 = 100 r–<br />
81 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Schutzmechanismen<br />
chmod<br />
Das folgende Programm setzt die Zugriffsrechte einer Datei<br />
(2.Parameter) so wie es die Maske (1. Parameter) angibt.<br />
simpleChmod.c<br />
1 # include <br />
2 main ( int argc , char ** argv ) {<br />
3 int mask ;<br />
4 if ( argc != 3) {<br />
5 fprintf ( stderr , " usage %s mask file \n", argv [0]);<br />
6 exit (1);<br />
7 }<br />
8 sscanf ( argv [1] ,"%o" ,& mask );<br />
9 if (( chmod ( argv [2] , mask )) < 0) { ←<br />
10 fprintf ( stderr , " chmod error \n");<br />
11 exit (2);<br />
12 }<br />
13 }<br />
82 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Schutzmechanismen<br />
setuid<br />
Wenn ein Programm gestartet wird, so hat es die Rechte, die der<br />
Aufrufer des Programms hat.<br />
• Deshalb kann ein Programm dem Superuser gehören (etwa das<br />
Programm rm=remove file)), aber der Aufrufer kann nur die Dateien<br />
löschen, für die er die Rechte hat.<br />
• Mit dem ”<br />
setuid“ ( ”<br />
setgid“) Bits kann man erreichen, dass ein<br />
Programm mit den Rechten des Besitzers (Gruppe)<br />
(setuid/setgid=1) abläuft und nicht wie normal mit den Rechten des<br />
Aufrufers (setuid/setgid=0).<br />
• Dies wird benötigt, um zum Beispiel die Passwort-Datei durch die<br />
Ausführung des Kommandos ”<br />
passwd“ abzuändern. Dazu hat das<br />
Kommando (=Datei) das setuid-Bit gesetzt.<br />
Beispiel: passwd<br />
83 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Schutzmechanismen<br />
access<br />
Wird ein Programm mit dem setuid-Bit ausgestattet, so kann es nicht<br />
prüfen, ob der Aufrufer die Rechte auf eine Datei hat, da das Programm<br />
selbst ja durch das setuid-Bit alle Rechte hat.<br />
Um per Programm auf die Zugriffsrechte des realen Benutzers zugreifen<br />
zu können, existiert der Systemaufruf access. Der erste Parameter von<br />
access“ ist die zu prüfende Datei, der zweite der Zugriffsmodus, der<br />
”<br />
geprüft werden soll.<br />
84 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Schutzmechanismen<br />
access<br />
Das folgende setuid-Programm prüft, ob eine Datei durch den Benutzer<br />
geöffnet werden könnte; danach wird die Datei geöffnet.<br />
access.c<br />
1 int main ( int argc , char ** argv ) {<br />
2 if ( argc != 2) {<br />
3 fprintf ( stderr , " usage %s file \n", argv [0]); exit (1);<br />
4 }<br />
5 if ( access ( argv [1] , R_OK ) < 0) {<br />
6 fprintf ( stderr , " access error \n"); exit (2);<br />
7 }<br />
8 if ( open ( argv [1] , O_RDONLY ) < 0) {<br />
9 fprintf ( stderr , " open error \n"); exit (3);<br />
10 }<br />
11 }<br />
$ ls -l access . txt<br />
-rw ------- 1 roo roo 6 24. Okt 2011 access . txt<br />
$ access access . txt<br />
access error<br />
$<br />
85 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Zeitverwaltung<br />
Zeitverwaltung<br />
In Unix ist Datum und Uhrzeit als Anzahl Sekunden, die seit dem ”<br />
Unix<br />
Urknall“ (1.1.1970 00:00:00) vergangen sind, abgelegt.<br />
Alle Datumsangaben, werden so gespeichert und erst in der Anzeige in<br />
lesbare Form gebracht. Dazu existiert der Systemaufruf ”<br />
time“, der die<br />
Sekundenanzahl liefert und Routinen, um diese Intergerzahl in ein<br />
lesbares Format zu konvertieren.<br />
now.c<br />
1 # include <br />
2 # include <br />
3 main () {<br />
4 time_t now ;<br />
5 now = time ( NULL ); /* now as no. secs since ZERO */<br />
6 printf ("%s", ctime (& now ));<br />
7 }<br />
86 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Ablaufumgebung von Prozessen<br />
Ablaufumgebung von Prozessen<br />
Um die systemweite oder Shell-spezifische Ressourcenverwendung zu<br />
kontrollieren, existiert das Kommando ulimit.<br />
$ ulimit -a<br />
core file size ( blocks , -c) 0<br />
data seg size ( kbytes , -d) unlimited<br />
scheduling priority (-e) 0<br />
file size ( blocks , -f) unlimited<br />
pending signals (-i) 16001<br />
max locked memory ( kbytes , -l) 64<br />
max memory size ( kbytes , -m) unlimited<br />
open files (-n) 1024<br />
pipe size (512 bytes , -p) 8<br />
POSIX message queues ( bytes , -q) 819200<br />
real - time priority (-r) 0<br />
stack size ( kbytes , -s) 8192<br />
cpu time ( seconds , -t) unlimited<br />
max user processes (-u) 1024<br />
virtual memory ( kbytes , -v) unlimited<br />
file locks (-x) unlimited<br />
$<br />
87 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
rlimit<br />
Ablaufumgebung von Prozessen<br />
Mit den Systemaufrufen setrlimit und getrlimit kann man die Ressourcen<br />
eines Prozesses beschränken.<br />
getrlimit-cpu.c<br />
1 # include <br />
2 # include <br />
3 # include < unistd .h><br />
5 int main () {<br />
6 struct rlimit rl; /* defines and stores the limits */<br />
8 getrlimit ( RLIMIT_CPU , &rl ); /* obtain the current limits . */<br />
9 rl. rlim_cur = 1; /* Set a CPU limit of 1 second . */<br />
10 setrlimit ( RLIMIT_CPU , &rl );<br />
12 /* Do busy work . */<br />
13 while (1);<br />
15 return 0;<br />
16 }<br />
88 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Systemaufrufe<br />
Ablaufumgebung von Prozessen<br />
fork Bomben<br />
Ein Beispiel für nichtlimitierte Ressourcen-Verwendung sind fork-Bomben:<br />
Programme, die eine Explosion von Prozessen verursachen 1 :<br />
forkbomb.c<br />
1 # include < unistd .h><br />
2 int main ( void ){<br />
3 while (1)<br />
4 fork ();<br />
5 return 0;<br />
6 }<br />
forkbomb.java<br />
1 public class forkbomb implements Runnable {<br />
2 public static void main ( String [] args ) {<br />
3 new forkbomb (). run ();<br />
4 }<br />
5 public void run () {<br />
6 new Thread ( this ). start ();<br />
7 new Thread ( this ). start ();<br />
8 }<br />
9 }<br />
1 Verhindern durch Eintrag in /etc/security/limits.conf<br />
@users soft nproc 128<br />
@users hard nproc 50 89 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Betriebssystemstrukturen<br />
Betriebssystemstrukturen<br />
Hier sollen verschiedene Ansätze für den inneren Aufbau eines<br />
Betriebssystems gezeigt werden.<br />
1 Überblick<br />
2 Was ist ein Betriebssystem<br />
3 Historische Betrachtung<br />
4 Grundlegende Konzepte<br />
5 Systemaufrufe<br />
6 Betriebssystemstrukturen<br />
Monolithische Systeme<br />
Schichtenmodell<br />
Virtuelle Maschinen<br />
90 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Betriebssystemstrukturen<br />
Monolithische Systeme<br />
Monolithische Systeme<br />
Monolithische Systeme sind als Menge von Prozeduren realisiert.<br />
• Jede Prozedur kann jede andere aufrufen.<br />
• Eine Struktur ist nicht erkennbar.<br />
• Das Betriebssystem wird generiert, in dem alle Prozeduren übersetzt<br />
werden und zu einem ein- zigen grossen Objekt gebunden werden.<br />
Systemaufrufe werden dabei wie folgt abgebildet:<br />
• Bei einem Systemaufruf werden die Parameter an eine wohldefinierte<br />
Stelle (spezielle Register oder Stack) geschrieben, dann wird der<br />
Aufruf durch einen speziellen Unterbrechungsbefehl (Kernel-Aufruf,<br />
Supervisor-Aufruf) zur Ausführung gebracht. Durch diese Instruktion<br />
wird die Hardware vom User-Modus in den Kernel-Modus<br />
umgeschaltet und die Kontrolle übernimmt das Betriebssystem.<br />
• Das Betriebssystem prüft die Parameter des Aufrufs, bestimmt<br />
welcher Systemaufruf durchgeführt werden soll.<br />
• Der Systemaufruf wird vom Betriebssystem ausgeführt und das<br />
Ergebnis wird wieder in speziellen Registern oder auf dem Stack<br />
abgelegt.<br />
• Dann gibt das BS die Kontrolle an das Benutzerprogramm zurück.<br />
91 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Betriebssystemstrukturen<br />
Monolithische Systeme<br />
Systemaufrufe<br />
• Diese Vorgehensweise wird von<br />
den meisten CPUs unterstützt.<br />
• Eine CPU kann in zwei Modi<br />
gefahren“ werden: im<br />
”<br />
Kernel-Modus sind alle<br />
Instruktionen zulässig, im<br />
User-Modus nur eingeschränkte<br />
Instruktionen, hauptsächlich<br />
E/A Befehle.<br />
92 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Betriebssystemstrukturen<br />
Schichtenmodell<br />
Schichtenmodell<br />
Ein Betriebssystem dieser Kategorie ist in Schichten eingeteilt, wobei eine<br />
Schicht auf der Basis der darunter liegenden Schicht konstruiert ist. Das<br />
klassische Beispiel ist das von Dijkstra 1968 entwickelte System THE<br />
(Technische Hogeschool Eindhoven).<br />
• Schicht 0 war für das Prozess-Scheduling verantwortlich, dadurch<br />
brauchte keine der anderen Schichten mehr zu berücksichtigen, dass<br />
es mehrere Prozesse auf der Maschine gibt.<br />
• Schicht 1 belegte für einen Prozess Platz im Speicher; war dort<br />
kein Platz wurden Speicherseiten auf eine Trommel verlagert;<br />
dadurch brauchte kein Programm der Schichten 2 und grösser sich<br />
um Speicherverwaltung zu kümmern.<br />
• Schicht 2 verwaltete die Bedienkonsole und die Kommunikation<br />
zwischen Prozessen; damit hatte ab der Schicht 3 jedes Programm<br />
seine eigene virtuelle Konsole.<br />
• Schicht 3 war für die Verwaltung der E/A-Geräte zuständig;<br />
damit konnte jedes Programm ab Ebene 4 mit logischen<br />
E/A-Geräten arbeiten.<br />
93 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Betriebssystemstrukturen<br />
Schichtenmodell<br />
Somit hatten Benutzerprogramme eine virtuelle Sicht auf die<br />
Hardware.<br />
Diese Konzeption war eine Entwurfentscheidung.<br />
Das gesamte Betriebssystem war aber immer noch ein aus mehreren<br />
Modulen zusammengefasstes grosses Programm.<br />
94 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Betriebssystemstrukturen<br />
Virtuelle Maschinen<br />
Virtuelle Maschinen<br />
Das IBM System VM/370 stellt einen Monitor zur Verfügung, der auf die<br />
nackte“ Hardware zugreift und Kopien der realen Maschine als virtuelle<br />
”<br />
Maschine für darüber liegende Schichten bereitstellt.<br />
• Auf jeder solcher virtuellen Maschinen<br />
kann ein eigenes BS ablaufen.<br />
• Wenn ein Benutzerprogramm einen<br />
Systemaufruf absetzt, wird er vom BS<br />
der virtuellen 370 behandelt. Dort gibt<br />
dieses BS den Aufruf nicht an die reale<br />
Hardware weiter, sondern an den<br />
Monitor, der dann den Zugriff auf die<br />
Hardware erledigt.<br />
Diese Architektur ist noch Heute Basis der ”<br />
grossen“ IBM<br />
<strong>Betriebssysteme</strong>.<br />
95 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Betriebssystemstrukturen<br />
Auftraggeber-Auftragnehmer Modell<br />
Auftraggeber-Auftragnehmer Modell<br />
In modernen <strong>Betriebssysteme</strong>n verwendet man das<br />
Auftraggeber-Auftragnehmer Modell (Client- Server Modell).<br />
• Ein Client-Prozess beauftragt einen Server,<br />
einen Dienst auszuführen, indem er dem<br />
Server eine Nachricht sendet.<br />
• Der Server antwortet mit der Erbringung des<br />
Dienstes.<br />
• Sowohl Client als auch Server laufen im<br />
User-Modus.<br />
• Dadurch hat der Betriebssystemkern nur noch<br />
elementare Aufgaben, wie z.B.<br />
Interprozesskommunikation.<br />
Der schlanke Kern und die unabhängigen Server bewirken, dass gut<br />
skalierbare und fehlertolerante <strong>Betriebssysteme</strong> realisierbar sind: ein<br />
Absturz des Datei-Servers führt nicht zum Absturz der davon nicht<br />
betroffenen Programme.<br />
96 / 97
<strong>Betriebssysteme</strong> - <strong>Einleitung</strong> ... alois.schuette@h-da.de<br />
Betriebssystemstrukturen<br />
Auftraggeber-Auftragnehmer Modell<br />
Verteilte <strong>Betriebssysteme</strong><br />
Weiterhin ist damit die Voraussetzung für verteilte <strong>Betriebssysteme</strong><br />
gesetzt:<br />
• im Kern wird Netzfunktionalität integriert und<br />
• die Server können auf beliebigen Rechnern im Netz ablaufen.<br />
97 / 97