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 del 7 maggio 2021

Istruzioni per l’esercitazione:

Per maggiori informazioni fate riferimento al regolamento delle esercitazioni.

Esercizio 1 (prefisso di stringa)

Tradurre in IA32 la seguente funzione is_prefix definita in E1-is-prefix/e1.c che, date due stringhe s1 e s2, verifica se la s1 è un prefisso di s2. Ad esempio ham è prefisso di hamburger, mentre hum non lo è.

e1.c

int is_prefix(const char* s1, const char* s2){
    while (*s1 && *s1++ == *s2++);
    return *s1 == 0;
}

Scrivere la soluzione nel file E1-is-prefix/e1.s. Usare il file E1-is-prefix/e1_eq.c per sviluppare la versione C equivalente e E1-is-prefix/e1_main.c come programma di prova.

Esercizio 2 (indicatore di progresso)

Si vogliono usare i segnali per creare un indicatore di progresso per la funzione do_sort, che implementa un semplice algoritmo di ordinamento a bolle. L’indicatore di progresso è la percentuale di n coperta da i, vale a dire 100.0*i/n. Si scriva la soluzione nel file E2-sort-timer/e2.c modificandolo e aggiungendo quanto necessario al raggiungimento dell’obiettivo.

e2.c

#include "e2.h"

static void do_sort(int *v, int n) {
    int i, j;
    for (i=0; i<n; ++i)
        for (j=1; j<n; ++j)
            if (v[j-1] > v[j]) {
                int tmp = v[j-1];
                v[j-1] = v[j];
                v[j] = tmp;
            }
}

void sort(int *v, int n) {

    // completare con gestione segnali...

    do_sort(v, n);
}

Il risultato atteso deve essere come segue (ovviamente i numeri esatti delle percentuali possono variare):

start sorting...
-------------------------------
5.4%
11.2%    <---- percentuali stampate dal gestore del segnale
17.1%
23.3%
29.7%
36.5%
43.7%
51.5%
59.7%
68.7%
79.0%
90.7%
-------------------------------
v[0]=0
v[1]=1
v[2]=2
v[3]=3
v[4]=4
v[5]=5
v[6]=6
v[7]=7
v[8]=8
v[9]=9

Suggerimento: rendere la variabile i di do_sort globale (dichiarata fuori dalla funzione) e tenere in un’altra variabile globale max il valore di n. In questo modo è possibile accedervi da un handler di un segnale che può stampare il rapporto tra i e max. Fare riferimento alla dispensa del corso sul segnali.

Esercizio 3 (Domande)

Domanda 1 Quale tra le seguenti affermazioni è VERA?

Domanda 2 Quale tra le seguenti affermazioni è VERA?

Domanda 3 Dato il seguente codice, quale tra le seguenti affermazione è VERA?

Domanda 3

Domanda 4 Dato il seguente codice, selezionare la risposta corretta:

Domanda 4

Domanda 5 Dato il seguente codice, selezionare la risposta corretta:

Domanda 5

Domanda 6 Quale delle seguenti affermazioni è FALSA?

Domanda 7 Quale delle seguenti affermazioni è VERA?

Soluzioni

Esercizio 1 (prefisso di stringa)

e1_eq.c

#include "e1.h"

int is_prefix(const char* s1, const char* s2){
    const char* c = s1;
    const char* d = s2;
L:; char a = *c;
    if (a == 0)
        goto E;
    char b = *d;
    c++;
    d++;
    if (a != b)
        goto E;
    goto L;
E:  a = *c;
    a = a == 0;
    return a;
}

e1.s

.globl is_prefix

is_prefix: # int is_prefix(const char* s1, const char* s2)
    pushl %ebx
    movl 8(%esp), %ecx              # const char* c = s1;
    movl 12(%esp), %edx             # const char* d = s2;
L:  movb (%ecx), %al                # char a = *c;
    testb %al, %al                  # if (a == 0)
    je E                            #     goto E;
    movb (%edx), %bl                # char b = *d;
    incl %ecx                       # c++;
    incl %edx                       # d++;
    cmpb %bl, %al                   # if (a != b)
    jne E                           #     goto E;
    jmp L                           # goto L;
E:  movb (%ecx), %al                # a = *c;
    testb %al, %al                  # a = a == 0;
    sete %al
    movsbl %al, %eax
    popl %ebx
    ret
Esercizio 2 (indicatore di progresso)

e2.c

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include "e2.h"

int i, max;

static void do_sort(int *v, int n) {
    int j;
    for (i=0; i<n; ++i)
        for (j=1; j<n; ++j)
            if (v[j-1] > v[j]) {
                int tmp = v[j-1];
                v[j-1] = v[j];
                v[j] = tmp;
            }
}

void handler(int sig) {
    printf("%3.1f%%\n", 100.0*i/max);
    ualarm(500000,0);
}

void sort(int *v, int n) {

    max = n;

    struct sigaction act = { 0 };               // preparazione struttura 
    act.sa_handler = handler;                   // gestore segnale
    int ret = sigaction(SIGALRM, &act, NULL);   // gestore installato
    if (ret == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }
    
    ualarm(500000,0);

    do_sort(v, n);

    act.sa_handler = SIG_IGN;                   // segnale ignorato
    ret = sigaction(SIGALRM, &act, NULL);       // gestore installato
    if (ret == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }
}
Esercizio 3 (Domande)
  1. D. L’interrupt generata dal timer è asincrona
  2. B. Il valore di ritorno di una system call viene scritto nel registro EAX.
  3. C. Il processo figlio entra nello stato zombie dopo aver terminato la propria esecuzione e fino al momento in cui il processo padre raccoglie il suo exit status

    Spiegazione: quando un processo termina, il kernel libera le risorse associate, ma deve conservarne alcune, come il PCB, per permettere al processo padre di raccogliere l’exit status tramite wait/waitpid. In questo intervallo di tempo il processo entra in uno stato denominato zombie. Quando il padre raccoglie l’exit status il kernel può rilasciare tutte le risorse associate al figlio, che esce dallo stato zombie e non compare più nella lista dei processi. Se il padre termina senza raccogliere l’exit status del figlio (con wait/waitpid) il processo figlio viene adottato da un processo antenato del processo padre (storicamente init, nei sistemi moderni un discendente di init) che raccoglie l’exit status del figlio permettendo così al kernel di liberare tutte le risorse associate (questi processi vengono detti anche reaper).

  4. A. Il programma non termina (o termina dopo molto) [dopo essere stato gestito, SIGFPE ritenta l’operazione che lo ha provocato]
  5. C. Il programma termina dopo circa 3 secondi ma non stampa “bye!” [il segnale SIGALRM ha come default la terminazione]
  6. B. Il segnale SIGSEGV non può essere catturato [gli unici segnali a non poter essere catturati sono SIGKILL e SIGSTOP]
  7. D. ualarm(t, n) invia un segnale dopo t microsecondi e successivamente ogni n microsecondi [vedi man ualarm]