Sistemi di Calcolo

Corso di Laurea in Ingegneria Informatica e Automatica

Home | Avvisi | Diario Lezioni | Esercitazioni | Esami | Materiale Didattico | Valutazioni Studenti | Lezioni di Camil Demetrescu |

[T10] Esercitazione 10

Istruzioni per l’esercitazione:

Per maggiori informazioni fate riferimento al regolamento delle esercitazioni.

Esercizio 1 (Parsing di una linea da stdin in C)

Questo è un esercizio di mera conoscenza del linguaggio C. Il canale stdin (di tipo FILE*) modella in C la sorgente di caratteri ASCII che proviene come input, salvo diversamente specificato, dal terminale. Si chiede di scrivere nel file E1-get-cmd-line/e1.c una funzione void get_cmd_line(char* argv[]); che legge la prossima linea di testo da stdin, estrae ciascun token (sequenza consecutiva di caratteri, esclusi spazi, tab \t e ritorni a capo \n) e produce un array di al più n<=MAX_TOKENS=64 stringhe come segue:

Assumere che la linea di testo letta da stdin contenga al più 1024 caratteri compreso il ritorno a capo \n.

Esempio: se la linea letta da stdin è rm -f .DS_store, la chiamata get_cmd_line(argv) restituisce in argv l’array di stringhe {"rm", "-f". ".DS_Store", NULL}.

Suggerimenti. Basandosi sul comando man (oppure la documentazione online cercando funzione opengroup) usare:

L’array di stringhe prodotto deve essere deallocabile con la semplice funzione:

void free_args(char* argv[]) {
    while (*argv) free(argv++);
}

Usare il main di prova nella directory di lavoro E1-get-cmd-line compilando con `gcc e1_main.c e1.c -o e1´.

Esercizio 2 (Scrittura di una semplice shell dei comandi Linux)

Una shell è un programma che chiede all’utente di eseguire altri programmmi sotto forma di nuovi processi, passandogli eventuali argomenti specificati dall’utente. Una shell fornisce normalmente un prompt, vale a dire un breve testo (es. $, >, ecc.) che segnala all’utente che la shell è in attesa di ricevere comandi.

Si chiede di scrivere nel file E2-shell/e2.c una semplice shell sotto forma di una funzione int do_shell(const char* prompt); che prende come parametro la stringa di prompt e si comporta come segue;

  1. stampa il prompt;
  2. attende che l’utente inserisca in stdin un comando seguito dai suoi eventuali argomenti (es: ls -l, dove ls è il comando e -l è il suo unico argomento). Per ottenere comando e argomenti da stdin usare il risultato dell’esercizio 1;
  3. se il comando è vuoto (NULL) tornare al punto 1;
  4. se il comando è quit terminare con successo la shell;
  5. creare con fork un nuovo processo che esegua il comando con gli argomenti dati usando execvp;
  6. se il comando si riferisce a un programma inesistente riportare l’errore unknown command seguito dal nome del comando e tornare al punto 1;
  7. attendere con wait la terminazione del processo e tornare al punto 1.

Per valutare il corretto funzionamento della shell, effettuare i seguenti quattro test:

  1. inserire ls -l e verificare che listi la directory corrente
  2. inserire echo hello e verificare che venga stampato hello
  3. inserire sergente hartman e verificare che venga stampato un messaggio di errore
  4. inserire quit e verificare la terminazione della shell

Il risultato di ogni system call deve essere controllato e in caso di errore segnalato con perror e terminazione EXIT_FAILURE.

Esercizio 3 (Calcolo parallelo con i thread)

In questo esercizio vogliamo implementare una funzione unsigned int counting_threads(unsigned int th, unsigned int it, unsigned int val) che calcola il prodotto dei tre valori th, it, val comportandosi come segue:

  1. genera th threads indipendenti
  2. ciascun thread calcola il prodotto di it e val
  3. al termine dell’esecuzione dei thread generati, la funzione calcola la somma dei risultati parziali e la restituisce come valore di ritorno.

NOTA: per evitare che i thread indpendenti sovrascrivano a vicenda una stessa variabile, si suggerisce di usare un array globale di th valori interi, uno per ciascun thread. In questo modo sarà possibile per ciascun thread scrivere in una variabile a lui riservata.

NOTA: per poter utilizzare le primitive per gestire i thread, è necessario aggiungere alla linea di comando di compilazione il flag -lpthread.

Usare il main di prova nella directory di lavoro E3-counting-threads compilando con `gcc e3_main.c e3.c -o e3´.

Domande

Rispondi alle seguenti domande, tenendo conto che una risposta corretta vale 1 punti, mentre una risposta errata vale 0 punti.

Domanda 1 Relativamente al seguente codice C (supponendo che la fork non generi un errore), quale tra le seguenti affermazioni è VERA?

Domanda 6

Domanda 2 Relativamente al seguente codice C (supponendo che la fork non generi un errore), qual è l’output atteso?

Domanda 7

Domanda 3 Relativamente al seguente codice C (supponendo che la execv non generi un errore), qual è l’output atteso?

Domanda 8

Soluzioni

Esercizio 1 (Parsing di una linea da stdin in C)
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "e1.h"

char* dup_string(const char* in) {
    size_t n = strlen(in);
    char* out = malloc(n + 1);
    strcpy(out, in);
    return out;
}

void get_cmd_line(char* argv[MAX_TOKENS]) {
    int argc = 0;
    char line[MAX_LINE];
    fgets(line, MAX_LINE, stdin);
    char* token = strtok(line, " \t\n");
    argc = 0;
    while (argc < MAX_TOKENS && token != NULL) {
        argv[argc++] = dup_string(token);
        token = strtok(NULL, " \t\n");
    }
    argv[argc] = NULL;
}
Esercizio 2 (Scrittura di una semplice shell dei comandi Linux)
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "e2.h"

#define MAX_LINE    1024
#define MAX_TOKENS  64

void do_cmd(char* argv[MAX_TOKENS]) {
    int res;
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork error");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        res = execvp(argv[0], argv);
        if (res == -1) {
            printf("unkwnown command %s\n", argv[0]);
            _exit(EXIT_FAILURE);
        }
    }

    res = wait(NULL);
    if (pid == -1) {
        perror("wait error");
        exit(EXIT_FAILURE);
    }
}

void deallocate_cmd(char* argv[MAX_TOKENS]) {
	while (*argv != NULL)
		free(*argv++);
}

char* dup_string(const char* in) {
    size_t n = strlen(in);
    char* out = malloc(n + 1);
    strcpy(out, in);
    return out;
}

void get_cmd_line(char* argv[MAX_TOKENS]) {
    int argc = 0;
    char line[MAX_LINE];
    fgets(line, MAX_LINE, stdin);
    char* token = strtok(line, " \t\n");
    argc = 0;
    while (argc < MAX_TOKENS && token != NULL) {
        argv[argc++] = dup_string(token);
        token = strtok(NULL, " \t\n");
    }
    argv[argc] = NULL;
}

int do_shell(const char* prompt){
    for (;;) {
        printf("%s", prompt);
        char* argv[MAX_TOKENS];
        get_cmd_line(argv);
        if (argv[0] == NULL) continue;
        if (strcmp(argv[0], "quit") == 0) break;
        do_cmd(argv);
	deallocate_cmd(argv);
    }
    return EXIT_SUCCESS;
}

Esercizio 3 (Calcolo parallelo con i thread)
#include <pthread.h>
#include <stdlib.h>

#include "ex.h"

unsigned int *shared_array;

// Inserire qui la soluzione

void* thread_work(void* arg)
{
	thread_data_t* data = (thread_data_t*)arg;

	int i;
	for (i = 0; i < data->it; i++) {
		shared_array[data->tid] += data->val;
	}
	return NULL;
}

unsigned int counting_threads(unsigned int th, unsigned int it, unsigned int val)
{
	shared_array = (unsigned int*)calloc(th, sizeof(unsigned int));
	pthread_t* threads = (pthread_t*)malloc(th * sizeof(pthread_t));
	thread_data_t* threads_data = (thread_data_t*)malloc(th * sizeof(thread_data_t));

	int i;
	for (i = 0; i < th; i++) {
		threads_data[i].it = it;
		threads_data[i].val = val;
		threads_data[i].tid = i;

		if (pthread_create(&threads[i], NULL, thread_work, &threads_data[i]) != 0) {
			exit(EXIT_FAILURE);
		}
	}

	unsigned int sum = 0;
	for (i = 0; i < th; i++) {
		if (pthread_join(threads[i], NULL) != 0) {
			exit(EXIT_FAILURE);
		}
		sum += shared_array[i];
	}

	free(shared_array);
	free(threads);
	free(threads_data);

	return sum;
}

Domande
  1. C. Viene stampato “Figlio: 1” e poi “Padre: 1
  2. A. Nell’ordine (una per linea): “Figlio 1: x=10”, “Figlio 2: x=20”, “Figlio 3: x=30”, “Padre: x=40
  3. C. Nell’ordine (una per linea): “--- started ---”, “Hello