En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

sábado, 25 de noviembre de 2017

Errno y sus hermanos
cómo està implementado errno en C

Este post es un necesario addendum al post anterior (que si aún no lo habéis leído deberíais hacerlo en seguida, ya que están estrechamente vinculados). Esta vez hablaremos de errno y sus hermanos, una familia compleja como la de Rocco y sus hermanos, una obra maestra del neorrealismo Italiano.
Rocco Errno y sus hermanos
Vamos al grano: después de leer el último post, los lectores más atentos se habrán preguntado: "Ok, con strerror_r() podemos manejar las cadenas de error de una manera thread-safe, pero ¿de qué nos sirve si, en la base de todo, hay la variable global errno que realmente no tiene el aire de ser thread-safe?" La pregunta es legítima, y para responder tenemos que retroceder un poco en el tiempo... la historia es análoga y paralela a la de strerror() (¡solo faltaria que no!). Antiguamente errno estaba definido en el header errno.h:
    extern int errno;
y hacia referencia a una simple variable global de la libc, exactamente un int llamado errno. Luego vinieron los thread, con el estándar POSIX 1003.1c (también conocido como POSIX.1c, pero hace lo mismo), y con él vino también la strerror_r() y, como no, también errno ha conseguido un hermano thread-safe. Como bien se puede leer en el manual de errno (después de POSIX.1c) ahora errno es:
    errno is defined by the ISO C standard to be a modifiable lvalue of
    type int, and must not be explicitly declared; errno may be a macro.
    errno is thread-local; setting it in one thread does not affect its
    value in any other thread.
Entonces la nueva definición de errno ahora está en bits/errno.h (que se incluye desde el clásico errno.h). Simplificando un poco (he omitido algunos detalles para que sea más fácil de leer) la nueva impostacion es:
en el header-file errno.h
#include <bits/errno.h>
/* Declare the `errno' variable, unless it's defined as a macro by
   bits/errno.h.  This is the case in GNU, where it is a per-thread
   variable.  This redeclaration using the macro still works, but it
   will be a function declaration without a prototype and may trigger
   a -Wstrict-prototypes warning.  */
#ifndef errno
extern int errno;
#endif

en el header-file bits/errno.h
/* Function to get address of global `errno' variable. */
extern int *__errno_location (void);

/* When using threads, errno is a per-thread value. */
#define errno (*__errno_location ())
Entonces, en pocas palabras, ahora errno ya no es un int global, sino que es "el contenido de una dirección devuelta por una función global". Obviamente, la variable int a la que apunta este objeto es el nuevo errno local de un thread (es decir, cada thread tiene su propio errno). Un ejemplo (muuuy simplificado) de cómo se podría implementar la __errno_location() es el siguiente:
// errno local de un thread: es una variable de tipo Thread-local storage (TLS)
__thread int th_errno;

int *__errno_location(void)
{
    // retorna la dirección de la variable th_errno
    return &th_errno;
}
Y, al final de todo, a pesar de los cambios descritos, aún será posible hacer operaciones como estas:
int my_errno = errno; // Ok, equivale a: int my_errno = (* __errno_location());
errno = EADDRINUSE;   // Ok, equivale a: (* __errno_location()) = EADDRINUSE;
porque, por supuesto, todo ha sido diseñado para ser retro-compatible, y por lo tanto errno, aunque ahora es una macro, todavía tiene que comportarse como si fuera un simple int.

Y, para terminar a lo grande, no podemos renunciar a un pequeño extracto del estándar POSIX.1c, que señala todo lo que se ha dicho hasta ahora:
Redefinition of errno
In POSIX.1, errno is defined as an external global variable. But this definition
is unacceptable in a multithreaded environment, because its use can result in 
nondeterministic results. The problem is that two or more threads can encounter 
errors, all causing the same errno to be set. Under these circumstances, a thread 
might end up checking errno after it has already been updated by another thread. 

To circumvent the resulting nondeterminism, POSIX.1c redefines errno as a service 
that can access the per-thread error number as follows (ISO/IEC 9945:1-1996, n2.4): 

Some functions may provide the error number in a variable accessed through the 
symbol errno. The symbol errno is defined by including the header <errno.h>, as 
specified by the C Standard ... For each thread of a process, the value of errno 
shall not be affected by function calls or assignments to errno by other threads. 

In addition, all POSIX.1c functions avoid using errno and, instead, return the 
error number directly as the function return value, with a return value of zero 
indicating that no error was detected. This strategy is, in fact, being followed 
on a POSIX-wide basis for all new functions.
Notar que la ultima parte del extracto (de In addition... en adelante) explica el porqué existe la strerror_r() XSI-compliant descrita en el post anterior: ¿Habéis visto? todo se aclara al final... Y con esto también podemos considerar resuelto el misterio del errno thread-safe. ¡Misión cumplida!

¡Hasta el próximo post!

No hay comentarios:

Publicar un comentario