¡Esta es una revisión vieja del documento!
/usr/include y /usr/local/include.char *directorioPtr, *pathPtr; pathPtr = getenv( "PATH" ); printf("%s\n", pathPtr);
Estas dos funciones devuelven 0 si el cambio ha sido correcto y -1 si no.
Las funciones que necesitamos llamar para listar el contenido de un directorio son:
Abriremos y cerraremos un directorio con las funciones opendir y closedir respectivamente, la primera devuelve una variable DIR que utilizaremos para llamar a readdir que a su vez de vuelve una variable de la estructura dirent que tiene el siguiente formato:
struct dirent { ino_t d_ino; // numero de i-node de la entrada de directorio off_t d_off; // offset wchar_t d_reclen; // longitud de este registro char d_name[MAX_LONG_NAME+1] // nombre de esta entrada }
La función readdir cada vez que se la llame devolverá un nombre de archivo distinto, recorrerá así el directorio; habrá pasado por todos cuando devuelva NULL.
Debido a que en Unix\Linux todo tiene una representación de fichero las funciones a más bajo nivel tienen que ver con estos. Los descriptores son la dirección en memoria necesaria para acceder a los recursos del sistema (ficheros, pantalla, sockets…) que tienen forma de ficheros.
Las funciones básicas para la apertura, creación, cerrado, escritura y lectura de descriptores son: open, creat, close, read y write.
Los procesos dentro del SO tienen un identificador denominado pid, este puede ser cogido mediante la función getpid(); también podremos coger el pid del padre mediante la función getppid().
Podremos crear procesos mediante las funciones system, exec o fork.
Utiliza el shell para lanzar un comando. El proceso donde es llamado quedará en espera del retorno de la función que será el integer correspondiente al retorno del proceso\comando lanzado.
int main(int argc, char** argv) { system("ls -la"); return 0; }
Duplica el proceso padre, copiando sus variables con sus respectivos valores en memoria, el proceso creado a partir de este será llamado proceso hijo y este proceso padre. El proceso hijo se ejecutará a partir de donde ha sido creado y tendrá un pid distinto al del padre. Aún así, la forma para diferenciarlos será el retorno de la función fork que en el proceso padre retornará el pid del proceso hijo, en cambio en el proceso hijo retornará un 0.
int main(int argc, char** argv) { if (fork() == 0) printf("Este es el proceso hijo\n"); else printf("Este es el proceso padre\n"); return 0; }
Reemplaza el proceso actual por otro proceso, es decir, detiene la ejecución del proceso donde es llamado y lanza el comando indicado, dando a la ejecución de este el identificador del proceso actual.
Las variantes del exec son:
execvp o execlp) buscan en el path actual el comando pasado (si el nombre de función no llevase p habría que indicar el path completo).execvp, execv o execve) aceptan la lista de parámetros del comando como un array de strings.execlp, execl o execle) aceptan la lista de parámetros del comando en formato de argumentos de C.
Una forma de trabajar con exec es, primero hacer un fork del proceso y luego lanzar un comando mediante exec
int child_pid; char* arg_list[] = { "ls", // El primer argumento ha de ser el nombre del programa "-l", "/", NULL // El último elemento de la lista ha de ser NULL }; child_pid = fork (); if (child_pid != 0) return child_pid; else { execvp ("ls", arg_list); fprintf (stderr, "Error en llamada de execvp\n"); abort (); } printf ("Done!\n"); return 0;
Para usar la función kill deberemos incluir signal.h y sys/types.h, dicha función acaba un proceso hijo indicado. Recibe por parámetro el pid del proceso y una señal de finalización, esta puede ser:
SIGINT, es como si el sistema enviase un ctrl+c.SIGTERM, el enviado por el comando del sistema kill.SIGKILL, la más poderosa ya que acaba el proceso sin contemplaciones.SIGABRT, SIGBUS, SIGSEGV, y SIGFPE.Una pipe es una conexión de la salida de un proceso a la entrada de otro. Esto es lo que ocurre cuando en la terminal hacemos:
command1 | command2
Internamente, lo que está pasando es:
FILE *popen(const char *command, const char *open_mode); int pclose(FILE *stream_to_close);
La forma más rápida de recibir los datos de un comando es mediante una pipe, y la forma más rápida de crear una pipe es utilizando las funciones popen y pclose. La primera (popen) devuelve un puntero a una variable FILE, recibe el comando que se lanzará y la forma en la que es la pipe (de lectura r o de escritura w), por lo que se podrá leer la salida con fread o escribir con fwrite. La función pclose cerrará una pipe abierta por popen.
FILE *read_fp; char buffer[BUFSIZ + 1]; int chars_read; memset(buffer, '\0', sizeof(buffer)); read_fp = popen("uname -a", "r"); if (read_fp != NULL) { chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp); if (chars_read > 0) printf("Output was:-\n%s\n", buffer); pclose(read_fp); exit(EXIT_SUCCESS); } exit(EXIT_FAILURE);
La función pipe crea una pipe a partir de un array de integers de tamaño 2, en este guardará dos descriptores de ficheros, uno de entrada (el primer entero del array, que es abierto como solo lectura) y otro de salida (el segundo entero del array, que es abierto como solo escritura). Una vez creado el pipe se podrán hacer lecturas y escrituras como si de un fichero se tratase.
Las pipes son perfectas para la comunicación entre procesos padre-hijo, se crea antes de llamar a fork y, como estos descriptores se comparten entre padre e hijo, una vez sea llamado uno podrá utilizar un descriptor para enviar y el otro para recibir. Es aconsejable que si los dos quisieran enviar y recibir se creasen dos pipes, y que se cerrasen en los procesos adecuados los descriptores que no se utilizarán.
Se utilizarán las funciones write en la pipe[1] y read en la pipe[2].
int tuberia [2]; int idson; char buffer[BSIZE]; int bytesReaded = 0; pipe(tuberia); idson = fork(); if (idson == 0) { // El hijo lee de la tuberia close (tuberia[1]); while ((bytesReaded = read(tuberia[0], buffer, BSIZE)) > 0) write(1, buffer, bytesReaded); close(tuberia[0]); } else { // El padre escribe por la tuberia close (tuberia[0]); strcpy(buffer, "hola caracola\n"); write(tuberia[1], buffer, strlen(buffer)); close(tuberia[1]); waitpid(idson, NULL, 0); }
Los threads se ejecutan dentro de un proceso que, a diferencia de estos, al crearse un thread no se copia su espacio de memoria sino que este es compartido con los demás threads y con el proceso principal.
Es el SO el que decide cuando un thread se ejecutará, es por ello que para la programación de estos no es correcto hacerlo pensando en que seguirán un orden concreto.
El tipo identificador de thread es denominado pthread este nombre viene de POSIX thread.
Para utilizar los pthreads debemos linkar la librería pthread (desde línea de comandos utilizando -lpthread) e incluir pthread.h.
Para crear un thread utilizamos la función pthread_create pasándole los siguientes argumentos:
void *nombre (void *param).Un thread puede finalizar de cualquiera de las siguientes formas:
pthread_exit dentro del thread.pthread_cancel.
Si el proceso principal acaba con un pthread_exit los threads vinculados a este no finalizarán y el proceso acabará cuando todos los threads acaben.
#include <pthread.h> #include <stdio.h> #define NUM_THREADS 5 void *PrintHello(void *threadid) { int tid; tid = (int)threadid; printf("Hello World! It's me, thread #%d!\n", tid); pthread_exit(NULL); } int main (int argc, char *argv[]) { pthread_t threads[NUM_THREADS]; int rc, t; for(t=0; t<NUM_THREADS; t++){ printf("In main: creating thread %d\n", t); rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t); if (rc){ printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } } pthread_exit(NULL); }
La función pthread_join bloquea el thread actual hasta que el thread indicado no acabe (se puede impedir que un thread sea joinable, es decir que no pueda esperarse, si se crea como detached, para ello hay que hacerlo con los atributos de este).
La función pthread_self devuelve el identificador del thread actual.
Consiste en la realización de operaciones atómicas, esto es que no pueden ser interrumpidas por el cambio de thread en ejecución realizado por el procesador, para que los threads no accedan a recursos compartidos entre ellos a la vez lo que provocaría acciones erróneas.
Entre otras formas de sincronización existen: los mutexes, los semáforos, las variables condicionales…
Un mutex es como una puerta a una porción de memoria compartida por los threads con la cual hacen operaciones. Cuando un thread pasa por dicha puerta la cierra (si estubiese cerrada no podría pasar), operación de mutex lock, cuando acaba las operaciones abre la puerta, unlock. La metodología para utilizar los mutex es la siguiente:
Un bloqueo del mutex que no bloquea el thread es una llamada a la función trylock.
Cuando se inicializa un mutex este lo hace desbloqueado.
Las funciones que se utilizan son:
Podemos inicializar un mutex con los atributos por defecto utilizando la macro PTHREAD_MUTEX_INITIALIZER:
pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
Las interficies gráficas (GUI) en Linux, en su nivel más bajo, son manejadas por las X Window (o X11 o símplemente las X). Consiste en una arquitectura cliente-servidor, en la que el servidor realiza el enlace con el hardware (ratón, pantalla, teclado…) y el cliente recibe datos de este y muestra las aplicaciones, por lo que podemos decir que los sistemas de escritorios son los clientes de las X. Una de las ventajas de este modo de comunicación es que el cliente no tiene por qué estar en la misma máquina que el servidor, ni tampoco tiene por qué ser una pantalla ya que puede ser cualquier dispositivo gráfico.
El X Server gestiona la pantalla, la entrada por teclado y por ratón. Cuando el usuario hace click sobre una posición concreta de la pantalla el X Server envia los datos a la aplicación adecuada. Los mensajes que recibirá el X Server serán de redibujar ventanas.
La comunicación entre cliente-servidor se realiza mediante el XProtocol.
Xlib es la librería que se ha de agregar para trabajar a bajo nivel con las X, es tan de bajo nivel que únicamente se utiliza para dibujar sobre la ventana (por ejemplo un menú requeriría de un gran número de líneas de código). Es por eso que para desarrollar una aplicación generalmente se utiliza un Toolkit (GTK, Qt…).
Para desarrollar con XLib deberemos linkar la librería X11 e incluir X11/Xlib.h y X11/Intrinsic.h (para poder disponer de este último necesitarás añadir el paquete libxt-dev).
Las funciones de XLib necesitan de una variable Display, esta equivale al enlace entre el cliente y el servidor, para conseguir una debemos llamar a la funcion XOpenDisplay la cual recibe el nombre de la pantalla. El nombre de la pantalla por defecto podemos encontrarlo en la variable de entono DISPLAY (aunque también podríamos pasarle NULL):
Display *dpy = XOpenDisplay(getenv("DISPLAY"));
Para la creación de ventanas utilizaremos la funciones XCreateWindow (función general para crear nuevas ventanas) y la XCreateSimpleWindow (función que crea una subventana utilizando los valores por defecto de la aplicación padre). Estas funciones requieren de una variable Window (la ventana padre) y una variable Visual (almacena la estructura de color de las X). Para conseguirlas podemos utilizar las siguientes funciones:
Visual *vis = DefaultVisual(dpy,0); // Devuelve el Visual por defecto de la Dislay Window w = DefaultRootWindow(dpy); // Devuelve la ventana raíz de la Display
Las funciones XCreateWindow y XCreateSimpleWindow devuelven una variable Window, esta no es más que un integer identificador de la ventana. Sus definiciones son:
Window XCreateWindow(display, parent, x, y, width, height, border_width, depth, class, visual, valuemask, attributes)
Display *display; // Display utilizado
Window parent; // Ventana padre
int x, y; // Posición de la ventana
unsigned int width, height; // Tamaño
unsigned int border_width; // Tamaño del borde
int depth; // Profundidad de color
unsigned int class; // Clase Visual
Visual *visual // La variable Visual
unsigned long valuemask; //
XSetWindowAttributes *attributes; // Atributos de la ventana
Window XCreateSimpleWindow(display, parent, x, y, width, height, border_width, border, background)
Display *display;
Window parent;
int x, y;
unsigned int width, height;
unsigned int border_width;
unsigned long border;
unsigned long background;
Tanto la clase Visual como la profundidad de color podemos coger las de por defecto mediante el comando CopyFromParent:
Window w; vis = DefaultVisual(dpy,0); w = XCreateWindow(dpy, DefaultRootWindow(dpy), 100, 100, 320, 200, 0, CopyFromParent, CopyFromParent, vis, 0, NULL);
Una vez creada una ventana necesitaremos hacerla visible, para ello debemos mapearla mediante la función XMapWindow.
Podremos asignar el nombre (título) de la ventana con XStoreName.
Mientras llamemos a la función XCheckWindowEvent la ventana se mostrará.
main() { Display* dpy = XOpenDisplay(NULL); Visual *vis; Window w; vis = DefaultVisual(dpy,0); w = XCreateWindow(dpy, DefaultRootWindow(dpy), 100, 100, 320, 200, 0, CopyFromParent, CopyFromParent, vis, 0, NULL); XStoreName(dpy, w, "hello"); XMapWindow(dpy, w); XCheckWindowEvent(dpy,w,0,0); while(1) {} }
La función para desmapear una ventana es XUnmapWindow y para destruir la ventana XDestroyWindow. Si quisiesemos destruir todas las sub-ventanas de una aplicación utilizaríamos XDestroySubWindows.
Para interceptar eventos de en las X debemos indicar de qué eventos queremos se nos avise, para ello XSelectInput que recibe la Display, la Window y los siguientes eventos de la lista:
0, KeyPressMask, KeyReleaseMask, ButtonPressMask, ButtonReleaseMask, EnterWindowMask, LeaveWindowMask, PointerMotionMask, PointerMotionHintMask, Button1MotionMask, Button2MotionMask, Button3MotionMask, ButtonMotionMask, ExposureMask, VisibilityChangeMask, ResizeRedirectMask, FocusChangeMask
Para tratar los eventos tenemos las siguientes funciones:
gcc permite insertar código ensamblador en medio de código C, este código es denominado inline assembly y, al ser en Linux, sigue la sitnaxis AT&T.
Para la inserción de código inline utilizaremos la función especial es __asm__, su sintaxis es:
__asm__ ("instrucciones" : lista_salida : lista_entrada : lista_destruida);
Las instrucciones es un string donde está el código ensamblador. La lista de salida son los registros donde se guardará algún dato. La lista de entrada son los registros y variables que se utilizaran dentro del código. La lista destruida son los registros que han sido modificados por las instrucciones (para que gcc sepa cuales ha de reservar en un futuro). A las variables y registros utilizados en el código se les hace referencia a partir de un índice (que empieza por 0 (indicado con %) y cuenta a partir de la lista de salida y se les une las demás).
int i = 100; __asm__ ("movl %0, %%eax" : : "g" (i));
En este caso %0 corresponde al valor de entrada i; el acceso al registro eax se hace mediante %% (esto se ha de hacer para cada registro); lo que el código hace es asignar el valor de i (100) a eax.
La directiva “g”(i) indica al compilador donde almacenar el parámetro. “r” le permitiría guardar la variable en cualquier registro qeu no esté en uso. “a” en ax\eax, “b” en bx\ebx, “c” en cx\ecx, “d” en dx\edx, “D” en di\edi, “S” en si/esi, etc.
void *memcpy( void *dest, const void *src, unsigned int n) { __asm__("cld ; rep ; movsb": : "c" ((unsigned int) n), "S" (src), "D" (dest)); return dest; }
También podemos utilizar la directiva volatile que optimiza el código.
int i=0, j=1; __asm__ __volatile__(" \ pushl %%eax \n \ movl %0, %%eax \n \ addl %1, %%eax \n \ movl %%eax, %0 \n \ popl %%eax " : : "g" (i), "g" (j) );
Para indicar valores constantes utilizaremos $ delante del valor.
Las variables de salida han de ser precedidas por =.
int i=0, j=1, k=0; __asm__ __volatile__(" \ pushl %%eax \n \ movl %1, %%eax \n \ addl %2, %%eax \n \ movl %%eax, %0 \n \ popl %%eax" : "=g" (k) : "g" (i), "g" (j) ); /* k = i + j; */
Si quisieramos decir que después de la llamada al código ensamblador no se toque el ecx haríamos:
__asm__ __volatile__ (" ..." : : : "ecx");
Podemos insertar etiquetas locales dentro del ensamblador en línea, la llamada a estas debe terminar por una b o una f según si dicha etiqueta esta después o antes de la instrucción de salto:
__asm__ __volatile__(" 0: \n \ ... jmp 0b \n \ ... jmp 1f \n \ ... 1:\n \ ..." );
Son los compiladores de C y C++ (gcc y g++ respectivamente) en linux.
gcc hola.c.gcc -o hola hola.c-c hace que no salga un ejecutable sino uno con código objeto.-l seguido del nombre de la librería, pero seguido sin espacios; por ejemplo, tenemos la librería ddraw.lib o gdi32.lib haremos -lddraw o -lgdi32.-s saca el archivo en ensamblador del compilado.
Si compilamos un código con el parámetro -g podremos ejecutar el comand gdb <archivo salida> y nos aparecerá una consola de debugación, los comandos que pueden sernos útiles son:
break file:line, para poner un breakpoint en una línea del programa.