Archive for FreeBSD
March 28, 2010 a las 5:31 pm por Edwin Plauchu · Archivado en FreeBSD
# sudo su
# port install postgresql84-server
# launchctl load -w /Library/LaunchDaemons/org.macports.postgresql84-server.plist
# mkdir -p /opt/local/var/db/postgresql84/defaultdb
# chown postgres:postgres /opt/local/var/db/postgresql84/defaultdb
# # su - postgres
Edwin-Plauchus-MacBook:~ postgres$ /opt/local/lib/postgresql84/bin/initdb -D /opt/local/var/db/postgresql84/defaultdb
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
The database cluster will be initialized with locale C.
The default database encoding has accordingly been set to SQL_ASCII.
The default text search configuration will be set to "english".
fixing permissions on existing directory /opt/local/var/db/postgresql84/defaultdb ... ok
creating subdirectories ... ok
selecting default max_connections ... 20
selecting default shared_buffers ... 1600kB
creating configuration files ... ok
creating template1 database in /opt/local/var/db/postgresql84/defaultdb/base/1 ... ok
initializing pg_authid ... ok
initializing dependencies ... ok
creating system views ... ok
loading system objects' descriptions ... ok
creating conversions ... ok
creating dictionaries ... ok
setting privileges on built-in objects ... ok
creating information schema ... ok
vacuuming database template1 ... ok
copying template1 to template0 ... ok
copying template1 to postgres ... ok
WARNING: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the -A option the
next time you run initdb.
Success. You can now start the database server using:
/opt/local/lib/postgresql84/bin/postgres -D /opt/local/var/db/postgresql84/defaultdb
or
/opt/local/lib/postgresql84/bin/pg_ctl -D /opt/local/var/db/postgresql84/defaultdb -l logfile start
Finalmente si se desea arrancar postgresql usaremos el siguiente comando como root
# launchctl start org.macports.postgresql84-server
It’s just UNIX
November 23, 2009 a las 3:04 pm por Edwin Plauchu · Archivado en FreeBSD, Linux, Solaris
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);
November 18, 2009 a las 5:06 pm por Edwin Plauchu · Archivado en Ubuntu, FreeBSD, Linux
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
November 18, 2009 a las 4:31 pm por Edwin Plauchu · Archivado en 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.
November 18, 2009 a las 2:51 pm por Edwin Plauchu · Archivado en FreeBSD
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;
}
November 18, 2009 a las 1:31 pm por Edwin Plauchu · Archivado en FreeBSD
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.
November 18, 2009 a las 1:18 pm por Edwin Plauchu · Archivado en FreeBSD
La utilidades standard para buscar cadenas de texto en ficheros son grep/egrep para la búsqueda de expresiones regulares y fgrep para buscar cadenas literales. Para buscar una expresión en todos los ficheros del directorio actual sólo tipea:
egrep -i "search expression" *
Para buscar cadenas en todos los ficheros de todo el árbol de directorios puedes combinar el comando find con otro comando de búsqueda de archivo, por ejemplo: egrep. Esto puede ser realizado de muchas formas:
egrep -i "expression" `find . -type f -print`
find . -type f -exec egrep -i "expression" /dev/null {} ;
find . -type f -print | xargs egrep -i "expression"
Un programa de búsqueda muy interesante es agrep. Este funciona básicamente como egrep pero realiza búsquedas que toleran errores en la escritura del nombre. De esta manera puedes también encontrar palabras mal escritas. Para buscar una expresión con un máximo de dos errores de escritura puedes usar:
agrep -i -2 "search exprission" *
Hay muchas utilidades de búsqueda disponibles para Unix. Por lo tanto este artículo no puede estar completo.
November 18, 2009 a las 1:10 pm por Edwin Plauchu · Archivado en FreeBSD
Probablemente puedes recordar este problema: Tenías algún tipo de fichero y no podías recordar donde lo pusiste.
Aquí es donde el comando find viene a ser útil. ¿Cómo usarlo? find viene, por supuesto, con un extenso man page pero veamos unos “casos típicos”: Buscar un archivo llamado lostfile.txt, recorriendo el árbol de directorios comenzando en el directorio actual:
find . -name lostfile.txt -print
find acepta también wildcards. Recuerda colocar entre comillas los wildcards, de lo contrario el shell no los reconocerá como tales. Aquí un ejemplo:
find . -name "lost*" -print
Este comando puede ser un poco lento cuando necesites buscar en un árbol de directorios muy grande. Aquí el comando locate puede ayudar. Este realmente no busca directamente un archivo en el sistema de ficheros. Busca en una base de datos. Es bastante más rápido pero la base de datos podría estar desactualizada, pero puedes, por supuesto, correr el comando updatedb manualmente de vez en cuando para actualizarla. locate realiza búsquedas de subcadenas.
locate lostfile
Este localiza los ficheros lostfile.txt o mylostfile.txt, etc…
Algunas veces te gustaría encontrar todos los ficheros en el árbol de directorios que no contengan cierta cadena. Por ejemplo todos los ficheros excepto .o y .c. Aquí están un par de posibilidades para hacerlo::
find . ! -name "*.o" ! -name "*.c" -print
find . -print | fgrep -v '.o' | fgrep -v '.c'
Hay muchas utilidades de búsqueda disponibles para Unix. Por lo tanto este artículo no puede estar completo.
November 16, 2009 a las 9:11 pm por Edwin Plauchu · Archivado en FreeBSD
Las entradas al kernel pueden ser categorizadas en base al tipo de evento que las origina.
* Interrupciones de Hardware
* Mensajes de Hardware (hardware trap)
* Mensajes de Software-Inicializado
* Interrupciones de Hardware son el origen de eventos externos, tales como un dispositivo I/O solicitando atencion o un reloj reportando el paso del tiempo. Las Interrupciones de Hardware ocurren asincronamente y pudieran no estar relacionadas al contexto del actual proceso en ejecucion.
* Hardware traps pueden ser sincronos o asincronos pero estan relacionados al actual proceso en ejecucion. Ejemplos de hardware traps son aquellos generados como un resultado de una operacion aritmetica ilegal, tal como los es la division ocacionada por cero.
* Mensajes de Software-Inicializado son usados como la consecuencia de que el sistema force la planeacion de un evento, tal como procesos replaneando procesos o procesamiento de red, tan pronto como sea posible. Mensajes de Software-Inicializado son implementados mediante el seteo de una bandera, que es activada siempre que un proceso este preparando salir del kernel. Si la bandera es seteada, el codigo de interrupcion de software es ejecutado en lugar de salir desde el kernel.
Las llamadas al sistema(System calls) son un caso especial de Mensajes de Software-Inicializado, la instruccion maquina usada para inicializar una system call tipicamente genera un hardware trap que es atendido especialmente por el kernel.
November 16, 2009 a las 5:44 pm por Edwin Plauchu · Archivado en FreeBSD
La organizacion del kernel de FreeBSD puede ser vista como un proveedor de servicios a los procesos de los usuarios. Procesos usualmente accesaran a esos servicios mediante las llamadas al sistema (system calls). Algunos servicios, tales como planeacion y administracion de memoria, son implementados como procesos que se ejecutaran en modo kernel o como subrutinas que se ejecutan periodicamente dentro del kernel.
Existen procesos de kernel, y ellos funcionan enteramente dentro del kernel. (Los procesos de kernel executan codigo que es compilado dentro de la imagen cargada y opera con el modo de ejecucion privilegiada del kernel).
| Nombre del Proceso |
Funcion |
| idle |
Corre cuando no hay mas nada por hacer |
| swapper |
Programa la carga de procesos proveenientes de la memoria secundaria hacia en memoria principal, cuando los recursos del sistema empiezan a estar disponibles |
| vmdaemon |
Programa la transferencia de procesos enteros hacia la memoria secundaria, cuando los recursos del sistema son bajos |
| ramdon |
Colecta datos para la produccion de semillas aleatorias para el kernel y el dispositivo /dev/ramdon |
| vnrlu |
Mantiene una fuente de vnodes libres, esto lo logra limpiando los vnodes cuya fecha de uso fue la menos reciente |
| pagedaemon |
Escribe partes de el espacio de direcciones de un proceso hacia un sistema de almacenamiento secundario, a razon de soportar las facilidades de paginacion , de un sistema de memoria virtual. |
| ktrace |
Escribe los registros de seguimiento a las llamadas al sistema (system calls) a sus archivos de salida. |
| bufdaemon |
Mantiene un suministro de buffers limpios, producto de buffers sucios cuando el suministro de buffers limpios esta abajo del nivel optimo. |
| syncer |
Asegura que los datos de archivo sucio estan escritos despues de 30 segundos. |
| pagezero |
Mantiene un suministro de paginas Cero. |
| g_up |
Toma los datos que van llegando de los dispositivos manejadores y los entrega a los procesos. |
| g_down |
Toma los datos que van llegando de los procesos y los entrega a los dispositivos manejadores. |
| g_event |
Toma tareas de configuracion, inclullendo en esto el descubrimiento de nuevos dispositivos y la remocion de estos cuando ellos ya han desaparecido. |
Despues de que los procesos de kernel son creados, el kernel crea el primer proceso a ejecuta en modo usuario. Este sirve como el proceso padre para todos los subsecuentes procesos. El primer proceso en modo usuario es el programa init. Este metodo realizara tareas administrativas, tales como ejecutar procesos getty para cada terminal sobre la maquina, etc. El proceso init es un proceso de modo usuario y corre fuera del kernel.