This commit is contained in:
Robert Jeutter 2021-03-01 16:39:46 +01:00
parent 0453269f24
commit 5b651516d2
35 changed files with 1013 additions and 14 deletions

View File

@ -0,0 +1,16 @@
all: p1 p2 p3 p4
p1:
cc -o p1 p1.c
p2:
cc -o p2 p2.c
p3:
cc -o p3 p3.c
p4:
cc -o p4 p4.c
clean:
rm -f p1 p2 p3 p4

View File

@ -0,0 +1,11 @@
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("%d: code before forking - executed once only\n", getpid());
fork();
printf("%d: code after forking - executed by each process\n", getpid());
}

View File

@ -0,0 +1,20 @@
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
if (fork() == 0)
{ // child proc
printf("%d: child created by parent %d; executing 'p4'...\n", getpid(), getppid());
execl("p4", "p4", NULL);
printf("system call execl(): no success\n");
}
else
{ // parent proc
printf("%d: parent process\n", getpid());
}
wait(NULL);
}

View File

@ -0,0 +1,22 @@
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
if (fork() == 0)
{ // child proc
printf("%d: child created by parent %d; executing 'p4'...\n", getpid(), getppid());
execl("p4", "p4", NULL);
}
else
{ // parent proc
printf("%d: parent process\n", getpid());
}
fork();
printf("%d: terminating\n", getpid());
wait(NULL);
}

View File

@ -0,0 +1,7 @@
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("%d: p4 running\n", getpid());
}

View File

@ -0,0 +1,13 @@
all: p5 p6 p7
p5:
gcc -o p5 p5.c
p6:
gcc -o p6 p6.c
p7:
gcc -o p7 p7.c
clean:
rm -f p5 p6 p7

View File

@ -0,0 +1,23 @@
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int pid;
pid = fork();
if (pid == 0)
{ // child proc looping endlessly
printf("%d: child ...\n", getpid());
for(;10;);
}
else
{ // parent proc
printf("%d: parent; child pid: %d \n", getpid(), pid);
wait(NULL); // for child termination
printf("%d: parent after wait ... terminating\n", getpid());
}
}

View File

@ -0,0 +1,7 @@
#include <stdio.h>
int main()
{
printf("p6: calling getchar()\n");
getchar();
}

View File

@ -0,0 +1,8 @@
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("p7: calling sleep()\n");
sleep(100);
}

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1,10 @@
all: client server
client:
gcc -o msgclient msgclient.c
server:
gcc -o msgserver msgserver.c
clean:
rm -f msgclient msgserver

View File

@ -0,0 +1,68 @@
/*
* Interprozesskommunikation über Message Queues - msgclient.c
*
* Funktionsweise: Der Client schickt eine Losung an einen Server. Falls die
* Losung die vom Server erwartete war, gibt letzterer dem Client sein
* Geheimnis preis.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#define MSGSIZE 100
key_t key = 1337;
int msgflg = 0666;
// Typdefinition für Message Queue (muss explizit geschehen, da noch nicht
// vorhanden)
struct message {
long msgType;
char msgText[MSGSIZE];
};
// char CODEWORD[] = "<Losung>";
char CODEWORD[] = "Losung";
int main() {
struct message msg_snd;
struct message msg_rcv;
// Verbinden mit Message-Queue des Servers
printf("Client: Verbinde mich mit Message Queue.\n");
int id_q = msgget(key, msgflg);
if (id_q >= 0) {
printf(" OK.\n\n");
}
// Absenden des Codeworts
printf("Client: Sende Codewort an den Server.\n");
msg_snd.msgType = 5;
strcpy(msg_snd.msgText, CODEWORD);
int result_snd = msgsnd(id_q, &msg_snd, MSGSIZE, 0);
if (result_snd >= 0) {
printf(" OK.\n\n");
}
// Empfangen der Antwort
printf("Client: Warte auf Geheimnis.\n");
// Abholen des Geheimnisses aus der Message-Queue
int result_rcv = msgrcv(id_q, &msg_rcv, MSGSIZE, 6, 0);
if (result_rcv >= 0) {
printf(" Geheimnis erhalten.\n\n");
}
// Ausgabe des Geheimnisses
printf("Client: Das Geheimnis lautet: '%s'.\n\n", msg_rcv.msgText);
printf("Client: Ende.\n");
return 0;
}

View File

@ -0,0 +1,76 @@
/*
* Interprozesskommunikation über Message Queues - msgcserver.c
*
* Funktionsweise: Der Server erzeugt eine Message Queue und erwartet über diese
* die Losung eines Client. Falls die Losung mit der vom Server erwarteten
* übereinstimmt, teilt der Server dem Client sein Geheimnis mit.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <unistd.h>
#define MSGSIZE 100
#define SECRET "GGG ..."
key_t key = 1337;
int msgflg = IPC_CREAT | 0666; // Vergibt lese und schreibrechte an alle Nutzer, user group others
struct message {
long msgType;
char msgText[MSGSIZE];
};
int main() {
struct message msg_rcv;
struct message msg_snd;
// Erzeugen einer Message Queue
printf("Server: Erzeuge Message Queue.\n");
int id_q = msgget(key, msgflg);
if (id_q >= 0) {
printf(" OK.\n\n");
}
// Losung empfangen
printf("Server: Warte auf Losung.\n");
int result_rcv = msgrcv(id_q, &msg_rcv, MSGSIZE, 5, 0); // hier war
if (result_rcv >= 0) {
printf(" Losung empfangen.\n\n");
}
// Vergleich
if (strncmp(msg_rcv.msgText, "Losung", strlen("Losung")) == 0) {
printf("Server: Losung ist korrekt. Sende jetzt das Geheimnis.\n");
// Senden des Geheimnisses
msg_snd.msgType = 5;
strcpy(msg_snd.msgText, SECRET);
int result_snd = msgsnd(id_q, &msg_snd, MSGSIZE, 0);
if (result_snd >= 0) {
printf(" OK.\n\n");
}
}
else {
printf("Server: Das war falsch.\n");
}
sleep(1);
int result_ctl = msgctl(id_q, IPC_RMID, 0);
if (result_ctl >= 0) {
printf("Server: Message Queue gelöscht. Ende.\n\n");
return 0; // alles OK
}
else {
printf("Server: Message Queue löschen fehlgeschlagen. Ende.\n\n");
return 10; // Fehler
}
}

View File

@ -0,0 +1,10 @@
all: client server
client:
gcc -o shmclient shmclient.c
server:
gcc -o shmserver shmserver.c
clean:
rm -f shmclient shmserver

View File

@ -0,0 +1,76 @@
/*
* Interprozesskommunikation über Shared Memory - shmclient.c
*
* Funktionsweise: Das Programm kommuniziert mit einem Server mittels Shared
* Memory und fragt mit einem Passwortsatz nach einem Geheimnis. Der Server
* hinterlegt die Antwort ebenfalls im Shared-Memory-Bereich. Dabei müssen beide
* Kommunikationspartner jeweils mit Semaphoren den Ablauf ihrer Kommunikation
* steuern.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>
#define SHMSIZE 256
#define CODEWORD "Losung"
// statisch vereinbarte Schlüssel
const int shmKey = 1337; // Schlüssel zum Benutzen des Shared Memory
const int readSemaphoreKey = 100; // Schlüssel zum Benutzen der Semaphore
const int writeSemaphoreKey = 101; // Schlüssel zum Benutzen der Semaphore
const int textLen = 128;
int main(int arc, char** argv) {
// dynamisch vergebene ID's
// Benutzung der Semaphore und des Shared Memory vorbereiten
int shmID = shmget(shmKey, 2 * textLen, 0);
int readSemID = semget(readSemaphoreKey, 1, 0666);
int writeSemID = semget(writeSemaphoreKey, 1, 0666);
// Anhängen des Shared-Memory-Segments
printf("Client: Hänge Shared-Memory-Segment an.\n\n");
char *shm_ptr;
shm_ptr = shmat(shmID, 0, 0);
char *shm_ptr_alt;
shm_ptr_alt = shm_ptr;
// Absenden der Losung
printf("Client: Sende meine Losung.\n\n");
for (int i = 0; i <= strlen(CODEWORD); i++) {
*shm_ptr++ = CODEWORD[i];
}
*shm_ptr = '\0';
// Signal an den Server, dass die Daten jetzt lesbar sind
struct sembuf semaphoreOperation;
semaphoreOperation.sem_num = 0;
semaphoreOperation.sem_op = 1;
semaphoreOperation.sem_flg = SEM_UNDO;
semop(writeSemID, &semaphoreOperation, 1);
// Warten auf die Antwort (das Signal vom Server)
semaphoreOperation.sem_num = 0;
semaphoreOperation.sem_op = -1;
semaphoreOperation.sem_flg = SEM_UNDO;
semop(readSemID, &semaphoreOperation, 1);
// Abolen des Geheimnisses und direkte Ausgabe
char *sbuf;
sbuf = shmat(shmID,0,0);
printf("Client: Das Geheimnis lautet: '%s'.\n\n", sbuf);
printf("Client: Ende.\n\n");
// Ausblenden des Shared Memory
shmdt(&shmID);
return 0;
}

View File

@ -0,0 +1,90 @@
/*
* Interprozesskommunikation über Shared Memory - shmserver.c
*
* Funktionsweise: Das Programm kommuniziert mit einem Server mittels Shared
* Memory und fragt mit einem Passwortsatz nach einem Geheimnis. Der Server
* hinterlegt die Antwort ebenfalls im Shared-Memory-Bereich. Dabei müssen beide
* Kommunikationspartner jeweils mit Semaphoren den Ablauf ihrer Kommunikation
* steuern.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>
// SHM
#define SHMKEY 1337
#define SHMSIZE 256
// SEM
#define RSEMKEY 100
#define SSEMKEY 101
#define SECRET "xxx..."
#define Losung "Losung"
struct sembuf sb[1];
int rsem_id, ssem_id;
char *sbuf;
int main() {
// Erzeugen eines Shared-Memory-Segments
printf("Server: Erzeuge Shared-Memory-Segment.\n");
int id_shm = shmget(SHMKEY, SHMSIZE, IPC_CREAT|0666);
if (id_shm >= 0) {
printf(" OK.\n\n");
}
// Anhängen des Shared-Memory-Segments
printf("Server: Hänge Shared-Memory-Segment an.\n");
sbuf = shmat(id_shm, 0, 0);
if (sbuf >= 0) {
printf(" OK.\n\n");
}
// Erzeugen Semaphore
ssem_id = semget(SSEMKEY, 1, IPC_CREAT|0666);
rsem_id = semget(RSEMKEY, 1, IPC_CREAT|0666);
semctl(ssem_id, 0, SETVAL, 0);
semctl(rsem_id, 0, SETVAL, 0);
printf("Server: Warte auf Semaphor.\n\n");
sb[0].sem_num = 0;
sb[0].sem_op = -1;
sb[0].sem_flg = SEM_UNDO;
semop(ssem_id, sb, 1);
// Warten bis der Client geschrieben hat
// Synchronisation
printf("Server: Die Losung des Client lautet: '%s'.\n", sbuf);
if (strncmp(sbuf, Losung, sizeof(Losung)) == 0 ) {
printf(" Das war richtig.\n\n");
printf("Server: Schreibe das Geheimnis in den Shared Memory.\n\n");
snprintf(sbuf, SHMSIZE, "%s", SECRET);
}
else {
printf(" Das war falsch.\n");
snprintf(sbuf, 128, "Pech gehabt.");
}
printf("Server: Signalisiere, dass Client nun lesen kann.\n\n");
sb[0].sem_num = 0;
sb[0].sem_op = 1;
sb[0].sem_flg = SEM_UNDO;
semop(rsem_id, sb, 1);
printf("Server: Ende.\n\n");
shmdt(&id_shm);
return 0;
}

View File

@ -0,0 +1,10 @@
all: client server
client:
gcc -o pipeclient pipeclient.c
server:
gcc -o pipeserver pipeserver.c
clean:
rm -f pipeclient pipeserver

View File

@ -0,0 +1,65 @@
/*
* Interprozesskommunikation über Named Pipes (FIFOs) - pipeclient.c
*
* Funktionsweise: Dieses Client-Programm meldet sich über ein benanntes Pipe
* bei einem geeigneten Server und fragt mit einer Losung (Passwortsatz) nach
* einem Geheimnis. Der Server kontrolliert die Losung. Ist diese korrekt,
* antwortet der Server mit dem Geheimnis über die gleiche Pipe, welches auf
* geeignete Weise vom Client gelesen werden kann.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
//char CODEWORD[] = "<Schluessel>";
char CODEWORD[] = "<Schluessel>";
char buffer[8];
int main() {
// Öffnen der Named Pipe nur zum Schreiben
printf("Client: Öffne Pipe nur zum Schreiben.\n");
int id_p = open("P1", O_WRONLY); // const char *path, int oflag … / returnt einen Filedeskriptor
if (id_p >= 0) {
printf(" OK.\n\n");
}
// Absenden des Codeworts an Server
printf("Client: Übergebe Codewort an den Server.\n");
int result_write = write(id_p, CODEWORD, strlen(CODEWORD));
if (result_write > 0) {
printf(" OK.\n\n");
}
close(id_p);
// Empfangen der Antwort
printf("Client: Empfange Geheimnis.\n\n");
// Öffnen der Pipe zum Lesen
printf("Client: Öffne Pipe nur zum Lesen.\n");
id_p = open("P1", O_RDONLY);
if (id_p >= 0) {
printf(" OK.\n");
}
// Lesen des Geheimnisses aus der Pipe
int result_read = read(id_p, buffer, sizeof(buffer));
if (result_read > 0) {
printf(" Geheimnis erhalten.\n\n");
}
// Ausgabe des Geheimnisses
printf("Client: Das Geheimnis lautet: '%s'.\n\n", buffer);
printf("Client: Ende.\n");
return 0;
}

View File

@ -0,0 +1,83 @@
/*
* Interprozesskommunikation über Named Pipes (FIFOs) - pipeserver.c
*
* Funktionsweise: Dieses Server-Programm erwartet von seinem Client über ein
* von ihm angelegtes benanntes Pipe eine Losung (Passwortsatz). Nach
* (erfolgreicher) Überpruefung verrät der Server ein Geheimnis. Anschließend
* löscht er das Pipe wieder.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
char buffer[30];
#define SECRET "xxx ..."
int main() {
// Erzeugen der Named Pipe
printf("Server: Erzeuge Named Pipe (FIFO).\n");
int rw = mkfifo("P1", 0666); // erstellt PIPE bei const char *pathname hier P1, mit mode mode_t mode = 0_RDWR /erlaubt lesen und schreiben
if (rw >= 0) {
printf(" OK.\n\n");
}
// Öffnen der Pipe zum Lesen
printf("Server: Öffne Pipe zum Lesen.\n");
int id_p = open("P1", O_RDONLY);
if (id_p >= 0) {
printf(" OK.\n\n");
}
// Losung empfangen
printf("Server: Warte auf Losung.\n");
int result_read = read(id_p, buffer, sizeof(buffer));
if (result_read > 0) {
printf(" Losung empfangen.\n\n");
printf("Received: %s\n", buffer);
}
// Vergleich
if (strncmp(buffer, "<Schluessel>", strlen("<Schluessel>")) == 0) { // int strncmp(const char *str1, const char *str2, size_t n)
printf("Server: Losung ist korrekt. Übergebe jetzt das Geheimnis.\n\n");
// Öffnen der Pipe zum Schreiben
printf("Server: Öffne dazu Pipe nur zum Schreiben.\n");
close(id_p);
id_p = open("P1", O_WRONLY);
if (id_p >= 0) {
printf(" OK.\n");
}
// Übergeben des Geheimnisses
int result_write = write(id_p, SECRET, strlen(SECRET));
if (result_write > 0) {
printf(" OK.\n\n");
}
}
else {
printf("Server: Das war falsch.\n");
}
sleep(5);
// Vernichte Pipe
unlink("P1");
printf("Server: Ende.\n");
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -0,0 +1,7 @@
all: syscall
syscall: syscall.c
$(CC) -o syscall syscall.c
clean:
rm -f syscall syscall

View File

@ -0,0 +1,2 @@
#!/bin/sh
make --silent ; ./syscall ; echo "[returned $?]"

Binary file not shown.

View File

@ -0,0 +1,105 @@
/* syscall.c
*
* Call OS functions without using libc. The example `my_print` could be done in
* different flavors:
*
* **my_print_64:**
* Using the SYSCALL instruction (successor to SYSENTER). Should work for both
* IA-64 and AMD64 CPUs, can be ported to `my_print_trap` (see there).
* Register usage:
*
* - RAX: pass syscall number, hold return value from syscall execution
* - RDI: syscall argument 1
* - RSI: syscall argument 2
* - RDX: syscall argument 3
*
* **my_print_trap:**
* Using the trap interrupt to switch to kernel mode. Slower, but
* architecturally neutral. Register usage:
*
* - EAX: pass syscall number, hold return value from syscall execution
* - EBX: syscall argument 1
* - ECX: syscall argument 2
* - EDX: syscall argument 3
*/
#include <asm/unistd.h> /* compile with -m32 for 32-bit syscall numbers,
without for 64-bit syscall numbers. */
/* simple inline assembler (asm) requires global symbols */
#define __NR_exit 60
// text buffer pointer
char *my_print_text;
// text buffer length, print return value
int my_print_len, my_print_ret;
// write() syscall number
int call_write;
int exitnum;
int my_print_64(char *text) {
my_print_text = text;
/* strlen(my_print_text) manually */
for (my_print_len = 0; my_print_text[my_print_len]; ++my_print_len);
/* system call signature:
* ssize_t write(int fd, const void *buf, size_t count);
*
* write() system call number is defined by __NR_write
*/
call_write = __NR_write;
/* stdout is file descriptor no. 1 */
asm("mov call_write, %rax"); /* arg 0 (rax): syscall number */
asm("mov $1, %rdi"); /* arg 1 (rdi): file descriptor */
asm("mov my_print_text, %rsi"); /* arg 2 (rsi): buffer */
asm("mov my_print_len, %rdx"); /* arg 3 (rdx): length */
asm("syscall"); /* SYSCALL instruction */
asm("mov %rax, my_print_ret"); /* save return code (rax) */
return my_print_ret;
}
int my_print_trap(char *text) {
/* system call signature: see my_print_64 */
/* TODO */
return 7;
}
/* simple inline assembler (asm) requires global symbols */
// exit return value
int my_exit_status;
void my_exit_64(int status) {
my_exit_status = status;
/* system call signature:
* void exit(int status);
*
* exit() system call number is defined by __NR_exit
*/
/* TODO */
// Syscall
//exitnum = __NR_exit;
//asm("mov $60, %rax"); // verwende den exitnum / 60 Syscall
//asm("mov my_exit_status, %rdi"); //status = myexitstatus oder 0
//asm("syscall");
//TRAP
asm("mov $1, %eax");
asm("mov my_exit_status, %ebx");
asm("int $0x80");
}
int main(void) {
my_print_64("Hello World!\n");
my_exit_64(42);
/* never come here, if my_exit_64 works */
return 6;
}

View File

@ -0,0 +1,13 @@
CFLAGS = -g
all: daemon
clean:
rm -f helpers.o daemon.o daemon
helpers.o: helpers.c helpers.h
daemon.o: daemon.c helpers.h
daemon: daemon.o helpers.o
$(CC) -o daemon daemon.o helpers.o

View File

@ -0,0 +1,54 @@
#include <stdio.h>
#include <signal.h>
#include "helpers.h"
void got_signal(int s) {
/*
* For demonstration purposes only!
*
* In real-world applications avoid the execution of complex
* tasks in signal handlers, this cries for race conditions and
* exploits.
*
* https://web.archive.org/web/20070204064240/http://www.bindview.com:80/
% Services/Razor/Papers/2001/signals.cfm
*/
static int count = 2;
fprintf(stderr, "Caught Ctrl-C, press %i more time%s to really"
" quit.\n", count, count == 1 ? "" : "s");
if (count-- > 1) {
/* Catch subsequent SIGINTs */
signal(SIGINT, got_signal);
}
else {
/* Don't catch SIGINT anymore */
signal(SIGINT, SIG_DFL);
}
}
int main(void) {
/*
* Initialization...
*/
void load_cfg(int x) // Wrapper um die inkompatibilität durch die fehlerhafte methodendeklaration zu beheben
{
load_config();
}
load_config();
signal(SIGHUP, load_cfg);
signal(SIGINT, got_signal);
signal(SIGKILL, SIG_IGN); // Unaufhaltsam, da SIGKILL sich ja eben genau nicht um das Programm kümmern soll, sondern einfach immer töten soll
signal(SIGTERM, SIG_IGN); // Aufhaltbar
/*
* Do some work.
*/
main_loop();
return 0;
}

Binary file not shown.

View File

@ -0,0 +1,103 @@
/*
* This is just some code behind the curtain.
* You really don't need to touch this.
*
*/
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "helpers.h"
int do_quit = 0;
int do_reload = 0;
int load_config(void) {
static char firsttime_message[] = "Loading";
static char subsequent_message[] = "Reloading";
static char *message = firsttime_message;
fprintf(stderr, "%s configuration... ", message);
fprintf(stderr, "done.\n");
message = subsequent_message;
do_reload = 0;
return 1;
}
int main_loop(void) {
fd_set rfds;
struct timeval tv;
int retval;
char buf[1024];
ssize_t len;
#if 0
/* fork() to the background */
if(fork()) return 0;
#endif
fprintf(stderr, "I'm just a simple daemon, my PID is: %i\n",
getpid());
while (!do_quit) {
/*
* Let's just do our really simple job:
* Copy stdin to stdout (just another cat(1)).
*/
if (do_reload) {
load_config();
}
/* Watch stdin (fd 0) to see when it has input. */
FD_ZERO(&rfds);
FD_SET(0, &rfds);
/* Wait up to five seconds. */
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Don't rely on the value of tv now! */
if (retval == -1) {
if (errno == EINTR) {
continue;
}
perror("select()");
continue;
}
if (retval == 0) {
/* fprintf(stderr, "Timeout reached.\n"); */
continue;
}
if (!FD_ISSET(0, &rfds)) {
fprintf(stderr, "BUG: Empty read-set, this should"
" never happen. Never ever. Really! :-)\n");
continue;
}
len = read(0, buf, sizeof(buf) - 1);
if (len < 0) {
perror("read()");
continue;
}
buf[len] = 0;
printf("%s", buf);
if (len == 0) {
do_quit = 1;
}
}
return 1;
}

View File

@ -0,0 +1,7 @@
#ifndef _HELPERS_H
#define _HELPERS_H
int load_config(void); /* load/reload our daemon's configuration */
int main_loop(void); /* our daemon's main task */
#endif /* _HELPERS_H */

Binary file not shown.

View File

@ -0,0 +1,19 @@
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p;
p = malloc(sizeof(int));
if(p != NULL) {
*p=99;
printf("Allokation erfolgreich ... \n");
}
else {
printf("Kein virtueller RAM mehr verfügbar ...\n");
return EXIT_FAILURE;
}
free(p);
printf("Speicher nun erfolgreich freigegeben ");
return EXIT_SUCCESS;
}

Binary file not shown.

View File

@ -86,7 +86,7 @@
\begin{document}
\begin{myboxii}[Disclaimer]
Die Übungen die hier gezeigt werden stammen aus der Vorlesung \textit{Betriebssysteme}! Für die Richtigkeit der Lösungen wird keine Gewähr gegeben.
Die Übungen die hier gezeigt werden stammen aus der Vorlesung \textit{Betriebssysteme}! Für die Richtigkeit der Lösungen wird keine Gewähr gegeben. Anlagen sind im Ordner \textit{Assets/Betriebssysteme\_uebung/} zu finden.
\end{myboxii}
@ -508,7 +508,7 @@ Bei aufruf von sleep() wird der Prozess sofort alle CPU Ressourcen freigeben, al
Zwei Prozesse definieren (0,4,4,10) und (2,5,5,8) (Arrival,Burst,Priority,Deadline). Mit RR können die Deadlines nicht eingehalten werden.
\begin{center}
\includegraphics[width=0.8\linewidth]{Assets/Betriebssystem_uebung/u3_a1.png}
\includegraphics[width=0.8\linewidth]{Assets/Betriebssysteme_uebung/u3_a1.png}
\end{center}
%##########################################
@ -606,7 +606,7 @@ Siehe auch Linux Manpages
\end{itemize}
\end{description*}
\begin{center}
\includegraphics[width=0.8\linewidth]{Assets/Betriebssystem_uebung/u4_a1.png}
\includegraphics[width=0.8\linewidth]{Assets/Betriebssysteme_uebung/u4_a1.png}
\end{center}
%##########################################
\subsection{Aufgabe 1: Das Problem des schlafenden Barbiers}
@ -662,7 +662,7 @@ Idee: Es gibt vier Ressourcen um die sich die Kunden und der Barbier streiten. D
}
}
\end{lstlisting}
%##########################################
\subsection{Aufgabe 2: Das Achterbahnproblem}
@ -678,8 +678,8 @@ Idee: Es gibt vier Ressourcen um die sich die Kunden und der Barbier streiten. D
\vspace{10mm}
\begin{itemize}
\item Autos: warten auf Passagiere (bis voll)
\item Passagiere: warten auf Start/Ende
\item Autos: warten auf Passagiere (bis voll)
\item Passagiere: warten auf Start/Ende
\end{itemize}
\begin{lstlisting}
@ -787,9 +787,9 @@ Prozess Car()
\vspace{10mm}
\begin{itemize}
\item Lieferant: wartet auf Wartungsauftrag
\item Kunden: wartet auf Kaffee
\item Maschine: Portionen = 0? Wartet auf Wartung : warten auf Bestellung \& Bezahlung
\item Lieferant: wartet auf Wartungsauftrag
\item Kunden: wartet auf Kaffee
\item Maschine: Portionen = 0? Wartet auf Wartung : warten auf Bestellung \& Bezahlung
\end{itemize}
\begin{lstlisting}
@ -911,26 +911,76 @@ ServiceMan{
Prinzipiell stehen dafür nachrichtenbasierte und speicherbasierte Kommunikationsmechanismen zur Verfügung. Für welche der existierenden Mechanismen würden sie sich entscheiden, um einerseits Kontrollinformationen und andererseits Mediendatenströme auszutauschen? Begründen Sie Ihre Antwort.\\
Hinweis: Klären Sie zuerst, was die prinzipiellen Vor- und Nachteile dieser beiden Kommunikationsvarianten sind. Betrachten Sie anschließend die Kommunikationsmuster und Anforderungen der beiden Klassen (Kontroll- und Multimediadaten), bevor Sie eine Empfehlung geben.
}
\begin{itemize}
\item \textbf{Message Passing vs. Shared Memory} Shared Memory hat die wünschenswerte Eigenschaft, dass die gesamte Kommunikation über implizites Laden und Speichern in einem globalen Adressraum erfolgt.
Eine weitere grundlegende Eigenschaft von Shared Memory ist, dass Synchronisation und Kommunikation getrennt sind. Zusätzlich zu den Lade- und Speicheroperationen müssen spezielle Synchronisationsoperationen (Mechanismen) verwendet werden, um zu erkennen, wann Daten produziert und/oder konsumiert wurden.
Im Gegensatz dazu wird beim Message Passing ein explizites Kommunikationsmodell verwendet. Explizite Nachrichten werden zwischen den Prozessen ausgetauscht.
Synchronisation und Kommunikation sind im Message Passing vereint. Die Erzeugung von entfernten, asynchronen Ereignissen ist ein integraler Bestandteil des Message-Passing-Kommunikationsmodells.
Es ist jedoch wichtig, darauf hinzuweisen, dass Shared-Memory- und Message-Passing-Kommunikationsmodelle universell sind, d.h., es ist möglich, das eine zu verwenden, um das andere zu simulieren.
Es ist jedoch zu beobachten, dass es einfacher ist, Shared Memory mit Message Passing zu simulieren als umgekehrt.
Das liegt im Wesentlichen an der asynchronen Ereignissemantik von Message Passing im Vergleich zur Polling-Semantik des Shared Memory.
Das Shared-Memory-Kommunikationsmodell ermöglicht es dem Programmierer, sich auf die mit der Parallelität verbundenen Probleme zu konzentrieren, indem er von den Details der Interprozessorkommunikation entlastet wird.
In diesem Sinne stellt das Shared-Memory-Kommunikationsmodell eine geradlinige Erweiterung des Uniprozessor-Programmierparadigmas dar. Darüber hinaus ist die Shared-Memory-Semantik unabhängig vom physikalischen Speicherort und daher offen für die dynamische Optimierung, die das zugrunde liegende Betriebssystem bietet.
Auf der anderen Seite ist das Shared-Memory-Kommunikationsmodell im Wesentlichen eine Polling-Schnittstelle. Dies ist ein Nachteil, was die Synchronisation betrifft.
Die Nachrichtenweitergabe kann als ein interruptgesteuertes Kommunikationsmodell charakterisiert werden. Bei der Nachrichtenübermittlung enthalten die Nachrichten sowohl Daten als auch Synchronisation in einer einzigen Einheit. Als solches eignet sich das Message-Passing-Kommunikationsmodell für Betriebssystemaktivitäten, bei denen die Kommunikationsmuster im Voraus explizit bekannt sind, z. B. E/A, Interprozessor-Interrupts und Task- und Datenmigration.
Auf der anderen Seite leidet das Message Passing unter der Notwendigkeit von Marshaling-Kosten, d. h. den Kosten für das Assemblieren und Disassemblieren der Nachricht.
Eine natürliche Schlussfolgerung aus der obigen Diskussion ist, dass sich Shared-Memory- und Message-Passing-Kommunikationsmodelle jeweils für bestimmte Anwendungsdomänen eignen. Shared Memory bietet sich für Anwendungsentwickler an, während Message Passing sich für Betriebssystementwickler anbietet.
Es ist daher naheliegend, die Kombination von Shared Memory und Message Passing in Mehrzweck-Multiprozessorsystemen in Betracht zu ziehen. Dies war die Hauptantriebskraft hinter Systemen wie dem Stanford FLexible Architecture for SHared memory (FLASH) System. Dabei handelt es sich um ein Multiprozessorsystem, das die Unterstützung für Shared Memory und Message Passing effizient integriert und dabei sowohl den Hardware- als auch den Software-Overhead minimiert.
\item \textbf{Nachrichtenbasiert}: + kein Blockieren bei asynchroner Variante, einfache, einheitliche Nutzung (Architekturunabhängig), keine Synchronisation nötig, - ggf. kopieren der Daten (ineffizient und redundant), Blockieren bei der synchronen Variante, ~ unidirektional (geht nur in eine Richtung)
\item \textbf{Speicherbasiert}: + Ideal für große Dateien, da annähernd Verzögerungsfrei, nicht blockierend (im allgemeinen), kann bidirektional verwendet werden - Synchronisation notwenig, gemeinsamer Speicher, also nicht für verteilte Systeme geeignet, aufwändiger bei korrekter Implementierung
\item Idee: Verwenden von Speicherbasierterkommunikation für die großen Video \& Audiodateien, da man diese nur schwer \& insbesondere langsam per Nachrichten verschicken kann. Verwendung von Messaepassing für Kontroolldatenströme
\end{itemize}
\vspace{10mm}
\paragraph{Frage 2: Synchronisation durch Semaphore}
\textit{Bei asynchroner nachrichtenbasierter Kommunikation kommen stets Warteschlangen zum Einsatz, um unterschiedliche Geschwindigkeiten der Sender- und Empfängerprozesse auszugleichen. Der Zugriff auf diese Warteschlangen muss aus verschiedenen Gründen durch Synchronisationsmechanismen (z. B. Semaphore) geregelt werden. Was sind diese Gründe und weshalb sind insgesamt drei Semaphore pro Warteschlange notwendig?}
Benötigen drei Semaphore: Schreibzugriff auf volle Schlange, Lesezugriff auf leere Schlange, und Zugriffskontrolle.
\vspace{10mm}
\paragraph{Frage 3: Synchronisationsvarianten bei nachrichtenbasierter Kommunikation}
\textit{Welche Nachteile asynchroner Kommunikation treten beim Einsatz synchroner Varianten der Sende- und Empfangsoperationen nicht auf? Warum ist es trotzdem manchmal sinnvoll oder unumgänglich, die asynchronen Varianten einzusetzen? Nennen Sie mindestens drei Beispiele realer Applikationen, in denen asynchron kommuniziert wird.}
\begin{itemize}
\item Pufferspeicher: Größe?
\item eventuell Synchronisation notwendig. (z.B. für Datenströme), eventuell extra Techniken zur Benachrichtigung oder Synchronisation
\item Trotzdem notwenig wenn:
\begin{itemize}
\item Hoher Grad an Parallelität notwendig
\item Ereignisse sporadisch oder unvorhersehbar (hier synchrones Warten ineffizient)
\item Auf Ereignisse zeitnah reagiert werden muss (z.B. in Echtzeitsystemen)
\end{itemize}
\item Es wird beispielsweise in E-Mails, Whatsapp, SMS asynchron kommuniziert
\end{itemize}
\vspace{10mm}
\paragraph{Frage 4: Management asynchroner Ereignisse}
\textit{Welche Alternativen haben die Entwickler von Betriebssystemen, um mit asynchron auftretenden
Ereignissen (Mausbewegungen, Einstecken von USB-Geräten etc.) umzugehen? Welche Technik
erlaubt es auch einem Benutzerprozess, auf asynchrone Ereignisse zu reagieren, ohne direkten
Hardwarezugriff zu haben?}
\textit{Welche Alternativen haben die Entwickler von Betriebssystemen, um mit asynchron auftretenden Ereignissen (Mausbewegungen, Einstecken von USB-Geräten etc.) umzugehen? Welche Technik erlaubt es auch einem Benutzerprozess, auf asynchrone Ereignisse zu reagieren, ohne direkten Hardwarezugriff zu haben?}
\begin{itemize}
\item Busy Waiting (Warte in Endlosschleife / sehr ineffizient)
\item Polling (Wahl der Zykluszeit)
\item Interrupts (HW-Signal, Behandlung über Routine aus IVT)
\begin{itemize}
\item inline-Prozeduraufruf
\item IPC über Botschaften
\item pop-up threads
\end{itemize}
\item Hierzu gibt es die Möglichkeit, dass der Prozessor alle Eingabe-Geräte zyklisch abfragt (Polling). Was bei der Vielzahl an Komponenten in einem Computer bedeuten würde, dass der Prozessor mit nichts anderem mehr beschäftigt wäre.
\item Eine Alternative ist die sogenannten Unterbrechungsanforderung (to interrupt, unterbrechen), die dann eintritt, wenn Daten von außen anstehen. Dazu wurde die Möglichkeit geschaffen den Hauptprozessor auf definierte Weise bei der laufenden Arbeit zu unterbrechen.
\item Auf Anwendungsebene: Registrieren von eigenen Signalhandlern
\end{itemize}
%##########################################
\subsection{Aufgabe 1: Nachrichtenwarteschlangen (Message Queues)}
\textit{a) Recherchieren Sie die Funktionsweise, Charakteristiken und Eigenschaften von Message Queues. Wie wird der Kontrollfluss der Prozesse dabei durch das Betriebssystem gesteuert (z. B. Synchronisation durch Blockierungen, durch die Ankunft von Daten usw.)?}
\vspace{10mm}
\begin{itemize}
\item Hier werden Nachrichten von einem Prozess in eine Nachrichtenschlange (Message Queue) geschickt, welche typischerweise nach dem FIFO Prinzip arbeitet. Jede Messagequeue ist durch einen eindeutigen Bezeichner gekennzeichnet, unidirektional und mit festgelegtem Format.
\item MessageType: Unterscheidung innerhalb der Warteschlange möglich.
\item send blockiert, wenn die Queue voll ist.
\item receive blockiert, wenn keine Nachricht mit dem spezifizierten Type in der Queue ist.
\item Es gibt auch eine nichtblockierende Variante. (IPC-NOWAIT)
\item Das Einsatzgebiet der Warteschlangen ist typischerweise die Datenübergabe zwischen asynchronen Prozessen in verteilten Systemen.
\end{itemize}
\textit{b) In der Anlage zu dieser Übungsaufgabe (u4-a1-anlage) befinden sich ein Server- und ein Client-Programm, die beide Lücken enthalten. Vervollständigen und übersetzen Sie die Programme. Starten Sie anschließend zuerst den Server und dann den Client. Falls Sie die Lücken richtig ausgefüllt haben, muss das Client-Programm ein "Passwort" an den Server senden und anschließend ein Geheimnis ausgeben, das es vom Server als Antwort erhalten hat.}
\vspace{10mm}
@ -939,6 +989,19 @@ ServiceMan{
\subsection{Aufgabe 2: Gemeinsamer Speicher (Shared Memory)}
\textit{a) Recherchieren Sie die Funktionsweise, Charakteristiken und Eigenschaften von Shared Memory. Wie wird der Kontrollfluss der Prozesse dabei durch das Betriebssystem gesteuert? (Wann und wodurch erfolgt eine Synchronisation?)}
\vspace{10mm}
\begin{itemize}
\item Shared Memory ist eine durch das Betriebssystem bereitgestellte Möglichkeit, bei welcher mehrere Prozesse gemeinsam auf einen gemeinsamen Speicher zugreifen können. Um dies zu erreichen muss zuerst ein gemeinsamer Datenspeicher angelegt werden. Nachdem dies geschehen ist, muss der Datenspeicher den Prozessen bekanntgemacht werden (einfügen in deren Adressraum), welche darauf zugreifen sollen dürfen.
\item Wichtig hierbei: Shared Memory ist eine der schnellsten, wenn nicht die schnellste Art der Interprozesskommunikation, da das Kopieren/Versenden zwischen Clients/Server, bzw. verschiedenen Prozessen entfällt.
\item Da man allerdings gemeinsam auf Speicher zugreift, ist eine Synchronisation, meist durch Semaphore oder Monitore unumgänglich.
\item shmat() fügt das durch shmid identifizierte Speichersegment an den Adressraum des aufrufenden Prozesses an. Die Anfügeadresse wird durch shmaddr spezifiziert.
\item Weiterhin gibt es Einschränkungen bezüglich der Rechte im shmflg Bitmaskargument:
\begin{itemize}
\item $SHM\_EXEC$: erlaubt eine Ausführung der Segmentinhalte
\item $SHM\_RDONLY$: Fall gesetzt, so ist das Segment nur für den Lesezugriff angehängt, ist es nicht gesetzt, so ist Lesen und Schreiben erlaubt.
\item $SHM\_REMAP$. Dieses Flag spezifiziert, dass das Mapping des Segments alle bisherigen Mappings im Bereich von shmaddr und den folgenden dateigrößelangen Segment ersetzt.
\end{itemize}
\item Ist shmat() erfolgreich, so wird $shm\_atime$ auf die jetzige Zeit gesetzt, $shm\_lpid$ auf die ProzessID des aufrufenden Prozess gesetzt und $shm\_nattch$ wird um 1 erhöht.
\end{itemize}
\textit{b) In der Anlage zu dieser Übungsaufgabe (u4-a2-anlage) befinden sich ein Server- und ein Client-Programm, die beide Lücken enthalten. Vervollständigen und übersetzen Sie die Programme. Starten Sie anschließend zuerst den Server und dann den Client. Falls Sie die Lücken richtig ausgefüllt haben, muss das Client-Programm ein "Passwort" an den Server senden und anschließend ein Geheimnis ausgeben, das es vom Server als Antwort erhalten hat.\\
Hinweis: An den verwendeten Semaphoroperationen sind keine Änderungen notwendig.}
@ -948,7 +1011,18 @@ ServiceMan{
\subsection{Aufgabe 3: Benannte Pipes (Named Pipes, FIFOs)}
\textit{a) Recherchieren Sie die Funktionsweise, Charakteristiken und Eigenschaften von Pipes und Named Pipes. Wie wird der Kontrollfluss der Prozesse dabei durch das Betriebssystem gesteuert? (Wann und wodurch erfolgt eine Synchronisation?)}
\vspace{10mm}
\begin{itemize}
\item Pipes stellen einen unidirektionalen Kommunikationskanal zwischen zwei Prozessen dar. Ein Pipe hat ein Lese- und ein Schreibende, wobei man am Leseende diejenigen Daten lesen kann, welche zuvor auf das Schreibende geschrieben wurden.
\item Wenn ein Prozess von einer leeren Pipe lesen will, so blockiert read() bis Daten vorhanden sind. Wenn ein Prozess versucht auf eine volle Pipe zu schreiben, dann wird write() solange blockieren, bis wieder genügend Platz auf der Pipe frei ist.
\item Die über Pipes ablaufende Kommunikation ist nachrichtenfrei und bytestromorientiert.
\item Eine Pipe hat eine begrenzte Kapazität
\item POSIX besagt, dass write() von weniger als $PIPE\_BUF$ Bytes atomar sein muss, bei mehr als $Pipe\_BUF$ Bytes kann es auch nichtatomar sein.
\begin{center}
\includegraphics[width=0.5\linewidth]{Assets/Betriebssysteme_uebung/u5_a5.png}
\end{center}
\item Named Pipes / FIFO sind einer Pipe sehr ähnlich, mit dem kleinen Unterschied, dass darauf als Teil des Dateisystems zugegriffen wird. Wenn Prozesse jetzt Daten über FIFO austauschen, dann behandelt der Kernel alle Daten intern ohne sie in das Dateisystem zu schreiben. Somit dienen die FIFO Dateien im Filesystem nur als Referenzpunkt und Namen und geben Prozessen Informationen darüber, an welcher Stelle Prozesse auf die Pipe zugreifen können. Dies bedeutet aber auch, dass hier im Gegensatz zu unbenannten Pipes Prozesse miteinander kommunizieren können, die nicht miteinander verwandt sind.
\item Der Kernel verwaltet genau ein Pipe-Objekt für jede FIFO-Spezialdatei, die von mindestens einem Prozess geöffnet wird. Das FIFO muss an beiden Enden (lesend und schreibend) geöffnet werden, bevor Daten übergeben werden können. Normalerweise wird auch das Öffnen der FIFO-Blöcke bis zum anderen Ende geöffnet.
\end{itemize}
\textit{b) In der Anlage zu dieser Übungsaufgabe (u4-a3-anlage) befinden sich ein Server- und ein Client-Programm, die beide Lücken enthalten. Vervollständigen und übersetzen Sie die Programme. Starten Sie anschließend zuerst den Server und dann den Client. Falls Sie die Lücken richtig ausgefüllt haben, muss das Client-Programm ein "Passwort" an den Server senden und anschließend ein Geheimnis ausgeben, das es vom Server als Antwort erhalten hat.}
\vspace{10mm}