Al desarrollar un programa, el programador debe tener en cuenta varias cosas, como que el código no debe ser complejo, es decir, debe poder mantenerse, la portabilidad es otra área que debe tenerse en cuenta. Entonces vemos que hay algunas buenas prácticas que el programador debe seguir para producir un buen código. Aquí, en este artículo, nos centraremos en algunas buenas prácticas que el programador debe seguir mientras trabaja con llamadas al sistema en Linux.
¿Qué es una llamada al sistema?
Una llamada al sistema es una llamada de función especial que se realiza para solicitar algún servicio del Kernel. El servicio solicitado podría ser para crear un nuevo proceso, para acceder al hardware como el disco duro, etc. Cuando se realiza una llamada al sistema, la ejecución cambia del modo de usuario al modo kernel y cuando el kernel proporciona el servicio requerido, entonces el la ejecución vuelve al modo de usuario. Ejemplos de llamadas al sistema podrían ser fork(), read(), write() etc.
Tratar con llamadas al sistema
Los siguientes puntos deben tenerse en cuenta al tratar con llamadas al sistema:
- El programador debe tener conocimiento de entrada y salida de la llamada al sistema. Por ejemplo, qué hace exactamente, los recursos del sistema que usa, qué tipo de argumentos espera y especialmente en qué casos falla.
- La mayoría de las llamadas al sistema Linux devuelven un código de error si fallan. Estos códigos de error pueden variar según el tipo de error que causó la falla. Por lo tanto, se debe implementar un manejo de errores adecuado para que cada tipo de error se maneje correctamente y se escale claramente (ya sea al usuario o al módulo principal).
- Para obtener un conocimiento completo de la llamada al sistema y los códigos de error que devuelve, le recomiendo encarecidamente que consulte la página del manual de esa llamada al sistema específica. Las páginas del manual son las mejores referencias para comenzar y desarrollar una buena comprensión fundamental sobre cualquier llamada al sistema en Linux.
Fallas generales de llamadas al sistema
Aunque la falla de una llamada al sistema puede depender del tipo de error encontrado durante la ejecución de la llamada al sistema, aquí hay una lista de razones que contribuyen principalmente a las fallas de la llamada al sistema:
- Si una llamada al sistema intenta acceder al hardware del sistema y, por algún motivo, el hardware no está disponible o supone que el hardware está defectuoso, en ese caso la llamada al sistema fallará.
- Mientras se ejecuta una llamada al sistema, si se produce una señal de alta prioridad, también puede causar la falla en la ejecución de la llamada al sistema.
- Hay situaciones en las que a través de una llamada al sistema, un programa intenta realizar una tarea específica que requiere privilegios especiales o de raíz. Si el programa no tiene ese tipo de privilegios, la llamada al sistema también fallará.
- Pasar argumentos inválidos es otra razón muy común para que las llamadas al sistema fallen.
- Supongamos que se realiza una llamada al sistema para solicitar memoria del montón y, por algún motivo, el sistema no puede asignar memoria al proceso solicitante que realizó la llamada al sistema; en este caso, la llamada al sistema también fallará.
La lista anterior no es exhaustiva, ya que podría haber muchas otras razones por las cuales una llamada al sistema puede fallar.
Trabajar con códigos de error
Como ya se discutió, cada llamada al sistema devuelve un código de error específico para cada tipo de error que encontró (lo que causó la falla de la llamada al sistema). Por lo tanto, identificar y comunicar la información de error es una tarea muy importante de la programación. En general, la mayoría de las llamadas al sistema devuelven '0' en caso de éxito y distinto de cero en caso de error, pero aquellas llamadas al sistema que devuelven un puntero a una memoria (como malloc() ) devuelven '0' o NULL en caso de error y un valor de puntero distinto de cero en caso de éxito .
NOTA:La observación anterior puede no ser cierta para todas las llamadas al sistema. Es muy posible que haya algunas excepciones.
Entonces, volviendo a los códigos de error, como se discutió, pueden proporcionar información vital sobre la causa de la falla de una llamada al sistema. Ahora bien, como cada código de error está asociado a un motivo específico, el programa puede tener un mapa de códigos de error y el texto que describe la causa del error. Sin embargo, esto es muy ineficiente y no práctico, ya que supondría una gran cantidad de mapeo para cada llamada al sistema que se utiliza en el programa. Entonces, ahora la pregunta es cuál podría ser una forma más eficiente de lograr esto.
La variable 'errno'
Desde la página man de esta variable:
El archivo de encabezado
Los números de error válidos son todos distintos de cero; errno nunca se establece en cero por ninguna llamada al sistema o función de biblioteca. Para algunas llamadas al sistema y funciones de biblioteca (por ejemplo, getpriority), -1 es un retorno válido en caso de éxito. En tales casos, una devolución exitosa se puede distinguir de una devolución de error configurando errno en cero antes de la llamada, y luego, si la llamada devuelve un estado que indica que puede haber ocurrido un error, verificando si errno tiene un error. valor cero errno está definido por el estándar ISO C como un lvalue modificable de tipo int, y no debe declararse explícitamente; errno puede ser una macro. errno es subproceso local; configurarlo en un subproceso no afecta su valor en ningún otro subproceso.
Asi que. de la descripción anterior, está bastante claro que es una herramienta muy útil cuando se trata de la gestión de errores de las llamadas al sistema en Linux y puede ahorrarnos mucho trabajo duro. Pero tenga cuidado con el uso de esta variable en un programa de subprocesos múltiples, ya que es local para un subproceso y, por lo tanto, cualquier cambio de valor de errno en un subproceso no se puede acceder en ningún otro subproceso.
La API de strerror()
Bueno, un problema con usar solo errno es que todavía es solo un valor entero. Una descripción siempre es más útil al iniciar sesión o al pasar la causa del error al usuario. Entonces, tiene que haber un mapa de códigos de error y la causa a la que se asignan. Aquí viene la API 'strerror()'. Esta función toma la variable errno como argumento y devuelve un puntero a una cadena que contiene la descripción de la causa a la que se asigna el código de error.
#include <string.h> char *strerror(int errnum);
También están disponibles otras variantes de esta función. Para obtener más información, visite la página man de esta API.
NOTA:Los lectores interesados también pueden pasar por la API perror(). Se utiliza para imprimir el mensaje de error para una falla de llamada al sistema en un error estándar.
Un ejemplo
Tomemos un ejemplo para demostrar el uso de errno y strerror()
#include<stdio.h> #include<errno.h> #include<string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { int fd = -1; // Always Reset errno before use. errno = 0; // Make sure you are opening a file that does not exist fd = open("abcd",O_RDONLY); if(fd == -1) { // Seems like some error occured. Use strerror to print it printf("\nStrerror() says -> [%s]\n",(char*)strerror(errno)); return 1; } return 0; }
En el código de arriba:
- errno se inicializa en '0' ya que no se garantiza que sea cero inicialmente.
- Abra un archivo inexistente para que la llamada al sistema open() falle.
- Ahora, la API strerror() se usa para imprimir el mensaje de error basado en el código errno.
Cuando se ejecuta el programa anterior:
$ ./strerror Strerror() says -> [No such file or directory]
Entonces vemos que en la salida podemos ver un mensaje de error significativo en lugar de un código de error.