You are on page 1of 16

Guia rápida de referencia para estudiantes de la

materia Sistemas Operativos


Gabriel Valentin Hugo Ruscitti
27 de agosto de 2004

Índice
1. Introducción 2

2. Compilar en GNU/Linux 3
2.1. Lo básico de GCC . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.2. Desarrollando programas grandes . . . . . . . . . . . . . . . . . . 3
2.3. La utilidad make y el archivo Makefile . . . . . . . . . . . . . . . 4
2.4. Variables del archivo Makefile . . . . . . . . . . . . . . . . . . . . 5
2.5. Etiquetas utiles para el archivo Makefile . . . . . . . . . . . . . . 5

3. Utilizar el editor de texto VIM 7


3.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2. Ingresar al editor VIM . . . . . . . . . . . . . . . . . . . . . . . . 7
3.3. En el modo Orden . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.4. Otras caracterı́sticas . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.5. Salir del editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

4. Misceláneas 9
4.1. Crear archivos log mediante la redirección del archivo stderr . . . 9
4.2. Cambiar el nombre de un proceso . . . . . . . . . . . . . . . . . . 10
4.3. Ejecutar un programa como hijo de un proceso . . . . . . . . . . 10
4.4. Atender Señales . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
4.5. Manejo de pantalla y colores sin utilizar ncurses . . . . . . . . . . 12
4.6. Programas auxiliares . . . . . . . . . . . . . . . . . . . . . . . . . 13
4.7. Evitar procesos zombies y defunct . . . . . . . . . . . . . . . . . 14
4.8. Utilizar tar y gzip para comprimir y descomprimir . . . . . . . . 14
4.9. Documentos recomendados . . . . . . . . . . . . . . . . . . . . . 15
4.10. Resumen de gdb para encontrar rápidamente causas de un Seg-
mentation Fault . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.11. Realizar una pausa de n segundos . . . . . . . . . . . . . . . . . . 15

1
1. Introducción
Esta guia es desarrollada con la intención de ayudar a los estudiantes que
cursan la materia Sistemas Operativos de la UTN FRBA, el documento es breve
para cumplir con las necesidades de tiempo de los estudiantes.

Quienes escribimos este documento no formamos parte cátedra ni somos ex-


pertos del sistema GNU/Linux, somos estudiantes de sistemas y simplemente
creemos que mediante este trabajo podemos colaborar con muchas personas.

Cualquier duda o sugerencia es bienvenida, gracias.

Hugo Ruscitti - hugoruscitti@yahoo.com.ar


Gabriel Valentin - vcgdebian@yahoo.com.ar

Secciones de este documento


Compilar en GNU/Linux - por Hugo Ruscitti.
Misceláneas - por Hugo Ruscitti.
Utilizar el editor de texto VIM - por Gabriel Valentin.

Última versión
Podes encontrar la última versión de este documento en:
www.apuntes-utn.com.ar

2
2. Compilar en GNU/Linux
2.1. Lo básico de GCC
El comando gcc es utilizado para compilar programas escritos principalmente
en lenguaje C, por defecto este programa realiza varias acciones: preprocesa,
compila y asocia el ejecutable (link), el comando :

gcc -o arch bin fuente.c

Nos genera un archivo ejecutable (arch bin) a partir de un programa en


lenguaje C (fuente.c)

Nota: la opción -o es utilizada para nombrar el archivo de salida generado


por gcc, en este caso el archivo ejecutable.

También podemos realizar este mismo proceso en dos pasos:

gcc -o fuente.o -c fuente.c (compila el archivo fuente.c)


gcc -o arch bin fuente.o (genera el archivo ejecutable)

Esta segunda forma es mas útil cuando tenemos un programa dividido en


varios archivos fuente.

2.2. Desarrollando programas grandes


Dado que el trabajo práctico que nos proponemos realizar es bastante am-
plio, nuestra intención es dividir el proyecto en diferentes módulos; de esta forma
cada integrante del grupo puede dedicar mayor empeño a cada parte del pro-
grama.
Sólo para proponer un ejemplo:

// archivo main.c

#include "utils.h"

int main(void)
{
ImprimirMensaje();
return 0;
}

// archivo utils.h

void ImprimirMensaje(void);

// archivo utils.c

3
#include <stdio.h>

void ImprimirMensaje(void)
{
printf("Ejemplo básico con dos módulos\n");
}

Para generar un programa con estos 2 archivos fuente debemos ejecutar:

gcc -o utils.o -c utils.c (generamos el objeto de util.c)


gcc -o main.o -c main.c (generamos el objeto de main.c)
gcc -o ejecutable main.o utils.o (generamos el archivo ejecutable a partir del
los ficheros objetos)

Por último podemos ver el resultado ejecutando:

./ejecutable

Nota: en este caso para que main.c pueda ser compilado es necesario que
GCC conozca todas las cabeceras de la funciones llamadas, para este propósito
se utilizan los archivos .h que solo deben contener declaraciones de estructuras
y cabeceras de funciones.

2.3. La utilidad make y el archivo Makefile


El comando make de GNU es utilizado para automatizar el proceso de com-
pilación de proyectos grandes, make determina que partes del programa deben
ser recompiladas.
Cuando se ejecuta make, este lee un archivo de texto llamado Makefile que es-
pecifica reglas para que make pueda regenerar el programa recompilando sólo
los archivos necesarios.
Retomando el ejemplo anterior, main.c dependı́a claramente de utils.c, ya que
no podemos generar un programa solamente con main.c, además vimos que
se podı́a generar un fichero objeto de cada uno por separado y generar un eje-
cutable uniendo ambos; para especificarle a make esta situación debemos generar
un archivo llamado Makefile con este contenido:

#archivo Makefile

ejecutable: main.o util.o


gcc -o ejecutable main.o utils.o

main.o: main.c
gcc -o main.o -c main.c

utils.o: utils.c
gcc -o utils.o -c utils.c

4
Nota: Todas la lineas que especifican un comando deben comenzar con una
tabulación, en este caso antes de gcc
De esta forma al ejecutar make, el programa interpretara el archivo Make-
file intentando cumplir el primer objetivo (ejecutable), para ello necesita que los
archivos main.o y utils.o existan y estén actualizados, si esto se cumple make eje-
cutará la siguiente linea que comienza con tabulación (gcc -o ejecutable main.o
utils.o). En cambio, si alguno de los archivos dentro de sus dependencias no
existe o está desactualizado, make intentará generarlo y este será su nuevo ob-
jetivo a cumplir, este proceso se repite hasta lograr el objetivo inicial.

2.4. Variables del archivo Makefile


Otra caracterı́stica importante de make es la posibilidad de manejar vari-
ables dentro de archivo Makefile, estas variables funcionan de forma similar a
las macros del lenguaje C, veamos un ejemplo:

#archivo Makefile:

CC = gcc
CCFLAGS = -Wall

ejecutable: main.o utils.o


$(CC) $(CCFLAGS) -o $@ $^

main.o: main.c
$(CC) $(CCFLAGS) -o $@ -c $^

utils.o: utils.c
$(CC) $(CCFLAGS) -o $@ -c $^

Nota: make reemplaza cada $(..) por el valor de la variable contenida en el


paréntesis, el sı́mbolo $@ es reemplazado por el nombre completo del objetivo y
$ˆ es reemplazado por el nombre completo de todas las dependencias.

Nota: en este ejemplo la variable CCFLAGS contiene opciones enviadas al


compilador gcc, la opción -Wall se utiliza para que gcc nos informe cualquier
advertencia sobre el código fuente, por ejemplo si tenemos variables declaradas
sin utilizar.

2.5. Etiquetas utiles para el archivo Makefile


Si ejecutamos make sin parámetros el objetivo a cumplir es el primero del
archivo Makefile, pero además podemos especificarle por parámetro el objetivo
que queremos cumplir, de esta forma podemos agregar objetivos utiles para el
desarrollo, por ejemplo:

5
clean:
rm -f *.o ejecutable

exportar: clear
rm -f proyecto.tgz
tar czf proyecto.tgz *

Ahora cuando queramos limpiar nuestro trabajo, dado que los archivos .o
ocupan mucho espacio, ejecutamos:

make clean

Cuando realicemos la entrega del trabajo practico vı́a e-mail podemos eje-
cutar:

make exportar

Y enviar solamente el proyecto comprimido ’proyecto.tgz’.

6
3. Utilizar el editor de texto VIM
3.1. Introducción
En esta guia lo que trato de hacer es poder darles una introducción acerca
de este rápido y eficiente editor de texto. NO es mi objetivo imponer una moda
ni tampoco imponer ningún tipo de gusto personal, pero es un editor muy in-
teresante y bastante útil. VIM es una versión de vi pero mejorada VIM=VI+IM
(improved que quiere decir mejorado).Tampoco es posible especificar todos los
comandos o posibilidades del editor, me limitaré solo a aquellos que me resul-
taron mas útiles

A continuación les enumero algunas de las ventajas de VIM:

Se encuentra en la mayorı́a de las distribuciones de GNU/Linux.


En el laboratorio de sistemas no encontraras otro editor.
Todos los comandos de VI funcionan en VIM.
No es tan difı́cil de usar una vez que te acostumbras.

Nota: Debo advertirte que VIM es un editor de texto y no un procesador de


textos, la diferencia radica en que VIM no formatea el texto (no subraya, no
cambia de fuentes, etc).

3.2. Ingresar al editor VIM


Cuando te encuentres en la consola luego de haber ingresado la contraseña
del usuario, puedes comenzar a ejecutar VIM con el archivo a editar:

vim archivo

Ya dentro de VIM, debemos saber que existen tres modos de estados:

Nota: A partir de ahora indicaré entre paréntesis la tecla que se debe utilizar
en el editor

Orden - Normal (ESC)


Insertar (i)

Visual (CTRL + v)
Comandos (:)

Al iniciar el editor este se encuentra en modo Orden, ingresando al modo


Insertar (i) ya podemos editar el archivo.

7
3.3. En el modo Orden
Como indica el subtitulo debemos encontrarnos en modo Orden (ESC)

cc / dd - Corta/borra una linea completa


yy - Copia una linea completa
p - Pega una linea anteriormente copiada o cortada
/<palabra> - Busca una palabra avanzando en el texto
?< palabra> - Busca una palabra retrocediendo en el texto
n - Repite la busqueda
∗ - Busca la misma palabra que indica el cursor (muy útil)
G - ir al final del archivo
gg - ir al principio del archivo
w - avanza a la siguiente palabra
b - retrocede una palabra
u - Deshacer

3.4. Otras caracterı́sticas


VIM reconoce varias sintaxis de lenguajes, entre ellos el lenguaje C:

:syntax on - colorea la sintaxis


:colorscheme <tab> - cambia los colores de la pantalla
:set tabstop <numero> - define la tabulación
:split - Divide horizontalmente el editor
:vsplit - Divide verticalmente el editor
CTRL + w + direccion - Selecciona una ventana del editor
CTRL + w + (+/-) - Altera el tamaño de la ventana
:help - Nos muestra toda la ayuda sobre el editor
Nota: si queremos que nuestros cambios queden guardados para la próxima
ejecución del editor debemos agregar estas opciones al archivo .vimrc dentro de
nuestro directorio inicial ’/home/usuario’

3.5. Salir del editor


Primero debemos encontrarnos en modo Orden (ESC):

:w - Guarda los cambios


:wq - Guarda los cambios y sale
:q - Intenta salir (si el archivo no se modificó)
:q! - Sale ignorando los cambios

8
4. Misceláneas
4.1. Crear archivos log mediante la redirección del archivo
stderr
Uno de los requisitos indispensables para cada entrega del trabajo practico
son los reportes log: mientras el programa corre debe informar directamente en
un archivo cada una de las tareas que realiza.
Veamos una de las tantas formas de realizar esto con dos funciones, la primera
genera el archivo log y lo asigna a stderr, la segunda registra un evento del
programa en el archivo:

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

void GenerarArchivoLog(const char *Nombre)


{
FILE *arch;

arch = fopen(Nombre, "at");

if (arch != NULL)
stderr = arch;
}

void Log(int iTipo, char *czProceso, char *czEvento)


{
time_t timeActual;
struct tm *pTiempo;
char czFecha[50];
char czTipoEvento[50];

/* se obtine el tiempo actual */


timeActual = time(NULL);
pTiempo = localtime(&timeActual);

/* transforma los datos de fecha y hora a un formato de cadena */


strftime(czFecha, 50, "%d-%m %T", pTiempo);

switch (iTipo)
{
case 1:
strcpy(czTipoEvento, "Información");
break;

9
case 2:
strcpy(czTipoEvento, "Error");
break;

case 3:
strcpy(czTipoEvento, "Mensaje");
break;
}

/* registramos todo en el archivo */


fprintf(stderr, "%s %s [%d]\n", czFecha, czProceso, getpid() );
fprintf(stderr, "\t%s:%s\n\n", czTipoEvento, czEvento);
}

Nuestro programa principal debe llamar a GenerarLog(..) cuando inicia, y


luego a Log(..) cada vez que surja un evento.

Nota: Es recomendable diseñar varias funciones similares a Log(..) pero con


más parámetros (int o char*) para describir con exactitud el evento ocurrido,
por ejemplo: si nuestro programa se conecta a otro debe registrar en el archivo
log el ip y puerto al que se conecta, no simplemente ”conectandoce”.

4.2. Cambiar el nombre de un proceso


Cuando ejecutamos ps -fea podremos observar que nuestro programa tiene
asociado el nombre del archivo ejecutable; para cambiar este nombre simple-
mente podemos ejecutar al principio del programa:

[...]

int main( int iParam, char *pczParam[])


{
strncpy(pczParam[0], "Nuevo Nombre");

[...]

4.3. Ejecutar un programa como hijo de un proceso


Una forma es duplicar el proceso actual con la función fork(), obtendremos
entonces dos procesos casi idénticos ejecutandoce ”simultaneamente”(dicho de
esta manera para simplificar la explicación), luego tomamos el proceso hijo y lo
reemplazamos por el programa a ejecutar.
Tengan en cuenta que el siguiente ejemplo esta muy incompleto ya que no con-
templa ningún error.

#include <unistd.h>

void EjecutarProceso(void)

10
{
int iPid;

iPid = fork(); // aquı́ el proceso se duplica

if (iPid == 0) // para el proceso hijo esta condición es verdadera


{
execl("./programa", "programa", "parametro" , 0);
/* se debe contemplar el retorno de execl, -1 es error */
}
else // el proceso padre obtiene en iPid el identificador del hijo
{
/* falta contemplar un retorno de fork*/
/* si es < 0 = no se puede duplicar el proceso */
printf("Fork retorna %d, soy el proceso original\n", iPid);
}
}

4.4. Atender Señales


El sistema operativo informa determinados eventos a nuestra aplicación en
forma de señales.
Una de las pruebas realizadas en las entrega final consta en verificar que estas
señales sean tratadas correctamente, por ejemplo:
Si un usuario pulsa CTRL+c nuestro programa abortará inmediatamente sin
liberar su memoria reservada, nosotros podemos capturar varias de estas señales
y realizar determinadas acciones, en este caso cuando detectemos que el usuario
ha pulsado CTRL+c podrı́amos liberar toda la memoria reservada.

Cada señal se identifica con un numero entero, podemos hacer referencia a


cada señal conociendo su numero o bien utilizando las constantes definidas en
el archivo ’signal.h’.

Estas son las señales mas conocidas (con ’man 7 signal’ se puede consultar
el resto):

SIGINT (2) interrupción desde el teclado (generalmente CTRL+C)


SIGQUIT (3) salir desde el teclado (generalmente CTRL+Z)
SIGKILL (9) (no se puede atender)
SIGPIPE (13) se intenta escribir en un PIPE/socket que nadie puede lee
SIGTERM (15) señal de terminación
SIGCHLD un proceso hijo se detiene o termina

El siguiente ejemplo muestra como podemos atender la señal SIGTERM y


SIGINT para terminar normalmente nuestro programa.

#include <signal.h>
#include <stdio.h>

11
void CapturadorSignal(int iSignal);

int main(void)
{
printf("Iniciando el programa\n");

signal(SIGINT, CapturadorSignal); // indicamos que capturaremos SIGINT


signal(SIGTERM, CapturadorSignal); // indicamos que capturaremos SIGTERM

while (1)
{
}
}

void CapturadorSignal(int iSignal)


{

switch (iSignal)
{
case SIGINT:
printf("llegó la se~
nal SIGINT\n");
break;

case SIGTERM:
printf("Llegó la se~
nal SIGTERM\n");
break;
}

printf("Terminado el programa\n");
/* liberar listas */
exit(-1);
}

Nota: Desde un terminal también es posible enviar cualquier señal a otro


proceso, se debe ejecutar utilizar el comando kill con el numero de señal e iden-
tificador del proceso.

4.5. Manejo de pantalla y colores sin utilizar ncurses


Ncurses es una biblioteca diseñada para manejar sucesos en pantalla y even-
tos de entrada; es muy buena, sólo que muchas veces no es adecuada para realizar
tareas demasiado simples.
Para crear una interfaz sencilla en colores no es necesario utilizar ’ncurses’, los
terminales de GNU/Linux pueden interpretar caracteres especiales dentro de
una cadena de caracteres para realizar determinadas tareas como limpiar la
pantalla, imprimir en colores, mover el cursor etc.
Para indicar el comienzo de estos caracteres especiales se utiliza el carácter de
escape, este se obtiene escribiendo \e dentro de una cadena de caracteres (o
CTRL + v y luego escape, si lo realizamos desde VI o el terminal). Luego de

12
este carácter se debe especificar el código a ejecutar y, opcionalmente, una ca-
dena de caracteres; veamos un ejemplo:

[..]

printf("\e[34m Texto \e[0m \n");

[..]

Esta linea imprime ’Texto’ en color azul. Agregamos \e[0m al final de la


cadena para restaurar el color original del terminal.
Para terminar les muestro una lista de los códigos de escape mas útiles:

[L;CH Posiciona el cursos en la linea L y la columna C


[NA Mueve el cursor N lineas hacia arriba
[NB Mueve el cursor N lineas hacia abajo
[NC Avanza el cursor N columnas
[ND Retrocede el cursos N columnas
[2J Limpia la pantalla
[K Limpia hasta el final de la linea
[S Salva la posición del cursor
[U Restaura la posición del cursor
[30m Asigna el color negro
[34m Asigna el color azul
[32m Asigna el color verde
[36m Asigna el color cyan
[31m Asigna el color rojo
[37m Asigna el color gris claro
[1;30m Asigna el color gris oscuro
[1;33m Asigna el color amarillo
[1;37m Asigna el color blanco

4.6. Programas auxiliares


man - Como referencia rápida de funciones.
strace - Captura señales y llamadas al sistema.
gdb - Depurador.

tail - Imprime el final de un archivo.


splitvt - Divide horizontalmente el terminal y nos permite utilizar ambos en
la misma pantalla (CTRL + w nos permite ir de uno a otro).
top - Informa el rendimiento de cada proceso en ejecución, es importante que
nuestros procesos nunca requieran demasiado CPU.

ps -fea - Imprime una lista de procesos en el sistema junto con información


especifica de cada uno.
kill - Envı́a una señal a un proceso.

13
killall - Envı́a una señal a varios procesos juntos.

Nota: Cada una de las utilidades puede realizar muchas tareas, la descrip-
ción corresponde a las tareas mas comunes que realizaremos con cada programa.

Nota: ps es utilizado habitualmente para verificar que nuestro programa no


deja procesos en el equipo cuando algún proceso termina. Además es utilizado
para identificar el PID (identificador de proceso) de uno o varios programas para
luego enviarles señales con kill; por supuesto nuestro programa debe contemplar
estos casos (ver atender señales)

4.7. Evitar procesos zombies y defunct


Cuando tenemos una serie de procesos relacionados entre si, como padres e
hijos, puede que se nos presenten dos problemas:

1 - Si un proceso padre es eliminado todos su procesos hijos no terminaran su


ejecución, al verificar el listado de procesos con ’ps -fea’ podremos ver que estos
procesos hijos siguen su ejecución e identifican como proceso padre al proceso
init (PID = 1), esto no deberı́a ocurrir, el proceso hijo pasa a ser un proceso
’zombie’.

2 - En cambio si el proceso que termina es hijo, el proceso padre recibe la


señal SIGCHLD. Esta señal se debe atender, independientemente de la tarea
que realicemos (cerrar el programa, regenerar el proceso, ignorar la salida), ya
que si no atendemos la señal el proceso hijo seguirá en la lista de procesos que
muestra ’ps’ con la leyenda <defunct>, esto tampoco nos debe ocurrir.

Para el caso 1 podemos terminar el proceso hijo en cuanto detectemos que el


proceso padre terminó su ejecución. Esto se puede detectar fácilmente si man-
tenemos comunicación con el proceso padre.

Para el caso 2 debemos atender la señal SIGCHLD y si queremos seguir


ejecutando el proceso padre debemos llamar a wait(0) para esperar la salida del
proceso hijo.

4.8. Utilizar tar y gzip para comprimir y descomprimir


para comprimir:

tar czf archivo a generar.tgz directorio a comprimir/

para descomprimir:

tar xzf archivo a descomprimir.tgz

14
4.9. Documentos recomendados
- www.geocities.com/chuidiang, contiene varios documentos de programación
avanzada en C sobre Linux/Unix. Se tratan temas como socket, procesos, hilos,
memoria compartida, semáforos, señales, makefiles... etc. [CASTELLANO]

- En el sitio www.apuntes-utn.com.ar pueden encontrar un archivo pdf llama-


do ’Detalles a tener en cuenta en una entrega de Sistemas Operativos’ (realizado
por Feito, Nazareno Vicente), este documento enumera varios items evaluados
en las entregas de trabajos prácticos. [CASTELLANO]

4.10. Resumen de gdb para encontrar rápidamente causas


de un Segmentation Fault
Cuando nuestro programa falla inesperadamente podemos buscar la causa
del error utilizando un depurador como gdb; primero debemos debemos asegu-
rarnos de que el programa ha sido compilado con el parametro -g, si no vamos
a utilizar gdb debemos quitar esta opción ya que la compilación con -g es mas
lenta.
Muchas personas desarrollan este trabajo desde el sistema gráfico X Window,
seguramente ellos preferirán otros depuradores como kdbg o ddd; si bien el fun-
cionamiento de estos programas es similar, es aconsejable que conozcan por lo
menos lo mı́nimo de gdb, ya que los problemas mas inesperados surgen en el
laboratorio donde sólo podemos contar con gdb.
Retomando, una vez que tenemos el programa compilado con -g ejecutamos:

gdb ejecutable

Ya estamos dentro de gdb sin ejecutar la aplicación

run

Ejecutamos el programa hasta que este caiga, la mayorı́a de las veces cae
por la llegada de la señal SIGSEGV (Segmentation Fault)

where

Obtenemos una descripción mas precisa de nuestra posición en el programa.

A partir de aquı́ podremos realizar varios análisis; si el problema es un Seg-


mentation Fault es probable que en este momento hallamos encontrado la causa
(punteros sin inicializar, acceso a posiciones invalidas de un vectores etc.)

4.11. Realizar una pausa de n segundos


Muchas veces se nos pide detener un proceso durante algunos segundos, por
ejemplo para simular una situación de la realidad. Para realizar esta tarea no
podremos simplemente realizar un ciclo hasta que se cumpla ese tiempo, esto

15
nos resultarı́a muy costoso (en términos de recursos).
Una posibilidad, que no consume tantos recursos de sistema es:

void Delay (int iSegundos)


{
struct timeval tv;

tv.tv_sec = iSegundos;
tv.tv_usec = 0;
select (1, NULL, NULL, NULL, &tv);
}

/* similar al anterior pero con milisegundos */


void DelayMsecs (float iMS)
{
struct timeval tv;

tv.tv_sec = 0;
tv.tv_usec = iMS * 1000000;
select(1, NULL, NULL, NULL, &tv);
}

16

You might also like