Tutorial de llamadas al sistema de Linux con C

Tutorial de llamadas al sistema de Linux con C
En nuestro último artículo sobre llamadas al sistema de Linux, definí una llamada del sistema, discutí las razones por las que uno podría usarlos en un programa y profundicé en sus ventajas y desventajas. Incluso di un breve ejemplo en la asamblea 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, vamos a utilizar llamadas de sistema reales para hacer un trabajo real en nuestro programa C. Primero, revisaremos si necesita usar una llamada del sistema, luego proporcionaremos un ejemplo usando la llamada sendFile () que puede mejorar drásticamente el rendimiento de la copia del archivo. Finalmente, repasaremos algunos puntos para recordar mientras usan llamadas al sistema Linux.

¿Necesita una llamada del sistema??

Si bien es inevitable que use una llamada del sistema en algún momento de su carrera de desarrollo de C, a menos que se apunte a una funcionalidad de alto rendimiento o 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 y 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 características (i.mi. Mejor manejo de errores, IO formateado, etc.) y funcionará en cualquier sistema de GLIBC del sistema.

Por otro lado, hay momentos en que el rendimiento intransigente y la ejecución exacta son críticos. El envoltorio que proporciona Fread () va a agregar sobrecarga, y aunque menor, no es del todo transparente. Además, es posible que no desee o necesite las características adicionales que proporciona el envoltorio. En ese caso, se le sirve mejor con una llamada del sistema.

También puede usar llamadas del sistema para realizar funciones aún no compatibles con GLIBC. Si su copia de GLIBC está actualizada, esto difícilmente será un problema, pero el desarrollo de distribuciones más antiguas con núcleos más nuevos podría requerir esta técnica.

Ahora que ha leído las renuncias, las advertencias y los desvíos potenciales, ahora profundicemos en algunos ejemplos prácticos.

¿En qué CPU estamos??

Una pregunta que la mayoría de los programas probablemente no piensen hacer, pero no obstante. Este es un ejemplo de una llamada del sistema que no se puede duplicar con GLIBC y no está cubierto con un envoltorio 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 del 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 del 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. Recuerde que la mayoría de los argumentos, especialmente para las devoluciones, requerirán punteros para matrices de carbón 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 CPU actual y el nodo NUMA a través de la llamada del sistema
// Tenga en cuenta que esto no tiene un envoltorio GLIBC, por lo que debemos llamarlo directamente
syscall (sys_getcpu, & cpu, & nodo, null);
// Mostrar información
printf ("Este programa se ejecuta en CPU Core %U y Numa Node %U.\ n \ n ", CPU, nodo);
regresar 0;

Para compilar y ejecutar:
GCC Ejemplo1.C -O Ejemplo1
./Ejemplo 1

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

SendFile: Performance superior

SendFile proporciona un excelente ejemplo de mejora del rendimiento a través de llamadas al sistema. La función sendFile () copia los datos de un descriptor de archivo a otro. En lugar de usar múltiples funciones Fread () y fwrite (), SendFile realiza la transferencia en el espacio del núcleo, reduciendo 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, vamos a usar los métodos de lectura/escritura estándar en la biblioteca estándar. En el otro, usaremos llamadas del sistema y la llamada sendFile () para expulsar estos datos de una ubicación a otra.

prueba1.C (GLIBC)

#incluir
#incluir
#incluir
#incluir
#define buffer_size 67108864
#define buffer_1 "buffer1"
#define buffer_2 "buffer2"
int main ()
Archivo *fout, *fin;
printf ("\ ni/o prueba con funciones de GLIBC tradicionales.\ n \ n ");
// agarra un búfer buffer_size.
// El búfer tendrá datos aleatorios, pero no nos importa eso.
printf ("Asignar 64 mb búfer:");
char *buffer = (char *) malloc (buffer_size);
printf ("hecho \ n");
// Escribe el búfer a Fout
printf ("Escribir datos para el primer búfer:");
fout = fopen (buffer_1, "wb");
fwrite (buffer, sizeof (char), buffer_size, fout);
fclose (fout);
printf ("hecho \ n");
printf ("Copiar datos del primer archivo al segundo:");
fin = fopen (buffer_1, "rb");
fout = fopen (buffer_2, "wb");
fread (búfer, sizeof (char), buffer_size, fin);
fwrite (buffer, sizeof (char), buffer_size, fout);
fclose (aleta);
fclose (fout);
printf ("hecho \ n");
printf ("Freeing Buffer:");
gratis (búfer);
printf ("hecho \ n");
printf ("Eliminar archivos:");
eliminar (buffer_1);
eliminar (buffer_2);
printf ("hecho \ n");
regresar 0;

test2.C (llamadas del sistema)

#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#define buffer_size 67108864
int main ()
int fout, aleta;
printf ("\ ni/o prueba con sendFile () y llamadas al sistema relacionadas.\ n \ n ");
// agarra un búfer buffer_size.
// El búfer tendrá datos aleatorios, pero no nos importa eso.
printf ("Asignar 64 mb búfer:");
char *buffer = (char *) malloc (buffer_size);
printf ("hecho \ n");
// Escribe el búfer a Fout
printf ("Escribir datos para el primer búfer:");
fout = open ("buffer1", o_rdonly);
escribir (fout y buffer, buffer_size);
cerrar (fout);
printf ("hecho \ n");
printf ("Copiar datos del primer archivo al segundo:");
fin = abre ("buffer1", o_rdonly);
fout = open ("buffer2", o_rdonly);
sendFile (fout, fin, 0, buffer_size);
cerrar (aleta);
cerrar (fout);
printf ("hecho \ n");
printf ("Freeing Buffer:");
gratis (búfer);
printf ("hecho \ n");
printf ("Eliminar archivos:");
Unlink ("Buffer1");
Unlink ("Buffer2");
printf ("hecho \ n");
regresar 0;

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

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

APT Instalar esencials de construcción

Luego compilar con:

prueba de GCC1.c -o test1 && gcc test2.C -O test2

Para ejecutar ambos y probar el rendimiento, ejecute:

tiempo ./test1 && time ./Test2

Deberías obtener resultados como este:

Prueba de E/S con funciones tradicionales de GLIBC.

Asignación de 64 MB Buffer: Hecho
Escribir datos para el primer búfer: hecho
Copiar datos del primer archivo al segundo: Hecho
Liberación de búfer: hecho
Eliminar archivos: hecho
Real 0m0.397s
usuario 0m0.000s
SYS 0M0.203S
Prueba de E/S con SendFile () y llamadas al sistema relacionadas.
Asignación de 64 MB Buffer: Hecho
Escribir datos para el primer búfer: hecho
Copiar datos del primer archivo al segundo: Hecho
Liberación de búfer: hecho
Eliminar archivos: hecho
Real 0m0.019S
usuario 0m0.000s
SYS 0M0.016S

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

Cosas para recordar

Las llamadas del sistema pueden aumentar el rendimiento y proporcionar una funcionalidad adicional, pero no están exentos de desventajas. Tendrá que sopesar que las llamadas del sistema de beneficios proporcionan a la falta de portabilidad de la plataforma y, a veces, una 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 de las llamadas del sistema en lugar de las funciones de la biblioteca. Por ejemplo, la estructura de archivo utilizada para las funciones FOPEN (), fread (), fwrite () y fclose () de GLIBC no son las mismas que el número de descriptor de archivo desde la llamada del sistema Open () (devuelto como un entero). Mezclar estos puede conducir a problemas.

En general, las llamadas del sistema Linux tienen menos carriles para el parachoques que las funciones GLIBC. Si bien es cierto que las llamadas del sistema tienen un manejo e informes de errores, obtendrá una funcionalidad más detallada de una función GLIBC.

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

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