Espacio de tecnologia, software libre y sus derivados. Una horda de monos entrenados escriben de vez en cuando por aqui algunas noticias, opiniones e incluso alguna que otra cosa fuera del tema. Maqueros, favor de abstenerse que no somos lo suficientemente guapos.

Piano daemon

Archive for November, 2009

Como obtener un segmentation fault en C

Las causas comunes como obtendremos esto son:

* Tratando de escribir a un puntero NULL

      char *foo = NULL;
      strcpy(foo, "bang!");
* Usando un puntero no inicializado

      char *foo;
      strcpy(foo, "bang!");
* Accesar mas alla de los limites de un arreglo

int bar[20];
bar[27] = 6;
* Tratando de almacenar algo sobre memoria de solo lectura

char *foo = "My string";
strcpy(foo, "bang!");
* Y por ultimo utilizando de manera tonta free y malloc

char bar[80];
free(bar);

o

char *foo = malloc(27);
free(foo);
free(foo);

Sintesis04 - Estados de los procesos en MINIX

Aunque cada proceso es una entidad independiente, con su propio contador de programa y estado interno, los procesos a menudo necesitan interactuar con otros procesos. Un proceso podría generar ciertas salidas que otro proceso utiliza como entradas. En el comando de shell

$ cat capítulol capítulo2 capítulo3 | grep árbol

El primer proceso, que ejecuta cat, concatena y envía a la salida tres archivos. El segundo proceso, que ejecuta grep, selecciona todas las líneas que contienen la palabra “árbol”. Dependiendo de las velocidades relativas de los dos procesos (que a su vez dependen de la complejidad relativa de los programas y de cuánto tiempo de CPU ha ocupado cada uno), puede suceder que grep esté listo para ejecutarse, pero no haya entradas esperando ser procesadas por él. En tal caso, grep deberá bloquearse hasta que haya entradas disponibles.

Cuando un proceso se bloquea, lo hace porque le es imposible continuar lógicamente, casi siempre porque está esperando entradas que todavía no están disponibles. También puede ser que un programa que conceptualmente está listo y en condiciones de ejecutarse sea detenido porque el sistema operativo ha decidido asignar la CPU a otro proceso durante un tiempo. Estas dos condiciones son totalmente distintas. En el primer caso, la suspensión es inherente al problema (no es posible procesar la línea de comandos del usuario antes de que éste la teclee). En el segundo caso, se trata de un tecnicismo del sistema (no hay suficientes CPU para darle a cada proceso su propio procesador privado).

En MINIX, cuando un proceso lee de una tuberia (pipe) o archivo especial (p. ej., una terminal) y no hay entradas disponibles, se bloquea automáticamente.

transiciones_procesos

Las transiciones 2 y 3 son causadas por el planificador de procesos, una parte del sistema operativo, sin que el proceso se entere siquiera de ellas. La transición 2 ocurre cuando el planificador decide que el proceso en ejecución ya se ejecutó durante suficiente tiempo y es hora de dejar que otros procesos tengan algo de tiempo de CPU. La transición 3 ocurre cuando todos los demás procesos han disfrutado de una porción justa y es hora de que el primer proceso reciba otra vez la CPU para ejecutarse. El tema de la planificación, es decir, de decidir cuál proceso debe ejecutarse cuándo y durante cuánto tiempo, es muy importante. Se han inventado muchos algoritmos para tratar de equilibrar las exigencias opuestas de eficiencia del sistema global y de equitatividad para los procesos individuales.

La transición 4 ocurre cuando acontece el suceso externo que un proceso estaba esperando (como la llegada de entradas). Si ningún otro proceso se está ejecutando en ese instante, se dispara de inmediato la transición 3, y el proceso comienza a ejecutarse. En caso contrario, el proceso tal vez tenga que esperar en el estado listo durante cierto tiempo hasta que la CPU esté disponible.

OJO:

El “planificador” no sólo se refiere a planificación de procesos, sino también a manejo de interrupciones y toda la comunicación entre procesos.

Sintesis03 - El porque de las llamadas al sistema para señalización en MINIX

Aunque casi todas las formas de comunicación entre procesos son planeadas, existen situaciones en las que se requiere una comunicación inesperada. Por ejemplo, si un usuario accidentalmente le pide a un editor de textos que liste todo el contenido de un archivo muy largo, y luego se percata de su error, necesita alguna forma de interrumpir el editor.

En MINIX, el usuario puede pulsar la tecla DEL (suprimir) del teclado, la cual envía una señal al editor. El editor atrapa la señal y detiene el listado.

Donde mas se puede emplear las llamadas al sistema para Señalizacion en MINIX?
* Para informar de ciertas trampas detectadas por el hardware, como una instrucción no permitida o un desbordamiento de punto flotante.
* Las expiraciones de tiempo también se implementan como señales ( En muchas aplicaciones de tiempo real se hace necesario interrumpir un proceso después de un intervalo de tiempo específico a fin de hacer algo, como retransmitir un paquete que tal vez se perdió en una línea de comunicación no confiable ).

OJO:

Cuando se envía una señal a un proceso que no ha anunciado su disposición a aceptar esa señal, el proceso simplemente se termina sin más.

Sintesis02 - Introduccion a las Llamadas al sistema en MINIX

Armados con nuestro conocimiento general de cómo MINIX maneja los procesos y los archivos, ahora podemos comenzar a examinar la interfaz entre el sistema operativo y sus programas de aplicación, es decir, el conjunto de llamadas al sistema. Si bien esta explicación se refiere específicamente a posix (Norma Internacional 9945-1), y por tanto también a MINIX, casi todos los sistemas operativos modernos tienen llamadas al sistema que realizan las mismas funciones, aunque los detalles sean diferentes. Puesto que el mecanismo real de la emisión de una llamada al sistema depende mucho de la máquina, y a menudo debe expresarse en código de ensamblador, se proporciona una biblioteca de procedimientos que permite efectuar llamadas al sistema desde programas en C.

A fin de hacer más claro el mecanismo de las llamadas al sistema, examinemos brevemente READ (leer) Esta llamada tiene tres parámetros: el primero especifica el archivo, el segundo especifica el buffer, y el tercero especifica el número de bytes por leer. Una llamada de READ desde un programa en C podría verse así:

cuenta = read(file, buffer, nbytes);

La llamada al sistema (y el procedimiento de biblioteca) devuelve en cuenta el número de bytes que realmente se leyeron. Este valor normalmente es igual a nbytes, pero puede ser menor, si, por ejemplo, se llega al fin del archivo durante la lectura.

Si la llamada al sistema no puede ejecutarse, ya sea a causa de un parámetro no válido o de un error de disco, se asignará el valor — 1 a cuenta, y el número del error se colocará en una variable global, errno. Los programas siempre deben revisar los resultados de una llamada al sistema para ver si ocurrió un error. MINIX tiene un total de 53 llamadas al sistema

Las llamadas al sistema en MINIX se clasifican en:
* Administracion de Archivos
* Administracion de Procesos
* Administracion del Tiempo
* Señales
* Proteccion relacionada con usuarios y grupos

Sintesis01 - Introduccion a los Procesos en MINIX

Un concepto clave en MINIX, y en todos los sistemas operativos, es el proceso. Un proceso es básicamente un programa en ejecución. Cada proceso tiene asociado un espacio de direcciones, una lista de posiciones de memoria desde algún mínimo (usualmente O) hasta algún máximo, que el proceso puede leer y escribir.

Que contiene el espacio de Direcciones de un Proceso?
* El programa ejecutable (o Segmento de Texto)
* Los datos del programa
* La Pila o Stack del programa

Los procesos en MINIX tienen su memoria dividida en tres segmentos: el segmento de texto (esto es,el código de programa), el segmento de datos (es decir, las variables) y el segmento de pila. El segmento de datos crece hacia arriba y el de pila lo hace hacia abajo, como se muestra en la Figura siguiente:

Segmentos de un proceso

A cada proceso también se asocia un conjunto de registros, que incluyen el contador del programa, el apuntador de la pila y otros registros de hardware, así como toda la demás información necesaria para ejecutar el programa.

Tabla de procesos
Arreglo (o lista enlazada) de estructuras, una para cada proceso existente en ese momento. Toda la información acerca de cada proceso, aparte del contenido de su propio espacio de direcciones se almacena sobre esta tabla.

Procesos padre
Un proceso puede crear uno o más procesos distintos (denominados procesos hijos) y éstos a su vez pueden crear procesos hijos, pronto llegamos a la estructura de árbol de procesos. Los procesos relacionados que están cooperando para realizar alguna tarea a menudo necesitan comunicarse
entre sí y sincronizar sus actividades. Esta comunicación se llama comunicación entre procesos (IPC).

El administrador del sistema asigna un uid (identificador de usuario) a cada persona autorizada para usar MINIX. Cada proceso iniciado en MINIX tiene el uid de la persona que lo inició. Un proceso hijo tiene el mismo uid que su padre.

Sintesis00 - Empezando con MINIX

En todas estas sintesis sobre MINIX todo lo que se diga acerca de MINIX, a menos que se refiera al código en sí, también aplica a UNIX. Muchos de estos comentarios también aplican a otros sistemas. Esto debe tenerse siempre presente al leer el libro. Como acotación, es posible que unas cuantas palabras acerca de LINUX y su relación con MINIX sean de interés para algunos lectores.

Poco después de liberarse MINIX, se formó un grupo de noticias de USENET para hablar de él. En pocas semanas, este grupo tenía 40000 suscriptores,
la mayor parte de los cuales quería agregar enormes cantidades de nuevas capacidades a MINIX a fin de hacerlo más grande y mejor (bueno, al menos más grande). Cada día, varios cientos de ellos ofrecían sugerencias, ideas y fragmentos de código. El autor de MINIX resistió con éxito esta arremetida durante varios años, a fin de mantener a MINIX lo suficientemente pequeño y aseado como para que los estudiantes lo entendieran. Gradualmente, la gente comenzó a convencerse de que su posición era inamovible. Finalmente, un estudiante finlandés, Linus Torvalds, decidió escribir un clon de MINIX que pretendía ser un sistema de producción con abundantes capacidades, más que una herramienta educativa.
Así fue como nació LINUX.

Como funcionan las autotools y que coños son estas

Yo fui un usuario de Slackware
Y solia compilarme todas las fuentes de lo que requeria… y estas siempre usaban el configure.
Aqui ya te puedes dar cuenta como fregados se puede generar una aplicacion con su configure.. para que la puedas distribuir..

PDF de las Autotools

Mejores Practicas al usar autotools

Comunicacion Interprocesos System V sobre FreeBSD

El problema es más complejo de lo que parece: no es únicamente una cuestión de sincronización de la ejecución de los procesos, sino también de compartición de datos, tanto en modo lectura como en escritura.

Hablemos sobre algunos problemas clásicos de acceso concurrente de datos; si dos procesos leen el mismo conjunto de datos esto obviamente no es un problema, y la ejecución es CONSISTENTE. Ahora si dejamos a uno de los dos procesos modificar el conjunto de datos: el otro devolverá resultados diferentes de acuerdo al momento en el cual lee el conjunto de datos, antes o después de la escritura por parte del primer proceso.

Entendemos inmediatamente cómo es de importante gestionar correctamente estas situaciones: el riesgo de INCONSISTENCIA de datos es grande e inaceptable. Intentemos pensar que los conjuntos de datos representan nuestra cuenta bancaria y nunca subestimaremos este problema.

La solución a este problema viene dada por un conjunto de primitivas de la librería estándar conocida como SysV IPC (System V InterProcess Communication).

Llaves SysV
Antes de encarar los argumentos estrictamente relacionados con la teoría de la concurrencia y su implementación introduciremos una estructura SysV típica: las llaves IPCS. Una llave IPC es un número utilizado para identificar unívocamente una estructura de control IPC (descrita más adelante), pero también puede ser utilizada para generar identificadores genéricos, p.e. para organizar estructuras no IPC. Una clave se puede crear con la función ftok()

key_t ftok(const char *pathname, int proj_id);

que utiliza el nombre de un fichero existente (pathname) y un entero. No se puede asegurar que la clave sea única, porque los parámetros tomados del fichero (número de i-nodo y número de dispositivo) pueden dar lugar a combinaciones idénticas. Una buena solución consiste en crear una pequeña librería que revise las claves asignadas y evite los duplicados.

Semáforos
Los semáforos pueden utilizarse para controlar el acceso a recursos: el valor del semáforo representa el número de procesos que pueden acceder al recurso; cada vez que un proceso accede al recurso el valor del semáforo debe ser decrementado e incrementado de nuevo cuando el recurso sea liberado. Si el recurso es exclusivo (p.e. sólo un proceso puede acceder) el valor inicial del semáforo será 1.

Consideremos un caso práctico, en el que el se utilizará el tipo del semáforo: imaginemos que tenemos un buffer en el cual varios procesos S1,…,Sn pueden escribir pero del cual únicamente un proceso L puede leer; además, las operaciones no se pueden realizar al mismo tiempo (i.e. en un momento dado sólo un proceso está operando con el buffer). Obviamente los procesos S pueden escribir siempre excepto cuando el buffer está lleno, mientras que el proceso L puede leer sólo si el buffer no está vacío. Así, necesitamos tres semáforos: el primero gestionará el acceso al recurso, el segundo y el tercero seguirá la pista de cuántos elementos hay en el buffer (veremos más adelante por qué dos semáforos no son suficientes).

Considerando que el acceso al buffer es exclusivo el primer semáforo será uno binario (su valor será 0 o 1), mientras que el segundo y el tercero reflejará valores relacionados a la dimensión del buffer.

Aprendamos cómo se implementan los semáforos en C utilizando las primitivas SysV. La función que crea un semáforo es semget()

int semget(key_t key, int nsems, int semflg);

donde key es la clave IPC, nsems es el número de semáforos que queremos crear y semflg es el control de acceso implemenado con 12 bits, los 3 primeros relacionados con las políticas de creación y los otros 9 con los accesos de lectura y escritura del usuario, grupo y otros (nótese la similitud con el sistema de ficheros Unix).Tal y como notamos SysV gestiona conjuntos de semáforos en vez de semáforos únicos, resultando un código más compacto.

Creemos nuestro primer semáforo

#include "stdio.h"
#include "stdlib.h"
#include "linux/types.h"
#include "linux/ipc.h"
#include "linux/sem.h"

int main(void){
key_t key;
int semid;

key = ftok("/etc/fstab", getpid());

/* creamos un conjunto de semaforos con un solo semaforo: */
semid = semget(key, 1, 0666 | IPC_CREAT);

return 0;
}

Avanzando un poco más, tenemos que aprender cómo gestionar y eliminar semáforos; la gestión del semáforo se realiza mediante la primitiva semctl()

int semctl(int semid, int semnum, int cmd, ...)

que realiza la operación identificada por cmd en el conjunto semid y (si la acción lo requiere) en el semáforo semnum. Introduciremos algunas opciones cuando las necesitemos, pero en la página del man se puede encontrar una lista completa. Dependiendo de la acción cmd puede ser necesario especificar otro argumento para la función, cuyo tipo es

union semun {
int val;                  /* valor para SETVAL */
struct semid_ds *buf;     /* buffer para IPC_STAT, IPC_SET */
unsigned short *array;    /* array para GETALL, SETALL */
/* parte especifica Linux: */
struct seminfo *__buf;    /* buffer para IPC_INFO */
};

Para establecer el valor del semáforo se debe utilizar la directiva SETVAL y el valor se tiene que especificar en la union semun; modifiquemos el programa anterior estableciendo el valor del semáforo a 1

[...]

/* creamos un conjunto de semaforos con un solo semaforo: */
semid = semget(key, 1, 0666 | IPC_CREAT);

/* establecemos el valor del semaforo de 0 a 1 */
arg.val = 1;
semctl(semid, 0, SETVAL, arg);

[...]

Entonces tenemos que liberar el semáforo liberando las estructuras utilizadas para su gestión; esta tarea se realiza con la directiva IPC_RMID de semctl. Esta directiva elimina el semáforo y envía un mensaje a todos los procesos esperando para conseguir el acceso al recurso. Una última modificación al programa es

[...]

/* establecemos el valor del semaforo de 0 a 1 */
arg.val = 1;
semctl(semid, 0, SETVAL, arg);

/* liberamos el semaforo */
semctl(semid, 0, IPC_RMID);

[...]

Tal y como hemos visto antes, cear y gestionar una estructura para controlar una ejecución concurrente no es difíl; cuando introduzcamos gestión de errores las cosas se volverán más complicadas, pero sólo desde el punto de vista de la complejidad del código.

El semáforo se puede utilizar a través de la función semop()

int semop(int semid, struct sembuf *sops, unsigned nsops);

donde semid es el identificador del conjunto, sops un array que contiene las operaciones a realizar y nsops el número de estas operaciones. Cada operación está representada por una estructura sembuf.

unsigned short sem_num; short sem_op; short sem_flg;

i.e. por el número de semáforo en el conjunto (sem_num), la operación (sem_op) y un flag estableciendo la política de espera; por ahora dejemos sem_flg a 0. Las operaciones que podemos especificar son números enteros y siguen estas reglas:

   1. sem_op < 0

Si el valor absoluto del semáforo es mayor o igual que el de sem_op la operación continúa y se añade sem_op al valor del semáforo (realmente se resta, número negativo). Si el valor absoluto de sem_op es menor que el valor del semáforo el proceso se duerme hasta que esté disponible tal número de recursos.

   2. sem_op = 0
      El proceso se duerme hasta que el valor del semáforo alcance 0.
   3. sem_op > 0
      El valor de sem_op se añade al valor del semáforo, liberando los recursos obtenidos previamente.

El siguiente programa intenta mostrar cómo utilizar semáforos implementando el ejemplo del buffer anterior: crearemos 5 procesos denominados W (escritores) y un proceso R (lector). Cada proceso W intenta obtener el control del recurso (el buffer) bloqueándolo a través de un semáforo y, si el buffer no está vacío, inserta un elemento en él y libera el recurso. El proceso R intenta bloquear el recurso, toma un elemento del buffer si no está vacío y desbloquea el recurso.

La lectura y escritura del buffer es sólo virtual: esto sucede porque, tal y como se vió en el artículo anterior, cada proceso tiene su propio espacio de memoria y no puede acceder al de otro proceso. Esto hace imposible la correcta gestión del buffer con 5 procesos, porque cada uno verá su propia copia del buffer. Esto cambiará cuando hablemos sobre la memoria compartida, pero mejor aprendamos las cosas paso a paso.

¿Por qué necesitamos 3 semáforos? El primero (número 0) actúa como un bloqueo de acceso al buffer, y tiene un valor máximo de 1, mientras que los otros dos gestionan las condiciones de desbordamiento, tanto por arriba como por abajo. Un único semáforo no puede gestionar ambas situaciones, debido a que semop actúa en un único sentido.

Aclaremos un poco esto: con un semáforo (llamado O), cuyo valor representa el número de espacios vacíos en el buffer. Cada vez que un proceso W inserta algo en el buffer, decrementa el valor del samáforo en una unidad, hasta que el valor llegue a 0, i.e. el buffer esté lleno. Este semáforo no puede gestionar la condición de desbordamiento por abajo: el proceso R, de hecho, puede incrementar su valor sin límites. Necesitamos, por tanto, un semáforo especial (llamado U), cuyo valor represente el número de elementos en el buffer. Cada vez que un proceso W inserte un elemento en el buffer también incrementará el valor del semáforo U y decrementará el del semáforo O. Por el contrario, el proceso R decrementará el valor del semáforo U e incrementará el del semáforo O.

Así, la condición de desbordamiento por arriba estará identificada por la imposibilidad de decrementar el semáforo O, y la condición de desbordamiento por abajo por la imposibilidad de decrementar el semáforo U.

#include "stdio.h"
#include "stdlib.h"
#include "errno.h"
#include "linux/types.h"
#include "linux/ipc.h"
#include "linux/sem.h"

int main(int argc, char *argv[])
{
/* IPC */
pid_t pid;
key_t key;
int semid;
union semun arg;
struct sembuf lock_res = {0, -1, 0};
struct sembuf rel_res = {0, 1, 0};
struct sembuf push[2] = {1, -1, IPC_NOWAIT, 2, 1, IPC_NOWAIT};
struct sembuf pop[2] = {1, 1, IPC_NOWAIT, 2, -1, IPC_NOWAIT};

/* Other */
int i;

if(argc < 2){
printf("Usage: bufdemo [dimensione]n");
exit(0);
}

/* Semaphores */
key = ftok("/etc/fstab", getpid());

/* Create a semaphore set with 3 semaphore */
semid = semget(key, 3, 0666 | IPC_CREAT);

/* Initialize semaphore #0 to 1 - Resource controller */
arg.val = 1;
semctl(semid, 0, SETVAL, arg);

/* Initialize semaphore #1 to buf_length - Overflow controller */
/* Sem value is 'free space in buffer' */
arg.val = atol(argv[1]);
semctl(semid, 1, SETVAL, arg);

/* Initialize semaphore #2 to buf_length - Underflow controller */
/* Sem value is élements in buffer' */
arg.val = 0;
semctl(semid, 2, SETVAL, arg);

/* Fork */
for (i = 0; i < 5; i++){
pid = fork();
if (!pid){
for (i = 0; i < 20; i++){
sleep(rand()%6);
/* Try to lock resource - sem #0 */
if (semop(semid, &lock_res, 1) == -1){
perror("semop:lock_res");
}
/* Lock a free space - sem #1 / Put an element - sem #2*/
if (semop(semid, &push, 2) != -1){
printf("---> Process:%dn”, getpid());
}
else{
printf(”—> Process:%d  BUFFER FULLn”, getpid());
}
/* Release resource */
semop(semid, &rel_res, 1);
}
exit(0);
}
}

for (i = 0;i < 100; i++){
sleep(rand()%3);
/* Try to lock resource - sem #0 */
if (semop(semid, &lock_res, 1) == -1){
perror("semop:lock_res");
}
/* Unlock a free space - sem #1 / Get an element - sem #2 */
if (semop(semid, &pop, 2) != -1){
printf("<--- Process:%dn", getpid());
}
else printf("<--- Process:%d  BUFFER EMPTYn", getpid());
/* Release resource */
semop(semid, &rel_res, 1);
}

/* Destroy semaphores */
semctl(semid, 0, IPC_RMID);

return 0;
}

Comentemos las partes más interesantes del código:

struct sembuf lock_res = {0, -1, 0};
struct sembuf rel_res = {0, 1, 0};
struct sembuf push[2] = {1, -1, IPC_NOWAIT, 2, 1, IPC_NOWAIT};
struct sembuf pop[2] = {1, 1, IPC_NOWAIT, 2, -1, IPC_NOWAIT};

Estas 4 líneas son las acciones que podemos realizar en nuestro conjunto de semáforos: las dos primeras son acciones individuales, mientras que las otras son dobles. La primera acción, lock_res, intenta bloquear el recurso: decrementa el valor del primer semáforo (número 0) en una unidad (si el valor no es cero) y la política adoptada si el recurso está ocupado es ninguna (i.e. el proceso espera). La acción rel_res es idéntica a lock_res, pero el recurso se libera (el valor es positivo).

Las acciones push y pop son un poco especiales. Son arrays de dos acciones, la primera sobre el semáforo número 1 y la segunda sobre el semáforo número 2; mientras que el primero se incrementa el segundo se decrementa y viceversa, pero la política ya no es de espera: IPC_NOWAIT fuerza al proceso a continuar la ejecución si el recurso está ocupado.

/* Initialize semaphore #0 to 1 - Resource controller */
arg.val = 1;
semctl(semid, 0, SETVAL, arg);

/* Initialize semaphore #1 to buf_length - Overflow controller */
/* Sem value is 'free space in buffer' */
arg.val = atol(argv[1]);
semctl(semid, 1, SETVAL, arg);

/* Initialize semaphore #2 to buf_length - Underflow controller */
/* Sem value is élements in buffer' */
arg.val = 0;
semctl(semid, 2, SETVAL, arg);

Aquí inicializamos el valor de los semáforos: el primero a 1 porque controla el acceso en exclusiva a un recurso, el segundo a la longitud del buffer (dado en la línea de comando) y el tercero a 0, tal y como comentamos antes acerca del desbordamiento por arriba y por abajo.

/* Try to lock resource - sem #0 */
if (semop(semid, &lock_res, 1) == -1){
perror("semop:lock_res");
}
/* Lock a free space - sem #1 / Put an element - sem #2*/
if (semop(semid, &push, 2) != -1){
printf("---> Process:%dn", getpid());
}
else{
printf("---> Process:%d  BUFFER FULLn", getpid());
}
/* Release resource */
semop(semid, &rel_res, 1);

El proceso W intenta bloquear el recurso a través de la acción lock_res; una vez hecho esto, realiza un push y lo avisa por la salida estándar: si la operación no se puede realizar avisa de que el buffer está lleno. Después de esto libera el recurso.

/* Try to lock resource - sem #0 */
if (semop(semid, &lock_res, 1) == -1){
perror("semop:lock_res");
}
/* Unlock a free space - sem #1 / Get an element - sem #2 */
if (semop(semid, &pop, 2) != -1){
printf("<--- Process:%dn", getpid());
}
else printf("<--- Process:%d  BUFFER EMPTYn", getpid());
/* Release resource */
semop(semid, &rel_res, 1);

El proceso R actúa más o menos como el proceso W: bloquea el recurso, realiza un pop y libera el recurso.

Procesos, C , FreeBSD y algo de Cerveza. Me agrada

Hola a todos.
Aqui anexo algunas anotaciones sobre C, espero que a alguien que pase por aqui le sea de utilidad

Estamos sobre un sistema FreeBSD 7.X usando un compilador gcc

$ gcc -v
Using built-in specs.
Target: i386-undermydesk-freebsd
Configured with: FreeBSD/i386 system compiler
Thread model: posix
gcc version 4.2.1 20070719  [FreeBSD]

La librería estándar de C (libc, implementada sobre entornos GNU como glibc) usa las facilidades de la multitarea de Unix System V; el Unix System V (desde ahora SysV) es una implementación comercial de Unix, es el fundador de una de las dos familias mas importantes de Unix, la otra es Unix BSD.

En la libc el tipo pid_t está definido como un entero capaz de contener un pid. Desde ahora lo usaremos para almacenar el valor de un pid, pero solo por claridad: usar un entero es lo mismo.

Descubramos que función nos dice cual es el pid del proceso que contiene nuestro programa

pid_t getpid (void)

(la cual está definida con pid_t en unistd.h y sus/types.h) y escriba un programa cuyo objetivo sea imprimir por la salida estándar su pid. Con cualquier editor escriba el siguiente código

#include "unistd.h"
#include "sys/types.h"
#include "stdio.h"

int main(void){
  pid_t pid;

  pid = getpid();
  printf("El pid asignado a el proceso es %d\" , pid);

  return 0;
}

Guarde el programa como print_pid.c y compílalo

$ gcc -Wall -o pid.exe pid.c && ./pid.exe
El pid asignado a el proceso es 87866

Ya es hora de aprender a crear procesos, pero debo añadir algunas palabras sobre que ocurre realmente durante esta acción. Cuando un programa (contenido en el proceso A) crea otro proceso (B) los dos son idénticos, tienen el mismo código, la memoria llena de los mismos datos (no la misma memoria) y el mismo estado del procesador. A partir de este punto los dos se ejecutarán de manera diferente, por ejemplo dependiendo de la entrada del usuario o algún dato aleatorio. El proceso A es el “proceso padre” mientras que el B es el “proceso hijo”; ahora es mas fácil de entender el concepto “padre de todos los procesos” dado a init. La función que crea un nuevo proceso es

pid_t fork(void)

y su nombre viene de la propiedad de bifurcar la ejecución de procesos. El número devuelto es un pid, pero merece una atención particular. Dijimos que el actual proceso se duplica en un padre y un hijo, que se ejecutarán entrelazándose con los otros procesos en ejecución, haciendo diferentes trabajos; pero inmediátamente después de la duplicación, ¿que proceso será ejecutado, el padre o el hijo? Bueno, la respuesta es simple: uno de los dos. La decisión de que proceso debe ser ejecutado la toma una parte del sistema operativo llamado planificador, y no presta atención si un proceso es padre o hijo, sino que sigue un algoritmo basado en otros parámetros.

De todas formas, es importante conocer que proceso está en ejecución, ya que el código es el mismo. Ambos procesos contendrán el código del padre y del hijo, pero cada uno de ellos debe ejecutar solo uno de los códigos. Para clarificar este concepto, veamos el siguiente algoritmo:

- FORK
- Si eres HIJO EJECUTA (...)
- Si eres PADRE EJECUTA (...)

que representa en un pequeño pseudocódigo el código de nuestro programa. Vamos a revelar el misterio: la función fork devuelve ‘0′ al proceso hijo y el pid del hijo al padre. Así que es suficiente comprobar si el pid devuelto es cero y sabremos que proceso esta ejecutando el código. En el lenguaje C obtendremos

int main(void){
  pid_t pid;

  pid = fork();
  if (pid == 0)
  {
    CODIGO DE EL PROCESO HIJO
  }
  CODIGO DEL PROCESO PADRE
}

Es hora de escribir el primer ejemplo real de código multitarea: puedes grabarlo en un fichero fork_demo.c y compilarlo como se hizo anteriormente. He puesto el número de las lineas sólo por claridad. El programa se bifurcará a si mismo y el padre y el hijo escribirán algo en pantalla; la salida final será el entrelazamiento de las dos salidas (si todo va bien).

(01) #include "unistd.h"
(02) #include "sys/types.h"
(03) #include "stdio.h"

(04) int main(void)
(05) {
(05)   pid_t pid;
(06)   int i;

(07)   pid = fork();

(08)   if (pid == 0){
(09)     for (i = 0; i  < 8; i++){
(10)       printf("-HIJO-");
(11)     }
(12)     return(0);
(13)   }

(14)   for (i = 0; i < 8; i++){
(15)     printf("+PADRE+");
(16)   }

(17)   return(0);
(18) }

Las lineas número (01)-(03) contienen los includes de las librerías necesarias (E/S estándar, multitarea).
El main (como siempre en GNU), devuelve un entero, que es normalmente cero si el programa llega al final sin errores o un código de error si va algo mal; supondremos por ahora que todo marcha sin errores (añadiremos control de errores cuando estén claros los conceptos básicos). Luego definimos un tipo de dato para contener un pid (05) y un entero para usarlo como contador del bucle (06). Estos dos tipos, como se dijo antes, son idénticos, pero están ahí para mayor claridad.
En la linea (07) llamamos a la función fork, la cual devolverá cero al programa ejecutado en el proceso hijo y el pid del proceso hijo al padre; la comprobación está en la linea (08). Ahora el código de las lineas (09)-(13) se ejecutarán en el proceso hijo, mientras que el resto (14)-(16) las ejecutará el padre.
Las dos partes simplemente escriben 8 veces en la salida estándar la palabra “-HIJO-” o “+PADRE+”, dependiendo en que proceso se ejecuta, y luego termina devolviendo 0. Esto es verdaderamente importante, ya que sin este último “return” el proceso hijo, una vez que el bucle termine, seguirá ejecutando el código del padre (compruébalo, no dañará tu máquina, simplemente no hará lo que queremos). Por eso, un error será realmente difícil de encontrar, desde la ejecución de un programa multitarea (especialmente uno complejo) se da diferentes resultados en cada ejecución, haciendo la depuración basada en los resultados simplemente imposible.

Ejecutando el programa quizás quedes insatisfecho: no puedo asegurar de que el resultado será una mezcla real entre dos cadenas, y esto es debido a la velocidad de ejecución del pequeño bucle. Probablemente tu salida sea una sucesión de cadenas “+PADRE+” seguidas por unas de “-HIJO-” o viceversa. Sin embargo, intenta ejecutar mas de una vez el programa y el resultado podrá cambiar.

Insertando un retraso aleatorio antes de cada llamada a printf, obtendremos un mejor efecto visual de la multitarea: lo hacemos con las funciones sleep y rand.

sleep(rand()%4)

esto hace que el programa duerma durante un número aleatorio de segundos entre 0 y 3 (% devuelve el resto de una división entera). Ahora el código es

(09)  for (i = 0; i < 8; i++){
(->)    sleep (rand()%4);
(10)    printf(”-FIGLIO-n”);
(11)  }

y lo mismo para el código del padre. Guárdalo como fork_demo2.c, compílalo y ejecútalo. Ahora es mas lento, pero notamos la diferencia en el orden de salida:

$ ./fork_demo.exe
+PADRE+
-HIJO-
+PADRE+
-HIJO-
-HIJO-
+PADRE+
+PADRE+
-HIJO-
+PADRE+
-HIJO-
+PADRE+
-HIJO-
+PADRE+
-HIJO-
+PADRE+
-HIJO-
$

Ahora miremos los problemas que tenemos que ahora hacer frente: podemos crear un cierto número de procesos hijo dado un proceso padre, de modo que ejecuten operaciones diferentes a las que ejecute el proceso padre en un entorno de procesamiento concurrente; a menudo, el padre necesita comunicarse con sus hijos o al menos sincronizarse con ellos, para ejecutar operaciones en el orden correcto. Un primer modo para obtener dicha sincronización entre procesos es la función wait

pid_t waitpid (pid_t PID, int *STATUS_PTR, int OPTIONS)

donde PID es el PID del proceso cuyo fin estamos esperando, STATUS_PTR un puntero a un entero el cual contendrá el estado del proceso hijo (NULL si no se necesita la información) y OPTIONS un conjunto de opciones que no debemos tener en cuenta por ahora. Este es un ejemplo de un programa en el cual el padre crea un proceso hijo y espera a que acabe

#include "unistd.h"
#include "sys/types.h"
#include "stdio.h"

int main()
{
  pid_t pid;
  int i;

  pid = fork();

  if (pid == 0){
    for (i = 0; i < 14; i++){
      sleep (rand()%4);
      printf("-HIJO-");
    }
    return 0;
  }

  sleep (rand()%4);
  printf("+PADRE+ Esperando la terminacion del hijo..");
  waitpid (pid, NULL, 0);
  printf("+PADRE+ ...terminado");

  return 0;
}

T-bit, SUID y SGID

Después de haber trabajado durante un tiempo con UNIX, probablemente descubres que hay mas sobre los permisos de ficheros que los bits “rwx”. Cuando examines tu sistema de ficheros verás también “s” y “t”:

$ ls -ld /usr/bin/crontab  /usr/bin/passwd  /usr/sbin/sendmail  /tmp

drwxrwxrwt   5 root   root   1024 Jan 1 17:21 /tmp
-rwsr-xr-x   1 root   root   0328 May 6 1998 /usr/bin/crontab
-r-sr-xr-x   1 root   bin     5613 Apr 27 1998 /usr/bin/passwd
-rwsr-sr-x   1 root   mail   89524 Dec 3 22:18 /usr/sbin/sendmail

Cuando ejecutes el script verás que el proceso que lo ejecuta toma tu user-ID y tu group-ID:

 user-ID efectivo:
alice
 user-ID real:
alice
 ID del grupo:
users

Cuando Tux ejecuta tu programa “idinfo” obtiene una salida similar que muestra el proceso ejecutándose ahora bajo el ID de tux. La salida del programa depende sólo del usuario que lo ejecuta y no del propietario del fichero.

Por razones de seguridad, el s-bit funciona sólo cuando se usa con binarios (código compilado) y no en scripts (una excepción son los scripts de Perl). Por lo tanto crearemos un programa en C que llamará a nuestro programa idinfo:
¿Qué son estos bits “s” y “t”?. La longitud del vector de los bits de permiso es en realidad de 4 * 3 bits. chmod 755 es solamente una abreviación de chmod 0755.

El t-bit

El t-bit (a veces conocido como “sticky bit= bit pegajoso”) es útil solo en combinación con directorios. Se utiliza con el directorio /tmp como puedes ver mas arriba.

Normalmente (sin el t-bit activado en el directorio) los ficheros pueden ser borrados si el directorio que contiene los ficheros es escribible por la persona que borra los ficheros. De esta manera, si tienes un directorio en el que nadie puede depositar ficheros, entonces nadie podrá borrar tampoco los ficheros de nadie mas.

El t-bit cambia esta regla. Con el t-bit activado, solo el propietario del fichero o el propietario del directorio pueden borrar los ficheros. El t-bit puede ser activado con chmod a+tw o chmod 1777. Aquí vemos un ejemplo:

Alice crea un directorio con el t-bit activado:

$ mkdir mytmp
chmod 1777 mytmp

ahora Bob pone un fichero en él:

$ ls -al
drwxrwxrwt   3 alice    users  1024 Jan  1 20:30 ./
-rw-r--r--  1 bob   users     0 Jan  1 20:31 f.txt

Este fichero puede ser borrado ahora por Alice (propietaria del directorio) y Bob (propietario del fichero) pero no puede ser borrado por Tux:

$whoami
tux
rm -f f.txt
rm: f.txt: Operation not permitted

S-bit activado en el usuario

Con procesos de UNIX ejecutandose bajo un “user-ID”. Esto les da acceso a todos los recursos (ficheros, etc…) a los que este usuario tendría acceso. Hay dos user-ID’s. El user-ID real y el user-ID efectivo. El user-ID efectivo es el que determina el acceso a los ficheros. Salva el siguiente script con el nombre de idinfo y hazlo ejecutable (chmod 755 idinfo).

#!/bin/sh
#idinfo: Imprime información del usuario
echo " user-ID efectivo:"
id -un
echo " user-ID real:"
id -unr
echo " ID del grupo:"
id -gn

Cuando Tux ejecuta tu programa “idinfo” obtiene una salida similar que muestra el proceso ejecutándose ahora bajo el ID de tux. La salida del programa depende sólo del usuario que lo ejecuta y no del propietario del fichero.

Por razones de seguridad, el s-bit funciona sólo cuando se usa con binarios (código compilado) y no en scripts (una excepción son los scripts de Perl). Por lo tanto crearemos un programa en C que llamará a nuestro programa idinfo:

/*suidtest.c*/
#include 
#include 
int main(){
/*Los programas seguros con SUID no deben fiarse
* de ninguna entrada de usuario o variable de entorno!! */

char *env[]={”PATH=/bin:/usr/bin”,NULL};
char prog[]=”/home/alice/idinfo”;
if (access(prog,X_OK)){
    fprintf(stderr,”ERROR: %s no ejecutablen”,prog);
    exit(1);
}
printf(”ejecutando ahora %s …n”,prog);
execle(prog,(const char*)NULL,env);
perror(”suidtest”);

return(1);
}

Compila el programa con “gcc -o suidtest -Wall suidtest.c” y activa el s-bit para el propietario:

$ chmod 4755   suidtest

o

$ chmod u+s   suidtest

¡Ejecútalo!, ¿Qué ocurre?, ¿Nada?, ¡ejecútalo desde un usuario distinto!

El fichero suidtest pertenece a alice y tiene el s-bit activado donde la x normalmente es para el propietario del fichero. Esto provoca que el fichero se ejecute bajo el user-ID efectivo del usuario al que pertenece el fichero en lugar del user-ID del que lo ejecuta. Si Tux ejecuta el programa obtendremos lo siguiente:

$ ls -l suidtest
-rwsr-xr-x   1 alice   users   4741 Jan 1 21:53 suidtest
$ whoami
tux

ejecutando ahora /home/alice/idinfo …

user-ID efectivo:
alice
user-ID real:
tux
ID del grupo:
users

Como puedes ver, esta es una característica muy potente, especialmente si el fichero con el s-bit activado pertenece a root. Cualquier usuario puede hacer entonces cosas que normalmente sólo puede hacer root. Unas palabras sobre seguridad. Cuando escribas un programa SUID debes asegurarte de que sólo puede usarse para el propósito para el cual pretendas que séa usado. Establece siempre el path (camino) a un valor absoluto. Nunca hagas que dependa de variables de entorno o de funciones que usen variables de entorno. Nunca te fíes de la entrada del usuario (ficheros de configuración, argumentos en línea de comandos…). Comprueba la entrada de usuario byte por byte y compárala con valores que consideres válidos.

Cuando un programa SUID pertenece a root, podemos establecer el user-ID efectivo y el real (con la funcion setreuid()).

Los programas Set-UID son utilizados normalmente por “root” para dar a los usuarios normales acceso a cosas que, normalmente, solo “root” puede hacer.

S-bit activado en el grupo

Los ficheros ejecutables que tienen el s-bit activado en el grupo ejecutado bajo el group-ID del propietario del fichero. Es bastante similar al s-bit para el usuario en el párrafo anterior.

Cuando activamos el s-bit en el grupo para un directorio, se asigna también el grupo para cualquier fichero que sea creado en ese directorio. Alice pertenece a 2 grupos:

$ id
uid=550(alice)  gid=100(users)  groups=100(users),6(disk)

Normalmente, los ficheros se créan para ella con el grupo asignado a “users”. Pero si se crea un directorio con el grupo asignado a “disk” y el s-bit al grupo, entonces todos los ficheros que crée alicia tendrán también el ID del grupo “disk”:

$ chmod 2775 .
$ ls -ld .
drwxrwsr-x  3 tux   disk     1024 Jan 1 23:02 .

Si alice crea ahora un fichero nuevo en este directorio, el grupo de ese directorio será “disk”

$ touch newfile
$ ls -l newfile
-rw-r--r--   1 alice    disk      0 Jan 1 23:02 newfile

Esto es interesante cuando quieres trabajar con varias personas en un equipo asegurándote de que los group-ID de los ficheros se establecen para el grupo correcto en el directorio de trabajo de ese equipo, especialmente en un entorno donde los usuarios tienen normalmente el umask a 027, lo cual hace los ficheros inaccesibles para la gente ajena al grupo.

« Entradas anteriores