Sistemi di Calcolo

Corso di Laurea in Ingegneria Informatica e Automatica - A.A. 2017-2018

HomePage | Avvisi | Diario lezioni | Programma | Materiale didattico | Esami | Forum | Login

Operazioni I/O

Stream
example-stream.c
#include <stdio.h>  // header in cui sono dichiarate:
                    // - stdout
                    // - stderr
                    // - stdin

int main() {
    fprintf(stdout, "ciao\n");  // scrive sullo stream relativo a standard output

    printf("ciao\n");           // Stesso effetto della fprintf con stdout
                                // Standard C11 N1570:
                                // The printf function is equivalent to fprintf with the argument stdout interposed before the arguments to printf.
                                // https://port70.net/~nsz/c/c11/n1570.html#7.21.6.3p2
   
    fprintf(stderr, "errore!!!\n"); // scrive sullo stream relativo allo standard error
    return 0;
}

> gcc -m32 example-stream.c -o example-stream
> ./example-stream
ciao
ciao
errore!!!
> ./example-stream 1> standard_output.txt
errore!!!
> ./example-stream 2> standard_error.txt
ciao
ciao


File Descriptors
example-file-descriptors.c
#include <unistd.h>   // write, STDOUT_FILENO
#include <string.h>   // strlen
#include <stdio.h>    // printf, perror
#include <stdlib.h>   // exit
#include <errno.h>    // errno

int main() {

    ssize_t scritti;
    char* testo = "Hello World\n";

    // costante STDOUT_FILENO == 1
    scritti = write(STDOUT_FILENO, testo, strlen(testo));

    if (scritti == -1) { // errore della write, vedere: man 2 write
        exit(1);
    }

    printf("%lu\n", scritti);
    return 0;
}


example-file-descriptors-perror.c
#include <unistd.h>   // write, STDOUT_FILENO
#include <string.h>   // strlen
#include <stdio.h>    // printf, perror
#include <stdlib.h>   // exit
#include <errno.h>    // errno

int main() {

    ssize_t scritti;
    char* testo = "Hello World\n";

    scritti = write(176677, testo, strlen(testo)); // file descriptor volutamente non valido per indurre un errore nella write

    if (scritti == -1) {
               perror("Errore in write");  // perror permette di stampare una stringa che descrive l'errore
                                   // prende come argomento un ulteriore stringa che viene concatenata alla descrizione dell'errore
                                   // in questo caso emettera sullo stderr: "Errore in write: Bad file descriptor"
                                   // dove ": Bad file descriptor" è stato aggiunto da perror in coda
                                   // alla stringa fornita come argomento
        exit(1);
    }

    printf("%lu\n", scritti);
    return 0;
}


example-file-descriptors-errno.c
#include <unistd.h>   // write, STDOUT_FILENO
#include <string.h>   // strlen
#include <stdio.h>    // printf, perror
#include <stdlib.h>   // exit
#include <errno.h>    // errno

int main() {

    ssize_t scritti;
    char* testo = "Hello World\n";

    scritti = write(176677, testo, strlen(testo));

    if (scritti == -1) {

        // valutiamo la causa dell'errore e
        // reagiamo diversamente in base all'errore
        switch (errno) {
            case EBADF: // uno dei possibile errori di write. Vedere: man 2 write, sezioni RETURN VALUE e ERRORS
                fprintf(stderr, "Descrittore errato\n"); // stampiamo semplicemente una stringa sullo stderr ma potremmo fare qualcosa di piu' utile
                break;
            default:
                perror("Errore in write");
        }
       
        exit(1);
    }

    printf("%lu\n", scritti);
    return 0;
}


Tracciamento invocazione system call

hello.c
#include <stdio.h>

int main() {
    printf("hello\n"); // printf utilizza internamente gli stream, che a loro volta sono implementati utilizzando i file descriptor
    return 0;
}


Per tracciare le system call invocate da un eseguibile in Linux possiamo utilizzare il tool strace:
> strace ./hello 2> dump_syscall.txt
hello

L'output di strace è emesso sullo standard error.

Contenuto di dump_syscall.txt:
execve("./hello", ["./hello"], [/* 85 vars */]) = 0
brk(NULL)                               = 0x21d1000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fddc6825000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=211509, ...}) = 0
mmap(NULL, 211509, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fddc67f1000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fddc6238000
mprotect(0x7fddc63f8000, 2097152, PROT_NONE) = 0
mmap(0x7fddc65f8000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7fddc65f8000
mmap(0x7fddc65fe000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fddc65fe000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fddc67f0000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fddc67ef000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fddc67ee000
arch_prctl(ARCH_SET_FS, 0x7fddc67ef700) = 0
mprotect(0x7fddc65f8000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7fddc6827000, 4096, PROT_READ) = 0
munmap(0x7fddc67f1000, 211509)          = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 5), ...}) = 0
brk(NULL)                               = 0x21d1000
brk(0x21f2000)                          = 0x21f2000
write(1, "hello\n", 6)                  = 6
exit_group(0)                           = ?
+++ exited with 0 +++

Effettivamente printf invoca la system call write:

write_system_call
Image credits to: shichao.io

Invocazione di una system call

Diverse funzioni definite da POSIX sono semplicemente composte da codice che innesca l'invocazione di una system call. Il meccanismo di invocazione di una system call è specifico per l'architettura e per il kernel in uso. Il meccanismo di invocazione non è stabilito dallo standard POSIX. Per scrivere codice portabile POSIX dobbiamo sempre utilizzare le API POSIX: sarranno quest'ultime a gestire l'invocazione della system call in base alla piattaforma utilizzata.

Invocazione di una system call in x86 Linux

Il meccanismo di invocazione di una system call in Linux è specificato in man syscall, sezione Architecture calling conventions.

Le system call implementate da Linux sono elencate in un header del kernel:
> cat /usr/src/linux-headers-$(uname -r)/arch/x86/include/generated/uapi/asm/unistd_32.h
[...]
#define __NR_write 4
[...]

La system call Linux relativa alla write ha l'id numerico 4. Gli argomenti presi dalla system call write sono gli stessi presi dalla funzione POSIX write, vedere man 2 write.

main.c
// Non usiamo la funzione POSIX write ma implementiamo noi una
// nostra funzione che invoca la system call linux che implementa il task della write
int mywrite(int fd, const void *buf, int count);

int main() {
    mywrite(1, "Hello\n", 6);
    return 0;
}


mywrite.s
# syscall numbers:
# cat /usr/src/linux-headers-$(uname -r)/arch/x86/include/generated/uapi/asm/unistd_32.h

# ssize_t write(int fd, const void *buf, size_t count)
# eax           ebx     ecx              edx

.globl mywrite

mywrite:
    pushl %ebx
    movl $4, %eax           # syscall write ha id numerico 4
                            # #define __NR_write 4 in /usr/src/linux-headers-$(uname -r)/
                            #     arch/x86/include/generated/uapi/asm/unistd_32.h
    movl 8