22.02.2014 Aufrufe

Betriebssysteme - Einleitung

Betriebssysteme - Einleitung

Betriebssysteme - Einleitung

MEHR ANZEIGEN
WENIGER ANZEIGEN

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

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!