Programación en C

Tutorial de llamadas al sistema Linux con C

Tutorial de llamadas al sistema Linux con C
En nuestro último artículo sobre llamadas al sistema Linux, definí una llamada al sistema, discutí las razones por las que uno podría usarlas en un programa y profundicé en sus ventajas y desventajas. Incluso di un breve ejemplo en montaje dentro de C. Ilustró el punto y describió cómo hacer la llamada, pero no hizo nada productivo. No es exactamente un ejercicio de desarrollo emocionante, pero ilustró el punto.

En este artículo, usaremos llamadas al sistema reales para hacer un trabajo real en nuestro programa C. Primero, revisaremos si necesita usar una llamada al sistema, luego proporcionaremos un ejemplo usando la llamada sendfile () que puede mejorar drásticamente el rendimiento de la copia de archivos. Finalmente, repasaremos algunos puntos para recordar al usar llamadas al sistema Linux.

¿Necesita una llamada al sistema??

Si bien es inevitable que use una llamada al sistema en algún momento de su carrera de desarrollo en C, a menos que esté apuntando a un alto rendimiento o una funcionalidad de tipo particular, la biblioteca glibc y otras bibliotecas básicas incluidas en las principales distribuciones de Linux se encargarán de la mayoría de tus necesidades.

La biblioteca estándar glibc proporciona un marco multiplataforma bien probado para ejecutar funciones que de otro modo requerirían llamadas al sistema específicas del sistema. Por ejemplo, puede leer un archivo con fscanf (), fread (), getc (), etc., o puede usar la llamada al sistema read () Linux. Las funciones glibc proporcionan más funciones (i.mi. mejor manejo de errores, IO formateado, etc.) y funcionará en cualquier sistema compatible con glibc.

Por otro lado, hay momentos en los que el rendimiento sin concesiones y la ejecución exacta son fundamentales. La envoltura que proporciona fread () agregará gastos generales y, aunque es menor, no es completamente transparente. Además, es posible que no desee o necesite las funciones adicionales que proporciona el contenedor. En ese caso, lo mejor es una llamada al sistema.

También puede usar llamadas al sistema para realizar funciones que aún no son compatibles con glibc. Si su copia de glibc está actualizada, esto difícilmente será un problema, pero desarrollar en distribuciones más antiguas con kernels más nuevos puede requerir esta técnica.

Ahora que ha leído las exenciones de responsabilidad, las advertencias y los posibles desvíos, profundicemos en algunos ejemplos prácticos.

¿En qué CPU estamos??

Una pregunta que la mayoría de los programas probablemente no piensan hacer, pero que, no obstante, es válida. Este es un ejemplo de una llamada al sistema que no se puede duplicar con glibc y no está cubierta con un contenedor glibc. En este código, llamaremos a la llamada getcpu () directamente a través de la función syscall (). La función syscall funciona de la siguiente manera:

syscall (SYS_call, arg1, arg2,…);

El primer argumento, SYS_call, es una definición que representa el número de la llamada al sistema. Cuando incluye sys / syscall.h, estos están incluidos. La primera parte es SYS_ y la segunda parte es el nombre de la llamada al sistema.

Los argumentos para la llamada van a arg1, arg2 arriba. Algunas llamadas requieren más argumentos y continuarán en orden desde su página de manual. Recuerde que la mayoría de los argumentos, especialmente para devoluciones, requerirán punteros a matrices de caracteres o memoria asignada a través de la función malloc.

Ejemplo 1.C

#incluir
#incluir
#incluir
#incluir
 
int main ()
 
CPU sin firmar, nodo;
 
// Obtener el núcleo de la CPU actual y el nodo NUMA a través de la llamada al sistema
// Tenga en cuenta que esto no tiene un contenedor glibc, por lo que debemos llamarlo directamente
syscall (SYS_getcpu, & cpu, & node, NULL);
 
// Mostrar información
printf ("Este programa se está ejecutando en el núcleo de la CPU% u y el nodo NUMA% u.\ n \ n ", CPU, nodo);
 
return 0;
 

 
Para compilar y ejecutar:
 
gcc example1.c -o example1
./Ejemplo 1

Para obtener resultados más interesantes, puede girar subprocesos a través de la biblioteca pthreads y luego llamar a esta función para ver en qué procesador se está ejecutando su subproceso.

Sendfile: rendimiento superior

Sendfile proporciona un excelente ejemplo de mejora del rendimiento a través de llamadas al sistema. La función sendfile () copia datos de un descriptor de archivo a otro. En lugar de utilizar múltiples funciones fread () y fwrite (), sendfile realiza la transferencia en el espacio del kernel, lo que reduce la sobrecarga y, por lo tanto, aumenta el rendimiento.

En este ejemplo, vamos a copiar 64 MB de datos de un archivo a otro. En una prueba, usaremos los métodos estándar de lectura / escritura en la biblioteca estándar. En el otro, usaremos llamadas al sistema y la llamada sendfile () para enviar estos datos de una ubicación a otra.

test1.c (glibc)

#incluir
#incluir
#incluir
#incluir
 
#define BUFFER_SIZE 67108864
#define BUFFER_1 "buffer1"
#define BUFFER_2 "buffer2"
 
int main ()
 
ARCHIVO * fOut, * fIn;
 
printf ("\ nPrueba de E / S con funciones glibc tradicionales.\ n \ n ");
 
// Coge un búfer BUFFER_SIZE.
// El búfer tendrá datos aleatorios, pero eso no nos importa.
printf ("Asignación de búfer de 64 MB:");
char * buffer = (char *) malloc (BUFFER_SIZE);
printf ("HECHO \ n");
 
// Escribe el búfer en fOut
printf ("Escribiendo datos en el primer búfer:");
fOut = fopen (BUFFER_1, "wb");
fwrite (buffer, sizeof (char), BUFFER_SIZE, fOut);
fclose (fOut);
printf ("HECHO \ n");
 
printf ("Copiando datos del primer archivo al segundo:");
fIn = fopen (BUFFER_1, "rb");
fOut = fopen (BUFFER_2, "wb");
fread (buffer, sizeof (char), BUFFER_SIZE, fIn);
fwrite (buffer, sizeof (char), BUFFER_SIZE, fOut);
fclose (fin);
fclose (fOut);
printf ("HECHO \ n");
 
printf ("Liberando búfer:");
libre (búfer);
printf ("HECHO \ n");
 
printf ("Eliminando archivos:");
eliminar (BUFFER_1);
eliminar (BUFFER_2);
printf ("HECHO \ n");
 
return 0;
 

test2.c (llamadas al sistema)

#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
 
#define BUFFER_SIZE 67108864
 
int main ()
 
int fOut, fIn;
 
printf ("\ nPrueba de E / S con sendfile () y llamadas al sistema relacionadas.\ n \ n ");
 
// Coge un búfer BUFFER_SIZE.
// El búfer tendrá datos aleatorios, pero eso no nos importa.
printf ("Asignación de búfer de 64 MB:");
char * buffer = (char *) malloc (BUFFER_SIZE);
printf ("HECHO \ n");
 
// Escribe el búfer en fOut
printf ("Escribiendo datos en el primer búfer:");
fOut = open ("buffer1", O_RDONLY);
escribir (fOut, & buffer, BUFFER_SIZE);
cerrar (fOut);
printf ("HECHO \ n");
 
printf ("Copiando datos del primer archivo al segundo:");
fIn = open ("buffer1", O_RDONLY);
fOut = open ("buffer2", O_RDONLY);
sendfile (fOut, fIn, 0, BUFFER_SIZE);
cerrar (fin);
cerrar (fOut);
printf ("HECHO \ n");
 
printf ("Liberando búfer:");
libre (búfer);
printf ("HECHO \ n");
 
printf ("Eliminando archivos:");
desvincular ("buffer1");
desvincular ("buffer2");
printf ("HECHO \ n");
 
return 0;
 

Compilación y ejecución de pruebas 1 y 2

Para crear estos ejemplos, necesitará las herramientas de desarrollo instaladas en su distribución. En Debian y Ubuntu, puede instalar esto con:

apt install build-essentials

Luego compila con:

gcc test1.c -o test1 && gcc test2.c -o test2

Para ejecutar ambos y probar el rendimiento, ejecute:

hora ./ test1 && time ./ test2

Debería obtener resultados como este:

Prueba de E / S con funciones glibc tradicionales.

Asignación de búfer de 64 MB: HECHO
Escribiendo datos en el primer búfer: HECHO
Copiando datos del primer archivo al segundo: HECHO
Liberación de búfer: HECHO
Eliminando archivos: HECHO
real 0m0.397
usuario 0m0.Miles
sys 0m0.203
Prueba de E / S con sendfile () y llamadas al sistema relacionadas.
Asignación de búfer de 64 MB: HECHO
Escribiendo datos en el primer búfer: HECHO
Copiando datos del primer archivo al segundo: HECHO
Liberación de búfer: HECHO
Eliminando archivos: HECHO
real 0m0.019s
usuario 0m0.Miles
sys 0m0.016s

Como puede ver, el código que usa las llamadas al sistema se ejecuta mucho más rápido que el equivalente de glibc.

Cosas para recordar

Las llamadas al sistema pueden aumentar el rendimiento y proporcionar funcionalidad adicional, pero no están exentas de desventajas. Tendrá que sopesar los beneficios que brindan las llamadas al sistema frente a la falta de portabilidad de la plataforma y, a veces, la funcionalidad reducida en comparación con las funciones de la biblioteca.

Al usar algunas llamadas al sistema, debe tener cuidado de usar los recursos devueltos por las llamadas al sistema en lugar de las funciones de la biblioteca. Por ejemplo, la estructura FILE utilizada para las funciones fopen (), fread (), fwrite () y fclose () de glibc no es la misma que el número de descriptor de archivo de la llamada al sistema open () (devuelto como un número entero). Mezclar estos puede dar lugar a problemas.

En general, las llamadas al sistema Linux tienen menos carriles de parachoques que las funciones glibc. Si bien es cierto que las llamadas al sistema tienen algún manejo e informe de errores, obtendrá una funcionalidad más detallada de una función glibc.

Y finalmente, unas palabras sobre seguridad. Las llamadas al sistema interactúan directamente con el kernel. El kernel de Linux tiene amplias protecciones contra las travesuras de la tierra del usuario, pero existen errores no descubiertos. No confíe en que una llamada al sistema validará su entrada o lo aislará de los problemas de seguridad. Es aconsejable asegurarse de que los datos que entregue a una llamada al sistema estén desinfectados. Naturalmente, este es un buen consejo para cualquier llamada a la API, pero no puede tener cuidado al trabajar con el kernel.

Espero que hayas disfrutado de esta inmersión más profunda en la tierra de las llamadas al sistema Linux. Para obtener una lista completa de llamadas al sistema Linux, consulte nuestra lista maestra.

5 mejores juegos de arcade para Linux
Hoy en día, las computadoras son máquinas serias que se usan para jugar. Si no puede obtener la nueva puntuación más alta, sabrá a qué me refiero. En ...
Batalla por Wesnoth 1.13.6 Desarrollo liberado
Batalla por Wesnoth 1.13.6 lanzado el mes pasado, es el sexto lanzamiento de desarrollo en el 1.13.x y ofrece una serie de mejoras, sobre todo en la i...
Cómo instalar League Of Legends en Ubuntu 14.04
Si eres fanático de League of Legends, esta es una oportunidad para que pruebes League of Legends. Tenga en cuenta que LOL es compatible con PlayOnLin...