0% encontró este documento útil (0 votos)
367 vistas284 páginas

Problemas en Sistemas Operativos

Este documento es un libro de problemas de sistemas operativos y sus soluciones. Contiene 28 problemas relacionados con procesos y 21 problemas relacionados con sistemas de ficheros. El libro ha sido editado por profesores de la Universidad Politécnica de Madrid y contiene soluciones a problemas propuestos en exámenes de la asignatura de Sistemas Operativos durante varios años.

Cargado por

AndresPolania
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
367 vistas284 páginas

Problemas en Sistemas Operativos

Este documento es un libro de problemas de sistemas operativos y sus soluciones. Contiene 28 problemas relacionados con procesos y 21 problemas relacionados con sistemas de ficheros. El libro ha sido editado por profesores de la Universidad Politécnica de Madrid y contiene soluciones a problemas propuestos en exámenes de la asignatura de Sistemas Operativos durante varios años.

Cargado por

AndresPolania
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Sistemas Operativos

D.A.T.S.I.
Facultad de Informtica
Universidad Politcnica de Madrid

PROBLEMAS DE
SISTEMAS OPERATIVOS DE 2
15 ed. Madrid, noviembre de 2014

Pedro de Miguel Anasagasti


Victor Robles Forcada
Francisco Rosales Garca
Francisco Snchez Moreno
Mara de los Santos Prez Hernndez
Angel Rodrguez Martnez de Bartolom
Antonio Ruiz Mayor
ISB N
Depsito Legal:

Edita e imprime:
Fundacin General de la Universidad Politcnica de Madrid
Servicio de Publicaciones - Facultad de Informtica
Campus de Montegancedo -Boadilla del Monte - 28660 - Madrid

Ediciones:
1 ed. Madrid, noviembre de 1998
2 ed. Madrid, noviembre de 1999
3 ed. Madrid, noviembre de 2000
4 ed. Madrid, noviembre de 2001
5 ed. Madrid, noviembre de 2002
6 ed. Madrid, febrero de 2004
7 ed. Madrid, marzo de 2005
8 ed. Madrid, febrero de 2006
9 ed. Madrid, mayo de 2007
10 ed. Madrid, junio de 2009
11 ed. Madrid, julio de 2010
12 ed. Madrid, abril de 2012
13 ed. Madrid, julio de 2012
14 ed. Madrid, enero de 2013
15 ed. Madrid, noviembre de 2014
PRLOGO

Este libro trata de problemas de sistemas operativos y sus soluciones. La fuente de estos problemas son los exme -
nes propuestos durante varios aos por el grupo docente de Sistemas Operativos de la Facultad de Informtica de la
Universidad Politcnica de Madrid en la asignatura de Sistemas Operativos de 2 curso.
Conviene advertir que las soluciones propuestas en este libro no son las nicas posibles. Como bien se sabe, en
programacin pueden usarse diferentes algoritmos para resolver el mismo problema y por supuesto el estilo del pro-
gramador puede variar grandemente en claridad, eficiencia, y concisin del cdigo. Se debe advertir tambin que
muchas de las soluciones no han sido compiladas ni ejecutadas por lo que aunque se ha hecho una revisin bastante
exhaustiva se pueden encontrar errores. Por otra parte, no es nada fcil asegurar que un programa concurrente fun-
ciona correctamente. A diferencia de los programas secuenciales, los concurrentes no son reproducibles y por tanto
resulta muy difcil de caracterizar perfectamente un error concreto en los mismos.
En algunos casos los problemas propuestos han tratado de simplificar la realidad para evitar la complejidad de
los detalles y realzar ms los aspectos de diseo.
No seis faratones,
que lo bien hecho bien queda
y ms vale no hacerlo que hacerlo mal.

"Logarte"
ndice

Procesos...................................................................................................................1
Problema 1.1...........................................................................................................................1
Problema 1.2 (septiembre 1998)............................................................................................4
Problema 1.3...........................................................................................................................7
Problema 1.4...........................................................................................................................9
Problema 1.5 (junio 2000)....................................................................................................12
Problema 1.6 (junio 2002)....................................................................................................15
Problema 1.7.........................................................................................................................19
Problema 1.8.........................................................................................................................21
Problema 1.9.........................................................................................................................27
Problema 1.10 (2004)...........................................................................................................30
Problema 1.11 (abril 2005)...................................................................................................31
Problema 1.12 (junio 2005)..................................................................................................33
Problema 1.13 (abril 2006)...................................................................................................35
Problema 1.14 (abril 2007)...................................................................................................40
Problema 1.15 (junio 2009)..................................................................................................42
Problema 1.16 (abril 2010)...................................................................................................45
Problema 1.17 (junio 2010)..................................................................................................47
Problema 1.18 (sep-2010)....................................................................................................48
Problema 1.19 (nov-2010)....................................................................................................49
Problema 1.20 (febrero 2011)...............................................................................................51
Problema 1.21 (abril 2011)...................................................................................................53
Problema 1.22 (abril 2011)...................................................................................................54
Problema 1.23 (junio 2011)..................................................................................................54
Problema 1.24 (julio 2011)...................................................................................................56
Problema 1.25 (nov-2011)....................................................................................................58
Problema 1.26 (abril 2012)...................................................................................................61
Problema 1.27 (abril 2012)...................................................................................................61
Problema 1.28 (abril 2012)...................................................................................................62
Sistema de Ficheros..............................................................................................65
Problema 2.1 (septiembre 1998)..........................................................................................65
Problema 2.2.........................................................................................................................67
Problema 2.3 (septiembre 2000)..........................................................................................70
Problema 2.4 (junio 2001)....................................................................................................71
Problema 2.5 (septiembre 2001)..........................................................................................74
Problema 2.6 (septiembre 2002)..........................................................................................78
Problema 2.7 (junio 2003)....................................................................................................82
Problema 2.8.........................................................................................................................86
Problema 2.9 (mayo 2004)...................................................................................................89
Problema 2.10 (febrero 2005)..............................................................................................91
Problema 2.11 (mayo 2005).................................................................................................93
Problema 2.12 (febrero 2006)..............................................................................................95
Problema 2.13 (abril 2006)...................................................................................................97
Problema 2.14 (junio 2006)................................................................................................100
Problema 2.15 (septiembre 2006)......................................................................................103
Problema 2.16 (febrero 2007)............................................................................................105
Problema 2.17 (abril 2007).................................................................................................108
Problema 2.18 (junio 2007)................................................................................................109
Problema 2.19 (abril 2008).................................................................................................115
Problema 2.20 (mayo 2009)...............................................................................................117
Problema 2.21 (abril 2010).................................................................................................120
Problema 2.22 (junio 2010)................................................................................................121
Problema 2.23 (sep 2010)...................................................................................................126
Problema 2.24 (oct-2010)...................................................................................................129
Problema 2.25 (octubre 2011)............................................................................................130
Problema 2.26 (octubre 2011)............................................................................................131
Problema 2.27 (octubre 2011)............................................................................................131
Problema 2.28 (marzo 2012)..............................................................................................132
Problema 2.29 (julio 2012).................................................................................................135
Problema 2.30 (marzo 2013)..............................................................................................138
Problema 2.31 (marzo 2013)..............................................................................................139
Problema 2.32 (julio 2013).................................................................................................140
Problema 2.33 (marzo 2014)..............................................................................................141
Problema 2.34 (abril 2014).................................................................................................143
Gestin de memoria............................................................................................146
Problema 3.1.......................................................................................................................146
Problema 3.2.......................................................................................................................149
Problema 3.3.......................................................................................................................151
Problema 3.4 (junio 2003)..................................................................................................154
Problema 3.5 (septiembre 2003)........................................................................................157
Problema 3.6 (abril 2004)...................................................................................................160
Problema 3.7 (junio 2004)..................................................................................................161
Problema 3.8 (septiembre 2004)........................................................................................164
Problema 3.9 (abril 2005)...................................................................................................167
Problema 3.10 (junio 2005)................................................................................................168
Problema 3.11 (junio 2006)................................................................................................170
Problema 3.12 (junio 2008)................................................................................................172
Problema 3.13 (junio 2009)................................................................................................174
Problema 3.14 (septiembre 2009)......................................................................................176
Problema 3.15 (junio 2010)................................................................................................179
Problema 3.16 (junio 2011)................................................................................................182
Problema 3.17 (feb 2011)...................................................................................................182
Problema 3.18 (feb-2011)...................................................................................................184
Problema 3.19 (junio 2011)................................................................................................186
Problema 3.20 (julio-2011)................................................................................................187
Problema 3.21 (mayo 2011)...............................................................................................191
Problema 3.22 (septiembre 2011).......................................................................................192
Problema 3.23 (julio 2013).................................................................................................194
Problema 3.24 (noviembre 2013).......................................................................................195
Problema 3.25 (enero 2014) Tiene parte de sincronizacin...............................................197
Problema 3.26 (mayo 2014)...............................................................................................200
Comunicacin y sincronizacin.........................................................................203
Problema 4.1 (septiembre 1999)........................................................................................203
Problema 4.2 (abril 2000)...................................................................................................206
Problema 4.3 (septiembre 2000)........................................................................................208
Problema 4.4.......................................................................................................................211
Problema 4.5 (septiembre 2001)........................................................................................217
Problema 4.6.......................................................................................................................219
Problema 4.7.......................................................................................................................221
Problema 4.8 (septiembre 2003)........................................................................................223
Problema 4.9 (2004)...........................................................................................................226
Problema 4.10 (2004).........................................................................................................229
Problema 4.11 (mayo 2005)...............................................................................................234
Problema 4.12 (septiembre 2005)......................................................................................235
Problema 4.13 (junio 2006)................................................................................................238
Problema 4.14 (junio 2007)................................................................................................241
Problema 4.15 (junio 2007)................................................................................................242
Problema 4.16 (junio 2010)................................................................................................245
Problema 4.17 (junio 2010)................................................................................................247
Problema 4.18(enero 2011)................................................................................................249
Problema 4.19 (junio 2011)................................................................................................251
Problema 4.20 (junio 2011)................................................................................................253
Problema 4.21 (junio 2011)................................................................................................255
Problema 4.22 (enero 2012)...............................................................................................259
Problema 4.23 (enero 2012)...............................................................................................261
Problema 4.24 (junio 2012)................................................................................................263
Problema 4.25 (junio 2012)................................................................................................266
Problema 4.26 (dic 2012)...................................................................................................269
Problemas propuestos........................................................................................272
A.-Tema de introduccin....................................................................................................272
B.-Procesos y llamadas al sistema......................................................................................274
C.-Comunicacin y sincronizacin entre procesos............................................................276
D.-E/S y sistema de ficheros..............................................................................................276
1
PROCESOS

Problema 1.1
Se quiere realizar un programa que cree un conjunto de procesos que acceden en exclusin mutua a un fichero com-
partido por todos ellos. Para ello, se deben seguir los siguientes pasos:
a) Escribir un programa que cree N procesos hijos. Estos procesos deben formar un anillo como el que se
muestra en la figura 1.1. Cada proceso en el anillo se enlaza de forma unidireccional con su antecesor y su
sucesor mediante un pipe. Los procesos no deben redirigir su entrada y salida estndar. El valor de N se re-
cibir como argumento en la lnea de mandatos. Este programa debe crear adems, el fichero a compartir
por todos los procesos y que se denomina anillo.txt

Pipe N 1 Pipe 1

N 2

Pipe 5 Pipe 2

5 3

Pipe 4 4 Pipe 3

Figura 1.1

b) El proceso que crea el anillo inserta en el mismo un nico carcter que har de testigo, escribiendo en el
pipe de entrada al proceso 1. Este testigo recorrer el anillo indefinidamente de la siguiente forma: cada
proceso en el anillo espera la recepcin del testigo; cuando un proceso recibe el testigo lo conserva durante
5 segundos; una vez transcurridos estos 5 segundos lo enva al siguiente proceso en el anillo. Codifique la
funcin que realiza la tarea anteriormente descrita. El prototipo de esta funcin es:
void tratar_testigo(int ent, int sal);
donde ent es el descriptor de lectura del pipe y sal el descriptor de escritura (vase figura 1.2).

Ent Proceso Sal

Figura 1.2

c) Escribir una funcin que lea de la entrada estndar un carcter y escriba ese carcter en un fichero cuyo
descriptor se pasa como argumento a la misma. Una vez escrito en el fichero el carcter ledo, la funcin es -
cribir por la salida estndar el identificador del proceso que ejecuta la funcin.

1
2 Problemas de sistemas operativos
d) Cada proceso del anillo crea dos procesos ligeros que ejecutan indefinidamente los cdigos de las funciones
desarrolladas en los apartados b y c respectivamente. Para asegurar que los procesos escriben en el fichero
en exclusin mutua se utilizar el paso del testigo por el anillo. Para que el proceso pueda escribir en el fi-
chero debe estar en posesin del testigo. Si el proceso no tiene el testigo esperar a que le llegue ste. Nte -
se que el testigo se ha de conservar en el proceso mientras dure la escritura al fichero. Modificar las
funciones desarrolladas en los apartados b y c para que se sincronicen correctamente utilizando semforos.

Solucin FG
a) El programa propuesto es el siguiente:
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])


{
int fd[2];
int ent;
int sal;
pid_t pid;
int i;
int N;
char c;

if (argc != 2)
{
printf("Uso: anillo <N> \n");
return 0;
}

N = atoi(argv[1]);

/* se crea el pipe de entrada al proceso 1 */


if (pipe(fd) < 0)
{
perror("Error al crear el pipe\n");
return 0;
}
ent = fd[0];
sal = fd[1];
write(sal, &c, 1); /* se escribe el testigo en el primer pipe */

for(i = 0; i < N; i++)


{
if (i != N-1)
if (pipe(fd) < 0)
{
perror("Error al crear el pipe\n");
return 0;
}

pid = fork();
switch(pid)
{
case -1: /* error */
perror("Error en el fork \n");
return 0;
case 0: /* proceso hijo */
if (i != N-1)
{
3
close(sal);
sal = dup (fd[1]);
close(fd[0]);
close(fd[1]);
}
i = N
break;
default: /* proceso padre */
if (i == N-1) /* ltimo proceso */
return 0;
else
{
close(ent);
close(fd[1]);
ent = fd[0];
}
break;
}
}

/* a continuacin los procesos del anillo continuaran sus acciones */


return 0;
}
b) El programa propuesto es el siguiente:
void tratar_testigo (int ent, int sal)
{
char c;

for(;;)
{
read(ent, &c, 1);
sleep(5);
write(sal, &c, 1);
}
}
c) El programa propuesto es el siguiente:
void escribir_en_fichero(int fd)
{
char c;
pid_t pid;

read(0, &c, 1);


write(fd, &c, 1);
pid = getpid();
printf("Proceso %d escribe en el fichero\n", pid);
return;
}
d) Los procesos ligeros ejecutan los cdigos de las funciones desarrolladas en b y c de forma indefinida. Para sincro-
nizar correctamente su ejecucin es necesario utilizar un semforo con valor inicial 0 y que denominaremos sincro.
Los cdigos de las funciones tratar_testigo y escribir_en_fichero quedan de la siguiente forma:
void tratar_testigo (int ent, int sal)
{
char c;

for(;;)
{
read(ent, &c, 1);
sem_post(&sincro);
4 Problemas de sistemas operativos
/* se permite escribir en el fichero */
sleep(5);
sem_wait(&sincro);
/* se espera hasta que se haya escrito en el fichero */
write(sal, &c, 1);
}
}

void escribir_en_fichero(int fd)


{
char c;
pid_t pid;

for(;;)
{
read(0, &c, 1);
sem_wait(&sincro);
/* se espera a estar en posesin del testigo */
write(fd, &c, 1);
pid = getpid();
printf("Proceso %d escribe en el fichero\n", pid);
sem_post(&sincro);
/* se permite enviar el testigo al siguiente proceso */
}
}

Problema 1.2 (septiembre 1998)

Escribir un programa en C, que implemente el mandato cp. La sintaxis de este programa ser la siguiente:
cp f1 f2
donde f1 ser el fichero origen y f2 el fichero destino respectivamente (si el fichero existe lo trunca y sino lo
crea). El programa deber realizar un correcto tratamiento de errores.
Se quiere mejorar el rendimiento del programa anterior desarrollando una versin paralela del mandato cp, que
denominaremos cpp (vase figura 1.3). La sintaxis de este mandato ser:
cpp n f1 f2

cpp

1 2 n

Fichero origen
copia

Fichero destino
Figura 1.3

donde f1 ser el fichero origen y f2 el fichero destino respectivamente, y n el nmero de procesos que debe crear
el programa cpp para realizar la copia en paralelo (ver figura 1.3). Este programa se encargar de:
a) Crear el fichero destino.
b) Calcular el tamao que tiene que copiar cada uno de los procesos hijo y la posicin desde la cual debe co-
menzar la copia cada uno de ellos
c) Crear los procesos hijos, encargados de realizar la copia en paralelo
d) Deber esperar la terminacin de todos los procesos hijos.
5
Nota: Suponga que el tamao del fichero a copiar es mayor que n. Recuerde que en C, el operador de
divisin, /, devuelve la divisin entera, cuando se aplica sobre dos cantidades enteras, y que el operador modulo es
%.

Solucin
a) El programa propuesto es el siguiente:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#define MAX_BUF 4096

int main(int argc, char **argv)


{
int fde, fds;
int leidos;
char buffer[MAX_BUF];

if (argc != 3) {
printf("Uso: cp f1 f2 \n");
exit(1);
}

fde = open(argv[1], O_RDONLY); /* se abre el fichero de entrada */


if (fde < 0) {
perror("Error al abrir el fichero de entrada\n");
exit(1);
}

fds = creat(argv[2], 0644); /* se crea el fichero de salida */


if (fds < 0) {
perror("Error al crear el fichero de salida\n");
close(fde);
exit(1);
}

/* bucle de lectura del fichero de entrada y escritura en el fichero de salida */


while ((leidos = read(fde, buffer, MAX_BUF)) > 0)
if (write(fds, buffer, leidos) != leidos) {
perror("Error al escribir en el fichero\n");
close(fde);
close(fds);
exit(1);
}

if (leidos == -1)
perror("Error al leer del fichero\n");

if ((close(fde) == -1) || (close(fds) == -1))


perror("Error al cerrar los ficheros\n");

return 0;
}
b) En este caso el programa se encargar de:
Crear el fichero destino.
Calcular el tamao que tiene que copiar cada uno de los procesos hijo y la posicin desde la cual debe
comenzar la copia cada uno de ellos
Crear los procesos hijos, encargados de realizar la copia en paralelo
Deber esperar la terminacin de todos los procesos hijos.
6 Problemas de sistemas operativos
Cada uno de los procesos hijos debe abrir de forma explcita tanto el fichero de entrada como el fichero de salida
para disponer de sus propios punteros de posicin. En caso contrario todos los procesos heredaran y compartiran el
puntero de la posicin sobre el fichero de entrada y salida y el acceso a los ficheros no podra hacerse en paralelo.

#include <sys/types.h>
#include <wait.h>#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define MAX_BUF 4096

int main(int argc, char **argv)


{
char buffer[MAX_BUF];
int fde; /* descriptor del fichero de entrada */
int fds; /* descriptor del fichero de salida */
int n; /* nmero de procesos */
int size_of_file; /* tamao del fichero de entrada */
int size_proc; /* tamao a copiar por cada proceso */
int resto; /* resto que copia el ltimo proceso */
int aux; /* variables auxiliares */
int leidos;
int j;

if (argc != 4){
printf("Error, uso: cpp n f1 f2 \n");
return 0;
}

n = atoi(argv[1]); /* numero de procesos */

fde = open(argv[2], O_RDONLY); /* se abre el fichero de entrada */


if (fde < 0) {
perror("Error al abrir el fichero de entrada \n");
return 0;
}

fds = creat(argv[3], 0644); /* se crea el fichero de salida */


if (fds < 0) {
close(fde);
perror("Error al crear el fichero de salida \n");
return 0;
}

/* obtener el tamao del fichero a copiar */


size_of_file = lseek(fde, 0, SEEK_END);

/* calcular el tamao que tiene que escribir cada proceso */


size_proc = size_of_file / n;

/* El ltimo proceso escribe el resto */


resto = size_of_file % n;

/* el proceso padre cierra los ficheros ya que no los necesita*/


/* cada uno de los procesos hijo debe abrir los ficheros*/
/* de entrada y salida para que cada uno tenga sus propios*/
/* punteros de posicin */

for (j = 0; j < n; j++) {


if (fork() == 0) {
/* se abren los ficheros de entrada y salida */
7
fde = open(argv[2], O_RDONLY);
if (fde < 0) {
perror("Error al abrir el fichero de entrada \n");
return 0;
}

fds = open(argv[3], O_WRONLY);


if (fds < 0) {
perror("Error al abrir el fichero de entrada \n");
return 0;
}

/* Cada hijo sita el puntero en el lugar correspondiente */


lseek(fde, j * size_proc, SEEK_SET);
lseek(fds, j * size_proc, SEEK_SET);

/* el ultimo proceso copia el resto */


if (j == n - 1) /* ltimo */
size_proc = size_proc + resto;

/* bucle de lectura y escritura */


while (size_proc > 0) {
aux = (size_proc > MAX_BUF ? MAX_BUF : size_proc);
leidos = read(fde, buffer, aux);
write(fds, buffer, leidos);
size = size - leidos;
}

close(fde);
close(fds);
return 0;
}
}

/* esperar la terminacin de todos los procesos hijos */


while (n > 0) {
wait(NULL);
n --;
}
return 0;
}

Problema 1.3
Escribir un programa que cree dos procesos que acten de productor y de consumidor respectivamente. El produc-
tor abrir el fichero denominado /tmp/datos.txt y leer los caracteres almacenados en l. El consumidor tendr que
calcular e imprimir por la salida estndar el nmero de caracteres almacenados en ese fichero sin leer del fichero.
La comunicacin entre los dos procesos debe hacerse utilizando un pipe. Por otra parte, el proceso padre tendr
que abortar la ejecucin de los dos procesos hijos, si transcurridos 60 segundos stos no han acabado su ejecucin.

Solucin
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
8 Problemas de sistemas operativos
#define MAXBUF 512

pid_t pid1;
pid_t pid2;

void MatarProcesos(void)
{
/* Esta funcin se encargar de matar a los procesos hijos */
kill(pid1, SIGKILL);
kill(pid2, SIGKILL);
exit(0);
}

void Productor(int f)
{
/* El proceso productor lee del fichero y escribe los datos en el pipe
utilizando el descriptor de escritura que se pasa como parmetro */
int fd;
int n;
char buf[MAXBUF];

fd = open("/tmp/datos.txt", O_RDONLY);
if (fd == -1)
perror("Error en open");
else {
while ((n = read(fd, buf, MAXBUF)) != 0)
write(f, buf, n);
if (n == -1)
perror("Error en write");
}

close(fd);
return;
}

void Consumidor(int f)
{
/* El proceso consumidor lee del pipe utilizando el escriptor de lectura
que se pasa como parmetro y escribe por la salida estndar (descript. 1) */
int n;
char buf[MAXBUF];

while ((n = read(f, buf, MAXBUF)) != 0)


write(1, buf, n);

if (n == -1)
perror("Error en read ");

return;
}

int main(void)
{
int fd[2];
struct sigaction act;

/* se crea el pipe */
if (pipe(fd) < 0) {
perror("Error al crear el pipe");
return 0;
}

pid1 = fork(); /* Se crea el primer hijo */


9
switch (pid1) {
case -1: /* error */
perror("Error en el fork");
return 0;
case 0: /* proceso hijo Productor */
close(fd[0]);
Productor(fd[1]);
close(fd[1]);
return 0;
default: /* proceso padre */
pid2 = fork(); /* Se crea el segundo hijo */
switch (pid2) {
case -1: /* error */
perror("Error en el fork");
return 0;
case 0: /* proceso hijo consumidor */
close(fd[1]);
Consumidor(fd[0]);
close(fd[0]);
return 0;
default: /* proceso padre */
/* Cierra los descriptores de lectura y escritura porque no
Los necesita */
close(fd[0]);
close(fd[1]);

/* Se prepara para recibir la seal SIGALRM */


act.sa_handler = MatarProcesos;
act.sa_flags = 0;
sigaction(SIGALRM, &act, NULL);
alarm(60);
/* Cuando llegue la seal SIGLARM se ejecuta el cdigo
De la funcin MatarProcesos que mata a los dos procesos hijo */

/* Se espera a que acaben los dos procesos hijos. Si vence el


Temporizador antes se les mata */
wait(NULL);
wait(NULL);
}
}
return 0;
}

Problema 1.4
Se quiere desarrollar una aplicacin que se encargue de ejecutar los archivos que se encuentren en una serie de di -
rectorios (todos los archivos sern ejecutables). Para ello, se deben seguir los siguientes pasos:
a) Escribir un programa que cree cuatro procesos, que denominaremos de forma lgica 0, 1, 2 y 3. Estos cuatro
procesos estarn comunicados entre ellos a travs de un nico pipe. Uno de los procesos (por ejemplo el 0)
ejecutar el cdigo de una funcin que se denomina Ejecutar() y el resto (procesos 1, 2 y 3) ejecutarn
una funcin que se denomina Distribuir(). Los cdigos de estas funciones se describen en los siguien-
tes apartados.
b) La funcin Distribuir() en el proceso i abrir el directorio con nombre directorio_i y leer las en-
tradas de este directorio (todas las entradas se refieren a nombres de archivos ejecutables). Cada vez que lea
una entrada enviar, utilizando el pipe creado en el apartado a, el nombre del archivo al proceso que est
ejecutando la funcin Ejecutar(). Cuando el proceso que est ejecutando la funcin Distribuir()
llega al fin del directorio acaba su ejecucin. Escribir el cdigo de la funcin Distribuir() para el pro-
ceso i.
10 Problemas de sistemas operativos
c) El proceso que ejecuta la funcin Ejecutar() leer del pipe que le comunica con el resto de procesos
nombres de archivos. Cada vez que lee un archivo lo ejecuta (recuerde que todos los archivos de los directo-
rios son ejecutables) esperando a que acabe su ejecucin. Este proceso finaliza su trabajo cuando todos los
procesos que ejecutan la funcin Distribuir() han acabado. Escribir el cdigo de la funcin Ejecu-
tar(). Existe algn problema en la lectura que realiza este proceso del pipe?
Nota: Para el desarrollo de este ejercicio considere que todos los nombres de archivos tienen 20 caracteres y
que todos los directorios se encuentran en la variable de entorno PATH.

Solucin
a) El programa propuesto es el siguiente:
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <dirent.h>

#define NOMBRE_SIZE 20

int main(void)
{
int fd[2]; /* pipe que comunica a todos los procesos*/
int j;
int pid;

if (pipe(fd) < 0) {
perror("Error en pipe");
exit(1);
}

for(j=0; j < 4; j++) {


pid = fork();
switch (pid){
case -1:
perror("Error en fork");
close(fd[0]);
close(fd[1]);
exit(1);
case 0:
if (j == 0){
close(fd[1]); /* Ejecutar no lo utiliza */
Ejecutar(fd[0]);
close(fd[0]);
return 0;
}
else {
close(fd[0]); /* Distribuir no lo utiliza */
Distribuir(fd[1], j);
close(fd[1]);
return 0;
}
default:
break;
}
}

/* el proceso inicial cierra los descriptores del pipe y acaba su ejecucin*/


close(fd[0]);
close(fd[1]);
return 0;
}
11
b) El programa propuesto es el siguiente:
void Distribuir(int fd, int i)
{
DIR *dirp;
struct dirent *dp;

dirp = opendir("directorio_i");
if (dirp == NULL) {
perror("Error en opendir");
return;
}

while ((dp=readdir(dirp))!= NULL){


/* Se saltan las entradas . y .. */
if ((strcmp(dp->d_name, ".") == 0) ||
(strcmp(dp->d_name, "..") == 0))
continue;

/* se escribe el nombre en el pipe. La escritura es atmica */


if (write(fd, dp->d_name, NOMBRE_SIZE) < NOMBRE_SIZE){
perror("Error en write");
closedir(dirp);
return;
}
}
return;
}
c) El programa propuesto es el siguiente:
void Ejecutar(int fd)
{
int pid;
char nombre[NOMBRE_SIZE];

while(read(fd, nombre, NOMBRE_SIZE) > 0){


pid = fork();
switch(pid){
case -1:
perror("Error en fork");
break;
case 0:
close (fd);
execlp(nombre, nombre, NULL);
perror("Error en execlp");
exit(1);
Default:
/* Espera la terminacin del proceso creado */
if (wait(NULL) < 0)
perror("Error en wait");
}
}

return;
}
El proceso que ejecuta la funcin Ejecutar() acaba cuando no quedan escritores en el pipe (los nicos procesos
escritores del pipe son aquellos que ejecutan la funcin Distribuir), por ello es importante que este proceso cierre el
descriptor fd[1] antes de comenzar la lectura del pipe. Recurdese que una lectura de un pipe sin escritores no blo -
quea y devuelve 0.
12 Problemas de sistemas operativos
La lectura del pipe no plantea ningn problema ya que las operaciones de acceso al pipe (read y write) se
realizan de forma atmica. Adems el proceso acaba cuando no queda ningn proceso escritor (proceso que ejecuta
la funcin Distribuir).

Problema 1.5 (junio 2000)

Se desea construir la funcin int pipe_two(int fd[4]), que asigna a las 4 entradas del vector fd el valor de los 4 nue-
vos descriptores, segn la siguiente relacin:
fd[0]: Primer descriptor de lectura, que lee los datos que se escriban a travs de fd[2]
fd[1]: Segundo descriptor de lectura, que lee los datos que se escriban a travs de fd[3]
fd[2]: Primer descriptor de escritura.
fd[3]: Segundo descriptor de escritura.
Dicha funcin devuelve 0 si su ejecucin ha sido correcta y 1 en caso de error.
Sobre este pipe doble, se definen las siguientes operaciones de lectura y escritura:
int read_from2 (int fd[2], char *buffer, int n_bytes): almacena en buffer los datos ledos de fd[0] y fd[1], de
forma que se lee un byte de fd[0] y se almacena en la primera posicin del buffer; a continuacin se lee un
byte de fd[1] y se almacena en la siguiente posicin del buffer y as sucesivamente. n_bytes contiene el nme -
ro de bytes totales a leer. Se supone que n_bytes es un nmero par.
La funcin devuelve el nmero total de bytes ledos si su ejecucin ha sido correcta y 1 en caso de error.
int write_to2 (int fd[2], char *buffer, int n_bytes): escribe los bytes que se encuentren en las posiciones pares
del buffer en el fichero descrito por fd[0] y las posiciones impares en el fichero descrito por fd[1]. n_bytes
contiene el nmero de bytes totales que se van a escribir.
Se considera que la primera posicin del buffer es la posicin 0 y que es una posicin par.
La funcin devuelve el nmero de bytes escritos si su ejecucin ha sido correcta y 1 en caso de error.
Se pide:
a) Implementar las funciones pipe_two( ), read_from2( ) y write_to2( ).
b) Utilizando las anteriores funciones, programar un proceso que realice los siguientes pasos:
Crea 2 procesos hijos.
Lee los caracteres del fichero /usr/datos.txt y a uno de los procesos hijos le pasa los caracteres pares
y a otro los caracteres impares.
c) Los procesos hijos leen los datos y los procesan mediante la funcin char * tratar_datos(char *buffer). La
funcin devuelve una cadena de caracteres, que contiene los datos procesados. Esta funcin no hay que im-
plementarla. Slo se debe utilizar.
d) Los procesos hijos envan los datos procesados de nuevo al padre.
e) El padre recibe los datos de ambos hijos, mezclndolos en un buffer; es decir, lee los caracteres que les en-
va los procesos hijos de forma alternativa.
El proceso que se pide implementar sigue la estructura que se representa en la figura 1.4:

Padre
pipe_two pipe_two

Hijo1 Hijo2

Figura 1.4

Solucin
a) La funcin pipe_two construye 2 pipes y los enlaza de la forma indicada en el enunciado, es decir, enlaza el des-
criptor fd[0] con el descriptor fd[2] y el descriptor fd[1] con el descriptor fd[3]:
int pipe_two(int fd[4]) {
int pipe1[2], pipe2[2];
int status=0;
13

if (pipe(pipe1) == -1)
status = -1;
else if (pipe(pipe2) == -1){
close(pipe1[0];
close(pipe1[1];
status = -1;
}

if (status == 0) {
fd[0]= pipe1[0];
fd[1]= pipe2[0];
fd[2]= pipe1[1];
fd[3]= pipe2[1];
}
return status;
}
La funcin read_from2 hace una lectura alternativa de los ficheros cuyos descriptores recibe como argumento.
Vamos a suponer que si uno de los ficheros llega al final, read_from2 deja de leer en esa vuelta del bucle y devuelve
el nmero de bytes ledos hasta entonces. Por otro lado, si hay una lectura incorrecta de alguno de los ficheros, ter -
mina inmediatamente devolviendo 1:

int read_from2(int fd[2], char *buffer, int n_bytes) {

int bytes_read=1;
int bytes_total=0;

for (i=0; ((i<n_bytes) && (bytes_read != 0)); i=i+2) {


if ((bytes_read = read(fd[0],(buffer+i),1)) == -1) {
bytes_total = -1;
break;
}
else {
bytes_total + = bytes_read;
}

if ((bytes_read = read(fd[1],(buffer+i+1),1) == -1) {


bytes_total = -1;
break;
}
else {
bytes_total + = bytes_read;
}
}
return bytes_total;
}
La funcin write_to2 escribe de forma alternativa en los ficheros cuyos
descriptores recibe como argumento. Si hay una escritura incorrecta en alguno
de los ficheros, termina inmediatamente devolviendo 1:

int write_to2 (int fd[2], char *buffer, int n_bytes) {

int bytes_write;
int bytes_total = 0;

for (i=0; i<n_bytes ; i=i+2) {


if ((bytes_write = write(fd[0],(buffer+i),1)) == -1) {
bytes_total = -1;
break;
}
else {
14 Problemas de sistemas operativos
bytes_total + = bytes_read;
}

if ((bytes_read = write(fd[1],(buffer+i+1),1) == -1) {


bytes_total = -1;
break;
}
else {
bytes_total + = bytes_read;
}
}
return bytes_total;
}
b) Se va a suponer que el fichero /usr/datos.txt tiene un nmero par de caracteres y que ocupa un mximo de 8192
bytes. (Esto se define en la constante MAX_BUF).
#define MAX_BUF 8192
/* En la funcin main() no se han tratado todos los casos de errores.
*/
int main(void) {
int fd[4], fd_file, bytes_read;
char buffer_write[MAX_BUF];
char buffer_read[MAX_BUF];

/* Creacin del pipe doble */


if (pipe_two(fd) < 0)
{
perror("pipe_two");
exit(1);
}

/* Creacin del primer proceso hijo */


switch (pid =fork()) {
case -1:
perror("fork 1");
close(fd[0]);
close(fd[1]);
close(fd[2]);
close(fd[3]);
exit(1);
case 0: /* Primer proceso hijo */

/* Cierro los descriptores que no utilizo */


close(fd[1]);
close(fd[3]);
/* Lee del primer descriptor de lectura */
bytes_read=read(fd[0],buffer_read, MAX_BUF);

buffer_write = tratar_datos(buffer_read);
/* Escribo en el primer descriptor de escritura */
write(fd[2],buffer_write, bytes_read);

/* Cierro el resto de descriptores */


close(fd[0]);
close(fd[2]);
return 0;

default: /* Proceso padre */


/* Creacin del segundo proceso hijo */
switch (pid =fork()) {
case -1:
perror("fork 2");
15
close(fd[0]);
close(fd[1]);
close(fd[2]);
close(fd[3]);
exit(1);
case 0: /* Segundo proceso hijo */

/* Cierro los descriptores que no utilizo */


close(fd[0]);
close(fd[2]);

/* Lee del segundo descriptor de lectura */


bytes_read=read(fd[1],buffer_read, MAX_BUF);
buffer_write = tratar_datos(buffer_read);
/* Escribo en el segundo descriptor de escritura */
write(fd[3],buffer_write, bytes_read);

/* Cierro el resto de descriptores */


close(fd[1]);
close(fd[3]);
return 0;

default: /* Proceso padre */


/* Leo los caracteres del fichero datos */
fd_file = open("/usr/datos.txt",O_RDONLY);
bytes_read=read(fd_file,buffer_write,MAX_BUF);

/* Escribo los bytes leidos a los


descriptores de escritura: fd[2], fd[3] */
write_to_2(fd+2,buffer_write,bytes_read);

/* Leo los datos que me pasan los procesos hijos


en el buffer de lectura de los descriptores de
lectura: fd[0] y fd[1] */
bytes_read= read_from2(fd,buffer_read,MAX_BUF);

/* Cierro todos los descriptores */


close(fd[0]);
close(fd[1]);
close(fd[2]);
close(fd[3]);

/* Aqu ira el cdigo de uso de buffer_read */


}
}
return 0;
}

Problema 1.6 (junio 2002)

a) Se desea implementar un programa que, utilizando los servicios de Unix, ejecute un ls recursivo, es decir, un
programa que liste los contenidos de un directorio, de todos sus subdirectorios y as sucesivamente hasta llegar a
las hojas del rbol que representa dicho directorio. No se debe utilizar en ningn caso el mandato ls ya imple -
mentado. El programa debe imprimir el rbol de directorios en profundidad.
Por ejemplo, para el rbol de directorios de la figura 1.5, ejecutando:
$ ls_recursivo /home
16 Problemas de sistemas operativos
home

users tmp doc mnt

prg datos otro otro

pr.c
Figura 1.5

Se obtendra la siguiente salida:


/home/users
/home/users/prg
/home/users/prg/pr.c
/home/users/datos
/home/users/otro
/home/tmp
/home/doc
/home/mnt
/home/mnt/otro
b) Se pide implementar el programa mifind_recursivo, a partir del ls implementado en el apartado a) y a partir
del mandato grep. No se debe implementar este ltimo mandato.
El programa mifind_recursivo debe tener el mismo comportamiento que el mandato find de Unix, que bus-
ca ficheros en un rbol de directorios. Recibe como argumentos el directorio a partir del cul se hace la bsqueda y
el nombre del fichero que se busca. La bsqueda se realiza de forma recursiva visitando todos los subdirectorios a
partir del inicial.
Ejemplo:
$ mifind_recursivo /home otro
La ejecucin de este mandato tendr la siguiente salida:
/home/users/otro
/home/mnt/otro
Aclaracin: El mandato grep permite mostrar lneas que concuerden con un patrn. Este mandato lee de la
entrada estandar y encuentra el patrn que se le pase como argumento. Ejemplo:
$ grep hola
La ejecucin de este mandato lee de la entrada estndar e imprime todas las lneas que contengan la palabra
hola.
c) Imagine que existe una implementacin multi-thread del ls recursivo, que crea un nuevo thread para explorar
cada directorio encontrado.
Plantear qu mecanismos de sincronizacin son necesarios en esta implementacin, de forma que la solucin
multi-thread produzca un resultado idntico (en el mismo orden) al producido por la solucin secuencial. No imple-
mentar nada. Responder brevemente.

Solucin
a) Supongo que se pasa a la funcin el nombre absoluto del directorio. Si no fuera as, habra que concatenar el di-
rectorio actual.
#define MAX_BUF 256
/* ls_recursivo */
int main(int argc, char *argv[])
{
struct stat inf_dir;
char buf[MAX_BUF];

if (argc != 2)
{
fprintf(stderr,"Error. Uso: ls_recursivo nombre_direct\n");
exit(1);
17
}

/* Comprobar si es un directorio */
stats(argv[1],&inf_dir);
if (!S_ISDIR(inf_dir.st_mode)){
fprintf(stderr,"Error. No es un directorio\n");
exit(1);
}

if (funcion_ls_recursivo(argv[1])<0)
{
fprintf(stderr,"Error: ls_recursivo\n");
exit(1);
}
return 0;
}

int funcion_ls_recursivo(char *nombre_directorio)


{
char buf[MAX_BUF];
struct dirent *dp;
DIR *dirp;
struct stat inf_dir;
char path_completo[MAX_BUF];

dirp = opendir(nombre_directorio);
if (dirp==NULL)
{
return 1;
}

while ((dp = readdir(dirp)) = NULL)


{
/* Imprime el directorio o fichero */
path_completo = strcat(nombre_directorio,dp->d_name);
fprintf(stdout,"%s\n",path_completo);
stat(path_completo,&inf_dir);
/* Si es un directorio, hacemos una
llamada recursiva */
if (S_ISDIR(inf_dir.st_mode)){
funcion_ls_recursivo(path_completo);
}
}
return 0;
}
b) La solucin consiste en unir mediante un pipe los procesos ls_recursivo y grep, de forma que la salida del
primero sea la entrada del segundo.
/* mifind_recursivo */
int main (int argc, char *argv[])
{
int fd[2];
int pid, pid2,pid3;

if (argc != 3)
{
fprintf(stderr,"Error. Uso: mi_find_recursivo directorio fichero\n");
exit(1);
}
if (pipe(fd)<0){
perror("pipe");
exit(1);
18 Problemas de sistemas operativos
}

pid = fork();
switch (pid)
{
case 1:
close(fd[0]);
close(fd[1]);
perror("fork");
exit(1);
case 0:
close(fd[0]);
close(1);
dup(fd[1]);
close(fd[1]);
pid2=fork();
switch(pid2)
{
case 1:
perror("fork");
exit(1);
case 0:
execl("ls_recursivo",argv[1],NULL);
perror("execl");
default:
wait(NULL);
return 0;
}
default:
close(fd[1]);
close(0);
dup(fd[0]);
close(fd[0]);
pid3 = fork();
switch (pid3)
{
case 1:
perror("fork");
exit(1);
case 0:
execl("grep",argv[2],NULL);
perror("execl");
default:
wait(NULL);
wait(NULL);
return 0;
}
}
return 0;
}
c) La solucin multi-thread debe utilizar mecanismos de sincronizacin de cara a imprimir la salida, ya que tiene
que seguir el orden expuesto en el problema. El resto de la operacin (lectura de los directorios) debe hacerse concu -
rrentemente, para beneficiarse de la caracterstica multi-thread. Se puede utilizar cualquier mecanismo de sincroni -
zacin, siendo bastante aconsejable utilizar mtex y variables condicionales, por su afinidad con los threads. Habra
que utilizar varios mtex para secuenciar la salida en el orden predeterminado. Un nico mtex no asegura ese or -
den.
19

Problema 1.7
Se desea desarrollar una aplicacin que debe realizar dos tareas que se pueden ejecutar de forma independiente.
Los cdigos de estas dos tareas se encuentran definidos en dos funciones cuyos prototipos en lenguaje de progra -
macin C, son los siguientes:
void tarea_A(void);
void tarea_B(void);
Se pide:
a) Programar la aplicacin anterior utilizando tres modelos distintos:
1. Un nico proceso.
2. Procesos para aprovechar paralelismo. Plantear una solucin que minimice el nmero de procesos crea-
dos y solicite el mnimo nmero de servicios al sistema operativo.
3. Procesos ligeros para aprovechar paralelismo. En este caso tambin se minimizar el nmero de procesos
ligeros creados y el nmero de servicios.
En cualquiera de los tres casos la aplicacin debe terminar cuando se hayan acabado las tareas A y B.
b) Suponiendo que las tareas tienen las siguientes fases de ejecucin:
A: 60 ms de CPU, lectura de 120 ms sobre el dispositivo 1, 60 ms de CPU
B: 40 ms de CPU, escritura de 100 ms sobre el dispositivo 2, 40 ms de CPU
Realizar un diagrama que muestre la ejecucin de los procesos en el sistema, obteniendo el tiempo que tardar
la aplicacin en terminar en cada uno de los modelos anteriores. Se tomar como t = 0 el instante en el que empie -
za la ejecucin del primer proceso.
Para la realizacin del apartado b se considerarn los siguientes supuestos:
1. El sistema operativo emplea un algoritmo de planificacin FIFO.
2. Se comenzar con la ejecucin de la tarea A.
3. Se tendr en cuenta el tiempo de ejecucin de los siguientes servicios que ofrece el sistema operativo:
- fork: 80 ms
- exec: 100 ms
- Tratamiento de una interrupcin de perifrico: 10 ms
Otros servicios (read, write, wait, pthread_create, pthread_join, exit, pthread_exit, etc. ): 10 ms
NOTA: los tiempos del SO no son realistas, puesto que estn por debajo del ms. Se han supuesto estas cifras
para que la grfica con la solucin quede ms clara.
4. En el sistema no ejecuta ningn otro proceso y no se tendrn en cuenta las interrupciones del reloj.
5. Cuando un proceso crea a otro, contina la ejecucin el primero.

Solucin
a) Con un nico proceso el programa quedara de la siguiente forma:
int main(void)
{
tarea_A();
tarea_B();
return 0;
}
En este caso las dos tareas se ejecutan de forma secuencial.
Cuando se utilizan procesos para aprovechar paralelismo, la solucin sera la siguiente:
int main(void)
{
pid_t pid;
20 Problemas de sistemas operativos

pid = fork();
if (pid == 0) /* el hijo ejecuta la tarea B */
Tarea_B;
else { /* el padre ejecuta la tarea A */
tarea_A();
wait(NULL); /* espera a que el hijo acabe */
}
return 0;
}
Empleando procesos ligeros la solucin sera:
int main(void)
{
pthread_t t;
int pid;

/* Se crea un proceso ligero para que ejecute la tarea B*/


pthread_create(&t, NULL, tarea_B, NULL);

/* El proceso ligero principal ejecuta la tarea A de forma concurrente con el proceso ligero creado anteriormente */
tarea_A();

/* En este punto el proceso ligero principal ha acabado la tarea A. Espera a que acabe el proceso ligero que ejecuta la tarea B
*/
pthread_join(t, NULL);
return 0;
}
El proceso ligero que ejecuta la tarea B, acaba cuando se llega al fin de la funcin tarea_B().
b) Los diagramas de tiempo se encuentran en las figuras 1.6, 1.7 y 1.8.
A Lectura
B Escritura Exit
E
Proceso LE
B

E
SO B

P. Nulo E
LE
A Interrupcin
Dispos. 1 B

A Interrupcin
Dispos. 2 B

50 100 150 200 250 300 350 400 450 t


Figura 1.6 Caso de un solo proceso

E
Fork Lectura Wait Exit
P. Padre LE
B

E Escritura Exit
P. Hijo LE
B

E
SO B

P. Nulo E
LE
A Interrupcin
Dispos. 1 B

A Interrupcin
Dispos. 2 B

50 100 150 200 250 300 350 400 450


t
Figura 1.7 Caso de dos procesos
21

PL crate
E Lectura Join Exit
PL. Main LE
B

E Escritura PL Exit
PL. Hijo LE
B
E
SO B

P. Nulo E
LE

A Interrupcin
Dispos. 1 B
A Interrupcin
Dispos. 2 B

50 100 150 200 250 300 350 400 450


t
Figura 1.8 Caso de procesos ligeros

Problema 1.8
El objetivo de este ejercicio es mostrar el uso de los dos mecanismos disponibles en UNIX para construir aplicacio -
nes concurrentes: los procesos convencionales, creados mediante la llamada fork, y los procesos ligeros o threa-
ds.
Para ello, se va a construir una aplicacin, denominada lincon, que recibe como argumento un conjunto de
ficheros y que muestra por la salida estndar el nmero de lneas que tiene cada uno de ellos y el nmero de lneas
total. Se pretende que dicha aplicacin procese de forma concurrente cada uno de los ficheros especificados por el
usuario. Adems, la aplicacin debe cumplir los siguientes requisitos:
a) Si no se puede acceder a alguno de los ficheros especificados, debern procesarse normalmente los ficheros
restantes, pero el programa deber terminar devolviendo un 1. Si todo va bien, devolver un 0. Ntese que se
est siguiendo la tpica convencin usada en UNIX.
b) Los ficheros se leern realizando lecturas de 8 KiB.
Se desarrollarn dos versiones de la aplicacin: una con threads y otra con procesos convencionales.

Solucin
Programa basado en threads. Se presentan dos versiones de este programa:
Uso de una variable global para acumular el nmero total de lneas (lincon_thr_v1.c). Es necesario
crear una seccin crtica para evitar los problemas de carrera que pueden producirse al actualizar la varia-
ble global.
Cada thread devuelve el nmero de lneas del fichero que ha procesado (lincon_thr_v2.c). Elimina
la necesidad de la seccin crtica.
Recuerde que debe especificar la biblioteca de threads a la hora de generar el programa:
cc -o lincon lincon.c -lpthread
Programa basado en procesos convencionales. Se presentan tres versiones de este programa:
a) Uso de una variable global para acumular el nmero total de lneas (lincon_proc_v1.c). Ntese
que esta versin no calcula bien el total: siempre sale 0. El problema no se debe a ninguna condicin de
carrera, sino a que cada proceso tiene su propio mapa de memoria. Por tanto, cuando un proceso hijo
actualiza la variable global, este cambio no afecta al proceso padre ni a ninguno de sus hermanos. sta
es una de las principales diferencias entre los procesos convencionales y los threads.
b) Cada proceso devuelve el nmero de lneas del fichero que ha procesado (lincon_proc_v2.c).
Esta versin slo funciona si el fichero que se procesa tienen como mucho 254 lneas. Esto se debe a
que en la llamada wait slo se reciben los 8 bits de menos peso del valor especificado en el exit co-
rrespondiente. Para entender esta extraa limitacin, hay que tener en cuenta que en el valor devuelto
por la llamada wait hay ms informacin, aparte del valor devuelto por el programa (por ejemplo, se
indica s el programa termin normalmente o debido a una seal). Ntese que tampoco sera vlida esta
versin para ficheros con 255 lneas, pues ese valor corresponde con el caso en el que el proceso hijo
devuelve un -1 para indicar que no ha podido procesar el fichero.
22 Problemas de sistemas operativos
c) Versin que usa una tubera para que cada hijo mande al padre el nmero de lneas del fichero que le ha
tocado procesar (lincon_proc_v1.c).
Fichero lincon_thr_v1.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define TAMBUF 8192

int totlin=0;

pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER;

int contar(char *arg) {


int fo, nbytes, i, nlineas=0;
char buf[TAMBUF];

if ((fo=open(arg, O_RDONLY))<0) {
perror("Error abriendo fichero");
return -1;
}
while ( (nbytes= read(fo, buf, TAMBUF)) >0)
for (i=0; i<nbytes; i++)
if (buf[i]==\n) nlineas++;

if (nbytes<0) {
perror("Error leyendo en fichero");
return -1;
}
printf("%s %d\n", arg, nlineas);

/* Seccin crtica para actualizar el total */


pthread_mutex_lock(&mutex);
totlin+= nlineas;
pthread_mutex_unlock(&mutex);

return 0;
}

int main(int argc, char *argv[]) {


int i, estado=0, retorno;
pthread_t *thid;

if (argc<2) {
fprintf(stderr, "Uso: %s fichero ...\n", argv[0]);
exit(1);
}

/* Reserva en tiempo de ejecucin el espacio para almacenar los


descriptores de los threads */
thid= malloc((argc -1)*sizeof(pthread_t));

for (i=0; i<argc-1; i++)


if (pthread_create(&thid[i], NULL, (void * (*)(void *))contar, (void
*)argv[i+1])!=0) {
perror("Error creando thread");
exit (1);
}
23
for (i=0; i<argc-1; i++) {
pthread_join(thid[i], (void **)&retorno);
if (retorno<0)
estado=1;
}
printf("total %d\n",totlin);
exit(estado);
}
Fichero lincon_thr_v2.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define TAMBUF 8192

int contar(char *arg) {


int fo, nbytes, i, nlineas=0;
char buf[TAMBUF];

if ((fo=open(arg, O_RDONLY))<0) {
perror(Error abriendo fichero);
return -1;
}
while ( (nbytes= read(fo, buf, TAMBUF)) >0)
for (i=0; i<nbytes; i++)
if (buf[i]==\n) nlineas++;

if (nbytes<0){
perror(Error leyendo en fichero);
return -1;
}
printf(%s %d\n, arg, nlineas);
return nlineas;
}

int main(int argc, char *argv[]) {


int i, estado=0;
pthread_t *thid;
int nlineas, totlin=0;

if (argc<2) {
fprintf(stderr, Uso: %s fichero ...\n, argv[0]);
exit(1);
}

/* Reserva en tiempo de ejecucin el espacio para almacenar los


descriptores de los threads */
thid= malloc((argc -1)*sizeof(pthread_t));

for (i=0; i<argc-1; i++)


if (pthread_create(&thid[i], NULL, (void * (*)(void *))contar, (void
*)argv[i+1])!=0) {
perror(Error creando thread);
exit (1);
}
for (i=0; i<argc-1; i++) {
pthread_join(thid[i], (void **)&nlineas);
if (nlineas>=0)
totlin+=nlineas;
24 Problemas de sistemas operativos
else
estado=1;
}
printf(total %d\n,totlin);
exit(estado);
}
Fichero lincon_proc_v1.c
/* Esta versin no funciona ya que no se actualiza el total */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

#define TAMBUF 8192

int contar(char *arg) {


int fo, nbytes, i, nlineas=0;
char buf[TAMBUF];

if ((fo=open(arg, O_RDONLY))<0) {
perror("Error abriendo fichero");
return -1;
}
while ( (nbytes= read(fo, buf, TAMBUF)) >0)
for (i=0; i<nbytes; i++)
if (buf[i]==\n) nlineas++;

if (nbytes<0) {
perror("Error leyendo en fichero");
return -1;
}
printf("%s %d\n", arg, nlineas);

/* Se pretende actualizar el total, pero no funciona... */


totlin+= nlineas;

return 0;
}

int main(int argc, char *argv[]) {


int i, estado=0, retorno, valor;

if (argc<2) {
fprintf(stderr, "Uso: %s fichero ...\n", argv[0]);
exit(1);
}

for (i=0; i<argc-1; i++)


if (fork()==0) {
valor=contar(argv[i+1]);
exit(valor);
}
for (i=0; i<argc-1; i++) {
wait(&retorno);
if (retorno!=0)
estado=1;
}
printf("total %d\n",totlin);
exit(estado);
25
}
Fichero lincon_proc_v2.c
/* Esta versin no funciona para ficheros que tengan ms de 254 lneas por la limitacin existente en el rango del valor
devuelto por el exit */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

#define TAMBUF 8192

int contar(char *arg) {


int fo, nbytes, i, nlineas=0;
char buf[TAMBUF];

if ((fo=open(arg, O_RDONLY))<0) {
perror("Error abriendo fichero");
return -1;
}
while ( (nbytes= read(fo, buf, TAMBUF)) >0)
for (i=0; i<nbytes; i++)
if (buf[i]==\n) nlineas++;

if (nbytes<0) {
perror("Error leyendo en fichero");
return -1;
}
printf("%s %d\n", arg, nlineas);
return nlineas;
}

int main(int argc, char *argv[]) {


int i, estado=0, retorno, valor, totlin=0;

if (argc<2) {
fprintf(stderr, "Uso: %s fichero ...\n", argv[0]);
exit(1);
}

for (i=0; i<argc-1; i++)


if (fork()==0) {
valor=contar(argv[i+1]);
exit(valor);
}
for (i=0; i<argc-1; i++) {
wait(&retorno);
if (!WIFEXITED(retorno))
/* el hijo ha terminado debido a una seal */
estado=1;
else {
/* el hijo ha terminado normalmente */

/* extraigo el valor devuelto */


retorno=WEXITSTATUS(retorno);

/* el -1 devuelto se ha convertido en un 255 */


if (retorno==255)
estado=1;
else
totlin+= retorno;
26 Problemas de sistemas operativos
}
}
printf("total %d\n",totlin);
exit(estado);
}
Fichero lincon_proc_v3.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

#define TAMBUF 8192

int tuberia[2];

int contar(char *arg) {


int fo, nbytes, i, nlineas=0;
char buf[TAMBUF];

if ((fo=open(arg, O_RDONLY))<0) {
perror("Error abriendo fichero");
return -1;
}
while ( (nbytes= read(fo, buf, TAMBUF)) >0)
for (i=0; i<nbytes; i++)
if (buf[i]==\n) nlineas++;

if (nbytes<0) {
perror("Error leyendo en fichero");
return -1;
}
write(tuberia[1], &nlineas, sizeof(nlineas));
printf("%s %d\n", arg, nlineas);
return 0;
}

int main(int argc, char *argv[]) {


int i, estado=0, retorno, valor, totlin=0, nlineas;

if (argc<2) {
fprintf(stderr, "Uso: %s fichero ...\n", argv[0]);
exit(1);
}

pipe(tuberia);
for (i=0; i<argc-1; i++)
if (fork()==0) {
close(tuberia[0]);
valor=contar(argv[i+1]);
exit(valor);
}
close(tuberia[1]);
for (i=0; i<argc-1; i++) {
wait(&retorno);
if (retorno!=0)
estado=1;
else {
read(tuberia[0], &nlineas, sizeof(nlineas));
totlin+= nlineas;
}
}
27
printf("total %d\n",totlin);
exit(estado);
}

Problema 1.9
Se debe escribir en C el programa paralelo pwc que cuente el nmero de lneas y los caracteres que no sean espa -
cio de un determinado fichero.
La sintaxis del programa ser: pwc n fich, donde n es el nmero de procesos hijo que debe crear el programa
pwc y fich es el nombre del fichero de entrada.
El programa deber:
a) Proyectar en memoria el fichero fich recibido como parmetro de entrada. Se recuerda que la llamada al sis-
tema mmap tiene el siguiente prototipo:
void * mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
b) Calcular el tamao que debe procesar cada uno de los procesos hijo y la posicin desde la cual debe comen-
zar cada uno de ellos (vese la figura 1.9). El proceso almacenar estos valores en dos vectores int offset[n],
int size[n].
c) Crear los procesos hijo encargados de realizar la cuenta en paralelo, pasndoles la posicin k que referen -
cia a los valores offset[k] y size[k] con los valores que debe utilizar.
d) Esperar la finalizacin de los procesos hijo y recoger, utilizando pipes, los valores calculados por cada uno
de ellos.

pwc

1 2 n

...
Figura 1.9

Se pide:
a) Desarrollar el programa descrito.
b) Explicar de forma breve y razonada si los procesos hijos podran comunicar los valores calculados al padre
a travs de memoria compartida.
c) Se podran haber creado los vectores int numLineas[n] e int palabras[n] y que los procesos hijos escribie-
ran en ellos sus resultados?

Solucin
a) El programa propuesto es el siguiente:
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#define MAX_PROC 20

struct datos{
int numLineas;
int numCaracteres;
28 Problemas de sistemas operativos
};

int main (int argc, char **argv){

int fde; // Descriptor del fichero de entrada


int fd[2]; // Descriptores del pipe
struct datos resul; // Resultado obtenido por cada uno de los hijos
int tamFichero; // Tamao del fichero a proyectar
void *p, *q; // Punteros al fichero proyectado en memoria
int size_proc; // Tamao del fichero entre nmero de procesos
int resto; // Tamao restante del fichero
int offset[MAX_PROC]; // Direccin de comienzo para cada proceso hijo
int size[MAX_PROC]; // Tamao que debe procesar cada proceso hijo
int n; // Nmero de procesos hijo
int i,j; // Para bucles
int pid; // Identificador de los procesos hijo
char *c; // Carcter actual que se trata
int nLineas, nCaracteres; // Lneas y caracteres no espacio contados por
// cada uno de los hijos
int lineasTot, caracteresTot; // Lneas y caracteres no espacio totales
//(del padre)

if (argc!=3){
printf("Lo siento, este programa necesita dos argumentos %d");
return 0;
}

// Nmero de procesos hijo


n = atoi(argv[1]);

// Antes de proyectar el fichero hay que abrirlo


fde = open(argv[2], O_RDONLY);
if (fde<0){
perror("Error abriendo el fichero!");
return 0;
}

// Calculo el tamao del fichero


tamFichero = lseek (fde, 0, SEEK_END);

// Proyecto el fichero como MAP_SHARED, para que pueda ser compartido


// por los procesos hijo
p = mmap(0,(size_t) tamFichero, PROT_READ, MAP_SHARED, fde, 0);
if (p<0){
perror("Error proyectando el fichero");
return 0;
}

// Se calcula el tamao que tiene que procesar cada proceso


size_proc = tamFichero / n;

// El ltimo proceso procesar tambin el resto


resto = tamFichero % n;

// Vectores size y offset, con los tamaos y direcciones


for (i=0; i<n; i++){
size[i] = size_proc;
offset[i] = i * size_proc;
}

// El ltimo proceso se encarga del resto del fichero


size[n-1] += resto;
29

// Se crea el pipe
if (pipe(fd) < 0) {
perror("Error al crear el pipe");
return 0;
}

// Creo los procesos hijo


// Cada uno se encarga de su parte del fichero proyectado
for (i=0; i<n; i++){
pid = fork();

// Cdigo del proceso hijo


if (pid == 0){

// Puntero en el punto de comienzo


q = p + offset[i];
nLineas=0;
nCaracteres=0;

// Leo carcter por carcter y compruebo si es salto


// de lnea o si no es espacio
for (j=0 ; j<size[i];j++) {
c=q;

if (*c==\n)
nLineas++;

if (*c != )
nCaracteres++;

q++;
}

// Escribo la estructura
resul.numLineas = nLineas;
resul.numCaracteres = nCaracteres;

// Escribo la estructura en el pipe


write (fd[1], &resul, sizeof(resul));
return 0;
}
}

// El padre espera los valores mandados por todos los procesos hijo
lineasTot=0;
caracteresTot=0;

// Por cada uno de los hijos tengo que leer del pipe
for (i=0;i<n;i++) {
read(fd[0],&resul,sizeof(resul));
lineasTot += lineasTot + resul.numLineas;
caracteresTot += caracteresTot + resul.numCaracteres;
}

printf("Caracteres totales no espacio %d\n Lineas Totales %d\n",


caracteresTot, lineasTot);
return(0);
}
b) S que sera posible usar el mecanismo de memoria compartida para pasar los resultados al padre. Por ejemplo, se
podra utilizar un fichero proyectado en memoria, en el cual los hijos escribieran su resultado y el padre lo leyera.
30 Problemas de sistemas operativos
c) No sera posible utilizar los dos vectores propuestos, ya que los hijos, al escribir en ellos, se crearan una copia
privada, siendo imposible para al proceso padre acceder a dichos valores.

Problema 1.10 (2004)

Dado el siguiente cdigo de un ejecutable cuyo nombre es run_processes:


1 int main(int argc, char *argv[]){
2 int pid, number, status;
3
4 number = atoi(argv[1]);
5 if (number<=0)
6 return 0;
7 pid = fork();
8 if (pid == 0) {
9 fork();
10 number=number-1;
11 sprintf(argv[1], "%d", number);
12 execvp(argv[0],argv);
13 perror("exec");
14 exit(1);
15 } else {
16 while (wait(&status)!= pid)
17 continue;
18 }
19 return 0;
20 }
Aclaracin:La funcin atoi() devuelve el valor entero representado por la cadena de caracteres dada. Por
otro lado, dado un valor entero y un buffer, sprintf()almacena en el buffer la tira de caracteres que representa
el entero. Ejemplo:
atoi("35")devuelve 35.
sprintf(cadena, "%d", 35) almacena en la variable cadena el texto 35.
Se supone que los servicios fork() y execvp() se ejecutan de forma correcta.
a) Indicar qu sucede si se ejecuta run_processes sin ningn argumento.
Para el resto del ejercicio considere la ejecucin del mandato run_processes 3.
b) Indicar qu har el primer proceso hijo creado durante la ejecucin de run_processes 3.
c) Dibujar el rbol de procesos resultantes de la ejecucin de run_processes 3.
d) Cul sera el nmero total de procesos creados durante la ejecucin de run_processes 3, sin contar el
proceso padre original?
e) Razonar si al realizar la ejecucin de run_processes 3, podr existir algn proceso hurfano y/o algn
proceso zombie. Se considera proceso hurfano aquel cuyo padre ha muerto, y es adoptado por el proceso
init.
f) Si se incluye delante de la lnea 16 el siguiente cdigo:
act.sa_handler = tratar_alarma;
act.sa_flags = 0;
sigaction(SIGALRM, &act, NULL);
alarm(10);
while (pid != wait(&status)) continue;
return 0;
Se declara dentro del main(), la variable: struct sigaction act; y se declara como variable global
int pid;, para que pueda ser vista por la funcin tratar_alarma, cuyo cdigo se muestra a continuacin:
void tratar_alarma(void) {
kill(pid, SIGKILL);
}
31
En el caso de que se lleve a cabo la ejecucin run_processes 3, cul es el nmero mximo de procesos
que podran morir por la ejecucin del servicio kill invocado en la funcin tratar_alarma?

SOLUCIN
a) Se producira un acceso a memoria incorrecto al acceder a argv[1].
b) El primer proceso hijo crear otro proceso y ambos ejecutarn recursivamente run_processes 2.
c) El rbol de procesos se encuentra en la figura . Dicha figura tambin muestra los procesos que realizan un wait
por el proceso hijo as como el valor que toma la variable number en cada uno de ellos.
Proceso
padre
N=3
wait

N=2
wait

N=2 N=1
wait
wait

N=1 N=1 N=0


wait
wait

N=1 N=0 N=0 N=0

wait

N=0 N=0 N=0

N=0
N... number
Figura 1.10
c)

d) 14
e) En algn orden de ejecucin se producira la situacin de procesos hijos hurfanos y procesos zombies. En el pri-
mer caso, los procesos padres que no hacen wait por sus procesos hijos (tal y como est representado en la figura ),
podran morir antes que los procesos hijos correspondientes. En el segundo caso, si los procesos hijos por los cuales
el proceso padre no hace un wait mueren, se convertiran en procesos zombies.
f) 7 (el mismo nmero de procesos que hacen wait por el proceso hijo y que tienen su pid).

Problema 1.11 (abril 2005)

Se tiene desarrollada una aplicacin que realiza tres procedimientos que son independientes entre s. Las cabeceras
de estos procedimientos son las siguientes:
void Procedimiento1(void);
void Procedimiento2(void);
void Procedimiento3(void);
Suponga que los procedimientos tienen las siguientes fases de ejecucin y que se usa E/S por DMA:
a) Procedimiento 1: 25 ms de CPU, lectura de dispositivo1 25 ms, 25 ms de CPU
b) Procedimiento 2: 30 ms de CPU, lectura de dispositivo1 35 ms, 20 ms de CPU
c) Procedimiento 3: 50 ms de CPU
Esta aplicacin se ha desarrollado utilizando dos modelos diferentes, cuyo cdigo se detalla a continuacin:
/* Modelo 1 Un nico proceso */
1 int main (void)
2 {
3 Procedimiento1();
4 Procedimiento2();
5 Procedimiento3();
6 return 0;
32 Problemas de sistemas operativos
7 }

/* Modelo 2 Tres procesos concurrentes */


1 int main (void)
2 {
3 if (fork()!=0)
4 {
5 if (fork()!=0)
6 Procedimiento1();
7 else
8 Procedimiento3();
9 }
10 else
11 {
12 Procedimiento2();
13 }
14 wait(NULL);
15 return 0;
16 }
Se consideran los siguientes supuestos:
a) El sistema operativo emplea un algoritmo de planificacin basado en prioridades (en caso de que existan
varios procesos listos para ejecutar, se seleccionar el ms prioritario). En el caso del Modelo 2 el proceso
padre tiene prioridad 1, el primer proceso hijo prioridad 2 y el segundo proceso hijo prioridad 3 (nmeros
ms bajos indican mayores prioridades).
b) Se tomar como t=0 el instante en el que comienza la ejecucin de la lnea 3 en el Modelo 1 y la ejecucin
de la lnea 3 en el Modelo 2.
c) El SO no ejecuta ningn otro proceso y no se tendr en cuenta ningn tipo de interrupcin de reloj.
d) El tiempo de ejecucin de los servicios del sistema operativo es el siguiente (estos tiempos incluyen la plani-
ficacin y activacin del siguiente proceso. En realidad el SO consume mucho menos tiemo que los indica -
dos, se utilizan estas cifras para que la figura sea ms clara):
Tratamiento de la llamada al sistema fork: 20 ms
Tratamiento de la llamada al sistema para la lectura de dispositivo: 10 ms
Tratamiento de cualquier otra llamada al sistema: 10 ms
Tratamiento de interrupcin de fin de operacin de E/S: 15 ms
e) Considere cualquier otro tiempo despreciable.
Se pide:
a) Qu tiempo total tarda en ejecutar el Modelo 1?
b) Qu jerarqua de procesos surge de la ejecucin del Modelo 2?
c) Qu sucede si estando en ejecucin un proceso menos prioritario termina una operacin de E/S de un pro -
ceso ms prioritario?
d) En el Modelo 2, quin est ejecutando en el instante 110 ms?
e) Qu tiempo total tarda en ejecutar el Modelo 2?
f) En el Modelo 2, qu pasa con el wait() de la lnea 14 en el segundo proceso hijo creado?

Solucin
a) El diagrama de tiempos se puede encontrar en la figura 1.11.
b) La jerarqua resultante es un proceso padre que ejecuta el Procedimiento1 con dos procesos hijo que ejecutan Pro-
cedimiento2 y Procedimiento3.
c) Al finalizar una operacin de E/S se genera una interrupcin, por lo que entra a ejecutar el sistema operativo.
Cuando se termine el tratamiento de la interrupcin se planificar al siguiente proceso a ejecutar, seleccionndose el
ms prioritario.
d) En el instante 110 est ejecutando el Sistema Operativo, tal y como se puede observar en la figura .
e) El diagrama de tiempos se puede encontrar en la figura 1.12.
33

E lectura lectura exit


LE
Proceso B

E
Sist. Oper. B
E
Proc. Nulo LE
A interrupcin interrupcin
Dispositivo B
50 100 150 200 250
Figura 1.11

E fork fork lectura wait exit


LE
Padre (Proc1) B
E lectura wait
LE exit
Hijo 1 (Proc2) B

E wait
LE exit
Hijo 2 (Proc3) B

1
Sist. Oper. 0
E
Proc. Nulo LE
A interrupcin interrupcin
Dispositivo B
50 100 150 200 250 300
Figura 1.12

f) El segundo proceso hijo no es padre de ningn otro proceso, por lo que la llamada wait devuelve un error directa-
mente.

Problema 1.12 (junio 2005)

Se pide implementar en C y para UNIX un programa con las siguientes caractersticas:


a) Se debe invocar de la siguiente manera:
verificar_actividad minutos mandato [args...]
b) Su objetivo es verificar, con cierta periodicidad, que un operario de una central nuclear est alerta.
c) El programa debe alternar fases de espera de los minutos especificados como primer argumento en la invo -
cacin, con fases de verificacin de presencia del operario.
d) Para la fase de verificacin deber crearse un proceso hijo cuyo estado de terminacin indique si todo ha
ido de forma correcta (terminacin 0) o si ha habido algn error (terminacin no 0).
e) En la fase de verificacin se debe presentar un mensaje como Introduzca 4:, siendo el dgito mos-
trado el menos significativo del valor del pid del proceso hijo. El operario dispondr de 30 segundos para
introducir correctamente el dgito presentado. Durante este tiempo, cada 2 segundos se llamar la atencin
del operario con una seal acstica producida por la funcin void beep (void) de la biblioteca
curses.
f) Si la segunda fase va mal el proceso principal deber terminar ejecutando el mandato (con sus respectivos
argumentos) indicado en la invocacin del programa.
g) Tanto en el proceso padre como en los procesos hijos creados se debern enmascarar las seales generables
desde el teclado INT y QUIT.
RECUERDE QUE: Cuando llega una seal a un proceso que est bloqueado en una llamada al sistema, la lla-
mada se aborta, devolviendo un -1, y la variable errno toma el valor EINTR.
34 Problemas de sistemas operativos

Solucin
#include <curses.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int beep(void);

void tratar_alarma()
{
beep();
}

int main(int argc, char **argv) {


int status;
int tiempo;
int i;
sigset_t mascara;
struct sigaction act;
char car;
char ultimoDigitoPid;
pid_t pid;
int resul;

sigemptyset(&mascara);
sigaddset(&mascara, SIGINT);
sigaddset(&mascara, SIGQUIT);
sigprocmask(SIG_SETMASK, &mascara, NULL);

tiempo = atoi(argv[1])*60;

while(1)
{
sleep(tiempo);
pid = fork();
switch(pid)
{
case -1: /* error del fork() */
perror ("fork");
exit(1);
case 0: /* proceso hijo */
/* La mscara de seales se mantiene, no hay que cambiarla */
/* Establecer el manejador */
act.sa_handler = tratar_alarma; /* funcin a ejecutar */
act.sa_flags = 0; /* ninguna accin especfica */
sigaction(SIGALRM, &act, NULL);

ultimoDigitoPid = '0' + getpid() % 10;


printf("\nIntroduce %c:", ultimoDigitoPid);

for (i=0;i<15;i++)
{
alarm(2);

do
{
/* Si vence la alarma en resul obtenemos un -1 */
resul = read(0,&car,1);
35
} while (resul >=0 && car != ultimoDigitoPid);

if (car==ultimoDigitoPid)
{
return 0;
}
}
exit(1); /* Salimos dando error */
default: /* padre */
wait(&status);
if (status != 0)
{
execvp(argv[2], &argv[2]);
perror("exec");
exit(1);
}
}
}
return 0;
}

Problema 1.13 (abril 2006)


Dados los siguientes cdigos:
Fichero arbol.c
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[])


{
int total, x, i;

total = atoi(argv[1]);
i = 0;
x = 5;

while (i < total)


{
fork();
i++;
x = x + 2;
}

printf("%d \n", x);


return 0;
}
Fichero arbolv2.c
void func(void)
{
...
pthread_exit(0);
}
int main(int argc, char *argv[])
{
pid_t pid;
int total, x, i, fd;
36 Problemas de sistemas operativos
pthread_attr_t attr;
pthread_t thid1,thid2;

pthread_attr_init(&attr);
total = atoi(argv[1]);
pid = getpid();
i = 0;
x = 5;
fd = open("/home/paco/datos.txt", O_WRONLY)
while (i < total)
{
fork();
i++;
x = x + 2;
}
if (getpid()!=pid)
{
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
pthread_create(&thid1, &attr, func, NULL);
pthread_create(&thid2, &attr, func, NULL);
}
printf("%d\n", x);
return 0;
}
Se generan los ejecutables arbol y arbolv2 de los ficheros anteriores.
a) Suponiendo que como procesos arbol se entienden todos los procesos creados durante la ejecucin del pro-
grama, incluyendo al proceso inicial, cuntos procesos arbol se crearn durante la ejecucin de arbol 4?
b) Qu valor de x imprimir el segundo de los procesos creados directamente por el proceso inicial durante la
ejecucin de arbol 4?
c) Cuntos procesos zombies podramos llegar a tener, como mximo, durante la ejecucin de arbol n, inde -
pendientemente del valor de n?
d) Cuntas llamadas al sistema close seran necesarias para que el fichero no tuviera ningn descriptor
abierto al ejecutar arbolv2 n, independientemente del valor de n?
e) Se desea que el proceso arbol inicial, a los 10 segundos, escriba el nmero de procesos que ya han finaliza-
do y termine su ejecucin. Escriba el cdigo que se debe incluir en el programa para conseguir dicho objeti-
vo. Subraye las llamadas al sistema que utilice. Nota: para solucionar este apartado no se pueden utilizar
pipes.

SOLUCION
a) La jerarqua de procesos que se obtiene es la que se puede ver en la figura 1.13. El nmero que se puede observar
dentro de cada proceso corresponde al valor inicial de la variable i para dicho proceso.

Figura 1.13

Es importante destacar que en el momento en que se realiza el fork el hijo se crea como una copia exacta del pa-
dre, pero a partir de dicho momento las variables del padre y del hijo son completamente independientes. Por tanto,
37
a partir del momento de la creacin si el padre toca su variable i, este cambio no afecta a la variable i del hijo y vice-
versa.
b) El valor de x que se imprime por pantalla es 13.
Al comenzar x = 5. A continuacin, el proceso inicial crea su primer proceso hijo y, a continuacin, incrementa
en 2 el valor de x, por lo que pasa a valer 7. En la segunda iteracin, cuando x=7, crea a su segundo proceso hijo. Es
decir, el segundo proceso hijo del proceso inicial comienza con x = 7.
Por ltimo, el segundo proceso hijo realiza tres iteraciones en el bucle, por lo que el resultado final es de 13.
De hecho, si se realiza este mismo procedimiento para cualquier de los procesos, se podr ver como el valor final
que se imprime por pantalla es 13 en todos los casos. La nica diferencia existente es en dnde se realizan los incre-
mentos de x.
c) Podramos llegar a tener a tener tantos procesos zombies como procesos hay en la jerarqua, ya que en ningn
caso se est realizando un wait por ninguno de los hijos. De hecho, el proceso inicial tambin podra llegar a quedar-
se zombie porque, tal y como est definido el problema no es posible saber si su padre est o no haciendo un wait
por l.
Por tanto, podramos llegar a tener tantos procesos zombies como procesos arbol hay en la jerarqua de procesos
que se genera.
d) Efectivamente slo se est realizando un open del fichero, el que realiza el proceso inicial. Sin embargo, cada vez
que se crea un nuevo hijo, se realiza una copia de la Tabla de Descriptores. Sin embargo, la creacin de threads den-
tro de cada proceso no altera las Tablas de Descriptores.
Por tanto, para que el fichero no tenga ningn descriptor abierto habr que hacer tantos close como procesos ar -
bolv2.
e) Para que el proceso inicial escriba por pantalla a los 10 segundos el nmero de procesos que ya han terminado es
necesario establecer una alarma (alarm) y capturarla (sigaction). Transcurrido el tiempo de la alarma se sacar el
mensaje por pantalla.
La parte de contabilizar el nmero de procesos que ya han finalizado es ms compleja. Lo que no se puede hacer,
y es un error muy grave, es usar una variable global para llevar esta cuenta. Los procesos de nuestra jerarqua no
comparten sus variables, una vez creados los nuevos procesos sus variables slo le pertenecen a l, y los cambios
que hagan sobre los valores de sus variables no afectan al resto de los procesos.
Otro error grave bastante comn es que el proceso inicial realice un wait por el resto de los procesos. Si bien es
posible realizar esta operacin, slo estara esperando por sus cuatro hijos directos, pero no por el resto de procesos
de la jerarqua. En este caso el contador de procesos del padre slo podra llegar a tener un valor de 4.
Un posible procedimiento a utilizar es que los procesos, cuando finalicen, manden una seal al proceso inicial
para indicarle esta situacin. El proceso inicial puede tener una variable en la que ir contando el nmero de seales
de este tipo que ha recibido. Cuando llegue la alarma, simplemente habr que imprimir ese valor por pantalla y fina -
lizar la ejecucin.
Un posible cdigo es el siguiente:
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#define SENAL 40
int contador;

void tratar_alarma(int signum)


{
printf("Contador = %d\n", contador);
exit(0);
}

void incrementar_contador(int signum)


{
contador++;
}

int main(int argc, char *argv[])


38 Problemas de sistemas operativos
{
pid_t pid;
int total, x, i;
struct sigaction act;

total = atoi(argv[1]);
i = 0;
x = 5;
contador = 0;

/* establecer el manejador para SIGALRM */


act.sa_handler = tratar_alarma;
act.sa_flags = 0 ;
sigaction(SIGALRM, &act, NULL);

/* establecer el manejador para SIGUSR1 */


act.sa_handler = incrementar_contador;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SENAL, &act, NULL);

pid = getpid();

/* Se crean los procesos hijos */


while (i < total)
{
fork();
i++;
x = x + 2;
}

/* Si soy el proceso inicial establezco la alarma y espero */


if (getpid()==pid)
{
alarm(10);
while (1){
pause();
}
}
else /* Cualquier otro proceso: manda seal a proc ini */
{
kill(pid, SENAL);
}

return 0;
}
Nota avanzada: Tal y como est solucionado el ejercicio su ejecucin funciona perfectamente porque los procesos
envan una seal de tiempo real al proceso inicial y estas seales siempre se encolan. Estas seales se pueden reco -
nocer porque su nmero es mayor o igual a 34. Sin embargo, si hubiramos usado una seal como SIGUSR1, se po-
dran estar perdiendo seales. Segn esta definido el estndar POSIX, si mientras se esta tratando una seal llega
otra del mismo tipo, esta se encola, pero si en ese momento llegara otra mas, esa seal ya no se encola, con la co -
rrespondiente prdida de seales.
A continuacin se muestra otra posible solucin al problema basada en ficheros. En este caso se crea un fichero
auxiliar en modo O_APPEND en donde los procesos escriben un carcter cuando finalizan. Para saber el nmero de
procesos que han finalizado nos basta con comprobar el tamao final de fichero.
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
39
#include <sys/stat.h>
#include <fcntl.h>

int contador;

void tratar_alarma(int signum)


{
struct stat buf;

stat("aux.txt", &buf);
printf("Contador = %d\n", buf.st_size);
exit(0);
}

void incrementar_contador(int signum)


{
contador++;
}

int main(int argc, char *argv[])


{
pid_t pid;
int total, x, i;
struct sigaction act;
int fd;

total = atoi(argv[1]);
i = 0;
x = 5;
contador = 0;

fd = open("aux.txt", O_CREAT | O_APPEND | O_WRONLY | O_TRUNC, 0644);

/* establecer el manejador para SIGALRM */


act.sa_handler = tratar_alarma;
act.sa_flags = 0 ;
sigaction(SIGALRM, &act, NULL);

pid = getpid();

/* Se crean los procesos hijos */


while (i < total)
{
fork();
i++;
x = x + 2;
}

/* Si soy el proceso inicial establezco la alarma y espero */


if (getpid()==pid)
{
alarm(10);
pause();
}
}
else /* Si soy cualquier otro proceso mando una seal al proceso inicial */
{
write(fd,"1",1);
close(fd);
}

return 0;
}
40 Problemas de sistemas operativos

Problema 1.14 (abril 2007)

El siguiente programa, denominado watchdog_timer, nos permite comprobar cada cierto tiempo que un opera-
rio est alerta en su terminal de trabajo. Este programa recibe dos argumentos: (i) espera1, que nos indica cada
cunto tiempo hay que verificar la presencia del operario y (ii) espera2, que nos indica el tiempo mximo que
tiene el operario para responder correctamente. El cdigo del programa, a falta de completar algunas partes, es el
siguiente.
#define true 1
pid_t pid;

void tratar_alarma()
{
// Cdigo de tratamiento de la alarma
}

int main (int argc, char **argv)


{
struct sigaction act;
int num, ch, espera1, espera2, numErrores;
int valor;

espera1 = atoi(argv[1]);
espera2 = atoi(argv[2]);

while (true)
{
sleep(espera1);

pid = fork();
switch (pid)
{
case -1:
perror("Error en la llamada fork");
exit(1);
case 0:
numErrores = 0;
while (true)
{
num = generar_numero();
printf("Introduzca el nmero %d", num);
scanf("%d\n", &ch);
if (ch==num)
return 0;
numErrores++;
if (numErrores==5)
exit(1);
}
default:
act.sa_handler = tratar_alarma;
act.sa_flags = 0;
sigaction(SIGALRM, &act, NULL);
alarm(espera2);
while (pid != wait(&valor));
// Cdigo para la comprobacin de la finalizacin del proceso hijo
}
}
return 0;
}
41
a) Estn manejando correctamente la alarma el proceso padre y los procesos hijo que se van creando?
b) Indica el contenido correcto de la pila nada mas comenzar la ejecucin de watchdog_timer 240 30
c) Para verificar la presencia del operario se crea un proceso hijo, y el proceso padre se queda bloqueado es -
perando por l. Sin embargo, es necesario verificar que el proceso hijo ha finalizado correctamente y no por
la recepcin de una seal. Codifique el fragmento de cdigo que deberamos aadir a nuestro programa des -
pus de la sentencia while(pid!=wait(&valor)).
d) En caso de que el operario no responda correctamente en el tiempo mximo establecido, se activa la funcin
tratar_alarma, que debe matar al proceso hijo y, a continuacin, ejecutar un programa denominado alarmon
que no recibe ningn parmetro de entrada. Codifique la funcin tratar_alarma.
e) Queremos que nuestro programa no se vea afectado por las seales generadas desde teclado con Ctrl+C y
con Ctrl+\. Codifique, utilizando una mscara de seales, una posible solucin, teniendo en cuenta que
tanto el proceso padre como los procesos hijo que se vayan creando deben estar protegidos ante dichas se-
ales.
f) En esta ocasin, deseamos que tanto el proceso inicial como los procesos hijo estn protegidos ante las se -
ales generadas con Ctrl+C y con Ctrl+\, pero no queremos proteger la ejecucin del programa alar-
mon. Para resolver este problema, sera posible codificar una solucin basada en mscaras?

SOLUCIN
a) Los procesos hijo no tienen nada que ver con la alarma, ya que quien la activa es el proceso padre. Por tanto, en el
caso de los procesos hijo todo es correcto. Sin embargo, el proceso padre no maneja correctamente la alarma cuando
el proceso hijo finaliza antes del vencimiento de la misma. De esta manera, y justo despus de la instruccin while
(pid != wait(&valor)), el proceso padre debera ejecutar la sentencia alarm(0) para desconectar la alar-
ma. En caso de que la alarma no fuera desconectada se ejecutara de forma incorrecta la funcin de tratamiento de
alarma tratar_alarma().

Direccin
Pila
byte3 byte2 byte1 byte0

00 00 00 04
02 E5 00 10
02 E5 00 38
02 E5 00 20
02 E5 00 2F
Crecimiento de la pila

02 E5 00 33
00 00 00 00
ctaw
godh
mit_
2 00 re
3 00 04
00 0
02 E5 00 40
00 00 00 00
MRET
1tv=
00 02
Figura 1.14

b) En la figura 1.14 se puede observar el contenido completo de la pila. Se debe tener en cuenta que el nmero de
parmetros de invocacin al programa es 4, siendo argv[0] el nombre del programa y argv[3]=NULL. La parte
superior de la pila debe contener, adems del nmero de parmetros del programa, un puntero a los argumentos y un
puntero al entorno. Por ltimo, los valores de los parmetros y de las variables de entorno deben estar separados en-
tre s con valores 00, de forma que se pueda saber dnde finaliza cada uno de ellos.
c) El cdigo a insertar sera el siguiente:
if (WIFEXITED(valor))
if (WEXITEDSTATUS(valor)==0)
continue;
42 Problemas de sistemas operativos
else
exit(1);
else if (WIFSIGNALED(valor))
exit(2);

En primer lugar debemos comprobar que el proceso hijo ha finalizado correctamente, para lo cual verificamos
que la macro WIFEXITED ha devuelto un valor positivo. Si esta verificacin es positiva, debemos comprobar que el
valor de finalizacin del proceso hijo es 0. Este valor se comprueba a travs de la macro WEXITEDSTATUS. En
cualquier caso, si el proceso hijo ha finalizado por la recepcin de una seal (macro WIFSIGNALED), terminamos
la ejecucin devolviendo un cdigo de error.
d) El cdigo de la funcin tratar_alarma sera el siguiente:
void tratar_alarma() {
kill(pid, SIGKILL);
execlp("alarmon", "alarmon", NULL);
exit(1);
}
La funcin comienza con el envo de la seal SIGKILL, a travs de la llamada al sistema kill, al proceso hijo.
El envo de esta seal garantiza que el proceso hijo morir, ya que esta seal no se puede capturar. A continuacin, a
travs del servicio execlp se pasa a ejecutar el programa alarmon que, tal y como se dice en el enunciado, no re-
cibe ningn parmetro de entrada. En caso de error en la llamada exec, se finaliza la ejecucin a travs de la llama-
da exit.
e) Debemos establecer una mscara para la seales SIGINT (Ctrl+C) y SIGQUIT (Ctrl+\). Con establecer la
mscara en el proceso padre es suficiente, ya que los procesos hijo que se vayan creando heredarn esta mscara. Se
deber insertar al comienzo del main el siguiente fragmento de cdigo:
sigset_t mascara;
sigaddset(&mascara, SIGINT);
sigaddset(&mascara, SIGQUIT);
sigprocmask(SIG_SETMASK, &mascara, NULL);
f) No, ninguna solucin basada en mscaras es factible. Si se usa una mscara en el proceso padre, tanto los proce-
sos hijo como el programa alarmon heredaran dicha mscara. Si queremos que las seales SIGINT y SIGQUIT
puedan llegar al programa alarmon, deberamos quitar la mscara justo antes de hacer el exec, lo que significara
que la seales pendientes (en caso de existir) llegaran al proceso padre. Por tanto, el proceso padre no estara co -
rrectamente protegido.

Problema 1.15 (junio 2009)

Dado el siguiente programa:


#define NPH 10
int main(int argc, char *argv[])
{
int i, sig;
int fd[NPH][2];

for (i=0; i<NPH; i++)


pipe(fd[i]);
for (i=0; i<NPH; i++) {
if (fork()==0) {
sig = (i+1)%NPH;
<<CDIGO_A_RELLENAR>>
/* Ejecucin de cada mandato. *
* Por simplicidad no se muestra *
* el paso de argumentos a execvp. */
execvp(...);
perror("exec");
exit(1);
43
}
}
write(fd[0][1], "ABCDEFGHIJ", 10);
for (i=0; i<NPH; i++) {
close(fd[i][0]);
close(fd[i][1]);
}
while (wait(NULL)>0) ;
return 0;
}
Se pretende que los procesos hijo ejecuten mandatos estndar conectados en una secuencia cclica, esto es, el
primer hijo mandar su salida a la entrada del segundo, el segundo mandar al tercero, y as sucesivamente, hasta
el ltimo hijo, que tendr su salida estndar conectada a la entrada estndar del primer hijo.
Se pide:
a) Dibuje el diagrama de procesos en el momento en que todos los hijos estn en ejecucin. Muestre la jerar-
qua de procesos y los mecanismos de comunicacin entre ellos y las asociaciones de los primeros con los
segundos mediante descriptores.
b) Implemente el <<CDIGO_A_RELLENAR>>, para que se puedan ejecutar mandatos estndar en secuencia
cclica.
c) Implemente el mandato estndar cat en la versin sin argumentos.
d) Si el primer hijo ejecuta el mandato ls y el resto el mandato cat, responda:
1. Cundo finalizan todos los procesos?
2. Qu estarn haciendo todos los procesos cuando se haya puesto todo en marcha?
3. Cmo se llama la situacin que alcanzan estos procesos?
e) Responda a las preguntas del apartado anterior para el caso en que el proceso padre no realice la llamada
write y todos los hijos ejecuten el mandato cat (sin argumentos).
f) Responda las mismas preguntas para el caso en que el proceso padre s realice la llamada write y todos
los hijos ejecuten el mandato cat (sin argumentos).
g) En el escenario anterior, explique qu ocurre en el caso de que al hijo 1 le llegue una seal que lo mata. In -
dicar:
1. En qu orden terminaran todos los procesos?
2. En qu estado finalizaran cada uno de ellos?
44 Problemas de sistemas operativos

Solucin.-

Padre

TN_PROC
T1

T2

T3
H1 H2 H3 HN_PROC

Figura 1.15

a) En la figura 1.15 se muestra el diagrama de procesos as como las tuberas que permiten comunicar dichos proce -
sos.
b) En el cdigo a rellenar hay que redirigir la entrada y salida de cada proceso para que se comunique con el proceso
anterior y posterior. Adems, se tienen que cerrar todos los descriptores abiertos que no se vayan a utilizar. El cdigo
quedara del siguiente modo:
close(0);
dup(fd[i][0]);
close(1);
dup(fd[sig][1]);
for (i=0; i<NPH; i++){
close(fd[i][0]);
close(fd[i][1]);
}
c) El mandato cat escribe por la salida estndar lo que lee por la entrada estndar:
#define BUFSIZE 1024
int main(int argc, char *argv[])
{
char buffer[BUFSIZE];
int leidos;

while ((leidos = read(0, buffer, BUFSIZE)) > 0)


write(1,buffer,leidos);

if (leidos < 0)
return 1;
else
return 0;
}
d) El primer hijo ejecuta el mandato ls, teniendo redirigida la salida al extremo de escritura de la primera tubera.
El segundo proceso ejecuta el mandato cat, escribiendo por la salida estndar (asociada al extremo de escritura de
la segunda tubera) lo que previamente ley por su entrada estndar (asociada al extremo de lectura de la primera tu-
bera). Esto se repite para cada uno de los procesos de manera circular. Por tanto:
1. El primer proceso termina cuando ejecuta el mandato ls. El segundo proceso lee de su entrada estn-
dar la salida de la ejecucin de este mandato, que lo escribe por su salida estndar y muere. As sucesiva-
mente, hasta que el ltimo de los procesos lee de su entrada estndar y al escribir en su tubera, como no
45
tendr lectores (salvo que el proceso padre no hubiera cerrado el descriptor correspondiente), terminar con
error.
2. Una vez que los procesos arrancan, el primero ejecutar el mandato ls. Los dems ejecutarn el man-
dato cat, leyendo de la entrada estndar asociada a la tubera correspondiente. El proceso padre cerrar los
descriptores y esperar a que finalicen todos los procesos hijos.
3. Los procesos finalizan de forma correcta, excepto el ltimo que finaliza por un error (le llega la seal SI-
GPIPE) al intentar escribir sobre una tubera sin lectores (excepto en el caso de que escriba en la tubera an-
tes de que el proceso padre haya cerrado el descriptor correspondiente). Hay tambin una situacin en la
que se produce un interbloqueo. Si el mandato ls produce muchos datos, stos se escribirn de manera
sucesiva en todas las tuberas, hasta llegar a la ltima, que como no se vaca provoca de manera encadenada
que todas las tuberas se llenen.
e) En este segundo escenario, todos los procesos hijos estaran bloqueados esperando leer algo por la entrada estn-
dar.
1. Los procesos estaran bloqueados eternamente, excepto si les llega una seal que les mata.
2. Una vez que los procesos arrancan, todos los procesos hijos estaran bloqueados en la llamada read del
mandato cat. El proceso padre estara bloqueado esperando a la finalizacin de sus hijos.
3. Los procesos estaran en una situacin de deadlock.
f) En el tercer escenario, todos los procesos hijos estaran continuamente pasndose los datos escritos por el padre.
1. Los procesos no finalizaran, excepto si les llega una seal que les mata.
2. Una vez que los procesos arrancan, todos los procesos hijos estaran ejecutando el mandato cat. El pro-
ceso padre estara bloqueado esperando a la finalizacin de sus hijos.
3. Todos los procesos llevan a cabo su trabajo. Se encuentran en un bucle infinito de trabajo.
g) Al proceso hijo le llega una seal que provoca que muere. El orden y el estado de terminacin de los procesos de-
pende del orden de planificacin de los mismos por parte del sistema operativo. Si por ejemplo, despus de morir el
primer proceso hijo, se planifica el ltimo proceso hijo, ste morir al intentar escribir en una tubera sin lectores.
Le llegara la seal SIGPIPE. Si despus, se planifica el penltimo proceso hijo, le ocurrira lo mismo y as sucesiva-
mente. Por el contrario, si despus de morir el primer proceso hijo, se planifica el segundo, ste terminar de manera
correcta, pues ya no hay ms procesos escritores sobre la tubera de la que lee y por tanto finalizar y morir correc-
tamente. Lo mismo le ocurrir al tercer proceso si se planificara a continuacin. Todo depende si el proceso poste-
rior al planificado ha finalizado o no. Si ha finalizado, el proceso correspondiente terminar de manera incorrecta al
intentar escribir sobre una tubera sin lectores. Si no ha terminado y el proceso anterior s, terminar correctamente
al no tener ya ms procesos escritores sobre la tubera de la que est leyendo.

Problema 1.16 (abril 2010)

Dado el siguiente fuente:


01 int bucle_fork(int M)
02 {
03 int n;
04 for(n = 0; n < M; n++){
05 switch(fork()){
06 case -1:
07 perror(fork());
08 return -1;
09 case 0:
10
11 default:
12
13 }
14 }
15 return 0;
16 }
46 Problemas de sistemas operativos
a) Dibujar el diagrama del rbol genealgico de procesos (relaciones padre-hijo) que resulta al invocar bu-
cle_fork(3), incluyendo el proceso que originalmente hace la invocacin. Identifique a cada hijo con el va-
lor de n en el momento de su creacin.
b) Cuntos procesos (incluyendo al primer padre) se producen al invocar bucle_fork(M)? Suponer que to-
dos los fork() retornan sin error.
c) Qu cdigo hay que escribir en la lnea 10 y en la lnea 12 para conseguir crear solo M procesos hijo (to -
dos hijos del mismo padre)?
d) Qu cdigo hay que escribir en la lnea 10 y en la lnea 12 para conseguir que el proceso inicial tenga una
descendencia de M generaciones de procesos (padre hijo nieto bisnieto ...)?
e) Cmo temporizar para que cada hijo muera a los n+1 segundos? Suponer que el proceso inicial, ante la se-
al SIGALRM, realiza la accin por defecto (terminar)
f) Qu comportamiento obtenemos si lnea 10 = exit(0); lnea 12 = pause(); wait(NULL);?

Solucin
a)

0 1 2

1 2 2

2
Figura 1.16
b) 2M
c) En general:
lnea 10 = Debe acabar en alguna sentencia que termine al hijo (que no tenga descendencia)
lnea 12 = El padre debe continuar iterando
Por ejemplo.
lnea 10 = pause(); exit(0);
lnea 12 = break;
d) En general:
lnea 10 = El hijo debe continuar iterando (para seguir generando la siguiente generacin)
lnea 12 = El padre debe terminar
Por ejemplo.
lnea 10 = break;
lnea 12 = exit(0);
e) En general:
lnea 10 = Dado que cada hijo hereda la disposicin de seales de su padre, ante SIGALRM terminar, por lo
que basta con solicitar SIGALRM a los n+1 segundos. Cuando reciba SIGALRM, terminar.
lnea 12 = El padre debe seguir iterando, para que se generen los hijos.
Por ejemplo:
lnea 10 = alarm(n+1);
lnea 12 = break;
f) El primer hijo se convierte en zombie. En efecto, el primer hijo termina, pero el padre no est ejecutando wait(),
sino pause(), por lo que est esperando la llegada de la siguiente seal. En principio no se indica que el padre haya
47
armado ninguna seal. Cuando llegue (si llega) una seal al padre puede que el padre termine involuntariamente
(por ejemplo ante una seal no armada cuyo comportamiento por defecto sea la terminacin) y wait(NULL) nunca
ejecute, por lo que el proceso Init adopta al zombie, ejecuta wait() y el zombie desaparece. Si el padre no termina,
ejecuta wait(NULL) y el zombie desaparece igualmente.

Problema 1.17 (junio 2010)

Dados los programas programa_a y programa_b, rellenar las casillas vacas de la tabla 1.1, indicando el tiempo
que dura la ejecucin del proceso hijo de programa_a y la causa de su terminacin:
Suponer despreciables todos los tiempos de ejecucin (SO, otros procesos, etc.) distintos de los
indicados en las invocaciones a sleep().
Suponer que la accin por defecto al recibir cualquier seal es terminar involuntariamente el proceso.
programa_a:
void funcion1(int s){ }
int main(void){
pid_t p;
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = &funcion1;
act.sa_flags = 0;
sigaction(SIGUSR1, &act, NULL);
p = fork();
switch(p){
case -1:
perror("fork() = -1");
exit(1);
case 0:
sleep(2);
act.sa_handler = SIG_IGN;
sigaction(SIGUSR2, &act, NULL);
sleep(2);
execl("./programa_b", "programa_b", (char *)NULL);
perror("exec");
exit(1);
default:
sleep(T1);
kill(p, SIG_X);
sleep(T2);
kill(p, SIGQUIT);
wait(NULL);
}
return 0;
}
programa_b:
int main(void){
sleep(2);
return 0;
}
48 Problemas de sistemas operativos
SIG_X T1(s) T2(s) Tiempo vida hijo (s) Causa terminacin hijo
SIGUSR1 1 6
3 5
3 1
5 4
SIGUSR2 1 5
3 4
5 2
SIGINT 1 2
5 3
SIGKILL 5 2
Tabla 1.1

SOLUCIN

SIG_X T1(s) T2(s) Tiempo vida hijo (s) Causa terminacin hijo
SIGUSR1 1 6 5 (a) Terminacin voluntaria normal
3 5 5 (a) Terminacin voluntaria normal
3 1 4 (a) Accin por defecto al recibir SIGQUIT
5 4 5 (a) Accin por defecto al recibir SIGUSR1
SIGUSR2 1 5 1 (a) Accin por defecto al recibir SIGUSR2
3 4 6 Terminacin voluntaria normal
5 2 6 Terminacin voluntaria normal
SIGINT 1 2 1 Accin por defecto al recibir SIGINT
5 3 5 Accin por defecto al recibir SIGINT
SIGKILL 5 2 5 Accin por defecto al recibir SIGKILL
Tabla 1.2
(a) La seal interrumpe la temporizacin de sleep() en proceso hijo, pero sin terminar el proceso, por lo que parte
de la temporizacin de sleep no se contabiliza en la duracin total del proceso hijo.

Problema 1.18 (sep-2010)

Dado el siguiente programa:


int main(void){
int s, n, k = 0;
pid_t p1;
for(n = 0; n < 3; ){
p1 = fork();
switch(p1){
case -1:
perror("fork"); exit(1);
case 0:
if(getpid()%2 == 0)
exit(0);
else
exit(1);
default:
k++;
wait(&s);
if(WIFEXITED(s))
if(WEXITSTATUS(s) == 0)
n++;
}
}
printf("k = %i\n", k);
return 0;
}
a) Qu contiene la variable p1?
b) Qu sentencias del cuerpo del switch corresponden al proceso hijo?
49
c) Qu funcin realiza el proceso hijo? (Recuerde que a % b es el resto de la divisin entera de a entre b)
d) Qu funcin realiza la macro WEXITSTATUS()?
e) Qu operaciones hace el proceso padre tras crear al proceso hijo?
f) Cuntos procesos hijo pueden existir al mismo tiempo? Porqu?
g) Qu informacin contienen las variables k y n al final de cada iteracin del bucle for?
h) Puede en algn caso caso iterar indefinidamente el bucle for? Si es as, En cul?
i) Qu informacin contiene la variable k al final del programa?
j) Qu escribe el programa en la salida estndar si el sistema operativo asigna la siguiente secuencia de
PIDs a los procesos hijo: 29180, 29181, 29182, 29183, 29184, 29185, 29186, 29187, etc.?

SOLUCIN
a) El cdigo retornado por fork(). En el proceso hijo vale 0 y en el proceso padre contiene el PID del proceso hijo.
b) Las sentencias de la etiqueta case 0: if(getpid()%2 == 0) exit(0); else exit(1);
c) El proceso hijo termina con cdigo de terminacin 0 si su PID es par y con 1 si es impar
d) Retorna el cdigo de terminacin del hijo.
e) Incrementa k, espera a la terminacin del hijo y si ste termina con cdigo 0 (pid par), entonces incrementa n.
f) Solo uno, por que el proceso padre espera a la terminacin de cada hijo.
g) k = Nmero de procesos hijo creados hasta el momento
n = Nmero de procesos hijo que han tenido pid par hasta el momento.
h) S. En el caso en que n siempre sea menor que 3, es decir, en el que el sistema operativo no asigne ms de 2 pid
pares a los sucesivos procesos hijo.
i) k = Nmero de procesos hijo que ha sido necesario crear para conseguir que 3 de ellos tengan pid par.
j) k = 5

Problema 1.19 (nov-2010)

a) Escribir un programa que cree N procesos hijo (hijos del proceso principal). Los hijos se deben comunicar con el
padre mediante un pipe nico, en el que escribe cada hijo y slamente lee el padre. T segundos despus de crear los
procesos hijo el padre enviar a cada hijo una seal SIGUSR1. Cada hijo, al recibir tal seal, escribir en el pipe
su nmero de hijo mediante un carcter nico 0, 1...N-1 y terminar (suponer que N < 11 para simplificar esta eta -
pa). Despues el padre leer todos los caracteres y terminar.
NOTA: Definir N y T como constantes internas del programa.
b) Modificar el programa anterior para que la temporizacin de T segundos empiece antes de crear los procesos
hijo.

SOLUCIN
a)
#define N 10
#define T 3
int f[2];
char buf;
int i;

void f1(int s){


write(f[1], &buf, sizeof(buf));
}

int main(void){
50 Problemas de sistemas operativos
pid_t p[N];
if (pipe(f) < 0){
perror("pipe\n"); return 1;
}
struct sigaction a1;
sigemptyset(&a1.sa_mask);
a1.sa_handler = f1;
a1.sa_flags = 0;
sigaction(SIGUSR1, &a1, NULL);
for(i = 0; i < N; i++){
if((p[i] = fork()) == -1){
perror("fork"); return 1;
}else if(p[i] == 0){
close(f[0]);
buf = '0' + i;
pause();
return 0;
}
}
close(f[1]);
sleep(T);
for(i = 0; i < N; i++){
kill(p[i], SIGUSR1);
}
for(i = 0; i < N; i++){
if(read(f[0], &buf, 1) < 0){
perror("read"); return 1;
}
}
return 0;
}
b) En paralelo con la temporizacin se deben crear los hijos, por lo que no podemos usar sleep(T). El padre debe
temporizar con alarm(T) antes del primer for (armando previamente SIGALRM). Cada hijo no hereda las seales
pendientes del padre, por lo que SIGALRM no se hereda y llegar exclusivamente al padre, permitiendo un funcio -
namiento correcto. La accin asociada a SIGALRM sern los bucles de envo de SIGUSR1 a hijos y lectura del
pipe.
#define N 10
#define T 3
int f[2];
char buf;
int i;
pid_t p[N];

void f2(int s){


for(i = 0; i < N; i++){
kill(p[i], SIGUSR1);
}
for(i = 0; i < N; i++){
if(read(f[0], &buf, 1) < 0){
perror("read"); exit(1);
}
}
}

void f1(int s){


write(f[1], &buf, sizeof(buf));
}

int main(void){

struct sigaction a1;


sigemptyset(&a1.sa_mask);
a1.sa_handler = f2;
a1.sa_flags = 0;
51
sigaction(SIGALRM, &a1, NULL);

sigemptyset(&a1.sa_mask);
a1.sa_handler = f1;
a1.sa_flags = 0;
sigaction(SIGUSR1, &a1, NULL);

if (pipe(f) < 0){


perror("pipe\n"); return 1;
}

alarm(T);
for(i = 0; i < N; i++){
if((p[i] = fork()) == -1){
perror("fork"); return 1;
}else if(p[i] == 0){
close(f[0]);
buf = '0' + i;

pause();
return 0;
}

}
close(f[1]);
pause();
return 0;
}

Problema 1.20 (febrero 2011)

Dado el siguiente programa:


#define N 10
int i, p1[2];
pid_t h1;
char c1;

void f1(int s){


c1 = '0' + i - 1;
write(p1[1], &c1, 1);
}
void f2(int s){
for(i = 0; i < N; i++) /*I*/
read(p1[0], &c1, 1);
}
int main(void){
struct sigaction a1;
sigemptyset(&a1.sa_mask);
a1.sa_handler = f1;
a1.sa_flags = 0;
sigaction(SIGUSR1, &a1, NULL); /*A*/

pipe(p1); /*B*/
for(i = 0; i < N; i++) /*C*/
if((h1 = fork()) > 0)
break;

if(i == 0){ /*D*/


a1.sa_handler = f2;
sigaction(SIGUSR1, &a1, NULL);
52 Problemas de sistemas operativos
pause();
waitpid(h1, NULL, 0);
}else if(i < N){ /*E*/
pause();
kill(getppid(), SIGUSR1);
waitpid(h1, NULL, 0);
}else{ /*F*/
sleep(1);
f1(0);
kill(getppid(), SIGUSR1);
}
return 0;
} //(FIN PROGRAMA)

k) Qu accin realiza la sentencia A? Qu consecuencias tiene la accin de A en los procesos hijo del proce-
so principal (si los hay)?
l) Qu accin realiza la sentencia B? Qu consecuencias tiene en los procesos hijo (si los hay)?
m) Cuntos procesos genera el bucle C? Qu relaciones padre-hijo hay entre ellos?
n) Qu proceso ejecuta el cuerpo del if de D?
o) Qu procesos ejecutan el cuerpo del if de la sentencia E?
p) Que proceso ejecuta el cuerpo de else de la sentencia F?
q) Cuntas seales SIGUSR1 se envan en total? Explicar su origen, destino y en qu orden se envan.
r) En qu orden acceden al pipe p1 los procesos creados por fork? Leen, escriben o ambas cosas?
s) Qu secuencia de caracteres se ha ledo del pipe p1 al terminar el for de la lnea I?
t) Garantiza este cdigo los mismos resultados en un sistema muy cargado con otros procesos? Porqu? Si
no es as, proponer una solucin.

SOLUCIN
a) Qu accin realiza la sentencia A? Qu consecuencias tiene la accin de A en los procesos hijo del proceso
principal (si los hay)?
Arma la seal SIGUSR1 con la funcin f1. Los procesos hijo heredan el armado de la seal SIGUSR1.

b) Qu accin realiza la sentencia B? Qu consecuencias tiene en los procesos hijo (si los hay)?
Crea un fichero pipe cuyos descriptores estn en registrados en p1. Los hijos heredan el pipe abierto.

c) Cuntos procesos genera el bucle C? Qu relaciones padre-hijo hay entre ellos?


Genera N procesos. N generaciones a partir del proceso principal (cada generacin tiene un nico hijo).

d) Qu proceso ejecuta el cuerpo del if de D?


El proceso principal.

e) Qu procesos ejecutan el cuerpo del if de la sentencia E?


Los procesos hijo de generacin i = 1, 2 ...N-1, o sea, todas las generaciones excepto la ltima.

f) Que proceso ejecuta el cuerpo de else de la sentencia F?


El proceso de la ltima generacin (i = N), ltimo hijo creado.

g) Cuntas seales SIGUSR1 se envan en total? Explicar su origen, destino y en qu orden se envan.
53
En total se envan N seales SIGUSR1. Se enva una desde cada proceso hijo creado por fork. El destino es su pro-
ceso padre, que espera a su recepcin para enviar a su vez su seal. El orden de envo comienza por el ltimo hijo (i
= N) y va ascendiendo por el rbol de procesos, de cada hijo su padre, hasta llegar al proceso principal.

h) En qu orden acceden al pipe p1 los procesos creados por fork? Leen, escriben o ambas cosas?
En el mismo orden de la cadena de seales, ascendente por el rbol de procesos, comenzando por el ltimo hijo (i =
N) y terminando en el proceso principal (i = 0). Cada proceso solo escribe el caracter correspondiente al dgito i-1.

i) Qu secuencia de caracteres se ha ledo del pipe p1 al terminar el for de la lnea I?


9876543210

j) Garantiza este cdigo los mismos resultados en un sistema muy cargado con otros procesos? Porqu? Si no es
as, proponer una solucin.
No lo garantiza. Si alguna de las seales llega antes de que se ejecute su correspondiente pause(), el proceso de des -
tino queda esperando indefinidamente y no enviar nunca la seal a su padre. Esto puede ocurrir si el proceso de
destino tarda en entrar a ejecucin en un sistema muy cargado. La solucin consiste en eliminar los pause() y mover
los kill(getppid(), SIGUSR1) al final de f1(). As la funcin de armado f1() maneja completamente la cadena de se-
ales. Los procesos pueden recibir la seal mientras esperan en waitpid().

Problema 1.21 (abril 2011)

Dado un planificador ROUND ROBIN con rodaja de tiempo de 3ms, obtener el cronograma de ejecucin de los si -
guientes procesos y fases de ejecucin:
Proceso A: 5ms de CPU, lectura de 5ms sobre el dispositivo D1, 3ms de CPU
Proceso B: 2ms de CPU, escritura de 4ms sobre el dispositivo D2, 1ms de CPU
Consideraciones: Al comenzar, asignar el procesador al proceso A y colocar el proceso B en la cola de listos.
Asignar 1ms en cada intervencin del SO. El sistema no tiene que atender a ningn otro evento o proceso de usua -
rio. Los tiempos de las operaciones de entrada/salida indicadas comienzan al terminar la intervencin previa del
SO.

SOLUCIN
(sombreado = origen del siguiente cambio de estado)
t(ms) 0 3 4 6 7 9 10 11 12 13 14 15 16 19

dt 3 1 2 1 2 1 1 1 1 1 1 1 3

A E p p p E - - - - - - -p E fin

B p p E - - - - -p E fin

SO p E p E p E p E p E p E p

nul p p p p p p E p p p E p p

D1 1 1 1 1 1

D2 2 1 1

dt = duracin del estado (columna de la tabla), nul = proceso nulo


E= Ejecutando, p= preparado (listo), - = bloqueado
54 Problemas de sistemas operativos
D1 y D2: <nmero> = duracin de cada intervalo de tiempo

Problema 1.22 (abril 2011)

Escribir un programa que realice lo siguiente. El programa genera un nico proceso, que ejecuta 3 tareas cuyo c -
digo fuente ya est programado en funciones que tienen los siguientes prototipos:
int tarea_A(void);
int tarea_B(void);
int tarea_C(int n);
Sean a, b y c los valores que retornan la tarea_A, tarea_B y tarea_C, respectivamente. Si a > 0, la tarea C debe
ser invocada con el valor n = a. Sino, debe ser invocada con n = b. Las tareas no tienen ninguna otra dependencia
entre ellas. El proceso debe maximizar la concurrencia entre tareas. Antes de terminar, el proceso debe escribir los
valores de a, b y c en la salida estndar.

SOLUCIN
int n;
pthread_t thid = 0;
int tarea_A(void);
int tarea_B(void);
int tarea_C(int n);
void *thread_B(void *p) {pthread_exit((void *)tarea_B());}
int main(void){
int ret, a, b, c;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
ret = pthread_create(&thid, &attr, &thread_B, NULL);
if(ret != 0){perror("p_create()"); exit(1);}
if((a = tarea_A()) > 0){
c = tarea_C(a);
ret = pthread_join(thid, (void **)(&b));
if(ret != 0){perror("p_join()"); exit(1);}
}else{
ret = pthread_join(thid, (void **)(&b));
if(ret != 0){perror("p_join()"); exit(1);}
c = tarea_C(b);
}
pthread_attr_destroy(&attr);
printf("a=%i b=%i c=%i\n", a, b, c);
return 0;
}

Problema 1.23 (junio 2011)

Dado el siguiente programa:

#define N 10
int i, pipe_datos[2], pipe_pid[2];
pid_t pid[N], pid_aux;
void f1(int s){
if(i < N-1)
read(pipe_pid[0], &pid_aux, sizeof(pid_t));
pid_t pid2 = getpid();
write(pipe_pid[1], &pid2, sizeof(pid_t));
55
if(i == 0)
f2(0);
else
kill(pid[i-1], SIGUSR1);
}
void f2(int s){
char c1 = '0' + i;
write(pipe_datos[1], &c1, 1);
if(i < N-1)
kill(pid_aux, SIGUSR2);
exit(0);
}
int main(void){
struct sigaction a1;
sigemptyset(&a1.sa_mask);
a1.sa_flags = 0;
a1.sa_handler = f1;
sigaction(SIGUSR1, &a1, NULL); /*A*/
a1.sa_handler = f2;
sigaction(SIGUSR2, &a1, NULL); /*B*/
pipe(pipe_datos);
pipe(pipe_pid);
for(i = 0; i < N; i++){
if((pid[i] = fork()) == 0){
if(i == N-1) f1(0); /*D*/
while(1){}
}
}
for(i = 0; i < N; i++){
char c2;
waitpid(pid[i], NULL, 0);
read(pipe_datos[0], &c2, 1); /*C*/
}
return 0;
} /*FIN PROGRAMA*/

u) Cuntos procesos genera este programa? Qu relaciones padre-hijo hay entre ellos?
v) Qu realizan las sentencias A y B? A cuntos y a qu procesos afectan estas sentencias?
w) Qu procesos escriben en pipe_datos? Qu procesos leen de pipe_datos? Qu procesos escriben en
pipe_pid? Qu procesos leen de pipe_pid?
x) Ntese que la sentencia D hace que el ltimo hijo N-1 comience su ejecucin invocando a la funcin f1.
Qu procesos envan la seal SIGUSR1? Hacia qu procesos van destinadas? Cuntas seales SIGUSR1
se envan en total?
y) Qu proceso escribe primero en pipe_pid? Qu dato escribe? Qu proceso lee ese dato?
z) Ntese que la variable pid_aux es global. Se asigna en la funcin f1 y se usa en la funcin f2. Al terminar de
tratar la ltima seal SIGUSR1, Qu dato contiene la variable pid_aux de cada proceso hijo?
aa)Qu procesos envan la seal SIGUSR2? Hacia qu procesos van destinadas? Cuntas seales SIGUSR2
se envan en total?
ab)Qu proceso enva la primera seal? Y la ltima seal?
ac) Justo antes de terminar el proceso padre, Qu dato contiene el fichero de pipe_pid?
ad)Qu secuencia de caracteres lee el proceso padre en el bucle final (sentencia C)

SOLUCIN
a) Cuntos procesos genera este programa? Qu relaciones padre-hijo hay entre ellos?
Genera N+1 procesos. 1 padre y N hijos (hermanos entre s).
56 Problemas de sistemas operativos
b)Qu realizan las sentencias A y B? A cuntos y a qu procesos afectan estas sentencias?
Arman las seales SIGUSR1 y SIGUSR2 con las funciones de armado f1 y f2, respectivamente. Afectan al proceso
padre y a los N procesos hijo (N+1 procesos)

c)Qu procesos escriben en pipe_datos? Qu procesos leen de pipe_datos? Qu procesos escriben en pipe_pid?
Qu procesos leen de pipe_pid?
Escriben en pipe_datos: Los N procesos hijo
Leen de pipe_datos: El proceso padre
Escriben en pipe_pid: Los N procesos hijo
Leen de pipe_pid: Los procesos hijo 0, 1, 2...N-2

d) Ntese que la sentencia D hace que el ltimo hijo N-1 comience su ejecucin invocando a la funcin f1. Qu
procesos envan la seal SIGUSR1? Hacia qu procesos van destinadas? Cuntas seales SIGUSR1 se envan en
total?
SIGUSR1 es enviada por los procesos hijo 1, 2, ... N-1. Cada hijo i envia SIGUSR1 a su hermano mayor i-1. En to-
tal se envan N-1 seales SIGUSR1.

e) Qu proceso escribe primero en pipe_pid? Qu dato escribe? Qu proceso lee ese dato?
El primer proceso en escribir en pipe_pid es el hijo N-1. Escribe su PID. Ese dato lo lee el proceso hijo N-2.

f) Ntese que la variable pid_aux es global. Se asigna en la funcin f1 y se usa en la funcin f2. Al terminar de tratar
la ltima seal SIGUSR1, Qu dato contiene la variable pid_aux de cada proceso hijo?
Contiene el PID del hermano menor inmediato (proceso i+1)

g) Qu procesos envan la seal SIGUSR2? Hacia qu procesos van destinadas? Cuntas seales SIGUSR2 se
envan en total?
SIGUSR2 es enviada por los procesos hijo 0, 1, 2,.. N-2. Cada hijo i enva SIGUSR2 a su hermano menor i+1. En
total se envan N-1 seales SIGUSR2.

h) Qu proceso enva la primera seal? Y la ltima seal?


Primera seal (SIGUSR1): Proceso hijo N-1.
ltima seal (SIGUSR2): Proceso hijo N-2.

i) Justo antes de terminar el proceso padre, Qu dato contiene el fichero de pipe_pid?


El pid del primer proceso hijo (hijo 0)

j) Qu secuencia de caracteres lee el proceso padre en el bucle final (sentencia C)


0123456789

Problema 1.24 (julio 2011)

Dado el siguiente programa:

#define N 10
#define T 8
57
int f[2];
char buf;
int i;
pid_t p[N];
void f2(int s){
for(i = 0; i < N; i++){
kill(p[i], SIGUSR1);
}
for(i = 0; i < N; i++){/*E*/
while(read(f[0],&buf,1) < 1){
}
}
}
void f1(int s){
buf = '0' + i;
write(f[1], &buf, sizeof(buf));/*F*/
}
int main(void){
struct sigaction a1;
sigemptyset(&a1.sa_mask);
a1.sa_handler = f2;
a1.sa_flags = 0;
sigaction(SIGALRM, &a1, NULL);/*A*/

sigemptyset(&a1.sa_mask);
a1.sa_handler = f1;
a1.sa_flags = 0;
sigaction(SIGUSR1, &a1, NULL);/*B*/

if(pipe(f) < 0){


perror("pipe"); return 1;
}
/*C*/
alarm(T); /*D*/
for(i = 0; i < N; i++){
if((p[i] = fork()) == -1){
perror("fork"); return 1;
}else if(p[i] == 0){
close(f[0]);
pause();
return 0;
}
}
close(f[1]);
pause();
return 0;
}

a) Qu hacen la sentencias A y B?
b) Qu contiene la variable f en el punto C del cdigo?
c) Qu accin realiza la sentencia D? Qu consecuencias produce?
d) Dibujar el rbol de procesos generados por el programa
e) Qu seales recibe el proceso padre? Qu seales recibe cada proceso hijo? (Indicar cuantas seales en cada
caso)
f) Cuntos bytes se leen del pipe f en cada iteracin del bucle for de la sentencia E?
g) Cuntos bytes lee cada proceso hijo del pipe? Cuntos escribe?
h) Ntese que el proceso padre no ejecuta ningn servicio wait. Podra algn hijo quedar hurfano antes de ejecutar
la sentencia F? Porqu?
58 Problemas de sistemas operativos
i) Ntese que el programa puede tener un resultado inesperado si el sistema est muy cargado y T es pequeo. Expli-
car qu problema puede aparecer.
j) Proponer una solucin para el problema de la pregunta anterior, para cualquier valor de T (no se admite aumentar
T) y manteniendo la sentencia D (alarm(T)) en su ubicacin actual. No es necesario escribir el cdigo fuente, pero
indicar qu servicios del sistema operativo son necesarios.

SOLUCIN
a) Qu hacen la sentencias A y B?
A: Arma la seal SIGALRM con la funcin de armado f2
B: Arma la seal SIGUSR1 con la funcin de armado f1
b) Qu contiene la variable f en el punto C del cdigo?
Contiene los descriptores de lectura/escritura del fichero pipe recin creado. f[0] = fd de lectura, f[1] = fd de es-
critura.
c) Qu accin realiza la sentencia D? Qu consecuencias produce?
Inicia un temporizador de T segundos. Cuando termine, el sistema operativo enviar al proceso principal la seal
SIGALRM.
d) Dibujar el rbol de procesos generados por el programa
e) Qu seales recibe el proceso padre? Qu seales recibe cada proceso hijo? (Indicar cuantas seales en cada
caso)
El padre recibe 1 SIGALRM.
Cada hijo recibe 1 SIGUSR1.
f) Cuntos bytes se leen del pipe f en cada iteracin del bucle for de la sentencia E?
1
g) Cuntos bytes lee cada proceso hijo del pipe? Cuntos escribe?
Lee 0 bytes. Escribe 1 byte.

h) Ntese que el proceso padre no ejecuta ningn servicio wait. Podra algn hijo quedar hurfano antes de ejecutar
la sentencia F? Porqu?
No. El proceso padre antes de morir tiene que haber ledo n bytes del pipe, que deben haber sido escritos por los
hijos (ejecutando F).
i) Ntese que el programa puede tener un resultado inesperado si el sistema est muy cargado y T es pequeo. Expli -
car qu problema puede aparecer.
No se crearan todos los hijos. SIGALRM llegara al padre antes de que todos los hijos hayan sido creados. Se
ejecutara la funcin de armado f2 pero el envo de SIGUSR1 sera incorrecto. El pipe contendra menos de los N
bytes esperados, por lo que el padre quedara esperando indefinidamente en read(f[0]...), etc.
j) Proponer una solucin para el problema de la pregunta anterior, para cualquier valor de T (no se admite aumentar
T) y manteniendo la sentencia D (alarm(T)) en su ubicacin actual. No es necesario escribir el cdigo fuente, pero
indicar qu servicios del sistema operativo son necesarios.
Hay que garantizar que SIGALRM llega despus de haber creado todos los hijos. Para ello:
Enmascarar SIGALRM (servicio sigprocmask) antes de invocar 'alarm(T)'
Desenmascarar SIGALRM (servicio sigprocmask) despus del bucle for de creacin de hijos.

Problema 1.25 (nov-2011)

1 #include <error.h>
59
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <sys/types.h>
7 #include <sys/wait.h>
8
9 pid_t hpid, ppid;
10 int i; int n; int estado;
11
12 int main (int argc, char **argv)
13 {
14
15 n = atoi (argv[1]);
16 ppid = getpid ();
17 fprintf (stdout, "Proceso padre: %d Proceso P: %d\n", (int) getppid (),
18 (int) ppid);
19
20 for (i = 1; i < n; i++) {
21 hpid = fork ();
22 if (hpid == -1) {
23 perror ("P: fork"); exit (1);
24 }
25 if (hpid == 0) {
26 fprintf (stdout, "Hijo: %d pid: %d Padre: %d\n", i, (int) getpid (),
27 (int) getppid ());
28 }
29 else break;
30 }
31
32 if (hpid != 0) {
33 hpid = wait (&estado);
34 if (hpid == -1)
35 {
36 perror ("wait"); return 1;
37 }
38 else if (ppid != getpid ())
39 {
40 fprintf (stdout, "Finalizado el hijo: %d\n", (int) getpid ());
41 return 0;
42 }
43 else fprintf (stdout, "Finalizado el proceso P: %d\n", (int) getpid ());
44 }
45 else if (i == n)
46 fprintf (stdout, "Finalizado el ultimo hijo: %d\n", (int) getpid ());
47
48 return 0;
49 }

Suponiendo que el proceso padre P tiene PID 100, que los procesos hijos incrementan en una unidad el PID del
padre segn se vayan creando y que durante la ejecucin de P no se crean ms procesos en el sistema:
a) Obtenga el diagrama jerrquico de procesos creados indicando los PID de los procesos cuando se invoca la
ejecucin del proceso de la siguiente forma: ./P 3
b) Indique una posible traza de ejecucin obtenida con la invocacin anterior.
c) Si se elimina el servicio wait de la lnea 33, cmo afecta esta moficacin a la terminacin de los proce-
sos?
d) Sobre la versin inicial del cdigo adjuntado en el enunciado, aada el cdigo necesario para bloquear la
recepcin de la seal SIGINT en todos los procesos salvo en el proceso P, indicando dnde lo incluira.
e) Sobre la versin inicial del cdigo adjuntado en el enunciado, aada el cdigo necesario para que los hijos
ignoren la recepcin de la seal SIGSTOP, indicando dnde lo incluira.
f) Sobre la versin inicial del cdigo adjuntado en el enunciado, aada el cdigo necesario para que los hijos
creen cada uno un thread de tipo ULT que ejecute el cdigo asociado a la funcin void *termi-
nar(void *p) en modo detached:
60 Problemas de sistemas operativos
void *terminar(void *p) {
printf("Thread %u \n", (int)pthread_self());
exit(0);
}
g) Tomando como referencia el cdigo desarrollado para el apartado anterior, indique el resultado de una posi-
ble traza de ejecucin al invocar: ./P 2

SOLUCIN
a) P: 100 -> 101 -> 102
b)
Proceso padre: 50 Proceso P: 100
Hijo: 1 pid: 101 Padre: 100
Hijo: 2 pid: 102 Padre: 101
Finalizado el ultimo hijo: 102
Finalizado el hijo: 101
Finalizado el proceso P: 100
c) Los procesos que son padres no esperan por sus hijos. La traza de ejecucin se modificar al perder el control so-
bre el orden de finalizacin de los procesos. Esto afecta al orden en el que se imprimen los mensajes. Probablemen -
te, aparecern en el mismo orden en el que se han creado los procesos. Respecto al estado en el que quedan los hijos,
al no esperar sus padres por ellos, se convierten en zombies.
d) Bastara con declarar una variable local a la funcin main que recogiera el valor de la mscara que se quiere defi-
nir y aadir a continuacin de la lnea 25 (primera lnea de cdigo ejecutada por los hijos) las siguientes lneas de
cdigo:
sigset_t mascara; // Variable local de la funcin main
25 if (hpid == 0) {
sigemptyset(&mascara);
sigaddset(&mascara,SIGINT);
sigprocmask(SIG_SETMASK, &mascara, NULL);
26 fprintf (stdout, "Hijo: %d pid: %d Padre: %d\n", i, (int) getpid (),
27 (int) getppid ());
28 }

e) No se puede hacer porque las seales SIGKILL y SIGSTOP son las dos nicas que no se pueden ignorar.
f) Declaracin de variables locales de la funcin main:
pthread_attr_t attr;
pthread_t thid;
Antes del bucle de creacin de los hijos, definicin de los atributos de los threads:
pthread_attr_init(&attr);
pthread_attr_setscope(&attr,PTHREAD_SCOPE_PROCESS);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
A continuacin de la sentencia de la lnea 26 (fprintf), se aade la creacin de los threads:
if (pthread_create(&thid,&attr,&terminar,NULL) == -1) {
perror("pthread_create"); exit(1);
}
g) La traza de ejecucin sera:
Proceso padre: 50 Proceso P: 100
Hijo: 1 pid: 101 Padre: 100
Thread: 21678761
Finalizado el proceso P: 100
Al invocar el exit desde la funcin terminar, no solo se termina el thread sino el proceso desde el cual se ha invo -
cado, as como todos los threads que se hubieran creado dentro de ese proceso, por eso no se imprime el mensaje de
finalizacin del proceso hijo.
61

Problema 1.26 (abril 2012)

Sea un proceso padre con un nmero indeterminado de hijos (al menos 1). Escribir un tramo de cdigo fuente que
permita al proceso padre esperar la terminacin del proceso hijo con PID p1 y todos los que terminen antes. Si el
hijo p1 ha terminado involuntariamente por la llegada de la seal SIGKILL el proceso padre debe esperar la termi-
nacin del resto de hijos durante un tiempo mximo de 5 segundos y despus terminar voluntariamente con cdigo
de terminacin 2.
NOTA: No usar ninguna variable N, o similar, para indicar el nmero total de hijos.

SOLUCIN
void f1(int s){exit(2);}
main(...){
...
struct sigaction act;
act.sa_handler = &f1;
act.sa_flags = 0;
sigaction(SIGALRM, &act, NULL);
...
while (wait(&status)!= p1)
continue;
if(WIFSIGNALED(status))
if(WTERMSIG(status) == SIGKILL){
alarm(5);
while(wait(&status) > 0)
continue;
exit(2);
}
}

Problema 1.27 (abril 2012)

Escribir un tramo de cdigo que realice las mismas operaciones que el cdigo que se muestra a continuacin, pero
maximizando la concurrencia entre las funciones invocadas y generando un nico proceso.
int a, b, c, d, e;
a = tarea_A(0);
b = tarea_B(0);
d = tarea_D(a);
c = tarea_C(a);
e = tarea_E(b+c);
printf("%i %i %i %i %i\n", a, b, c, d, e);

SOLUCIN
pthread_t Id_B = 0, Id_D = 0;
int a, b, c, d, e;
void *B(void *p){
b = tarea_B(0);
pthread_exit(NULL);
}
void *D(void *p){
d = tarea_D(a);
pthread_exit(NULL);
}
int main(void){
pthread_attr_t attr;
62 Problemas de sistemas operativos
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);

pthread_create(&Id_B, &attr, &B, NULL);


a = tarea_A(0);
pthread_create(&Id_D, &attr, &D, NULL);
c = tarea_C(a);
pthread_join(Id_B, NULL);
e = tarea_E(b+c);
pthread_join(Id_D, NULL);
pthread_attr_destroy(&attr);
printf("%i %i %i %i %i\n", a, b, c, d, e);
}

OTRA SOLUCIN:
pthread_t Id_CE = 0, Id_B = 0;
int a, b, c, d, e;
void *B(void *p){
b = tarea_B(0);
pthread_exit(NULL);
}
void *CE(void *p){
c = tarea_C(a);
pthread_join(Id_B, NULL);
e = tarea_E(b+c);
pthread_exit(NULL);
}
int main(void){
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
pthread_create(&Id_B, &attr, &B, NULL);
a = tarea_A(0);
pthread_create(&Id_CE, &attr, &CE, NULL);
d = tarea_D(a);
pthread_join(Id_CE, NULL);
pthread_attr_destroy(&attr);
printf("%i %i %i %i %i\n", a, b, c, d, e);
return 0;
}

Problema 1.28 (abril 2012)

Se desea escribir un programa servidor de peticiones externas que van llegando indefinidamente en el tiempo. El
programa debe generar un proceso padre con N procesos hijo. Las peticiones van llegando por la entrada estndar
del proceso padre. Cada peticin est codificada mediante un byte nico que se debe procesar invocando a la fun -
cin ya programada: void procesa_peticion(char c), siendo c el byte que identifica la peticin. Los
procesos (padre e hijos) se deben turnar para leer y procesar la siguiente peticin, de manera que cada proceso
solo debe atender una peticin cada vez que le toca el turno. Se debe maximizar la concurrencia (cada proceso
debe pasar el turno de lectura al siguiente proceso lo antes posible).
a) Escribir el programa para que los turnos tengan un orden predeterminado que pase por los N+1 procesos y
que se repita indefinidamente. Sugerencia: Usar seales para pasar el turno al siguiente proceso. Suponer
que durante la ejecucin de una funcin de armado de una seal A el SO enmascara automticamente la
misma seal A.
b) Escribir el programa sin un orden predeterminado de los turnos.
c) Explicar ventajas e inconvenientes de ambas soluciones.
63

SOLUCIN
a) Turnos en anillo Padre -> HijoN -> Hijo N-1... -> Hijo 1 -> Padre... El turno se pasa mediante la seal SIGUSR1.
La invocacin a procesa_peticion() debe estar dentro de la funcin de armado para protegerla de la siguiente llegada
de SIGUSR1.
pid_t p1;
void procesa_peticion(char c);
void f1(int IDsegnal){
char b;
read(0, &b, 1); //LEE PETICIN
kill(p1, SIGUSR1); //PASA EL TURNO
procesa_peticion(b); // PROCESA PETICIN
}
int main(void){
pid_t p;
int i;
struct sigaction act;
act.sa_handler = &f1;
act.sa_flags = 0;
sigaction(SIGUSR1, &act, NULL);

p1 = getpid();
for(i=0; i<N; i++){
switch(p = fork()){
case -1:
perror("fork() = -1"); exit(1);
case 0: //HIJO:
while(1) pause();
default: //PADRE:
p1 = p;
}
}
//PADRE:
kill(p1, SIGUSR1);
while(1) pause();
}

b) Cada proceso simplemente ejecuta un bucle infinito de leer y procesar peticin. As, puede haber varios procesos
compitiendo por leer del mismo fichero (la entrada estndar, heredada por todos los hijos). El SO entrega cada byte
a solo 1 proceso. El orden de turno no est predefinido.
int main(void){
char b;
pid_t p;
int i;
for(i=0; i<N; i++)
if((p = fork()) == -1){
perror("fork() = -1"); exit(1);
}else if(p == 0) //HIJO:
while(1){
read(0, &b, 1);
procesa_peticion(b);
}
//PADRE:
while(1){
read(0, &b, 1);
procesa_peticion(b);
}
}
c)
64 Problemas de sistemas operativos
Programa con turnos predefinidos:
Ventaja: Orden conocido permitira implementar funciones adicionales al servidor. Por ejemplo, cada peti-
cin podra ser procesada con diferente algoritmo segn su orden de llegada.
Inconveniente: Si uno de los procesamientos de peticin tarda mucho, la seal puede dar una vuelta com -
pleta al anillo y el servicio de peticiones se detendra hasta terminar tal procesamiento, habiendo procesos
ociosos que podan haberlo atendido.
Programa con turnos no predefinidos:
Ventaja: Ms eficiente. Procesos no se entorpecen.

Problema 1.29 (julio 2014)

Se dispone de dos versiones de la funcin B:


int B1(int fd, int init, int size);
int B2(char *name, int init, int size);
Dicha funcin recorre secuencialmente el fragmento del fichero que empieza en la posicin init y que tiene un
tamao size, generando un entero que entrega como valor de retorno. En caso de error en el acceso al fichero retor-
na un -1.
Se quiere disear un mandato A que reciba como argumento un nombre de fichero y que genere un proceso hijo
por cada fragmento de 10 KiB del fichero. Cada hijo ejecutar la funcin B sobre su fragmento y enviar al padre
el entero generado, en formato binario y a travs de un pipe. El padre sumar dichos enteros y sacar la suma por
su salida estndar.
a) 1 Indicar las ventajas e inconvenientes que presenta cada versin B1 y B2 para su utilizacin en el mandato
A.
b) 1 Dibujar dos esquemas de procesos. El primero deber indicar la jerarqua de procesos que genera el man-
dato A y el segundo deber indicar el esquema de comunicacin entre los procesos.
c) 1 Explicar qu ocurre si un hijo muere antes de devolver el valor generado. Explicar qu ocurre si el proceso
padre muere antes de que terminen los hijos.
d) 1 Explicar si sera posible que el padre leyese solamente la mitad de los bytes que forman un entero, produ -
ciendo, por tanto, un resultado errneo.
e) 3 Desarrollar el programa A, destacando todos los servicios utilizados.
f) 1 Incluir un mecanismo para evitar que el proceso padre se quede eternamente esperando ante cualquier tipo
de problema que pueda tener alguno de los hijos. Escoja la accin que realizar el padre si se da esta situacin.
g) 2 El programa A se monta estticamente y tiene los siguientes tamaos: text: X bytes, data Y bytes, bss Z by-
tes
Suponga que el tamao de pgina es de 4KiB, que estn en ejecucin el padre y 9 hijos, y que todas las pginas
de todos los procesos han migrado y residen en memoria principal. Desarrolle en funcin de X, Y y Z la expresin
que calcule el total de la memoria principal ocupada por los 10 procesos.
Nota. Suponga que dispone de las funciones siguientes:
Redondeo al ms prximo: int RedProxo(float m);
Redondeo por exceso: int RedEx(float m);
Redondeo por defecto: int RedDef(float m);

Solucin
a) Lo primero que hay que plantearse es que vamos a tener n procesos hijos coutilizando el fichero. Cada uno tiene
que acceder a una parte especfica del fichero, por lo que necesita su propio puntero. Se necesitarn tantas aperturas
de fichero como hijos. Si se utiliza la funcin B2, la apertura la har dicha funcin. Si utilizamos la funcin B1, cada
hijo deber abrir el fichero antes de llamar a la funcin B1. No sirve que el padre abra el fichero y que lo hereden los
hijos, puesto que, en ese caso, se compartira el puntero dando resultados errneos.
65
Por tanto, no hay una gran diferencia entre usar una funcin u otra, salvo el tener que incluir en el cdigo de los
hijos la apertura del fichero.
b) Se generarn RedEx(FileSize / 101024) hijos. En relacin con el esquema de comunicaciones nos podemos pla-
tear un solo pipe para comunicar todos los hijos con el padre o utilizar un pipe por hijo. Es ms sencilla de progra -
mar y utiliza menos recursos la solucin de un solo pipe. En todo caso, es muy importante destacar que los hijos
deben cerrar el descriptor de lectura del pipe y que el padre debe cerrar el descriptor de escritura del pipe o de los pi -
pes de comunicacin con los hijos.
c) Si el hijo muere antes de devolver el valor generado ocurren tres cosas. UNO: El padre recibe una seal SIG -
CHLD, que por defecto es ignorada. DOS: Mientras el padre no haga el correspondiente wait, el hijo queda en esta-
do zombie. TRES: si se hacen las cosas bien, es decir, si se cierran los descriptores no utilizados de los pipes,
cuando el padre lea del pipe recibir 0 bits ledos, lo que le indica que el hijo ha muerto, por lo que deber hacer el
correspondiente wait. Para el caso de un solo pipe esto ocurrir cuando todos los dems hijos hayan completado su
trabajo o hayan muerto. Para el caso de pipe por hijo, esto ocurrir cuando el padre lea de ese pipe.

A A A
fd[0]
fd[0] fd[0] fd[0]

fd[1] fd[1] fd[1] fd[1] fd[1]


fd[1]
H1 H2 Hn H1 H2 Hn H1 H2 Hn

Jerarqua de procesos Esquema de comunicaciones Esquema de comunicaciones


Figura 1.17

Si el padre muere antes que alguno de los hijos ocurren dos cosas. UNO: Los procesos se quedan hurfanos y el
proceso INIT los hereda. DOS: si se hacen las cosas bien no habr lectores en el pipe. Cuando el hijo intente escribir
en el pipe recibir la seal SIGPIPE, por lo que morir (a menos que la tenga tratada).
d) Dado que lo que se manda por el pipe es un entero en formato binario, ocupar sizeof(int) bytes. Los servicios de
escritura y lectura sobre el pipe se harn con ese tamao (por ejemplo: write(pipe[1], &n, sizeof(n)); read(pipe[0],
&n, sizeof(n));. Evidentemente, si hacemos un read o write indicando un tamao menor de sizeof(int) el padre reci-
bir menos bytes. En el supuesto de que el mandato est bien programado no es posible que se produzca esta situa-
cin puesto que las operaciones de pocos bytes sobre un pipe son atmicas, es decir, que se hacen completamente o
no se hacen, no se interrumpen mientras se ejecutan.
e)
#include <stdio.h>

int main(int argc, char *argv[]) {


struct stat buffer;
int tamfich, i, pp[2], n, size, suma = 0;
// Obtenemos la informacin del fichero
if (stat(argv[1], &buffer)<0) {
perror("Error en la llamada stat.\n");
return 1;
}
//Comprobamos si es directorio
if (S_ISDIR(buffer.st_mode)) {
perror("El nombre corresponde a un directorio.\n");
return 1;
}
tamfich = (int) buffer.st_size; // Obtenemos tamao del fichero
size = 10 * 1024; //Tamao particin
pipe(pp);
for (i = 0; i < tamfich; i+= size) {
switch(fork()) {
case -1: // Error
perror("fork()");
66 Problemas de sistemas operativos
return 1;
case 0: // Hijo
close(pp[0]);
if (i + size > tamfich) size = tamfich - i;
if (size != 0) n = B2(argv[1], i, size);
write(pp[1], &n, sizeof(n));
return 0;
default: // Padre
}
}
close(pp[1]);
for (i = 0; i < tamfich; i+= size) {
read(pp[0], &n, sizeof(n));
suma += n;
wait(NULL);
}
close(pp[0]);
write(STDOUT_FILENO, &suma, sizeof(suma));
return 0;
}
f) La forma de evitar que el proceso padre se quede indefinidamente en el ltimo bucle es poniendo un temporiza-
dor. La accin lgica sera sacar un mensaje de error y matar a todos los procesos. Para matar a los hijos se pueden
hacer dos cosas o bien salvar los pid de los hijos y hacer un bucle con kill en la funcin de la alarma o bien hacer
un kill al grupo de procesos. En este ltimo caso, si no queremos que el padre muera este tendra que ignorar la seal
enviada en el kill, por ejemplo con signal(SIGQUIT, SIG_IGN);.
Abra que aadir el siguiente cdigo en el main:
//Incluir en las declaraciones
struct sigaction act;

//Incluir justo antes o despus del close(pp[1]);


//Se prepara para recibir la seal SIGALRM
act.sa_handler = AccionTempor;
act.sa_flags = 0;
sigaction(SIGALRM, &act, NULL);
alarm(120); //Se esperan 2 minutos
Y aadir la siguiente funcin

void AccionTempor(void) {
pid_t parent_pid;
perror ("Error de temporizacin.\n")
parent_pid = getpid();
kill(-parent_pid, SIGQUIT); //Se matan todos los procesos del grupo parent_pid (padre e hijos)
return 0;
}

g) Suponiendo que los datos con valor inicial, los datos sin valor incial y el heap forman una sola regin, el tamao
en pginas de la imagen de memoria de cada proceso es el siguiente:
Texto = RedEx(X / 41024).
Datos = RedEx((Y+Z+H) / 41024), DCVI + DSVI + Heap expresados todos ellos en bytes.
Pila = Pila expresada en pginas.
Ahora bien, el Texto es compartido, por lo tanto, suponiendo que el tamao del heap y de la pila es el mismo en
todos los procesos, la ocupacin en memoria es:
4(Texto + 10(Datos + Pila)) KiB
De acuerdo a los prototipo de las funciones B no tiene sentido plantear que estas hagan proyeccin de memoria.
En caso de utilizar el mecanismo de proyeccin de memoria, sta la debera hacer el padre de todo el fichero y en
modo compartido. En esta caso los argumentos de B deberan ser direccin de memoria de comienzo de su zona y
tamao.
2
SISTEMA DE FICHEROS

Problema 2.1 (septiembre 1998)

Dado un sistema con memoria virtual y con preasignacin de swap, y con pginas y bloques de 4 KiB. Sea el cdi-
go adjunto, que pertenece al proceso 345, con UID real = 34.
if (fork () == 0)
execl ("/doc/edit.exe", "/doc/edit.exe", "/doc/pri1.txt", NULL);
else
wait (NULL);
a) Indicar en detalle las operaciones que realiza el SO al ejecutar el cdigo anterior, suponiendo que no se pro -
ducen errores en las llamadas.
b) Especificar qu estructuras de informacin se utilizan, indicando las modificaciones que se realizan sobre
ellas.
c) Suponiendo que el programa edit debe acceder en lectura-escritura al fichero cuyo nombre se suministra
como parmetro, indicar qu servicio debe solicitar y justificar si se realizar con xito.
Algunas de las estructuras del sistema son las mostradas en la figura 2.1 y en las tablas 2.1 y 2.2.

Nodo-i Dueo (UID) Permisos Tipo Agrupaciones Tamao SUID, SGID


2 Root (0) rwx r-x r-x directorio 7644 4 KiB
23 Pepa (34) rwx r-x r-x directorio 173 4 KiB
87 Root (0) rws --x --x archivo 79752 y 79753 7,5 KiB Activo SUID
273 Root (0) rwx r-x r-x directorio 7635 4 KiB
324 Tere (622) rwx --- --- directorio 23 4 KiB
753 Juana (62) rwx --- --- archivo 4546, 3874 y 22993 9,2 KiB
823 Root (0) rw- --- --- archivo 12764 3 KiB
876 Pepa (34) rwx --- --- directorio 453 4 KiB
Tabla 2.1 Contenido de algunos nodos-i

Tabla de procesos
BCP 1 BCP 2 BCP 3 BCP 4 BCP 5 BCP 6 BCP 7 BCP 8

Proceso Proceso Proceso Libre Proceso Libre Libre Libre


497 345 25 47
67

Figura 2.1
68 Problemas de sistemas operativos

Agrupacin Contenido
56 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 .......
443 qwei oier oeiru qoieruower qpoweioru qpwoeir....
453 . 876
.. 2
453 iue 8234
569 1234567890...r
4413 abcdefghijklme.....
7635 . 273
.. 2
7635 pri1.txt 823
edit.exe 87
7644 . 2
.. 2
doc 273
Proa 876
Prob 23
12764 ieqo ieoqo eir93o3hnf wqn34209e rn349 wheew.....
79752 ABCDEFGHIJKLM.......
Tabla 2.2 Contenido de algunas agrupaciones

Solucin PM
Las operaciones que realiza el sistema operativo, suponiendo que no hay errores en las llamadas, son las siguientes:
1.- fork crea un proceso hijo idntico al proceso padre. Devuelve al padre el pid del hijo y al hijo un
cero. Para ello, el S.O. busca una entrada libre en la tabla de procesos y se la asigna al nuevo proceso.
En este caso, se elige BCP4. Sobre dicha entrada se copia la del proceso padre (pid 345) situada en
BCP2. Sobre BCP4 se escribe el pid del hijo. La tabla de ficheros no debe ser modificada porque son
compartidos. A continuacin, el S.O. crea una tabla de pginas para el proceso hijo. Dicha tabla es una
copia idntica de la del padre, con las pginas de datos y pila marcadas como de copy-on-write en la ta-
bla del padre. Cuando ejecuta el hijo, se hace la llamada al sistema exec.
2.- exec Para ejecutar el mandato edit.exe, en primer lugar comprueba si el fichero con el cdigo del
mandato tiene permisos de lectura. Para acceder al fichero se parte del directorio raz / con nodo-i 2. Se
solicita dicho nodo-i (que habitualmente est en memoria) y se trae el primer bloque de disco de la agru -
pacin 7644 y se encuentra la entrada al directorio doc. En este caso, como el UID real del usuario es 34
(Pepa), se comprueba el acceso del directorio doc (nodo-i 273) y se ve que tiene permisos para acceder al
mismo (rwxr-xr-x). Dicha entrada nos remite al nodo-i 273 que est en la agrupacin 7635. Se trae el pri-
mer bloque de dicha agrupacin bloques de dicha agrupacin hasta que se encuentra la entrada de edi-
t.exe, que nos remite al nodo-i 87. Se trae a memoria dicho nodo-i y se ve que todo el mundo puede
ejecutarlo (rwx--x--x) y que adems tiene el SUID activo, con lo que Pepa pasa a tener el UID efectivo 0
(superusuario). A continuacin se comprueba que se puede acceder al fichero pri1.txt. Dicho fichero est
en la misma agrupacin que edit.exe (7635), por lo que se trae a memoria su nodo-i y se comprueban los
permisos de acceso. En este caso tiene rwx------, pero se puede acceder porque Pepa tiene el UID efecti-
vo del superusuario. Una vez comprobados los permisos de acceso al fichero, se vaca la tabla de pgi -
nas del proceso hijo, se trae al swap el fichero con el cdigo del mandato (preasignacin de swap), se
actualiza la tabla de pginas y se elimina el flag de COW en la tabla del padre. A continuacin empieza la
ejecucin preasignando una pgina nueva para datos y pila del nuevo mandato edit.exe. En este momen-
to, ambos procesos solamente comparten la tabla de ficheros. Tambin se carga en memoria los primeros
4 KiB del fichero pri1.txt, que estn en la agrupacin 12764.
3.- wait El proceso padre se queda esperando hasta que el hijo termina, evento que le comunica el S.O.
con una seal.
Las estructuras de datos utilizadas son:
69
Tabla de procesos. Copia de BCP2 sobre BCP4 y modificacin para introducir los datos del hijo, de su
nueva imagen de memoria cuando se hace el exec y de la entrada al fichero pri1.txt cuando se abre.
Tabla de pginas del proceso padre para activar y desactivar el COW.
Tabla de pginas del proceso hijo para cambiar las entradas de las pginas por las del nuevo ejecutable
edit.exe.
Mapa de bloques libres del swap para asignar las pginas del nuevo proceso.
Nodos-i de los ficheros compartidos para indicar que el nmero de procesos que los tienen abiertos es 2.
Para que el programa edit acceda en lectura-escritura el fichero, debe solicitar el siguiente servicio:
open(/doc/pri1.txt,O_RDWR);
La solicitud del servicio se realiza con xito si el fichero existe y si los permisos de acceso son los adecuados.
Como ya hemos visto anteriormente, cuando se ejecuta edit.exe se adquiere el UID efectivo del superusuario (0),
por lo que el directorio /doc, con permisos rwxr-xr-x, es accesible y el fichero pri1.txt, con permisos rw-------,
puede ser accedido con xito por Pepa. Por tanto, el servicio ser efectuado sin problemas.

Problema 2.2
Sean los cdigos programa 1 y programa 2 que ejecutar el usuario jperez
/* Programa 1 */
int main (void) {
int fd; int pid; int cont;

fd = open ("/alfa", O_RDWR);


cont = write(fd, "Escribo alfa", strlen("Escribo alfa"));
close(fd);
fd = open ("/beta/b4/tyz", O_RDWR);
lseek(fd, 13,SEEK_SET);
cont = write(fd, "del ejemplo", strlen("del ejemplo"));
pid = fork();
/* PUNTO A */
switch (pid) {
case -1:
break;
case 0: /* hijo */
cont = write(fd, "del hijo", strlen("del hijo"));
/* PUNTO C */
break;
default: /* padre */
cont = write(fd, "del padre", strlen("del padre"));
/* PUNTO D */
}
}

/* Programa 2 */
int main (void) {
int fd; int cont;
fd = open ("/beta/b4/tyz", O_RDWR);
cont = write(fd, "Proceso 2 escribe", strlen("Proceso 2 escribe"));
/* PUNTO B */
close(fd);
}
Suponiendo que el orden de ejecucin es el siguiente:
Proceso padre con cdigo programa 1 ejecuta hasta el punto A
El proceso con cdigo programa 2 ejecuta hasta el punto B
El proceso hijo con cdigo programa 1 ejecuta hasta el punto C
Vuelve a ejecutar el padre hasta el punto D
Y que los contenidos de algunas agrupaciones y nodos-i son los mostrados en las tablas adjuntas, se pide:
70 Problemas de sistemas operativos
a) Establecer el esquema de directorios.
b) Indicar el tamao de la agrupacin
c) Indicar, para cada uno de los procesos, los valores devueltos por cada uno de los servicios del SO que eje-
cuta.
d) Indicar el contenido del fichero /alfa en el punto A y el contenido del fichero /beta/b4/tyz en los puntos A, B y
C.
Agrupacin Contenido
32 . 2; .. 2; usr 121; alfa 243; bi 453; beta 54
73 . 417; .. 453 .....
74 . 628; .. 54; abc 742; tyz 375
112 . 412; .. 453 .....
138 . 453; .. 2; bi 430; apli1 412; apli2 417
255 Cmo se aplica la seccin 3.3 del plan de seguridad .....
271 . 121; .. 2; prof1 301; prof2 317; prof3 371
1300 . 301; .. 121 .....
2342 CALLE SANDOVAL, 3 ......
3207 . 430; .. 453 .....
3765 Desde el punto de vista del usuario, la tabla Panel ....
4731 El usuario jramirez est .......
6734 . 54; .. 2: b1 621; b2 612; b3 614; b4 628
6777 En el esquema correspondiente se representan estas lneas .....
15301 Para el ejemplo de prueba se han propuesto 5 casos que ....

Nodo-i Dueo (UID) Permisos Tipo Agrupaciones Tamao SUID, SGID


2 Root (0) rwx r-x r-x directorio 32 180
54 Root (0) rwx r-x r-x directorio 6734 180
121 Root (0) rwx r-x r-x directorio 271 150
243 Root (0) rwx --- --- usuario 4731 653
301 jperez rwx r-x r-x directorio 1300 300
317 lgomez rw- r-- r-- usuario 255 y 257 1568
371 rfung rw- r-- r-- usuario 15301 a 15305 4354
375 jperez rw- r-- r-- usuario 6777 a 6779 2564
412 Root (0) rwx r-x r-x directorio 112 120
417 Root (0) rwx r-x r-x directorio 73 180
430 Root (0) rwx r-x r-x directorio 3207 330
453 Root (0) rwx r-x r-x directorio 138 150
612 lgomez rw- r-- r-- usuario 3765 473
614 lgomez rw- r-- r-- usuario 4321 y 486 1324
621 lgomez rw- r-- r-- usuario 2342 875
628 jperez rwx r-x r-x directorio 74 120
742 jperez rw- r-- r-- usuario 5701 a 5708 7432

Solucin
a) El directorio raz tiene . y .. apuntando a s mismo. El esquema de directorio es el siguiente:

/ |Usr |prof1
|prof2
|prof3
|alfa
71
|bi |bi
|apli1
|apli2
|beta |b1
|b2
|b3
|b4 |abc
|tyz
b) El tamao de la agrupacin ha de ser un mltiplo del tamao del sector, es decir nxTsector, donde n es general -
mente una potencia de dos. Por otro lado, el sector tiene m bytes, siendo m una potencia de dos. Por tanto, el tamao
de la agrupacin ha de ser una potencia de dos.
Viendo el nmero de agrupaciones que tienen los distintos ficheros, y comparando este nmero con el correspon-
diente tamao del fichero, se deduce fcilmente que el tamao de la agrupacin es de 1 KiB. Por ejemplo, el fichero
de nodo_i 317 tiene dos agrupaciones y ocupa 1.568 B. Dos agrupaciones de 1 KiB permiten tamaos de fichero en-
tre 1.025 y 2.048 B.
c) Los valores devueltos son los que se indican seguidamente. Son de destacar los siguientes extremos:
El usuario jperez no puede abrir el fichero alfa, por lo que los tres primeros servicios devuelven error.
El valor devuelto al padre por el fork puede ser un -1 en caso de que el SO no pueda crear al hijo.
Los blancos tambin son caracteres que ocupan su byte.
Los valores devueltos por el servicio open dependen de la ocupacin de la tabla de fd. Se ha supuesto
que solamente estn abiertas la entrada y salidas estndar. El open puede devolver un -1 en caso de que
est llena la tabla de fd. Sin embargo, los write no `pueden devolver error, puesto que se sobreescribe en
espacio ya asignado (solamente se podra dar error en caso de avera del controlador o del disco).

int main (void) {


int fd; int pid; int cont;

-1 error fd = open ("/alfa", O_RDWR);


-1 cont = write(fd, "Escribo alfa", strlen("Escribo alfa"));
-1 close(fd);
3 fd = open ("/beta/b4/tyz", O_RDWR);
13 lseek(fd, 13,SEEK_SET);
11 cont = write(fd, "del ejemplo", strlen("del ejemplo"));
0 y n pid = fork();
/* PUNTO A */
switch (pid) {
case -1:
break;
case 0: /* hijo */
8 cont = write(fd, "del hijo", strlen("del hijo"));
/* PUNTO C */
break;
default: /* padre */
9 cont = write(fd, "del padre", strlen("del padre"));
/* PUNTO D */
}
}

int main (void) {


int fd; int cont;
3 fd = open ("/beta/b4/tyz", O_RDWR);
17 cont = write(fd, "Proceso 2 escribe", strlen("Proceso 2 escribe"));
/* PUNTO B */
0 close(fd);
}
d) El fichero alfa no cambia, puesto que no se puede abrir.
El fichero tyz sufre las siguientes modificaciones:
Inicialmente: En el esquema correspondiente se representan estas lneas....
Punto A: En el esquemadel ejemploiente se representan estas lneas ....
72 Problemas de sistemas operativos
Punto B: Proceso 2 escribeejemploiente se representan estas lneas
Punto C: Proceso 2 escribeejemplodel hijo representan estas lneas
Punto D: Proceso 2 escribeejemplodel hijodel padretan estas lneas ...

Problema 2.3 (septiembre 2000)

Dado un disco de 4 GiBytes con tamao de bloque de 1 KiB se quieren analizar los dos siguientes sistemas de fiche-
ros:
1.- Sistema de ficheros tipo UNIX con las siguientes caractersticas:
Representacin del fichero mediante nodos-i con 10 direcciones directas a bloque, un indirecto simple,
un indirecto doble y un indirecto triple y direcciones de bloque de 4 bytes. El sistema utiliza un mapa de
bits para la gestin del espacio vaco.
2.- Sistema de ficheros tipo MS-DOS (FAT) con las siguientes caractersticas:
Entradas de 4 bytes y tamao de agrupaciones de 4 bloques.
Se pide:
a) Cul es el tamao mximo de los ficheros en cada sistema de ficheros?
b) Qu tamao ocupan la FAT y el mapa de bits en cada caso?
c) Se desea abrir un fichero llamado datos.txt que se encuentra en el directorio user y acceder a los bytes
273.780.000 y 281.450.500. Si nos encontramos en el directorio raz, cul ser el nmero de accesos al dis-
co para realizar la anterior operacin en cada sistema de ficheros?
d) Dnde se almacenan los atributos del fichero en cada sistema de ficheros?. Qu problemas puede presen-
tar este sistema de atributos en MS-DOS?

Solucin
a) UNIX:
10 bloques directos a bloque
1024/4 bloques con indireccin simple
(1024/4)^2 bloques con indireccin doble
(1024/4)^3 bloques con indireccin triple
Tamao mximo de fichero:
(10 + 256 + 256^2 + 256^3) bloques * 1024 Bytes/bloque = 16 GiB
La mxima longitud de fichero que podra ser alcanzada con este esquema es de aproximadamente 16
GiBytes.
MS-DOS: Usando la FAT descrita en el enunciado el tamao mximo de fichero viene determinado por el tama-
o de disco, 4 GiB, ya que la FAT es simplemente una lista enlazada y el direccionamiento especificado es suficiente
para apuntar a todas las agrupaciones.
b) MS-DOS: Tenemos un disco con 4 GiB con 1 KiB de tamao de bloque y 4 KiB de tamao de agrupacin. De
esta forma en la FAT se necesita 1 Mega entradas. Cada entrada de la FAT apunta a una agrupacin y tiene un tama-
o de 4 B, por lo que el tamao total de la FAT es de 4 MiB.
UNIX: El mapa de bits incluye 1 bit para cada recurso existe, es decir, un bit por cada bloque de disco o nodo-i.
En total se tienen que controlar 4 Mega bloques por lo que son necesarios 512 bloques para el mapa de bits, es decir
512 KiB.
Nota: hay que tener en cuenta que en MS-DOS se ha direccionado a nivel de agrupacin y en UNIX a nivel de
bloque.
c) MS-DOS: Supondremos el fichero est muy disperso en la FAT, de forma que cada acceso a ella exige un acceso
al disco. Adems, consideraremos que el sistema mantiene el valor de la agrupacin accedida y su posicin de pun -
tero, de forma que accesos sucesivos no requieran volver a recorrer toda la FAT. Para poder acceder al fichero se ne -
cesitan los siguientes accesos a disco:
1 lectura del directorio raz para localizar el directorio user
73
Supondremos que el directorio user cabe en un bloque. Har falta un acceso a la FAT para conocer la ubi-
cacin del directorio y 1 lectura del bloque que ocupa el directorio user para localizar el fichero datos.txt,
as como su primera agrupacin (agrupacin 0).
Una vez localizado el fichero son necesarios los siguientes accesos:
Los bytes a leer se encuentran en las agrupaciones 66.840 y 68.713. Por tanto, se necesitan 68.713 acce -
sos a la FAT para localizar las agrupaciones ms 2 accesos para leer los datos pedidos de disco.
En total 68.718 accesos.
UNIX: Para acceder al fichero:
Traer el bloque / a memoria y buscar la entrada /user
Traer el nodo-i de /user a memoria
Traer un bloque de /user a memoria y buscar la entrada de datos.txt
Traer el nodo-i de datos.txt a memoria
Por tanto para acceder al fichero necesitamos 4 accesos a memoria.
Para leer los datos hay que acceder a los bloques 32500 * 4 = 130000 y 32750 * 4 = 131000
Para direccionar estos bloques necesitamos los punteros de triple indireccin del nodo-i (con los punteros
de doble indireccin se llega al bloque: 10+256+256*256= 65802). Accesos requeridos:
3 para acceder a los 3 niveles de indireccin
1 para acceder al bloque donde se encuentra el primer byte
3 para acceder a los 3 niveles de indireccin
1 para acceder al bloque donde se encuentra el segundo byte
Total 12 accesos.
d) En MS-DOS los atributos se guardan en la entrada del directorio, mientras que en UNIX se almacenan en el no -
do-i.
Los problemas que se presentan en MS-DOS son que al modificar los atributos hay que acceder a la informacin
de los directorios y la complicacin que supone el crear enlaces a archivos.

Problema 2.4 (junio 2001)

Dado el siguiente cdigo:


1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>

6 #define BUFFSIZE 27

7 int main (int argc, char *argv[]) {


8 int miFd, miFd2, miFd3, pid;
9 char buff[BUFFSIZE]="abcdefghijklmnopqrstuvwxyz";
10 char buff2[10], buff3[10];
11 int pos1, pos2, pos3;

12 if (argc != 3) {
13 fprintf(stderr,"Error al introducir argumentos");
14 exit(1); }

15 miFd= open(argv[1],O_CREAT|O_TRUNC|O_RDWR, 0700);

16 pid = fork();
17 switch (pid) {
18 case -1: perror("fork");
19 exit(2);
20 case 0: //proceso hijo
21 write(miFd,buff,BUFFSIZE);
22 link(argv[1],argv[2]);
23 miFd2=open(argv[2],O_RDWR);
74 Problemas de sistemas operativos
24 miFd3=dup(miFd);
25 pos1=lseek(miFd,4,SEEK_SET);
26 read(miFd2,buff2,10);
27 read(miFd3,buff3,10);
28 pos2=lseek(miFd2,0,SEEK_END);
29 write(miFd2,buff2,10);
30 write(miFd3,buff3,10);
31 pos3=lseek(miFd3,0,SEEK_CUR);
32 close(miFd);
33 close(miFd2);
34 close(miFd3);
35 return 0;
36 default:
37 write(miFd,buff,BUFFSIZE);
38 close(miFd);
39 return 0;
40 }
41 }
Resolver las siguientes cuestiones:
a) Explicar el cdigo, dibujando la relacin existente entre los distintos descriptores y ficheros para los proce-
sos involucrados. Dibujar los nodos-i de los ficheros con los campos ms significativos.
b) En qu consisten los argumentos que recibe el programa principal? Teniendo en cuenta las llamadas al sis-
tema que se realizan en el cdigo, qu restricciones deben cumplir dichos argumentos para que la ejecu -
cin sea correcta?
c) Cul es el contenido final de los ficheros involucrados? Depende del orden de ejecucin de los procesos
padre e hijo? Si es as, indicar el contenido de los ficheros suponiendo que el proceso padre se ejecuta de
forma completa antes que el proceso hijo.
d) Cules son los valores que reciben las variables pos1, pos2 y pos3 en las lneas 25, 28 y 31? Suponer
que el proceso padre se ha ejecutado de forma completa antes de que comience a ejecutar el proceso hijo.
e) Dibujar un diagrama indicando cmo se modifican las entradas de directorios y nodos-i correspondientes
cuando se lleva a cabo la llamada al sistema link() (lnea 22).
f) Sera equivalente sustituir la lnea 15 por:
int miFd=creat(argv[1],0700); ?
Si no lo es, explicar cmo se ve modificada la ejecucin del programa.
g) Utilizar un mecanismo de sincronizacin para lograr que el proceso padre realice todas las operaciones an-
tes de que el proceso hijo realice cualquier operacin de escritura.

Solucin
a) El programa principal recibe dos argumentos, correspondientes a nombres de ficheros. Lo primero que se hace en
el cdigo es comprobar que el nmero de argumentos es correcto (lneas 12 a 14). A continuacin, se crea el fichero
pasado como primer argumento, abrindolo en modo lectura/escritura (lnea 15). El proceso crea un proceso hijo
(llamada al sistema fork()). Se comprueba que la llamada ha sido correcta. El cdigo que ejecuta el hijo se en-
cuentra entre las lneas 21 y 25. El cdigo del padre es el cdigo situado entre las lneas 37 y 39. Padre e hijo com -
parten el descriptor de fichero fd.
El proceso padre escribe en el fichero el buffer buff, que contiene una cadena de caracteres con las letras del
abecedario. A continuacin, cierra el descriptor correspondiente y finaliza.
Por su parte, el proceso hijo tambin escribe en dicho fichero el buffer buff. A continuacin crea un enlace fsi-
co al primer fichero, usando como nombre el segundo argumento (lnea 22). Por tanto, ambos nombres referencian
al mismo fichero. Se abre el fichero correspondiente al segundo fichero, asociandole el descriptor miFd2. En la l-
nea 24, se duplica el descriptor miFd y el descriptor duplicado se recoge en la variable miFd3. A continuacin, se
posiciona el fichero en el cuarto byte, utilizando el descriptor miFd. Los descriptores miFd y miFd3 comparten
puntero, frente al descriptor miFd2, que no lo comparte. Se leen 10 bytes del fichero a travs del descriptor miFd2
y se almacena en buff2. A continuacin, se leen otros 10 bytes del fichero a travs del descriptor miFd3 y se al-
macena en buff3. Se posiciona el fichero al final del mismo, utilizando el descriptor miFd2. Posteriormente, se
75
escriben 10 bytes situados en buff2 en el fichero a travs del descriptor miFd2 y otros 10 bytes situados en bu-
ff3 a travs del descriptor miFd3. En la lnea 31 el puntero no se ve modificado, ya que se posiciona en el lugar
actual. A continuacin se cierran todos los descriptores y el proceso hijo finaliza.
Slo hay un nodo-i involucrado, ya que se trata de un enlace fsico. La representacin grfica de las entradas de
directorio correspondientes y el nodo-i se encuentra en la figura 2.2, suponiendo que se ha ejecutado el programa
con los argumentos texto.txt y texto2.txt:
Directorio donde est texto.txt Directorio donde est texto2.txt
. 22 . 24
.. 13 .. 15
texto.txt 8 texto2.txt 8
<<otros> <<otros>> <<otros> <<otros>>

Tipo Fich. = Normal


Num. Enlaces = 2
Fechas del Fichero
nodo_i 8 .....................
Enlaces directos a bloques
Enlaces indirectos simples a bloques
Enlaces indirectos dobles a bloques
Enlaces indirectos triples a bloques
Figura 2.2

Suponemos que el fichero no tena ningn enlace inicialmente.


b) Los argumentos que recibe el programa principal son los dos nombres del fichero. Las restricciones que deben
cumplir ambos argumentos vienen determinadas por las llamadas al sistema open() y link(): ambos argumen-
tos deben corresponder a nombres de ficheros normales (no directorios existentes) y adems ambos ficheros deben
situarse en un mismo volumen. Por otro lado, se deben tener los permisos adecuados sobre el directorio en el cul se
van a crear. Adems, si ya existe un fichero con el nombre correspondiente al primer argumento, ste debe tener per-
misos de lectura/escritura. No debe existir ya un fichero con un nombre igual al segundo argumento.
c) El contenido de ambos ficheros es el mismo, porque de hecho se trata del mismo fichero, pero con dos nombres
diferentes. El contenido del mismo depende del orden de ejecucin de los procesos padre e hijo. Si el proceso padre
se ejecuta de forma completa antes que el hijo, el contenido final del fichero ser igual a:
abcdefghijklmnefghijklmnyz/0abcdefghijklmnopqrstuvwxyz/0abcdefghij (Los caracteres /0 corresponden
a caracteres nulos, debidos a las escrituras de una cadena de caracteres)
d) Teniendo en cuenta que los descriptores miFd y miFd3 comparten puntero, mientras que el descriptor miFd2 no
lo comparte, los valores que reciben las variables pos1, pos2 y pos3 son:
pos1 = 4
pos2 = 54
pos3 = 24
e) La situacin antes de la llamada al sistema link()(suponiendo que el fichero no tuviera ms de un enlace) se
encuentra en al figura 2.3:
Directorio donde est texto.txt
nodo_i 8
. 22
.. 13 Tipo Fich. = Normal
texto.txt 8 Num. Enlaces = 1
<<otros> <<otros>> Fechas del Fichero
.....................
Enlaces directos a bloques
Enlaces indirectos simples a bloques
Enlaces indirectos dobles a bloques
Enlaces indirectos triples a bloques
Figura 2.3

La situacin despus de realizar la llamada al sistema link() se encuentra en la figura 2.2.


f) No es equivalente. La llamada creat(argv[1],0700) equivaldra a:
open(argv[1],O_CREAT|O_TRUNC|O_WRONLY,0700). Si se utilizara la llamada creat() no se podra
leer del fichero correspondiente y las llamada read() de la lnea 27 dara un error, ya que miFd y miFd3 compar-
ten modos de acceso (llamada dup); no as la llamada read() de la lnea 26, ya que en este caso el fichero se ha
abierto en modo lectura/escritura.
76 Problemas de sistemas operativos
g) Podemos utilizar cualquier mecanismo de sincronizacin para lograr que el proceso padre realice todas las opera-
ciones antes de que el proceso hijo realice cualquier operacin de escritura. Si utilizamos un pipe, la solucin sera
la siguiente:
int pp[2]; char testigo;
pipe(pp);

20 case 0: /* proceso hijo */
21 read(pp[0],&testigo,1);
22 close(pp[0]);
23 close(pp[1]);
24 /* resto proceso hijo */
.............
36 default: /* proceso padre */
37 write(miFd,buff,BUFFSIZE);
38 close(miFd);
39 write(pp[1],&testigo,1);
40 close(pp[0]);
41 close(pp[1]);
42 exit(0);
43 }
44 }
Falta realizar el tratamiento de error correspondiente a las llamadas al sistema.

Problema 2.5 (septiembre 2001)

Bloque Agrupacin CONTENIDO


0 BOOT
1 SUPERBLOQUE
2 001011110000111000..00000
3 011100100100101011..00000
4
5
6
7 NODOS-I DEL SISTEMA
8
....
600
601 0
602 1
603 2
604 3
605 4
606 5
607 6 AGRUPACIONES DE DATOS Y DIRECTORIOS
608 7
... ...
... ...
... ...
4095 3494
Tabla 2.3
La tabla 2.3 muestra el esquema de un microsistema de ficheros tipo UNIX, que est simulado sobre memoria. En l
se pueden distinguir seis partes: el bloque de boot, el superbloque, el mapa de bits para los nodos-i del sistema, el
mapa de bits para las agrupaciones de datos, los nodos-i (uno por cada entrada de la tabla) y finalmente las agru-
paciones de datos y directorios. Todos los bloques de disco simulados aparecen numerados y tienen un tamao de
256 bytes. Ntese que las agrupaciones son de un bloque.
77
Bloque de BOOT: Ocupa el bloque 0. No es utilizado en el problema.
Superbloque: Ocupa el bloque 1. No es utilizado en el problema.
Mapa de bits para los nodos-i del sistema: Ocupa el bloque 2. Cada bit representa el estado de un nodo-i del
sistema: 0 significa que el nodo-i est libre y 1 implica que el nodo-i est ocupado. En la figura se ha repre-
sentado un posible estado del mapa de bits.
Mapa de bits para los bloques de datos: Ocupa los bloques 3 y 4. Cada bit representa el estado de un bloque
de datos: 0 significa que el bloque est libre y 1 implica que el bloque est ocupado. En la figura se ha repre-
sentado un posible estado del mapa de bits.
Nodos-i del sistema: En este rea se almacenan los nodos-i del sistema. Este rea ocupa 596 bloques (blo -
ques 5 a 600, ambos inclusive).
Agrupaciones de datos y directorios: en este rea se almacenan los bloques de datos y directorios. Ocupa
3495 bloques (desde el bloque 601 hasta el 4095, ambos inclusive).
Responder razonadamente a las siguientes preguntas:
a) Calcular el espacio que puede ocupar un nodo-i en este sistema, considerando que la representacin del fi-
chero se realiza mediante nodos-i con 10 direcciones directas a bloque, un indirecto simple, un indirecto do -
ble y un indirecto triple. Comprobar cuantos nodo-i caben en un bloque del sistema.
b) Tamao mximo de un fichero en este sistema.
c) Nmero mximo de ficheros del sistema.
d) Por qu los bloques de datos y los directorios aparecen mezclados en un sistema de ficheros tipo UNIX?
e) Nmero mximo de caracteres que puede tener el nombre de un fichero de directorio en este sistema, supo-
niendo que cada entrada de directorio es de tamao fijo y ocupa un bloque.
f) Rellenar la tabla que representa el sistema en el caso de que se tenga la estructura de ficheros de la figura
2.4.
Dir. raiz

users mnt

prg otro
Figura 2.4

donde los ficheros ordinarios aparecen encuadrados. El fichero otro contiene las 10 primeras letras del abe-
cedario. Los nodos-i y los bloques de datos y directorios se pueden rellenar de forma esquemtica.
g) Se pueden utilizar ficheros para llevar a cabo la comunicacin de procesos? Es aconsejable? Por qu?

Solucin
a) Los atributos son los siguientes, para los cuales se ha supuesto un tamao adecuado para un microsistema de fi-
cheros.
Atributo Tamao en B Comentario
Identificador nico del fichero 2 Permite direcciar 64 Ki ficheros
Tipo de fichero 1
Dueo y grupo 4 Dueo y grupo de 16 bits.
Informacin de proteccin 2
Tamao real en bytes 3 Permite ficheros de hasta 16 MiB
Hora y fecha de creacin 4
Hora y fecha del ltimo acceso 4
Hora y fecha de la ltima modificacin 4
Nmero de enlaces (nmero de nombres) 1 Permite hasta 256 enlaces por fichero
Total de 25
78 Problemas de sistemas operativos
Adems, tenemos 13 punteros (10 punteros directos, 1 puntero indirecto simple, 1 puntero indirecto doble y 1
puntero indirecto triple). Hay que calcular lo que ocupa 1 puntero. El mapa de bits sirve para 256 8 2 = 4.096
agrupaciones, que son ms de las 3.495 de las que consta el sistema.
Para direccionar esas 3.495 agrupaciones, necesitamos 12 bits, que permite hasta 4.096 agrupaciones. Sin embar-
go, dado que el servidor de ficheros que maje este sistema tambin debera poder soportar implementaciones con
ms capacidad, supondremos direcciones de 16 bits. Lo que permitira tener 64 Ki agrupaciones, es decir, un sistema
de ficheros con 64 Ki 256 B = 16 MiB de espacio para datos y directorios.
Un nodo-i ocupa: 2 B 13 punteros + 25 B = 51 B. Por lo que caben 256 / 51 = 5 nodos-i por bloque.
Como tenemos 596 bloques para nodos-i, podemos tener hasta 596 5 = 2.980 ficheros. Esta cantidad parece
adecuada ya que tenemos solamente 3.495 agrupaciones.
b) El mximo tamao del fichero puede venir limitado por varias rozones.
Dado que el atributo tamao tiene 3 B, significa que el tamao mximo del fichero es de 16 MiB.
El tamao mximo de un fichero viene determinado por la estructura del nodo-i y segn dicha estructura,
la expresin para calcularla es la siguiente:
Tam. Max = Tam(punt. directos) + Tam(punt. indir. simple) + Tam(punt. indir. doble) + Tam(punt. indir.
triple) = (10 + (Sb/n) + (Sb/n)2 + (Sb/n)3) Sb
Donde Sb es el tamao de una agrupacin y n es lo que ocupa la direccin de una agrupacin, que hemos
establecido en el apartado anterior en 2 B.
De la expresin anterior, se deduce que Tam. Max = 2.113.674 agrupaciones = 541.100.544 B 516
MiB.
Hemos dedicado 16 bits para direccionar las agrupaciones, por lo que no podemos tener ms de 64 Ki
agrupaciones, lo que da una capacidad de 64Ki 265 = 16 MiB. El fichero sera algo menor, puesto que
hay que descontar las agrupaciones necesarias para directorios y para punteros indirectos.
Finalmente, si las 3.495 agrupaciones disponibles se asignan a un solo fichero este tendra 3.466 agrupa-
ciones (3.495 menos una para el directorio raz y 28 agrupaciones para los punteros indirectos), es decir,
3.466 256 = 887.296 B = 866,5 KiB.
De estos cuatro lmites el menor es el debido al espacio fsico de almacenamiento, lo que indica que el mayor fi -
chero ser de 866,5 KiB.
c) El nmero mximo de ficheros viene limitado por el nmero de nodos-i, que se ha indicado anteriormente que es
de 2.980 ficheros.
d) Los directorios son ficheros ordinarios que contienen un registro por cada entrada del directorio. Este registro tie -
ne la siguiente informacin: (nombre, num. nodo-i)
e) Nos indican que cada entrada del directorio ocupa un bloque de 256 bytes. Hay que calcular el nmero de bytes
necesarios para representar un nodo-i. Necesitamos 12 bits. Sin embargo, por las consideraciones que hemos indica-
do en el apartado a), dedicaremos 2 bytes para representar el nodo-i. Luego, nos restan 254 bytes para representar el
nombre.
f) Supongamos que los nodos-i de nuestro sistema son los siguientes (los nodos-i 0 y 1 estn ocupados por el siste -
ma) (vase figura 2.5).
Dir. raiz Nodo-i 2

users Nodo-i 3 mnt Nodo-i 4

prg Nodo-i 5 otro Nodo-i 6


Figura 2.5

Entonces, la tabla de la figura podra quedar como se indica en la tabla 2.4.


79

Bloque Agrupacin CONTENIDO


0 BOOT
1 SUPERBLOQUE
2 111111100000000000..00000
3 111111111111000000..00000
4
5
6
7 NODOS-I DEL SISTEMA
8
....
600
601 0
602 1
603 2
604 3
605 4
606 5
607 6 AGRUPACIONES DE DATOS Y DIRECTO-
608 7 RIOS
... ...
... ...
... ...
4095 3494
Tabla 2.4
Nodos-i:
Nodo-i 2: Agrupaciones 0, 1, 2 y 3
Nodo-i 3: Agrupaciones 4, 5 y 6
Nodo-i 4: Agrupaciones 7, 8 y 9
Nodo-i 5: Agrupaciones 10 y 11
Nodo-i 6: Agrupacin 12
Agrupaciones de datos y directorios:
0 . 2
1 .. 2
2 users 3
3 mnt 4
4 . 3
5 .. 2
6 prg 5
7 . 4
8 .. 2
9 otro 6
10 . 5
11 .. 3
12 ABCDEFGHIJ000000......................0
g) S, un archivo puede utilizarse para comunicar procesos. Por ejemplo, un proceso puede escribir datos en un ar-
chivo y otro puede leerlos.
Es un mecanismo simple que, sin embargo, presenta una serie de inconvenientes que hacen que en general no sea
un mecanismo de comunicacin ampliamente utilizado, a saber:
Es un mecanismo bastante ineficiente, ya que la escritura y lectura en disco es lenta.
Necesitan algn otro mecanismo que permita que los procesos se sincronicen en el acceso a los datos al -
macenados en un archivo.
80 Problemas de sistemas operativos

Problema 2.6 (septiembre 2002)

La siguiente figura muestra el esquema de un pequeo sistema de ficheros tipo UNIX. En l se pueden distinguir
seis partes: el bloque de boot, el superbloque, el mapa de bits para los nodos-i del sistema, el mapa de bits para los
bloques de datos, los nodos-i (uno por cada entrada de la tabla) y finalmente los bloques de datos y directorios. To-
dos los bloques de disco aparecen numerados y tienen un tamao de 4 KiB.
0 BOOT
1 SUPERBLOQUE
2 1110100100000100000000......0000000000000
3 1111110110000001000000..0000000000000
4 Nodo-i 2

5
... NODOS-I DEL SISTEMA
258
259
260 Bloque Datos 0
261
262
... BLOQUES DE DATOS Y DIRECTORIOS
2306
2307 Bloque Datos 2047
Bloque de BOOT: Ocupa el bloque 0.
Superbloque: Ocupa el bloque 1.
Mapa de bits para los nodos-i del sistema y mapa de bits para los bloques de datos: Ocupan los bloques 2 y 3
respectivamente. Cada bit representa el estado de un nodo-i o bloque de datos del sistema: 0 significa que
est libre y 1 implica que est ocupado. En la figura se ha representado el estado actual de ambos mapas de
bits. El mapa de bits de nodos-i comienza por el nodo-i 2 (nodo-i situado en el bloque de disco 4) y el mapa
de bits de los bloques de datos comienza por el bloque de datos 0 (bloque de disco 260).
Nodos-i del sistema: En este rea se almacenan los nodos-i del sistema. Cada nodo-i ocupa un bloque. Este
rea ocupa 256 bloques (bloques 4 a 259, ambos inclusive).
Bloques de datos y directorios: en este rea se almacenan los bloques de datos y directorios. Ocupa 2048
bloques (desde el bloque 260 hasta el 2307, ambos inclusive).
Los nodos-i del sistema tienen la siguiente estructura:
Tipo Fichero (Normal o Directorio)
Num. Enlaces
UID
GID
Bits de proteccin
Num. Bloques Directos
1. Bloque directo
2. Bloque directo
.....
N. Bloque directo
....
Slo se utilizan bloques directos y adems slo se distingue entre fichero normal y directorio. Se ha simplificado
la estructura habitual del nodo-i, eliminando algunos atributos. El nodo-i 2 es el nodo-i del directorio raz. El no -
do-i 0 queda reservado para indicar que la correspondiente entrada de directorio no est ocupada. El nodo-i 1 est
reservado por el sistema.
Los nodos-i del sistema quedan representados en la siguiente figura:
Nodo-i 2 Nodo-i 3 ... Nodo-i 6 Nodo-i 7 ... Nodo-i 9 Nodo-i 10 ... Nodo-i 15
Directorio Directorio Directorio Directorio Normal Directorio Normal
4 3 3 3 1 2 1
81
0 0 0 101 101 101 101
1 1 1 30 30 30 30
777 777 755 755 640 755 640
1 1 1 1 2 1 1
0 1 2 3 5 4 5
#### #### #### #### 6 #### ####
........ ........ ........ ........ #### ........ ........
........ ........ ........ ........ ........ ........ ........
#### #### #### #### #### #### ####
El contenido de los bloques de datos se representa en la siguiente figura:
Bloque 0 . 2 ; .. 2 ; tmp 3 ; home 6 ;
Bloque 1 . 3 ; .. 2 ; fich.txt 9 ; proyecto 10 ;
Bloque 2 . 6 ; .. 2 ; pepe 7 ;
Bloque 3 . 7 ; .. 6 ; fich2.txt 9 ;
Bloque 4 . 10 ; .. 3 ; fich3.txt 15 ;
Bloque 5 456700..... ............... ............... ............... ............... .............. ............... ..........000
Bloque 6 00000...... ............... ............... ............... ............... .............. ............... ..........000
Bloque 7 00000...... ............... ............... ............... ............... .............. ............... ..........000
Bloque 8 00000...... ............... ............... ............... ............... .............. ............... ..........000
....
Responder razonadamente a las siguientes preguntas:
a) Es el sistema de ficheros consistente? Si no es as, indicar qu problemas tiene y modificar las estructuras
de datos mostradas en el problema para que el sistema sea consistente. Indicar cules son los pasos que ten-
dra que seguir una herramienta tipo fsck para buscar las inconsistencias y corregirlas. Describir las estruc -
turas de datos adicionales que podra utilizar para llevar a cabo dicho proceso.
b) Indicar el tamao mximo de un fichero de este sistema.
c) Existe algn problema respecto a la proteccin de los ficheros en el escenario descrito? Si es as, indicar la
solucin.
d) Rellenar la metainformacin del sistema que queda modificada despus de la ejecucin de cada una de las
llamadas al sistema que se enumeran a continuacin por parte del usuario con uid 101 y gid 30. Representar
en forma de rbol la estructura de directorios resultante en cada uno de los pasos e indicar si las llamadas
al sistema se realizan de forma correcta.
1. link("/tmp/fich.txt", "/home/fich4.txt");
2. unlink("/tmp/fich.txt");
3. mkdir("/tmp/datos", 0755);
Las llamadas se llevan a cabo en el orden descrito.
e) Se desea abrir el fichero fich2.txt y acceder al byte 5000. Si nos encontramos en el directorio raz, cul ser
el nmero de accesos a disco para realizar dicha operacin en este sistema de ficheros?

Solucin
a) El sistema de ficheros no es consistente. Para deducirlo, se han comprobado los siguientes aspectos:
1.- Nodos-i utilizados en el sistema
Segn el mapa de bits de nodos-i Nodos-i utilizados: 2, 3, 4, 6, 9, 15
Segn el recuento de nodos-i ocupados en el sistema (Recorriendo los bloques de directorios) Nodos-i
utilizados: 2, 3, 6, 7, 9, 10, 15
El estado es inconsistente. La solucin consiste en marcar como libre el nodo-i 4 y como ocupados los nodos-i 7
y 10 en el mapa de bits de nodos-i:
Mapa de bits de nodos-i: 1100110110001000......
2.- Bloques de datos utilizados en el sistema
Segn el mapa de bits de bloques de datos Bloques utilizados: 0, 1, 2, 3, 4, 5, 7, 8, 15
Segn el recuento de bloques ocupados en el sistema (Recorriendo los nodos-i que los referencian)
Bloques de datos utilizados: 0, 1, 2, 3, 4, 5 (2 veces), 6
82 Problemas de sistemas operativos
El estado es inconsistente. Los errores afectan al mapa de bits de bloques de datos y a los ficheros fich.txt y
fich3.txt. La solucin al mapa de bits consiste en marcar como libre los bloques 7, 8 y 15, quedando del siguiente
modo: 1111111000000.........
Para resolver el problema del bloque compartido por los ficheros, la mejor solucin sera copiar el bloque 5 a
otro bloque nuevo, por ejemplo el bloque 7 y asignar este bloque a uno de los dos ficheros, por ejemplo fich3.txt.
El mapa de bits de bloques de datos quedara: 1111111100000.........
Adems, el nodo-i 15 deber referenciar al bloque 7 en lugar del bloque 5.
3.- Nmero de enlaces
Segn el nodo-i 9: Nmero de enlaces=1. Segn las referencias a dicho nodo-i: Nmero de enlaces=2
Segn el nodo-i 7: Nmero de enlaces=3. Segn las referencias a dicho nodo-i: Nmero de enlaces=2
El estado es inconsistente. La solucin consiste en incrementar el campo nmero de enlaces correspondiente al
nodo-i 9 y decrementar el campo nmero de enlaces correspondiente al nodo-i 7.
La herramienta para la comprobacin de la consistencia tendra que seguir los pasos utilizados anteriormente.
Las estructuras de datos que podra utilizar seran:
Un contador para cada nodo-i, que se debe incrementar a partir de los nodos-i referenciados al recorrer el
rbol de directorios. Esta estructura nos permite comprobar la consistencia del mapa de bits de nodos-i
(contador>0) y nmero de enlaces del mismo (contador=campo n de enlaces).
Un contador por cada bloque de datos, que debe incrementarse a partir del recuento de los mismos.
Otras comprobaciones adicionales son:
Existencia de los UID y GID utilizados.
Que todos los nmeros de nodo-i estn dentro del rango (2-257)
b) El tamao mximo de un fichero viene determinado por la estructura del nodo-i y por el tamao del rea de datos.
Segn la estructura del nodo-i, la expresin para calcula el tamao mximo es la siguiente:
Tam. Max = Num. Punteros directos Sb, donde Sb es el tamao de un bloque (4 KiB).
Se necesita conocer cuntos punteros directos puedo almacenar en el nodo-i. Dado el tamao del rea de datos,
bastara con 11 bits para direccionar los 2048 bloques de datos.
Al ser un sistema de ficheros pequeo, sera razonable utilizar 16 bits para los campos de cada nodo-i. Por tanto,
un nodo-i tiene 2048 campos, por lo que permite direccionar: 2048-6=2042 bloques de datos. Luego, Tam. Max =
2042 4 KiB = 8168 KiB
Los bloques que se pueden dedicar a datos son: 2048-1 (directorio raz) =2047. Por tanto, el tamao mximo li -
mitado por el nmero de bloques del sistema permite almacenar el fichero calculado previamente.
c) El nodo raz no debera tener permisos 777, que implica permisos de lectura, escritura y ejecucin para todos los
usuarios del sistema. La solucin sera modificarle los permisos, establecindolos a 755.
Otro problema de seguridad proviene de la no-existencia de un campo que establezca el tamao real del fichero.
Un usuario puede crear un fichero y escribir un byte y posteriormente leer todo el bloque con el contenido que dej
otro usuario previamente.
d) Se supone que se han corregido los problemas de consistencia del sistema de ficheros y en concreto, que el nme -
ro de enlaces del nodo-i 9 vale 2.
/

tmp home

proyecto fich.txt pepe

fich3.txt fich2.txt
Figura 2.6

El rbol de directorios inicial es el de la figura 2.6.


83
1.- Error. El usuario con uid 101 y gid 30 no tiene permisos para crear un fichero en el directorio home. La es-
tructura de rbol no se modifica.
2.- La llamada se realiza de forma correcta. La operacin modifica el bloque de datos 1 y el nodo-i 9 de la si -
guiente forma:
Tipo Num. En- UID GID Bits de Bloques Bloque di- Bloque di- Bloque di- ....
laces protec. Directos recto 1 recto 2 recto 3
Normal 1 101 30 640 2 5 6 ####
Se decrementa el nmero de enlaces del nodo-i. Como antes vala 2, ahora pasa a tomar el valor 1
Bloque 1 . 3 ; .. 2 ; proyecto 10 ;
Se elimina la entrada correspondiente a fich.txt.
Esta operacin no afecta a los mapas de bits, dado que no se eliminan ni bloque ni nodos-i.
El rbol de directorios resultante es el de la figura 2.7.
/

tmp home

proyecto pepe

fich3.txt fich2.txt
Figura 2.7

3.- La llamada se realiza de forma correcta. Se va a crear un nuevo directorio en tmp, por lo que se habilita una
nueva entrada en el bloque 1. Adems, necesita un nuevo nodo-i libre, por ejemplo, el 4:
Bloque 1 . 3 ; .. 2 ; proyecto 10 ; datos 4
El nodo-i 4 tendr la siguiente estructura:
Tipo Num. En- UID GID Bits de Bloques Bloque di- Bloque di- Bloque di- .....
laces protec. Directos recto 1 recto 2 recto 3
Normal 2 101 30 750 1 7 #### ####
Se le asigna un bloque libre, por ejemplo, el 7
El bloque de datos 7 contendr:
Bloque 7 . 4 ; .. 3 ;
Adems, el nodo-i 3 tambin es modificado al incrementarse el nmero de enlaces:
Tipo Num. En- UID GID Bits de Bloques Bloque di- Bloque di- Bloque di- .....
laces protec. Directos recto 1 recto 2 recto 3
Directorio 4 0 1 777 1 1 #### ####
Se incrementa en 1 el nmero de enlaces
Respecto a la metainformacin, en el mapa de nodos-i se marca el nodo-i 4 como ocupado y en el mapa de blo -
ques de datos se marca el bloque 7 como ocupado.
El rbol de directorios resultante es el de la figura 2.8.
e) Se supone que el nodo-i 2 (raz) est ya en memoria.
84 Problemas de sistemas operativos
/

tmp home

proyecto datos pepe

fich3.txt fich2.txt
Figura 2.8

El nmero de accesos a disco para acceder al fichero:


Acceso 1: Traer el bloque / a memoria y buscar la entrada /home
Acceso 2: Traer el nodo-i de /home a memoria
Acceso 3: Traer el bloque de /home a memoria y buscar la entrada de /home/pepe
Acceso 4: Traer el nodo-i de /home/pepe a memoria
Acceso 5: Traer el bloque de /home/pepe a memoria y buscar la entrada de /home/pepe/fich2.txt
Acceso 6: Traer el nodo-i de /home/pepe/fich2.txt
Por tanto para acceder al fichero necesitamos 6 accesos a disco.
Acceso 7: Para leer el byte 5000 del fichero hay que acceder al bloque 6.
Total: 7 accesos.

Problema 2.7 (junio 2003)

a) Implementar el programa GenerarClave con los siguientes requisitos:


El programa recibe como nico parmetro el nombre de un fichero o directorio.
En caso de haber recibido un directorio el programa emite un 1 por su salida estndar y finaliza.
Si el parmetro recibido es un fichero el programa deber emitir por su salida estndar un nmero ente-
ro calculado de la siguiente manera: tamao en bytes del fichero ms la suma de todos los bytes del fi-
chero.
b) Se desea realizar el programa InsertarClave con los siguientes requisitos:
El programa recibe como argumentos el nombre de un directorio y el nombre de un fichero. Se debe su-
poner que tanto el directorio como el fichero existen y son vlidos.
Calcula la clave del fichero indicado como segundo argumento, usando para su clculo el programa
GenerarClave realizado en el anterior apartado.
Una vez calculada la clave, crea en el directorio especificado como primer argumento un enlace simb-
lico con nombre el valor de la clave calculada, apuntando al fichero recibido como entrada.
Por ejemplo, en la siguiente invocacin del programa:
InsertarClave /var/claves/ /tmp/fichDatos.txt
si la clave calculada es 67534, se debera crear la siguiente entrada en el directorio /var/claves/:
lrwxr-xr-x 1 luis luis 13 Jun 2 16:36 67534 -> /tmp/fichDatos.txt
c) Realizar el programa ComprobarClaves con los siguientes requisitos:
Recibe como parmetro de entrada el nombre de un directorio. Se debe suponer que este directorio sola-
mente contiene entradas creadas por el programa InsertarClave.
Cada 20 segundos, de forma predeterminada, recorre uno por uno todos los ficheros del directorio, com-
probando que su clave es correcta.
En caso de encontrar una clave incorrecta, imprime por su salida estndar el nombre del fichero corres-
pondiente.
d) Responda razonadamente. Habra alguna diferencia si en vez de enlaces simblicos el programa Inser-
tarClave creara enlaces fsicos?, qu ventajas e inconvenientes podra suponer esta situacin?
85

Solucin
a) El programa propuesto es el siguiente.
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>

int main(int argc, char *argv[])


{
struct stat buffer;
int clave=0;
FILE *file;
int s;

/* Obtenemos la informacin del fichero */


if (stat(argv[1], &buffer)<0)
{
perror("Error en la llamada stat");
return 1;
}

/* Comprobamos si es directorio */
if (S_ISDIR(buffer.st_mode))
{
clave = -1;
printf("%d\n", clave);
return 0;
}
else
{
/* Obtenemos tamao del fichero */
clave = (int) buffer.st_size;
file = fopen(argv[1],"r");
if (!file)
{
perror("Error al abrir el fichero");
return 1;
}

/* Clculo de la suma de los bytes del fichero */


s = getc(file);
while (s!=-1)
{
clave += s;
s = getc(file);
}
}

/* Imprimimos por la salida estndar la clave */


printf("%d\n", clave);
return 0;
}
b) Debemos recoger la salida estndar del programa anterior, por lo que se debe crear un hijo que ejecute dicho pro -
grama y recoger su salida a travs de una tubera.
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[])


86 Problemas de sistemas operativos
{
int fd[2];
FILE *file;
pid_t pid;
int n;
int clave;
char nuevoPath[200];
char origPath[200];
char claveStr[100];

/* Creamos la tubera */
if (pipe(fd)<0)
{
perror("error en la tuberia");
return 1;
}

/* Creamos el hijo */
pid = fork();

switch(pid)
{
case -1:
perror("Error en el fork");
return 1;
case 0:
/* El hijo ejecuta GenerarClave */
close(fd[0]);
close(1);
dup(fd[1]);
close(fd[1]);
execlp("GenerarClave", "GenerarClave", argv[2], NULL);
perror("Error en el exec de GenerarClave");
return 1;
break;
default:
/* El padre lee del pipe */
close(fd[1]);
file = fdopen(fd[0], "r");
fscanf(file, "%d", &clave);

/* Ruta original */
strcpy(origPath,"");
if (argv[2][0]!=/)
{
getcwd(origPath, 200);
strcat(origPath, "/");
}
/* Ahora es ruta absoluta */
strcat(origPath, argv[2]);

/* Nueva ruta */
sprintf(claveStr,"/%d", clave);
strcpy(nuevoPath,"");
strcat(nuevoPath, argv[1]);
strcat(nuevoPath, claveStr);

/* Creacin del enlace simblico */


symlink(origPath,nuevoPath);
}
return 0;
}
87
c) El programa propuesto es el siguiente.
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>

int main(int argc, char *argv[])


{
int fd[2];
pid_t pid;
int n;
int clave;
char dirActual[200];
char origPath[200];
char claveStr[100];
DIR *dirp;
FILE *file;
struct dirent *dp;

dirp = opendir(argv[1]);

if (dirp == NULL)
{
perror("No puedo abrir el directorio");
exit(1);
}
else
{
/* Nos saltamos las dos primeras entradas: . y .. */
readdir(dirp);
readdir(dirp);

/* Leemos el directorio entrada por entrada */


while ((dp = readdir(dirp)) != NULL)
{
printf("Nombre fich: %s\n",dp->d_name);

/* Se crea la tubera */
if (pipe(fd)<0)
{
perror("error en la tuberia");
return 1;
}

/* Ruta original */
sprintf(origPath,"%s%s%s",argv[1],"/",dp->d_name);

pid = fork();

switch(pid)
{
case -1:
perror("Error en el fork");
return 1;
case 0:
/* El hijo ejecuta GenerarClave */
close(fd[0]);
close(1);
dup(fd[1]);
close(fd[1]);
execlp("GenerarClave", "GenerarClave", origPath, NULL);
88 Problemas de sistemas operativos
perror("Error en el exec de GenerarClave");
return 1;
break;
default:
/* El padre lee del pipe y comprueba que la clave es correcta*/
close(fd[1]);
file = fdopen(fd[0], "r");
fscanf(file, "%d", &clave);
sprintf(claveStr,"%d", clave);

if (strcmp(claveStr,dp->d_name))
fprintf(stderr,"Error en la clave de %s\n",dp->d_name);
}
}
}
return 0;
}
d) La primera diferencia es que no se pueden crear enlaces fsicos entre diferentes volmenes. La segunda diferencia
vendra a la hora del borrado de los ficheros. Dependiendo de la poltica que nos interese podemos utilizar cada uno
de los enlaces. Si usamos enlaces simblicos podemos tener enlaces errneos en nuestro directorio. Si usamos enla-
ces fsicos podemos estar generando problemas con el borrado de los ficheros, ya que estamos aumentando el conta -
dor del nmero de enlaces en los mismos.
Desde el punto de vista de la programacin, slo necesitamos cambiar la llamada symlink por link.

Problema 2.8
a) Dado el siguiente cdigo, en el cual no se ha realizado ninguna comprobacin de errores:
1 int main(int argc, char *argv[]) {
2 int fd, fd2, fd3;
3 pid_t pid1, pid2;
4 char buffer[1024]="1234567890123456789012345";
5
6 fd=open(argv[1],O_RDWR);
7 pid1=fork();
8 if (pid1==0) {
9 fd2 = creat(argv[2], 0640);
10 fd3 = dup(fd);
11 read(fd3,buffer,10);
12 read(fd,buffer,10);
13 write(fd2,buffer,20);
14 close(fd);
15 close(fd2);
16 close(fd3);
17 return 0;
18 }
19 else {
20 pid2=fork();
21 if (pid2==0) {
22 fd2=open(argv[1],O_RDONLY);
23 fd3=open(argv[2],O_RDWR);
24 write(fd3, buffer, 10);
25 read(fd,buffer,10);
26 write(fd,buffer+10,10);
27 close(fd);
28 close(fd2);
29 close(fd3);
30 return 0;
31 }
89
32 else {
33 fd2=creat(argv[2],0755);
34 fd3=dup(fd2);
35 read(fd,buffer,10);
36 write(fd2,buffer,10);
37 write(fd3,buffer,10);
38 close(fd);
39 close(fd2);
40 close(fd3);
41 return 0;
42 }
43 }
44 }
Si se ejecuta este programa con los argumentos fich1 fich2 y el fichero fich1 tiene el contenido abcdefghijklm-
nopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ, responder razonadamente a las siguientes cuestiones:
1. Si el orden en el que se ejecutan los procesos de forma completa es: proceso padre, primer proceso hijo, se-
gundo proceso hijo, cul es el contenido del fichero fich2 al final de la ejecucin?
2. Realizar el mismo ejercicio para el caso de que el orden de ejecucin sea el inverso (segundo proceso hijo,
primer proceso hijo, proceso padre).
3. Para el orden de ejecucin del apartado 1, rellenar la siguiente tabla, con los valores correspondientes de la
tabla en la lnea ejecutada:
Num. lnea Puntero de posi- Puntero de posi- Puntero de posi- Contenido de bu-
cin del fichero cin del fichero cin del fichero ffer
fd1 fd2 fd3
4. Una vez que el proceso correspondiente ejecuta el cdigo de la lnea 10, qu modo de acceso tiene sobre el
fichero cuyo descriptor es fd3? Responder a la misma cuestin para el caso del proceso que ejecuta el cdigo de la
lnea 34.
5. El cdigo correspondiente a la lnea 26, producira un error de compilacin?, producira un error de ejecu-
cin? Explicar grficamente en qu consiste esta operacin, dibujando el buffer y el fichero cuyo descriptor es fd.
6. Qu diferencias habra en la ejecucin del proceso que ejecuta el cdigo de la lnea 36, si dicha lnea se sus-
tituyera por write(fd3,buffer,10);?
7. Qu diferencias habra en la ejecucin si en lugar de utilizar procesos pesados utilizaramos procesos lige-
ros?
b) Escribir en C una pequea funcin int generar_fichero(int offset), que realice las siguientes
tareas:
1. Abre el fichero que se encuentra en el directorio de trabajo inicial del usuario del proceso, cuyo nombre es el
identificador del usuario que est ejecutando el programa y que tiene extension .dat.
2. Crea un fichero en el directorio actual, con nombre el identificador del proceso y con extensin .dat.
3. Lee los bytes del primer fichero, a partir del desplazamiento del fichero offset y los escribe en el segundo
fichero.
4. Cierra ambos ficheros.
5. Devuelve 0 si todo ha ido bien y 1 en caso contrario.

Solucin
a) La solucin de cada apartado es la siguiente:
a.1. 12345678901234567890
a.2. Si el fichero fich2 no existiera, habra un error en la llamada de la lnea 23. No obstante, el contenido del fi -
chero al final de la ejecucin quedara: OPQRSTUVWXOPQRSTUVWX
a.3.
Num. lnea Puntero fd Puntero fd2 Puntero fd3 Buffer
90 Problemas de sistemas operativos
6 0 # # 1234567890123456789012345
33 0 0 # 1234567890123456789012345
34 0 0 0 1234567890123456789012345
35 10 0 0 abcdefghij123456789012345
36 10 10 10 abcdefghij123456789012345
37 10 20 20 abcdefghij123456789012345
38 # 20 20 abcdefghij123456789012345
39 # # 20 abcdefghij123456789012345
40 # # # abcdefghij123456789012345
9 10 0 # 1234567890123456789012345
10 10 0 10 1234567890123456789012345
11 20 0 20 klmnopqrst123456789012345
12 30 0 30 uvwxyzABCD123456789012345
13 30 20 30 uvwxyzABCD123456789012345
14 # 20 30 uvwxyzABCD123456789012345
15 # # 30 uvwxyzABCD123456789012345
16 # # # uvwxyzABCD123456789012345
22 30 0 # 1234567890123456789012345
23 30 0 0 1234567890123456789012345
24 30 0 10 1234567890123456789012345
25 40 0 10 EFGHIJKLMN123456789012345
26 50 0 10 EFGHIJKLMN123456789012345
27 # 0 10 EFGHIJKLMN123456789012345
28 # # 10 EFGHIJKLMN123456789012345
29 # # # EFGHIJKLMN123456789012345
a.4. Lnea 10: Modo de acceso lectura y escritura
Lnea 34: Modo de acceso slo escritura.
a.5. No, es una lnea de cdigo perfectamente vlida. La figura 2.9 muestra el resultado de la operacin grfica-
mente,

Figura 2.9

a.6. Ninguna, dado que estos dos descriptores comparten el pntero de posicin.
a.7. En el caso de utilizar procesos ligeros, se comparte la memoria, por lo que la variable buffer es compartida y
todos los cambios sobre esta variable son vistos por dichos procesos ligeros. En este caso, adems habra que utilizar
mecanismos de sincronizacin, para evitar que se dieran condiciones de carrera.
b) El programa siguiente muestra una posible solucin.
#define TAM_FICH 256
#define TAM_BUFFER 1024

int generar_fichero(int offset) {


char *fichero_origen;
char *fichero_destino;
char buffer[TAM_BUFFER];
int fd_origen, fd_destino;
int leidos;

fichero_origen = malloc(TAM_FICH*sizeof(char));
if (fichero_origen == NULL)
return -1;

fichero_destino= malloc(TAM_FICH*sizeof(char));
if (fichero_destino== NULL)
return -1;
91
sprintf(fichero_destino, "%d", (int) getpid());
fichero_destino=strcat(fichero_destino,".dat");
fd_destino=creat(fichero_destino, 0640);
if (fd_origen <0)
return -1;

chdir(getenv("HOME"));
sprintf(fichero_origen,"%d", (int) getuid());
fichero_origen=strcat(fichero_origen,".dat");
fd_origen=open(fichero_origen, O_RDONLY);
if (fd_destino <0)
return -1;

lseek(fd_origen, offset, SEEK_SET);


while ((leidos = read(fd_origen, buffer, TAM_BUFFER))>0)
write(fd_destino, buffer, leidos);

free(fichero_origen);
free(fichero_destino);
close(fd_origen);
close(fd_destino);

if (leidos <0)
return -1;
else
return 0;
}

Problema 2.9 (mayo 2004)

En un sistema operativo de tipo UNIX tenemos, entre otros muchos, los siguientes ficheros:
Nodoi Permisos Usuario Grupo Tamao B Nombre_Fichero
123 d rwx r-x --x Pedro Datsi 1024 /home/Pedro
656 - r-x r-s --- root root 1546 /home/Pedro/Cancelar
678 - rw- rw- --- Pedro Datsi 30 /home/Pedro/Informe.txt
697 - rw- --- --- Pedro Datsi 30 /home/Pedro/Informe2.txt
708 - r-x r-x --- Pedro Datsi 998 /home/Pedro/Prog1
351 d rwx --- --- Maria Datsi 1024 /home/Maria
697 - rw- --- --- Pedro Datsi 30 /home/Maria/Informe3.txt
720 - r-x r-x --- Maria Datsi 345 /home/Maria/Prog2
Un permiso s implica que, adems del correspondiente permiso de ejecucin, est activo el bit SETUID si apa-
rece en el primer trio el bit SETGID si aparece en el segundo trio.
El cdigo fuente de Prog1 es:
1 int main (int argc, char **argv)
2 {
3 int fd=-1, fd2=-1;
4 pid_t pid;
5 char cadena[512]="122333444455555";
6
7 access("Cancelar", X_OK);
8 chmod("Informe2.txt",0660);
9
10 fd = open ("Informe2.txt", O_RDWR);
11
12 pid=fork();
92 Problemas de sistemas operativos
13
14 if (pid!=0) {
15 lseek(fd, 12, SEEK_CUR);
16 write(fd2, cadena, 10);
17 write(fd, cadena, 4);
18 read(fd, cadena+2, 5);
19 close(fd2);
20 close(fd);
21 }
22 else {
23 fd2 = dup2(fd, 10);
24 write(fd, "77777", 3);
25 read(fd2, cadena, 5);
26 close(fd);
27 close(fd2);
28 }
29 }
El cdigo fuente de Prog2 es:
1 int main (int argc, char **argv)
2 {
3 int sd, fd;
4 char cadena[256]="55555555555555555555";
5
6 link("../Pedro/Informe.txt","Informe.txt");
7 sd = open("Informe.txt",O_RDWR);
8 lseek(sd, 0, SEEK_END);
9 fd = open ("Informe3.txt", O_RDWR|O_CREAT, 0666);
10
11 lseek(fd, -10, SEEK_END);
12 write(fd, cadena, 20);
13 lseek(fd, 20, SEEK_END);
14 close(fd);
15 }
El fichero descrito por el nodo-i 678 comienza por: 123451234554321543211234554321...
El fichero descrito por el nodo-i 697 comienza por: 123456789012345678901234567890...
En este entorno, los usuarios Pedro y Mara, que pertenecen al grupo Datsi, ejecutan desde sus respectivos
HOME, los programas Prog1 y Prog2 respectivamente.
Suponiendo el siguiente orden de ejecucin:
Prog1:padre hasta la lnea 15 (incluida).
Prog1:hijo hasta la lnea 25 (incluida).
Prog1:padre hasta la lnea 17 (incluida).
Prog2 hasta la lnea 12 (incluida).
Prog1:padre hasta que termina.
Prog1:hijo hasta que termina.
Prog2 hasta que termina.
Responder a las siguientes preguntas.
a) Qu devuelve la llamada access de la lnea 7 de Prog1?
b) Qu devuelve la llamada write de la lnea 24 de Prog1?
c) Qu devuelve la llamada write de la lnea 16 de Prog1?
d) Qu devuelve la llamada lseek de la lnea 8 de Prog2?
e) Cul es el tamao final del fichero Informe2.txt?
f) Cul es el contenido de las 5 primeras posiciones de la variable cadena despus de ejecutar la lnea 18
de Prog1?
93

SOLUCION
a) La llamada access slo comprueba los UID y GID reales, y no los efectivos por lo que no tiene permisos suficien-
tes y devuelve 1.
b) La llamada write, que tiene permisos, hace la operacin y devuelve 3, ya que es el nmero de bytes que realmente
consigue escribir, aunque la cadena sea de mayor tamao.
c) La llamada write devuelve 1 ya que fd2 tiene un valor de 1 en el proceso padre, que no es un valor vlido de
descriptor.
d) La llamada lseek sita el puntero al final del fichero, por lo que el valor devuelto ser el tamao del mismo, es
decir 30. Este valor se encuentra especificado en el nodo-i y se ha suministrado en la tabla de descripcin de fiche -
ros (hay que recordar que el tamao fsico menor del fichero ser un bloque o agrupacin, que tendr un cierto con-
tenido).
e y f) En la tabla 2.5 se analizan los programas Prog1 y Prog2 lnea por lnea, segn el orden de ejecucin especifi-
cado
Por lo tanto, el tamao el final de Informe2.txt es 40 bytes y las cinco primeras posiciones de la variable cadena
en el padre son 12555.
Prog Lnea Resultado
1 7 access devuelve -1
1 8 Informe2.txt con permisos rw- rw- ---
1 10 Se abre Informe2.txt y, por tanto, fd=3
1 12 Creamos proceso hijo. Padre e hijo de Prog1 comparten puntero fd
1 15 lseek cambia el puntero de posicin y devuelve 12
1 23 fd2=10 y se comparte puntero con fd
1 24 Se escriben 3 bytes.Informe2.txt = 123456789012777678901234567890
1 25 Se leen 5 bytes desde el puntero. Cadena(hijo) = 678903444455555
1 16 La llamada devuelve 1
1 17 Se escriben 4 bytes de la variable cadena(padre) en el fichero.Informe2.txt =
123456789012777678901223567890
2 6 link devuelve 0 por que se realiza correctamente
2 7 sd=3
2 8 lseek devuelve 30
2 9 fd=4. Se abre Informe3.txt que es un enlace fsico a Informe2.txt.El puntero de acceso es inde-
pendiente al de Prog1
2 11 lseek devuelve 20 y sita al puntero en esa posicin
2 12 Escribimos 20 veces el carcter 5.Informe2.txt =
1234567890127776789055555555555555555555Tamao Informe2.txt = 40
1 18 read devuelve un 5. El puntero estaba en la posicin 25. Se lee lo que ha escrito
Prog2.Cadena(padre) = 125555544455555
1 19 close devuelve 1, ya que fd2 era 1
1 20 Se cierra fd en el padre
1 26 Se cierra fd en el hijo
2 13 lseek devuelve 60, pero no cambia el tamao del fichero
2 14 Se cierra fd en el Prog2
Tabla 2.5

Problema 2.10 (febrero 2005)

Sea un sistema que utiliza pginas de 4 KiB y cuyos bloques de sistema de ficheros son de 4 KiB. En este sistema
existe un fichero denominado fichpru de 8742 B que ocupa, por ese orden, los bloques 4, 7 y 1.
94 Problemas de sistemas operativos
a) Recordando la forma en que el sistema de ficheros accede al disco, indicar las operaciones que hace el siste -
ma de ficheros sobre el disco para atender el servicio: write(fd, "X", 1);. Supondremos que el siste-
ma de ficheros no tiene cache, por lo que hace todas las operaciones de lectura y escritura directamente
sobre disco.
b) Sean los dos programas adjuntos, que sobrescriben las 3000 primeras posiciones pares del fichero, respetan-
do el valor original de las impares.
Seguimos suponiendo que el sistema de ficheros no tiene cache.
Para cada uno de los programas de la tabla 2.6, indicar por orden los 6 primeros accesos a disco que se gene-
ran dentro del bucle for (externo para el programa 2). Detallar el bloque accedido y el tipo de acceso.
c) Realizar un programa que realice la misma funcin pero proyectando el fichero en memoria. Analizar las
operaciones de disco que se producen en este caso.
Programa 1 Programa 2
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
int main(void) int main(void)
{ {
int fd, i; int fd, i, j;
fd = open("fichpru", O_WRONLY); char buff[20];
for (i=0; i<3000; i++) { fd = open("fichpru", O_RDWR);
write(fd, "X", 1); for (i=0; i<300; i++) {
lseek(fd, +1, SEEK_CUR); read(fd, buff, 20);
} for (j=0; j<10; j++)
close(fd); buff[2*j] = X;
return 0; lseek(fd, -20, SEEK_CUR);
} write(fd, buff, 20);
}
close(fd);
return 0;
}
Tabla 2.6

Solucin
a) Para atender el servicio mencionado, el sistema de ficheros debe determinar el bloque de disco afectado. Esto exi-
ge leer la tabla de descriptores de fichero del proceso as como la tabla de punteros (FILP). Ambas estn en memo-
ria, por lo que no afecta al disco. Con el valor del puntero debe obtener el bloque. Esta informacin est almacenada
en el nodo-i (o estructura equivalente), por lo que pueden ser necesarias 0, 1 o ms operaciones de lectura en el dis -
co, dependiendo de que el nodo-i o estructura equivalente est mantenido en memoria (lo que, por ejemplo, ocurre
en los sistemas Unix), y de que el bloque sea directo, simple indirecto, doble indirecto o triple indirecto.
Una vez conocido el bloque afectado, el sistema de ficheros ha de modificar un solo byte de dicho bloque. Pero
el disco no admite operaciones de 1 byte, solamente operaciones de un sector completo. Por tanto, el sistema de fi -
cheros, escribir siempre bloques enteros. Para no modificar el resto de los bytes del bloque lo que tiene que hacer
es: primero, leer el contenido del bloque a memoria, segundo, modificar en memoria ese contenido con el nuevo
byte y, tercero, reescribir el bloque completo al disco.
Solamente en el caso de la primera escritura en un nuevo bloque aadido a un fichero (o en el caso de que se es -
cribiera un bloque completo) no hay que conservar informacin, por lo que no es necesario hacer la lectura.
b) Los accesos de datos a disco del programa 1 son : lee bloque 4, escribe bloque 4, lee bloque 4, escribe bloque 4,
lee bloque 4, escribe bloque 4 ......... escribe bloque 7, lee bloque 7, escribe bloque 7.
El bucle se repite 3000 veces, por lo que se generan 3000 lecturas y 3000 escrituras al disco.
Los accesos de datos a disco del programa 2 son : lee bloque 4, lee bloque 4, escribe bloque 4, lee bloque 4, lee
bloque 4, escribe bloque 4 ......... , lee bloque 7, lee bloque 7, escribe bloque 7.
El bucle se repite 300 veces, por lo que se generan 600 lecturas y 300 escrituras al disco.
95
c) Un posible programa es el siguiente:
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd, i;
char * data;

fd = open("fichpru", O_RDWR);
data = mmap(0, 6000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
for (i=0; i<3000; i++)
data[2*i] = X;
munmap(data, 6000);
return 0;
}
Al pasar por primera vez por la sentencia data[2*i] = X; se produce un fallo de pgina, por lo que el
bloque 4 es ledo. Ms adelante se sobrepasa el bloque 4, por lo que se produce un segundo fallo de pgina, por lo
que se lee el bloque 7.
Finalizado el bucle se libera la imagen de memoria, pero como est en MAP_SHARED el sistema operativo sal -
va lo escrito. Esto significa que se escriben en el disco los bloques 4 y 7. El orden en que se escribe depende de la
implementacin del sistema operativo, por lo que podemos suponer uno cualquiera.
Es de notar que esta solucin ha requerido solamente 2 lecturas y 2 escrituras al disco (ms las posibles lecturas
y escrituras relativas a la metainformacin que no se est considerando).
Dentro del bucle no se produce ninguna llamada al sistema operativo.

Problema 2.11 (mayo 2005)

En un sistema UNIX existen los siguientes usuarios:


alumno1 (UID = 101)
alumno2 (UID = 102)
alumno3 (UID = 103)
alumno4 (UID = 104)
root (UID = 0)
El grupo alumnosSO, cuyo identificador es el 300, incluye los siguientes usuarios: alumno1, alumno2. Por su
parte, el grupo alumnosDSO, cuyo identificador es el 400, incluye los siguientes usuarios: alumno3, alumno4. El
usuario root pertenece al grupo root (GID = 0).
Si se lleva a cabo un listado del directorio /fichs en formato largo e incluyendo informacin sobre los nodos-i (ls
lai) se obtiene la siguiente informacin:
Nodo-i Permisos Enlaces Usuario Grupo Tamao Fecha Nombre
41190 drwxrwxrwx 2 root root 4096 2004-05-11 .
2 drwxr-xr-x 26 root root 4096 2004-05-11 ..
36890 rws--x--x 1 root root 28004 2004-08-12 cambiarClaves
37820 rwxr-s--x 1 alumno2 alumnosSO 28004 2004-03-16 cambiarClaves2
54321 rw------- 1 root root 80 2004-04-10 claves
46230 rw-r--r-- 1 alumno2 alumnosSO 10 2004-07-22 datos
13357 rw-r--r-- 1 alumno3 alumnosDSO 334 2004-09-23 pre.dat
12356 -rw-r--r-- l root root 16170 2004-05-11 variables.pdf
Los permisos rws------ implican que el propietario del fichero tiene permiso de lectura, escritura y ejecu -
cin y adems que el fichero tiene activado el bit SETUID. Los permisos ---r-s--- implican que el grupo tiene
permiso de lectura y ejecucin y adems que el fichero tiene activado el bit SETGID.
96 Problemas de sistemas operativos
Se va a suponer que el sistema en caso de asignar nuevos nodos-i, lo har utilizando valores crecientes a partir
de 58743 inclusive.
Los ficheros cambiarClaves y cambiarClaves2 son dos ejecutables con el mismo cdigo. A continuacin se des-
cribe el cdigo de ambos:
int main (int argc, char *argv[])
{ int fd, acceso, pos;
struct stat estado;
char buffer[1024]="ABCDEFGHIJKLMNOPQRSTUVWXYZ";

if (argc!=3) {
fprintf(stderr,"Error\n");
exit(1);
}
link (argv[1], argv[2]);

fd = open(argv[2], O_RDWR | O_APPEND);


if (fd>=0)
{
write(fd, buffer, 10);
pos = lseek(fd, 20, SEEK_CUR);
fstat(fd, &estado);
printf("%d\n",estado.st_ino);
}
acceso = access(argv[2], R_OK);
if (acceso ==0)
unlink(argv[1]);
return 0;
}
Responder a las siguientes cuestiones:
Si el usuario alumno1 lleva a cabo la siguiente ejecucin:
$ /fichs/cambiarClaves /fichs/claves /fichs/claves2
a) Cul es el valor de la variable fd?
b) Despus de ejecutar el servicio lseek, cul es el valor de la variable pos?
c) Cul es el valor impreso por la salida estndar?
d) Al final de la ejecucin, Cuntos enlaces fsicos tiene el fichero cuyo nodo-i es 54321?
Si el usuario alumno3 lleva a cabo la siguiente ejecucin:
$ /fichs/cambiarClaves2 /fichs/datos /fichs/datos2
e) Cul es el valor de la variable fd?
f) Al final de la ejecucin, cuntos enlaces fsicos tiene el fichero cuyo nodo-i es 46230?

Solucin
a) Dado que el ejecutable /fichs/cambiarClaves tiene activado el bit SETUID y pertenece al superusuario,
el proceso tiene como identidad efectiva la del superusuario. Por tanto, puede acceder en modo lectura y escritura
sobre el fichero /fichs/claves. La llamada open devolver el descriptor ms bajo disponible del proceso.
b) Dado que la apertura se ha realizado utilizando el modo O_APPEND, el puntero se coloca al final del fichero an-
tes de cada escritura. Por tanto, los 10 bytes del buffer se escriben al final del fichero y a continuacin se posiciona
el puntero 20 bytes ms all del final de dicho fichero. Por tanto, la variable pos toma el valor 110 (80 (tam. Inicial
de fichero) + 10 bytes escritos + 20 (posicionamiento)).
c) El valor impreso por la salida estndar corresponde al valor del nodo-i del fichero cuyo nombre es
/fichs/claves2, que al ser un enlace fsico del fichero con nombre /fichs/claves, tiene el mismo nodo-i.
Por tanto, el valor impreso es: 54321
d) Dado que hemos creado un enlace fsico y no hacemos el unlink correspondiente (access devuelve -1, de-
bido a que comprueba los derechos de acceso de lectura con la identidad real del proceso, es decir, la del usuario
alumno1), el nmero de enlaces del nodo-i 54321 es 2.
97
e) El proceso adquiere como GID efectivo el grupo alumnosSO. No obstante, el grupo slo tiene permisos de lectura
sobre el fichero /fichs/datos y, por tanto, tambin sobre el fichero /fichs/datos2. Esto provoca que la lla-
mada open devuelva -1.
f) Dado que el proceso realiza un link y posteriormente un unlink (access devuelve 0, debido a que com-
prueba los derechos de acceso de lectura del fichero /fichs/datos2 con la identidad real del proceso, es decir,
la del usuario alumno3), el nodo-i 46230 tendr un nico enlace.

Problema 2.12 (febrero 2006)

En un sistema tipo UNIX se dispone de dos sistemas de ficheros A y B, soportados respectivamente en las particio -
nes A y B. El sistema de ficheros B est montado en /usr del sistema A, segn se muestra en la figura 2.10. Dicha
figura muestra, para cada fichero, su nombre despus del montaje, si es un fichero directorio o normal, el nombre
del dueo y de su grupo, as como los permisos.
/
directorio
root, root
rwx r-x r-x Sistema de ficheros A

/lib /bin
directorio directorio Sistema de ficheros B
root, root /usr root, root
rwx r-x r-x directorio rwx r-x r-x
root, root
/lib/fich1 rwx r-x r-x
normal
root, root
rwx r-x r-x

/usr/di1 /usr/di2 /usr/di3


directorio directorio directorio
usu1, gr1 usu2, gr1 usu3, gr2
rwx --x --x rwx --x --x rwx --x ---

/usr/di2/gi1 /usr/di3/fi1 /usr/di3/fi2


normal normal normal
usu2, gr1 usu3, gr2 usu3, gr2
rwx r-x r-x rwx --x --x rwx --x --x
Figura 2.10

Consideraremos tres procesos: el X del usuario usu3 y grupo gr2, el Y del usuario usu1 y grupo gr1, y el Z del
usuario root y grupo root. Inicialmente los procesos solamente tienen ocupadas las tres primeras entradas de sus
tablas de descriptores fd.
Para cada paso de ejecucin indicar todos y cada uno de los puntos siguientes, en caso de no modificarse al -
guno indicarlo expresamente:
1.- El valor devuelto por el servicio (por ejemplo, 0).
2.- Campos modificados en la tabla de descriptores (fd) del o de los procesos involucrados (por ejemplo, se
rellena la entrada nmero 0 de la tabla fd del proceso M).
3.- Campos modificados en la tabla intermedia residente en memoria (por ejemplo, la tabla intermedia no se
modifica).
4.- Modificaciones en nodos-i de ficheros de usuario en la tabla de nodos-i residente en memoria (por ejem -
plo, se incrementa el campo xxx del nodo-i del fichero yyy).
5.- Modificaciones en la particin A (por ejemplo, en la particin A se rellena un nuevo nodo-i y se aade una
entrada en el directorio xxx con el valor yyy).
6.- Modificaciones en la particin B (por ejemplo, la particin B no se modifica).
Los pasos son los siguientes, que se ejecutan en el orden dado:
a) Proceso X: fd1 = open ("/usr/di2/gi1", O_RDONLY);
b) ProcesoY: fd1 = open ("/usr/di2/fi1", O_RDONLY);
c) Proceso X: pid = fork ( ); ,ejecuta con xito, generando el proceso XH
d) Proceso XH: fd1 = open("/lib/fich1", O_RDWR);
e) Proceso Y: li = symlink ("/lib/fich1", "/usr/di1/fich1");
f) Proceso XH: li = link ("/usr/di2/gi1", "/usr/di3/fich1");
g) Proceso XH: fd2 = dup(3);
98 Problemas de sistemas operativos
h) Proceso Y: fd2 = open ("/usr/di1/fich1", O_RDONLY);
i) Proceso Z: lf = unlink ("/lib/fich1");
j) Proceso Y: n = read(fd2, buf, 128);

Solucin
a) En principio, el servicio ejecutar correctamente, puesto que se tienen permisos para llegar al fichero y para leer
de l. Por tanto:
1.-Devuelve 3 (el enunciado dice que solamente estn ocupados los fd estndar).
2.- Se modifica la entrada cuarta de la tabla de descriptores, incluyendo el valor de la entrada en la tabla interme -
dia.
3.- Se rellena una entrada en la tabla intermedia con el nmero de nodo-i del fichero /usr/di2/gi1, puntero
0 y n de referencias 1.
4.- Se aade el nodo-i del fichero en la tabla de memoria (si no estaba antes) y n de opens 1. Si el nodo-i ya
estaba en la tabla, se incrementa el n de opens.
5.- y 6.- No se modifican.
b) El servicio fracasa puesto que no existe el fichero, por lo que:
1.- Devuelve -1.
2.-, 3.-, 4.-, 5.- y 6.- No se modifican.
c) Nos dicen que el fork se ejecuta con xito, luego:
1.- X recibe el PID de XH y XH recibe un 0.
2.- Se crea la tabla de descriptores de XH, copindola de X.
3.- Se incrementan los n de referencias de todos las entradas de la tabla intermedia referenciadas en la tabla de
descriptores de HX.
5.- y 6.- No se modifican.
d) El servicio fracasa puesto que usu3 y gr2, si bien tienen permisos para llegar al fichero, no tienen derechos de es -
critura sobre el fichero.
1.- Devuelve -1.
2.-, 3.-, 4.-, 5.- y 6.- No se modifican.
e) En principio, el servicio se ejecuta con xito, puesto que se tienen permisos para llegar al fichero
"/usr/di1/fich1", as como permisos de escritura en el mismo. Dado que es un enlace simblico no se com-
prueba "/lib/fich1". Por lo que:
1.- Devuelve 0.
2.-,3.- y 4.- No se modifican.
5.-No se modifica, no se incrementa el n de enlaces del correspondiente nodo-i.
6.-Se utiliza un nuevo nodo-i que se marcar como enlace simblico y que indicara un bloque que contendr el
enlace, es decir, la cadena: /lib/fich1. Se aade una entrada en el directorio /usr/di1 que asocia el nombre
fich1 y el nmero de nodo-i anterior. Tambin se actualiza el tiempo de modificado en el nodo-i del directorio
/usr/di1.
f) En principio, el servicio se ejecuta con xito, puesto que se tienen permisos para llegar al fichero
"/usr/di3/fich1" as como permisos de escritura en el mismo, Por otro lado, se tienen derechos de acceso en
el camino "/usr/di2/" y existe el fichero gi1, por lo que se puede obtener su nodo_i. Por tanto:
1.- Devuelve 0.
2.- ,3.-, 4.- y 5.- No se modifican.
6.- Se modifica el nodo-i del fichero /usr/di2/gi1 incrementado el n de enlaces. Se aade una entrada en el
directorio /usr/di3 que asocia el nombre fich1 y el nmero de nodo-i del fichero /usr/di2/gi1. Tambin
se actualiza el tiempo de modificado en el nodo-i del directorio /usr/di3.
99
g) En principio, el servicio se ejecuta con xito, dado que el descriptor 3 es vlido (fue heredado). Como estn utili -
zadas las entradas 0, 1, 2 y 3, se asigna la 4.
1.-Devuelve 4.
2.- Se modifica la entrada quinta de la tabla de descriptores, copiando el valor de la entrada en la tabla intermedia
que tiene la entrada cuarta.
3.- Se modifica la correspondiente entrada en la tabla intermedia incrementado el n de referencias.
4.-, 5.- y 6.- No se modifican
h) En principio, el servicio ejecutar correctamente, puesto que se est abriendo el fichero enlazado en el paso e) y
se tienen derechos, por tanto:
1.-Devuelve 3 (el enunciado dice que solamente estn ocupados los fd estndar).
2.- Se modifica la entrada cuarta de la tabla de descriptores, incluyendo el valor de la entrada en la tabla interme -
dia.
3.- Se rellena una entrada en la tabla intermedia con el nmero de nodo-i del fichero /lib/fich1, puntero
0 y no de referencias 1. Observe que el nodo-i es el del fichero real, no el del enlace simblico.
4.-Se aade el nodo-i del fichero /lib/fich1 en la tabla de nodos-i en memoria (si no estaba antes) y n de
opens 1. Si el nodo-i ya estaba en la tabla, se incrementa el n de opens.
5.- y 6.- No se modifican.
i) En principio, el servicio se ejecuta con xito. El fichero est abierto y seguir abierto. No se borra hasta que no
tenga ningn usuario, por lo que:
1.-Devuelve 0.
2.- y 3.- No se modifican.
4.- En la tabla de nodos-i en memoria se decrementa el n de enlaces del nodo-i del fichero /lib/fich1. No se
elimina este nodo-i, puesto que todava tiene usuarios como es el proceso Y.
5.-Se modifica el nodo-i del fichero /lib/fich1 decrementado el n de enlaces. Si el contador de enlaces lle-
ga a 0, en principio, habra que recuperar los recursos asignados al fichero (nodo-i y bloques). Sin embargo, el nodo-
i en memoria tiene n de opens = 1 (paso h), por lo tanto, el fichero no se puede borrar an. Cuando el proceso Y cie-
rre dicho fichero o termine, es cuando se recuperarn realmente el nodo-i y los bloques. S se borra la entrada de va-
lor fich1 en el directorio /lib. Tambin se actualiza el tiempo de modificado en el nodo-i del directorio /lib.
6.- No se modifica.
j) En principio, el servicio se ejecuta con xito, puesto que el proceso tiene abierto el fichero, por lo que:
1.- Devuelve 128, siempre que el fichero tenga datos suficientes.
2.- No se modifica.
3.-Se incrementa el puntero de posicin de la entrada correspondiente en la tabla intermedia.
4.-, 5.-y 6.- No se modifican. La actualizacin de los tiempos de ltimo acceso dependen de la implementacin,
por lo que no se han contemplado.

Problema 2.13 (abril 2006)

En un sistema consideraremos los tres usuarios juan (uid 100, gid 100), pablo (uid 101, gid 100) y fran (uid 202,
gid 200). En dicho sistema, los contenidos en el instante X de algunas agrupaciones (no todas) y algunos nodos-i
(no todos) de dos dispositivos hda y sda que tiene el sistema son los mostrados en las tablas adjuntas. Adems, el
sistema de ficheros sda est montado sobre /home del hda.
Dispositivo hda
Agrupacin Contenido (nombre - n nodo_i)
3 . 2; .. 2; usr 121; home 243; etc 453; boot 54; dev 38; sys 15; sbin 73
62 . 73; .. 2; link 174;...
100 Problemas de sistemas operativos

73 . 243; .. 2; abc 742; tyz 375


213 abcdefghijk...
532 ^?ELF^A^A^A^@^@^@^@^@^@^@^@^@^B^@^C^@^A^@^@^@P.
7 . 453; .. 2; passwd 13;
56 root:x:0:0:Superusuario:/root:/bin/bash

Nodo-i Dueo (UID-GID) Permisos Agrupaciones Tamao N enlaces


2 Root (0-0) d rwx r-x r-x 3 180 9
243 Root (0-0) d rwx r-x r-x 73 273 2
73 Root (0-0) d rwx r-x r-x 62 512 2
174 Root (0-0) rwx r-x r-x 532, 732, 555, . 270053 1
742 Root (0-0) rw- r-- r-- 213 654 1
453 Root (0-0) d rwx r-x r-x 7 304 9
13 Root (0-0) rw- r-- r-- 56 978 1
375 Root (0-0) rw- r-- r-- 333
Dispositivo sda
Agrupacin Contenido
3 . 2; .. 2; juan 15; pablo 243; fran 453
25 .15; ..2; j1 460; j2 461
30 .243; ..2; p1 701; p2 650
43 .453; ..2; f1 534; f2 475
67 000
123 671482
234 000000

Nodo-i Dueo (UID-GID) Permisos Agrupaciones Tamao N enlaces


2 Root (0-0) d rwx r-x r-x 3 345 5
15 juan (100-100) d rwx r-x r-x 25 274 2
243 pablo (101-100) d rwx rwx r-x 30 325 2
453 fran (202-200) d rwx r-x r-x 43 156 2
460 juan (100-100) rw- rw- --- 234 6 1
701 pablo (101-100) rw- rw- rw- 123 4 1
475 fran (202-200) rw- r-- r-- 67 3 1
En el mismo instante X, el sistema operativo tiene, entre otras, las siguientes tablas de descriptores
Proceso 273 (juan) Proceso 678 (pablo) Proceso 534 (fran)
fd Entrada Tabla Inter- fd Entrada Tabla Inter- fd Entrada Tabla Inter-
media media media
0 1 0 3 0 5
1 2 1 4 1 6
2 2 2 4 2 6
3 0 3 8 3 7
4 0 4 0 4 0
5 0 5 10 5 9
6 0 6 0 6 0
Y la siguiente tabla intermedia:
Nodo-i Posicin Referencias
..... ..... ..... .....
7 sda-460 2 1
101
8 sda-701 2 1
9 sda-701 1 1
10 sda-460 0 1
11 ..... ..... .....
Partiendo de la situacin de las tablas descritas anteriormente, responder a las siguientes preguntas.
a) El proceso 534 ejecuta la clusula siguiente: fd = open ("/home/abc", O_RDWR);
Qu valor devuelve el servicio? Por qu?
b) Para que el proceso 678 abra para lectura con xito el fichero de nombre local j1 (n nodo_i = sda-460),
cul debe ser el valor del primer argumento del open?
c) El superusuario podra realizar la operacin de umount del sistema de ficheros sda en el instante X?
d) El proceso 534 ejecuta: n = write(5, "65414",2);
seguidamente el proceso 678 ejecuta: n = write(3, "3453",4);
Indique los cinco primeros caracteres contenidos en el fichero de n nodo_i = sda-701
e) El proceso 678 crea un hijo (proceso 789) y, seguidamente, se ejecutan las siguientes escrituras:
Proceso 789: n = write(5, "21345", 2);
Proceso 678: n = write(5, "18212", 3);
Indique los cinco primeros caracteres contenidos en el fichero de n nodo_i = sda-460
f) El programa /sbin/link es el mandato de sistema link que produce enlaces fsicos siendo el primer argu -
mento el fichero existente. El usuario fran est ejecutando un interprete de mandatos (proceso 345) y desea estable-
cer un enlace: Cul de los siguientes mandatos producira realmente un enlace?
$ /sbin/link /home/juan /home/fran/juan
$ /sbin/link /etc/passwd /home/fran/pw
$ /sbin/link /home/juan/j1 /home/juan/j3
$ /sbin/link /home/juan/j2 /home/fran/j2
g) Cuntos subdirectorios tiene el directorio /etc.
h) El proceso 273 tiene como mscara 011 y el proceso 678 tiene 033. El proceso 273 ejecuta:
fd = open("/home/pablo/dat.txt", O_WRONLY|O_CREAT|O_TRUNC, 0777);
Indicar si se crea o no el fichero, y en caso de crearse los permisos con los que se crea.

Solucin
a) Al estar montado el dispositivo sda sobre /home del hda, los subdirectorios y ficheros del /home/ dejan de ser
accesibles, por lo que nadie puede abrir el fichero "/home/abc". El usuario fran puede atravesar el directorio
/home/ , pero no encuentra el fichero abc. Si se desmontase el dispositivo sda volvera a ser accesible el mencio-
nado fichero abc.
b) El valor ha de ser: "/home/juan/j1"
c) No se puede desmontar el dispositivo sda puesto que hay ficheros del mismo abiertos, como se observa en la tabla
intermedia del enunciado.
d) El resultado es 66345
e) El resultado es 21182
f) Para poder hacer un enlace fsico es necesario disponer de permisos de escritura en el directorio destino y tener
permisos de acceso al fichero origen. El nico mandato que cumple estas condiciones es el siguiente:$ /sbin/link
/home/juan/j1 /home/juan/j3
g) El directorio /etc tiene 7 subdirectorios. Dado que tiene 9 enlaces, de los cuales uno pertenece al nombre etc
que se encuentra en la tabla directorio raz / y otro pertenece al nombre . de la tabla directorio /etc/. Los 7 restan-
tes corresponden al nombre .. de otros tantos subdirectorios del /etc/.
102 Problemas de sistemas operativos
h) El fichero, en principio, se crea puesto que el usuario juan tiene permisos de acceso al directorio /home/pablo
y permisos de escritura en el mismo. Teniendo en cuenta que la mscara del proceso 273 es 011 y que en el open se
solicitan los permisos 0777, los permisos con los que se crea el fichero dat.txt, son 0766

Problema 2.14 (junio 2006)

Se desea implementar un spooler, un programa que recorre un directorio de trabajos pendientes


(/var/spool/works/) y los enva a otro programa que los ejecuta. Cuando se desea que un trabajo se lance
por medio del spooler se escribe un nuevo fichero en el directorio compartido, que este proceso inspeccionar pe -
ridicamente. Las caractersticas del funcionamiento del programa spooler son las siguientes:
Durante el recorrido del directorio se comprueba si hay nuevas entradas (saltndose las entradas . y ..).
Esta operacin la realizar la funcin recorrer_directorio.
La verificacin de si hay trabajos pendientes en el directorio se ejecuta cada segundo. La funcin main pro-
gramar la temporizacin y preparar a la funcin anterior (recorrer_directorio) para que se ejecu-
te en ese momento.
Por cada fichero encontrado en el directorio se debe ejecutar el programa /bin/ejecutor, al que se le
pasar por su entrada estndar el contenido del fichero. La ejecucin de este programa se har desde la fun-
cin procesar_fichero.
El fichero de un trabajo, una vez procesado, se debe borrar. Esto tambin lo har la funcin
procesar_fichero.
a) Implementar la funcin recorrer_directorio
b) Implementar el mecanismo de temporizacin.
c) Implementar la funcin procesar_fichero.
d) La implementacin de este spooler presenta una posible condicin de carrera, ya que podra estar a medias
de escribir un fichero en el directorio mientras el spooler lee, manda el trabajo y lo borra. Sin implementar
nada, proponga cul sera un buen mecanismo para asegurar la sincronizacin del spooler y los potenciales
procesos pesados no emparentados que escriben trabajos en el directorio compartido.
e) En la escritura y posterior borrado de trabajos se plantea otro problema. Los usuarios que escriben trabajos
en el directorio no tienen por qu ser los mismos que el usuario que ejecuta el spooler (que ser, probable-
mente, un usuario del sistema llamado spool). Supongamos que los usuarios usan un programa llamado
submit, para programar trabajos (escribirlos en el directorio), y que el procesamiento es por medio del pro-
grama spooler que ya hemos visto. Cules seran las consideraciones sobre propietarios y permisos que ha-
bra que tener en cuenta en los ficheros, directorios y programas que participan?

Solucin
a) Complete el cdigo de la funcin recorrer_directorio para que realice las operaciones descritas anterior-
mente.
void recorrer_directorio(char* dir)
{
/* Declaracin de variables */
DIR* dir_handler;
struct dirent* entrada;
char buffer[1024];

/* Apertura del directorio */


dir_handler=opendir(dir);

while((entrada=readdir(dir_handler))!=NULL)

{
/* Verificacin de la entrada (saltarse . y ..), e invocacin de la funcin procesa_fichero(...) */
if((strcmp(entrada->d_name,"..")==0)||(strcmp(entrada->d_name,".") ==0))
103
continue;

sprintf(buffer,"%s/%s",dir, entrada->d_name);
procesar_fichero(buffer);
}
/* Cierre del directorio */
closedir(dir_handler);
}
b) La funcin recorrer_directorio se debe ejecutar con la periodicidad descrita anteriormente. Implemente
la programacin de esa periodicidad (sin usar sleep o cualquier otro mecanismo de espera bloqueante, ya que se
puede requerir del proceso que ejecute otras operaciones mientras no se active la temporizacin).
int main(int argc , char *argv[])
{
/* Temporizacin */
signal(SIGALARM,manejador);
alarm(1);
/* Seguira haciendo operaciones *)
...
}
/* Funciones auxiliares */
void manejador (int sig)
{
recorrer_directorio("/var/spool/work");
signal(SIGALARM,manejador); // Volver a montar la seal
alarm(1);
}
c) Complete el cdigo de la funcin procesar_fichero para que realice las operaciones descritas anteriormen-
te.
void procesar_fichero(char* fichero)
{
/* Declaracin de variables */
int pp[2];
char buffer[4096];
int fd, leidos;

/* Construccin del mecanismo de comunicacin entre ambos procesos */


pipe(pp);

switch(fork())
{
case -1:
/* Error */
perror("fork()");
exit(1);

case 0:
/* Hijo */
close(0);
dup(pp[0]);
close(pp[0]);
close(pp[1]);
execlp("ejecutor",NULL);
perror("execlp()");

default:
/* Padre */
close(pp[0]);
if((fd=open(fichero,O_RDONLY))<0))
perror("open()"); exit();
while((leidos=read(fd,buffer,4096))>0)
104 Problemas de sistemas operativos
write(pp[1],buffer,leidos);
close(pp[1]);
wait(NULL);
}
/* Borrado del fichero */
unlink(fichero);
}
Existe una implementacin alternativa a este apartado c) que es ms compacta
void procesar_fichero(char* fichero)
{
/* Declaracin de variables */
int fd;

switch(fork())
{
case -1:
/* Error */
perror("fork()");
exit(1);

case 0:
/* Hijo */
close(0);
if((fd=open(fichero,O_RDONLY))<0))
perror("open()"); exit();
execlp("ejecutor",NULL);
perror("execlp()");

default:
/* Padre */
wait(NULL);
}
/* Borrado del fichero */
unlink(fichero);
}
d) Hay varias alternativas a este nivel, lo fundamental es que hay que considerar que los procesos que participan, el
spooler y los procesos que dejan trabajos en el directorio no estn emparentados. De esta forma se tiene que optar
por un mecanismo que valga para ese caso.
Las alternativas posibles seran (todas ellas vlidas):
Semforos con nombre: Creado por el spooler y cuyo nombre es conocido por los otros programas.
Este semforo gestionara la regin crtica en un modelo lectores-escritores.
Un pipe con nombre (o FIFO). Se podra crear en el mismo o en otro directorio y podra tener un byte de
contenido que hara de token para acceder al directorio. Antes de hacer operaciones sobre el mismo lee
del FIFO, si lee un byte (el FIFO se queda vaco) y se pueden hacer las operaciones sobre el directorio.
Al salir del directorio y terminar de hacer las operaciones se vuelve a escribir el byte en el FIFO. Este
mecanismo asegura exclusin mutua al ser operaciones atmicas el read y write de ese tamao.
Una ltima alternativa podra ser el uso de cerrojos de ficheros. Esta alternativa no se ha visto en el cur-
so, pero es muy utilizada en procesos de sistema. Las llamadas flock() y lockf() permiten estable-
cer cerrojos sobre ficheros.
e) La opcin ms sencilla es hacer que el propietario del directorio /var/spool/work sea el propio usuario
spool. De forma que, al menos el permiso de escritura, sea slo para este usuario, el propietario del directorio. Lo
que se hara es que el programa submit tenga activado el bit s (setuid), de forma que la identidad efectiva de
cualquiera que ejecute ese programa sea la del propietario del mismo. Es decir que cualquiera que ejecute submit
se convierta en el usuario spool de forma efectiva. De esta forma el programa podr escribir en el directorio nue -
vas entradas sin problemas.
105
Cualquier otra solucin es problemtica, por ejemplo que todos los usuarios de un grupo o todos los de una m -
quina puedan escribir en el directorio, porque implicara que programas que no slo ponen trabajos en el directorio
pudieran manipular su contenido. Uno podra copiar, mover o renombrar sus ficheros en ese directorio. Adicional -
mente el spooler no podra borrar los ficheros de otros usuarios al no ser propietario de los mismos (como ocurre
en el /tmp).

Problema 2.15 (septiembre 2006)

El programador A debe escribir el cdigo de un ejecutable denominado escribirRodaja que cumpla los siguientes
requisitos:
1. Recibe tres argumentos: el nombre de un fichero y dos nmeros enteros, que representan dos des-
plazamientos (offsets) dentro del fichero: desp1 y desp2. Se supone que 0 desp1 desp2.
2. Si el fichero no existe, lo crea con un tamao igual a desp2 y con contenido todos los bytes a cero.
3. Si el fichero existe y su tamao es superior a desp2, se leen los bytes existentes entre desp1 y desp2
(mbos inclusive) y se escriben por la salida estndar. En el caso de que los dos desplazamientos, desp1 y
desp2, sean iguales, slo se escribe el byte correspondiente.
4. Si el fichero existe y desp2 es superior o igual al tamao del fichero, se cierra dicho fichero y no se
escribe nada por la salida estndar.
El programador A desea estructurar de forma modular el cdigo fuente correspondiente al ejecutable escribi-
rRodaja, haciendo uso, entre otras, de las dos siguientes funciones:
int crear_fichero(char *nombre_fich, int tamanyo);, que se utiliza para resolver el requisito nmero 2. Esta
funcin recibe como argumentos el nombre y el tamao del fichero y crea un nuevo fichero con dicho nombre
y tamao y relleno a ceros. La funcin devuelve el descriptor del nuevo fichero creado o -1 en caso de error.
int leer_fichero_despl (int fd, int desp_min, int desp_max);, que se utiliza para resolver el requisito nmero 3.
Esta funcin recibe un descriptor de fichero y dos desplazamientos, uno mnimo y otro mximo. La funcin
lee del fichero a partir de la posicin desp_min hasta desp_max y escribe dichos bytes por la salida estndar.
La funcin devuelve el nmero de bytes escritos por la salida estndar o -1 en caso de error. Se supone que
0 desp_min desp_max < tamao del fichero.
Ponindose en el caso del programador A:
a) Implementar la funcin int crear_fichero(int tamanyo); correspondiente al ejecutable escribirRodaja.
b) Implementar la funcin int leer_fichero_despl (int fd, int desp_min, int desp_max); correspondiente al ejecu-
table escribirRodaja.
El programador B recibe el ejecutable escribirRodaja, teniendo que programar el cdigo correspondiente a otro
ejecutable (llamado crearSubficheros), que haga lo siguiente:
Recibe dos argumentos: el nombre de un fichero y un nmero entero mayor que 0.
El segundo argumento representa el nmero de subficheros en los que se debe dividir el contenido del fichero
de entrada, al que hace referencia el primer argumento. La concatenacin del contenido de todos los subfi -
cheros, ordenados por nombre o sufijo, debe ser igual al contenido del fichero de entrada. Los nombres de es-
tos subficheros son iguales a los del fichero de entrada, aadindoles el sufijo "_x", donde x vale 0 para el
primer subfichero, 1 para el segundo y as sucesivamente. Para crear los nombres de los subficheros se dis-
pone de una funcin char * crear_nombre_subfichero (char *nombre_fich, int numero); que dado un nombre
de fichero y un nmero devuelve el nombre del subfichero correspondiente. Esta funcin NO HAY QUE IM -
PLEMENTARLA.
El ejecutable crearSubficheros divide el nmero de bytes del fichero original por el nmero de subficheros
para calcular cuntos bytes secuenciales deben escribirse en cada subfichero. Se supone que el nmero de
bytes del fichero original es mltiplo exacto del nmero de subficheros.
Se supone que el usuario que ejecute el programa tiene permisos de lectura del fichero de entrada y permisos
de escritura sobre el directorio donde est el fichero de entrada y donde debe crear los subficheros.
Ponindose en el caso del programador B:
c) Implementar el cdigo correspondiente al ejecutable crearSubficheros, haciendo uso del ejecutable escribi-
rRodaja y de la funcin crear_nombre_subfichero.
106 Problemas de sistemas operativos
d) Suponga que se desea implementar el cdigo del ejecutable crearSubficheros sin hacer uso del ejecutable es-
cribirRodaja y utilizando una solucin multihilo, en la cual cada hilo se encargue de resolver la escritura de
cada subfichero. Qu ventajas y desventajas ofrecera esta solucin respecto a la solucin del apartado an-
terior?

Solucin
a) Hay varias alternativas. Dos de ellas son:
int crear_fichero_v1(char *nombre_fich, int tamanyo)
{
int fd;
fd = creat(nombre_fich, 0666);
ftruncate(fd, tamanyo);
return fd;
}

int crear_fichero_v2(char *nombre_fich, int tamanyo)


{
int fd;
char byte = 0;
fd = creat(nombre_fich, 0666);
lseek(fd, tamanyo-1, SEEK_SET);

write(fd, &byte, 1);


return fd;
}
b)
int leer_fichero_despl(int fd, int desp_min, int desp_max)
{
char *buffer;
int escritos = 0;

buffer = (char *) malloc(desp_max-desp_min+1);


if (!buffer)
return -1;

lseek(fd, desp_min-1, SEEK_SET);


escritos = desp_max-desp_min+1;
read(fd, buffer, escritos);
write(1, buffer, escritos);

free(buffer);
return escritos;
}
c)
// ejecutable crearSubficheros
int main (int argc, char *argv[])
{

int num_subfich;
int i;
int fd;
char *nombre_subfich;
char desp1[6];
char desp2[6];
struct stat inf_fich;
int num_bytes_total, num_bytes_fich;
int offset;

if (argc != 3)
107
{
fprintf(stderr, "Error: Uso: ./crearSubficheros nombre_fich
num_subfich\n");
return 1;
}

num_subfich = atoi(argv[2]);
if (num_subfich <=0)
{
fprintf(stderr, "Error: Numero de subficheros debe ser mayor que 0\n");
return 1;
}

stat(argv[1], &inf_fich);
num_bytes_total = inf_fich.st_size;
num_bytes_fich = num_bytes_total/num_subfich;

offset = 0;

for (i=0; i<num_subfich; i++)


{
switch(fork())
{
case -1: perror("fork");
return 1;
case 0:
nombre_subfich=crear_nombre_subfichero(
argv[1]);
close(1);
fd=creat(nombre_subfich, 0666);
sprintf(desp1, "%d",offset);
sprintf(desp2, "%d", offset+num_bytes_total);
execlp("escribirRodaja", "escribirRodaja", argv[1], desp1, desp2, NULL);
perror("execlp escribirRodaja 1");
close(fd);
return 2;
default:
offset = offset+num_bytes_fich;
}
}
for (i=0; i<num_subfich; i++)
wait(NULL);

return 0;
}
d) En general, el uso de threads es adecuado para soluciones concurrentes, que permitan dividir un proceso en tareas
que se puedan ejecutar concurrentemente dentro del propio proceso. En este tipo de escenario, los threads pueden
mejorar el rendimiento de la solucin, dado que la creacin de un proceso pesado implica mayor carga que la crea-
cin de un thread. No obstante, en este caso, al tratarse de la lectura del fichero de entrada, habra problemas para
sincronizar la lectura del mismo de una forma correcta. La creacin de los ficheros de salida no sera un problema.
Si se siguiera la filosofa de la solucin anterior (utilizando escribirRodaja) tendramos problemas, dado que todos
los procesos ligeros tendran la misma salida estndar y se mezclaran los resultados. Si se hace de otro modo, ten-
dramos que sincronizar el acceso al fichero de entrada.

Problema 2.16 (febrero 2007)

Se desea implementar un programa que cifre los contenidos de todos los ficheros de un directorio. El programa re -
cibir dos argumentos: (1) el nombre de un ejecutable que har de cifrador y (2) el directorio a cifrar. El programa
108 Problemas de sistemas operativos
no tiene que cifrar subdirectorios, slo los ficheros que se encuentre en el directorio pasado como argumento. Las
caractersticas del funcionamiento del programa son las siguientes:
Se recorre el directorio dado con la funcin recorrer_directorio.
Durante el recorrido del directorio, por cada fichero que se localice, se llamar a la funcin cifrar_fi-
chero.
Para cifrar el fichero se ejecutar el cifrador, este ejecutable recibe por la entrada estndar los conteni-
dos a cifrar y genera por la salida estndar dichos contenidos cifrados. Esta tarea la realiza la funcin
cifrar_fichero.
Al final de la ejecucin, el directorio debe contener los mismos nombres de ficheros, pero sus contenidos
deben de estar cifrados.
a) Implementar la funcin recorrer_directorio.
b) El proceso de cifrado manipula dos ficheros, el original y el cifrado. Potencialmente, que estos dos ficheros
tengan el mismo nombre puede ser un problema. Considerando que no se puede dar un fallo o cada en el
programa durante el proceso de cifrado, existen tres alternativas para hacerlo:
1. Renombrar el fichero original, crear el fichero cifrado con el antiguo nombre del original, cifrar y
finalmente borrar el fichero original renombrado. (Usando rename).
2. Abrir el fichero original, borrar el nombre de fichero mantenindolo abierto y crear el fichero ci-
frado con dicho nombre. Luego cifrar y cerrar los ficheros. (Usando unlink)
3. Abrir el fichero original, enganchar la salida del cifrador a una tubera (pipe), cifrar, borrar el fi-
chero original y crear un fichero con el mismo nombre y volcar el contenido del pipe a dicho fichero. (Usan-
do pipe).
Alguna de estas alternativas no funcionara correctamente? Indique para cada alternativa sus ventajas e
inconvenientes.
c) Implementar la funcin cifrar_fichero.
d) Si reconsideramos el apartado b), plantendonos la posibilidad de que el proceso falle bajo las siguientes
condiciones:
d.i) Que el proceso de cifrado y/o el que recorre el directorio se caiga en cualquier punto de la ejecu-
cin.
d.ii) Que el espacio en disco sea muy escaso y pueda agotarse, sobretodo al cifrar ficheros de gran ta-
mao.
Bajo estos escenarios, comente las alternativas 1, 2 y 3, del apartado b) bajo estas dos posibles causas de
error Cmo calificara la fragilidad de cada una de las soluciones?

Solucin
a)
int main(int argc, char *argv[])
{
if(argc!=3)
{
fprintf(stderr,"uso: %s prog_cifrador directorio\n",argv[0]);
return 1;
}
recorrer_directorio(argv[2],argv[1]);
return 0;
}

void recorrer_directorio(char* cifrador, char* directorio)


{
DIR* dir;
struct dirent* ent;
char buff[MAXPATHLEN];

dir=opendir(directorio);

while((ent=readdir(dir))!=NULL)
{
109
/* Si son las entradas . y .. nos las saltamos */
if(!strcmp(ent->d_name,".") || !strcmp(ent->d_name,".."))
continue;
/* Componemos el path del directorio */
sprintf(buff,"%s/%s",directorio,ent->d_name);
cifrar_fichero(buff,cifrador);
}
closedir(dir);
}
b) Todas las alternativas funcionan correctamente, la diferencia radica en cmo utiliza los recursos (bloques de disco
y espacio en los directorios), en el caso de b.1) lo que se hace es duplicar los ficheros, es decir mantener el original
bajo otro nombre e ir construyendo el cifrado. Esta opcin, como se puede observar es menos eficiente en el consu-
me de recurso (espacio en disco y entradas de directorio). La solucin es quizs la ms intuitiva y la que puede gene-
rar menos problemas.
La opcin b.2) aparentemente parece que puede dar un error, pero no es as. Un fichero cuando se borra no se li -
berar sus bloques de datos del disco hasta que lo ha cerrado el ltimo programa que lo ha abierto. Lo que s que es
verdad, es que el nombre del fichero, la entrada dentro del directorio se elimina, de forma que en el intervalo entre el
borrado (unlink) y el momento en el que finalice el proceso que lo tiene abierto ningn otro proceso puede acceder
al fichero. El consumo de bloques de disco es el mismo, lo bloques originales no se liberan hasta el close del fichero
y los nuevos contenidos del fichero ya codificado empezarn a consumir bloques. Un aspecto interesante de esta al-
ternativa es que la solucin es ms elegante al no aparecer varios nombres de fichero en el directorio y haciendo que
los ficheros en fase de cifrado slo sean accesibles por el proceso que realiza la operacin.
La opcin b.3) puede parecer an ms refinada que la anterior, aqu los bloques de disco se liberan antes de es-
cribir el nuevo fichero. Comparte con la solucin anterior el no generar nombre intermedios de ficheros o entradas
extraas en el directorio, pero esta vez usando el buffer del pipe como almacn de los datos. Esta solucin sin em-
bargo s tiene un claro problema que puede hacer que no sea vlida. La capacidad de almacenamiento del pipe es li-
mitada, si se intentan meter ms datos de los 4 KiB que generalmente tiene como espacio disponible hace que el
write se bloquee. Esto hara que el programa cifrador se quedase tambin bloqueado. Es, con diferencia, la peor de
las soluciones.
b.1) La ms segura
b.2) La ms refinada
b.3) No funciona en muchos casos.
c)
void cifrar_fichero(char* fichero, char* prog_cifrador)
{
/* Seleccionada la opcin b.2), borrar el fichero abierto */

switch(fork())
{
case -1: /* Error */
perror("fork");
exit(1);
case 0: /* Hijo */
/* Cerramos la entrada estndar */
close(0);
/* Al abrir el fichero toma el descriptor 0 */
open(fichero, O_RDONLY);
/* Borrar el fichero, no se liberan los bloques hasta que
se haga el close del fichero que est abierto */
unlink(fichero);
/* Cerramos la salida */
close(1);
/* Abrirmos el descriptor 1 como el mismo nombre de
fichero */
creat(fichero, 0660);
execlp(prog_cifrador, prog_cifrador, NULL);
perror("execlp");
exit(1);
110 Problemas de sistemas operativos
default:
wait(NULL);
}
}
d. i) Que el proceso de cifrado y/o el que recorre el directorio se caiga en cualquier punto de la ejecucin.
En este caso las alternativas b.2 y b.3 pueden plantear problemas, en ambos casos el contenido del fichero origi -
nal se perdera y nos quedara una versin parcial del cifrado.
La opcin d.1 evita ese problema, si se corta el proceso quedara dos ficheros, el original y parte del contenido
del cifrado, bastara con borrar el cifrado, volver a renombrar el original y repetir el proceso.
d. ii) Que el espacio en disco sea muy escaso y pueda agotarse, sobretodo al cifrar ficheros de gran tamao.
Aqu el problema con b.1 y b.2 es el mismo, aunque b.2 borre el nombre del fichero sus bloques de datos siguen
existiendo y al ir cifrando se duplicara este espacio de disco.
La opcin b.3 no tiene ese problema pero, como ya vimos, su funcionamiento est restringido a ficheros que en -
tren en el buffer del pipe (4 KiB). Esa restriccin no se cumple en ficheros grandes as que no es tampoco una buena
alternativa.
Quizs la mejor solucin efectiva a este caso sera almacenar el fichero en memoria y desde ella cifrar y sobres -
cribir el fichero. La nica restriccin sera la memoria virtual disponible para los procesos (a priori, la memoria fsi -
ca ms el espacio de swap, siempre y cuando los procesos de usuario no tengan restricciones adicionales por parte
del administrador).

Problema 2.17 (abril 2007)

Dado el siguiente cdigo:


1 int main(int argc, char *argv[])
2 {
3 int fd1, fd2, bytes;
4 char ch1, ch2, buff[64];
5 umask(0022);
6 fd1=open("f.txt", O_CREAT|O_RDWR|O_TRUNC, 0660);
7 fd2=open("f.txt", O_RDWR);
8
9 write(fd1,"9876543210",6);
10 lseek(fd1,2,SEEK_SET);
11 read(fd1,&ch1,1);
12 /* INSTANTE A */
13
14 lseek(fd2,-2,SEEK_END);
15 read(fd2,&ch2,1);
16 write(fd2,&ch2,1);
17 /* INSTANTE B */
18
19 bytes=read(fd1,buff,64);
20 /* INSTANTE C */
21
22 ...
23 }
a) Con qu bits de proteccin se creara el fichero f.txt? Indquelo en formato numrico octal.
b) Suponiendo que el proceso tiene abiertos slo los descriptores por defecto (entrada, salida y error estndar),
cul sera la situacin de las estructuras de gestin de ficheros tras la realizacin de los dos open?
c) En el INSTANTE A, qu dgito se habr ledo en la variable ch1?
d) Si se hiciera un lseek ms all del tamao actual del fichero qu ocurre?
e) En el INSTANTE B, qu dgito se habr escrito mediante la ltima llamada write?
111
f) Si tras haber hecho los open, otro proceso quiere borrar el fichero f.txt, qu ocurre?
g) En el INSTANTE C, cuntos bytes se habrn ledo del fichero en esa ltima operacin?
h) Supongamos que f.txt ya existiese (siendo del mismo usuario y con permisos de lectura y escritura).Qu
ocurrira si fuese un enlace?
i) Si despus del INSTANTE C se ejecuta:
if(fork())
close(fd1);
Qu ocurre?

SOLUCIN
a) El fichero se intenta crear con proteccin 0660, pero antes de ello se ha definido una mscara de proteccin 0022.
Esta mscara representa los bits que quedan excluidos (la operacin lgica aplicable sera 0660 & ~0022). Los per-
misos resultantes son 0640.
b) Los descriptores 3 y 4 estaran asignados, apuntando a dos entradas diferentes de la tabla intermedia, pero ambas
apuntaran a la misma copia del i-nodo. Cada open, siempre implica una nueva entrada en la tabla intermedia. Hay
que recordar que esta tabla incluye la posicin del puntero de lectura/escritura. Por lo tanto, para cada descriptor de
fichero tendramos.
c) Hay que tener en cuenta que el primer write, aunque tiene como buffer de partida una cadena de 10 elementos
slo se escriben los 6 primeros 987654. Despus de esa operacin el fichero estar al final de esa cadena. Sin em -
bargo, se hace un lseek a la posicin absoluta (SEEK_SET) 2. Es decir, sobre el 7 (se comienza por la posicin,
0, luego 1 y luego 2). Y luego se lee un byte, por lo tanto, el carcter ledo es el 7.
d) Una escritura posterior hara crecer el fichero hasta ese tamao rellenndose el espacio extra con el carcter \0.
Si no se hiciera esa escritura, y slo el lseek, el fichero no crecera. Hay que resaltar que para hacer crecer un fi-
chero no hace falta la opcin O_APPEND, puesto que esta opcin slo implica que el puntero de lectura/escritura se
coloque de partida al final del fichero. Con o sin esa opcin el fichero puede crecer.
e) Como se ha comentado en el tercer apartado, el fichero contiene la cadena 987654. La llamada a lseek sobre
el descriptor fd2 se coloca en la antepenltima posicin (SEEK_END -2). SEEK_END es el final del fichero, y re-
troceder dos posiciones coloca el puntero sobre el dgito 5. Dicho dgito se lee y se escribe de nuevo en el fichero,
dejando su contenido como 987655. El carcter escrito es el 5.
f) Curiosamente, el fichero se ha borrado, pero permanece abierto y sus datos accesibles hasta que se haga un clo-
se del ltimo descriptor que lo manipule. El borrado de ficheros no interfiere con los descriptores ya abiertos que lo
recorren. Lo que se hace es eliminar el nombre de dicha entrada en el directorio que la contiene, el contenido del fi-
chero seguir existiendo pero no estar accesible para otros procesos debido a que su nombre se ha eliminado. En
cuanto se haga el ltimo close, todo el espacio reservado se eliminar.
g) Recordemos en que posicin dejamos el descriptor de fd1. Dicho descriptor tena posicionado su puntero de lec-
tura/escritura en la posicin inmediatamente posterior al 7 que haba ledo. Es importante recalcar que las opera -
ciones realizadas con el descriptor fd2 no han afectado a este puntero (cada open implica un puntero
independiente). As pues desde la posicin actual del puntero de fd1 se invoca una lectura de 64 bytes. La llamada
read intenta proporcionar todos los bytes solicitados (64 bytes), pero al no haber suficientes datos se leen nica-
mente los disponibles, es decir 655, un total de 3 bytes.
h) Si f.txt es un enlace (de cualquier tipo) a un fichero, el contenido se modificara para cualquier otro enlace que
lo apunte. Los parmetros de la llamada open, O_CREAT|O_RDWR|O_TRUNC indican, respectivamente, que si no
existe se cree, que se abra con permisos de lectura/escritura y, por ltimo, que si haba algn contenido ste se trun-
case. Este ltimo argumento no quiere decir que el fichero se borre y se cree otro, indica que se elimine su conteni -
do. Al conservarse el fichero, el i-nodo, tanto los enlaces fsicos como los simblicos seguiran compartiendo
contenido (cada uno por medio de la redireccin correspondiente).
i) Se crea un proceso hijo y el proceso padre cierra el descriptor 3, que permanecera abierto en el hijo.
112 Problemas de sistemas operativos

Problema 2.18 (junio 2007)

Sea el programa TT.c, al que le faltan las lneas 21 a 51 y al que se le han tachado ciertos textos con la notacin
XXX##XXX, siendo ## es el nmero de lnea donde estn.
___1 /* TT.c */
___2 #include <fcntl.h>
___3 #include <stdio.h>
___4 #include <stdlib.h>
___5 #include <unistd.h>
___6 #define SIZE 1024
___7
___8 int main(int argc, char *argv[])
___9 {
__10 int pp[2], fd;
__11 char buff[SIZE];
__12 int ret;
__13
__14 /* Comprobar Argumentos */
__15 if (!argv[1]) {
__16 fprintf(stderr, "USO: %s mandato [args...]\n", argv[0]);
__17 exit(1);
__18 }
__19
__20 /* Almacenar Copia de Entrada Estndar */
....
....
__52
__53 /* Almacenar Copia de Salida Estndar */
__54 ret = pipe(pp);
__55 if (ret < 0) {
__56 perror("pipe");
__57 exit(1);
__58 }
__59 switch(fork()) {
__60 case -1:
__61 perror("fork");
__62 exit(1);
__63 case 0:
__64 close(XXX64XXX);
__65 fd = creat("salida", 0666);
__66 if (fd < 0) {
__67 perror("salida");
__68 exit(1);
__69 }
__70 while((ret = read(pp[0], buff, XXX70XXX)) > 0) {
__71 write(fd, buff, XXX71XXX);
__72 write(1, buff, XXX72XXX);
__73 }
__74 if (ret < 0) {
__75 perror("salida");
__76 exit(1);
__77 }
__78 return 0;
__79 default:
__80 close(XXX80XXX);
__81 dup(XXX81XXX);
__82 close(pp[0]);
__83 close(pp[1]);
__84 }
113
__85
__86 /* Ejecutar Mandato */
__87 execvp(argv[1], &argv[1]);
__88 perror(argv[1]);
__89 exit(1);
__90
__91 return 0;
__92 }
Dado el cdigo presentado (sin considerar las lneas 21 a 51 que faltan) se pide que deduzca cul es la utilidad del
programa. Para ello:
a) Suponga que el fichero dias.txt contiene, uno por lnea, los nombres de los das de la semana. Es-
tudie el siguiente ejemplo de uso del mandato TT.
$ ./TT sort < dias.txt
Identifique claramente qu informacin llegar por la entrada estndar, qu informacin producir por
la salida estndar y cul ser la informacin finalmente contenida en los ficheros auxiliares que se ha-
yan creado.
b) Dibuje un diagrama de procesos comunicados, donde quede clara la jerarqua de los procesos, los des-
criptores que utilizan y para qu los utilizan.
c) Explique con sus propias palabras para qu sirve el mandato TT.
d) Razone y explique cul sera el valor correcto del texto tachado en las lneas 70 a 72.
e) Razone y explique cul sera el valor correcto del texto tachado en las lneas 80 y 81.
f) Razone y explique cul sera el valor correcto del texto tachado en la lnea 64. Qu le sucedera a la
ejecucin del mandato si se hubiese omitido esta llamada a close?
Dado el cdigo expuesto, no es difcil imaginar el cdigo de las lneas 21 a 51 que faltan, ya que su misin es sim-
trica al de las lneas 54 a 84.
g) Codifique las lneas que faltan. No se acelere. Preste especial atencin a las diferencias necesarias y
subryelas.
h) En vista al nuevo cdigo que ha escrito, sera posible eliminar las lneas 54 a 58?, es decir, seran
necesarias una o dos llamadas a pipe?, y en este ltimo caso, bastara con la declaracin de pp en
la lnea 10 o hara falta una segunda?
i) Conteste nuevamente al apartado a).
j) Conteste nuevamente al apartado b).

Solucin
___1 /*TT.c*/
___2 #include<fcntl.h>
___3 #include<stdio.h>
___4 #include<stdlib.h>
___5 #include<unistd.h>
___6 #defineSIZE1024
___7
___8 intmain(intargc,char *argv[])
___9 {
__10 intpp[2],fd;
__11 charbuff[SIZE];
__12 intret;
__13
__14 /*ComprobarArgumentos*/
__15 if(!argv[1]){
__16 fprintf(stderr,"USO:%smandato[args...]\n",argv[0]);
__17 exit(1);
__18 }
__19
__20 /*AlmacenarCopiadeEntradaEstndar*/
114 Problemas de sistemas operativos
__21 ret=pipe(pp);
__22 if(ret<0){
__23 perror("pipe");
__24 exit(1);
__25 }
__26 switch(fork()){
__27 case-1:
__28 perror("fork");
__29 exit(1);
__30 case0:
__31 close(pp[0]);
__32 fd=creat("entrada",0666);
__33 if(fd<0){
__34 perror("entrada");
__35 exit(1);
__36 }
__37 while((ret=read(0,buff,SIZE))>0){
__38 write(fd,buff,ret);
__39 write(pp[1],buff,ret);
__40 }
__41 if(ret<0){
__42 perror("entrada");
__43 exit(1);
__44 }
__45 return 0;
__46 default:
__47 close(0);
__48 dup(pp[0]);
__49 close(pp[0]);
__50 close(pp[1]);
__51 }
__52
__53 /*AlmacenarCopiadeSalidaEstndar*/
__54 ret=pipe(pp);
__55 if(ret<0){
__56 perror("pipe");
__57 exit(1);
__58 }
__59 switch(fork()){
__60 case-1:
__61 perror("fork");
__62 exit(1);
__63 case0:
__64 close(pp[1]);
__65 fd=creat("salida",0666);
__66 if(fd<0){
__67 perror("salida");
__68 exit(1);
__69 }
__70 while((ret=read(pp[0],buff,SIZE))>0){
__71 write(fd,buff,ret);
__72 write(1,buff,ret);
__73 }
__74 if(ret<0){
__75 perror("salida");
__76 exit(1);
__77 }
__78 return 0;
__79 default:
__80 close(1);
__81 dup(pp[1]);
115
__82 close(pp[0]);
__83 close(pp[1]);
__84 }
__85
__86 /*EjecutarMandato*/
__87 execvp(argv[1],&argv[1]);
__88 perror(argv[1]);
__89 exit(1);
__90
__91 return0;
__92 }

a) El ejemplo propuesto de uso del mandato: $ ./TT sort < dias.txt


sugiere la puesta en ejecucin, desde el shell que nos muestra el prompt $, del ejecutable TT (que se supone que es
el resultado de la compilacin correcta del programa TT.c), que se encuentra en el directorio actual de trabajo (de
ah la ruta indicada ./), pasndole un nico argumento (sort) y asocindole la entrada estndar al fichero dia-
s.txt mediante la notacin <. Todo esto lo realiza automticamente el shell. La salida estndar y la salida estn-
dar de error no han sido redirigidas, luego permanecen asociadas a donde estuvieran en el momento de introducir
esta lnea de mandatos. Digamos, por ejemplo, que permanecen asociadas al terminal desde el que un usuario ha in-
troducido el mandato.
Viendo el cdigo de TT.c se observa la creacin de una tubera (pipe) y su uso para la comunicacin del proce-
so original (que llamaremos padre) con otro proceso hijo creado al efecto. El proceso padre est destinado a ejecutar
el mandato pasado como primer argumento (en este caso concreto, el mandato sort) parametrizado con el resto de
argumentos (en este caso concreto no hay tales). Estos mandatos (denominados genricamente mandatos estndar)
utilizan los descriptores estndar, y el proceso hijo lee de la tubera. De ello debemos deducir que el proceso padre
en las lneas 81 y 82 redirige su salida estndar a la tubera.
As pues, la informacin que el conjunto de estos procesos presentar por la salida estndar, ser la que presente
el proceso hijo (con la llamada de la lnea 72) que quedar simultneamente registrada en el fichero auxiliar sali-
da, y que, en definitiva, ser trascripcin de la que el proceso padre inyecte por el otro extremo de la tubera. Esto
es, la salida producida por el mandato indicado como argumentos a TT por su salida estndar.
En resumen: La informacin que llegar por la entrada estndar ser el contenido del fichero dias.txt, esto
es, los das de la semana, uno por lnea. La informacin producida por la salida estndar sern, uno por lnea, los
das de la semana, pero ordenados alfabticamente (esto es lo que sort realiza). Y la informacin finalmente regis-
trada en el archivo salida, ser copia de la producida por la salida estndar.
b) Dos procesos: padre he hijo, como muestra la figura 2.11.
Padre
EXEC
mandato [args...] Hijo2
SALIDA
0 1
pp[0] 1

fd ?
dias.txt

SALIDA

Figura 2.11:
El padre tiene su entrada estndar asociada al fichero dias.txt, su salida estndar asociada a una tubera y
ejecuta el mandato sort.
El proceso hijo lee del otro extremo de la tubera, a travs del descriptor pp[0], y lo que lee lo escribe por el
descriptor fd (asociado al fichero salida creado al efecto) y lo repite por la salida estndar.
116 Problemas de sistemas operativos
c) El mandato tee de UNIX (man 2 tee) funciona al igual que una tubera en forma de T, y de ah le viene el
nombre. Copia lo que lee de su entrada estndar sobre su salida estndar y adems en otro archivo indicado como ar-
gumento.
De forma semejante (pero no idntica) el mandato presentado como TT tiene como objetivo final, ejecutar el
conjunto mandato+argumentos indicado en sus argumentos, pero aadiendo una T a la entrada y otra T a la salida de
dicho mandato.
Un proceso central, ejecutar el mandato+argumentos indicado. Dos procesos auxiliares, hijos del primero, filtra-
rn la entrada y salida estndar respectivamente, registrando copia de dichas informaciones sobre sendos archivos
auxiliares de nombre prefijado: entrada y salida.
d) El proceso hijo intenta procesar la informacin que lee de la tubera en cantidades de tamao razonable. En la l -
nea 70, intenta leer sobre buff SIZE bytes cada vez. Por tratarse de una tubera, se leer la informacin que haya
disponible en cada momento hasta el lmite indicado (SIZE). Es por eso que las dos operaciones de escritura si-
guientes deben transferir slo la informacin que fue realmente leda de la tubera y ahora est en nuestro buffer. Di-
cha cantidad de informacin son ret bytes. El tercer parmetro correcto para las llamadas write de las lneas 71 y
72 ser pues la variable ret.
__70 while((ret=read(pp[0],buff,SIZE))>0){
__71 write(fd,buff,ret);
__72 write(1,buff,ret);
e) Por razones que se han expuesto detalladamente en el apartado a, el proceso padre est destinado a enviar lo que
emita por su salida estndar a la tubera que hemos creado en la lnea 54. Lo que debe hacer pues es redirigir su sali-
da estndar al extremo de escritura de la tubera, y eso es exactamente lo que hacen las dos instrucciones siguientes:
__80 close(1);
__81 dup(pp[1]);
f) Una regla de buena programacin en UNIX dice que deben cerrarse todos los descriptores (a excepcin de los es-
tndar) que no vayan a utilizarse. Pero en este caso hay otra razn de mayor peso.
La lnea 64 se corresponde con el cdigo que ejecuta el proceso hijo, que lee de la tubera y lo copia sobre
salida y sobre la salida estndar. Esta copia se realiza mientras se lea informacin de la tubera, y, por lo tanto,
terminar cuando la llamada de lectura devuelva un valor 0. Como se ha estudiado en teora, para que una llamada
de lectura sobre una tubera devuelva 0 bytes ledos, es preciso que se den simultneamente dos circunstancias: que
no haya datos en la tubera (de haberlos la llamada los leera) y que no los vaya ha haber en el futuro, y la nica for -
ma de garantizar esto ltimo es que no quede abierto ningn descriptor asociado al extremo de escritura de la tube -
ra.
__64 close(pp[1]);
Es un fallo comn al comunicar procesos por tuberas dejar sin cerrar un descriptor de escritura a una tubera,
con el efecto de dejar permanentemente colgado al proceso lector en la llamada de lectura del otro extremo de la tu -
bera.
g) Anteriormente se ha mostrado el cdigo completo del mandato TT. A continuacin se muestran, de manera
comparada, slo los cambios entre las correspondientes lneas 21 a 51 y 54 a 84.
Es necesario entender que, en este caso, el sentido de la comunicacin a travs de la tubera es del hijo hacia el
padre.
Se cierran los descriptores que no se utilizan.
ENTRADA __31 close(pp[0]); no va ha leer de la tubera
SALIDA __64 close(pp[1]); no va ha escribir en la tubera
El nombre del fichero auxiliar de registro.
ENTRADA __32 __34 __42 "entrada"
SALIDA __65 __67 __75 "salida"
El sentido de la comunicacin a travs de la tubera.
ENTRADA __37 read(0,... del hijo al padre
ENTRADA __38 write(fd,...
ENTRADA __39 write(pp[1],...
SALIDA __70 read(pp[0],... del padre al hijo
SALIDA __71 write(fd,...
SALIDA __72 write(1,...
117
La redireccin. El hijo utiliza los descriptores de manera explcita, pero el padre, al ir a ejecutar manda-
tos, debe redirigir sus descriptores estndar.
ENTRADA __47 close(0); entrada estndar
ENTRADA __48 dup(pp[0]); de la tubera
SALIDA __80 close(1); salida estndar
SALIDA __81 dup(pp[1]); a la tubera
h) No es posible eliminar las lneas 54 a 58. Si, son necesarias dos llamadas a pipe, para crear las dos tuberas que
comunican al priper hijo (entrada) con el padre y a este con su segundo hijo (salida).
Por otro lado, y dado el cdigo, basta con la declaracin de pp en la lnea 10 y no hara falta una segunda. Esta
variable no es distinta las otras de este mismo programa, que hemos venido reutilizando. La variable pp est desti-
nada a contener, temporalmente, el valor de los descriptores asociados a uno y otro extremo de las tuberas. Primero
de una y luego de la otra.
i) Ahora, tenemos implementadas las dos Tes, no slo la de salida, sino tambin la de entrada. La ejecucin del man-
dato sugerido, ser prcticamente la misma. La nica diferencia respecto al apartado a) es que ahora tenemos un pri-
mer hijo destinado a filtrar la entrada.
As pues, la informacin que el conjunto de estos procesos tomar de la entrada estndar, ser la que tome el pri-
mer proceso hijo (con la llamada de la lnea 37) que ser simultneamente registrada en el fichero auxiliar entra-
da, y ser inyectada por el extremo de la tubera para que le llegue al proceso padre como entrada estndar.
En resumen: La informacin que llegar por la entrada estndar ser el contenido del fichero dias.txt, esto
es, los das de la semana, uno por lnea. Y la informacin registrada en el archivo entrada, ser copia de la recibi-
da por la entrada estndar. La informacin producida por la salida estndar ser, uno por lnea, los das de la semana,
pero ordenados alfabticamente (esto es lo que sort realiza). Y la informacin finalmente registrada en el archivo
salida, ser copia de la producida por la salida estndar.
j) Tres procesos: padre y dos hijos, ENTRADA y SALIDA., como se muestra en la figura 2.12.
Padre
Hijo1 EXEC
ENTRADA mandato [args...] Hijo2
SALIDA
0 pp[1] 0 1
pp[0] 1
fd
fd ?
dias.txt

ENTRADA
SALIDA

Figura 2.12
El padre tiene su entrada estndar asociada a una tubera que lo comunica con un proceso hijo denominado EN-
TRADA. As mismo, su salida estndar est asociada a una tubera que lo comunica con un proceso hijo denominado
SALIDA. El proceso padre ejecuta el mandato indicado con los argumentos indicados: sort, sin argumentos.
El proceso hijo ENTRADA lee de la entrada estndar que tiene asociada al fichero dias.txt, y lo que lee lo es-
cribe por el descriptor fd (asociado al fichero entrada creado al efecto) y lo repite por la tubera que lo comunica
con su padre a travs del descriptor pp[1].
El proceso hijo SALIDA lee de la tubera que lo comunica con su padre a travs del descriptor pp[0], y lo que lee
lo escribe por el descriptor fd (asociado al fichero salida creado al efecto) y lo repite por la salida estndar.

Problema 2.19 (abril 2008)

En el directorio /home de un sistema de ficheros A se ha montado el sistema de ficheros B, sin enmascarar ningn
permiso. Parte del rbol de nombres resultante se muestra en la figura 2.13, en base a una salida parcial de varios
mandatos ls (se recuerda que el segundo dato del ls es el nmero de enlaces).
Las mscaras de creacin de ficheros y los grupos de algunos usuarios son los siguientes:
118 Problemas de sistemas operativos
Usuario pepe, mscara = 022 y grupo div1
Usuario macu, mscara = 027 y grupo div1
Usuario mari, mscara = 077 y grupo div2

Sistema de ficheros A (visin parcial)


Raz sbin
drwxr-xr-x 3 root root sbin -rwsr-xr-x 1 root root mount.cifs
drwxrwxrwt 45 root root kk
drwxr-x--x 16 root root home

Sistema de ficheros B (visin parcial)


home pepe
drwxrwx--x 8 pepe div1 pepe drwx------ 8 pepe div1 prac1
drwxrwx--- 12 macu div1 macu -rwsr-x--- 1 pepe div1 iue.exe
drwx--x--x 8 mari div2 mari -rwsr-xr-x 1 macu div1 itec.exe
-rw-rw-rw- 1 root root iome
macu -rw-rw-rw- 1 pepe div1 ikelu
drwx------ 8 macu div1 prac1
-rw-rw---- 1 macu div1 teri.c mari
-rws--xr-x 1 macu div1 teri.exe drwx------ 8 mari div2 prac1

Figura 2.13
El programa itec.exe contiene el siguiente esquema de cdigo
fd = open("/home/macu/teri.c", O_RDWR);
n = fork();
a) El usuario mari crea un nuevo programa xx que incluye la siguiente lnea:
m = execl("/home/pepe/itec.exe", "itec.exe", NULL);
Teniendo en cuenta que m es una variable de xx y fd de itec.exe, y que no hay errores inesperados, indicar
el valor de m y el de fd cuando mari ejecuta el programa xx..
b) Los usuarios pepe y macu lanzan al tiempo la ejecucin del programa itec.exe. Cul sera el mximo
nmero de referencias (tambin llamados ndups) y de nopens que podran llegar a alcanzarse?
c) El programa iue.exe contiene la siguiente lnea de cdigo:
n = creat("/home/pepe/prac1/siet.c", 0666);
El usuario macu intenta ejecutar dicho programa desde su home, indicar si se crea el fichero y, en su caso, indi-
car el dueo y los derechos.
Ahora, el programa que ha lanzado el usuario pepe ejecuta la siguiente lnea de cdigo:
fd = open("/home/pepe/ikelu", O_RDWR|O_CREAT|O_TRUNC,0640);
obteniendo fd= 5. Seguidamente, el programa crea dos hijos H1 y H2. Inmediatamente, H2 hace un exec.
Al mismo tiempo, pero cuando pepe ya ha ejecutado el open, el usuario mari ejecuta otro programa (proceso
M) que incluye:
fd = open("/home/pepe/ikelu", O_RDWR);
recibiendo fd = 3.
A partir de este punto se produce la siguiente secuencia de ejecuciones
Proceso Servicio
1 H1 write(5, "111111111111111111111111111111", 23);

2 H2 write(5, "222222222222222222222222222222", 26);

3 M write(1, "3333", 4);

4 H1 lseek(5, 17, SEEK_END);

5 H2 write(5, "9999", 4);


119

6 M lseek(3, 2, SEEK_SET);

7 H1 write(5, "4444", 4);

8 M write(3, "45", 2);

9 H2 lseek(5, 4, SEEK_SET);

10 H1 write(5, "8901", 4);

11 H1 close(5);

12 H2 close(5);

13 M Close(3);

Aplicando la poltica de coutilizacin de UNIX:


d) Indicar el tamao real del fichero al final de esta secuencia.
e) Indicar el contenido del fichero.

Solucin
a) Tanto home como pepe tienen derechos de visita para el mundo, adems, el fichero itec.exe tiene derechos
de ejecucin para el mundo, por lo que mari lo puede ejecutar. Al tratarse de un exec exitoso no se devuelve nin-
gn valor, por lo que m no llega a cambiar de valor.
Observamos que el programa itec.exe tiene activo el SUID, por lo que ejecutar con la identidad de su due-
o, es decir, macu. Como macu es el dueo, puede visitar su directorio y llegar al fichero /home/macu/teri.c,
por tanto, lo podr abrir para escritura y lectura. Esto significa que fd recibir el valor del primer descriptor libre,
por ejemplo, 3.
b) Tanto pepe como macu pueden ejecutar el programa itec.exe, que ejecutar con identidad efectiva macu en
ambos casos, por lo que cada ejecucin abrir el fichero /home/macu/teri.c. Esto significa que nopens = 2
(suponiendo que el fichero no estaba abierto con anterioridad). Por otro lado, aparecern dos entradas en la tabla in-
termedia, cada una de las cuales llegar a tener dos referencias, dado que cada ejecucin producir un proceso hijo.
c) El usuario macu tiene derechos de ejecucin de iue.exe. Adems, dicho fichero tiene activo su SUID, por lo
que ejecuta con la identidad efectiva de pepe. Se aplica la mscara de creacin de ficheros que tiene el proceso, por
tanto, la de macu que es 027. Luego, se crea el fichero con dueo pepe y con derechos rw-r-----
d) El tamao del fichero ser de 23 + 26 + 17 + 4 + 4 = 74 bytes.
e) El contenido del fichero ser el siguiente, de acuerdo a la lnea ejecutada:
1 11111111111111111111111
2 1111111111111111111111122222222222222222222222222
3 (se escribe en otro fichero)
5 1111111111111111111111122222222222222222222222222000000000000000009999
7 11111111111111111111111222222222222222222222222220000000000000000099994444
8 11451111111111111111111222222222222222222222222220000000000000000099994444
10 11458901111111111111111222222222222222222222222220000000000000000099994444

Problema 2.20 (mayo 2009)

Sea un sistema de ficheros UNIX con las siguientes caractersticas:


Bloque de 1 KiB y agrupacin de 1 bloque.
Nodos i ocupados del 2 al 29, el resto libres.
Bloques ocupados del 1 al 79, el resto libres.
Para asignar un bloque o un nodo i se selecciona el primero libre.
Entradas libres de la tabla intermedia: a partir de la 11.
El directorio "/" (raz), pertenece al usuario y grupo root y tiene los permisos: rwx rwx --x
El directorio "/home", pertenece al usuario y grupo root, tiene los permisos: rwx rwx rwx y su nodo i es
el 14.
El fichero "/home/user2/prog" tiene: UID = 400, GID = 7 y permisos = 06755.
120 Problemas de sistemas operativos
Enteros de 4 bytes.
En el instante inicial existen dos procesos PA y PB cuyas identidades y descriptores de fichero se indican en la
figura 2.14.
BCP PA BCP PB BCP
UID = 100 UID = 101 UID = Agrupaciones
GID = 4 GID = 4 GID = Tamao
Tabla fd Tabla fd Tabla fd
11
0 1 0 3 0
12
1 2 1 4 1
13
2 2 2 4 2
14
3 0 3 0 3
15
4 0 4 0 4
16
5 0 5 0 5
17
6 0 6 0 6
18
7 0 7 0 7
Tabla intermedia Tabla de nodos_i
Figura 2.14

Se ejecuta la secuencia siguiente:


Proceso cdigo ejecutado
PA umask(0023);
mkdir("/home/user1", 0753); // El directorio no existe inicialmente.
fd=creat("/home/user1/file1.txt", 0666);
for (i=0;i<100;i++)
n=write(fd, "0123456789ABCDEFGHJK", 20);
pid=fork(); // Suponer que no falla. Llamar PC al hijo.

PC lseek(fd, 3, SEEK_SET);
n=write(fd, "xter",4);
PB fd2=open("/home/user1/file1.txt", O_RDWR);
Punto 1 de la secuencia de ejecucin.
PB lseek(fd2, 5*1024, SEEK_CUR);
n=write(fd2, "ProcesoB",8);
Punto 2 de la secuencia de ejecucin.
PB execl("/home/user2/prog", "/home/user2/prog", NULL);
exit (1);
PB (nuevo cdigo)
fd3=creat("/home/user1/file2.txt", 0666); // El fichero no existe inicialmente.
n3=access("/home/user1", R_OK);
Punto 3 de la secuencia de ejecucin.

Se pide:
a) Determinar los permisos con los que se crea el fichero "/home/user1/file1.txt".
b) Completar la figura 2.14 para el punto 1 de la secuencia de ejecucin. Deber indicar los ttulos de las co-
lumnas de la tabla intermedia y de nodos i que faltan.
c) Indicar el contenido de los bloques que se modifican por la secuencia anterior en su ejecucin hasta el punto
1.Usar para los bloques de directorio el siguiente formato: nombre nodo i; nombre nodo i; ... Limitar el conte -
nido presentado a los primeros caracteres de cada bloque seguido de algunos puntos, si ste tiene un contenido ma -
yor. Indicar el comienzo de la basura poniendo interrogaciones ???.
d) En el punto 2 de la secuencia de ejecucin indicar las agrupaciones asignadas al fichero
"/home/user1/file1.txt"
e) En el punto 3 de la secuencia de ejecucin indicar los valores devueltos en las variables fd3 y n3.
121

Solucin
a) Como el directorio /home tienen permisos de escritura tanto para el dueo, como para el grupo como para el
mundo, todos los usuarios pueden escribir en l, por lo que el mkdir se ejecutar con xito. El fichero
"/home/user1/file1.txt" se crea con los permisos: rw- r-- r-- .
b) La creacin del directorio /home/user1 sopone la creacin de un fichero tipo directorio, al que se le asignar
el nodo i n 30. El proceso PB no puede abrir el fichero "/home/user1/file1.txt", por lo que solamente lo tienen abier -
to los procesos PA y PC. La figura 2.15 muestra la situacin de las tablas.
BCP PA BCP PB BCP Nodo_i
UID = 100 UID = 101 UID = 100 Nodo_i Referencias N_opens Agrupaciones
GID = 4 GID = 4 GID = 4 Tamao
Tabla fd Tabla fd Tabla fd Posicin rw
11 31 7 2 -w 31 1 81,82 2.000
0 1 0 3 0 1 12
1 2 1 4 1 2
13
2 2 2 4 2 2 14
3 11 3 0 3 0
15
4 0 4 0 4 0 16
5 0 5 0 5 0
17
6 0 6 0 6 0
18
7 0 7 0 7 0
Tabla intermedia Tabla de nodos_i
Figura 2.15

En el supuesto de que PB hubiese podido abrir l fichero "/home/user1/file1.txt" las tablas quedaran como se in-
dica en la figura 2.16.
BCP PA BCP PB BCP Nodo_i
UID = 100 UID = 101 UID = 100 Nodo_i Referencias N_opens Agrupaciones
GID = 4 GID = 4 GID = 4 Tamao
Tabla fd Tabla fd Tabla fd Posicin rw
11 31 7 2 -w 31 2 81,82 2.000
0 1 0 3 0 1
12 31 0 1 rw
1 2 1 4 1 2
13
2 2 2 4 2 2 14
3 11 3 0 3 11
15
4 0 4 0 4 0 16
5 0 5 0 5 0
17
6 0 6 0 6 0
18
7 0 7 0 7 0
Tabla intermedia Tabla de nodos_i
Figura 2.16

c) El contenido de los bloques es el siguiente:


Bloque Contenido
80 . 30; .. 14; file1.txt 31;?????
81 012xter789ABCDEFGHJK0123456789ABCDEFGHJK0123456789ABCDEFGHJK..
82 456789ABCDEFGHJK0123456789ABCDEFGHJK0123456789ABCDEFGHJK....???????
El bloque 82 tendr los ltimos 48 bytes con basura, dado que no han sido escritos.
d) Igual que para el apartado b) consideraremos dos situaciones, la real consistente en que el proceso PB no consi -
gue abrir el fichero "/home/user1/file1.txt" y la hipottica en la que s consigue abrir el fichero.
En el primer supuesto, el fichero no es modificado por el proceso PB, por lo que sus agrupaciones son la 81 y 82,
cono se indica en el apartado c). Hay que tener en cuenta que la agrupacin 80 es ocupada por el directorio
"/home/user1".
En el segundo supuesto, las agrupaciones ocupadas por el fichero "/home/user1/file1.txt" son las 81, 82 y 83.
Adems el fichero tiene un hueco desde la posicin 2.000 a la 5*1.024 = 5.120, ocupndose las posiciones 5.120 a
5.120 por el texto "ProcesoB". Las agrupaciones 81 y 82 dan soporte a los 2.000 caracteres escritos, debiendo ser re-
122 Problemas de sistemas operativos
llenados con ceros al crearse el hueco los 48 bytes no escritos previamente. Los bytes 2.048 a 5.119 (que correspon-
den a hueco) no tienen agrupacin asignada dado que no tiene sentido asignar unas agrupaciones que tendramos,
adems, que rellenar con ceros. Finalmente, la agrupacin 83 dar soporte a los 8 caracteres "ProcesoB", quedando
el resto fuera del tamao del fichero.
e) El fichero "/home/user1/file2.txt" no puede ser creado, puesto que la identidad efectiva del proceso PB despus
del exec es UID = 400, GID = 7, que no tiene permisos de escritura en "/home/user1". Por lo tando, fd3 = -1
Dado que el servicio access utiliza la identidad real del proceso PB, que es UID = 101, GID = 4, por lo que si
tiene permisos de lectura. Por tanto n3 = 0.

Problema 2.21 (abril 2010)

Sean los fragmentos de programas de la tabla adjunta, dichos programas se ejecutan de forma entrelazada de
acuerdo a la secuencia establecida en dicha tabla. Supondremos que el fichero fich existe y tiene un tamao de 43
bytes, que el tamao del bloque es de 1 KiB y que ninguno de los servicios produce error.

Programa 1 Programa 2 Orden ejecu-


cin
(lnea)
fd = open(/us/pe/fich, O_RDWR); 1
write(fd, 145243, 4); 2
fd = open(/us/pe/fich, O_WRONLY); 3
lseek(fd, 3, SEEK_CUR); 4
lseek(fd, 50, SEEK_SET); 5
write(fd, 7824, 4); 6
write(fd, 123434, 5); 7
lseek(fd, -53, SEEK_CUR); 8
write(fd, 86, 2); 9
close(fd); 10

a) Indicar el contenido del fichero al final de la ejecucin de ambos fragmentos de programa, lnea 10.
b) Indicar el tamao del fichero al final de la ejecucin de ambos fragmentos de programa.
c) Suponiendo que el nodo_i raz ya est en memoria, que cada directorio ocupa un bloque y que no existe ca-
che de bloques, determinar el nmero de accesos a disco que se producen hasta la ejecucin de la lnea 2 inclusive.
d) Repetir la pregunta c para el caso de que exista cache de bloques, considerando la ejecucin hasta la lnea 6
inclusive y que la escritura es write-through.
e) Representar de forma grfica las estructuras de informacin afectadas por dichos programas tal y como que-
dan el final de la ejecucin de la lnea 10 inclusive.

Solucin
a) Los contenidos son los siguientes, representando los valores originales del fichero con ? y todo ello expresado
como cadena de caracteres.
Lnea 2: 1452???????????????????????????????????????
Lnea 6: 1457824????????????????????????????????????
Lnea 7: 1457824????????????????????????????????????000000012343
Lnea 9: 1486824????????????????????????????????????000000012343
b) El tamao final del fichero lo obtenemos de la respuesta anterior, siendo de 55 B.
c) Los accesos son los siguientes:
Lectura del directorio raz (un bloque). Lnea 1
Lectura del nodo_i del directorio us (un bloque). Lnea 1
Lectura del directorio us (un bloque). Lnea 1
123
Lectura del nodo_i del directorio pe (un bloque). Lnea 1
Lectura del directorio pe (un bloque). Lnea 1
Lectura del nodo_i del fichero fich (un bloque). Lnea 1
Lectura del primer bloque del fichero fich. Lnea 2
Escritura del primer bloque del fichero fich. Lnea 2
Total 8 accesos al disco. El nodo_i queda modificado, puesto que se modifican los instantes de acceso y escritu -
ra. Esta modificacin se hace primero en la copia que est en memoria y ms adelante se lleva al disco.
d) Los accesos son los siguientes:
Lectura del directorio raz (un bloque). Lnea 1
Lectura del nodo_i del directorio us (un bloque). Lnea 1
Lectura del directorio us (un bloque). Lnea 1
Lectura del nodo_i del directorio pe (un bloque). Lnea 1
Lectura del directorio pe (un bloque). Lnea 1
Lectura del nodo_i del fichero fich (un bloque). Lnea 1
Lectura del primer bloque del fichero fich. Lnea 2
Escritura del primer bloque del fichero fich. Lnea 2
Escritura del primer bloque del fichero fich. Lnea 6
Total 9 accesos al disco. Es de destacar que la lnea 3 ya encuentra todos los bloques en la cache y la lnea 6 en -
cuentra en la cache el valor escrito por la lnea 2, por lo que no necesita leerlo. Igual que en el caso anterior, ms
adelante habr que actualizar el nodo_i.
e) Las estructuras quedan como se indica en la figura 2.17. Se ha supuesto que los procesos que ejecutan los progra-
mas dados solamente tienen abiertos los descriptores estndar y que las lneas 5 y 6 de la tabla intermedia estn li-
bres. Tambin hemos considerado que el nodo_i del fichero es el 33. La lnea 6 queda ocupada por el proceso del
programa 2, pero despes del close queda libre, lo que se refleja puesto que el nmero de referancias es 0:
En la tabla de nodos_i tambin quedarn modificados los instantes de acceso y modificacin del fichero.
BCP 1 BCP 2 Tablas en memoria
fd fd nNodo-i Posicin Referen. rw
0 1 0 3 1 Nodo-i Tipo nopens Tamao
1 2 1 4 2
2 2 2 4 3
4 33 1 55
3 5 3 0
4 0 4 0 5 33 4 1 11
5 0 5 0 6 33 7 0 01
6 0 6 0

Figura 2.17
Dado que no se asignan bloques nuevos ni nodos_i, los mapas de bits del disco no quedan modificados.

Problema 2.22 (junio 2010)

Debe usted implementar en lenguaje C y para UNIX el cdigo fundamental del mandato cyp (CopiaYPega) cuya
utilidad es poder copiar una parte de un fichero, dadas su posicin y longitud (de bytes a gigabytes), a otro fichero
en otra posicin dada. Realice el cdigo ms correcto y claro que pueda (NO se admitir pseudocdigo), realice un
tratamiento correcto de los errores y libere todos los recursos previamente reservados, cuando dejen de usarse.
a) Siga estrictamente las siguientes instrucciones de diseo:
Implemente la funcin copia que copia size bytes del fichero abierto fdorg al fichero abierto
fddst:
copia(fdorg, size, fddst)
Haciendo uso de la funcin anterior, implemente la funcin copiaypega, que aade a la anterior la
posicin absoluta de origen y destino, offorg y offdst:
124 Problemas de sistemas operativos
copiaypega(fdorg, offorg, size, fddst, offdst)
Haciendo uso de la funcin anterior, implemente la funcin cyp que realiza la operacin de
copiaypega entre los ficheros origen y destino de nombre dado:
cyp(origen, offorg, size, destino, offdst)
b) Considere la siguiente secuencia de mandatos y analice el comportamiento de cyp:
(1) echo n 0123456789 > A
(2) echo n abcdefghij > B
(3) cyp A 7 5 B 2
(4) cyp A 1 5 B 8
Razone cul sera el contenido del fichero B al final de los pasos (3) y (4) y porqu.
c) Ahora, piense y rehaga slo la parte imprescindible del cdigo anterior, para realizar una implementacin equi -
valente de cyp pero mediante ficheros proyectados en memoria. Observe que conseguir el mismo comportamiento
anterior implica una solucin un poco ms completa que la trivial.
d) Razone:
Cul de estas implementaciones ser ms rpida (en bytes copiados por unidad de tiempo)?
Cul de estas implementaciones ser ms apta para copiar cantidades muy grandes de datos (decenas
de gigas) (considere un sistema de 32 bits)?
e) Considere ahora la necesidad de poder ejecutar varios mandatos cyp concurrentes haciendo copias cruzadas de
informacin entre un conjunto de ficheros.
Implemente el cdigo necesario para asegurar que cada cyp sucede sin verse afectado por otros en eje-
cucin simultnea.
Podra suceder interbloqueo?, cmo podra evitarse?

Solucin

a) Las funciones solicitadas seran:


/* Copia size bytes
* del fichero abierto fdorg
* al fichero abierto fddst.
*/
int copia(int fdorg, int size, int fddst)
{
char buf[4096];
int ret;
do {
ret = size;
if (ret > sizeof(buf)) ret = sizeof(buf);
ret = read (fdorg, buf, ret);
if (ret <= 0) break;
ret = write(fddst, buf, ret);
if (ret <= 0) break;
size -= ret;
} while(size > 0);
if (ret < 0) return -1;
return size;
}
/* Copia size bytes
* del fichero abierto fdorg en su posicin absoluta offorg
* al fichero abierto fddst en su posicin absoluta offdst.
*/
int copiaypega_rdwr(int fdorg, int offorg, int size, int fddst, int offdst)
{
int ret;
ret = lseek(fdorg, offorg, SEEK_SET);
if (ret < 0) return -1;
ret = lseek(fddst, offdst, SEEK_SET);
if (ret < 0) return -1;
return copia(fdorg, size, fddst);
}
/* Copia size bytes
* del fichero de nombre org en su posicin absoluta offorg
* al fichero de nombre dst en su posicin absoluta offdst.
*/
125
int cyp(char * org, int offorg, int size, char * dst, int offdst)
{
int fdorg = open(org, O_RDONLY);
int fddst = open(dst, O_RDWR|O_CREAT, 0666);
int ret = 0;
if (fdorg < 0) ret = -1;
if (fddst < 0) ret = -1;
if (ret >= 0) ret = copiaypega_rdwr(fdorg, offorg, size, fddst, offdst);
if (fdorg >= 0) close(fdorg);
if (fddst >= 0) close(fddst);
return ret;
}
b) El fichero A contiene los diez dgitos del 0 al 9. El fichero B contiene las 10 primeras letras minsculas.
En el paso (3), al intentar leer ms all del tamao del fichero A, 5 bytes desde el 7, cuando su tamao son 10,
slo podremos leer los 3 que hay de la posicin 7 en adelante 789. Estos datos son los que debern ser copiados
sobre B en su posicin 2, luego B quedar como ab789fghij.
En el paso (4), se leern sin problemas los 5 caracteres contenidos en el fichero A desde su posicin 1, estos son
12345. Estos datos se tratarn de escribir llegando ms all del tamao actual del fichero B, que son 10 bytes. Al
intentar escribir ms all de su tamao, el fichero crecer automticamente. El contenido final de B ser ab789-
fgh12345 y su tamao final sern 13 bytes. Si hubiese quedado hueco entre el tamao anterior del fichero y la
posicin de escritura, este hueco se rellenara con el bytes nulos.
c) La versin basada en ficheros proyectados en memoria consistira en implementar copiaypega como sigue (la
implementacin tiene en cuenta la realidad de que los offset deben estr alineados a pgina):
/* Copia por proyeccin en memoria size bytes
* del fichero abierto fdorg en su posicin absoluta offorg
* al fichero abierto fddst en su posicin absoluta offdst.
*/
int copiaypega_mmap(int fdorg, int offorg, int size, int fddst, int offdst)
{
int page = sysconf(_SC_PAGE_SIZE);
/* Los offset deben estar alineados a pgina. */
void * org = mmap(NULL, size+offorg%page, PROT_READ , MAP_SHARED, fdorg, offorg/page*page);
void * dst = mmap(NULL, size+offdst%page, PROT_WRITE, MAP_SHARED, fddst, offdst/page*page);
int ret = 0;
offorg %= page;
offdst %= page;
if (org == MAP_FAILED) ret = -1;
if (dst == MAP_FAILED) ret = -1;
if (ret >= 0) memcpy(dst+offdst, org+offorg, size);
if (org != MAP_FAILED) ret = munmap(org, size+offorg);
if (dst != MAP_FAILED) ret = munmap(dst, size+offdst);
return ret;
}
La simple proyeccin en memoria de los ficheros ofrecera un comportamiento distinto, por dos razones:
Cuando el sistema de memoria virtual subyacente se basa en paginacin, las zonas de memoria proyecta-
das se redondean a pgina. Esto implicara en nuestro caso, poder leer bytes nulos al acceder ms all del
tamao real del fichero proyectado, por no ser mltiplo exacto del tamao de pgina. As pues, en el caso
(3) anterior el resultado habra sido ab789__hij, indicando con _ el byte nulo.
Por la misma razn, si el fichero no tiene un tamao mltiplo del tamao de pgina, ser posible escribir
ms all del tamao real del fichero proyectado. Sin embargo, esto no se ver reflejado en el fichero aso -
ciado, ni aunque, como es lgico la proyeccin sea MAP_SHARED. El resultado pues del paso (4) ante-
rior sera ab789__h12345___... en memoria, pero ab789__h12 sobre B.
As pues, para corregir estos dos efectos, debemos proceder a limitar el espacio de lectura y a ampliar el de
escritura, a saber, habra que aadir la siguiente funcin a antes de llamar a la anterior en cyp.
/* Ajusta tamaos de copia y destino para tener
* el mismo comportamiento proyectando que por E/S.
*/
int copiaypega_ajst(int fdorg, int offorg, int*sizeptr, int fddst, int offdst)
{
struct stat st;
int size;
int ret;
/* Corregir mximos datos de origen. */
126 Problemas de sistemas operativos
ret = fstat(fdorg, &st);
if (ret < 0) return ret;
size = st.st_size - offorg;
if (*sizeptr > size)
*sizeptr = (size >= 0) ? size : 0;
/* Corregir tamao final del destino. */
ret = fstat(fddst, &st);
if (ret < 0) return ret;
size = offdst + *sizeptr;
if (size > st.st_size)
if (*sizeptr > 0)
ret = ftruncate(fddst, size);
return ret;
}
d) Respecto a la velocidad de las soluciones hay que notar que la solucin basada en operaciones de E/S realiza ms
llamadas al sistema operativo que la basada en proyeccin y, adems, exige copiar los datos al espacio de memoria
del proceso de usuario (en el buffer buf). Por su lado, la solucin basada en proyeccin requiere atender los fallos
de pgina de las regiones proyectadas y, por razones de seguridad, limpiar las pginas nuevas que se asignen al fi -
chero en el que se copia. La velocidad de cada solucin depende mucho de la implementacin del sistema operativo
y de las caractersticas de la operacin solicitada.
Sobre cul de las soluciones es ms apta para grandes cantidades de datos, est muy claro, si consideramos un
sistema de 32 bits malamente podremos proyectar zonas de ficheros por decenas de gigas. Simplemente no caben en
nuestro mapa de memoria. Esto se podra solucionar realizando proyecciones parciales. Por otro lado, la implemen -
tacin basada en E/S no plantea ningn lmite en el tamao de la copia.
e) Estamos hablando de un mecanismo de sincronizacin adecuado para coordinar la coutilizacin de ficheros por
parte de procesos pesados arrancados independientemente. La definicin/adecuacin est muy clara. Debemos usar
cerrojos sobre ficheros. Adems, estos permiten distinguir entre uso compartido y exclusivo, para accesos concu -
rrentes de lectura o accesos exclusivos de escritura, respectivamente.
Por supuesto que, si no hacemos nada, podran darse situaciones de interbloqueo al requerirse dos cerrojos por
ejecucin de cyp. La solucin ms cmoda para esto es establecer algn orden global que nos permita tomar todos
los recursos que se requieran de manera ordenada. De este modo ser imposible que aparezca un ciclo de posesin,
reclamo de recursos.
/* Bloquea las zonas a leer/escribir,
* para evitar la corrupcin de los datos.
* Aplica in orden global nico para evitar interbloqueos.
*/
int copiaypega_lock(int fdorg, int offorg, int size, int fddst, int offdst)
{
struct flock florg;
struct flock fldst;
struct stat storg;
struct stat stdst;
int ret;
florg.l_whence = SEEK_SET;
florg.l_start = offorg;
florg.l_len = size;
florg.l_pid = getpid();
florg.l_type = F_RDLCK;
fldst.l_whence = SEEK_SET;
fldst.l_start = offdst;
fldst.l_len = size;
fldst.l_pid = getpid();
fldst.l_type = F_WRLCK;
ret = fstat(fdorg, &storg);
if (ret < 0) return ret;
ret = fstat(fddst, &stdst);
if (ret < 0) return ret;
if (storg.st_dev < stdst.st_dev) {
ret = fcntl(fdorg, F_SETLKW, &florg);
if (ret < 0) return ret;
ret = fcntl(fddst, F_SETLKW, &fldst);
} else if (storg.st_dev > stdst.st_dev) {
ret = fcntl(fddst, F_SETLKW, &fldst);
if (ret < 0) return ret;
ret = fcntl(fdorg, F_SETLKW, &florg);
} else {
if (storg.st_ino < stdst.st_ino) {
ret = fcntl(fdorg, F_SETLKW, &florg);
127
if (ret < 0) return ret;
ret = fcntl(fddst, F_SETLKW, &fldst);
} else if (storg.st_ino > stdst.st_ino) {
ret = fcntl(fddst, F_SETLKW, &fldst);
if (ret < 0) return ret;
ret = fcntl(fdorg, F_SETLKW, &florg);
} else {
if (offorg < offdst) {
ret = fcntl(fdorg, F_SETLKW, &florg);
if (ret < 0) return ret;
ret = fcntl(fddst, F_SETLKW, &fldst);
} else if (offorg > offdst) {
ret = fcntl(fddst, F_SETLKW, &fldst);
if (ret < 0) return ret;
ret = fcntl(fdorg, F_SETLKW, &florg);
} else {
/* Mismo fichero, misma zona. Mala cosa */
ret = -1;
}
}
}
return ret;
}
/* Desbloquea las zonas a leer/escribir.
*/
int copiaypega_ulck(int fdorg, int offorg, int size, int fddst, int offdst)
{
struct flock fl;
int ret;
fl.l_whence = SEEK_SET;
fl.l_start = offorg;
fl.l_len = size;
fl.l_pid = getpid();
fl.l_type = F_UNLCK;
ret = fcntl(fdorg, F_SETLK, &fl);
if (ret < 0) return ret;
fl.l_whence = SEEK_SET;
fl.l_start = offdst;
fl.l_len = size;
fl.l_pid = getpid();
fl.l_type = F_UNLCK;
ret = fcntl(fddst, F_SETLK, &fl);
if (ret < 0) return ret;
return ret;
}
/* Copia size bytes
* del fichero de nombre org en su posicin absoluta offorg
* al fichero de nombre dst en su posicin absoluta offdst.
*/
int cyp(char * org, int offorg, int size, char * dst, int offdst)
{
int fdorg = open(org, O_RDONLY);
int fddst = open(dst, O_RDWR|O_CREAT, 0666);
int ret = 0;
if (fdorg < 0) ret = -1;
if (fddst < 0) ret = -1;
#if 0
/* Mediante Entrada/Salida convencional. */
if (ret >= 0) ret = copiaypega_lock(fdorg, offorg, size, fddst, offdst);
if (ret >= 0) ret = copiaypega_rdwr(fdorg, offorg, size, fddst, offdst);
if (ret >= 0) ret = copiaypega_ulck(fdorg, offorg, size, fddst, offdst);
#else
/* Mediante Proyeccin de Ficheros en Memoria. */
if (ret >= 0) ret = copiaypega_ajst(fdorg, offorg,&size, fddst, offdst);
if (ret >= 0) ret = copiaypega_lock(fdorg, offorg, size, fddst, offdst);
if (ret >= 0) ret = copiaypega_mmap(fdorg, offorg, size, fddst, offdst);
if (ret >= 0) ret = copiaypega_ulck(fdorg, offorg, size, fddst, offdst);
#endif
if (fdorg >= 0) close(fdorg);
if (fddst >= 0) close(fddst);
return ret;
}
128 Problemas de sistemas operativos

Problema 2.23 (sep 2010)

Sea la siguiente descripcin de un programa.


El programa consistir en tres procesos que llamaremos padre, hijo y nieto.
Se abrir como origen de datos el fichero indicado como primer argumento.
Se crear como destino de datos el fichero indicado como segundo argumento.
Los procesos debern estar convenientemente comunicados para intercambiar informacin y comportar-
se segn se describe.
El hijo leer los datos de origen, en trozos de medio KiB y los enviar al nieto y simultneamente ir
calculando el nmero de bytes de origen.
El nieto habr de ejecutar el mandato estndar indicado como tercer argumento (que denominaremos
filtro), para procesar la informacin que le llegue del hijo y enviar su resultado al destino.
NOTA: Se ha de entender que el mandato filtro, como otros mandatos estndar, si se invoca sin argu-
mentos, se comporta como un filtro entre su entrada estndar y su salida estndar.
Cuando el nieto haya concluido, el hijo emitir por su salida estndar una lnea de texto indicando el
numero total de bytes de origen.
Cuando el hijo haya concluido, el padre emitir por su salida estndar una lnea de texto indicando el
numero total de bytes de destino. Esta informacin habr de obtenerla directamente de dicho descriptor.
Se pide:
a) Dibuje un esquema con la jerarqua de procesos requerida donde aparezcan los descriptores abiertos por es-
tos procesos y los objetos de datos asociados a estos descriptores.
b) Se pide que implemente en C y para UNIX el programa descrito, haciendo uso directo de los servicios del sis -
tema operativo.
Suponga que el sistema donde se ejecuta este programa dispone de dos discos hda1 y hdb1, usados para el
sistema de ficheros raz y para las cuentas de usuario (montado en /home), respectivamente.
c) Detalle los accesos al sistema de ficheros (a inodos y a bloques datos) necesarios para completar la llamada
de apertura del fichero de origen /home/alumno/tmp/datos.txt.
d) Suponga que en el sistema de ficheros de hda1 existe (antes de montar hdb2) el archivo /home/archivo,
as como dos enlaces a este, uno fsico en la ruta /tmp/fisico y otro simblico en /tmp/simbolico.
e) Determine cul sera el comportamiento del programa si el superusuario invoca al programa indicando
como destino las siguientes rutas:
/home/archivo
/tmp/fisico
/tmp/simbolico

SOLUCIN
a) Esquema:
padre
origen destino

X
129
origen destino
hijo X
pphn[1]
argv[1] argv[2]

X
origen destino 1
nieto
exec(argv[3])
pphn[0] 0

Figura 2.18

b) Programa:
/* programa */

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define SIZE 512

int main(int argc, char*argv[])


{
int origen;
int destino;
int padre = getpid();
int hijo;
int nieto;
int pphn[2];
unsigned char datos[SIZE];
int ret;
int nbytes = 0;
int status;

origen = open(argv[1], O_RDONLY);


if (origen < 0)
{
perror(argv[1]);
exit(1);
}
destino = creat(argv[2], 0666);
if (destino < 0)
{
perror(argv[2]);
exit(1);
}
switch(hijo=fork())
{
default: /* padre */
close(origen);
waitpid(hijo,&status,0);
nbytes = lseek(destino,0,SEEK_CUR);
printf("Total bytes destino = %d\n", nbytes);
break;
case 0: /* hijo */
ret = pipe(pphn);
if (ret < 0)
{
perror(argv[0]);
exit(1);
}
switch(nieto=fork())
{
130 Problemas de sistemas operativos
default: /* hijo */
close(destino);
close(pphn[0]);
while ((ret = read(origen,datos,SIZE)) > 0)
{
nbytes += ret;
write(pphn[1],datos,ret);
}
close(pphn[1]);
waitpid(nieto,&status,0);
printf("Total bytes origen = %d\n", nbytes);
break;
case 0: /* nieto */
close(origen);
close(0); dup(pphn[0]); close(pphn[0]); close(pphn[1]);
close(1); dup(destino); close(destino);
execvp(argv[3],&(argv[3]));
perror(argv[3]);
exit(1);
case -1: /* error */
perror(argv[0]);
exit(1);
}
break;
case -1: /* error */
perror(argv[0]);
exit(1);
}

return 0;
}
c) Accesos a inodos y bloques.
acceso a num. device path luego
inodo 2 hda1 / DIR, bloques: 100, ...
bloque 100 hda1 // entrada: home, 20
inodo 20 hda1 /home flag: sistema montado hdb1
inodo 2 hdb1 / DIR, bloques: 333, ...
bloque 333 hdb1 / entrada: alumno, 44
inodo 44 hdb1 /alumno DIR; bloques: 555, ...
bloque 555 hdb1 /alumno/ entrada: tmp, 66
inodo 66 hdb1 /alumno/tmp DIR, bloques: 777, ...
bloque 777 hdb1 /alumno/tmp/ entrada: datos.txt, 88
inodo 88 hdb1 /alumno/tmp/datos.txt FILE, permisos RD
d) Comportamiento si destino es:
/home/archivo
Dicho archivo est oculto bajo el punto de montaje /home. Luego la llamada creat no lo vera, puesto que ira
a ver si existe la entrada archivo en el directorio raz (inodo 2) del sistema montado hdb1. Al no existir, se crea-
ra este.ooo
/tmp/fisico
Un enlace fsico es otro nombre para un mismo inodo y datos. Digamos que el inodo de /home/archivo y el
de /tmp/fisico, es el 18553. La llamada creat estara truncando el fichero /tmp/fisico y, por lo tanto,
tambin el de /home/archivo.
/tmp/simbolico
Al decodificarse esta ruta durante la llamada creat, el enlace simblico nos llevara a intentar decodificar
aquella a la que apunta, esto es: /home/archivo. Lo cual no s lleva al primer caso que ya hemos visto.
131

Problema 2.24 (oct-2010)

Sea un sistema de ficheros de tipo UNIX con bloques de 1 KiB, cuya estructura de directorios se indica, parcial -
mente, a continuacin. Asuma que el directorio tmp est vaco inicialmente.
tmp drwxrwxrwt root root
bin drwxr-xr-x root root
etc drwxr-xr-x root root
home drwxr-xr-x root root
rodri drwxr-xr-x rodri prof
perez drwxr-xr-x perez adm
lopez drwxr-xr-x lopez adm
ruiz drwxr-xr-x ruiz prof
fich1 -rwxrwxr-- ruiz prof

Tres procesos P1 (usuario rodri, grupo prof) P2 (usuario ruiz, grupo prof) y P3 (usuario lopez, grupo
adm) ejecutan las sentencias de la tabla adjunta, en el orden de dicha tabla.
Orden Proceso
1 P1 umask(007);
2 P1 fd1 = open("/home/ruiz/fich1", O_WRONLY|O_CREAT|O_TRUNC, 0640);
3 P1 fd2 = open("/tmp/pru", O_WRONLY|O_CREAT|O_TRUNC, 0660);
4 P1 buf = ABCDEFGHIJK
5 P1 write(fd2, buf, 10);
6 P1 lseek(fd2,-8,SEEK_END);
7 P2 umask(000);
8 P2 fd1 = open("/tmp/pru", O_RDWR, 0666);
9 P2 i = 24; //i est definido como un entero y ocupa 4 bytes
10 P2 lseek(fd1,2,SEEK_CUR);
11 P2 write(fd1, &i, sizeof(int));
12 P1 fork(); //Genera el proceso P4
13 P4 lseek(fd2,5200,SEEK_SET);
14 P2 lseek(fd1,1024,SEEK_CUR);
15 P1 write(fd2, buf, 10);
16 P2 write(fd1, &i, sizeof(int));
17 P3 fd1 = open("/home/ruiz/fich1", O_RDWR, 0666);
18 P1 write(fd1, buf, 10);
19 P3 write(fd1, "BB", 2);
Contestar las siguientes preguntas:
a) Indicar los permisos de /home/ruiz/fich1 y de /tmp/pru despus de ejecutarse la orden n 8.
b) Indicar el contenido de /tmp/pru despus de ejecutarse la orden n 11
c) Indicar el tamao de /tmp/pru despus de ejecutarse la orden n 15, as como el espacio fsico que ocupa.
d) Indicar el tamao de /tmp/pru despus de ejecutarse la orden n 16, as como el espacio fsico que ocupa.
e) Indicar el contenido de los 15 primeros bytes de /home/ruiz/fich1 despus de ejecutarse la orden n
19.
f) Realizar una grfica con las estructuras del servidor de ficheros despus de ejecutarse la orden n 19.
g) Indicar qu ocurre si el proceso P3 ejecuta despus de la orden n 19 la sentencia
unlink("/tmp/pru");. Indicar qu ocurre si es el proceso P2 quin la ejecuta.

SOLUCIN
a) El fichero /home/ruiz/fich1 ya existe, por lo que el open no cambia sus permisos, que son: 774. El fichero
/tmp/pru no existe previamente, por lo que el open lo crea y establece sus permisos, que son: 660.
b) Despus de la orden 5 el contenido de /tmp/pru, expresado en ASCII, es: ABCDEFGHIJ y expresado en exa-
decimal es: 41 42 43 44 45 46 47 48 49 4A. En la orden 8, el proceso P2 abre, a su vez, este fichero, por lo que tiene
132 Problemas de sistemas operativos
su propio puntero, que est a 0. En la orden 10 el proceso P2 mueve su puntero a la posiscin 2 y en la orden 11 es-
cribe cuatro bytes con el contenido de la variable i, que exadecimal es 00 00 00 18. El resultado depende de si el
computador es de tipo little endian o big endian. Para little endian ser: 41 42 18 00 00 00 47 48 49 4A y para big
endian ser: 41 42 00 00 00 18 47 48 49 4A
c) El proceso P4 y P1 comparten puntero, por lo que la orden 15 escribe desde la posicin 5200, haciendo que el ta -
mao del fichero pase a ser de 5210 B. Dado que exite un hueco que va desde la posicin 11 a la 5199, el servidor
de ficheros no asignar espacio fsico a los bloque intermedios, es decir, solamente exitir un bloque que da soporte
a las posiciones 0 a 1023 del fichero y cuyo contenido ser 41 42 18 00 00 00 47 48 49 4A ms 1014 blancos (el blo-
que ha de quedar limpio con la orden 15) y un bloque da soporte a las posiciones 5120 a 6133 del fichero y conten-
dr blancos en sus posiciones 0 a 79, seguido de 41 42 43 44 45 46 47 48 49 4A, siendo el resto basura. En resumen
el fichero tiene asignados dos bloques, es decir, 2 KiB de espacio fsico,
d) En la orden 16 el proceso P2 escribe en las posiciones 1030 a 1034 del fichero, lo que supone que el servidor de
ficheros debe asignar un nuevo bloque. El fihero no cambia de tamao, pero ahora ocupa 3 KiB de espacio fsico.
e) El proceso P3 no tiene permisos de escritura en el fichero /home/ruiz/fich1, por lo tanto la apertura fracasa
y como intenta escribir en el descriptor -1, la escritura tambin fracasa. El fichero ha sido truncado en la apertura por
el proceso P1, por lo que solamente tiene un tamao de 10 B que adquiere en la escritura de la lnea 18. Su conteni-
do expresado en ASCII, es: ABCDEFGHIJ y expresado en exadecimal es: 41 42 43 44 45 46 47 48 49 4A.
f) La figura 2.19 muestra las estructuras de datos del servidor de ficheros
BCP P1 BCP P2 BCP P3 BCP P4 Tabla intermedia
fd fd fd fd nNodo-i Posicin Referen. rw
0 1 0 3 0 5 0 1 1
1 2 1 4 1 6 1 2
2 2 2 4 2 6 2 2 21 33 10 2 01 (fich1)
3 21 3 23 3 0 3 21 22 78 5210 2 01 (pru)
4 22 4 0 4 0 4 22 23 78 1034 1 11 (pru)
5 0 5 0 5 0 5 0 24
6 0 6 0 6 0 6 0

Tabla nodos-i
Nodo-i Tipo nopens Tamao Bloques

33 1 10 354
78 2 5210 248, 3652, 476

Figura 2.19
g) Observar que el directorio /tmp tiene activo el bit t, lo que significa que solamente el dueo de un fichero lo
puede borrar. Dado que el dueo del fichero /tmp/pru es el usuario rodri, grupo prof, ni el proceso P2 ni el
P3 tienen permisos para realizar un unlink, por lo que fracasar.

Problema 2.25 (octubre 2011)

Sean los fragmentos de programas de la tabla adjunta, dichos programas se ejecutan de forma entrelazada de
acuerdo a la secuencia establecida en dicha tabla. Supondremos que el fichero fich existe y tiene un tamao de
20 bytes, que el tamao del bloque es de 1 KiB y que ninguno de los servicios produce error. Indicar el contenido
del fichero y el valor de los punteros de posicin de ambos procesos al final de la ejecucin (lnea 9).
Orden de Programa 1 Programa 2
ejecucin
1 fd = creat(/tmp/fich,0666);
2 write(fd, 01234, 4);
3 fd = open(/tmp/fich, O_WRONLY);
4 lseek(fd, 10, SEEK_SET);
5 lseek(fd, -4, SEEK_END);
6 write(fd, 8888, 4);
7 write(fd, abc, 3);
133
8 lseek(fd, -6, SEEK_CUR);
9 write(fd, A, 1);

SOLUCIN
Lnea 2: 0123
Lnea 6: 01230000008888
Lnea 7: abc30000008888
Lnea 9: abc30000A08888 (tamao = 14 bytes)
Al terminar, p1 apunta a byte 9 (0), p2 apunta a byte 3 (3)

Problema 2.26 (octubre 2011)

Dado un sistema de ficheros tipo UNIX con direcciones de 32 bits, bloques de 4 KiB y agrupaciones de 2 bloques:
j) Calcular el nmero de direcciones por agrupacin.
k) Calcular el mximo nmero de agrupaciones direccionables.
l) Calcular el tamao mximo (en bytes) de fichero suponiendo un nodo-i con estructura interna igual a la pre-
sentada en las transparencias de teora.
m) Calcular el nmero de accesos a disco necesarios para leer el byte 32 MiB de un fichero cuyo nodo-i ya est
en memoria principal y suponiendo que el sistema no dispone de cach de acceso a disco.

SOLUCIN
a) Calcular el nmero de direcciones por agrupacin.
Tamao agrupacin = tamao bloque * n bloques por agrup. = 4 KiB/bloque * 2 blq/agrup = 8 KiB / agrupacin
N direcciones/agrup = tamao agrup. / ancho direccin = (8 KiB/agr) / (4B/dir) = 2 Kidirecciones / agrupacin
b) Calcular el mximo nmero de agrupaciones direccionables.
Mximo n de agrupaciones direccionables = 2^32 agr = 4 Gi agrupaciones
c) Calcular el tamao mximo (en bytes) de fichero suponiendo un nodo-i con estructura interna igual a la presenta-
da en las transparencias de teora.
- 10 punt. directos a agr: 10 agr
- 1 punt. ind. Simple: 2 Ki agr
- 1 punt. ind. Doble: 2 Ki^2 agr = 2^2 Mi agr
- 1 punt. ind. triple: 2Ki^3 agr = 2^3 Gi agr
- Total: (2^3 Gi + 2^2 Mi + 2 Ki + 10) agr * 8 KiB/agr = 2^6 TiB + 2^5 GiB + 2^4 MiB + 80 KiB =
= 64 TiB + 32 GiB + 16 MiB + 80 KiB
d) Calcular el nmero de accesos a disco necesarios para leer el byte 32 MiB de un fichero cuyo nodo-i ya est en
memoria principal y suponiendo que el sistema no dispone de cach de acceso a disco.
El byte 32MiB corresponde a un puntero indirecto doble, que (dado que el nodo-i ya est en memoria) requiere 3 ac-
cesos a disco (2 accesos a agrupaciones de punteros y 1 acceso final a la agrupacin de datos)

Problema 2.27 (octubre 2011)

Completar el siguiente programa fuente (escribir el cdigo que falta en los puntos A, B y C) para que realice lo si -
guiente:
- Comunicar con un pipe la salida estndar del padre con la entrada estndar del hijo.
134 Problemas de sistemas operativos
- El padre abre el fichero fich1 y lo copia al pipe.
- El hijo lee del pipe y lo copia al fichero fich2
- Al terminar, fich2 debe contener los mismos datos que fich1
Notas:
- Minimizar el nmero de servicios usados.
- Minimizar el nmero de descriptores de fichero abiertos.
main(void){
<A>
switch (fork()){
case -1: perror(fork()); exit(1);
case 0: /* HIJO */
<B>
return 0;
default: /* PADRE */
<C>
return 0;
}
}

SOLUCIN
#define SZ 1024
<A>
int pp1[2], fd, n1;
char buf[SZ];
pipe(pp1);

<B>
close(pp1[1]);
dup2(pp1[0], 0);
close(pp1[0]);
fd = open("fich2", O_WRONLY|O_CREAT|O_TRUNC, 0666);/*Alternativa: creat()*/
while((n1 = read(0, buf, SZ)) > 0)
write(fd, buf, n1);

<C>
close(pp1[0]);
dup2(pp1[1], 1);
close(pp1[1]);
fd = open("fich1", O_RDONLY);
while((n1 = read(fd, buf, SZ)) > 0)
write(1, buf, n1)

Problema 2.28 (marzo 2012)

a) Sea un dispositivo de almacenamiento de 240 MiB con un sistema de ficheros de tipo UNIX. Contestar las si-
guientes preguntas. En cada caso indique las unidades en que expresa los clculos.
a1) Escoja un tamao razonable de agrupacin para este sistema de ficheros. Cuntas agrupaciones
habr? De qu tamao sern las direcciones de agrupacin? Cunto ocupar el mapa de bits
correspondiente?
a2) Considere un nico archivo de 128 MiB de datos en este sistema de ficheros. Calcule la cantidad de
metadatos (informacin necesaria para poder localizar los datos) que requiere este archivo.
a3) Suponiendo que el tamao medio de los archivos a almacenar en el sistema es de 8 KiB, cunto
espacio ocupar el mapa de bits de nodos_i? y cunto el vector de nodos_i?
135
b) El dispositivo anterior tiene, entre otros, los siguientes directorios:
drwxr-xr-x root root /
drwxrwxrwt root root /tmp
drwxr-xr-x root root /bin
drwxr-xr-x root root /etc
drwxr-xr-x root root /home
drwxrwxr-x rodri prof /home/rodri
drwxr-xr-x perez adm /home/perez
drw-r-xr-x root root /home2
Existe otro dispositivo, de caractersticas similares al anterior, que est montado, con la opcin MS_NOSUID,
sobre home2 y que tiene los siguientes directorios y ficheros:
drwxr-xr-x root root /
drwxr-xr-x lopez adm /lopez
drwxr-xr-x ruiz prof /ruiz
-rwxrwxr-- ruiz prof /ruiz/fich1
-rwsrwxr-- ruiz prof /ruiz/p.exe
Se ponen en ejecucin los procesos A (usuario y grupo efectivo: ruiz y prof) y B (usuario y grupo efectivo: rodri
y prof).
b1) El proceso B crea un hijo que intenta ejecutar el programa p.exe. Explicar si puede ejecutar dicho
programa. Indicar y explicar los valores reales y efectivos de usuario y grupo de dicho proceso.
b2) Completar la tabla adjunta que indica la secuencia ordenada en tiempo de servicios ejecutados por
los procesos. Inicialmente los procesos tienen como directorio de trabajo su 'home' y como mscara
0750.
N Proceso Servicio Valor Permisos, dueo, contenido, del fichero
devuelto usado en el servicio
1 A mimasc = umask(0037);
2 A dir = chdir("/home/rodri");
3 A fda = creat("F1.txt", 0771); Permisos: Dueo:
4 B fdb1 = open("F1.txt", O_WRONLY| Permisos:
O_CREAT|O_TRUNC, 0640);
5 A n = write(fda, 1234, 3); Contenido:
6 A m = chown("F1.txt", rodri, prof); Dueo:
7 A n = write(fda, 5678, 4); Contenido:
8 B fdb2 = open("F1.txt", O_RDWR);
9 B n = write(fdb2, abcd, 4); Contenido:
10 A fork, creando AH
11 AH n = write(fda, 1234, 3); Contenido:
b3) Realizara una figura con las principales estructuras de informacin utilizadas por el servidor de
ficheros que refleje la situacin existente al final de secuencia anterior.

Solucin
a1) El tamao del bloque vara entre 1 KiB y 8 KiB. Por otro lado, en Unix la agrupacin suele ser de un bloque. En
la tabla adjunta se muestra el nmero de agrupaciones y de bits para varios tamaos de agrupacin.
Tamao de la agrupacin N de agrupaciones Mnimo n de bits de la direccin Mapa bits
1 KiB 240 Ki agrupaciones 18 bits 30 bloques
2 KiB 120 Ki agrupaciones 17 bits 8 bloques
4 KiB 60 Ki agrupaciones 16 bits 2 bloques
8 KiB 30 Ki agrupaciones 15 bits 1 bloque
136 Problemas de sistemas operativos
En realidad el tamao de las direcciones de las agrupaciones viene determinada por el software del servidor de
ficheros y no por el tamao del disco. Por lo que este tamao en un gran porcentaje de las implementaciones actua-
les ser de 32 bits.
El mapa de bits debe ocupar un nmero entero de bloques, por lo que hay que redondear por exceso.
a2) El tamao de la metainformacin depender del tamao de la agrupacin y de las direcciones, por lo que plan -
tearemos los cuatro casos anteriores:
Tamao agrupacin (R) 1 KiB 2 KiB 4 KiB 8 KiB
Direcciones/agrupacin (S = R/4) 256 512 1 Ki 2 Ki
Agrupaciones del fichero (T = 128 128 Ki 64 Ki 32 Ki 16 Ki
MiB / R)
Direcciones en nodo_1 (U) 10 10 10 10
Agrupacin indirectos (Y) 1 1 1 1
Agrupaciones para doble indirecto 256 (ms el ndice) 127 (ms el ndice) 31 (ms el ndice) 7 (ms el ndice)
usados (V)
Agrupaciones para triple indirecto 255 (ms dos de n- ninguno ninguno ninguno
usados (X) dice)
Tamao metainformacin 516 KiB + 128 B 258 KiB + 128 B 132 KiB + 128 B 72 KiB + 128 B
((V + X + Y)R + 128 B)
V = (T U Y*S)/S, con la condicin de que V=< S. Esta condicin solo se aplica para la agrupacin de 1 KiB
X = (T U Y*S V*S)/S = (T U)/S Y V
Se observa que con agrupaciones ms grandes se requiere menos espacio de metainformacin, pero aumenta la
fragmentacin interna (por trmino medio se pierde la mitad de la ltima agrupacin).
Un clculo aproximado, algo menor que el real, se puede hacer considerando solamente el nmero de direccio -
nes de bloque que se necesitan multiplicado por el tamao de la direccin. Esto dara, respectivamente, para las
agrupaciones de 1, 2, 4 y 8 KiB, los tamaos de 512 KiB, 256 KiB, 128 KiB y 64 KiB.
a3) Si el tamao medio de los ficheros es de 8 KiB, cabran en el mismo 240 MiB / 8 KiB = 30 Ki ficheros. Se nece -
sitaran 30 Kib en el mapa de bits y 30 Ki nodos_i en el vector de nodos_i (en la realidad se pondran ms, puesto
que es muy negativo quedarse sin nodos_i mientras quede espacio en el disco). Por otro lado, el tamao tpico del
nodo_i es de 128 B.
Dado que tendremos un nmero entero de bloques para el mapa y el vector, el espacio realmente ocupado se muestra
en la tabla adjunta.
Tamao agrupacin (R) 1 KiB 2 KiB 4 KiB 8 KiB
Agrupaciones del mapa bits (30 Kib/R) 4 2 1 1
Tamao ocupado por el mapa bits 4 KiB 4 KiB 4 KiB 8 KiB
Agrupaciones del vector nodos_i (30x128 KiB/R) 3840 1920 960 480
Tamao ocupado por el vector de nodos_i 3.840 KiB 3.840 KiB 3.840 KiB 3.840 KiB
b1) Todos los directorios tienen la x activa en los tres grupos, por lo que se puede llegar al fichero p.exe. El proceso
B tiene usuario y grupo efectivo: rodri y prof. Por tanto, puede ejecutar el programa p.exe, que tiene permisos de
ejecucin para el grupo prof.
El programa p.exe tiene activo el bit SETUID, por lo que parecera que el hijo del proceso B tomara la identidad
del dueo del fichero, es decir usuario y grupo efectivo: ruiz y prof. Sin embargo, no es as, puesto que el disco se ha
montado con la opcin MS_NOSUID que inhibe los bits SETUID y SETGID, manteniendo el usuario y grupo
efectivo: rodri y prof. Por otro lado, la identidad real es la heredada del proceso B, que supondremos es rodri y prof.
b2) .
N Proceso Servicio Valor de- Permisos, dueo y contenido del fichero usa-
vuelto do en el servicio
1 A mimasc = umask(0037); 0750
137

2 A dir = chdir("/home/rodri"); 0
3 A fda = creat("F1.txt", 0771); 3 Permisos: 0740 Dueo: ruiz, prof
4 B fdb1 = open("F1.txt", O_WRONLY| -1 Permisos: 0740
O_CREAT|O_TRUNC, 0640);
5 A n = write(fda, 1234, 3); 3 Contenido: 123
6 A m = chown("F1.txt", rodri, prof); 0 Dueo: rodri, prof
7 A n = write(fda, 5678, 4); 4 Contenido:1235678
8 B fdb2 = open("F1.txt", O_RDWR); 3
9 B n = write(fdb2, abcd, 4); 4 Contenido:abcd678
10 A fork, creando AH
11 AH n = write(fda, 1234, 3); 3 Contenido: abcd678123

Nodo_i
BCP A BCP B BCP AH Nodo_i Referencias N_opens Agrupaciones
Tamao
Tabla fd Tabla fd Tabla fd Posicin rw
31 d1-13 10 2 -w
0 1 0 4 0 1
32
1 2 1 5 1 2
33 d1-13 2 4567 10
2 2 2 5 2 2
34 d1-13 4 1 rw
3 31 3 34 3 31
35
4 0 4 0 4 0
36
5 0 5 0 5 0
37
6 0 6 0 6 0
38
7 0 7 0 7 0
Tabla intermedia Tabla de nodos_i
Figura 2.20
b3) Supondremos que los procesos solamente tienen abiertas la entrada y salidas estndar, que el nodo_i del fichero
es el 13 del disco d1 y que las primeras lneas libres de la tabla intermedia son la 31 y la 34. La figura 2.20 muestra
el contenido de las estructuras pedidas.

Problema 2.29 (julio 2012)

Se desea disear un programa Lanzador que reciba como argumentos las rutas de dos ficheros fich1 y fich2 ms un
nmero variable de nombres de programas, que llamaremos worker1, worker2, worker3, etc. El programa Lanza-
dor debe poner en ejecucin los workers que debern acceder a los dos ficheros.
fich1 est formado por registros de 124 B, que contienen informacin para los workers, mientras que fich2 est
formado por registros de 12 B, que se rellenarn por los workers.
Se contemplan dos escenarios: Lanzador1 y Lanzador2.
Para el caso de Lanzador1, cada worker lee un registro (no ledo por nadie ms) de fich1, lo procesa y escribe
el resultado en un registro de fich2. El orden de los registros de fich2 no es importante, es decir, el resultado del re-
gistro 5 de fich1 no tiene por qu estar en el registro 5 de fich2.
a) Plantear justificadamente cmo pasar Lanzador1 los ficheros a los workers.
b) Plantear justificadamente si es necesario que Lanzador1 espere a que terminen todos los workers antes de
terminar. En cualquier caso, codificar cmo esperara.
c) Disear Lanzador1 para el supuesto de no esperar a que terminen los workers.
d) Disear los workers, suponiendo que el tratamiento del registro lo hace una funcin fun1, fun2, fun3, etc. res -
pectivamente para cada worker.
Para el caso de Lanzador2 el worker1 lee el primer registro de fich1 lo procesa con fun1 y le pasa el resultado
al worker2, seguidamente lee el segundo registro, lo procesa y le pasa el resultado al worker2 y as sucesivamente
138 Problemas de sistemas operativos
hasta el final de fich1. El Worker2 vuelve a leer el mismo registro de fich1 que utiliz el worker1 y lo combina me -
diante fun2 con el resultado producido por el worker1, generando un resultado que pasa al worker3, y as sucesiva-
mente. Cada worker recibe del worker anterior un resultado rlativo al registro n, lee el registro n de fich1 y genera
un realtado que pasa al siguiente worker. El ltimo worker de la cadena escribe en fich2 el resultado final de haber
procesado cada registro de fich1 por todos los workers.
e) Plantear justificadamente cmo pasar Lanzador2 los ficheros a los workers.
f) Plantear detalladamente cmo pasa cada worker su resultado al siguiente, indicando los cambios a introducir
en el diseo de Lanzador1 para pasar a Lanzador2.
g) Disear los workers.

Solucin
a) Nos interesa que los workers compartan el puntero a los ficheros, para que leean automticamente registros con-
secutivos de fich1 y escriban automticamente registros consecutivos en fich2. Dado que tanto el fork como el exec
conservan los ficheros abiertos, basta con que Lanzador1 abra ambos ficheros y redirija la entrada estndar al fich1
y la salida estndar al fich2 en los procesos hijos antes de realizar el exec. Otra alternativa, no muy recomendable,
sera que Lanzador1 les pase a los workers como argumentos los correspondientes descriptores de fichero.
b) No hace falta que Lanzador1 espere a que terminen los procesos hijos workers. Lo que ocurrir ser que dichos
workers se quedarn hurfanos y los heredar el proceso init.
Dado que el nmero de workers ser de argc - 3, puesto que primero vienen los nombres de los ficheros y luego
los de los workers, Lanzador1 debe esperar por argc 3 procesos:
for (i=0; i< argc-3; i++)
{
wait(NULL);
}
Se puede observar que el cdigo propusto simplemente espera por argc 3, que, en un caso general, podran no ser
los workers si Lanzador1 generase ms hijos. Una solucin que garantice que se espera por los workers exige
almacenar los pid de los workers en un vector int workerpid[argc-3]; y hacer wait(workerpid[i-3])
c) Un posible cdigo para Lanzador1 es el siguiente.
int main (int argc, char *argv[]) {
int fde, fds, i, pid;
fde = open(argv[1],O_RDONLY);
fds = open(argv[2],O_RDWR);
if (fde = -1 || fds = -1) {
perror("Error en los ficheros.");
exit(1);
}
for (i=3; i< argc; i++){
pid = fork ;
switch( fork()) {
case -1:
perror("fork");
exit(1);
case 0:
dup2(0, fde);
dup2(1, fds);
execl(argv[i],argv[i]); //Se lanza el worker
perror(argv[i]);
exit(1);
}
}
return 0;
}
d) El cdigo de los workers puede ser el siguiente:
#define TAM_REGE 124
#define TAM_REGS 12
int main (void) {
char rege[TAM_REGE], regs[TAM_REGS] ;
while (read(0, rege, TAM_REGE)>0){
139
funi(rege, regs);
write (1, regs, TAM_REGS);
}
return 0;
}
e) En este caso, todos los worker necesitan su propio puntero a fich1, puesto que, para procesar el registro n, lo tie -
nen que leer previemente y combinar con el resultado del worker anterior. Por ello Lanzador2 deber pasar como ar-
gumento la ruta de dicho fichero. Adems, el ltimo worker escribe en el fichero fich2, por lo que Lanzador2 puede
abrir dicho fichero y redirigir la salida estndar del ltimo worker a dicho fichero (tambin podra pasar el nombre
del fichero al ltimo worker, pero esto le hara diferente de los otros).
f) Para pasar los resultados entre los workers, lo mejor es utilizar un pipe entre cada pareja de workers, redirigiendo
la entrada y salida estndar en cada hijo adecuadamente. De esta forma los workers leern el resultado del worker
anterior por su entrada estndar y escribirn por la salida estndar.
En la solucin propuesta se han lanzado todos los workers de la misma forma, pero hay que tener en cuenta que
el primero no leer del pipe de entrada.
int main (int argc, char *argv[]) {
int fds, i, pid;
int entrada, pipe[2];
entrada = dup(0);
for (i=3; i< argc; i++){
pipe(pipe); //En realidad para i = argc no hara falta crear el pipe porque no se usa
pid = fork ;
switch( fork()) {
case -1:
perror("fork");
exit(1);
case 0:
dup2(0, entrada);
close (entrada);
if (i = argc - 1) { //En el ltimo worker cambiamos el pipe de salida por el fichero fich2
close (pipe[1]);
fds = open(argv[2],O_RDWR);
pipe[1] = fds
}
dup2(1, pipe[1]);
close (pipe[0]);
execl(argv[i], argv[i], argv[2],NULL); //Se lanza el worker
perror(argv[i]);
exit(1);
default:
close (entrada);
entrada = pipe[0];
close (pipe[1]);
}
}
close (entrada);
return 0;
}
g) Suponiendo que el tamao de los resultados parciales que enva un worker al siguiente es tembin de 12 B, el c -
digo general propuesto para los workers es el siguiente:
#define TAM_REGE 124
#define TAM_REGS 12
#define TAM_RESULTADO 12
int main (void) {
int fde;
char rege[TAM_REGE], regs[TAM_REGS], resultado[TAM_RESULTADO];
fde = open(argv[1],O_RDONLY);
while (read(0, resultado, TAM_REGE)>0){
read(fde, rege, TAM_REGE);
funi(rege, resultado, regs);
write (1, regs, TAM_REGS);
}
return 0;
140 Problemas de sistemas operativos
}
El cdigo del primer worker es distinto, puesto que en el control de while no se lee de la entrada estndar sino
del descriptor fde y el segundo read no existe.

Problema 2.30 (marzo 2013)

Considere el contenido en un sistema de ficheros de un equipo Unix mostrado en la figura 2.21.


Exponer, en todas las preguntas, los pasos a seguir por el servidor de ficheros para determinar la respuesta
dada, indicando el nmero de lnea (de 1 a 18) y el grupo de permisos que interviene.
a) Pueden los usuarios pepe, macu y mari acceder a sus directorios?
b) Puede mari ejecutar el siguiente mandato?
/home/pepe/itec /home/macu/teri.c /home/macu/teri.bak
Indicar las identidades reales y efectivas del proceso itec.
c) Podr itec abrir para leer y escribir el fichero dado como primer argumento (teri.c)?
Podr itec crear el fichero dado como segundo argumento (teri.bak)?
raz Sistema de ficheros A (visin parcial)
1 drwxr-xr-x 10 root root .
sbin
2 drwxr-xr-x 3 root root sbin
3 drwxrwxrwt 45 root root kk 5 -rwsr-xr-x 1 root root mount.cifs
4 drwx------ 16 root root home

User Group Mask


raz (/home) Sistema de ficheros B (visin parcial) root root 000
6 drwxr-xr-x 14 root root . pepe pepe div1 022
13 drwx------ 8 pepe div1 prac1 macu div1 027
7 drwxrwx--x 8 pepe div1 pepe
mari div2 077
8 drwxrwx--- 12 macu div1 macu 14 -rwsr-x--- 1 pepe div1 iue
9 drwx--x--x 8 mari div2 mari 15 -rwxr-sr-x 1 macu div1 itec
16 -rw-rw-rw- 1 root root iome.c
macu 17 -rw-rw-rw- 1 pepe div1 ikelu
10 drwx------ 8 macu div1 prac1
11 -rw-rw---- 1 macu div1 teri.c mari
12 -rws--xr-x 1 macu div1 teri 18 drwx------ 8 mari div2 prac1

Figura 2.21

Solucin
a) Tendremos que analizar los permisos del directorio raz, del directorio home y de cada uno de los directorios
pepe, macu, mari. Marcaremos en negrita, subrayado y rojo el permiso que nos permite acceder. Es de destacar que
los permisos del directorio home se indican en la lnea 6 no la 4, puesto que, al estar montado el sistema de ficheros
B en home, los permisos del home original (lnea 4) no se consideran. Por otro lado, la mascara no tiene nada que
ver con los permisos de acceso, se utiliza exclusivamente para determinar los permisos con los que se crean los fi -
cheros.
Usuario Lnea Permisos Acceso
pepe 1 drwxr-xr-x Puede atravesar
6 drwxr-xr-x Puede atravesar
7 drwxrwx--x Puede atravesar
macu 1 drwxr-xr-x Puede atravesar
6 drwxr-xr-x Puede atravesar
7 drwxrwx--- Puede atravesar
mari 1 drwxr-xr-x Puede atravesar
6 drwxr-xr-x Puede atravesar
7 drwx--x--x Puede atravesar
141
b) Hay que ver los permisos para ejecutar /home/pepe/itec, los argumentos no hay que analizarlos para ver si
/home/pepe/itec se puede ejecutar.

Usuario Lnea Permisos Acceso


mari 1 drwxr-xr-x Puede atravesar
6 drwxr-xr-x Puede atravesar
7 drwxrwx--x Puede atravesar
15 -rwxr-sr-x Puede ejecutar
Dado que itec tiene activo el bit SETGID, el grupo efectivo se cambiar al grupo del fichero (es decir div1, no a
superusuario), por lo tanto, el proceso itec ejecutado por mari tendr las siguientes identidades
UID real = mari, GID real = div2, UID efectivo = mari. GID efectivo = div1
c) Se puede observar que, al igual que mari, todo usuario podr ejecutar itec y que el grupo efectivo de ese proceso
ser siempre div1. Para teri.c hay que ver que se puede llegar a acceder al fichero y que se tienen permisos de rw.
Para el fichero teri.bak hay que ver que se puede llegar la directorio macu y que se tienen permisos de escritura en el
mismo. Dado que teri.bak no existe, los permisos con los que se cree dependern de los permisos que se pongan en
el servicio open o creat as como de la mscara que tenga el usuario que ejecuta itec.
Fichero Lnea Permisos Acceso Comentario
teri.c 1 drwxr-xr-x Puede atravesar
6 drwxr-xr-x Puede atravesar
8 drwxrwx--x Puede atravesar Si el usuario fuese macu se utilizara la x del dueo.
En otros casos se usa la x del grupo
11 -rw-rw---- Puede abrir para rw Si el usuario fuese macu se utilizara rw del dueo
teri.bak 1 drwxr-xr-x Puede atravesar
6 drwxr-xr-x Puede atravesar
8 drwxrwx--x Puede crear teri.bak Si el usuario fuese macu se utilizara la w del dueo

Problema 2.31 (marzo 2013)

Tenemos que dar formato a un disco de 256 GiB que se va a emplear para almacenar fotografas de alta definicin,
con un tamao medio de 8 MiB por fotografa. El mandato que utilizaremos ser:
mke2fs -b 4096 -N numeronodos_i
La opcin -b permite definir el tamao de la agrupacin en bytes (valores vlidos son 1024, 2048 y 4096).
La opcin -N permite definir el nmero total de nodos_i del sistema de ficheros.
a) Calcular el valor que se debe dar a la opcin -N.
b) Calcular el espacio que se reservar en el sistema de ficheros para la gestin de las agrupaciones libres.

Solucin
a) Se necesita un nodo-i por cada fichero, por lo tanto, debemos ajustar el nmero de nodos-i al nmero previsto de
ficheros. No conviene quedarse cortos, puesto que podramos llegar a tener espacio libre, pero no nodos-i libres.
Tampoco conviene poner muchos ms de los necesario, puesto que ocupan espacio en el disco que se podra dedicar
a ficheros de usuario.
El numero de ficheros previstos ser el tamao del disco partido por el tamao de los ficheros, es decir, 256
GiB / 8 MiB = 32 Ki ficheros. El nmero mnimo que deberamos poner para la opcin -N es de 32 Ki (en la prcti -
ca se pondran bastante ms).
142 Problemas de sistemas operativos
Observacin: Suponiendo que el nodo-i ocupa 128 B, cada agrupacin alberga 4 KiB / 128 = 32 nodos-i. El n -
mero de nodos-i debera se mltiplo de 32, porque no tiene sentido dejar una agrupacin dedicada a nodos-i medio
llena.
b) El espacio libre se puede gestionar mediante un mapa de bits que tenga un bit por cada agrupacin del espacio li-
bre del disco. Para calcular el nmero de agrupaciones del disco, dividimos su tamao por el de la agrupacin: 256
GiB / 4 KiB = 64 Mi agrupaciones. Por tanto, el mapa de bits tendr 64 Mib = 8 MiB = 2 Ki agrupacin.
Por su lado, el mapa de bits de nodos-i necesita 32 Kib = 4KiB = 1 agrupacin.
De forma ms precisa habra que descontar, del nmero de agrupaciones del espacio libre, las empleadas por los
mapas de bits y por los nodos-i. Los nodos-i ocuparn 32 Ki nodos-i * 128 B/nodo-i = 4 MiB = 1 Ki agrupacin.
El espacio liebre es, realmente, 64 Mi agrupacin - 2 Ki agrupacin - 1 Ki agrupacin - 1 agrupacin, siendo ste
el nmero de bits del mapa de bits necesario para su gestin.

Problema 2.32 (julio 2013)

Un sistema Linux tiene los usuarios siguientes: A1 del grupo G1, A2 del grupo G2 y root del grupo root.
Las caractersticas de algunas de las entradas de directorio son las siguientes:
Entrada Permisos Enlaces Dueo Grupo
/ . drwxr-xr-x 32 root root
/mnt . drwxr-xr-x 5 root root
/mnt/usbflsh. d--------- 2 root root

Se dispone de una memoria USB en la que se ha establecido un sistema de ficheros con formato ext2.
Las caractersticas de algunas de las entradas de dicho sistema de ficheros son las siguientes:
Entrada Permisos Enlaces Dueo Grupo
/ . drwx--x--x 10 A1 G1
/Pro1 . drwx--x--x 5 A1 G1
/Pro1/doc1. -rw-rw-rw- 2 A1 G1

El usuario A1 ejecuta un programa que contiene la siguiente lnea de cdigo:


n = mount(/dev/sda1, /mnt/usbflsh, ext2, MS_NOEXEC | MS_RDONLY);
a) Que valor recibir en n? Decir la razn por la cul recibe dicho valor.
b) Suponer ahora que el administrador ejecuta la lnea de cdigo de la pregunta anterior con xito. Ha tenido
que hacer algn cambio en permisos, dueos o grupos para que la ejecucin sea exitosa?
Una vez montada la memoria USB con las opciones detalladas en la lnea de cdigo anterior, el usuario A2 eje -
cuta un programa que contiene la siguiente lnea de cdigo:
fd = open(/mnt/usbflsh/Pro1/doc1, O_RDWR, 0666);
c) Qu valor tendr fd? Por qu?
d) Ahora el que ejecuta el programa con la lnea anterior es el administrador. Qu valor tendr fd? Por qu?

Solucin
a) El usuario A2 no es root, por lo tanto no puede ejecutar el mandato mount, con independencia de los permisos.
b) El administrador o root puede ejecutar el mandato sin problemas y sin tener que modificar nada. Es de observar
que los permisos, dueo y grupo de /mnt/usbflsh no se tienen en cuenta, puesto que quedarn sustituidos por los del
directorio raz del dispositivo montado.
c) Una vez montado el dispositivo flash vemos que el usuario A2 tiene los siguientes permisos
/ drwxr-xr-x permiso de atravesar
/mnt drwxr-xr-x permiso de atravesar
/mnt/usbflsh dr-xr-xr-x permiso de atravesar
/mnt/usbflsh/Pro1 dr-xr-xr-x permiso de atravesar
/mnt/usbflsh/Pro1/doc1 -r--r--r-- permiso de lectura, pero no de escritura
Por lo tanto, el servicio open devuelve error, es decir, fd = -1.
143
Adicionalmente, el mandato incluye el argumento mode, que solamente se aplica si est la opcin O_CREAT.
Este argumento, por tanto, se ignora.
d) En principio al administrador o root no se le comprueban permisos, por lo que cabra pensar que puede abrir el fi -
chero, aunque ste no tenga permisos de escritura. El servicio devolvera la posicin de la primera entra de la tabla
de descriptores que est a 0, por ejemplo la 3, es decir, fd = 3. Sin embargo, una unidad montada como MS_RDONLY
es como si fsicamente no se pudiese escribir en ella, por lo que el root tampoco puede abrir el fichero para escritura,
recibiendo, por tanto, fd = -1 y producindose el error EROFS.

Problema 2.33 (marzo 2014)

En un sistema Linux se desea disponer de un mandato que cambie el dueo y los permisos de los elementos conteni-
dos en un subdirectorio dado. Por ejemplo, para que el jefe pueda acceder a los ficheros que tiene en su directorio
home un empleado que deja el trabajo.
El mandato deber estar basado en la siguiente funcin:
int CambiaSubDir(char *DirectRecorrer, uid_t owner, gid_t group);
Esta funcin ha de recorrer el subdirectorio DirectRecorrer y procesar cada elemento encontrado cambiando
su dueo a owner y group y cambiando sus permisos de acuerdo a las siguientes reglas:
Si es un enlace simblico no deber ser procesado.
Si es un fichero sin permiso de ejecucin para su propietario, le pondr permisos 0600.
Si es un fichero con permiso de ejecucin para su propietario, le dejar los permisos que tiene, incluidos
los posibles bits SETUID y SETGID.
Si es un directorio le pondr permisos 0700.
En cualquier otro caso, dejar los permisos que tenga.
Nota: Para determinar si un fichero tiene activo un bit de permiso basta con hacer un AND con un mscara que
tenga todos ceros menos un 1 en la posicin del bit a analizar. Por ejemplo, (mode & 00001) ser cierto si el fiche -
ro tiene permiso de ejecucin para el mundo, (mode & 00002) si tiene permiso de escritura para el mundo, etc. y
(mode & 04000) si tiene activo el bit SETUID.
a) Indicar justificadamente la identidad efectiva que deber tener dicho proceso para poder ejecutar con xito.
b) Indicar justificadamente en qu orden deben hacerse los siguientes pasos: cambiar el dueo, determinar los
permisos, determinar el tipo de fichero y cambiar los permisos.
c) Escribir la funcin CambiaSubDir.
d) Modificar la funcin para que se llame recursivamente en cada directorio encontrado.cSe puede pensar, en
un principio, que sera suficiente con la identidad del empleado, puesto que se trata de sus ficheros. Sin embargo, al
cambiar el dueo se pierden los bits de SETUID y SETGID, por lo que el dueo original del fichero si quisiese res -
tituirlos no podra porque el fichero ya no sera suyo. Por lo tanto, solamente un proceso con identidad efectiva de
root podra ejecutar dichas llamadas.
b) Lo primero es ver el tipo de fichero, puesto que si es un enlace simblico no hay que procesarlo.
Seguidamente, hay que obtener los permisos, puesto que si cambiamos el dueo antes de obtener los permisos
perderamos los bits de SETUID y SETGID.
Luego se cambia el dueo y grupo y, finalmente se cambian los permisos, incluyendo, en su caso, los bits de SE -
TUID y SETGID.
c) Un aspecto importante a tener en cuenta en el diseo del programa es que los nombres que se obtienen en la es -
tructura de tipo dirent es el nombre local dentro del directorio. Si queremos utilizar ese nombre, en servicios que
requieran el nombre como chown y chmod, deberemos concatenarlos al nombre del directorio.
En caso de fallo en alguno de los servicios del sistema operativo la funcin termina y devuelve un valor que indi-
ca el lugar donde se ha producido el problema.
La entrada .. no hay que modificarla, puesto que es el directorio padre. La entrada . significa que cambiamos
el propio directorio pasado como argumento, lo que es correcto o no segn se interprete el enunciado. En la sulucin
presentada se procesa la entrada . .
144 Problemas de sistemas operativos
En algunos casos, por ejemplo en Linux, la estructura dirent contiene un campo d_type que permite conocer
el tipo de fichero sin necesidad de llamar al servicio stat. Sin embargo, como se necesita analizar el modo del fi-
chero en nuestro caso es necesario la llamada stat. Deberemos utilizar la versin lstat, para poder reconocer los
enlaces simblicos.
#define MAX_BUF 300
int CambiaSubDir(char *DirectRecorrer, uid_t owner, gid_t group) {
DIR *dirp;
struct dirent *dp;
struct stat atributos
char fichero[MAX_BUF];
mode_t mascaraset;
//Se abre el directorio DirectRecorrer. Puede ser direccin absoluta o relativa
if (dirp = opendir(DirectRecorrer) == NULL) return 1;
//Se lee entrada a entrada del directorio empezando por el "." y el ".."
while ((dp = readdir(dirp)) != NULL) { //El readdir obtiene el nombre local
if (strcmp(dp->d_name, "..") == 0) continue; //El directorio padre no hay que tocarlo
strcpy (fichero, DirectRecorrer); //Hay que concatenar el nombre local a DirectRecorrer
strcat (fichero, "/"); //para tener el nombre completo
strcat (fichero, dp->d_name);
if (lstat(fichero, &atributos) < 0) return 2;
if (S_ISLNK(atributos.st_mode)) continue; //Caso de enlace simblico
// Otra altenativa en Linux para enlace simblico es poner: if (dp->d_type == DT_LNK) continue;
if (S_ISDIR(atributos.st_mode)) { //Caso de directorio
if (chown(fichero, owner, group) < 0) return 3;
if (chmod(fichero, 00700) < 0) return 4;
continue;
}
mascaraset = atributos.st_mode & 06000; //Salvamos los bits de SETUID y SETGID
if (chown(fichero, owner, group) < 0) return 5;
if (S_ISREG(atributos.st_mode)) { //Caso de fichero de usuario
if (atributos.st_mode & 0100) //Bit de ejecucin del dueo
if (chmod(fichero, atributos.st_mode | mascaraset) < 0) return 6;
else
if (chmod(fichero, 00600) < 0) return 7;
continue;
}
if (chmod(fichero, atributos.st_mode | mascaraset) < 0) return 8;
}
closedir(dirp);
return 0;
}

d) Lo primero que hay que tener en cuenta es que si no se elimina el caso .. de la funcin, al hacerla recursiva
cambiara todo el rbol directorio y, adems, entrara en un bucle infinito. Si no se elimina el caso . el programa
entrara tambin en un bucle infinito.
Hay que tocar el if del caso directorio cambindolo, por ejemplo, por el siguiente cdigo:
if (S_ISDIR(atributos.st_mode)) { //Caso de directorio
if (chown(fichero, owner, group) < 0) return 2;
if (chmod(fichero, 00700) < 0) return 3;
//Evitar bucle infinito
if (strcmp((dp->d_name, ".") != 0) CambiaSubDir(fichero, owner, group);
continue;
}
Con la solucin propuesta los directorios, menos el original, se trataran dos veces, una al tratar su nombre y otra
al tratar el "." .
Tambin hay que destacar que ha incluido en la llamada la ruta completa al subdirectorio.
145

Problema 2.34 (abril 2014)

En un aula se encuentran en ejecucin el proceso p_profe y los procesos p_alumno_i, donde i puede tomar valores
de 1 a n. Durante una clase de teora de 60 minutos, el proceso p_profe puede invocar los ficheros ejecutables
fe_transpas y fe_contestar_p. La eleccin del fichero ejecutable se realiza con la lectura por la entrada estndar
de un valor entero.
Transcurridos los 60 minutos, el proceso p_profe debe recibir un aviso de que su actividad debe terminar y conclu-
ye su ejecucin.
Los procesos p_alumno_i ejecutan los siguientes ficheros ejecutables: fe_atender, fe_preguntar_a, fe_distraido.
En todos los casos, la ejecucin de estos cdigos implica la creacin de un proceso que al finalizar devuelve un es-
tado de terminacin al correspondiente proceso padre (p_profe o p_alumno_i). Antes de crear un nuevo proceso
hijo, los procesos p_alumno_i matan al hijo que estuviera previamente en ejecucin.
En el siguiente diagrama se ilustran distintas situaciones por las que pueden pasar los procesos, desde el comienzo
de la clase (izquierda).

a) Enumerar los servicios del sistema, funciones de biblioteca y sentencias de lenguaje C que permiten termi-
nar con la ejecucin:
De un proceso
De un thread
b) Se podra implementar con threads la ejecucin de fe_transpas y fe_contestar? Justifique la respuesta. In-
dique el mximo nmero de procesos o threads que podran coexistir con las restricciones dadas en el
enunciado.
c) Justifique cul es la solucin o combinacin de soluciones ms adecuada (ignorar, armar, bloquear) para
que el proceso p_profe atienda las seales SIGUSR1 que generan los procesos asociados a la ejecucin de
fe_preguntar_a. La seal SIGUSR1 la genera fe_preguntar_a cuando se quiere realizar una pregunta al
proceso p_profe.

d) Especifique el cdigo necesario para implementar el desarrollo de las actividades del profesor durante la
clase de tal forma que el proceso p_profe se ejecuta en el hilo principal del cdigo, los ficheros ejecutables
fe_transpas y fe_contestar_p en un hijo y se debe controlar la duracin de la clase. No hay que implemen-
tar la funcin de tratamiento de la seal SIGUSR1.
146 Problemas de sistemas operativos
e) Suponiendo que se dispone de los fuentes de fe_atender, fe_preguntar_a y fe_distraido de los ficheros eje-
cutables que invocan los procesos p_alumno_i, especifique el cdigo necesario para implementar con
threads el desarrollo de las actividades de los alumnos. La actividad de los procesos p_alumno_i finaliza
tras la recepcin de una seal SIGKILL enviada por el proceso p_profe al grupo de procesos p_alumno_i.

Solucin
a) De un proceso: kill, return (desde la funcin main), exit. De un thread: pthread_kill, pthread_exit, return.
b) Al tener que invocarse la ejecucin de cualquiera de los 2 programas con el servicio exec, si se hiciera con una
solucin basada en threads, no volvera a ejecutarse el cdigo del proceso p_profe una vez cargada en memoria la
imagen de memoria del nuevo ejecutable que entra en ejecucin, por lo que no es posible utilizar threads. El nmero
mximo de procesos ser: 2 (correspondientes al p_profe y al hijo creado) + 2 x n (correspondientes a los n alum -
nos presentes y por cada alumno existirn tanto el proceso p_alumno_i como el hijo creado).
c) El proceso p_profe no puede ignorar la seal enviada por estos procesos. El armado de la seal implica la aten-
cin inmediata con su recepcin, por lo que se podra interrumpir constantemente el hilo del discurso desarrollado
por el proceso p_profe. La solucin ms adecuada est basada en armar la seal y definir una mscara que incluya el
bloqueo de la seal SIGUSR1 de tal forma que la entrega de esta seal quede pendiente hasta que el proceso p_profe
la pueda atender desactivando en la mscara el bit de esta seal. Una vez atendida, habra que reactivar la mscara
con el bit de la seal SIGUSR1 activo.
d) pid_t pid=0;
void tratar_alarma(void)
{
kill(pid,SIGKILL);
fprintf(stderr,Fin de la clase\n);
exit(0);
}
int main(void){
int actividad;

sigaction struct act;


act.sa_handler=&tratar_alarma;
act.sa_flags=0;
sigaction(SIGALRM, &act, NULL);
alarm(3600);
while (1) {
read(0,&actividad,sizeof(int));
if (pid != 0)
kill(pid,SIGKILL);
pid=fork;
if (pid ==-1)
{perror(fork); exit(1);}
else if (pid==0)
{switch (actividad)
{
case 0:
execlp(fe_transpas,fe_transpas,NULL);
perror(exec fe_transpas); exit(1);
break;
case 1:
execlp(fe_contestar_p,fe_contestar_p,NULL);
perror(exec fe_contestar_p); exit(1);
break;
default:
fprintf(stderr,Actividad %d incorrecta\n,actividad);
147
exit (2);
break;
}
}
else waitpid(pid,&estado,0);
}
return 0;
}

e) void * func_atender (void *p){


/* Cdigo fuente del fichero ejecutable fe_atender */
pthread_exit(0);}
/* Habra que hacer lo mismo para la especificacin del cdigo de los ficheros
ejecutables fe_preguntar_a, fe_distraido que invocan los threads creados por
p_alumno_i */
int main (void){
int actividad;
pthread_attr_t attr;
pthread_t thid;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
while (1) {
read(0,&actividad,sizeof(int);
switch(actividad) { // Suponemos que los threads se crean correctamente
case 0: pthread_create(&thid, &attr, &func_atender, NULL); break;
case 1: pthread_create(&thid, &attr, &func_preguntar, NULL); break;

case 2: pthread_create(&thid, &attr, &func_distraido, NULL); break;


default: fprintf(stderr,Actividad %d incorrecta\n,actividad); exit
(1);
break;
}
pthread_join(thid,NULL);
}
pthread_attr_destroy (&attr);
return 0;
}
3
GESTIN DE MEMORIA

Problema 3.1
Sea un sistema monousuario con memoria virtual, que planteamos de muy pequeas dimensiones para no compli-
car el problema, que dispone de:
Una memoria principal de 8 pginas de 4 KiB
Un SO cuya parte residente ocupa 4 KiB
Una zona de swap con capacidad para 10 pginas.
Una poltica de no preasignacin de swap.
Un disco con dos particiones una para ficheros y otra para swap.
El SO operativo arranca un nico proceso shell y el usuario solicita la ejecucin de un proceso compuesto por:
Segmento de texto de 3,4 KiB
Segmento de pila, con un tamao inicial de 200 B
Segmento de datos de slo lectura de 14 KiB
Segmento de datos de escritura y lectura con un tamao inicial de 6 KiB
Se pide:
a) Suponer que el sistema de ficheros es de tipo UNIX y que la capacidad de datos til que se consigue es de
160 KiB. Establecer la estructura para este volumen o particin, proponiendo justificadamente el tamao de
cada parte.
b) Establecer la tabla de pginas del proceso de usuario. Justificar su estructura y proponer su contenido com -
pleto.
c) Indicar el contenido de la memoria principal y del swap al iniciar la ejecucin del proceso.
d) Suponer que, en un instante determinado, el proceso tiene asignado una sola pgina para la pila, que la p-
gina est llena (la pila ocupa toda la pgina) y que se ejecuta una instruccin de PUSH. Exponer las accio -
nes que se producen.

Solucin PM
a) El dispositivo est dividido en dos particiones, una para ficheros y otra para swap. Nos dicen que la particin de
ficheros tiene una capacidad til (o neta) de 160 KiB. Suponiendo bloques de 1 KiB y sectores de 512 B la particin
de ficheros tiene 160 bloques, con estos datos podemos hacer una buena estimacin de los tamaos de los diferentes
componentes de la particin (vase figura 3.1).
Boot: Este tamao puede ser de un sector (p.e. 512 B)

148
150 Problemas de sistemas operativos
DISCO

Super Mapas
Boot Nodos-1 Datos y directorios 160 KB Swap 40 KB
bloque de bits

Particin de ficheros Particin de swap


Figura 3.1

Superbloque: Este tamao puede ser de un sector (p.e. 512 B)


Nodos-i: El mximo seran 160 nodos-i, suponiendo que cada fichero ocupa un solo bloque. Sera ms
lgico tomar un valor menor p.e. 80. Cada nodo-i tiene los 13 punteros ms propietario, grupo del pro -
pietario, tamao, instante de creacin, instante de ltimo acceso, instante de ltima modificacin e infor-
macin de control, es decir un tamao de unas 20 palabras. Por tanto, 80 nodos-i ocupan 80204 = 6400
B.
Mapas de bits: Un mapa de bits para los bloques: 160 b, ms un mapa de bits para los nodos-i: 80 b.
Hace falta, por tanto, 30 B.
b) Como sabemos se debe emplear una tabla multinivel, para evitar los huecos. Tendremos un primer nivel con cua-
tro elementos, uno por segmento, y una tabla de segundo nivel por cada segmento. Las capacidades son las siguien -
tes:
Segmento de texto: 1 pgina
Segmento de pila: 1 pgina
Segmento de datos R: 4 pginas
Segmento de datos RW: 2 pginas
Como suele ser normal (vase figura 3.2), hemos incluido bits de proteccin en los dos niveles, aunque en el
ejemplo propuesto bastara con incluirlos en uno solo de ellos. Ntese que se han incluido bits de uso, para indicar
los elementos de cada tabla que estn en uso.

R W X Marco/swap
1 0 1 0 1 1
R W X Uso Puntero 0
Segmento de texto 0 0 1 1 16
0
Segmento de datos R 1 0 0 1 24
Segmento de datos RW 1 1 0 1 56 1 0 1 0 0 2
Pila 1 1 0 1 112 1 0 1 0 0 3
1 0 1 0 0 4
0
1 0 1 0 0 5
0
0
0

0 1 1 0 6
0 1 1 0 7
0

0 1 1 0 8
0

0
Figura 3.2

c) Al iniciar la ejecucin del proceso no tenemos ninguna pgina en memoria principal. De hecho, la tabla de pgi-
nas propuesta en la seccin anterior refleja esta situacin. Dependiendo del SO, la pila inicial podra estar en un
marco de pgina, puesto que es all donde la crea el SO.
Al intentar ejecutar el proceso dar un fallo de pgina que har traer a un marco de memoria principal la pgina
de texto. Seguidamente, se irn produciendo otros fallos de pgina que harn que otras pginas deban pasar a mar-
cos.
d) El proceso intenta ejecutar la instruccin de mquina PUSH. Segn crezca la pila se incrementa o decrementa el
SP, lo que genera una direccin virtual que corresponde a una pgina inexistente. La MMU recibe esta direccin
para traducirla y, al acceder a la tabla de pginas, detecta que se sale de rango. En efecto, la pila tiene asignada una
sola pgina, por lo que el tamao de su tabla es 1. Sin embargo, la direccin presentada a la MMU se refiere a la si-
guiente pgina, inexistente. Por tanto, la MMU genera un trap de violacin de memoria.
151
El SO trata la interrupcin y, como se trata de un trap de memoria, pasa a ejecutar el gestor de memoria. ste
analiza la situacin y detecta que se trata de un desbordamiento de pila, por lo que tiene dos posibilidades:
Si existe espacio disponible se asigna ms memoria al proceso. Dado que se funciona sin preasignacin
de swap el soporte fsico que se asigna es un marco de pgina. En caso de no tener marcos libres habra
de liberar uno previamente. En la figura 3.3 se ha asignado el marco 5.
Si no existe espacio disponible no puede continuar la ejecucin del proceso, por lo que el SO le enva
una seal, para que muera.

R W X Marco/swap
1 1 1 0 1 8
R W X Uso Puntero 0
Segmento de texto 0 0 1 1 16
0
Segmento de datos R 1 0 0 1 24
Segmento de datos RW 1 1 0 1 56 1 0 1 0 0 2
Pila 1 1 0 1 112 1 1 1 0 0 1
1 1 1 0 0 4
0
1 0 1 0 0 5
0
0
0

0 1 1 0 6
1 1 1 0 3
0

1 1 1 0 2
1 1 1 0 5
0

0
Figura 3.3

Problema 3.2
Sea un sistema con las siguientes caractersticas:
Memoria virtual con direcciones de 32 bits, en el que cada usuario tiene su propio espacio virtual que va des-
de la direccin 00000000 hasta la 7FFFFFFF.
Se utilizan pginas de 1 KiB y se realiza la asignacin de espacio de disco en bloques de 4 KiB.
A los discos se les ha dado formato con sectores de 512 B.
Se desea ejecutar la aplicacin definida seguidamente:
Fichero datos.h
char vector [23000];
int a = 5; int b = 15; int c = 23; int d = 7; int e ;
Fichero programa.c
#include <datos.h>
int main (void)
{
char x; char *w; char *m;
int v = 10; int fd;
e = v;
w = malloc (27000);
A
f (e);
fd = open ("dat.txt", O_RDONLY);
m = mmap(0, 4000, PROT_READ, MAP_SHARED, fd, 0);
B
.
.
exit (0);
}

void f (int a)
152 Problemas de sistemas operativos
{
printf ("%d\n", a);
return;
}
Suponiendo que la cabecera del ejecutable ocupa 1 KiB y que el cdigo de la mencionada aplicacin ocupa 211
KiB, determinar el tamao real y el espacio ocupado en disco por el fichero ejecutable.
Para los tres casos siguientes: al iniciar la ejecucin del mencionado programa, al llegar al punto A y al llegar
al punto B, definir las regiones o segmentos de la imagen de memoria del proceso especificando para cada una de
ellas:
Direccin de comienzo en hexadecimal y tamao en KiB.
Soporte fsico y, en su caso, tcnica de optimizacin empleada.
Contenido de la regin o segmento.

Solucin
1 Determinacin del tamao real y en disco del fichero ejecutable.
Supondremos 4 bytes para los enteros (depende del sistema, podran ser 2 bytes). La cabecera ocupa 1 KiB y el
cdigo 211 KiB, como slo se inicializan 4 enteros en el fichero de cabecera (a,b,c y d), estos ocuparan 16 bytes.
As el tamao real ser 212 KiB + 16 B, igual a 217.104 bytes.
El espacio se asigna en agrupaciones de 4 KiB, luego, para cubrir los 212 KiB + 16 B, se necesitan 54 agrupa-
ciones = 544 KiB. Es de notar que el ejecutable tambin podra incluir una tabla de smbolos cuyo tamao descono-
cemos.
2-a Regiones o segmentos de la imagen de memoria del proceso al comienzo, en A y en B.
Al comienzo:
00000000-00034BFF Cdigo, 211 KiB
00034C00-00034C0F DVI1, 16 B 4 B por 4 int definidos e iniciados en datos.h.
00034C10-0003A5EB DSVI2, 23.004 B 23.000 B char[ ] + 4 B por 1 int en datos.h
0003A5EC-xxxxxxxx Heap (Espacio libre) El Heap crece hacia abajo

PILA:Var. Locales 17 B Bloque 1 B char + 4 B por 2 char* + 4 B por 2 int


PILA: puntero a marco de 32 bits = 4 bytes
anterior 4 B Activacin
de
PILA: Dir. Retorno 4 B 32 bits = 4 bytes
main()
PILA: argc 4B 32 bits = 4 bytes
yyyyyyyy-7FFFFFFF PILA:Var. Entorno La pila crece hacia arriba
1
Datos con Valor Inicial.
2
Datos Sin Valor Inicial.
En el punto A: Se incluyen en el heap los 27.000 B del malloc. Si el heap no tena ese tamao habr de crecer.

0003A5EC-00040F63 27.000 bytes Reservado por el malloc(...)


00040F64-xxxxxxxx Espacio libre

En el punto B: Se aade una regin A al ejecutar el sistema operativo el mmap. La direccin de comienzo de la
regin del mmap depender del sistema operativo. Supondremos la 01000000. Adems crece la pila con el bloque
de activacin de la funcin f, compuesto por el argumento a, direccin de retorno de f y puntero a marco anterior. En
total 3 enteros de 4B.
153

01000000-01000F9F 4.000 bytes Reservado por el mmap(...)


01000FA0-zzzzzzzz Espacio libre

yyyyyyyy-7FFFFFFF Crece la pila con la llamada a la funcin f


2-b Soporte fsico al comienzo, en A y en B.
Comienzo: En A: En B:
Cdigo Paginar sobre fichero programa Ejecuta Algn marco Ejecuta Algn marco
DVI Paginar sobre fichero programa
DSVI Rellenar 0 e = 10 1 marco
Heap Rellenar 0 malloc() Rellenar 0
Regin A No existe No existe mmap() Fichero datos.txt
Pila Rellenar 0, marco con pila inicial. Igual que al Puede necesitar
comienzo un marco adicio-
nal.
2-c Contenido de la regin o segmento comienzo, en A y en B.
Comienzo: En A: En B:
Cdigo Fichero programa Fichero programa Fichero programa
DVI Fichero programa Fichero programa Fichero programa
DSVI Rellenar 0 e = 10 e = 10
Heap Rellenar 0 Rellenar 0
Regin A No existe Fichero datos.txt
Pila Entorno activacin main() w = dir malloc Se aade el marco de acti-
vacin de f

Problema 3.3
Un sistema tiene un disco de 128 KiB organizado en bloques e intercalado simple y una memoria principal de 44
KiB. La gestin de la memoria virtual se realiza con pginas y con tablas de pginas de dos niveles. Las direccio -
nes virtuales son de 24 bits, de los cuales 6 bits sirven para acceder a la tabla de pginas de primer nivel y 6bits
para acceder a la tabla de pginas de segundo nivel.
Adems, en nuestro sistema existe una biblioteca dinmica lib.so que contiene la funcin func.Se tiene, adems,
el siguiente fragmento de programa.
char x[1024];
char y[1024];
...

int main (void)


{
int i, pid;

/* A Situacin inicial */
for (i=0; i<1024; i++)
x[i]=y[i];
func("datos");
/* B Despus del for y de usar la biblioteca dinmica */
...
}
a) Sabiendo que una vez compilado el programa el cdigo ocupa 5K, los datos con valor inicial (d.v.i.) ocupan
6K y que los datos sin valor inicial (d.s.v.i.) tienen un tamao de 2K, se pide representar grficamente el fi-
chero ejecutable resultante, suponiendo que utilizamos el formato ELF y que el tamao de la cabecera del
ejecutable es de 1 KiB.
b) Representar grficamente el mapa de memoria del proceso en la situacin inicial A.
154 Problemas de sistemas operativos
c) Con el sistema de gestin de memoria propuesto inicialmente, indicar qu tipo de fragmentacin se est ge-
nerando.
d) Sabiendo que se tiene un sistema sin preasignacin de swap, se pide representar las tablas de pginas tanto
de primer como de segundo nivel en el instante A. Para cada Entrada de la Tabla de Pginas (ETP) se ten -
dr en cuenta la siguiente informacin: bit de validez, bit presente/ausente, bit modificado, bit referenciado,
bit COW, bit Rellenar a Ceros (RC), bit de Rellenar de Fichero (RF), proteccin y nmero de marco o bloque
de disco.
e) Responder brevemente a las siguientes preguntas: qu ocurre si una misma pgina es expulsada varias ve-
ces al rea de swap?, quin y cundo escribe el bit de modificado y quin y cundo lo inicializa?
f) Indicar qu cambios se realizaran en el mapa de memoria del proceso y en la tabla de pginas al llegar el
proceso al instante B. Suponer que la llamada a funcin func necesita solamente la primera pgina de la re -
gin de texto y la primera pgina de la regin de datos de la biblioteca dinmica.
g) Explicar brevemente qu ocurre si el proceso crea un proceso hijo y ste escribe en la zona de datos de la
biblioteca dinmica.
Estado inicial del disco
0 1 2 3

4 5 6 7
lib.so texto lib.so texto lib.so datos lib.so datos
8 9 10 11
lib.so datos
12 13 14 15

16 17 18 19
SWAP SWAP SWAP SWAP
20 21 22 23
SWAP SWAP SWAP SWAP
24 25 26 27
SWAP SWAP SWAP SWAP
28 29 30 31
SWAP SWAP SWAP SWAP
Siglas utilizadas:
RF: Rellenar de fichero
RC: Rellenar a ceros
COW: Copy on write
X: Ejecucin
R: Lectura
W: Escritura

Solucin
a) El fichero ejecutable est formado por la cabecera, y una serie de secciones que son descritas en la cabecera. Por
cada seccin viene su direccin inicial y su desplazamiento, excepto para la seccin de datos sin valor inicial, de la
que slo se especifica su tamao, vase figura 3.4.
Nmero Mgico (Hex7F 'E' 'L' 'F') Desplazamiento Tamao
Contador Programa inicial (PC) Cdigo 1K 5K
Tabla de secciones Datos v.i. 6K 6K
1K Datos s.v.i. - 2K
Cdigo Tabla simb. 12K -

6K
Datos con valor inicial

12K
Tabla de smbolos
lib.so: biblioteca dinmica

Figura 3.4
155
b) El mapa de memoria contiene: cdigo, datos con valor inicial, datos sin valor inicial y pila. Dado que se dedican
12 bits de la direccin para seleccionar la pginas, quedan otros 12 para la direcin dentro de la pgina, lo que signi -
fica que la pgina es de 4 KiB. Consideraremos que las tres primeras regiones estn en la zona alta y la ltima en la
zona ms baja. Adems, se ha supuesto que tanto los datos con valor inicial como los sin valor inicial estn en su
propia regin. El espacio total de direccionamiento es de 2^24 = 16 MiB. Vase figura 3.5.
0
Cdigo
8K-1
8K
Datos v.i.
16K-1
16K
Datos s.v.i.
20K-1

16MB - 4K
Pila
16MB-1
2^24 = 16MB
Figura 3.5

c) Debido a que tenemos un esquema de paginacin, se produce fragmentacin interna. Puede desperdiciarse parte
de ltimo marco asignado cada regin del proceso.
d) Vase figura 3.6.
Referenciada

1er nivel 2do nivel Byte


Moficada

Presente

6 bits 6 bits 12 bits


Valida

COW

R.C.
R.F.

Vlida Puntero PROT Nmarco/Bloque


RIED 0 1 0 1 1 0 0 1 0 0 RX Marco Mem. Texto
1 0- 1 1 0 0 0 0 0 1 RX Bloque Fich. Texto
2 0- 2 1 0 0 0 0 0 1 RW Bloque Fich. d.v.i
3 0- 3 1 0 0 0 0 0 1 RW Bloque Fich. d.v.i
... - 4 1 0 0 0 0 1 0 RW - d.s.v.i
62 0- 5 0 - - - - - - - -
63 1 6 0 - - - - - - - -
T.P. 1er nivel ..
63 0 - - - - - - - -

...

0 0 - - - - - - - -
1 0 - - - - - - - -
2 0 - - - - - - - -
..
6,2 0 - - - - - - - -
63 1 1 1 0 1 0 0 RW Marco Mem. Pila
T.P. 2do nivel (1)
Figura 3.6

e) Asignacin de espacio de swap: la primera vez se le reserva espacio en el swap, las siguientes veces usar el espa-
cio que se le reserv la primera vez. El bit de modificado lo inicializa el sistema operativo cada vez que lleva una
pgina de memoria secundaria a memoria principal. Es escrito por la MMU cuando hace un acceso de escritura a la
pgina. Sirve para saber si la pgina est sucia (con cambios), a la hora de expulsarla a Memoria Secundaria.
f) Vase figura 3.7.
156 Problemas de sistemas operativos

Referenciada
1er nivel 2do nivel Byte

Moficada

Presente
6 bits 6 bits 12 bits

Valida

COW

R.C.
R.F.
Vlida Puntero PROT Nmarco/Bloque
RIED 0 1 0 1 1 0 0 1 0 0 RX Marco Mem Texto
1 0- 1 1 0 0 0 0 0 1 RX Bloque Fich. Texto
2 0- 2 1 0 0 0 0 0 0 RW Bloque Fich. d.v.i
3 0- 3 1 0 0 0 0 0 0 RW Bloque Fich. d.v.i
... - 4 1 1 1 0 1 0 0 RW Marco Mem d.s.v.i
62 0- 5 0 - - - - - - - -
63 1 6 0 - - - - - - - -
T.P. 1er nivel ..
63 0 - - - - - - - -

...

0 1 1 0 0 1 0 0 RX Marco Mem Cod Bib


1 1 0 0 0 0 0 1 RX Bloque Fich Cod Bib
2 1 1 1 0 1 0 0 RW Marco Mem Datos Bib
3 1 0 0 0 0 0 1 RW Bloque Fich Datos Bib
4 1 0 0 0 0 0 1 RW Bloque Fich Datos Bib

..
62 0 - - - - - - - -
63 1 1 1 0 1 0 0 RW Marco Mem Pila
T.P. 2do nivel (1)
Figura 3.7

Los cambios son:


Accedo y escribo en la pila por la variable i
Accedo y escribo en los d.s.v.i. por los vectores x e y
Creo dos nuevas regiones para la biblioteca dinmica
Regin de datos: Se proyectan los datos de la Bibl dinmica de forma privada y de lectura/escritura
Regin de cdigo: Se proyecta el cdigo de la Bibl dinmica de forma compartida y de lectura/ejecucin
Dentro de la Bibl. Dinmica se accede a la primera pgina del cdigo y a la primera de la regin de datos.
Luego en el mapa de memoria del proceso se crean dos nuevas regiones para la Bibl. Dinmica. Estas regiones
se crean a partir de la direccin 10 MiB del mapa de memoria y tienen un tamao de 2 pginas (8 KiB) para la zona
de cdigo y 3 pginas (12 KiB) para la zona de datos.
Los cambios estn en negrita. Se han creado las regiones de la biblioteca dinmica algo por encima de la pila.
g) Inicialmente la regin se comparte como COW y el hijo, que escribe primero, crea una copia privada cuando va a
modificar los datos.

Problema 3.4 (junio 2003)

Sea el cdigo siguiente, con montaje dinmico, que ejecuta en una mquina con memoria virtual y pginas de 4
KiB. La funcin cos est en la biblioteca libm y printf en libc. Los tamaos de estas bibliotecas son los siguien-
tes:
Biblioteca Texto Datos con valor inicial Datos sin valor inicial
libm 191 KiB 8 KiB 3 KiB
libc 147 KiB 3 KiB 2 KiB
mi_cos.c:
#include <stdio.h>
#include <math.h>
double coseno(double v) {
return cos(v);
}
int main (void) {
double a, b = 2.223;
a = coseno(b);
printf("coseno de %f:%f\n",b,a);
157
return 0;
}
a) Establecer la imagen de memoria en el instante en el que el proceso mi_cos est ejecutando la funcin cos.
Suponer unos tamaos razonables para el texto y las variables de entorno de mi_cos.
b) Repetir para el instante en el que el proceso est ejecutando la funcin printf.
c) Una mquina con palabras de 64 bits y con alineacin de datos ejecuta el fragmento de cdigo siguiente:
int cnt;
struct {
char caracter;
double flotante;
} * estructura;

estructura = malloc(720);
for (cnt = 0; cnt < MAXIMO_POSIBLE; cnt++) {
estructura[cnt].flotante = cnt * 100.0;
}
Suponiendo que el double ocupa 64 bits, se pide determinar el mximo valor que se le podra asignar razo-
nablemente a MAXIMO_POSIBLE. Explicar el resultado obtenido.
d) Sean dos programas A y B que contienen los fragmentos de cdigos listados en la tabla 3.1.
Fragmento de programa A Fragmento de programa B
int fd; int fd;
struct { struct {
void * direccion; void * direccion;
int array[100]; int array[100];
} * area; } * area;
/* Se supone que "BUFFER" existe. */ /* Se supone que "BUFFER" existe. */
fd = open("BUFFER", O_RDWR); fd = open("BUFFER", O_RDWR);
area = mmap(0, sizeof(*area), area = mmap(0, sizeof(*area),
PROT_READ|PROT_WRITE, MAP_SHARED, PROT_READ|PROT_WRITE, MAP_SHARED,
fd,0); fd,0);
area->direccion = area; area = area->direccion;
area->array[0] = 1; area->array[2] = 3;
area->array[1] = 2; area->array[3] = 4;
munmap(area, sizeof(*area)); munmap(area, sizeof(*area));
close(fd); close(fd);
Tabla 3.1
Se supondr que el programa A requiere mucha ms memoria que el programa B para su ejecucin y que ejecu -
ta esta seccin de cdigo antes que el programa B ejecute la suya. Se pide determinar el contenido del fichero BU-
FFER al final de la ejecucin de ambos programas. Explicar el resultado obtenido.

Solucin
a) y b) Las soluciones a las preguntas a y b se encuentran en la figura 3.8, en la que son de destacar las siguientes
consideraciones:
El programa mi_cos no tiene ni datos con valor inicial ni datos sin valor inicial, por lo que su imagen no tendr
estas subregiones. Adems, no genera datos dinmicamente, por lo que no hace uso del heap. Sin embargo, el SO no
sabe que el programa no har uso del heap, por lo que, como siempre, dejar espacio para que se pueda crear el heap
y para que ste pueda crecer.
Dado que el montaje es dinmico, las bibliotecas no se encuentran en el cdigo del programa. Sabemos que las
bibliotecas dinmicas se pueden cargar segn tres procedimientos, de los cuales descartamos directamente el mtodo
de montaje explcito, puesto que el cdigo no incluye ninguna clusula para ello. Nos queda, por tanto, el montaje
en tiempo de carga en memoria y el montaje al invocar el procedimiento. Como sabemos, el montaje ms eficiente y
utilizado es al invocar el procedimiento, por lo que es el que hemos considerado en esta solucin.
158 Problemas de sistemas operativos
4KB compartida Texto (p.e.500B) 4KB compartida Texto (p.e.500B)
tamao fijo R-X tamao fijo R-X

?KB privada ?KB privada


Heap Heap
tamao variable RW tamao variable RW

Texto libm Texto libm


192KB compartida (191KB) 192KB compartida (191KB)
tamao fijo R-X tamao fijo R-X

12KB privada Datos CVI libm (8KB) 12KB privada Datos CVI libm (8KB)
tamao fijo RW- Datos SVI libm (3KB) tamao fijo RW- Datos SVI libm (3KB)

148KB compartida Texto libc


tamao fijo R-X (147KB)

Datos CVI libc (3KB)


8KB privada
Datos SVI libc (2KB)
tamao fijo RW-

Var. locales de cos (xB)


V. locales de printf (yB)
Puntero marco anterior (4B) Bloque
Ret de cos (4B) activacin Puntero marco anterior (4B)
cos Bloque
v (8B) Ret de print f (4B) activacin
Puntero marco anterior (4B) &coseno de ..... (4B) print f
Bloque
Pila 4KB privada Ret de coseno (4B) activacin Pila 4KB privada b (8B)
tamao variable RW- v (8B) coseno tamao variable RW- a (8B)
b (8B) b (8B)
a (8B) a (8B) Bloque
Bloque
Puntero marco anterior (4B) activacin Puntero marco anterior (4B) activacin
Ret de main (4B) main Ret de main (4B) main
Parmetros main Parmetros main
Entorno (p.e. 300B) Entorno (p.e. 300B)

Mapa de memoria caso a) Mapa de memoria caso b)


Figura 3.8

Por ello, en la situacin a) la imagen de memoria solamente incluye la biblioteca libm, que es la que contiene la fun-
cin cos. Sin embargo, en la situacin b) encontramos en la imagen de memoria ambas bibliotecas, puesto que el SO
no sabe que el programa ya no utilizar la biblioteca libm, por lo que no la elimina de la imagen de memoria del
proceso.
Ntese tambin que todas las regiones de memoria contienen un nmero entero de pginas, por lo que quedan
trozos de las mismas sin ocupar. Adems, es de destacar que las zonas de datos con y sin valor inicial se pueden jun-
tar en una nica regin, puesto que son de tamao fijo. As, la figura incluye una regin de datos para cada una de
las bibliotecas.
La figura no inlcuye, sin embargo, una regin de datos del proceso, pues su cdigo no contiene datos globales.
Dicha regin podra tambin albergar el heap. Se tratara de una regin privada con derechos RW- que podra estar
pegada a la regin de texto del proceso.
Las bibliotecas no tienen pila ni heap propio, utilizan las del proceso.
El SO podra haber pegado la regin de texto de la biblioteca libc a la regin de datos de la biblioteca libm, pues -
to que es de tamao fijo. Por el contrario, siempre ha de dejar espacio para el crecimiento de la pila y del heap.
c) El char ocupa un byte y el double ocupa 8 bytes. Sin embargo, nos dicen que la mquina hace alineacin de datos,
por lo que cada estructura ocupa dos palabras, como muestra la figura 3.9, al no poderse almacenar el double en la
misma palabra que el char.
char
double

Figura 3.9
159
Por lo tanto, para que no se desborde el espacio de 720 bytes reservado para las estructuras solamente pode-
mos almacenar 720/16 = 45 estructuras, por lo que MAXIMO_POSIBLE = 45.
d) El programa A proyecta el fichero en modo shared, por lo que se reflejar en el mismo todo lo que se escriba en la
regin de memoria. En concreto se escriben tres enteros. El primero corresponde a la direccin virtual en la que co-
mienza la regin de memoria area, que llamaremos dir_area. El segundo y tercero reciben un 0001 y un 0002 res-
pectivamente. El resto del fichero no queda modificado, por lo que mantiene los valores anteriores.
El programa B proyecta el mismo fichero tambin en modo shared, por lo que ste reflejar los cambios que
haga el programa en la regin de memoria.
Nos dicen que el programa A require mucha ms memoria que el B, lo que lleva a que el SO deber crear unas
imgenes de memoria muy distintas, por lo que la direccin de la regin area ser muy distinta en el programa A
que en el programa B.
Si nos fijamos en el programa B, podemos observar que toma la primera palabra de la regin (que contiene el va -
lor dir_area dejado en el fichero por el programa A) e intenta escribir en las direcciones dir_area + 3 y dir_area + 4.
Estas direcciones eran vlidas para el programa A, pero no tienen porqu serlo para el B. Es ms, lo ms probable es
que estas direcciones no correspondan a ninguna regin de dicho programa, por lo que el resultado ms probable
ser un error de violacin de memoria.
Podemos afirmar que el contenido final del fichero ser, seguramente, el de la figura 3.10.
dir_area 0001 0002 ? ? ? ?

Figura 3.10

Problema 3.5 (septiembre 2003)

Sea el cdigo adjunto que se monta en modo dinmico para ser ejecutado en una mquina con memoria virtual y
pginas de 2 KiB. Considere que el cdigo del programa ocupa 3.573 B.
01 #include <stdio.h> /* fichero ejemplo.c */
02 #include <stdlib.h>
03
04 int a;
05 int b = 50;
06
07 void funcion (int c)
08 {
09 int d;
10 static int e = 2;
11 d = e
12 c = d + 5;
13 printf ("Esto lo imprime la funcion funcion()\n");
14 }
15
16 int main (void)
17 {
18 char *f;
19 f = (char *) malloc (512);
20 funcion (b);
21 free (f);
22 exit (0);
23 }
a) Haga un grfico con las diferentes secciones del fichero ejecutable correspondiente, especificando en qu
seccin del mismo se almacena cada una de las variables del programa y por qu se almacena en dicha sec-
cin.
b) Para cada uno de los puntos de ejecucin correspondientes a la finalizacin de las clusulas 16, 12 y 21
haga un grfico con las distintas regiones de memoria del proceso que ejecuta el programa anterior, indi-
cando:
160 Problemas de sistemas operativos
Direccin de comienzo y fin de cada regin.
Caractersticas de la regin.
Contenido de cada regin, indicando expresamente las variables incluidas en cada una de ellas, as
como su valor.
Como no se indican las necesidades de memoria de la librera dinmica libc utilizada por el programa, especifi-
que los tamaos necesarios mediante nombres simblicos.

Solucin
a) El fichero ejecutable constar de las siguientes secciones:
Cabecera, donde se almacena el nmero mgico y el valor inicial de los registros del procesador en el
momento de la carga.
Cdigo, donde se almacena el cdigo mquina del programa ya enlazado con las libreras estticas nece-
sarias. El cdigo se dice en el enunciado que ocupa 3.573Bytes.
Datos con valor inicial, en este caso sera b=50 (lnea 05) y static e=2 (lnea 10). Se supone un siste-
ma en que cada entero ocupa 4bytes, por tanto esta regin ocupara 8 bytes. El resto de variables del pro -
grama se almacenarn en el cdigo.
Tablas para montaje dinmico y otra informacin necesaria para la formacin del ejecutable o para depu-
racin.
b) Nos piden la imagen de memoria al terminar la ejecucin de las lneas 16, 12 y 21. Suponemos tamao de me-
moria virtual de 32 bits y el tamao de los enteros de 4 bytes. El enunciado dice que el tamao de pgina es de 2
KiB.
Lnea 16: En esta lnea empieza a ejecutarse el programa y, por tanto, la imagen de memoria del proceso tendr
slo el bloque de activacin de main(). Vase tabla 3.2.
Lnea 12: En esta lnea se ha ejecutado buena parte de la funcin main() incluido la llamada a la funcin
malloc(), por tanto se habr enlazado la librera dinmica correspondiente a funciones estndar. En este momento se
encuentra en mitad de una llamada a la funcin funcion(), por tanto se habr escrito en la pila el correspondiente
bloque de activacin. Vase tabla 3.3.
Lnea 21: En esta lnea ser igual que lo anterior pero habr desaparecido de la pila el bloque de activacin de la
funcin funcion() y tambin se habr liberado del Heap los 512 bytes solicitados por malloc(). Vase tabla 3.4.
Direccin Tamao Caractersticas Contenido
0000.0000- 2 KiB RX El cdigo de 3.573 B utiliza dos pginas con permisos de lectura y
0000.07FF (Compartido y ejecucin. Queda espacio libre hasta 4 KiB
soporte en disco)
0000.0800-0000.0- 2KiB
FFF
0000.1000- 2 KiB RW Datos con valor inicicial: b=50 y static e=2, solo 8 bytes
0000.17FF (privado)
Datos sin valor inicial: int a (lnea 04), ocupa 4 bytes
Heap: Crecer hacia abajo segn se vaya demandando mas espa-
cio de forma dinmica. Los datos y el Heap tienen los mismos
permisos de lectura y escritura y estarn por tanto en la misma p-
gina. El Sistema Operativo asignar mas pginas al Heap segn
las vaya necesitando.
....
XXXX.F800-XX- 2 KiB RW Pila La pila crece hacia arriba
XX.FFFF (privado)
Variables locales char *f (lnea 18) 4 bytes Bloque ac-
Direccin de retorno y puntero marco anterior tivacin
( 4 + 4 bytes) main
Parmetros (0 bytes)
Variables de entorno del proceso
161
Tabla 3.2
Direccin Tamao Caractersticas Contenido
0000.0000- 2 KiB RX El cdigo de 3.573 B utiliza dos pginas con permisos de lectura y
0000.07FF (Compartido) ejecucin. Queda espacio libre hasta 4 KiB
0000.0800- 2 KiB
0000.0FFF
0000.1000- 2 KiB RW Datos con valor inicicial: b=50 y static e=2, solo 8 bytes, pero se
0000.17FF (privado) ocupa
Datos sin valor inicial: int a (lnea 04), ocupa 4 bytes
Heap: Crecer hacia abajo segn se vaya demandando mas espacio
de forma dinmica. Los datos y el Heap tienen los mismos permisos
de lectura y escritura y estarn por tanto en la misma pgina. El Sis-
tema Operativo asignar mas pginas al Heap segn las vaya necesi-
tando.
El Heap habr crecido 512 bytes del malloc().
....
8000.0000- N2 KiB RX Cdigo librera dinmica: Se carga por el medio del mapa. En este
8006.3FFF (Comparti- caso se han supuesto 200 pginas (N=200). Los permisos son de lec-
do) tura y ejecucin y es memoria compartida. En este caso sera la libre-
ra estndar.
8006.4000- M2 KiB RW DSVI y DCVI librera dinmica: Estos datos son privados del pro-
8007.CFFF (privado) ceso. Se ha supuesto que se necesitan 50 pginas (M=50)
....
FFFF.F800- 2 KiB RW Pila La pila crece hacia arriba
FFFF.FFFF (privado) Variables locales int d (lnea 09) (4 bytes). La Bloque acti-
variable e es esttica y est inicializada a un vacin de
valor, por tanto se encontrar en Datos con va- funcion()
lor inical
Direccin de retorno y puntero marco anterior (4
+ 4 bytes)
Parmetro c (4 bytes)
Variables locales char *f (lnea 18) 4 bytes Bloque acti-
Direccin de retorno y puntero marco anterior (4 vacin de
+ 4 bytes) main()
Parmetros (0 bytes)
Variables de entorno del proceso
Tabla 3.3
Direccin Tamao Caractersticas Contenido
0000.0000- 2 KiB RX El cdigo de 3.573 B utiliza dos pginas con permisos de lectura y
0000.07FF (Compartido) ejecucin. Queda espacio libre hasta 4 KiB
0000.0800- 2 KiB
0000.0FFF
0000.1000- 2 KiB RW Datos con valor inicicial: b=50 y static e=2, solo 8 bytes, pero se
0000.17FF (privado) ocupa
Datos sin valor inicial: int a (lnea 04), ocupa 4 bytes
Heap: Crecer hacia abajo segn se vaya demandando mas espacio
de forma dinmica. Los datos y el Heap tienen los mismos permisos
de lectura y escritura y estarn por tanto en la misma pgina. El Sis-
tema Operativo asignar mas pginas al Heap segn las vaya necesi-
tando. Se han liberado los 512 bytes del malloc()
....
162 Problemas de sistemas operativos

8000.0000- N2 KiB RX Cdigo librera dinmica: Se carga por el medio del mapa. En este
8006.3FFF (Compartido) caso se han supuesto 200 pginas (N=200). Los permisos son de lec-
tura y ejecucin y es memoria compartida. En este caso sera la libre-
ra estndar.
8006.4000- M2 KiB RW DSVI y DCVI librera dinmica: Estos datos son privados del pro-
8007.CFFF (privado) ceso. Se ha supuesto que se necesitan 50 pginas (M=50)
....
XXXX.F800- 2 KiB RW Pila La pila crece hacia arriba
XXXX.FFFF (privado)
Variables locales char *f (lnea 18) 4 bytes Bloque activa-
Direccin de retorno y puntero marco anterior cin main()
(4 + 4 bytes)
Parmetros (0 bytes)
Variables de entorno del proceso
Tabla 3.4

Problema 3.6 (abril 2004)

El programa adjunto ejecuta en un sistema con memoria virtual siendo el tamao de pgina de 8 KiB. Adems, el
montaje dinmico se realiza en el momento de carga en memoria del programa.
1 #include <sys/mman.h>
2 #include <sys/stat.h>
3 #include <sys/types.h>
4 #include <fcntl.h>
5 #include <stdio.h>
6 #include <unistd.h>
7
8 int v[1000]; /* Considere enteros de 4 bytes. */
9
10 int main(void)
11 {
12 int fd, pid;
13 int length = 1024, offset = 0;
14 char * area, caracter;
15
16 fd = open("compartido", O_RDWR);
17 area = mmap(0, length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset);
18
19 pid = fork();
20 if (pid == 0) {
21 strcpy(area, "12345");
22 caracter = area[2];
23 area[4] = '7';
24 printf("valor de area[4] = %c\n", area[4]);
25 } else {
26 area = area + 2;
27 strcpy(area, "123");
28 munmap(area-2,length);
29 caracter = area[2];
30 }
31 return 0;
32 }
Supngase que:
El texto del programa adjunto con compilacin dinmica tiene un tamao de 5032 B, lo que incluye el
cdigo y las cadenas de caracteres.
163
Dicho programa utiliza la biblioteca libc que tiene un texto de 123 KiB, unos datos con valor inicial de
13 KiB y unos datos sin valor inicial de 4 KiB.
Por defecto el sistema operativo reserva 20 KiB para el heap, que incluye en la regin de datos, y 48
KiB para la pila, incluyendo la pila inicial. Resaltamos que las regiones de datos con valor inicial, datos
sin valor inicial y heap se incluyen en la correspondiente regin de datos.
a) Calcular el nmero de pginas que requiere la imagen de memoria del proceso que ejecuta dicho programa
al iniciar la ejecucin de la lnea 19.
b) Calcular el tamao de la seccin de datos del fichero ejecutable. Exprese el resultado en Bytes.
Supngase ahora que el orden de ejecucin es el siguiente:
El proceso padre ejecuta de forma seguida hasta la lnea 26 inclusive.

El hijo ejecuta hasta la lnea 21 inclusive.

El proceso padre ejecuta la lnea 27.

El proceso hijo ejecuta la lnea 22.

El proceso padre ejecuta la lnea 28.

El proceso hijo ejecuta hasta terminar.

El proceso padre ejecuta hasta terminar.

c) Determinar en el proceso hijo el valor de la variable caracter una vez ejecutada la lnea 22.
d) Determinar el caracter que se imprimir al ejecutarse la lnea 24.
e) Determinar en el proceso padre el valor de la variable caracter una vez ejecutada la lnea 29.

Solucin
a) Las regiones que tendr la imagen de memoria del proceso son las siguientes:
Texto o cdigo: 5032 B, lo que requiere 1 pgina. Derechos de lectura y ejecucin.
Datos: No hay datos con valor inicial y hay 1000*4 B de datos sin valor incial. A ello hay que aadir los
20 KiB del heap, por lo que la regin es de 24 KiB, lo que requiere 3 pginas. Derechos de lectura y es-
critura.
Pila: 48 KiB, lo que requiere 6 pginas. Derechos de lectura y escritura.
Texto de la librera: 123 KiB, lo que requiere 16 pginas. Derechos de lectura y ejecucin.
Datos de la librera: 13 KiB + 4 KiB = 17 KiB, lo que requiere 3 pginas. Derechos de lectura y escritura.
Regin compartida creada por el mmap: 1024 B, lo que requiere 1 pgina. Derechos de lectura y escritu -
ra.
La suma de las regiones anteriores es de 30 pginas.
b) El fichero ejecutable incluye una seccin con los datos que tienen valor inicial. Dado que no hay datos con valor
incial, la respuesta es 0 B.
c) El hijo es un clon del padre, por lo tanto tiene las mismas regiones de memoria que el padre y con los mismos de -
rechos. En concreto el hijo tambin tiene la regin creada por el mmap, y de forma compartida con el padre. El hijo
escribe en los bytes 0 a 4 los valores 1, 2, 3, 4 y 5. Seguidamente el padre escribe en los bytes 2 a 4 los valores 1, 2 y
3 (sobreescribiendo el 3, 4 y 5 escrito por el hijo).
Por tanto, el valor que obtiene el hijo es un 1.
d) El padre desproyecta la regin del mmap. Esto no afecta al hijo, que sigue teniendo la regin hasta que termina,
puesto que no ejecuta ningn munmap. Por tanto, el resultado del printf es:
valor de area[4] = 7
e) Al ejecutar la lnea 29, el padre ha desproyectado la regin, por lo que no tiene derechos de acceso a esa zona de
memoria. Al intentar acceder area[2] se generar un error de violacin de memoria.

Problema 3.7 (junio 2004)

Dado un sistema de ficheros de tipo UNIX, se tiene el siguiente conjunto de ficheros y directorios:
Nodo-i Dueo Permisos Tamao Agrupaciones
2 0 0755 180 21
28 100 0600 1435 35 y 40
164 Problemas de sistemas operativos

32 100 0600 20 94
41 100 0755 180 345
50 100 0600 30 155
115 100 0755 180 87
213 100 0755 180 350
233 100 0600 30 103
241 100 0755 180 200
256 100 0600 40 330
335 100 0755 180 210
El contenido de las agrupaciones asociadas a los ficheros y directorios se muestra a continuacin:
Agrupaciones Contenido
21 . 2; .. 2; usr 115; home 335; etc 41
35 0000111122223333444455556666....
40 1111111111111111111111111111....
87 . 115; .. 2; fich2 233; fich3 256
94 33333333333333333333...
103 2222222222222222222222222222
155 1234567890123456789012345678....
200 . 241; .. 41
210 . 335; .. 2; fich4 50; fich6 28
330 9999888877776666555544443333222211110000.....
345 . 41; .. 2; prueba 213; prueba2 241; fich5 28
350 . 213; .. 41; fich1 32
a) Si se desea abrir el fichero ``fich1 utilizando su nombre absoluto, cuntas tablas de directorios se necesi-
tan leer?
b) Cuntos enlaces fsicos tiene el directorio /etc?
Suponga que en este sistema tenemos 3 procesos, un proceso A, un proceso hijo del proceso A, que denominare-
mos proceso B, y un tercer proceso independiente, llamado C. Los 3 procesos se ejecutan con identificacin efectiva
de usuario 100.
A continuacin, se muestra el cdigo de los 3 procesos:
1 int main(void)
2 { /* Proceso A */
3 int fd2, fd3, fd3_2, fd4;
4 int leidos, escritos, longitud=2048, offset = 0;
5 char *dir_mem;
6 fd3 = creat("/usr/fich3", 0600);
7 fd4 = open("/home/fich4", O_RDWR);
8 dir_mem = mmap(0,longitud, PROT_READ|PROT_WRITE,MAP_SHARED, fd4, offset);
9 switch(fork()) {
10 case -1: perror("fork");
11 exit(1);
12 case 0: /* Proceso B */
13 fd3_2 = dup(fd3);
14 fd2 = open("/usr/fich2", O_RDWR);
15 lseek(fd3, 10, SEEK_CUR);
16 escritos = write(fd3_2, dir_mem, 5);
17 escritos = write(fd2, dir_mem, 3);
18 leidos = read(fd3, dir_mem, 11);
19 leidos = read(fd2, dir_mem, 10);
20 lseek(fd3_2, 0, SEEK_SET);
21 munmap(dir_mem,longitud);
22 return 0;
165
23 default:
24 leidos = read(fd3, dir_mem, 4);
25 munmap(dir_mem, longitud);
26 }
27 return 0;
28 }

/* Proceso C */
1 char buffer[20];
2 int main(void)
3 {
4 int fd2, fd3;
5 fd2 = open("/usr/fich2", O_RDONLY);
6 fd3 = open("/usr/fich3", O_WRONLY);
7 read(fd2, buffer,6);
8 write(fd3, buffer,3);
9 return 0;
10 }
Supngase que:
El programa correspondiente al proceso A ejecuta en un sistema con memoria virtual siendo el tamao
de pgina de 8 KiB.
El cdigo del programa ms las cadenas de caracteres ocupan 5 KiB.
El programa correspondiente al proceso A se enlaza de forma esttica a la biblioteca libc. Dicha biblio-
teca tiene un texto de 200 KiB, datos con valor inicial de 20480 Bytes y datos sin valor inicial de 10 KiB.
El sistema operativo construye la zona de datos mnima para las necesidades iniciales del proceso. Re-
saltamos que la regin de datos incluye a las regiones de datos con valor inicial y datos sin valor inicial.
La pila inicial ocupa 24 KiB.
Se consideran enteros de 4 Bytes.
c) Calcular el nmero de pginas que requiere la imagen de memoria del proceso A al iniciar la ejecucin de la
lnea 9.
d) Calcular el tamao de la seccin de datos del fichero ejecutable correspondiente al proceso A, expresando el
resultado en Bytes.
Suponiendo que el orden de ejecucin de los 3 procesos es el siguiente:
El proceso A ejecuta hasta la lnea 9 inclusive.
El proceso B ejecuta hasta la lnea 20 inclusive.
El proceso C ejecuta de forma completa
El proceso A ejecuta hasta terminar
El proceso B ejecuta hasta terminar
e) Qu valor toma la variable leidos justo despus de la ejecucin de la lnea 18?
f) Tras finalizar todos los procesos, cul es el contenido de las 5 primeras posiciones del fichero /home/fi -
ch4"?

SOLUCIN
a) Analizando el rbol de directorios correspondiente a este sistema de ficheros, observamos que el fichero fich1
tiene como ruta absoluta /etc/prueba/fich1, por lo que se necesitan leer 3 tablas de directorios, correspondientes a
la tabla del directorio raiz, la tabla del directorio etc y la tabla del directorio prueba.
b) Observando la tabla de agrupaciones, podemos calcular el nmero de enlaces fsicos de cualquier fichero o direc-
torio, simplemente contando el nmero de referencias al nodo-i. En el caso del directorio /etc, su nodo-i es 41, y es
referenciado 4 veces. Por lo tanto, el nmero de enlaces fsicos es 4.
c) Para calcular el nmero de pginas que requiere la imagen de memoria del proceso A, necesitamos calcular:
Texto: 5 KiB (cdigo programa + cadenas de caracteres) + 200 KiB (Biblioteca esttica) = 205 KiB -> 26
pginas de 8 KiB.
Datos:
Datos sin valor inicial: 10 KiB
166 Problemas de sistemas operativos
Datos con valor inicial: 20480 Bytes = 20 KiB
Equivale a 30 KiB -> 4 pginas de 8 KiB
Pila: 24 KiB -> 3 pginas de 8 KiB.
Memoria compartida: 2048 Bytes -> 1 pgina de 8 KiB
Por tanto, el nmero de pginas que requiere la imagen de memoria del proceso A es 34 (26+4+3+1).
d) Para calcular el tamao de la seccin de datos del fichero ejecutable, debemos calcular el nmero entero de pgi -
nas que d soporte a los datos con valor inicial. En este caso, los datos con valor inicial ocupan 20 KiB, lo que re-
quiere 3 pginas. La seccin de datos del ejecutable ocupar por tanto en bytes: 3*8*1024 = 24576 bytes.
e) Debido a que fd3 utiliza modo slo escritura (O_WRONLY), no se puede leer a partir de dicho descriptor. Por lo
que la operacin read devuelve -1.
f) Tras la ejecucin del proceso A hasta la lnea 9, el fichero /home/fich4 no tiene ningn cambio. A continuacin,
ejecuta el proceso B hasta la lnea 20. En la lnea 18 se modifica el contenido de la zona de memoria compartida.
Las primeras posiciones son 0. Despus ejecuta el proceso C de forma completa. Este proceso modifica el fichero
/usr/fich3. El proceso A vuelve a ejecutar hasta terminar, modificando las 4 primeras posiciones de la zona de me -
moria compartida. Finalmente, ejecuta el proceso B hasta terminar. Lo nico que hace es desproyectar la zona de
memoria compartida.
De este modo, el contenido de las 5 primeras posiciones del fichero /home/fich4 al final de la ejecucin queda
del siguiente modo:
1. carcter del fichero 4 2. carcter del fichero 4 3. carcter del fichero 4 0 8. carcter del fichero 2.
Por tanto, las 5 primeras posiciones del fichero /home/fich4 tienen como contenido: 12302.

Problema 3.8 (septiembre 2004)

En un sistema con pginas de 4 KiB y palabras de 32 bits se tienen las tablas de pginas de la figura 3.11.
a) Compare las tablas de los dos procesos e indique las conclusiones a las que llegue.
b) Supongamos que el proceso 1 est ejecutando, que el tamao ocupado de la pila es de 400 B y que realiza
una llamada a un procedimiento que contiene exclusivamente la declaracin de una matriz local de 100x10
enteros con valor inicial. Indicar los cambios que se produciran en las tablas de pginas al ejecutarse dicha
llamada. Qu ocurre si la matriz es de 100x100 enteros con valor inicial? Y si es de 100x100 enteros sin
valor inicial?
c) Supngase que el proceso 1, partiendo de la situacin de la figura, ejecuta un bucle que recorre todo su seg -
mento 4. Justo despus de ello el proceso 2 ejecuta a su vez un bucle que recorre todo su segmento 4. Es
probable que el proceso 2 requiera el intercambio de alguna pgina? Justificar la respuesta.
d) Sea ahora una mquina con palabras de 64 bits y que requiere datos alineados. Los enteros ocupan 4 bytes
mientras que los double ocupan 8 bytes. Calcular el espacio real ocupado por la siguiente estructura supo-
niendo que el compilador no realiza ninguna optimizacin.
struct ditur {
char b;
double c;
int v[5];
double m;
char n;
char s;
int r;
}
Qu optimizacin se podra realizar?
167
Direccin R W X P Marco/swap
16000
0 0 1 1 124
TSN1 P = 1 significa que
la pgina est en
R W X Tamao Direccin memoria principal
16300
1 1 0 0 12
TSN2
Segmento 1 texto 0 0 1 1 16000

Segmento 2 datos 1 1 0 4 16300 1 1 0 0 13

Segmento 3 pila 1 1 0 2 16600 1 1 0 0 14

Segmento 4 fichero proyectado 1 1 0 1 16900 1 1 0 0 15

Tabla de primer nivel del proceso 1 16600 TSN3


1 1 0 1 341

1 1 0 0 3765

R W X Tamao Direccin 16900 TSN4


1 1 0 0 3876
Segmento 1 texto 0 0 1 1 16000

Segmento 2 datos 1 1 0 2 17200 17200


1 1 0 0 2
TSN5

Segmento 3 pila 1 1 0 2 17600 1 1 0 0 3


Segmento 4 fichero proyectado 1 1 0 1 16900
17600
1 1 0 1 263
TSN6
Tabla de primer nivel del proceso 2
1 1 0 0 37

Tablas de segundo nivel


Figura 3.11

Solucin
a.- A la vista de las tablas de pginas se observa que los dos procesos comparten dos regiones, la de texto y el fiche -
ro proyectado en memoria. Dado que ambos procesos tienen derechos de escritura sobre el fichero proyectado pode-
mos asegurar que se ha proyectado de forma MAP_SHARED.
Las regiones de texto no tienen derechos de lectura. Esto significa que no pueden contener cadenas de caracteres
ni constantes.
Los procesos no tienen ninguna pgina de datos en marcos de memoria, lo que parece indicar que se han puesto
en ejecucin recientemente.
Se podra pensar que uno de los procesos desciende del otro, pero el que sus regiones de datos tengan tamaos
distintos no apoya dicha idea, sobre todo por lo dicho en el prrafo anterior.
b.- La llamada a un procedimiento implica asignacin de memoria para los argumentos de la llamada, la direccin
de retorno, el puntero al bloque de activacin anterior y las variables locales. Dicha asignacin se realiza en la pila,
con el denominado bloque de activacin. Hay que reservar espacio para 100x10 = 1.000 enteros, lo que supone
4.000B (el tamao del entero se ha supuesto de 4 bytes), ms los argumentos (que desconocemos), la direccin de
retorno (4B) y el puntero de bloque (4B). Como la pila tiene ocupados 400 B llegaremos hasta 4.408 B, es decir, a
algo ms de 1 pgina. Ahora bien, se dispone de dos pginas, por lo que no es necesario incrementar la regin de
pila. Sin embargo, al tener la matriz valores iniciales, es necesario rellenar dicha estructura con esos valores, lo que
implica que ha de ser trada a memoria principal. Por todo ello, quedar modificada la tabla TSN3, en la que se pro -
ducen dos cambios: se pone a 1 el bit P de la segunda pgina y se pone el valor del marco asignado a la segunda
pgina.
Direccin R W X P Marco/swap
16600
1 1 0 1 341
TSN3
R W X Tamao Direccin 1 1 0 1 432
Segmento 1 texto 0 0 1 1 16000 1 1 0 1 433
Segmento 2 datos 1 1 0 4 16300 1 1 0 1 434
Segmento 3 pila 1 1 0 10 16600 1 1 0 1 435
Segmento 4 fichero proyectado 1 1 0 1 16900 1 1 0 1 436
Tabla de primer nivel del proceso 1 1 1 0 1 437

1 1 0 1 438

1 1 0 1 439

1 1 0 1 440

Figura 3.12
168 Problemas de sistemas operativos
En el caso de una matriz de 100x100 = 10.000 enteros con valor inicial hacen falta 40.000 B ms los argumen -
tos, retorno y puntero de bloque, es decir, unos 40.008 B. Como estn ocupados 400 B se necesita un total de 40.408
B, por lo que bastan 10 pginas (= 40.960 B) . La pila dispone de 2 pginas, por lo que es necesario aumentarla en 8
pginas. Adems, hay que rellenar todas estas pginas con los valores iniciales, por lo que hay que llevarlas a mar-
cos de memoria principal. Todo ello afecta a la tabla de primer nivel y a la tabla TSN3, que quedran como muestra
la figura 3.12, en la que se han resaltado en gris los elementos modificados (los valores de marco de pgina seran
los que el sistema operativo seleccionase).
Para el caso de la matriz de 100x100 sin valor inicial la situacin es bastante parecida a la anterior. Hay que aa-
dir 8 pginas a la pila, pero al no tener que rellenar su contenido con valores iniciales no es obligado que se carguen
en marcos de pgina (bastara con dejar marcadas esas pginas como de rellenar a cero). Las tablas quedaran de for -
ma similar a la figura , pero los bits de presente P estaran todos a 0 menos el de la primera pgina. Sin embargo,
hay una sutileza importante:cundo se entera el sistema operativo que tiene que aumentar la pila? Si el hardware
dispone de control de desbordamiento de pila, en cuanto se aumenta el puntero de pila por encima de su mximo va-
lor. Si no dispone de ese mecanismo se enterar cuando se acceda (para leer o escribir) a zonas prohibidas. En ese
momento ser cuando se aumente realmente la tabla de pginas.
c.- Dado que el proceso 2 ejecuta el mismo programa sobre el mismo fichero proyectado, al pasar del primer al se -
gundo proceso nos encontraremos con que tanto la pgina del texto como la pgina del fichero proyectado estarn en
sendos marcos de pgina. Hay que notar que el proceso 1, al hacer un bucle sobre los datos del fichero proyectado
trae a un marco la pgina de dicho fichero. Por lo tanto, el proceso 2, mientas no realice accesos a su regin de datos
o incremente su pila, no requerir ningn intercambio de pgina.
d.- Los caracteres ocupan un byte por lo que la distribucin de las variables en memoria en una mquina que realice
alineamiento de datos es la mostrada en la figura 3.13. El espacio requerido es de 7 palabras, desperdicindose un
total de 13 B (marcado en gris en la figura).
b
c
v[0] v[1]
v[2] v[3]
v[4]
m
c s r
Figura 3.13

La optimizacin consiste en reordenar la definicin de la estructura, ya sea por el programador o por el compila -
dor. Por ejemplo se puede redefinir la estructura de la forma mostrada a continuacin, por lo que la ocupacin en
memoria sera la de la figura 3.14 (basta con 6 palabras, perdindose solamente 5 bytes, que podran llegar a ser uti-
lizados por otra variable o estructura en caso de encajar en dicho espacio).
c
v[0] v[1]
v[2] v[3]
v[4] r
m
b c s
Figura 3.14

struct ditur {
double c;
int v[5];
int r;
double m;
char b;
char n;
char s;
}
169

Problema 3.9 (abril 2005)

Sea un sistema operativo que utiliza pginas de 4 KiB, tablas de pginas de 2 niveles y regiones de texto comparti -
das.
Existen simultneamente dos procesos A y B que ejecutan el mismo programa y de los que sabemos que en un
instante de tiempo determinado Ta tienen la siguiente situacin:
Proceso Regin N pginas N pginas Direccin
presentes comienzo
A Texto 24 7 0
Datos 13 5 223
Texto Biblioteca dinmica Z 45 12 224
Datos Biblioteca dinmica Z 5 3 224+223
Fichero M proyectado compartido 27 7 225
Pila 11 3 230
B Texto 24 7 0
Datos 15 5 223
Fichero M proyectado compartido 27 7 224
Texto Biblioteca dinmica Z 45 12 225
Datos Biblioteca dinmica Z 5 3 225+223
Pila 16 7 230
a) Calcular el n total de marcos de pginas que tienen asignados entre los dos procesos en ese instante.
b) Seguidamente, A ejecuta un bucle de lectura que recorre todo el fichero proyectado. Suponiendo que no se
reemplaza ninguna pgina de los procesos A y B, indicar el n de fallos de pgina que se producen as como
el total de marcos de pgina que tienen ahora asignados entre los dos procesos.
Tomando como referencia el instante de tiempo Ta, Indicar si se produce un error de ejecucin y, en su caso, el
valor de la variable v en cada uno de los procesos en los supuestos c, d, e, y f siguientes. (NOTA: recuerde que la
notacin C 1<<x equivale a 2x).
c) El proceso A ejecuta: v = *p (donde p vale 24); e inmediatamente el proceso B ejecuta el mismo trozo de c -
digo, pero ahora p vale 25. En este caso, indicar, adems, si se puede producir un fallo de pgina conside -
rando que las variables v de cada proceso estn cada una en su correspondiente marco de pgina.
d) El proceso A ejecuta: p = (1<<24) + 1; *p = 234; v = *p; e inmediatamente el proceso B ejecuta v = p.
e) El proceso A ejecuta: p = (1<<24) + (1<<23) + 12; *p = 234; e inmediatamente el proceso B ejecuta p =
1<<24) + (1<<23) +
f) El proceso A ejecuta: p = (1<<25) + 10012; *p = 234; e inmediatamente el proceso B ejecuta p = (1<<24)
+ 10012; v = *p.

Solucin
a) Las regiones privadas del problema son las de datos, las de datos de las bibliotecas dinmicas y las pilas. Hemos
de sumar todos los marcos de pgina del proceso A: 7 + 5 + 12 + 3 + 7 + 3 = 37 ms los marcos de pgina del proce -
so B que no comparte con A, es decir, 5 + 3 + 7 = 15. Lo que nos da un total de 52 marcos de pgina.
b) Como la regin del fichero proyectado tiene 27 pginas, de las que 7 ya estn en marcos, el recorrer todo el fiche-
ro har que se traigan a marcos las 27 7 = 20 pginas restantes, lo que produce 20 fallos de pgina. Despus de
esta bucle, entre los dos procesos se tendrn 52 + 20 = 72 marcos de pgina, dado que no se produce ningn reem-
plazo.
c) No se produce error de ejecucin vA = 24 y vB = 25. No es posible un fallo de pgina. Justo despus de ejecutar A,
B ejecuta el mismo cdigo, por lo que ya tiene que estar en un marco. Por otro lado v = *p con p = 24 implica un ac -
ceso a la primera pgina de la regin de cdigo, al igual que v = *p con p = 25, pgina que es compartida por los dos
procesos.
170 Problemas de sistemas operativos
d) Se produce un error al ejecutar A puesto que se intenta escribir en una regin (texto de biblioteca) que no tiene
derechos de escritura. Del valor vB no podemos decir su valor, puesto que no conocemos el valor de pB.
e) Se produce un error al ejecutar B, dado que la direccin 2 24 + 223 + 12 = 224 + 212211 + 12 no est en el mapa de
memoria de ese proceso.
f) En este caso no se produce error de ejecucin, siendo vB = 234.

Problema 3.10 (junio 2005)

Sea un sistema operativo que utiliza pginas de 4 KiB, tablas de pginas de 2 niveles, direcciones de 4 bytes y re -
giones de texto compartidas.
Dado el siguiente cdigo, correspondiente al ejecutable "ejec1":
#define TAM_BUF 1024
#define TAM_BUF_PROY 16384
char buffer[TAM_BUF];
char * buffer_proy;

int main(void)
{
int fd;
int i;
int leidos, total;
int pfd[2];

fd = open("FICHERO_PROY", O_RDWR, 0666);


buffer_proy = mmap(0, TAM_BUF_PROY, PROT_READ | PROT_WRITE,
MAP_SHARED, fd , 0);
if (buffer_proy == MAP_FAILED) perror("mmap");
pipe(pfd);

switch (fork()){
case -1:
perror("fork");
munmap(buffer_proy, TAM_BUF_PROY);
close(fd); close(pfd[0]); close(pfd[1]);
exit(1);
case 0:
read(pfd[0],&jtotal, sizeof(int));
close(fd); close(pfd[0]); close(pfd[1]);
write(1, buffer_proy, total);
munmap(buffer_proy, TAM_BUF_PROY);
return 0;
default:
total=0;
while ((leidos=read(0, buffer, TAM_BUF))>0){
for (i=0; i<leidos; i++) {
buffer_proy[j] = buffer[i];
total++;
}
}
write(pfd[1],&total,sizeof(int));
wait(NULL);
close(fd); close(pfd[0]); close(pfd[1]);
munmap(buffer_proy, TAM_BUF_PROY);
return 0;
}
return 0;
}
171
Se supone que el fichero cuyo nombre es FICHERO_PROY existe y tiene un tamao de TAM_BUF_PROY by-
tes o ms.
La versin compilada correspondiente al cdigo anterior ocupa 25200 bytes. El montaje del ejecutable utiliza
una biblioteca esttica, cuyo texto ocupa 52 KiB y cuyos datos sin valor inicial ocupan 12 KiB. Dicha biblioteca no
tiene datos con valor inicial.
Conteste de forma razonada a las siguientes preguntas:
a) Cuntos bytes ocupa el fichero ejecutable ejec1?
b) Dibuje el mapa de memoria de los dos procesos justo tras la llamada fork, indicando la disposicin y ta-
mao de cada regin del mapa y su carcter compartido o privado. Haga las suposiciones que considere
convenientes.
c) Describir de forma resumida la funcionalidad de ejec1.
d) Codifique el programa ejec2, que, sin rescribir el cdigo de ejec1, sino haciendo uso de su ejecutable,
imprima por la salida estndar el contenido de un determinado fichero dado como argumento, es decir:
$ ./ejec2 nombre_fichero
e) El programa ejec1" tiene la limitacin de que slo permite comunicar datos con un tamao inferior o igual
a TAM_BUF_PROY. Esboce, sin programar nada, una solucin que permitiese reutilizar el buffer comparti-
do eliminando esta limitacin.

Solucin
a) El fichero ejecutable estar compuesto por el texto correspondiente a ejec1, el texto de la biblioteca esttica,
los datos con valor inicial de ejec1 y los datos de valor inicial correspondientes a la biblioteca esttica. Es decir:
Texto ejec1: 25200 bytes
Texto biblioteca esttica: 52 KiB = 53284 bytes
La suma del texto es igual a 78484 bytes, que ocupan 20 pginas
Datos v.i. ejec1: 0 bytes
Datos v.i. biblioteca: 0 bytes
Para hacer el clculo, se ha considerado despreciable el tamao de la cabecera del ejecutable.
b) Justo antes de la llamada fork(), el proceso padre tiene la siguiente imagen de memoria:
Texto ejec1 + Texto biblioteca Compartido (20 pginas)
Datos sin v.i. ejec1 + biblioteca Privado (4 pginas)

Fichero proyectado Compartido (4 pginas)

Pila Privado (1 pgina)


Para calcular el nmero de pginas:
Texto compartido ejec1: 25200 bytes + Texto compartido biblioteca esttica: 52 KiB (53284 bytes) =
78448 bytes -> 20 pginas
Datos con v.i.: 0
Datos sin v.i. ejec1: 1024 bytes (buffer) + 1 direccin (4 bytes) + Datos sin v.i. biblioteca esttica: 12
KiB -> 4 pginas
Fichero proyectado: 16 KiB -> 4 pginas
Pila: Necesitamos espacio para entorno, variables locales, etc Tenemos 6 enteros como variables loca-
les. Tenemos suficiente con 1 pgina.
Una vez ejecutada la llamada fork(), el proceso hijo tiene las mismas regiones, aunque pueden estar en direc-
ciones diferentes. Si las regiones son compartidas, el proceso hijo comparte con el proceso padre dichas regiones. Si
las regiones son privadas, el proceso hijo tiene una copia de dichas regiones.
c) El cdigo tiene la siguiente funcionalidad. Un proceso crea otro proceso hijo y se comunica con l a travs de un
fichero proyectado en memoria y de un pipe. El proceso padre lee de la entrada estndar, escribiendo el contenido en
la zona de memoria compartida. Calcula el nmero de caracteres escritos en la zona de memoria compartida y enva
este dato a travs del pipe al proceso hijo. El proceso hijo recoge el nmero de caracteres y escribe a travs de la
salida estndar la parte de la memoria compartida que ha sido escrita por el padre. Se trata de una especie de filtro,
172 Problemas de sistemas operativos
en el cual el proceso padre lee de la entrada estndar, pasndole dicha informacin al proceso hijo, que lo escribe
por la salida estndar.
d) Se debe redirigir la entrada estndar al fichero que se le pasa como argumento a ejec2. El cdigo es el siguiente
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "Error. Uso: ./ejerc2 nom_fich\n");
exit(1);
}
close(0);
open(argv[1],O_RDONLY);
execlp("./ejerc1", "./ejerc1", NULL);
perror("exec");
return(1);
}
e) El padre tendra que detectar que ha llegado al final del buffer compartido (tamao TAM_BUF_PROY), en cuyo
caso se comunicara con el proceso hijo para enviarle el nmero de datos ledos hasta ese momento. El proceso hijo
leera los datos de la memoria compartida, los escribira por la salida estndar y pasara algn testigo al proceso pa -
dre para permitirle volver a escribir a partir del inicio de la memoria compartida. Este proceso de sincronizacin se
tendra que realizar con algn mecanismo de sincronizacin, como puede ser un semforo, otro pipe, etc.

Problema 3.11 (junio 2006)

Sea un sistema con memoria virtual que tiene un tamao de pgina de 4 KiB y direcciones de 4 bytes. Dado el si-
guiente cdigo:
struct datos {
int a;
char b;
int c[2048];
};

struct datos varDat;


char varChar = c;

int main (int argc, char *argv[])


{
static int varInt = 5;
int fd;
int i=0;
char *p, *q;
struct stat bstat;

if (argc != 2) {
printf("Error. Uso: ./ejec nombre_fichero");
return 1;
}

fd = open(argv[1], O_RDONLY);
if (fd < 0)
{
perror("open");
return 1;
}
fstat(fd, &bstat);
p = mmap(NULL, bstat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
173
varDat.a = varInt;
varDat.b = varChar;

q = p;
for (i=0; i< bstat.st_size; i++)
{
varDat.c[i] = *q;
q++;
}

munmap(p, bstat.st_size);
funcBib(varDat);
return 0;
}
Teniendo en cuenta los siguientes supuestos:
Esta arquitectura requiere alineacin de datos.
sizeof(int) es igual a 4 bytes.
Existe una nica regin de datos donde se incluyen los datos con valor inicial y sin valor inicial.
El cdigo utiliza una biblioteca dinmica, donde se encuentra la funcin funcBib. El montaje se lleva a
cabo al invocar el procedimiento y se supone que dicha biblioteca no se ha cargado previamente por
ningn otro proceso.
El cdigo del ejecutable ocupa 4020 bytes y el cdigo de la biblioteca dinmica 24 KiB.
Los datos con valor inicial de la biblioteca dinmica ocupan 6 KiB. No tiene datos sin valor inicial.
La tabla de smbolos del fichero ejecutable ocupa 4 KiB.
Se reservan 4 pginas para la pila inicial. Se supone que el sistema operativo no tiene que ampliar esta
regin en ningn momento de la ejecucin.
La proyeccin del fichero (uso de mmap) se lleva a cabo de forma correcta.
Responder a las siguientes preguntas:
a) Cul es el nmero de pginas ocupado por todas las regiones del mapa de memoria del proceso justo des-
pus de ejecutar munmap(p, bstat.st_size)?
b) Cuntas variables del cdigo forman parte de los datos con valor inicial?
c) Cul es el tamao en bytes que ocupan los datos sin valor inicial en la regin de datos del proceso?
d) Qu tipo de error se puede producir en la ejecucin de la sentencia varDat.c[i] = *q?

Solucin
a) Las regiones que tiene el proceso junto despus de desproyectar el fichero son las siguientes:
Regin de cdigo: 4020 bytes (la biblioteca es dinmica), lo que ocupa 1 pgina
Regin de datos:
Datos c.v.i.: 1 variable entera (la variable esttica) y 1 variable de tipo char: 4 bytes + 4 bytes (stos lti -
mos por alineacin de datos)
Datos s.v.i: 1 variable de tipo struct datos, que ocupa: 4 bytes del campo entero 4 bytes del campo char
(por alienacin de datos) 2048*4 del vector de enteros
En total la regin de datos ocupa 3 pginas.
Regin de pila: 4 pginas
En total, 8 pginas
Nota: Hay que tener en cuenta que todava la biblioteca dinmica no ha sido montada y el fichero ya se ha des -
proyectado.
Por supuesto, la tabla de smbolos no forma parte del mapa de memoria del proceso como una regin indepen-
diente.
b) 2, la variable entera esttica varInt y la variable de tipo char varChar.
c) Ya se ha calculado para responder a la pregunta a):
4 bytes del campo entero
174 Problemas de sistemas operativos
4 bytes del campo char (por alienacin de datos)
2048*4 del vector de enteros
Total: 8200 bytes
d) Se puede dar un desbordamiento de un dato mltiple (el vector varDat.c) si el tamao del fichero es superior a
2048.

Problema 3.12 (junio 2008)

Un sistema operativo con memoria virtual sigue el modelo clsico UNIX con una regin de datos que engloba los
datos estticos y los dinmicos (heap). Adems, asigna por defecto un heap de, al menos, 16 KiB y una pila de 32
KiB. El tamao de pgina es de 4 KiB.
Mediante el mandato size obtenemos los siguientes valores:
# size mi_programa
text data bss dec hex filename
12096 1080 8724 21900 558C mi_programa
# size mi_biblio
text data bss dec hex filename
645024 14720 13424 658448 A0C10 mi_biblio
a) Dibujar el mapa de memoria del proceso que ejecuta mi_programa al comienzo de la ejecucin, considerando
que el montaje de las bibliotecas se hace al invocar una de sus funciones. Indicar para cada regin el espacio que
ocupa, los permisos de acceso y el contenido. Marcar los huecos existentes.
b) mi_programa incluye una lnea que utiliza la funcin mi_funcion incluida en la biblioteca dinmica mi_biblio.
Dibujar el mapa de memoria del proceso justo despus de la llamada a mi_funcion, de igual forma que en el caso
anterior.
c) mi_programa incluye las siguientes lneas de cdigo:
p = mmap(NULL, 8192, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
q = p;
n = fork();
if (n == 0) {
for (i = 0; i < 2; i++){*q = 1; q++;}
m = *p;
......
}else{
for (i = 0; i < 2; i++){*q = 3; q++;}
r = *p;
......
}
Indicar el valor que tendrn las variables m y r despus de que el padre y el hijo hayan ejecutado completamen -
te las sentencias for.
d) mi_programa tiene definida como global la variable int *p; Adems tiene dos threads que ejecutan las lneas de
cdigo siguientes:
Thread 1
p = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
for (i = 0; i < 2; i++){*p = i; p++;}
Thread 2
for (j = 0; j < 5; j++){*p = j+5; p++;}
Suponiendo que primero el thread 1 ejecuta completamente las dos lneas indicadas y luego el thread 2 comple-
ta la ejecucin del for, indicar el contenido de la regin generada con el mmap.
e) mi_programa realiza montaje explcito de la biblioteca mis_numeros, utilizando la funcin mi_maximo, que se
define en dicha biblioteca. Esta funcin recibe dos nmeros enteros y devuelve otro nmero entero. Escriba un frag-
mento de cdigo que utilice dicha funcin mi_maximo.
175

Solucin
a) La regin de texto, como toda regin en un sistema con memoria virtual, se compone de un nmero entero de p -
ginas. En este caso el texto requiere 3 pginas, puesto que 3x4 KiB = 12.288 B > 12.096. Esta regin es compartida,
de tamao fijo, tiene permisos de lectura y ejecucin, y su fuente es el ejecutable. Quedan sin usar 192 B al final de
la ltima pgina.
La regin de datos incluye los datos con valor inicial ms los datos sin valor inicial ms el heap, es decir, 1.080
B + 8.724 B + 16 KiB = 9.804 B + 16 KiB. Se requieren, por tanto 7 pginas. El heap quedar de 7x4 KiB - 9.804
B = 18.868 B. Esta regin es privada, de tamao variable (el heap puede crecer), tiene permisos de lectura y escritu-
ra. La fuente de los datos con valor inicial es el ejecutable, para el resto es rellenar a ceros.
La pila incluir las variables de entorno ms la pila inicial y tendr un tamao de 32 KiB = 8 pginas. Esta regin
es privada, de tamao variable, tiene permisos de lectura y escritura y su fuente es rellenar a ceros.
La figura 3.15 muestra la imagen de memoria del proceso. Dependiendo del tipo de segmentos que permita el
sistema, la regin de datos puede o no estar pegada a la de texto. En la figura se ha dejado un hueco, puesto que es el
caso ms general, dado que el texto es de tamao fijo, este hueco no tiene por objeto que la regin pueda crecer. En -
tre heap y pila s existir un hueco, para que el heap pueda crecer.
texto r-x compartida
texto 3 pginas

hueco

datos con valor inicial datos rw privada


datos sin valor inicial 7 pginas

heap

hueco

pila rw privada
8 pginas

pila inicial
entorno
Figura 3.15
b) La biblioteca dinmica requiere dos regiones la de texto y la de datos. El tamao de la regin de datos de la bi -
blioteca es de 645.024 B, por lo que son necesarias 158 pginas = 647.168 B. Esta regin es compartida, de tamao
fijo, tiene permisos de lectura y ejecucin, y su fuente es el fichero de la biblioteca.
Para datos son necesarios 14.720 B + 13.424 B = 28.144 B. Son necesarias 7 pginas = 28.672 B. Esta regin es
privada, de tamao fijo, tiene permisos de lectura y escritura. La fuente de los datos con valor inicial es el fichero de
la biblioteca, mientras que para los datos sin valor inicial es rellenar a ceros.
La figura 3.16 muestra la imagen de memoria del proceso. Como se ha indicado anteriormente, el hueco entre
texto y datos puede no existir, dependiendo del sistema. Lo que siempre existir ser el hueco para que pueda crecer
el heap y la pila.
texto texto rx compartida
hueco 3 pginas

datos rw privada
datos 7 pginas
hueco
texto biblioteca rx compartida
158 pginas

texto biblioteca

hueco
datos biblioteca rw privada
datos biblioteca 7 pginas
hueco
pila rw privada
pila 8 pginas

Figura 3.16
176 Problemas de sistemas operativos
c) Tanto el padre como el hijo escriben en la regin compartida, por lo que los valores de la regin dependen del or-
den de ejecucin. En relacin con el contenido final de la regin se podran dar los siguientes casos:
11.....
33.....
13.....
31.....
siendo los dos primeros ms probables que los dos ltimos, que exigen ms cambios de contexto.
En cualquier caso, no se puede determinar si el valor de r y m ser un 1 o un 3.
d) Ahora nos especifican el orden de ejecucin, por lo que podemos determinar el resultado. Dado que p es una va-
riable global, los dos threads pueden acceder a la regin de memoria.
Al final del primer bucle la regin contiene: 01....
Al final del segundo bucle la regin contiene: 0156789....
e) El cdigo propuesto es el siguiente:
void *h;
int (*max) (int,int);

h = dlopen("/lib/mis_numeros.so", RTLD_LAZY);
max = dlsym(h,"mi_maximo");
printf("%d\n", (*max)(3,5));
Tambin sera correcto poner:
printf("%d\n", max(3,5));

Problema 3.13 (junio 2009)

Sea un sistema con las siguientes caractersticas:


Ancho de palabra de 32 bits.
Memoria virtual con direcciones de 32 bits, de tipo segmentado paginado. Se dedican 9 bits para especi-
ficar el segmento.
Se utilizan pginas de 1 KiB.
El segmento de datos incluye los datos con y sin valor inicial adems del heap.
Los programas son de montaje dinmico, realizndose el montaje de una biblioteca cuando se utiliza por primera
vez una funcin de la misma.Observe que malloc y mmap estn en libc y que cos est en libm.
Se van asignando los segmentos 0, 1, 2, 3, .... en el orden en que se van creando los segmentos del proceso.
Suponer que los enteros (int) son de 4 bytes.
Las funciones malloc, printf, etc. estn en la biblioteca dinmica libc, y la funcin cos en la biblioteca dinmica
libm.
prog1.c
int *f(void)
{
int miv[2000]; int i; int *ret;
for (i = 0; i < 2000; i++) miv[i] = i;
ret = miv;
return ret;
}
char vect[3*1024];
int a1 = 5; int a2 = 15; int a3 = 23;
int main(void)
{
int *w; int *v; int *m; int des; int af; int i;
w = malloc(4*1024);
v = f()+7;
af = *v;
des = creat("dat.txt", 0640);
ftruncate(des, 12*1024 +16);
m = mmap(0, 12*1024+16, PROT_READ|PROT_WRITE,MAP_SHARED, des, 0);
177
close(des);
m[0] = (int)m;
/*A*/ for (i = 1; i < (3*1024+4); i++) m[i] = i;
printf("%f\n", cos(2));
/*B*/ .
.
return 0;
}
prog2.c
int main(void)
{
int *n; int *m; int des; int fr; int p; int* q; int i;
fr = creat("prep.txt", 0640);
ftruncate(fr, 8*1024);
n = mmap(0, 8*1024, PROT_READ|PROT_WRITE, MAP_SHARED, fr, 0);
close(fr);
for (i = 0; i < 2*1024; i++) n[i] = i+200;
des = open("dat.txt", O_RDONLY);
m = mmap(0, 12*1024, PROT_READ, MAP_SHARED, des, 0);
close(des);
q = (int*)(m[0] + 4);
p = *q;
.
.
return 0;
}
a) Determine de forma exacta el nmero mximo de pginas que puede tener cada segmento.
b) Determine en bytes el tamao mnimo exacto que puede tener el segmento de datos de prog1 justo antes del mmap.
c) Determinar el valor que adquiere la variable af del prog1. Indique si considera razonable utilizar dicho valor.
d) Suponiendo que primero ejecuta el prog1 hasta la Lnea B y seguidamente ejecuta el prog2, determinar el valor que adquiere
la variable p de este ultimo programa, indicando si es razonable utilizar dicho valor.
e) Suponiendo que la Lnea A fuese: for (i = 1; i <(3*1024+100); i++) m[i] = i; Cul sera el efecto de la ejecucin de dicha
lnea?
f) Teniendo en cuenta los siguientes datos: Tamao de texto de prog1 = 2500 B. Tamao de texto de libc = 148*1024+35 B. Ta-
mao de datos de libc = 945 B. Tamao de texto de libm = 136*1024 + 563 B. Tamao de datos de libm = 10*1024 + 75 B. Re -
presentar la imagen de memoria del proceso prog1 cuando est ejecutando la lnea B. Identificar cada regin e indicar su
tamao y caractersticas.

Solucin
a) La direccin de 32 bits se descompone en 9 bits para el segmento, 13 bits para la pgina del segmento y 10 bits para el byte de
la pgina. Por tanto habr 213 = 8.192 pginas como mximo en un segmento.
b) Primero hay que destacar que el tamao ha de ser un mltiplo de pgina. Tenemos 3x4 bytes de datos con valor inicial (varia-
bles a1, a2 y a3), 3 pginas de datos sin valor inicial (vector vect) ms el heap de, al menos, 4 pginas. El total es de 8 pginas =
8.192 B como mnimo.
c) La variable af toma el valor 7. La funcin f devuelve un puntero con la direccin del vector miv. A ese puntero se le suma 7,
por lo tanto, v apuntar al sptimo elemento del vector, que tiene un valor = 7, valor que es asignado a la variable af.
Hay que notar que el vector miv ya no existe como tal, puesto que slo es vlido dentro de la funcin f(), pero su contenido
sigue estando presente en la pila, puesto que no se ha invocado ninguna otra funcin. Eso significa que efectivamente af tomar
el valor 7, pero claramente se est cometiendo un error al usar el valor de una variable obsoleta. Por tanto, NO debe utilizarse di -
cho valor.
d) La variable p tomar el valor 201. Para llegar a esa conclusin hemos de considerar lo siguiente: dado que las regiones se
crean por orden en los segmentos 0, 1, 2 , las regiones del proceso prog1 cuando ha ejecutado hasta la Lnea B son las siguien -
tes:
Segmento 0 Texto, que empieza en la direccin 0.
Segmento 1 Datos, que empieza en la direccin 1*2 13*210.
178 Problemas de sistemas operativos
Segmento 2 pila, que empieza en la direccin 2*213*210.
Segmento 3 Texto libc, que empieza en la direccin 3*213*210.
Segmento 4 Datos libc, que empieza en la direccin 4*213*210.
Segmento 5 Proyeccin dat.txt, que empieza en la direccin5*2 13*210.
Segmento 6 Texto libm, que empieza en la direccin 6*213*210.
Segmento 7 Datos libm, que empieza en la direccin 7*213*210.
Por tanto, la direccin de m ser 5*2 13*210.
Por su lado, el proceso de prog2 cuando est ejecutando la lnea p = *q tiene las siguientes regiones (hay que tener en cuenta
que las rutinas de llama al sistema operativo estn en la libc, por lo que esta se carga con el creat):
Segmento 0 Texto, que empieza en la direccin 0.
Segmento 1 Datos, que empieza en la direccin 1*213*210.
Segmento 2 pila, que empieza en la direccin21*213*210.
Segmento 3 Texto libc, que empieza en la direccin 3*213*210.
Segmento 4 Datos libc, que empieza en la direccin 4*213*210.
Segmento 5 Proyeccin prep.txt, que empieza en la direccin 5*2 13*210.
Segmento 6 Proyeccin dat.txt, que empieza en la direccin 6*2 13*210.
El programa prog2 obtiene m[0] = 5*2 13*210, puesto que comparte la regin dat.txt con el prog1, y ste ha puesto ese valor al
comienzo de la regin. Ahora bien, en prog2, esta direccin corresponde a la regin de la proyeccin prep.txt. Por tanto, m[0] + 4
corresponder a la direccin del segundo dato entero de la regin prep.txt, entero que tiene el valor 201.
Ntese que si las regiones se asignasen con otro criterio, la direccin 5*2 13*210 podra corresponder a otra regin o, incluso,
no estar asignada al proceso prog2. Por tanto, es claro que es un error de programacin y que no debera en ningn caso utilizarse
dicho valor.
e) La nueva lnea se ejecuta sin problemas, puesto que la regin tiene 13 pginas, lo que significa que caben 13x1024/4 = 3.328
enteros y el bucle solamente llega hasta el 3.172. Se escriben en memoria los 3.172 enteros. Como la proyeccin es ms pequea
(3x1024 + 4 = 3076), los 96 enteros sobrantes constituyen basura.
f) Las regiones son siempre mltiplo de pgina, por lo que daremos su tamao en pginas. La imagen se refleja en la tabla adjun -
ta.

Segm. Ttulo Tamao Permisos Tamao Compartido/ Fuente


(pginas) privado

0 Texto 3 rx Fijo Compartido Fichero


1 Datos 8 rw Variable Privado Fichero y rellenar 0
2 Pila 8 rw Variable Privado Inicial y rellenar 0
3 Texto libc 149 rx Fijo Compartido Fichero
4 Datos libc 1 rw Fijo Privado Fichero y rellenar 0
5 Proyeccin dat.txt 13 rw Fijo Compartido Rellenar 0
(fichero vaco)
6 Texto libm 137 rx Fijo Compartido Fichero
7 Datos libm 11 rw Fijo Privado Fichero y rellenar 0

El tamao de la regin de pila viene determinado fundamentalmente por la llamada a la funcin f(), dado que di -
cha funcin crea un vector de 2000 enteros ms otros dos enteros en la pila. Esto supone que necesita 2002x4 =
8.008 B. Como 8 pginas son 8.192 B, es posible que los 184 bytes adicionales sean suficientes para el entorno y el
resto de los bloques de activacin de main y f. La pila tendr asignada una pgina adicional, marcada de solo-escri -
tura, para avisar de que se reqiere ms pila.

Problema 3.14 (septiembre 2009)

Sea un sistema con pginas y bloques de e/s de 1 KiB y con agrupaciones de 4 KiB. El sistema tiene dos discos D1 y
D2 con tiempos medio de acceso de 15 y 12 ms respectivamente. Consideraremos que el SO consume 1 ms cada vez
que entra a ejecutar y que los enteros son de 32 bits.
179
El programa prog1 es ejecutado con el argumento /home/pepe/fich1, formando el proceso P1. El fichero fi-
ch1 tiene un tamao de 2.000 B y se encuentra en D1.
a) Analice el cdigo prog1 e indique si aadira algo al mismo, indicando qu y dnde.
b) Suponiendo que el nodo_i del directorio raz est en memoria y que no se producen errores, indicar la secuencia
de accesos a disco que es necesario realizar para ejecutar el servicio open de P1.
c) Suponiendo que el proceso P1, que ejecuta prog1, es el nico proceso en el sistema y que en instante t0 su ejecu -
cin se encuentra justo terminado el mmap, dibujar el cronograma de ejecucin hasta su finalizacin. Considerar
que el printf se realiza sobre el monitor, por lo que no existe tiempo de espera sobre el perifrico. (Reflejar en el
cronograma solamente los tiempos de la funcin Funcion1, del SO y de la e/s).
d) Supondremos, ahora, que el nico proceso es el P2, que ejecuta prog2 sobre un fichero de 1.000 B que se en -
cuentra en D1. Considerando que en el instante t0 su ejecucin se encuentra justo terminado el bucle for, dibujar el
cronograma de ejecucin hasta su finalizacin. (Reflejar en el cronograma solamente los tiempos de la funcin
Funcion2, del SO y de la e/s).
e) De forma similar, el proceso P3 ejecuta prog2 sobre un fichero de 3.000 B que se encuentra en D2. Dibujar el
cronograma a partir del bucle, como en el caso anterior.
f) Ahora supondremos que los tres procesos P1, P2 y P3 estn activos y que P1 es ms prioritario que P2 y ste ms
que P3. Supondremos que en el instante t0 se encuentran en los puntos de ejecucin indicados en las preguntas an-
teriores. Representar el correspondiente cronograma.
/*prog1*/
int main(int argc,char *argv[])
{
int i,desc,size;
int*ptr;
struct stat st;
double total=0.0;

desc=open(argv[1],O_RDONLY);
fstat(desc,&st);
size=st.st_size;
ptr=mmap(0,size,PROT_READ, MAP_SHARED,desc,0);
Funcion1(); /* Funcion1 usa 7 msecs de CPU */
size=size/sizeof(int);
for(i=0;i<size;i++)
total=total+ptr[i];
printf("suma = %g\n",total);
return 0;
}
/*prog2*/
int main(int argc,char *argv[])
{
int i,desc,size;
char*buff;
struct stat st;

desc=open(argv[1],O_WRONLY);
fstat(desc,&st);
size=st.st_size/2;
buff=malloc(size);
for(i=0;i<size;i++)
buff[i]=(char)i;
write(desc,buff,size);
free(buff);
close(desc);
Funcion2(); /* Funcion2 usa 5 msecs de CPU */
return 0;
}
180 Problemas de sistemas operativos

Solucin.-
a) En el cdigo prog1 se echa en falta las dos cosas siguientes:
La devolucin de los recursos que ya no son necesarios. En concreto, debera haber un close despus del
mmap y debera haber un munmap antes del printf.
El tratamiento de los errores, que debe ir detrs de cada llamada a un servicio del SO. Por ejemplo, si el
mmap falla, los accesos ptr[i] darn violacin de memoria.
Comprobar el argumento de llamada.
b) Los accesos al disco, para abrir el fichero /home/pepe/fich1, son los siguientes:
Leer el directorio raz.
Leer el nodo_i del directorio home.
Leer el directorio home.
Leer el nodo_i del directorio pepe.
Leer el directorio pepe.
Leer el nodo_i del fichero fich1.
Dependiendo del tamao de dicho directorio se puede necesitar ms de un acceso. Aqu consideraremos que los
directorios son pequeos, por lo que ser necesita un solo acceso para su lectura.
c) El cronograma se encuentra en la figura 3.17. Es de destacar que el acceso a ptr[i] produce dos fallos de pgina,
puesto que recorren 2.000 B.
d) El cronograma se encuentra en la figura 3.17. Con el write se escriben 1.000/2 = 500 B, lo que afecta a un solo
bloque, que no se escribe entero. Hay que leer primero el bloque y luego hay que escribirlo.
e) El cronograma se encuentra en la figura 3.17. Con el write se escriben 3.000/2 = 1.500 B, lo que afecta a dos
bloques. El primer bloque no hace falta leerlo, porque se escribe entero. El segundo s hay que leerlo. Teniendo en
cuenta que los dos bloques afectados pertenecen a la primera agrupacin del disco, significa que son contiguos, por
lo que basta con una operacin de escritura conjunta para los dos bloques.
f) El cronograma se encuentra en la figura 3.17.
181
Bucle hasta consumir la pgina Bucle hasta terminar
Llamada para printf
Funcion1 Fallo de pgina Fallo de pgina Return
P1
Interrupcin disco Interrupcin disco
Nulo
Lectura de D1 Lectura de D1
SO
10 20 30 40 50 60 70 80
ms

Write Close Funcion2 Return


P2
Interrupcin disco Interrupcin disco
Nulo
Lectura de D1 Escritura en D1
SO
10 20 30 40 50 60 70 80
ms

Write Close Funcion2 Return


P3
Interrupcin disco Interrupcin disco
Nulo
Lectura de D2 Escritura en D2
SO
10 20 30 40 50 60 70 80
ms
Bucle hasta consumir la pgina Bucle hasta terminar Llamada para printf
Funcion1 Fallo de pgina Return
Fallo de pgina
P1 Return
Write Close Funcion2
P2
Write Close Return
Funcion2
P3
Nulo

SO
Interrupcin D2 Interrupcin D1 Interrupcin D1
Interrupcin D1
Interrupcin D2 Interrupcin D1

Lectura de D1 P1 Lectura de D1 P2 Lectura de D1 P1 Escritura en D1 P2

Lectura de D2 Escritura en D2

10 20 30 40 50 60 70 80
ms

Figura 3.17

Problema 3.15 (junio 2010)

Sea un sistema con las siguientes caractersticas:


Memoria virtual con direcciones de 32 bits.
Se utilizan pginas de 4 KiB y se realiza la asignacin de espacio de disco en bloques de 4 KiB.
Los discos estn preformados con sectores de 1 KiB.
Los datos de tipo int ocupan 4 bytes y los de tipo short 2 bytes.
El mapa de direcciones de la imagen de memoria del proceso comienza en la direccin 0, y finaliza en la
1 GiB-1.
Se desea ejecutar una aplicacin que est compuesta por los siguientes mdulos:
Fichero cabecera.h: contiene declaracin de variables globales
Fichero proyecta.c: contiene el cdigo fuente de una funcin que est incluida en una biblioteca libproy
y que se invoca desde el programa principal
Fichero fuente.c: contiene el cdigo fuente de la aplicacin
Fichero datos.txt: contiene datos
Los ficheros de cabecera donde se declaran las funciones del lenguaje y los servicios del sistema utiliza-
dos por la aplicacin (no mostrados en el cdigo).
El contenido de cada uno de los ficheros es el siguiente:
Fichero datos.txt:
182 Problemas de sistemas operativos
abcdefghijklmnopqrstuvxyz0123456789
Fichero cabecera.h:
int a, b=10, c=15, d;
struct tipo_datos{
char a;
char b;
short c;
int d;
char e[5];
}
Fichero proyecta.c:
#include "cabecera.h"
#include <sys/mman.h>
char *proyecta(char * nombre) {
int fd;
char *t=NULL;
fd = open (nombre, O_RDWR);
t=mmap (0, sizeof(struct tipo_datos), PROT_READ|PROT_WRITE, MAP_SHARED, fd,
0);
close(fd);
return (t);
}
Fichero fuente.c:
#include "cabecera.h"
#include <stdio.h>
#include <stdlib.h>
extern char * proyecta(char * nombre);
char literal[36]="abcdefghijklmnopqrstuvxyz0123456789";
int main (void) {
char * aux;
int fd=0, i=5;
aux=malloc(sizeof(struct tipo_datos));
struct tipo_datos p;
printf("p.a %c, p.b %c, p.c %d, p.d %d, p.e %d, p.e[4] %c\n",p.a, p.b, p.c,
p.d, (int) p.e, p.e[4]);
/* Punto A */
aux=proyecta("datos.txt");
p.c=(short) c;
p.d=b;
printf("p.a %c, p.b %c, p.c %d, p.d %d, p.e %d, p.e[4] %c\n", p.a, p.b,
p.c, p.d, (int) p.e, p.e[4]);
/* Punto B */
for (i=3;i<6;i++)
aux[i]= literal[i+22];
munmap(aux,sizeof(struct tipo_datos));
return 0;
/* Punto C */
}
Se supone que el fichero ejecutable fuente y la biblioteca librproy estructuran su contenido de la siguiente for-
ma:
Texto Datos con valor inicial Datos sin valor inicial
fuente 35 KiB ? ?
libproy 165 KiB 16 KiB+? 27 KiB+?
donde ? se corresponde con el valor deducido de la informacin disponible en los mdulos del enunciado.
La biblioteca se montar dinmicamente al invocar el procedimiento.
Conteste a las siguientes preguntas.
183
a) Describir las regiones de la imagen de memoria del proceso al iniciar la ejecucin de la aplicacin, y en los
Puntos A y B, indicando su contenido, niveles de proteccin de la misma, direccin de comienzo y tamao de la re-
gin en pginas en memoria.
b) Indicar los valores impresos en los Puntos A y B.
c) Indicar el contenido del fichero datos.txt en los Puntos A y B.

Solucin
a) Comienzo de la ejecucin:
Regin de cdigo: Cdigo, R-X, 0 - 35210-1, Tam. memoria: 9 pginas.

Regin de datos con valor inicial (DVI): variables b, c y literal: 44 bytes, RW-, 362 10 36210+43 bytes,
Tam. memoria: 1 pgina.
Regin de datos sin valor inicial (DSVI): variables a y d: 8 bytes, RW-, 40210 40210+7 bytes, Tam.
memoria: 1 pgina.
Heap: vaco, RW-, 44210 - , Tam. memoria: 0 o el que asigne por defecto el montador.
Pila (direccionamiento en sentido descendente): Var. de entorno: X bytes, var. locales de la funcin main
(fd, i, p, aux): 25 bytes, retorno main (4 bytes) RW-, 2 20-1 220-X-30, Tam. memoria: 1 pgina (si X+29
< 4096 bytes).
Punto A:
Tanto la pila -invocacin de funciones- como la regin de datos actualizacin de variables- son regiones que se
han utilizado hasta llegar al Punto A, pero por el cdigo propuesto, la nica regin que ha sufrido una modificacin
es el heap si se ha supuesto que inicialmente estaba vaco. El resto de regiones no varan respecto de la situacin ini -
cial.
Heap: se reservan 13 bytes diseccionables desde la variable aux, RW-, 442 10 44210 +12, Tam. memo-
ria: 1 pgina
Punto B:
Mismo comentario que en el Punto A aplicable a todas las regiones que existan en ese momento.
Regin de cdigo de la biblioteca libproy: Cdigo, R-X, Y Y+168210 1, Tam. memoria: 42 pginas.
(Y es una posicin de memoria situada entre la pila y el heap).
DVI de la biblioteca libproy: variables b y c ms las variables con valor inicial definidas en otros mdu -
los de la biblioteca: 16 KiB + 8 bytes, RW-, Y+168210 Y+184210 +7, Tam. memoria: 5 pginas.
DVSI de la biblioteca libproy: variables a y d ms las variables con valor inicial definidas en otros mdu-
los de la biblioteca: 27 KiB + 8 bytes, RW-, Y+188210 Y+215210+7, Tam. memoria: 7 pginas.
Regin del fichero proyectado: 13 primeros bytes del fichero datos.txt, RW-, Z-Z+13,Tam. Memoria: 1
pgina. (Z es una posicin de memoria situada entre la pila y la DVSI de la biblioteca libproy).
Pila: misma situacin que en el Punto A, ms el bloque de activacin de la funcin proyecta. Se supone
que esta accin no modifica el tamao de la regin.
b) Punto A: la variable p es una variable local de la funcin main y est sin inicializar, por lo tanto se almacena
en la pila. Se puede considerar que la variable contendr basura porque el programador no tiene ningn control so-
bre el contenido inicial de las posiciones donde se ha almacenado. Hay que recordar que el lenguaje C no es un len -
guaje interpretado, como puede serlo Java, por lo que las variables locales se alojan en la pila en el bloque de
activacin de la funcin aunque su declaracin se haya realizado en una zona donde ya hay sentencias de cdigo. En
el campo p.e se imprimir la direccin de la variable p.e.
Punto B: Idem que en el Punto A salvo para: p.c 15, p.d 10
c) En ninguno de los dos puntos se llega a modificar el contenido del fichero. Slo se ha modificado en el Punto
C.
184 Problemas de sistemas operativos

Problema 3.16 (junio 2011)

Sea un sistema de memoria virtual con pginas de 4 KiB y con 4 GiB de memoria principal y 8 GiB de swap.
Suponiendo que el SO residente ocupa 1 GiB, indicar el mayor tamao que podra tener la imagen de memoria
de un proceso para los dos casos siguientes:
a. Con preasignacin de swap
b. Sin preasignacin de swap
Sea un sistema con memoria virtual y con pginas de 2 KiB, donde el SO mantiene una sola regin con los datos
con y sin valor inicial ms el heap. Un proceso tiene 512 B de datos con valor inicial, 1.300 B de datos sin valor
inicial y 2 KiB de heap. Supondremos que el primer dato sin valor inicial es: char car[50]. Dicho proceso ejecuta el
siguiente bucle:
for (i=0 ; i<10000 ; i++) {
car[i] = car[i+1];
}
c. Cul es el ltimo valor alcanzado por i?
d. Por qu causa se alcanza dicho valor?

SOLUCIN
a) Con preasignacin de swap significa que toda pgina tiene su ubicacin en swap. Por tanto, un nico proceso que
tuviese todo el swap tendra una imagen de memoria de 8GiB.
b) Sin preasignacin de swap significa que cada pgina reside en memoria o en swap. Por tanto, un nico proceso
que tuviese todo el swap y la memoria libre tendra una imagen de memoria de 8 GiB + 3 GiB = 11 GiB, dado que el
SO ocupa 1 GiB de memoria.
c y d) La regin de datos ms heap tiene que ocupar 2 pginas, es decir 4 KiB. Las posiciones relativas dentro de la
regin son desde la 0 hasta la 4.095. En la primera ejecucin del bucle (i = 0) se acceden a la primera y segunda po -
sicin de car[], es decir, a las posiciones 512 y 513 de la regin, dado que primero estn los datos con valor inicial
que ocupan desde la 0 hasta la 511.
El bucle ejecutara 10.000 veces a menos que se produzca un error. Efectivamente, se producir un error de acce-
so a memoria (segmentation fault) cuando se intente acceder a la posicin 4.096, puesto que nos habremos salido de
la regin y, por tanto, la MMU detectar que se est accediendo a una direccin no asignada al proceso. Por lo tanto,
se recorrern las direcciones desde la 512 hasta la 4.095. El intento de acceso a la posicin 4.096 es la que genera el
error. Con i = 0 se accede a las posiciones 512 y 513. Con i = 4.095 - 512 = 3.583, se accede a la posicin 4.095 y se
intenta la 4.096, producindose el error.

Problema 3.17 (feb 2011)

Los procesos siguientes aparecen en la cola del planificador en un determinado momento:


Trabajos Unidades de Tiempo Prioridad
1 8 1
2 5 4
3 2 2
4 7 3
Los procesos llegan en el orden 1, 2, 3, 4 y la prioridad ms alta es la de valor 1.
Se pide :
a) Obtener los diagramas de tiempo que ilustren la ejecucin de estos procesos usando:
1- Planificacin de prioridades no expulsiva
2- Round Robin (quantum = 2)
3- FCFS (First Come First Served)
185
b) Calcular cual es el algoritmo de planificacin con menor tiempo medio de espera (de todos los procesos).
c) Para la traza formada por las pginas: 4, 2, 1, 5, 4, 2, 3, 4, 2, 3, 4, 2, 1, 5, 3
Calcular el funcionamiento de los algoritmos de reemplazamiento FIFO y LRU con 3 y 4 marcos de pgina, ob -
teniendo el nmero de fallos de pgina producidos en cada uno de los cuatro casos. Comentar los resultados obte-
nidos.
d) Suponiendo que:
Los 4 procesos de la tabla siguen ejecutando una vez cumplida la rodaja de tiempo asignada en la tabla.
El modelo de memoria se basa en memoria virtual.
El gestor de memoria utiliza la tcnica del primer ajuste para asignar huecos libres.
En t=50 ut. el proceso 2 proyecta en memoria un fichero denominado 'datos.txt'.
Indicar las regiones de memoria involucradas en los procesos recogidos en la tabla anterior, sealando los nive-
les de proteccin de las mismas.

Solucin
a) Diagramas de tiempo.
1- Planificacin con prioridades no expulsiva.
08101722
p1 p3 p4 p2
Tiempo medio espera = (0+8+10+17)/4 = 8,75 u.t.
Tiempo medio respuesta = (8+10+17+22)/4 = 14,25 u.t.
2- Planificacin con Roun Robin (quantum = 2)
024681012141617192122
p1 p2 p3 p4 p1 p2 p4 p1 p2 p4 p1 p4
Tiempo medio espera = (13+12+4+15)/4=11 u.t.
Tiempo medio respuesta = (21+17+6+22)/4=16,5 u.t.
3- Planificacin FCFS
08131522
p1 p2 p3 p4
Tiempo medio espera = (0+8+13+15) / 4 = 9 u.t.
Tiempo medio respuesta = (8+13+15+22) /4 = 14.5 u.t.
b) Se observa que el caso con menor tiempo de espera es el de prioridades no expulsiva. Este tiempo resulta pareci -
do al del FCFS. En realidad el ejemplo es muy sencillo y limitado para sacar ninguna conclusin general vlida,
puesto que slo considera cuatro procesos que aparecen todos en el mismo instante.
c) 1) FIFO y 3 marcos de pgina.
PET 4 2 1 5 4 2 3 4 2 3 4 2 1 5 3
M1 4 4 4 5 5 5 3 3 3 3 3 3 3 3 3
M2 2 2 2 4 4 4 4 4 4 4 4 1 1 1
M3 1 1 1 2 2 2 2 2 2 2 2 5 5
FP P P P P P P P P P
2) FIFO y 4 marcos de pgina.
PET 4 2 1 5 4 2 3 4 2 3 4 2 1 5 3
M1 4 4 4 4 4 4 3 3 3 3 3 3 3 5 5
M2 2 2 2 2 2 2 4 4 4 4 4 4 4 3
M3 1 1 1 1 1 1 2 2 2 2 2 2 2
M4 5 5 5 5 5 5 5 5 5 1 1 1
PP P P P P P P P P P P
Comentario: Anomala de Belady.
3) LRU y 3 marcos de pgina.
M1 4 2 1 5 4 2 3 4 2 3 4 2 1 5 3
M2 4 4 4 5 5 5 3 3 3 3 3 3 1 1 1
186 Problemas de sistemas operativos
M3 2 2 2 4 4 4 4 4 4 4 4 4 5 5
M4 1 1 1 2 2 2 2 2 2 2 2 2 3
PP P P P P P P P P P P
4) LRU y 4 marcos de pgina.
M1 4 2 1 5 4 2 3 4 2 3 4 2 1 5 3
M2 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3
M3 2 2 2 2 2 2 2 2 2 2 2 2 2 2
M4 1 1 1 1 3 3 3 3 3 3 3 5 5
M5 5 5 5 5 5 5 5 5 5 1 1 1
PP P P P P P P P P
Comentario: Con FIFO la tasa de fallos decrece al aumentar el nmero de marcos de pgina. Con LRU no.
d) Como no nos dan ms datos, se supone que cada proceso (1, 2, 3, 4) dispondr de su regin de cdigo (R-X), da-
tos con valor inicial (RW-), datos sin valor inicial (RW-), heap (RW-) y pila (RW-). El proceso 2 proyecta un fichero
en memoria, por lo que habr una nueva regin especfica para este fichero (RW-). Muy probablemente, tambin
permanezcan en memoria las regiones de cdigo y datos correspondientes a las bibliotecas que contienen los servi -
cios del sistema y las funciones del lenguaje que utilizan los mencionados procesos.

Problema 3.18 (feb-2011)

Sean dos SO, A y B que tienen un sistema de memoria segmentado (NO paginado). En la implementacin de los ser-
vicios mmap y munmap el SO A emplea la siguiente poltica:
El gestor de memoria utiliza la variable DA, que originalmente apunta a la primera direccin libre despus
de la regin de datos sin valor inicial.
Al atender un servicio mmap con tamao de X bytes, se concede el espacio lgico delimitado por DA y DA +
X -1, y se modifica DA de la siguiente forma: DA DA + X.
Al atender un servicio munmap se intenta recuperar el espacio liberado, como se indica en la figura adjunta,
en la que se parte de la situacin (1) y se llega a las situaciones (2) y (3) al ejecutarse en distinto orden los
munmap 1 y 2. Es decir, DA se decrementa para reutilizar el espacio liberado.
Por su lado el SO B tambin utiliza la variable DA, que originalmente apunta a la primera direccin libre des-
pus de la regin de datos sin valor inicial. Igualmente, al atender un servicio mmap con tamao de X bytes, se con-
cede el espacio lgico delimitado por DA y DA + X 1, y se modifica DA de la siguiente forma: DA DA + X. Sin
embargo, a diferencia del caso anterior, este SO no decrementa nunca DA.
Datos SVI DA Datos SVI
munmap 2 munmap 1
Datos SVI DA mmap1
mmap1
mmap 2 Datos SVI
DA munmap 1 munmap 2 DA Datos SVI
DA mmap 2
(1) (2) (3)
Figura 3.18
Sea el cdigo
/* P1 */
1 struct datos {
2 int a;
3 int b;
4 int c;
5 int v[10];
6 }
7 int main (void) {
8 struct datos *p = NULL;
9 struct datos *t = NULL;
10 int fd, i;
11 fd = open("compartido", O_RDWR);
12 p = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
13 close(fd);
187
14 p->a = 7;
15 p->b = 527;
16 p->c = 9;
17 for (i = 0; i <10; i++)
18 p->v[i] = i*i;
19 printf("%d %d %d %d %d\n", p->a, p->b, p->c, p->v[0], p->v[1]); //X
20 munmap(p, 1024);
21
22 fd = open("/dev/zero", O_RDWR);
23 t = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
24 close(fd);
25 t->a = 1024;
26 for (i = 0; i <10; i++)
27 t->v[i] = 100-i;
28 printf("%d %d %d %d %d\n", t->a, t->b, t->c, t->v[0], t->v[1]);
//Y
29 printf("%d %d %d %d %d\n", p->a, p->b, p->c, p->v[0], p->v[1]);
//Z
30 munmap(t, 1024);
31 }

/* P2 */
1 struct datos {
2 int v[10];
3 int a;
4 int b;
5 int c;
6 }
7 int main (void) {
8 struct datos *p = NULL;
9 int fd;
10 fd = open("compartido", O_RDWR);
11 p = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
12 close(fd);
13 printf("%d %d %d %d %d\n", p->a, p->b, p->c, p->v[0], p->v[1]);
//W
14 munmap(p, 1024);
15 }
a) Para el caso de SO A indicar los valores impresos por cada uno de los printf X,Y,Z y W para la siguiente se-
cuencia de ejecucin: P1 de lnea 1 a lnea 19 inclusive, P2 completo y P1 de lnea 20 al final.
b) Para el caso de SO A indicar los valores impresos por el printf W en el caso de la siguiente secuencia de eje -
cucin: P1 de lnea 1 a lnea 21 inclusive, P2 completo y P1 de lnea 22 al final.
c) Para el caso de SO A indicar los valores impresos por el printf W en el caso de la siguiente secuencia de eje-
cucin: P1 de lnea 1 a lnea 28 inclusive, P2 completo y P1 de lnea 28 al final.
d) Para el caso de SO A indicar los valores impresos por el printf W en el caso de la siguiente secuencia de eje -
cucin: P1 completo y despus el P2 completo.
e) Repetir los casos a, b c y d para el SO B.

Solucin
a) Los valores impresos son los siguientes, de acuerdo al orden de impresin:
Impresin Comentario
X 7 527 9 0 1
W 49 64 81 7 527 El proceso P2 comparte la memoria con P1. Obsrvese que las estructuras utilizadas
en P1 y P2 para acceder a la memoria son distintas
Y 1024 0 0 100 99 Se ha supuesto que los valores no inicializados estn a 0.
Z 1024 0 0 100 99 Dado el funcionamiento del SO A, el segundo mmap devuelve el mismo valor que el
primero, por lo que el puntero p tiene, en este caso, el mismo valor que el t. Esto sig-
nifica que con p se accede a la zona de memoria del segundo mmap.
b), c) y d)
188 Problemas de sistemas operativos
Impresin Comentario
W 49 64 81 7 527 En estos tres casos el valor impreso en W es el mismo, puesto que P2 proyecta el fi-
chero previamente modificado por P1.
e)
Impresin Comentario
X 7 527 9 0 1
W 49 64 81 7 527 El proceso P2 comparte la memoria con P1.
Y 1024 0 0 100 99
Z En este caso el puntero p apunta a direcciones que ya no son vlidas, por lo que al in-
tentar la lectura de p->a se produce un intento de violacin de memoria. El SO matar
al proceso, por lo que no se imprime nada ms.
f), g) y h)
Impresin Comentario
W 49 64 81 7 527 En estos tres casos el valor impreso en W es el mismo, puesto que P2 proyecta el fi-
chero previamente modificado por P1.
i) No sera factible plantear este caso en un sistema paginado pues las direcciones de comienzo de las regiones de -
ben ser mltiplos del tamao de la pgina del sistema.

Problema 3.19 (junio 2011)

a) Detalle el cdigo necesario para que un proceso P1 acceda a un fichero de texto denominado texto.txt mediante
el mecanismo de proyeccin en memoria. El proceso debe invertir el orden de los bytes del fichero, pasando el pri -
mer byte a ser el ltimo y el ltimo se convierte en el primer byte. Cuando el proceso ha completado la reordena -
cin, libera esta regin de memoria, termina, y el fichero texto.txt habr quedado reordenado. Durante la
ejecucin se debe permitir que otros procesos puedan acceder simultneamente a la regin de memoria proyectada.
b) Indique qu le sucede al fichero texto.txt si el proceso P1 lo proyecta en memoria en modo privado. Expli-
que porqu.
c) Detalle el cdigo necesario para que un proceso P2 copie el contenido del fichero texto.txt sobre otro fiche-
ro denominado backup.txt garantizando que son finalmente idnticos. Como puede haber otros procesos en ejecu-
cin accediendo al fichero texto.txt, el proceso P2 debe garantizar, utilizando cerrojos como mecanismo de
sincronizacin, que este fichero no cambia mientras que dura la copia. Del mismo modo, debe garantizar que el fi-
chero backup.txt
d) Suponiendo que el proceso P2 cierra el descriptor de fichero asociado al fichero backup.txt sin haber finali -
zado an el proceso de copia, es posible que otros procesos sobreescriban este fichero mientras continua la ejecu -
cin del proceso P2? En caso de contestacin afirmativa, explique por qu.

Solucin
a)
/* Proceso P1 */
int main(int argc, char ** argv) {
int fd; char *p; char *org; char *pos_final;
int i; struct stat bstat; char aux;
fd=open(texto.txt, O_RDWR); //Abre fichero
fstat(fd, &bstat); //Averigua longitud del fichero
/* Se proyecta el fichero */
org=mmap(NULL, bstat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd,0);
close(fd); //Cierra el descriptor del fichero
p=org;
pos_final=org+bstat.st_size-1;
for (i=0; i<(bstat.st_size/2); i++) { //Bucle de acceso
aux=*p;
*p=*pos_final;
*pos_final=aux;
p++;
189
pos_final--;
}
munmap(org, bstat.st_size); //Se elimina la proyeccin
return 0;
}
b) Al proyectar P1 en modo privado, las modificaciones que se realizan sobre la regin de memoria correspondiente
a la proyeccin del fichero texto.txt no se reflejan en el fichero original.
/* Proceso P2 */
#define TAM_BUFFER 4096;
int main(int argc, char ** argv) {
int fd_ent;
int fd_sal;
char buffer[TAM_BUFFER];
int bytes;
int cod_error;
fd_ent=open(texto.txt, O_RDONLY); //Abre fichero de entrada
fd_sal=creat(backup.txt,0644); //Crea el fichero de salida
cod_error=flock(fd_ent, LOCK_SH); //Se bloquea el cerrojo sobre el fichero de entrada
if (cod_error == -1){
perror(flock texto.txt);
close(fd_ent);
close(fd_sal); return 0;
}
cod_error=flock(fd_sal, LOCK_EX); //Se bloquea el cerrojo sobre el fichero de salida
if (cod_error == -1){
perror(flock backup.txt);
flock(fd_ent, LOCK_UN);
close(fd_ent);
close(fd_sal); return 0;
}
while ((bytes =read(fd_ent, &buffer, TAM_BUFFER)) > 0){
if (write(fd_sal, buffer, bytes) < bytes) {
perror(Escritura);
close(fd_ent);
close(fd_sal); return 0;
};
}
if (bytes < 0) {
perror(Lectura);
close(fd_ent);
close(fd_sal); return 0;
}
flock(fd_ent, LOCK_UN);
flock(fd_sal, LOCK_UN); //Se liberan los cerrojos
close(fd_ent);
close(fd_sal); return 0;
}
d) Al cerrarse el descriptor de fichero, se pierden los cerrojos asociados a ese descriptor, por lo que el fichero se po -
dra sobreescribir por otros procesos.

Problema 3.20 (julio-2011)

Sea una aplicacin compuesta por tres tipos de procesos, que se crean de forma independiente y que se comunican
mediante una regin de memoria compartida. La memoria compartida est formada por 30 registros de 512 bytes.
Cada registro est formado por dos cadenas de caracteres C1 de 64 bytes y C2 de 448 caracteres.
Los procesos P1i generan un dato de tipo D1, buscan un registro libre y rellenan la cadena C1 del mismo. Un
registro est libre si su primer byte C10 contiene el valor 32 (lo que corresponde a un blanco ).
190 Problemas de sistemas operativos
Los procesos P2j buscan un registro con dato D1 y sin dato D2, leen el dato D1 y generan otro dato de tipo D2
que deben almacenar en el mismo registro del que leyeron el dato D1, ocupando la cadena C2. El primer byte de la
cadena C2 de un registro sin dato D2 contiene el valor 32.
Los procesos P3k buscan un registro completo (es decir, la pareja de datos D1-D2) para realizar su funcin. Por
tanto, los procesos P3k debern seleccionar registros que no tengan el valor 32 en el primer byte de sus cadenas C1
y C2. Una vez ledo el registro, el proceso P3k pondr dichos bytes 0 a 32 para indicar que queda totalmente libre.
Cuando un proceso no encuentre un registro con el que trabajar, se quedar bloqueado esperando a que cambie
esta situacin.
Existe un proceso inicial PA cuya misin ser crear aquellos elementos necesarios para que funcionen correcta-
mente los procesos de tipo P1, P2 y P3. Lo primero que hacen dichos procesos al arrancar es comprobar que exis -
ten los elementos necesarios creados por PA. En caso de no encontrarlos terminan con un 1.
El sistema en el que ejecutan dichos procesos es de tipo UNIX con memoria virtual segmentada paginada y con
pginas de 2 KiB.
a) Codificar para cada tipo de proceso PA, P1, P2 y P3 el establecimiento de la regin de memoria compartida.
Consideraremos tres funciones de bsqueda Acceso1, Acceso2 y Acceso3, que usarn, respectivamente, los pro-
cesos de tipo P1, P2 y P3 para comprobar que un registro cumple las condiciones necesarias para que el corres -
pondiente proceso pueda trabajar con l. Dichas funciones se utilizarn para encontrar registros con los que
trabajar. El prototipo de estas funciones es el siguiente:
int Accesoi (struct registro *r); // Devuelve 0 si registro no est disponible.
b) Codificar la funcin Acceso3, indicando si se puede producir un problema de carrera.
c) Codificar las operaciones de acceso al registro seleccionado que realiza el proceso de tipo P2.
El programa P1 se compila dinmicamente dando el siguiente resultado
# size P1
text data bss dec hex filename
14096 480 1024 15600 3CF0 P1
Dicho programa utiliza la biblioteca dinmica libdin
# size libdin
text data bss dec hex filename
3814 2148 2352 8314 207A libdin
Adems, el SO al cargar un programa le asigna, por defecto, una pila de 8 KiB y un heap de unos 3 KiB.
d) Mostrar la tabla de pginas del proceso de tipo P1 justo despus de su carga, antes de haber ejecutado, as
como despus de haber creado y almacenado el primer dato D1 (suponer que no ha ejecutado ningn otro proceso
hasta ese momento).
e) Indicar el valor real que tendr el heap inicialmente.
f) Seleccionar el tipo o los tipos de mecanismos de sincronizacin que considera ms adecuados para esta apli-
cacin, indicando las razones que justifican su seleccin. Definir cada mecanismo que se piensan utilizar, indican -
do la funcin que cumple cada uno.
g) Disear las secciones crticas de los programas PA, P1, P2 y P3.

Solucin
a) El proceso PA crea la regin a compartir
struct registro {
char C1[64];
char C1[448];
};
struct registro *p;
fd=open("/home/pepe/apli", O_CREAT|O_TRUNC|O_RDWR, 0640); //Crea fichero
ftruncate(fd, 30*512); //Tamao para los 30 registros de 512 B
p=mmap(NULL, 30*512, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
close(fd);
191
Todos los procesos de tipo P1, P2 y P3 leen y escriben de la regin compartida.
struct registro *p;
fd=open("/home/pepe/apli", O_RDWR); //Abre fichero
p=mmap(NULL, 30*512, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
close(fd);
Este cdigo debera se completado con los tratamientos de error en los servicios. Por ejemplo, si un proceso Pi
arranca antes de que complete el PA su secuencia puede no tener el fichero /home/pepe/apli disponible, o su tamao
puede ser errneo por lo que el mmap dara error.
b) Una posible solucin sera la siguiente:
int Acceso3 (strut registro *r) {
if ((r->C1[0] == ' ') || (r->C2[0] == ' ')) {
return = 0;
else
return = 1;
}
}
Condiciones de carrera:
Condicin de carrera de procesos de tipo P3 con procesos de tipo P1. No puede haber, puesto que no se
cumple la condicin de r.C2[0] distinto de " ".
Condicin de carrera de procesos de tipo P3 con procesos de tipo P2. Se puede evitar si los procesos P2
escriben C2 de forma que el ltimo carcter que se escriba sea el C2[0].
Condicin de carrera entre procesos de tipo P3. Puede haber si ambos leen C1[0] y C2[0] antes de seguir
avanzando.
Es de notar que los procesos de P1 presentan condicin de carrera entre ellos, as como los de tipo P2.
Se puede evitar la condicin de carrera entre procesos de tipo P1 y P2 haciendo que el ltimo carcter
que escriban los P1 sea el C1[0].
c) Una posible solucin sera la siguiente:
j=0;
while (j == 0) {
for (i = 0; i < 30; i++){ //Recorremos todos los registros
if (Acceso2(&p[i]) == 1){ //Encontramos registro a tratar
j=1;
break;
}
} //Si se completa el if volvemos a repetir
}
<<obtemos p[i].C1 con el que se genera la cadena X de tipo C2>>
strcpy(p[i].C2, X); //Se copia la cadena X en el registro i.
La solucin planteada tiene dos problemas: Presenta problemas de carrera y realiza espera activa, cuando no
hay registros. Estos problemas se deben resolver con mecanismos de sincronizacin.
d) Supondremos que las libreras dinmicas se cargan en el momento de cargar el programa. En caso contrario las
regiones no apareceran hasta que se utilizase la librera. Las regiones son las siguientes:
Texto 7 Pginas
Datos + heap (480 + 1024 + 31.024 = 4.576 B) 3 pginas
Pila 4 pginas
Texto librera 2 pginas
Datos librera (2.148 + 2.352 = 4.500 B) 3 pginas
Es de destacar que ninguna pgina est en memoria, con excepcin de la parte de la pila con el entorno y los
parmetros de llamada del main. La figura 3.19 muestra la tabla de pginas as como el mapa de memoria con la
imagen de memoria del proceso.
192 Problemas de sistemas operativos
Tabla de regiones Tablas de pginas Mapa de memoria
R W X Tamao Direccin R W X P Marco/swap
0 1 0 1 0
Texto 1 0 1 1 1 0 1 0 Texto
2 1 0 1 0
Datos y heap 1 1 0 3 1 0 1 0
4 1 0 1 0
Pila 1 1 0 5 1 0 1 0 Datos y heap
6 1 0 1 0
Texto librara 1 0 1

Datos librara 1 1 0 0 1 1 0 0
1 1 1 0 0 Texto librara
2 1 1 0 0

0 1 1 0 1
1 1 1 0 0
2 1 1 0 0
3 1 1 0 0 Datos librara

0 1 0 1 0
1 1 0 1 0

0 1 1 0 0
1 1 1 0 0
2 1 1 0 0

Pila
Figura 3.19
Antes de haber almacenado el primer dato se proyecta el fichero, por lo que aparece otra regin de 30*512 = 15
KiB, es decir de 8 pginas. Por lo tanto, se crea una nueva entrada en la tabla de regiones y se aade una tabla de p-
ginas de 8 entradas. Al haber ejecutado el programa, parte de las pginas de texto y datos estarn presentes en me-
moria.
e) Los tamaos que tenemos para datos y heap son: 480 + 1024 + 3*1024 = 4576 B. Se tienen que asignar 3 pgi-
nas, por lo que el heap quedar de 3*2*1.024 - (480 + 1024) = 4.640 B.
f) Como se trata de procesos no podemos usar los mutex, al comunicarse por memoria comn tampoco tiene sentido
intentar usar cerrojos de fichero. Por lo tanto se utilizarn semforos con nombre (nos dicen que los procesos se
crean de forma independiente, por lo que no estn emparentados). Necesitamos un semforo por registro que garan-
tice el acceso exclusivo al mismo.
Existe un problema con los procesos de tipo P2, puesto que toman un registro y leen C1 para generar C2. La ge-
neracin de C2 puede llevar mucho tiempo, por lo que el registro puede estar tomado mucho tiempo. Esto puede lle-
var a una alta contencin, si se permite que los procesos de tipo P1 o P3 se queden esperando a registros tomados
por un proceso de tipo P2.
g) El proceso PA que crea la regin de memoria comn, as como los mecanismos de sincronizacin, debe ejecutar
previamente a los otros procesos y no necesita sincronizarse con ellos.
Supondremos las funciones siguientes
int GeneraVectorSemaforos(char *nombre, int n, sem_t *misem[]);
Funcin que genera n semforos con los nombres nombre1, nombre2, etc., e inicializados a 1. Esta funcin la
ejecuta el proceso PA para crear los semforos.
int AbreVectorSemaforos(char *nombre, int n, sem_t *misem[]);
Funcin que que abre n semforos con los nombres nombre nombre1, nombre2, etc. Esta funcin la ejecutan los
procesos de tipo P1, P2 y P3 para abrir dichos semforos.
Se puede modificar el cdigo del apartado c), utilizando el semforo que protege cada registro, quedando el cdi-
go siguiente:
sem_t * misem[30];
AbreVectorSemaforos("MisSemaforos", 30, misem);
j=0;
while (j == 0) {
i=0;
for (i = 0; i < 30; i++){ //Recorremos todos los registros
sem_wait(misem[i]);
if (Acceso2(&p[i]) == 1){ //Encontramos registro a tratar
j=1;
break;
}
sem_post (misem[i]);
i++;
193
} //Si se completa el if volvemos a repetir
}
<<obtemos r.C1 con el que se genera la cadena X de tipo C2>>
strcpy(p[i].C2, X); //Se copia la cadena X en el registro.
sem_post (misem[i]);
El problema de esta solucin es la alta contencin que produce. En cuanto se encuentra un registro tomado, se
queda el proceso bloqueado. Esto es especialmente preocupante dado que los procesos de tipo P2 pueden tener to-
mado un registro un tiempo apreciable.
Una mejora consiste en hacer la comprobacin primero sin semforo, y, en caso de xito, repetirla con semforo.
Con ello disminuimos la contencin, pero no la eliminamos del todo:
sem_t * misem[30];
AbreVectorSemaforos("MisSemaforos", 30, misem);
j=0;
while (j == 0) {
i=0;
for (i = 0; i < 30; i++){ //Recorremos todos los registros
if (Acceso2(&p[i]) == 1){
sem_wait(misem[i]);
if (Acceso2(&p[i]) == 1){ //Encontramos registro a tratar
j=1;
break;
}
sem_post (misem[i]);
}
i++;
} //Si se completa el if volvemos a repetir
}
<<obtemos r.C1 con el que se genera la cadena X de tipo C2>>
strcpy(p[i].C2, X); //Se copia la cadena X en el elemento C2 del registro.
sem_post (misem[i]);
La solucin planteada sigue teniendo el problema de la espera activa cuando no hay un registro libre del tipo re-
querido por el proceso. Una forma de mitigar este problema sera aadiendo un sleep despus del for. El tiempo del
sleep se debera establecer teniendo en cuenta el tiempo durante el cual los procesos de tipo P2 retienen su registro.

Problema 3.21 (mayo 2011)

Complete el cdigo siguiente para recorrer el fichero caracteres.txt proyectndolo en memoria con el servi-
cio mmap. En el fichero caracteres.txt se tiene almacenada una secuencia de letras que no se repiten. Ade-
ms, durante el recorrido del fichero deber comprobar si en la biblioteca dinmica simbolos.so existe una
variable definida con el mismo nombre que la letra leda en caracteres.txt. Para ello, el alumno debe enla-
zar la biblioteca explcitamente en tiempo de ejecucin. Por ltimo, indique al final de la ejecucin el nmero de
bsquedas realizadas con xito.
int main(int argc, char ** argv) {
int fd; char *p; char *org; void *handle; char *error;
double *variable; int i; int cuenta=0; struct stat bstat;
char nombre[2]; // Almacena la letra leda y el carcter \0
fd=open("caracteres.txt", O_RDONLY)); //Abre fichero
// ...
// Bucle de acceso donde se buscan las variables y se lleva la
// cuenta de las bsquedas exitosas
// ...
else cuenta++;
// ...
printf("%d\n", cuenta);
return 0;
}
194 Problemas de sistemas operativos

Solucin
int main(int argc, char ** argv) {
int fd; char *p; char *org; void *handle; char *error;
double *variable; int i; int cuenta=0; struct stat bstat;
char nombre[2]; // Almacena la letra leda y el carcter \0

fd=open("caracteres.txt", O_RDONLY)); //Abre fichero


fstat(fd, &bstat); //Averigua longitud del fichero
/* Se proyecta el fichero */
org=mmap(NULL, bstat.st_size, PROT_READ, MAP_PRIVATE, fd,0);
close(fd); //Cierra el descriptor del fichero
handle = dlopen ("simbolos.so", RTLD_LAZY); //Abre la biblioteca
if (!handle) {
fprintf (stderr, "%s\n", dlerror());
return 1;
}
nombre[1]= "\0";
p=org;
for (i=0; i<bstat.st_size; i++, p++) //Bucle de acceso
{
nombre[0]=*p;
variable = dlsym(handle, nombre); //Se busca la variable
if ((error = dlerror()) != NULL) {
fprintf (stderr, "%s\n", error);
return 1;
}
else cuenta++;
}
dlclose(handle); // Se cierra la biblioteca
munmap(org, bstat.st_size); //Se elimina la proyeccin
printf("%d\n", cuenta);
return 0;
}

Problema 3.22 (septiembre 2011)

Dado el fichero /home/fich con un tamao inicial de 20 KiB, se desea ejecutar sobre un sistema UNIX el si-
guiente fragmento de cdigo:
struct point {
int x;
int y;
};
struct rect {
struct point pt1;
struct point pt2;
};
struct point *pp;
struct rect *pr;
void main(void)
{
int fd;
struct stat bstat;
int i;
void *p;
int pid;
... /* Cdigo de sincronizacin */
fd = open (/home/fich, O_RDWR);
fstat(fd, &bstat);
p= mmap((c_addr_t) 0,bstat.st_size,PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
pid=fork();
if (pid =! 0){ /* Cdigo del padre */
195
pr = p;
for ( i=0; i<=MAX_IT; i++){
... /* Cdigo de sincronizacin con el hijo*/
pr->pt1.x = 7*i;
pr->pt1.y =2*i;
pr->pt2.x = 3;
pr->pt2.y = 9*i;
pr++; /* Avanza hasta el inicio de la siguiente estructura */
... /* Cdigo de sincronizacin con el hijo*/
}
}else{ /* Cdigo del hijo */
pp = p;
for ( i=0; i<=MAX_IT; i++){
... /* Cdigo de sincronizacin con el padre*/
printf(coordenada x:%d,coordenada y:%d\n,pp->x, pp->y)
pp++; /* Avanza hasta el inicio de la siguiente estructura */
... /* Cdigo de sincronizacin con el padre*/
}
}
}

Si el cdigo de sincronizacin hace que ejecute primero el proceso padre, despus el proceso hijo y as sucesiva-
mente, se pide:
a) Indicar qu valores escribe el proceso hijo por pantalla durante las cinco primeras iteraciones.
Justificar razonadamente la respuesta.
b) Escribir el cdigo para realizar la sincronizacin necesaria utilizando el mecanismo de semforos.
c) Si se tiene un sistema que crea una regin independiente para el fichero proyectado, qu ocurre si
MAX_IT vale 2000? Suponga que un entero ocupa 4 bytes.

Solucin
a) En el cdigo del programa se proyecta el fichero /home/fich en memoria como una zona de datos compartida. De
esta forma, tanto el padre como el hijo estn accediendo a la misma zona de memoria y los cambios que realice cual-
quiera de ellos sern vistos por el otro. Adems hay que tener en cuenta que el padre est accediendo al fichero pro -
yectado con una estructura diferente a la del hijo, y que esta estructura tiene exactamente el doble de tamao.
Los valores escritos en pantalla por el hijo son los siguientes:
Iteracin Valor 1 Valor 2
1 0 0
2 3 0
3 7 2
4 3 9
5 14 4

b) No es posible realizar la implementacin con un solo semforo, ya que entonces no se podra tener control total
sobre la ejecucin del padre y del hijo y no se podra realizar la alternancia necesaria. Es por tanto necesario utilizar
dos semforos, uno para controlar la ejecucin del padre y otro para controlar la ejecucin del hijo. El cdigo queda-
ra de la siguiente manera:
/* Inicializacin de los semforos */
sem_t sem_padre, sem_hijo;
sem_init (&sem_padre, 1, 1); /* El padre es el primero en ejecutar */
sem_init (&sem_hijo, 1,0); /* A la espera de que el padre le de paso */
/* Cdigo del padre */
for (...){
sem_wait(sem_padre);

sem_post(sem_hijo);
}
/* Cdigo del hijo */
196 Problemas de sistemas operativos
for (...){
sem_wait(sem_hijo);

sem_post(sem_padre);
}
/* Liberamos los recursos utilizados */
sem_destroy(&sem_padre);
sem_destroy(&sem_hijo);
c) Si el nmero mximo de iteraciones es 2000 implica que el padre estara intentando escribir fuera de la imagen
del proceso, pues el fichero ocupa 20 KiBytes (20.480 bytes) y el padre intenta acceder a partir de la iteracin 1281
a una posicin de la regin proyectada que se encuentra fuera del rango direccionable por el proceso. La MMU al
realizar la traduccin de la direccin errnea generara una excepcin de referencia a memoria invlida y el kernel
mandara una seal SIGSEGV al proceso para matarle.

Problema 3.23 (julio 2013)

Sea un sistema Linux instalado en un instrumento de medida que tiene las siguientes caractersticas fsicas: Memo-
ria RAM de 2 MiB y disco flash de 12 GiB. Dicho instrumento debe tomar cada minuto (aproximadamente) un re -
gistro de medida, registro que ocupa 1KiB. Para el almacenamiento de los registros se utiliza el fichero Reg de
tamao fijo de 8 GiB, que se trata como un almacenamiento circular, es decir, una vez lleno, los nuevos registros
irn sobrescribiendo los ms antiguos.
El programador, partiendo de la funcin int getdata(struct registro *registro); que rellena el
buffer registro de 1 KiB con una medida, establece el siguiente bucle infinito de medida:
struct registro * unregistro;
int fd;
int cont = 0;
fd = creat(Reg, 0600);
while (TRUE) {
unregistro = malloc(sizeof(struct registro));
getdata(unregistro);
write(fd, unregistro, sizeof(struct registro));
cont++;
if (cont >= 8*1024*1024) {
lseek(fd, 0, SEEK_SET);
cont = 0;
}
sleep(60);
}
a) Indicar cuantas medidas podr tomar el proceso antes de que se produzca un error, as como la razn por la
que se produce dicho error. NOTA: tenga en cuenta todos los elementos involucrados en la gestin de la imagen de
memoria del proceso.
b) Rescribir el cdigo anterior proyectando en memoria el fichero Reg.

Solucin
a) El problema con el que nos encontramos es que hay un malloc en el bucle sin su correspondiente free, por lo que
el heap ir creciendo hasta que el sistema operativo no pueda asignar ms memoria al proceso. Para saber cundo
ocurrir esto hay que hacer ciertas suposiciones, pudindose dar los casos siguientes:
Sistema sin memoria virtual. Para el proceso se dispondr, como mximo, del tamao de la memoria principal
menos lo que ocupe el sistema operativo (suponiendo que no hay otros procesos). Suponiendo que el sistema opera-
tivo ocupe 512 KiB nos quedan 1536 KiB para el proceso. Suponiendo que el cdigo ocupa 120 KiB, que otros da-
tos ocupan 1 KiB y que para la pila se han reservado 50 KiB, nos quedara para el heap 1536 120 1 50 = 1365
KiB. Como cada pasada del bucle reserva sizeof(struct registro) = 1 KiB, al cabo de 1366 el sistema operativo dara
un error de falta de memoria.
197
Sistema con memoria virtual. En este caso el lmite nos viene impuesto o bien por que se alcanza el tamao m -
ximo del mapa de memoria o bien porque se alcanza el lmite impuesto por el soporte fsico de la memoria virtual,
es decir, por el swap del disco.
Por un lado tenemos un disco con 8 GiB libres, que se podran dedicar a swap. Por otro lado, la aplicacin es de
un instrumento de medida, por lo que es lgico que incorpore un pequeo procesador, por lo que supondremos que
sus direcciones son de 32 bits. En este caso el mapa de memoria del proceso ser la mitad del mapa de memoria to -
tal es decir 231 = 2 GiB. Algo de ese mapa estar ocupado por el cdigo, otros datos y la pila, por lo que al llegar a
algo menos de 2 Mi medidas el sistema operativo dar error.
b) El programa queda como sigue:
struct registro * unregistro;
int fd;
int cont = 0;
fd = creat(Reg, 0600);
unregistro = malloc(sizeof(struct registro)); //el registro ocupa 1024 B
char *org;
ftruncate (fd, 8*1024*1024*1024); //hacemos que el fichero tenga 8GiB
org=mmap(NULL, 8*1024*1024*1024, PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
while (TRUE) {
getdata(unregistro); //obtenemos una medida
memcpy (org + cont * 1024, unregistro, 1024); //copiamos a memoria
cont++;
if (cont >= 8*1024*1024) {
cont = 0;
}
sleep(60);
}
free(unregistro);
munmap(org, 8*1024*1024*1024);

Problema 3.24 (noviembre 2013)

Sea un sistema UNIX con memoria virtual de pginas de 4KiB, con direcciones de 64 bits y que crea una regin in -
dependiente para el heap.
a) Indicar el tamao mximo que podra tener una imagen de memoria en dicho sistema.
b) Suponiendo que en un instante determinado la regin de heap tiene un tamao de 8 KiB y que est vaca, cal -
cular justificadamente el tamao que tendr la regin del heap despus de ejecutar la secuencia siguiente.
p1 = (char *) malloc (2*1024);
p2 = (char *) malloc (9*1024);
free(p1);
p3 = (char *) malloc (1024);
p4 = (char *) malloc (3*1024);
free(p2);
c) Indicar justificadamente cuantas llamadas al SO se habrn producido en la secuencia anterior.
d) Sea un fichero con registros de 1KiB. La cabecera de cada registro es un entero de 4B que indica su tipo. Es -
cribir una funcin que, utilizando la tcnica de proyeccin de ficheros en memoria, calcule el nmero de registros
de un cierto tipo que hay en el fichero. El prototipo de la funcin ser el siguiente:
int CaculaReg(char *FileName, int TipoRegistro);

Solucin
a) El tamao de la imgen puede vernir limitado por los recursos fsicos de los que se disponga para soportar dicha
imagen (marcos de pgina y paginas de swap) o por el nmero de direcciones disponible. Desde el punto de vista de
las direcciones hay que tener en cuenta que, en principio, una mquina de 64 bits tiene direcciones de 64 bits, lo que
indica que tiene un espacio de direcciones de 2 64 = 16 EiB. Suponiendo que el computador reserva la mitad de ese
espacio para el modo de ejecucin de ncleo, queda un espacio de 8 EiB para el proceso, siendo ste el mayor tama-
198 Problemas de sistemas operativos
o de imagen posible. Evidentemente, es un espacio inmenso, por lo que la imagen vendr realmente limitada por
los recursos fsicos disponibles.
b) La figura 3.20 muestra la evolucin del heap. Se puede observar que ha crecido en dos pginas, por lo que tiene
un tamao de 16 KiB.
c) La figura 3.20 muestra que el heap ha aumentado por dos veces. Por tanto, se han tenido que realizar dos llamadas
al SO. Se ha supuesto que la librera del lenguaje solamente solicita el espacio que necesita, no pidiendo ms espa-
cio por adelantado.
malloc (2*1024) malloc (9*1024) free malloc (1024) malloc (3*1024) free

Figura 3.20

d) Se han planteado dos soluciones alternativas, mediante el uso de punteros y mediante el uso de arrays.
int CaculaReg(char *FileName, int TipoRegistro){
int *p, fd, NumRegist = 0;
struct stat bstat;
fd = open(FileName, O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
if (fstat(fd, &bstat) < 0) return 2;
p = mmap(NULL, bstat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (p == MAP_FAILED) {
perror("mmap");
return 1;
}
close(fd);
for (int i = 0; i< bstat.st_size / 1024; i++) {
if (p[i * 256] == TipoRegistro) {//un registro equivale a 1024/4 = 256 enteros
NumRegist++;
}
}
munmap(p, bstat.st_size);
return NumRegist;
}

int CaculaReg(char *FileName, int TipoRegistro){


int *p, *q, fd, NumRegist = 0, SizeFich;
struct stat bstat;
fd = open(FileName, O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
if (SizeFich = lseek(fd, 0, SEEK_END) < 0) return 2;
p = mmap(NULL, SizeFich, PROT_READ, MAP_PRIVATE, fd, 0);
if (p == MAP_FAILED) {
perror("mmap");
return 1;
199
}
close(fd);
q = p;
for (int i = 0; i< SizeFich / 1024; i++) {
if (*q == TipoRegistro) NumRegist++;
q = q + 256 //un registro equivale a 1024/4 = 256 enteros
}
munmap(p, SizeFich);
return NumRegist;
}

Problema 3.25 (enero 2014) Tiene parte de sincronizacin

Sea un sistema Linux con pginas de 2 KiB y palabras de 64 bits. Tenemos dos funciones llamadas Recept y
Distribut. La funcin Recept es un bucle infinito que se encarga de recibir mensajes a travs de un dispositi-
vo de comunicaciones leyendo de su descriptor de fichero, que se le pasa como un parmetro. El mensaje es de ta -
mao variable (de 64 B hasta 4 KiB) e incluye una cabecera con el destino y el tamao del mensaje. Recept crea
un buffer por mensaje mediante un malloc y, seguidamente, encola la direccin del buffer creado en un buffer cir-
cular llamado bufcir, con capacidad para 512 mensajes.
La funcin Distribut es un bucle infinito que se encarga de enviar, con otro formato, los mensajes previa -
mente encolados en bufcir, a travs del dispositivo de comunicaciones cuyo descriptor de fichero recibe como
parmetro.
El montaje es dinmico y se tienen los siguientes tamaos del programa y de las dos bibliotecas utilizadas:
text data bss dec hex filename
11189 168 9 11366 2c66 Miprogr.o
1719061 11508 11316 1741885 1a943d libc.so
92630 868 8352 101850 18dda libpthread.so
En una primera versin se ejecuta el programa siguiente:
#include <stdio.h>
#include <pthread.h>
void* Recept (void* arg)
{ ...
while (1) {...}
}
void* Distribut (void* arg)
{ ...
while (1) {...}
}
int main(void)
{
pthread_t distrib;
int fdin, fdout;
//Aqu hay un cdigo que abre fdin y fdout
pthread_create(&distrib, NULL, Distribut, (void *)fdout);
Recept((void *)fdin);
}

a) Indicar las regiones de memoria que formarn la imagen de memoria del proceso cuando Recept est ejecu-
tando su bucle.
b) Representar grficamente cmo estar el heap despus de la secuencia siguiente: Se recibe un mensaje de 2 KiB,
se recibe un mensaje de 1 KiB, se enva el primer mensaje, se recibe un mensaje de 3 KiB, se enva el segundo men-
saje y se recibe un mensaje de 1 KiB.
c) Indicar en qu parte del cdigo se debe poner la declaracin del buffer circular bufcir y cmo sera dicha de-
claracin.
200 Problemas de sistemas operativos
d) Considerar ahora que main crea varios threads con Recept y varios con Distribut. Indicar el modelo de
sincronizacin a aplicar entre dichos threads.
e) Plantear, para el caso anterior, el mecanismo de sincronizacin a utilizar, desarrollando el cdigo correspon -
diente.

Solucin
a) Las regiones se muestran en la tabla adjunta. Tenemos dos threads, el inicial (que usa la pila original) y el creado
(que requiere una nueva pila), por lo tanto necesitamos dos pilas. Las pilas requieren una pgina de salvaguarda, que
est asignada como r-- o como ---. De esta forma, si el proceso intenta escribir en dicha pgina es que necesita ms
pila. Por eso, consideramos que, como mnimo, las pilas han de tener dos pginas (una con la pila usada hasta ese
momento y la otra de salvaguarda).
Tamao
Regin Permisos Origen Tipo
N de pginas Fijo/Variable
Texto Miprogr 11.189 B 6 pginas Fijo r-x Ejecutable Compartida
Datos CVI y SVI de 177 B + heap, inicialmente 1 Variable rw- CVI: ejecut. Privada
Miprogr ms heap pgina SVI y heap: 0
Texto libc 1.719.061 B 840 pginas Fijo r-x Librera Compartida
Datos CVI y SVI de 22.824 B 12 pginas Fijo rw- CVI: librer. Privada
libc SVI: 0
Texto libpthread 92.630 B 46 pginas Fijo r-x Librera Compartida
Datos CVI y SVI de 9.220 B 5 pginas Fijo rw- CVI: librer. Privada
libpthread SVI: 0
Pila del thread Dos o ms pginas. La ltima Variable rw- 0 Privada
con permisos r-- (r--)
Pila del thread pri- Dos o ms pginas. La ltima Variable rw- Pila inicial Privada
mario del proceso con permisos r-- (r--) resto 0
b) La figura adjunta muestra la evolucin de la regin de datos que incluye el heap. Los datos con valor y sin valor
inicial ocupan los primeros 177 B, el resto es el heap, que va creciando a medida que se va necesitando. Lgicamen-
te, la funcin Distribut se ha de encargar de liberar la memoria de los mensajes creados por Recept.

Mensaje 4
Heap vaco Mensaje 1 Mensaje 1

Mensaje 2 Mensaje 2 Mensaje 2

Mensaje 3 Mensaje 3 Mensaje 3

c) La declaracin ha de ponerse antes de las funciones, en la zona de datos globales, debajo de los includes. La de-
claracin es:
void * buffdir[512];
d) Se trata de un clsico problema productor consumidor con varios procesos de cada tipo.Los procesos Recept
son los productores y los procesos Distribut son los consumidores.
e) Dado que hay que sincronizar threads, la solucin ms recomendable es la de utilizar mutex y condiciones. La in-
formacin de control que necesitamos es una variable con el nmero de mensajes activos en el sistema. Esta variable
201
ha de ser global. Por otro lado las condiciones que nos encontramos es que buffdir est totalmente lleno, condi-
cin por la que Recept debe esperar o que est totalmente vaco, condicin por la que Distribut ha de esperar.
Una posible solucin es la siguiente:
#define BUFCIR_SIZE 512
#define MAXMENSAJE 4*1024
int NumMensajes, PrimerHueco, PrimerMensaje;
void * buffdir[BUFCIR_SIZE];
pthread_mutex_t MiMutex; // Acceso a seccin crtica
pthread_cond_t no_lleno, no_vacio; // Condiciones de espera

void* Recept (void* arg)


{
void * p;
char buf[MAXMENSAJE]; //
int fdin = (int)arg; //Se convierte el parmtro, que viene en forma de puntero
while(1) {
/*Aqu va el cdigo de lectura del mensaje que suponemos lo deja en buf terminado en '\0'.
Primero se lee la cabecera para conocer el tamao del mensaje. Luego se lee el cuerpo.*/
p = malloc(strlen(buf) + 1) //Creamos el espacio para el mensaje.
strncpy(p, buf, strlen(buf) + 1);
pthread_mutex_lock(&MiMutex);
while(NumMensajes == BUFCIR_SIZE) //Condicin de espera
pthread_cond_wait(&no_lleno, &MiMutex);
buffdir[PrimerHueco] = p;
PrimerHueco = (PrimerHueco + 1) % BUFCIR_SIZE;
NumMensajes++;
pthread_cond_signal(&no_vacio);
pthread_mutex_unlock(&MiMutex);
}
}

void* Distribut (void* arg)


{
void * p;
int fdout = (int)arg; //Se convierte el parmtro, que viene en forma de puntero
while(1) {
pthread_mutex_lock(&MiMutex);
while(NumMensajes == 0) //Condicin de espera
pthread_cond_wait(&no_vacio, &MiMutex);
p = buffdir[PrimerMensaje];
PrimerMensaje = (PrimerMensaje + 1) % BUFCIR_SIZE;
NumMensajes--;
pthread_cond_signal(&no_lleno);
pthread_mutex_unlock(&MiMutex);
//Aqu est el cdigo que enva el mensaje a travs de fdout
free(p); //Liberamos el espacio de memoria que contiene el mensaje
}
}

int main(void)
{
pthread_t distrib;
int fdin, fdout;
//Aqu hay un cdigo que abre fdin y fdout
pthread_mutex_init(&MiMutex, NULL); //Inicializacin
pthread_cond_init(&no_lleno, NULL);
pthread_cond_init(&no_vacio, NULL);
NumMensajes = 0;
PrimerHueco = 0; //Los mensajes los consideramos numerados de 0 a BUFCIR_SIZE - 1
PrimerMensaje = 0;
pthread_create(&distrib, NULL, Distribut, (void *)fdout);
Recept((void *)fdin);
return 0;
202 Problemas de sistemas operativos
}

Problema 3.26 (mayo 2014)

Sea un sistema con memoria virtual que presenta las siguientes caractersticas: pginas de 8 KiB; palabras de 32
bits; el sistema funciona con alineamiento de los datos. Al ejecutar el mandato size, sobre un fichero ejecutable y
sobre una biblioteca, la salida que se obtiene es:
# size mi_programa
text data bss dec hex filename
12096 1080+ ? 8724+ ? 21900+ ? 558C + ? mi_programa
# size mi_biblio
text data bss dec hex filename
645024 16384 + ? 13424 + ? 674832 + ? A4C10 + ? mi_biblio
donde ? se corresponde con el valor deducido de la informacin disponible en el cdigo que est distribuido entre
los siguientes ficheros (suponga que tambin estn incluidos todos los ficheros de cabecera necesarios para las de-
claraciones de las funciones del lenguaje y los servicios del sistema pero esta informacin no se considera para el
clculo de ?):
Fichero cabecera.h:
int a=3, b=5; // Considere que el tipo int requiere 4 bytes
float *c; // Considere que el tipo float requiere 4 bytes
struct tipo_estruct{
short a; // Considere que el tipo short requiere 2 bytes
int b; char c; char d;
double e; // Considere que el tipo double requiere 8 bytes
} var_estruct;
Fichero load_dyn.c:
#include cabecera.h
void *load_dyn(char * nombre) {
void * hd;
hd=dlopen (nombre, RTLD_NOW));
/* Punto A */
return (hd);}
Fichero fuente.c:
#include cabecera.h
extern int load_dyn(char * nombre);
char literal[20]="0123456789=!?$%&/()";
int main (void) {
int fd=0, j=5;
void *hd;
hd=load_dyn("mi_biblio");
c=dlsym(hd,mi_variable); // En la biblioteca, int mi_variable=7;
struct tipo_estruct *vest, *aux;
vest=malloc(sizeof(struct tipo_estruct));
vest->a=(short) a;
vest->d='d';
vest->e=(double) mi_variable;
aux=vest;
/* Punto B */
free(vest);
fd = open (datos.txt, O_RDWR);
vest=mmap (0,sizeof(struct tipo_estruct), PROT_READ|PROT_WRITE,
MAP_SHARED,fd,0); close(fd);
aux=vest;
aux->c=literal[10]; aux->d=literal[11];
munmap(aux,sizeof(struct tipo_estruct));
printf(c: %c, d: %c, e: %f\n,vest->c, vest->d, vest->e);
return 0;
/* Punto C */
}
203
El fichero datos.txt es un fichero ASCII que ocupa 40 B.
Se pide:
a) Indique el espacio ocupado por el tipo de datos tipo_estruct justificando la respuesta.
b) Describa el mapa de memoria del proceso en los puntos A y B. Para cada regin existente, indique el espa-
cio ocupado en B y el nmero de pginas que abarca la regin, sus permisos, si es una regin privada o
compartida, el soporte fsico que tiene la regin y el origen de su contenido. Suponga que a la regin aso-
ciada a la pila se le asignan inicialmente 32 KiB y las variables de entorno ocupan 500 B.
c) Explicar cmo le afecta al heap y a la regin proyectada en memoria la ejecucin de free(vest).
d) Indicar a qu regin pertenece la direccin a la que apunta la variable vest en los puntos A, B y C.
e) Indique los valores impresos al invocar la funcin printf.

Solucin
a) El campo b de tipo int no puede ajustarse al campo a de tipo short al existir alineamiento de los datos y trabajar
con palabras de 32 bits, por lo que queda un hueco de 2 bytes sin poder utilizarse entre estos dos campos. Lo mismo
ocurre entre los campos d y e. En este caso, el hueco es de 1 byte. Por lo tanto, la estructura ocupa 20 bytes. Sola-
mente si se activara la optimizacin del compilador, ste podra reordenar los distintos campos de la estructura para
que el orden de almacenamiento de stos permitiera acceder a cada uno de ellos en un solo acceso a memoria. Una
posible reordenacin sera: struct tipo_estruct{ short a; char c; char d; int b; double e; } y en este caso, la estructura
pasara a ocupar 16 B.
b) Punto A:
Regin de cdigo: 12096 B; 2 pginas; R-X; compartida; fichero ejecutable; fichero ejecutable.
Regin de datos con valor inicial: 1080 B + 8 B (2 int); 1 pgina; RW-; privada; swap; fichero ejecutable.
Regin de datos sin valor inicial: 8724 B + 4 B (float *) + 20 B (tipo_estruct); 2 pginas; RW-; privada;
swap; rellenar con ceros.
Regin de heap: 0 B; 0 pginas; privada; swap; rellenar con ceros.
Regin de pila: a la pila se le asignan inicialmente 32 KiB, que permiten almacenar sin problemas las varia-
bles de entorno, el bloque de activacin de la funcin main con sus variables locales (20 B; 2 int + 3 punte-
ros), el bloque de activacin de la funcin load_dyn, la variable local de esta funcin (4 B de un puntero) y
el bloque de activacin de la funcin dlopen; 4 pginas; privada; swap; rellenar con ceros.
La invocacin del montaje de la biblioteca se ha realizado con la opcin RTLD_NOW, por lo que en ese mo-
mento se monta explcitamente la biblioteca.
Regin de cdigo de la biblioteca: 645024 B; 79 pginas; R-X; compartida; fichero biblioteca; fichero bi-
blioteca.
Regin de datos con valor inicial de la biblioteca: 16384 B + 8 B (2 int); 3 pginas; RW-; privada; swap; fi-
chero biblioteca.
Regin de datos sin valor inicial de la biblioteca: 13424 B + 4 B (float) + 20 B (tipo_estruct); 2 pginas;
RW-; privada; swap; rellenar con ceros.

Si en la solucin se ha considerado el modelo clsico de UNIX con una nica regin de datos que aglutina las
regiones de datos con y sin valor inicial y el heap, se necesitaran 2 pginas (2 pginas > 1080 B + 8 B (2 int) +
8724 B + 4 B (float) + 20 B); privada; swap; los datos con valor inicial se encontraran en el fichero ejecutable y el
resto de datos proceden del mecanismo de rellenar con ceros los marcos de pgina. En este caso, la regin de datos
de la biblioteca se describira: 16384 B + 8 B (2 int) + 13424 B + 4 B (float) + 20 B (tipo_estruct); 5 pginas; RW-;
privada; swap; rellenar con ceros.
Punto B:
Adems de las regiones existentes en el punto A, en el punto B se produce la siguiente modificacin en el
heap:
Regin de heap: 20 B; 1 pginas; privada; swap; rellenar con ceros.
Si en la solucin se ha considerado el modelo clsico de UNIX con una nica regin de datos que aglutina las regio-
nes de datos con y sin valor inicial y el heap, se necesitaran las mismas 2 pginas (2 pginas > 1080 B + 8 B (2 int)
+ 8724 B + 4 B (float) + 20 B + 20 B (heap)); privada; swap; los datos con valor inicial se encontraran en el fiche-
ro ejecutable y el resto de datos proceden del mecanismo de rellenar con ceros los marcos de pgina.
204 Problemas de sistemas operativos
c) Las funciones malloc y free actan solamente sobre el heap, mientras que el fichero proyectado en memoria es
una regin completamente independiente del heap que se gestiona con los servicios mmap y munmap. Al invocar la
funcin free pasando como argumento la variable vest de tipo tipo_estruct, se liberar una zona de memoria en el
heap correspondiente al tamao de sizeof(struct tipo_estruct).
d) La variable vest es una variable local de la funcin main y se almacenar en la pila, pero no est inicializada. Por
lo tanto, en el instante A, al no estar inicializada, se puede considerar que la variable apuntar a posiciones basura
porque el programador no tiene ningn control sobre el contenido inicial de la posicin donde se ha almacenado
vest.
En el B, apunta al heap tras la invocacin de malloc.
Tras la proyeccin del fichero en memoria, recoge el valor de comienzo de la regin creada. Al llegar al instante C,
esa regin ya no existe en la imagen de memoria del proceso tras la invocacin del servicio munmap, por lo que
apunta a una direccin no vlida en el espacio de memoria del proceso.
e) Al invocar la funcin printf se est accediendo a posiciones de memoria que ya no son vlidas en el proceso por-
que se ha eliminado la regin de memoria en la que estaban incluidas, por lo tanto se producir un error de acceso a
memoria y se abortar la ejecucin del proceso.
4
COMUNICACIN Y
SINCRONIZACIN

Problema 4.1 (septiembre 1999)

a) Se desea construir una funcin llamada pizarra, que reciba 3 parmetros:


num_escritores: Nmero de procesos que escribirn en dicha pizarra.
num_lectores: Nmero de procesos que leern los datos de la pizarra. Cuando se lee un dato, se borra de la
misma.
fichero_sal: Fichero de salida donde se copiarn los datos que permanezcan en la pizarra una vez finalizados
todos los procesos lectores y escritores.
La funcin crear num_escritores procesos escritores y num_lectores procesos lectores.
La simulacin de la pizarra se llevar a cabo a travs de un pipe.
Para la escritura de los datos en la pizarra, suponed que existe una funcin, llamada escribir_pizarra( ). Esta
funcin escribe los datos por la salida estndar. Su prototipo es el siguiente:
void escribir_pizarra (void);
Los procesos escritores realizarn una nica invocacin a esta funcin.
De forma anloga, para la lectura de los datos en la pizarra, suponed que existe una funcin llamada leer_piza-
rra( ). Esta funcin lee los datos de la entrada estndar. Su prototipo es el siguiente:
void leer_pizarra (void);
Los procesos lectores realizarn una nica invocacin a esta funcin.
Las escrituras y lecturas de la pizarra se realizan de forma concurrente.
Cuando finalizan todos los procesos lectores y escritores, la funcin pizarra, debe escribir los datos que queden
en la pizarra en el fichero cuyo nombre es fichero_sal, y que se debe crear en el directorio actual.
b) Considerad ahora que las funciones escribir_pizarra y leer_pizarra no escriben ni leen de la salida/entrada
estndar, sino del descriptor de fichero que reciben como parmetro. Los prototipos de las funciones, en este caso,
son:
void escribir_pizarra (int fd);
void leer_pizarra(int fd);
Cmo quedara modificado el cdigo del apartado a)? Reescribidlo teniendo en cuenta estas consideraciones.

205
206 Problemas de sistemas operativos

Solucin
a) La comunicacin se va a llevar a cabo mediante un pipe. Como el nmero de escrituras mximo es menor que
100 (por la restriccin de 100 escritores) y stos escriben un carcter cada uno, estamos seguros de que el pipe no se
va a llenar.
El cdigo correspondiente a este apartado es el siguiente:

/* El tamao es mayor de 100 caracteres, lo que garantiza su almacenamiento */


#define MAX_BUFF 1024

/* Prototipos de las funciones de escritura y lectura en la pizarra */


void escribir_pizarra(void);
void leer_pizarra(void);

/* Funcin pizarra: */
void pizarra (int num_esc, int num_lect, char *fichero_sal)
{

int fd_pipe[2];
int fd_sal;
int i, pid, n_leidos;
char

if (num_esc >= 100)


{
fprintf(stderr,"Nmero de escritores no valido\n");
exit(1);
}

if (pipe(fd_pipe) < 0)
{
perror("pipe");
exit(1);
}

/* Creacin de procesos escritores */


for (i=0; i<num_esc; i++)
{

switch( fork())
{

case -1:
perror("fork");
close(fd_pipe[0]);
close(fd_pipe[1]);
exit(1);
case 0:
close(fd_pipe[0]);
close(STDOUT_FILENO);
dup(fd_pipe[1]);
close(fd_pipe[1]);
escribir_pizarra();
/* El proceso hijo finaliza su ejecucin */
exit(0);
}
}

/* Creacin de procesos lectores */


207
for (i=0; i<num_lec; i++)
{

switch( fork())
{

case -1:
perror("fork");
close(fd_pipe[0]);
close(fd_pipe[1]);
exit(1);
case 0:
close(fd_pipe[1]);
close(STDIN_FILENO);
dup(fd_pipe[0]);
close(fd_pipe[0]);
leer_pizarra();
/* El proceso hijo finaliza su ejecucin */
exit(0);
}
}

/* El padre cierra el descriptor del pipe de escritura, ya que si hay algn proceso intentando leer del pipe
vaco, se quedara bloqueado */
close(fd_pipe[1]);

/* El padre espera por la finalizacin de todos los procesos hijos */


for (i=0; i< (num_esc+num_lec); i++)
{
pid= wait(NULL);
if (pid == -1)
{
perror("wait");
close(fd_pipe[0]);
close(fd_pipe[1]);
exit(1);
}
}

/* Se crea el fichero de salida y se escribe lo que queda en el pipe en el mismo */

fd = creat(fichero_sal, 0600);

if (fd <0)
{
perror("creat");
close(fd_pipe[0]);
exit(1);
}

while (n_leidos = read(fd_pipe[0], buff, MAX_BUFF) > 0)


{
if (write(fd,buff,n_leidos) != n_leidos)
{
perror("write");
close(fd_pipe[0]);
close(fd);
exit(1);
}
}
if (n_leidos < 0)
{
208 Problemas de sistemas operativos
perror("read");
close(fd_pipe[0]);
close(fd);
exit(1);
}

/* Se cierran todos los descriptores y se finaliza el programa */


close(fd_pipe[0]);
close(fd);

}
b) Este apartado slo queda modificado de la siguiente forma:
Los procesos escritores slo tendrn que ejecutar este cdigo:
escribir_pizarra(fd_pipe[1]);
close(fd_pipe[0]);
close(fd_pipe[1]);
exit(0);
Los procesos lectores ejecutarn este cdigo:
leer_pizarra(fd_pipe[0]);
close(fd_pipe[0]);
close(fd_pipe[1]);
exit(0);

Problema 4.2 (abril 2000)

Se desea programar un proceso (secuenciador), que realiza las siguientes operaciones:


Crea un nmero de procesos (P1, P2, P3, ...) determinado por la constante NUM_PROC.
Cada uno de los procesos hijos Pi debe bloquearse hasta que el secuenciador (proceso padre) lo desbloquee.
El orden en el que el proceso secuenciador debe desbloquear los procesos es el de creacin de los mismos
(P1, P2, P3, ...). El desbloqueo se hace de forma secuencial, es decir, en primer lugar se desbloquea al pri-
mer proceso creado (P1); cuando ste finalice y haya pasado cierto tiempo, se desbloquea al segundo (P2) y
as sucesivamente hasta que finalicen todos los procesos hijos.
El proceso secuenciador desbloquea al proceso que le corresponda cuando haya pasado un nmero determi -
nado de segundos (el proceso secuenciador recibe dicho valor como nico parmetro) y siempre que el ante-
rior proceso hijo desbloqueado haya finalizado.
Suponed que los hijos siempre finalizan de forma correcta.
Los procesos hijos deben comunicarse entre ellos. El proceso Pi debe comunicarse con el proceso P(i+1),
donde el orden se define por el momento de creacin del mismo. La comunicacin se lleva a cabo mediante el
envo de los datos generados por una funcin llamada procesar_datos. Esta funcin se define como:
char *procesar_datos (int fd);
Recibe como argumento un descriptor de fichero del cual se leern los datos que se deben procesar. Esta fun-
cin devuelve una cadena de caracteres, que contendr los datos que hay que enviar al siguiente proceso en
la cadena.
El proceso padre (secuenciador) se encargar de escribir un dato inicialmente para que el primer proceso
(P1) pueda generar los datos que le pasar al segundo proceso (P2).
Se pide programar el cdigo del proceso secuenciador as como de los procesos hijos. No hay que implementar
la funcin procesar_datos( ).
La estructura del cdigo de cada uno de los procesos hijos ser:
< Bloqueo esperando a que el proceso padre (secuenciador) lo despierte>
< Llamada a procesar_datos, para que se generen nuevos datos a partir de los datos que le ha comunicado el
proceso anterior en la cadena>
209
< Los datos generados se envan al siguiente proceso en la cadena>
< Termina la ejecucin, sealizndolo si fuera necesario>
Para sincronizar los procesos, utilizad el mecanismo de semforos. Para la comunicacin de los procesos, utili-
zad el mecanismo de pipes. Para la espera del proceso padre, se puede utilizar la funcin sleep( ). Se debe minimi-
zar el nmero de mecanismos a crear por el programa.

Solucin
La figura Error: Reference source not found muestra el esquema de la solucin propuesta.

Secuenciador

P1 P2 P3 . . . Pn

Figura 4.1
int main (int argc, char *argv[])
{
sem_t sem_inicio_ejecucion_hijo[NUM_PROC];

int i, fildes[2];
char *datos;
char c;

/* Creacin de los pipes */


if (pipe (fildes) < 0) {
perror("pipe");
exit(1);
}

/* Inicializacin de los semforos */


for (i=0; i<NUM_PROC; i++) {
sem_init(&(sem_inicio_ejecucion_hijo[i]),1,0);
}

/* El proceso Secuenciador crea NUM_PROC procesos hijos */


for (i=0; i<NUM_PROC; i++) {
if (fork() == 0 ) { /* Proceso hijo */
/* El proceso hijo se bloquea esperando a que le d paso el proceso secuenciador */
sem_wait(sem_inicio_ejecucion_hijo[i]);

/* Procesa los datos y los escribe en el descriptor de escritura del pipe */


datos = procesar_datos(fildes[0]);
write(fildes[1],datos,strlen(datos));

/* Cierre del pipe */


close(fildes[0]);
close(fildes[1]);

/* Se destruye el semforo correspondiente */


sem_destroy(&(sem_inicio_ejecucion_hijo[i]));

/* Sale de forma ordenada */


return 0;
}
}
210 Problemas de sistemas operativos
/* continua el proceso padre */

/* el proceso padre escribe un caracter en el pipe */


write(fildes[1],&c,1);

for (i=0; i<NUM_PROC; i++) {


/* Desbloquea a un proceso hijo en orden */
sem_post(sem_inicio_ejecucion_hijo[i]);

/* Duerme durante el tiempo especificado por la rodaja */


sleep(atoi(argv[1]);

/* Se bloquea esperando que termine el proceso hijo que corresponda */


wait(NULL);
}

/* Cierra el pipe */
close(fildes[0]);
close(fildes[1]);

/* Finaliza */
return 0;

Problema 4.3 (septiembre 2000)

Una aplicacin consta de 20 procesos, P1 a P20, que cooperan entre s de diversas formas, que se describen a con -
tinuacin.
Cooperacin A: Es un esquema productor-consumidor, en el que el cada proceso Pi genera de vez en cuando
una estructura de informacin que tiene un tamao de 1 KiB y que debe utilizar seguidamente otro de los procesos
Pj.
Cooperacin B: En este caso todos los procesos deben utilizar una misma informacin compuesta por 100.000
registros de 200B. Cada proceso, de acuerdo a determinadas solicitudes que van llegando a la aplicacin, de vez en
cuando debe obtener el contenido de 7 registros y debe modificar el contenido de 2 registros. La identidad de los re -
gistros ledos y modificados viene determinada por cada solicitud recibida, siendo stas completamente aleatorias.
Cooperacin C: El proceso Pi genera una informacin de 4 MiB. Cuando la ha completado el proceso Pj ha de
tratar dicha informacin.
Supondremos que el coste en tiempo de procesador de las operaciones de comunicacin y sincronizacin entre
procesos es proporcional al nmero de llamadas al SO, con la excepcin del coste de realizar un paso de mensaje
de ms de 300B, que consideraremos equivalente a 1+ KiB0,1 llamadas al sistema.
Proponer para cada uno de los casos anteriores dos soluciones en pseudocdigo que garanticen el correcto fun-
cionamiento y la coherencia de la informacin. Una solucin utilizar la tcnica de memoria compartida y la otra
utilizar la tcnica de paso de mensajes. Marcar (con un asterisco o subrayado) cada uno de los servicios del SO
utilizados.
Calcular el coste computacional de cada una de ellas y comentar los resultados obtenidos.

Solucin
El problema plantea una serie de situaciones de comunicacin entre procesos y nos pide que comparemos la solu -
cin basada en memoria compartida con la de paso de mensajes. Por tanto, las soluciones que se planteen con ambas
tcnicas han de ser lo ms parecidas posibles.
Por otro lado, en los supuestos A y B la comunicacin se dice que es repetitiva. Ello hace que se tenga que dife -
renciar claramente entre la fase de creacin y eliminacin de los mecanismos de comunicacin y sincronizacin, que
solamente se realizar una vez, y la fase de utilizacin de los mismos, que se utilizar repetidamente. Aunque en el
211
supuesto C no se especifica si la comunicacin es repetitiva o no, se seguir la misma filosofa que para los casos A
y B.
a) Cooperacin A
Consideraremos dos situaciones similares: una regin de memoria comn por proceso, que permita almacenar 1
KiB, o una cola de mensajes por proceso, que permita almacenar un mensaje de 1 KiB. Esta memoria o cola estar
asociada al proceso destinatario de la informacin (Pj). Consideraremos que este KiB contiene una estructura con
campos como edad, altura, etc. Con estas condiciones las soluciones son las siguientes:
Memoria compartida
Los mecanismos necesarios son la memoria comn asociada al proceso Pj, cuyo puntero estar almacenado en
mpj, y dos semforos asociados a esta memoria. Un semforo (lector) tendr por misin bloquear al lector hasta que
el productor no introduzca un dato, y otro semforo (escritor) tendr por misin impedir que el escritor entre a modi-
ficar la memoria si el lector no ha terminado con su acceso.
El trozo del proceso Pi que realiza la comunicacin es el siguiente:
.....
sem_wait (escritor); //se garantiza que solamente se escribe cuando se debe
mpj->edad = 7; //se asignan a los campos de la estructura establecida en la
//memoria compartida los valores deseados.
mpj->altura = 112;
.....
sem_post (lector); //se permite que entre el lector
.....
Mientras que el trozo de cdigo el proceso Pj que realiza la comunicacin es el siguiente:
......
sem_wait (lector); //se garantiza que solamente se lee cuando se debe
miedad = mpj->edad; //se asignan los valores de la estructura establecida
mialtura = mpj->altura; //en la memoria compartida a las variables
.... //deseadas
sem_post (escritor); //se permite que entre el escritor
....
En total cada comunicacin requiere 4 llamadas al SO.
Paso de mensajes
El mecanismo necesario es la cola de mensajes asociada al proceso Pj, cuyo descriptor denominaremos cmpj.
El trozo del proceso Pi que realiza la comunicacin es el siguiente:
....
mq_send (cmpj, &buffer, 1024, 0);
....
Mientras que el trozo de cdigo del proceso Pj que realiza la comunicacin es el siguiente:
....
mq_receive (cmpj, &buffer,1024, 0);
....
En total cada comunicacin requiere 2 llamadas al SO.
b) Cooperacin B
En este caso se presenta una disyuntiva de diseo muy importante, relativa al paralelismo en el acceso al alma -
cn de registros. En efecto, si la proteccin de acceso se extiende a todo el almacn, en cada instante solamente ha -
br un proceso que pueda estar trabajando con l. En el caso de que el tiempo que tarde el proceso en realizar su
funcin sea grande sera ms interesante establecer un mecanismo que permitiera bloquear solamente los registros
que estn en uso. Esta ser la solucin propuesta.
Memoria compartida
Para el caso de memoria compartida consideraremos que hay una nica memoria comn a todos los procesos
(mcomun). Dado que establecer un semforo por registro nos llevara a un nmero exagerado de ellos, se propone
tener otra memoria comn que mantenga la informacin de qu registros estn bloqueados (mbloqueados). Esta me -
moria vendr protegida por un semforo (semaforo), de forma que solamente un proceso pueda accederla en cada
212 Problemas de sistemas operativos
instante para bloquear o desbloquear los registros pertinentes. Ntese que el tiempo que se tarda en realizar este ac-
ceso es muy pequeo, por lo que esta solucin no debe producir una contencin apreciable entre los procesos.
El trozo del proceso Pi que realiza el acceso es el siguiente:
....
sem_wait (semaforo); //se garantiza que solamente uno acceda a mbloqueados
.... //se comprueba que los registros estn libres y, en su caso, se marcan como ocupados. Para los ledos se
//incrementar el nmero de lectores y los que hay que escribir se marcan cerrados para lectura y escritura.
//En caso de no poder tomar control de los registros, hay que cancelar la operacin e intentarla ms tarde.
sem_post (semaforo); //se permite que entre otro proceso

alfa = mcomun[4700].campo + mcomun[3826].campo;


.... //se procesan los registros y se graban los resultados
mcomun[37652].campo = alfa*3;
....
sem_wait (semaforo); //se garantiza que solamente uno acceda a mbloqueados
.... //se liberan los registros utilizados.
sem_post (semaforo); //se permite que entre otro proceso

En total cada comunicacin requiere 4 llamadas al SO en el supuesto de que los registros estn libres.
Cola de mensajes
En el caso de paso de mensajes la solucin pasa por dedicar un proceso a gestionar el almacn de datos. Dicho
proceso tendr asociada una cola de mensajes (cmgestor). Adems, cada proceso cliente Pi tendr su cola de mensa-
jes para las contestaciones. Cada proceso Pi le mandar la peticin con los registros que necesita. El proceso gestor
comprobar que los registros estn disponibles y, en caso afirmativo, los bloquear y enviar un mensaje con sus
contenidos. En caso contrario retendr la peticin hasta que se liberen los registros necesarios.
El trozo del proceso Pi que realiza el acceso es el siguiente:
.... //prepara mensaje de peticin en buffer1
mq_send (cmgestor, &buffer1, sizeof(buffer1), 0);
mq_receive (cmpi, &buffer2, sizeof(buffer2), 0);
.... //procesa los registros y los introduce en buffer3
mq_send (cmgestor, &buffer3, sizeof(buffer3), 0);
Mientras que el trozo de cdigo del proceso gestor es el siguiente:
....
mq_receive (cmgestor, &buffer1, sizeof(buffer1), 0); //espera orden o respuesta
//en caso de ser orden
.... //comprueba que los registro estn libres y los carga en buffer2
mq_send (cmpi, &buffer2, sizeof(buffer2), 0);
//en caso de respuesta
..... //graba los registros enviados y analiza si alguna peticin retenida puede ser ahora atendida.

El nmero de llamadas al sistema es de 6.


c) Cooperacin C
El cdigo es idntico al del caso A, por lo que no se repite.
Comparacin de prestaciones:
Llamadas SO Llamadas equivalentes SO
A memoria comn 4 4
A paso de mensajes 2 2(1+0,1) = 2,2
B memoria comn 4 4
B paso de mensajes 6 2 + 2(1+0,14) + 2(1+0,04) = 6,38
C memoria comn 4 4
C paso de mensajes 2 2(1+ 409,6) = 821,2
Para los casos A y B el coste es muy parecido. Habra que considerar otros factores para seleccionar la mejor so-
lucin al problema.
213
Para el caso C la solucin de paso de mensajes es muy costosa, por la sobrecarga introducida en el envo de gran-
des mensajes.

Problema 4.4
Un pool de threads es un mecanismo para ejecutar de forma concurrente varias tareas, por medio de threads, pero
que adicionalmente proporciona un control y una gestin adecuada para determinados problemas.
Un pool de threads consiste en un conjunto de procesos ligeros (threads) fijo o de tamao dinmico que de for -
ma permanente estn en ejecucin en el proceso. Si existe una tarea pendiente, sta es asignada al thread, si no
existe, el thread no se destruye sino que se deja bloqueado a esperas de una nueva tarea a realizar. Esta estrategia
permite ahorrarse los costes de creacin y destruccin de threads en aplicaciones que crean muchos de estos ele -
mentos para pequeas tareas.
Se propone el desarrollo de una pequea biblioteca que implemente una versin muy elemental de un pool de th-
reads. Esta biblioteca gestionar un nmero de hilos de ejecucin fijo (definidos en fase de inicializacin). La bi -
blioteca dispondr de un mecanismo para solicitar un hilo que se encuentre ocioso (estado IDLE) y asignarle una
tarea. Las tareas sern un conjunto de funciones TRABAJO_A, TRABAJO_B, TRABAJO_C, TRABAJO_D y TRA-
BAJO_E que sern pasadas como parmetro al thread.

Solucin
El cdigo est dividido en tres ficheros de implementacin:
thread_pool.c: Contiene la implementacin de las funciones proporcionadas por la biblioteca. ste
cdigo ser vlido para cualquier problema con la misma taxonoma. Las cabeceras de las funciones es-
tn en el fichero thread_pool.h.
main.c: Cdigo del programa principal, invoca las funciones de la biblioteca. Este cdigo abre un fi -
chero de rdenes que contiene la secuencia de trabajos a realizar, as como periodos en los cuales el siste-
ma esta dormido (no recibe nuevas peticiones de trabajos y est ejecutando las actuales).
jobs.c: Este fichero incluye las implementaciones de cada una de las funciones que representan las po -
sibles tareas que realizar el sistema. Son funciones simples que ejecutan uno o varios bucles anidados y
representan lo que sera posibles trabajos del sistema Las cabeceras de las funciones estn en el fichero
jobs.h. Las implementaciones de estas funciones son unos casos de ejemplo cualquiera.

Fichero main.c

#include <stdio.h>
#include <time.h>
#include <string.h>
#include "thread_pool.h"
#include "jobs.h"

struct thread_pool_t TP; /* Variable pool de threads */

/* Estructura usada para traducir los nombres de los trabajos (tal y como aparecen el fichero de ordenes) a las funciones
que los implementan. */
struct jobs_st
{
char job_name[24]; /* Nombre del trabajo */
void (*job)(int); /* Puntero a funcin */
} job_list[]={
{"TRABAJO_A",trabajo_A},
{"TRABAJO_B",trabajo_B},
{"TRABAJO_C",trabajo_C},
{"TRABAJO_D",trabajo_D},
{"TRABAJO_E",trabajo_E}};

/* Funcion auxiliar que busca el trabajo en la tabla anterior y devuelve su indice en dicha tabla.*/
214 Problemas de sistemas operativos
static int buscar_trabajo(const char* trabajo)
{
int i,tam;
tam=sizeof(job_list)/sizeof(struct jobs_st);

for(i=0;i<tam;i++)
if(!strcmp(trabajo,job_list[i].job_name))
return i;

return -1;
}

int main(int argc, char *argv[])


{
int i,j;
FILE* ordenes;
char linea[80];
char *accion,*aux;
int parametro;
struct timespec tiempo;

if (argc!=3)
{
fprintf(stderr,"Uso: thread_pool <num_threads> <fichero_ordenes>\n");
return 1;
}

/* Inicializacin del pool de threads */


thread_pool_init(&TP,atoi(argv[1]));

/* Apertura del fichero de prdenes */


if((ordenes=fopen(argv[2],"r"))==NULL)
{
fprintf(stderr,"Error al abrir el fichero %s\n",argv[2]);
fprintf(stderr,"Uso: thread_pool <num_threads> <fichero_ordenes>\n");
perror("fopen");
return 1;
}

while(fgets(linea,80,ordenes)!=NULL) /* Mientras no se termine el fichero */


{
/* Se divide cada linea del fichero en accion (al principio) y parametro. */
aux=strchr(linea,' ');
accion=linea;
if(aux!=NULL)
{
*aux='\0';
parametro=atoi(aux+1);
}
else
parametro=0;

/* Si la accion se llama SLEEP, entonces se duerme el proceso durante varios segundos. */


if(!strcmp(accion,"SLEEP"))
{
tiempo.tv_sec=parametro;
tiempo.tv_nsec=0;
fprintf(stdout,
"Durmiendo: No hay ms trabajos en los prximos %d seg.\n",
parametro);
nanosleep(&tiempo,NULL); /* Sleep */
fprintf(stdout,"Recibiendo nuevos trabajos\n");
215
}
else
{
/* En otro caso se busca en la tabla de trabajos la entrada asociada. */
if((j=buscar_trabajo(accion))==-1)
fprintf(stderr,"Trabajo '%s' no es vlido\n",accion);
else
{
/* Se obtiene un thread disponible. Si no hay se espera */
i=get_idle_thread(&TP);
fprintf(stdout,
"Asignando %s(%d) a thread %d\n",
accion,parametro,i);
/* Se asigna el trabajo al thread */
if(assign_job(&TP,i,job_list[j].job,parametro))
fprintf(stderr,"Error asignando trabajo a %d\n",i);
}
}
}

fprintf(stdout,"No hay ms trabajos\n");

/* Una vez finalizada la asignacin de trabajos se espera la terminacin de todos los threads. */
for(j=0;j<atoi(argv[1]);j++)
{
i=get_idle_thread(&TP);
fprintf(stdout,"Liberando thread %d\n",i);
release_idle_thread(&TP,i);
}

return 0;
}

Fichero thread_pool.h

#ifndef _THREAD_POOL_H_
#define _THREAD_POOL_H_

#include <stdlib.h>
#include <pthread.h>

/* Entrada del thread_pool */


struct thread_pool_entry_t
{
pthread_t thread_id; /* ID del thread */

void (*job)(int);/* Puntero a una funcin que recibe un entero como parmetro.
Esta funcin se inicializar */
int param; /* Parmetro del trabajo */
int status; /* Indica el estado del thread */
};

#define THREAD_IS_WORKING 1
#define THREAD_IS_IDLE 2
#define THREAD_IS_ASSIGNED 3
#define THREAD_IS_CANCELLED 4

struct thread_pool_t
{
struct thread_pool_entry_t* thread_pool; /* Vector de entradas */
unsigned int num_threads; /* Nmero de threads */
pthread_cond_t tp_condition; /* Variable condicin que se usara para asignar
216 Problemas de sistemas operativos
trabajos */
pthread_mutex_t tp_mutex; /* Mutex asociado a la condicin anterior */
};

/* thread_pool_init
================
Inicializa la estructura del pool de threads. Arranca todos los threads y los coloca en estado IDLE y a la espera de
trabajos. Esta funcin recibe como argumento el nmero de threads a crear en el pool.*/
void thread_pool_init(struct thread_pool_t* tp,
unsigned int num_threads);

/* get_idle_thread
===============
Esta funcin busca un thread que se encuentre en estado IDLE. Si no hay ningn thread en ese estado espera hasta que
quede uno disponible.

NOTA: Esta es una funcin bloquente.


El indice devuelto estar reservado hasta que se le asigne una tarea o
se invoque a la funcin release_idle_thread que implicara que dicho
thread se libera y ya no podr ser solicitado por otra llamada. */
int get_idle_thread(struct thread_pool_t* tp);

/* assign_job
==========
Esta funcin asigna el trabajo 'job' al thread pasado como indice. Ademas
del puntero a la funcin a ejecutar es necesario pasarle un parmetro.
Si el thread indicado no est en estado IDLE esta funcin devuelve un -1
en otro caso un 0.
*/
int assign_job(struct thread_pool_t* tp,
int index,
void (*job)(int),
int param);

/* release_idle_thread
===================
Libera el thread que se le pasa como parmetro. Este thread sera
cancelado y ya no podr ser usado en sucesivas asignaciones de trabajos.*/
void release_idle_thread(struct thread_pool_t* tp,int index);

#Endif

Fichero thread_pool.c

#include <stdio.h>
#include <signal.h>
#include <strings.h>
#include "thread_pool.h"
/* Implementacin de la biblioteca del pool de threads. */

static void *thread_entry_handler(void* arg)


{
int t;
struct thread_pool_entry_t* myself=NULL;
struct thread_pool_t* tp;
tp=(struct thread_pool_t*)arg; /* Conversin del tipo del argumento. */

/* Se hace una espera para que todos los threads arranquen a la vez. */
pthread_mutex_lock(&tp->tp_mutex);
pthread_mutex_unlock(&tp->tp_mutex);

/* Identifico dentro de la estructura TP mi propia entrada */


217
for(t=0;t<tp->num_threads;t++)
if(tp->thread_pool[t].thread_id==pthread_self())
myself=&(tp->thread_pool[t]);

if(!myself)
{
fprintf(stderr,"Fallo en thread [%ld]\n",pthread_self());
pthread_exit(NULL);
}

while(1)
{
/* Se espera a que se nos asigne un trabajo o se cancele el thread. */
pthread_mutex_lock(&tp->tp_mutex);
while(myself->status!=THREAD_IS_ASSIGNED &&
myself->status!=THREAD_IS_CANCELLED)
pthread_cond_wait(&(tp->tp_condition),
&(tp->tp_mutex));

/* Si el thread se cancela, se libera el semforo y se hace un thread_exist. */


if(myself->status==THREAD_IS_CANCELLED)
{
pthread_mutex_unlock(&tp->tp_mutex);
pthread_exit(NULL);
}

/* En otros casos se cambia de estado al thread y se libera el


semforo. */
myself->status=THREAD_IS_WORKING;
pthread_mutex_unlock(&tp->tp_mutex);

/* Se ejecuta el trabajo. Esto puede llevar tiempo. */


(*myself->job)(myself->param);

/* Se vuelve a tomar el semforo para cambiar el thread de estado y se manda una seal a los que estn esperando
en la variable condicin. */
pthread_mutex_lock(&tp->tp_mutex);
myself->status=THREAD_IS_IDLE;
pthread_cond_broadcast(&(tp->tp_condition));
pthread_mutex_unlock(&tp->tp_mutex);
}

return NULL;
}

void thread_pool_init(struct thread_pool_t* tp,


unsigned int num_threads)
{
int i;

/* Se reserva la estructura del tamao indicado */


tp->num_threads=num_threads;
tp->thread_pool=
(struct thread_pool_entry_t*) malloc(sizeof(struct thread_pool_entry_t)*
num_threads);

/* Se inicializan las variables semafor y condicion */


pthread_cond_init(&(tp->tp_condition),NULL);
pthread_mutex_init(&(tp->tp_mutex),NULL);

/* Se cierra el semforo para hacer que no se disparen los threads hasta que no se ha concluido la inicializacin de
todos. */
218 Problemas de sistemas operativos
pthread_mutex_lock(&tp->tp_mutex);

for(i=0;i<num_threads;i++)
{
tp->thread_pool[i].job=NULL;
tp->thread_pool[i].status=THREAD_IS_IDLE;
}

/* Se crea cada thread. */


for(i=0;i<num_threads;i++)
pthread_create(&(tp->thread_pool[i].thread_id), /* Thread ID */
NULL, /* Sin atributos */
thread_entry_handler, /* Manejador del thread */
(void*)tp); /* Como argumento pasamos el pool de threads */
pthread_mutex_unlock(&tp->tp_mutex);
}

int get_idle_thread(struct thread_pool_t* tp)


{
int i=0;
/* Se solicita el semforo */
pthread_mutex_lock(&tp->tp_mutex);

/* La condicin de espera es que se libere alguno de los threads */


while (1){
for(i=0;i<tp->num_threads;i++)
if(tp->thread_pool[i].status==THREAD_IS_IDLE)
return i;
pthread_cond_wait(&(tp->tp_condition),
&(tp->tp_mutex));
}
}

int assign_job(struct thread_pool_t* tp,


int index,
void (*job)(int),
int param)
{
/* Si el thread no estaba idle se debe a un error */
if(tp->thread_pool[index].status!=THREAD_IS_IDLE)
{
pthread_mutex_unlock(&tp->tp_mutex);
return -1;
}

/* Se asigna funcin y parmetro al thread y se cambia su estado. */


tp->thread_pool[index].job=job;
tp->thread_pool[index].param=param;
tp->thread_pool[index].status=THREAD_IS_ASSIGNED;

/* Se notifica que la variable condicin ha cambiado */


pthread_cond_broadcast(&(tp->tp_condition));
pthread_mutex_unlock(&tp->tp_mutex);

return 0;
}

void release_idle_thread(struct thread_pool_t* tp, int index)


{
void* ret;

/* Se cambia el thread a estado de cancelacin y se notifica de posibles cambios en la condicin*/


tp->thread_pool[index].status=THREAD_IS_CANCELLED;
219
pthread_cond_broadcast(&(tp->tp_condition));
pthread_mutex_unlock(&tp->tp_mutex);

/* Se espera a recoger el estado del thread cancelado */


pthread_join(tp->thread_pool[index].thread_id,&ret);
}

Problema 4.5 (septiembre 2001)

Se desea realizar una comunicacin entre dos procesos con tuberas sin nombre. Se plantean dos posibilidades.
A.- Comunicacin unidireccional entre dos procesos. (Con una nica tubera)
El padre deber leer de la entrada estndar (teclado) y enviarle la informacin al hijo a travs de una tubera
sin nombre. Si el mensaje que recibe el padre por la entrada estndar es FIN, despus de mandrselo al hijo por
la tubera, el padre terminar.
El hijo deber estar leyendo de forma continua del otro extremo de la tubera sin nombre y desviar el mensaje
hacia la salida estndar (monitor). Si recibe el mensaje FIN el proceso hijo terminar correctamente.
B.- Comunicacin bidireccional entre dos procesos. (Con dos tuberas)
El padre ser igual que en el caso anterior, pero antes de volver a leer de la entrada estndar, esperar la re -
cepcin del mensaje LISTO que le deber llegar por una segunda tubera sin nombre que le conecta con el hijo.
El hijo ser igual que el caso anterior, pero inmediatamente despus de escribir el mensaje por la salida estn-
dar, enviar al padre un mensaje de LISTO para recibir ms datos. Este mensaje se lo enviar por la segunda tu-
bera sin nombre.
Se pide:
a) Realizar un esquema de cmo sera el programa A, sealando el sentido de ejecucin, llamadas al sistema,
situacin de las tuberas, etc.
b) dem para el programa B.
c) Escribir el seudo cdigo el programa A.
d) Escribir el seudo cdigo el programa B.

Solucin
a) En la figura Error: Reference source not found aparece el esquema de lo que se pide para una comunicacin uni-
direccional entre dos procesos. El padre crea una tubera para comunicarse con el hijo(llamada pipe()). Seguidamen-
te crea el proceso hijo (llamada fork()). El padre estar en un bucle infinito esperando datos del teclado y
mandndoselos al hijo por la entrada de la tubera. Si se recibe el mensaje FIN por teclado, el padre escribir este
mensaje en la tubera y terminar. El hijo estar en otro bucle infinito leyendo de la salida de la tubera y enviando
lo recibido a pantalla. Si lo que recibe por esta es el mensaje FIN, ste terminar.
inicio

pipe()

Fork() read() mensaje = FIN

Read() mensaje FIN


fin
mensaje FIN write()
write()
mensaje = FIN
fin

Figura 4.2

b) La iniciacin es similar al caso anterior, con diferencia que ahora se crean dos tuberas. El padre estar en un bu -
cle infinito esperando por recibir informacin del teclado. Al igual que en el caso anterior todo lo recibido lo manda-
r por una tubera. En caso de recibir el mensaje FIN del teclado, tambin se mandar este mensaje y se finalizar.
Para el resto de mensajes, el padre esperar, leyendo en otra tubera, la confirmacin de que el hijo ha ledo el men -
220 Problemas de sistemas operativos
saje, esperando por la palabra LISTO. Cuando haya recibido este reconocimiento, el padre volver a leer datos del
teclado.
El hijo, al igual que el caso anterior, espera los datos por la primara tubera, sacar lo recibido por pantalla, salvo
que el mensaje recibido sea FIN. En este caso terminar su ejecucin. Despus de escribir el mensaje por pantalla, el
hijo enviar el mensaje LISTO al padre, escribiendo en la entrada de la segunda tubera. Vase la figura Error: Refe-
rence source not found.
inicio

pipe()

fork() read() mensaje=FIN


read() mensajeFIN
fin
mensaje=LISTO
write()
fin write()
mensaje=FIN LISTO
read()

mensajeLISTO
Figura 4.3

c) El seudo cdigo para el caso de comunicacin unidireccional es:


comienzo
pipe(tubera)
pid = fork()

/* Proceso Padre */

Si pid != 0
close(tuberia[0])
repetir
read (0 , mensaje)
write(tuberia[1], mensaje)
hasta mensaje = FIN
close (tubera[0])
exit(0)
fin-si

/*Proceso hijo */

Si pid = 0
close(tuberia[1])
repetir
read (tubera[0], mensaje)
si mensaje != FIN
write(1, mensaje)
fin-si
hasta mensaje = FIN
close (tubera[0])
exit(0)
fin-si
fin
d) El seudo cdigo para el caso de comunicacin bidireccional es:
comienzo
pipe(tuberia_padre_al_hijo)
pipe(tuberia_hijo_al_padre)
pid = fork()

/* Proceso Padre */

Si pid != 0
close(tuberia_padre_al_hijo[0])
close(tuberia_hijo_al_padre[1])
repetir
read(0, mensaje)
221
write(tuberia_padre_al_hijo[1], mensaje)
si mensaje != FIN
repetir
read(tuberia_hijo_al_padre[0],mensaje2)
hasta mensaje2 = LISTO
fin-si
hasta mensaje = FIN
close(tuberia_padre_al_hijo[1])
close(tuberia_hijo_al_padre[0])
exit(0)
fin-si

/*Proceso hijo */

Si pid != 0
close(tuberia_padre_al_hijo[1])
close(tuberia_hijo_al_padre[0])
repetir
read(tuberia_padre_al_hijo[0], mensaje)
si mensaje != FIN
write(1, mensaje)
write(tuberia_hijo_al_padre[1], LISTO)
fin-si
hasta mensaje = FIN
close(tuberia_padre_al_hijo[0])
close(tuberia_hijo_al_padre[1])
exit(0)
Fin-si

Problema 4.6
Dado el fichero /home/fich con un tamao inicial de 16 KiB, se desea ejecutar sobre un sistema UNIX el siguiente
fragmento de cdigo:
struct point {
int x;
int y;
};
struct rect {
struct point pt1;
struct point pt2;
};
struct point *pp;
struct rect *pr;

int main(void)
{
int fd;
struct stat bstat;
int i;
void *p;
... /* Cdigo de sincronizacin */
fd = open ("/home/fich", O_RDWR);
fstat(fd, &bstat);
p= mmap((c_addr_t) 0,bstat.st_size,PROT_READ | PROT_WRITE, MAP_SHARED, fd,0);
close(fd);
if (fork() =! 0){ /* Cdigo del padre */
pr = p;
for ( i=0; i<=MAX_IT; i++){
... /* Cdigo de sincronizacin con el hijo*/
pr->pt1.x = 3*i;
pr->pt1.y =5*i;
222 Problemas de sistemas operativos
pr->pt2.x = 1;
pr->pt2.y = 7*i;
pr++; /* Avanza hasta el inicio de la siguiente estructura */
... /* Cdigo de sincronizacin con el hijo*/
}
}else{ /* Cdigo del hijo */
pp = p;
for ( i=0; i<=MAX_IT; i++){
... /* Cdigo de sincronizacin con el padre*/
printf("coordenada x:%d,coordenada y:%d\n",pp->x, pp->y)
pp++; /* Avanza hasta el inicio de la siguiente estructura */
... /* Cdigo de sincronizacin con el padre*/
}
}
}
Si el cdigo de sincronizacin hace que ejecute primero el proceso padre, despus el proceso hijo y as sucesiva-
mente, se pide:
a) Indicar qu valores escribe el proceso hijo por pantalla durante las cinco primeras iteraciones. Justificar ra -
zonadamente la respuesta.
b) Escribir el cdigo para realizar la sincronizacin necesaria utilizando el mecanismo de semforos.
c) Si MAX_IT vale 1000 y un entero ocupa 4 bytes, calcular el nmero de veces que se activa el sistema opera -
tivo durante la ejecucin del programa, si se tiene un sistema de memoria virtual con paginacin por de-
manda, sin preasignacin de swap y con un tamao de pgina de 4K. Considerar que en memoria principal
hay espacio suficiente para albergar todas las pginas necesarias, sin necesidad de realizar ninguna expul-
sin.
d) Si se tiene un sistema que crea una regin independiente para el fichero proyectado, qu ocurre si MAX_IT
vale 2000?

Solucin
a) En el cdigo del programa se proyecta el fichero /home/fich en memoria como una zona de datos compartida.
De esta forma, tanto el padre como el hijo estn accediendo a la misma zona de memoria y los cambios que realice
cualquiera de ellos sern vistos por el otro. Adems hay que tener en cuenta que el padre est accediendo al fichero
proyectado con una estructura diferente a la del hijo, y que esta estructura tiene exactamente el doble de tamao.
Los valores escritos en pantalla por el hijo son los siguientes:
Iteracin Valor 1 Valor 2
1 0 0
2 1 0
3 3 5
4 1 7
5 6 10
b) No es posible realizar la implementacin con un solo semforo, ya que entonces no se podra tener control total
sobre la ejecucin del padre y del hijo y no se podra realizar la alternancia necesaria. Es por tanto necesario utilizar
dos semforos, uno para controlar la ejecucin del padre y otro para controlar la ejecucin del hijo. El cdigo queda-
ra de la siguiente manera:
/* Inicializacin de los semforos */
sem_t sem_padre, sem_hijo;
sem_init (&sem_padre, 1, 1); /* El padre es el primero en ejecutar */
sem_init (&sem_hijo, 1,0); /* A la espera de que el padre le de paso */
/* Cdigo del padre */
for (...){
sem_wait(sem_padre);

sem_post(sem_hijo);
223
}
/* Cdigo del hijo */
for (...){
sem_wait(sem_hijo);

sem_post(sem_padre);
}
/* Liberamos los recursos utilizados */
sem_destroy(&sem_padre);
sem_destroy(&sem_hijo);
c) Se deber tener en cuenta que el sistema operativo se activa con las llamadas al sistema y con los fallos de pgina.
Del total de 16 KiB (16.384 bytes) que ocupa el fichero, el padre va a acceder a: 4 enteros * 4 bytes/entero * 1001
iteraciones = 16.016 bytes. Inicialmente, al proyectar el fichero, no se produce ningn fallo de pgina, stos se irn
produciendo segn el padre vaya escribiendo sus valores. Al finalizar la ejecucin habremos accedido a 4 pginas.
En cuanto a llamadas al sistema las que se ejecutan una vez son las siguientes: sem_init y sem_destroy del padre,
sem_init y sem_destroy del hijo, open, fstat, mmap y close del fichero y fork para la creacin del proceso hijo. Las
llamadas que se repiten ms de una vez son sem_wait y sem_post tanto en el padre como en el hijo y se realizan un
total de 1001 veces cada una.
Por tanto, el nmero total de activaciones es:
4 + 9 + 4*1001 = 4017 activaciones
d) Si el nmero mximo de iteraciones es 2000 implica que el padre estara intentando escribir fuera de la imagen
del proceso. La MMU al realizar la traduccin de la direccin errnea generara una excepcin de referancia a me-
moria invlida y el kernel mandara una seal SIGSEGV al proceso para matarle.

Problema 4.7
Se pretende resolver un problema de comunicacin y sincronizacin entre procesos ligeros empleando semforos.
Se trata del tpico problema de lectores y escritores. Se dan las siguientes directrices:
El proceso ligero principal debe crear MAX_LECTORES procesos ligeros lectores y MAX_ESCRITORES
procesos ligeros escritores, que van a competir por un recurso comn. En este caso, el recurso comn es un
simple dato de tipo entero (dato).
Cada proceso ligero lector deber leer el recurso en exclusin mutua frente a escritores. Es decir, mientras
un lector est accediendo al recurso compartido ningn proceso ligero escritor podr acceder a este.
De manera contraria, si el recurso est ocupado por un escritor, el lector deber esperar que el escritor ter -
mine. Sin embargo el proceso ligero lector no debe mantener exclusin mutua frente a otros procesos ligeros
lectores, ya que la lectura no es destructiva.
El proceso ligero escritor debe mantener la exclusin mutua frente a procesos lectores, como ya se dijo, y
tambin frente a procesos escritores.
Se plantea en un primer momento el siguiente cdigo, pero la experiencia comprueba que es errneo:

01 int dato = VALOR_INICIAL; /* recurso */


02 sem_t semaforo; /* acceso a dato */
03
04 int main(void)
05 {
06 pthread_t th_lector[MAX_LECTORES], th_escritor[MAX_ESCRITORES];
07 int i;
08
09 sem_init(&semaforo, 0, 1);
10 for (i=0; i<MAX_LECTORES; I++)
11 pthread_create(&th_lector[i], NULL, Lector, NULL);
12 for (i=0; i<MAX_ESCRITORES; I++)
13 pthread_create(&th_escritor[i], NULL, Escritor, NULL);
14
224 Problemas de sistemas operativos
15 for (i=0; i<MAX_LECTORES; i++)
16 pthread_join(th_lector[i], NULL);
17 for (i=0; i<MAX_ESCRITORES; i++)
18 pthread_join(th_escritor[i], NULL);
19
20 /*Cerrar todos los semforos*/
21 sem_destroy(&semaforo);
22
23 return 0;
24 }
25
26 void Lector(void) /* cdigo del lector */
27 {
28 sem_wait(&semaforo);
29 printf("%d\n", dato); /* leer dato y mostrar el valor*/
30 sem_post(&semaforo);
31 pthread_exit(0);
32 }
33
34 void Escritor(void) /* cdigo escritor */
35 {
36 sem_wait(&semaforo);
37 dato = dato + 2; /* modificar recurso */
38 sem_post(&semaforo);
39
40 pthread_exit(0);
41 }
NOTA: Supngase VALOR_INICIAL, MAX_ESCRITORES, MAX_LECTORES cualquier valor.
Se pide:
a) Qu fallo tiene el cdigo?
b) Qu instrucciones daras a un informtico para que pueda corregirlo?
c) Escribir el cdigo corregido con el consejo que propones. Solamente escribe las lneas de cdigo que hay
que aadir, indicando entre que lneas deben ser colocadas.
d) Escribir el cdigo del escritor realizado con con mutex y variables de condicin.

Solucin
a) El fallo es que est haciendo exclusin mutua entre lectores, y el enunciado dice explcitamente que no debe ha -
cerse. Varios lectores deberan poder acceder simultneamente a la seccin crtica.
b) Ya que se est inicializando la variable semforo a 1, es decir se est empleando como un mutex, una posibilidad
sera que se establezca un contador de procesos lectores. Este contador debe ser accesible entre todos los procesos li-
geros lectores, y por tanto debe ser una variable global. Antes de acceder al recurso, y por tanto, de intentar decre -
mentar la variable semforo, el proceso ligero lector deber incrementar este contador, y cuando se abandone el
recurso, incrementar el semforo, deber decrementar este contador.
Mientras el contador sea distinto de cero, indicar que hay lectores, y por tanto la variable semforo no deber
ser incrementada. El recurso es de los lectores.
Cuando un lector termine, si el nmero de lectores es cero, entonces ste debe liberar el recurso compartido, para
permitir que puedan entrar escritores.
Ntese que esta variable contador de lectores, es una variable global entre todos los lectores, ya que todos la pue -
den modificar. Cualquier operacin de modificacin debe ser atmica, y por tanto hace falta otro semforo binario o
mutex para asegurar esto.
c) Las lneas de cdigo que faltan seran las siguientes:
En la lnea 3 inicializo las dos variables globales que voy a usar.
int n_lectores = 0; /* num. Lectores */
sem_t sem_lec; /* acceso n_lectores */
225
En la lnea 9
sem_init(&sem_lec, 0, 1);
En la lnea 22
sem_destroy(&sem_lec);
Antes ocupar el semforo (lnea 28) me aseguro que no hay otros lectores, si los hay ya tienen el semforo
sem_wait(&sem_lec);
n_lectores = n_lectores + 1;
if (n_lectores == 1)
Sem_wait(&semaforo); /* Se bloquea si est ocupado por el escritor */
sem_post(&sem_lec); /* Se libera el semforo de lectores */
printf("%d\n", dato); /* Leer dato y mostrar el valor */

Antes de liberar el semforo (lnea 30) me aseguro que no quedan lectores. Si quedan no libero el recurso.
sem_wait(&sem_lec);
n_lectores = n_lectores - 1;

if (n_lectores == 0)
sem_post(&semaforo); /* Libero el recurso */
sem_post(&sem_lec);
pthread_exit(0);
d) Sera el mismo que las lneas 34 a 41, pero sustituyendo sem_wait() por pthread_mutex_lock() y sem_post() por
pthread_mutex_unlock().

Problema 4.8 (septiembre 2003)

Se desea implementar un programa (multi_grep) que se encargue de llevar a cabo la bsqueda de un determinado
patrn en mltiples ficheros de texto. Su sintaxis es la siguiente:
$ multi_grep patron fichero1 fichero2 ... ficheroN
Para cada uno de los ficheros, el programa multi_grep debe crear un thread diferente, cada uno de los cuales
debe buscar el patrn sobre su correspondiente fichero. La bsqueda del patrn sobre los ficheros debe hacerse en
paralelo.
Cuando un thread encuentra el patrn buscado en alguna lnea, debe insertar dicha linea en una estructura en
memoria compartida por todos los threads. Esta estructura no debe modificarse de forma concurrente. Para llevar
a cabo la insercin, se debe utilizar la funcin insertar_linea (), que tiene la siguiente sintaxis:
void insertar_linea (char *nombre_fich, int num_linea, char *linea);
Esta funcin recibe el nombre del fichero, el nmero de lnea y la lnea donde se ha encontrado un determinado
patrn. La funcin insertar_linea() no debe utilizarse de forma concurrente, puesto que modifica la estructura que
almacena las lneas encontradas. La funcin insertar_linea no hay que implementarla, slo utilizarla de forma co-
rrecta.
Al finalizar la bsqueda del patrn en todos los ficheros, el programa deber imprimir por la salida estndar to-
das aquellas lneas en las cuales aparece el patrn buscado. Para ello, se podr utilizar la funcin
imprimir_lineas(), que tiene la siguiente sintaxis:
void imprimir_lineas ();
y que escribe por la salida estndar y en orden todas las lneas almacenadas en la estructura.
Para buscar un patrn sobre un determinado fichero, se pueden utilizar las siguientes funciones, que no hay
que implementar:
char * leer_linea ( int fd);
Esta funcin lee lnea a lnea un fichero cuyo descriptor es fd. Devuelve NULL cuando llega al final de fichero.
int esta_patron (char *patron, char *linea);
Esta funcin devuelve 1 si el patrn se encuentra en la lnea y 0 si no se encuentra.
226 Problemas de sistemas operativos
No considere los casos de error. Si necesita utilizar algn mecanismo de sincronizacin, utilice el mecanismo de
mutex.
Se pide:
a) Implementar el programa multi_grep, as como el cdigo asociado a cada uno de los threads que tratan los
ficheros, utilizando las funciones descritas.
b) Sera equivalente la solucin si en lugar de utilizar mutex se utilizaran semforos?
c) Implementar un nuevo programa coincidencias que bsque las lneas en las que aparece el patrn
mensaje en los ficheros data1.txt, data2.txt y data3.txt y contabilice el nmero de ellas,
escribindolo en el fichero num_ocurrencias.dat. Para ello utilice:
El ejecutable multi_grep visto anteriormente.
El mandato wc con el argumento l. Dicho mandato cuenta el nmero de lineas que recibe por la entrada
estndar y devolve el valor por la salida estndar.
La salida debe ser escrita en el fichero anteriormente indicado.
Nota: No modifique el cdigo de multi_grep, utilcelo con la sintaxis indicada al comienzo del ejercicio (su-
pngase que slo se dispone del fichero binario).

Solucin
a)
#include <pthread.h>
/* resto de includes */
struct argumentos {
char *patron;
char *nombre_fichero;
};
pthread_mutex_t mutex;

/* Programa multi_grep */
int main (int argc, char *argv[]) {
pthread_t *thread_id;
struct argumentos *arg;
int i;

/* Comprobacin de los argumentos */


if (argc < 3) {
fprintf(stderr, "Error. Uso: multigrep patron f1 [f2].. [fn]\n");
exit(1);
}
/* Reservamos espacio para los identificadores de los threads */
thread_id = (pthread_t *) malloc ((argc-2)*sizeof(pthread_t));

/* Inicializamos el mutex */
pthread_mutex_init(&mutex, NULL);

/* Creamos los threads */


for (i=2; i<argc; i++) {
arg = (struct argumentos *) malloc(sizeof(struct argumentos));
arg.patron=argv[1];
arg.nombre_fichero=argv[i];
pthread_create (&(thread_id[i-2]), NULL, simple_grep, &arg);
}

/* Esperamos a la finalizacin de los threads */


for (i=0; i<argc-2; i++) {
pthread_join(thread_id[i],NULL);
}
/* Destruimos el mutex */
227
pthread_mutex_destroy(&mutex);

/* Imprimimos las lneas y salimos */


imprimir_lineas();
return 0;
}

/* Cdigo de los threads */


void simple_grep (struct argumentos *arg){
int fd;
char *linea;
int num_linea = 0;

fd = open(arg->nombre_fichero, O_RDONLY);
linea = leer_linea(fd);
while (linea != NULL) {
num_linea++;
if (esta_patron(arg->patron, linea)) {
/* Entrada en la seccin crtica */
pthread_mutex_lock(&mutex);
insertar_linea(arg->nombre_fich, num_linea, linea);
/* Salida de la seccin crtica */
pthread_mutex_unlock(&mutex);
}
linea = leer_linea(fd);
}
free(arg);
pthread_exit(NULL);
}
b) Dado que utilizamos un mutex para crear una seccin crtica, el uso de un semforo binario sera equivalente.
Este semforo debera ser inicializado a 1. No obstante, habra que modificar las directivas correspondientes
(sem_wait y sem_post, para la entrada y la salida de la seccin crtica respectivamente). Por otro lado, los mutex
suelen ser ms apropiados para threads que los semforos.
c)
/* Programa coincidencias */
int main(void){
int fd, pfd;
int pid;

pipe(pfd);
pid=fork();

switch (pid){
case 1:
perror("fork");
exit(1);
case 0:
close(pfd[0]);
close(1);
dup(pfd[1]);
close(pfd[1]);
execlp("multi_grep", "multi_grep", "data1.txt", "data2.txt",
"data3.txt", NULL);
perror("exec");
exit(1);
default:
close(pfd[1]);
close(0);
dup(pfd[0]);
close(pfd[0]);
228 Problemas de sistemas operativos
close(1);
fd=open("num_ocurrencias.dat", O_WRONLY | O_CREAT | O_TRUNC, 0600);
execlp("wc", "wc", "-l", NULL);
perror("exec");
exit(1);
}
}

Problema 4.9 (2004)

Sea el cdigo adjunto, que es un esqueleto incompleto y/o errneo de un servidor genrico que presta su servicio a
procesos remotos va sockets.
/* ServidorGenerico.c */
/* 1*/ int main(int argc, char *argv[])
/* 2*/ {
/* 3*/ int sd, cd, size;
/* 4*/ struct sockaddr_in s_ain, c_ain;
/* 5*/
/* 6*/ sd = socket( ???? );
/* 7*/
/* 8*/ bzero((char *) &s_ain, sizeof(s_ain));
/* 9*/ s_ain.sin_family = AF_INET;
/*10*/ s_ain.sin_addr.s_addr = INADDR_ANY;
/*11*/ s_ain.sin_port = htons(atoi(argv[1]));
/*12*/ bind(sd, (struct sockaddr *) &s_ain, sizeof(s_ain));
/*13*/
/*14*/ listen(sd, 5);
/*15*/ while (1) {
/*16*/ size = sizeof(c_ain);
/*17*/ cd = accept(sd, (struct sockaddr *) &c_ain, &size);
/*18*/
/*19*/ ServidorDedicado(sd, cd);
/*20*/ }
/*21*/ }
Conteste a las siguientes preguntas para completar y/o corregir los errores presentes en este cdigo.
a) En vista del resto del cdigo presentado, cul sera la versin de la llamada a socket de la lnea 6?
Considere ahora las siguientes estrategias alternativas para el diseo de la funcin ServidorDedicado:
Estrategia 1, basada en Procesos Pesados:
El proceso ServidorGenerico original crea un proceso hijo por cada cliente para que preste un
determinado ServicioConcreto. Habr mltiples procesos, con un nico hilo de ejecucin cada
uno. El proceso padre no espera a que terminen sus hijos.
Estrategia 2, basada en Procesos Ligeros:
El proceso ServidorGenerico original crea un hilo de ejecucin por cada cliente para que le pres-
te un determinado ServicioConcreto. Habr un nico proceso (el original) con mltiples hilos de
ejecucin. El hilo principal no espera a que terminen los dems hilos.
b) Suponiendo que el ServicioConcreto que finalmente se desea implementar sea equivalente al telnet
(terminal remoto), va el cual un usuario remoto pueda trabajar en el sistema interactuando con un intrpre -
te de mandatos, cul de las dos estrategias de diseo se adaptara mejor a este uso?
Sean las siguientes implementaciones de las estrategias de diseo indicadas anteriormente:
/* Implementacin 1: Procesos Pesados */
/* 1*/ void ServidorDedicado(int sd, int cd)
/* 2*/ {
/* 3*/ switch (fork()) {
229
/* 4*/ case -1:
/* 5*/ perror("Servidor");
/* 6*/ exit(1);
/* 7*/ case 0:
/* 8*/ close(sd);
/* 9*/ ServicioConcreto(cd);
/*10*/
/*11*/
/*12*/ default:
/*13*/ close(cd);
/*14*/ }
/*15*/ }

/* Implementacin 2: Procesos Ligeros */


/* 1*/ void * HiloServicio(void * cdp)
/* 2*/ {
/* 3*/ int cd = *(int *)cdp;
/* 4*/ free(cdp);
/* 5*/ ServicioConcreto(cd);
/* 6*/ close(cd);
/* 7*/ return NULL;
/* 8*/ }
/* 9*/ void ServidorDedicado(int sd, int cd)
/*10*/ {
/*11*/ pthread_t th;
/*12*/ int * cdp = malloc(sizeof(int));
/*13*/ *cdp = cd;
/*14*/ pthread_create(&th, NULL, HiloServicio, cdp);
/*15*/ }

c) Cul de las dos implementaciones es incorrecta (presenta errores de concepto)?


d) Escriba el cdigo de la funcin ServicioConcreto para que implemente un servicio de eco, donde cada
cliente remoto reciba de vuelta cada byte de informacin que enve al servidor.
En un instante determinado de su ejecucin el ServidorGenerico tiene:
7 clientes que fueron ya completamente servidos y terminaron.
5 clientes que estn siendo servidos actualmente, es decir, hay un ServicioConcreto ejecutando
para cada uno.
4 clientes que estn intentando obtener servicio, pero su conexin no ha sido aceptada todava.
e) Si ServidorDedicado sigue la implementacin 2, de procesos ligeros, cul ser el nmero total de des-
criptores de fichero asociados a sockets (incluido el sd original) en uso en el proceso ServidorGeneri-
co en dicho instante determinado?
A partir de este momento, considere exclusivamente la estrategia 2, de procesos ligeros.
/* ServicioConcreto */
001: void ServicioConcreto(int cd) {
002: /* ...
... ...
017: ... */
018: BEGIN REGION_CRITICA 0
019: BEGIN REGION_CRITICA 1
020: cnt = cnt + 1;
021: first_cmd = cnt;
022: END REGION_CRITICA 1
023: /* ...
... ...
105: ... */
106: BEGIN REGION_CRITICA 2
107: colocar en la cola la peticin etiquetada con first_cmd
230 Problemas de sistemas operativos
108: END REGION_CRITICA 2
109: END REGION_CRITICA 0
110: /* ...
... ...
218: ... */
219: BEGIN REGION_CRITICA 0
220: BEGIN REGION_CRITICA 1
221: cnt = cnt + 1;
222: second_cmd = cnt;
223: END REGION_CRITICA 1
224: /* ...
... ...
300: ... */
301: BEGIN REGION_CRITICA 2
302: colocar en la cola la peticin etiquetada con second_cmd
303: END REGION_CRITICA 2
304: END REGION_CRITICA 0
305: return;
306: }
La funcin ServicioConcreto representa el grueso del proceso de servicio. Esta rutina utiliza y modifica
varias variables globales. Para evitar que los mltiples hilos concurrentes provoquen valores incoherentes de estas
variables, consideraremos "regin crtica" cada fragmento de cdigo donde se accedan dichas variables.
f) Cules son los servicios POSIX del sistema operativo ms apropiados para la defensa de este tipo de re-
gin crtica?
Si en un determinado instante T de la ejecucin de este programa en una mquina monoprocesador: el valor
de cnt es 5 y hay tres hilos de dentro ServicioConcreto en los puntos thA:017, thB:107 y thC:218.
g) Si, a partir del instante T, continuase ejecutando el hilo A, hasta qu lnea podra ejecutar (o dnde se blo-
queara)?
h) Si, a partir del instante T, continuase ejecutando el hilo B, hasta qu lnea podra ejecutar (o dnde se blo-
queara)?
i) Si, a partir del instante T, continuase ejecutando el hilo C, hasta qu lnea podra ejecutar (o dnde se blo -
queara)?
j) Si, a partir del instante T, primero ejecutase A (hasta donde pudiese) luego B y luego C, qu valor habra
tomado la variable compartida cnt?

SOLUCIN
a) Dado que el cdigo de ServidorGenerico contiene un bucle donde se utiliza la llamada accept, y dado que esta lla-
mada se utiliza para aceptar conexiones, el socket necesario debe estar orientado a conexin, es decir, debe ser de
tipo stream, y por lo tanto, el protocolo de transporte necesario debe ser TCP.
La llamada correcta ser sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
b) El servicio concreto descrito en el enunciado (equivalente a telnet) es un ejemplo muy claro de un servidor "con
estado". Todo usuario, al interactuar con un intrprete de mandatos "cambia" el estado de este intrprete (implcita o
explicitamente) al ejecutar mandatos internos (ej. cd) o al establecer variables de entorno.
La estratgia de implementacin ms adecuada para un servidor con estado es aquella basada en procesos pesa -
dos ya que el programador del servicio no tendr que preocuparse de mantener esta idea de estado por cada cliente
servido, sino que ser el sistema operativo el que se encargue automticamente de hacerlo.
Luego la estratgia ms adecuada es la 1, basada en Procesos Pesados.
c) La segunda implementacin, basada en procesos ligeros es completamente correcta. El paso del nico parmetro
que el hilo de servicio necesita se realiza a travs de memoria dinmica, y es este hilo el que se encarga de liberar la
memoria dinmica as como de cerrar el descriptor del socket de servicio cuando este termine.
Sin embargo, la primera implementacin no es completamente correcta. El problema reside en que el proceso
creado para la prestacin del servicio concreto no termina dnde debera. Hara falta una llamada exit(0) en las l -
neas 10 u 11. Sin esta llamada, cada proceso hijo creado continuar ejecutando el cdigo que le corresponde al pro -
231
ceso Servidor Genrico original. Si quisiesemos hacer ms evidente que, una vez concluida la prestacin del servicio
concreto no nos hace falta ya el descriptor de servicio, podramos hacerlo haciendo una llamada close(cd) en la lnea
10.
d)
/* ServicioConcreto */
void ServicioConcreto(int cd)
{
unsigned char byte;
while( recv(cd, &byte, 1, 0) > 0 )
send(cd, &byte, 1, 0);
}
e) Estarn en uso: el descriptor de aceptacin de conexiones (sd) ms un descriptor de servicio (cd) por cada cliente
que est actualmente en servicio.
Los descriptores de servicio de los clientes que fueron ya completamente servidos y terminaron fueron cerrados
por el correspondiente hilo de servicio.
Los clientes que estn intentando obtener servicio, no habrn superado todava la fase de aceptacin de conexin
(llamada accept del servidor), luego, todava no existir para ellos un descriptor de conexin.
Luego la respuesta correcta es 6.
f) Para sincronizar procesos ligeros (tambin llamados hilos, en ingls threads) las primitivas ms adecuadas, por li-
gras y cmodas de usar, son los mutex y las variables condicionales.
Otros mecanismos de sincronizacin, como por ejemplo semforos sobre memoria proyectada compartida, seran
los adecuados si lo que quisiramos sincronizar fueran procesos pesados.
g) Lo primero que hay que hacer notar es que los dos segmentos cdigo: de la lnea 19 a la 108 y de la lnea 220 a la
303, son "la misma regin crtica", la regin crtica nmero 0. Los son porque, muy probablemente, en ambos seg-
mentos se manipulan las mismas variables, y no se desea que pueda haber hilos ejecutando simultneamente dentro
de un segmento y del otro.
Lo mismo ocurre con las regiones crticas 1 (lneas 20 a 21 y 221 a 222) y 2 (lneas 107 y 302).
En el instante T, el hilo A est fuera de toda seccin crtica, y lo siguiente que har ser intentar entrar en (el pri -
mer segmento de) la regin crtica 0. La situacin del hilo C es semejante, fuera de toda seccin crtica e intentar
entrar en el segundo segmento de la regin crtica 0.
Sin embargo, el hilo B est en posesin de las regiones crticas 0 y 2, pero no de la 1.
Dada esta situacin en el intante T, si el siguiente en ejecutar fuese el hilo A, quedara bloqueado en la lnea 18,
compitiendo por entrar en la seccin crtica 0 (en posesin de B).
h) Dada la explicacin del apartado anterior, y entendiendo que los dems hilos estn parados, el hilo B podra eje-
cutar libremente hasta salir de la funcin por el return de la lnea 305.
Primero liberara las regiones crticas 2 y 0, para luego volver a tomar la 0, pasar por la 1 y la 2, liberar la 0 y ter -
minar.
i) Al igual que en el caso del hilo A, el hilo C no podr ir ms all de la lnea 219, donde quedar bloqueado inten -
tando entrar en la regin crtica nmero 0 que est en posesin del hilo B.
j) Vista la explicacin dada en los apartados anteriores, queda claro que A no modificar la variable cnt. En la ejecu -
cin del hilo B la variable cnt ser incrementada una vez, en la lnea 221. Una vez terminado el hilo B, el hilo C po -
dr ejecutar libremente hasta salir de la funcin, y en su camino, volver a incrementar la variable compartida cnt al
ejecutar la lnea 221.
Luego finalmente la variable cnt habr sido incrementada dos veces, y su valor final ser 7.

Problema 4.10 (2004)

Se desea realizar un sistema de envo de mensajes cortos (SMS), a travs de una serie de mdems GSM.
232 Problemas de sistemas operativos
Todos los mensajes a ser enviados tienen que almacenarse en un buffer circular acotado de tamao N mensajes.
De esta forma, los procesos que deseen enviar algn mensaje, debern rellenar el siguiente hueco libre de dicho bu-
ffer con la siguiente informacin:
struct mensaje{
long int destino; // Nmero telfono de destino
long int origen; // Nmero de telfono de origen
char sms[160]; // Cuerpo del mensaje
}
Para implementar este sistema se plantea la estructura de procesos de la figura Error: Reference source not
found:
P P1 P2 ... Pn

mdem1 G1

mdem2 G2

...

mdemm Gm - Buffer cerrado


y acotado

- Puntero al primer hueco libre


- Puntero al siguiente mensaje a enviar
OBJETO DE MEMORIA COMPARTIDO
Figura 4.4

Un proceso general P, que solicitar al SO la creacin del buffer (objeto de memoria compartido con nom -
bre) y todos los mecanismos de sincronizacin necesarios.
Los procesos Pi que son los que desean enviar mensajes. Para enviar un mensaje, P i debe rellenar el siguien-
te hueco vaco del buffer. Pi recibe los datos del mensaje a enviar como parmetros de salida de la siguiente
funcin, que retorna cuando tiene los datos (suponer que ya est implementada):
datosMensaje (long int * destino, long int * origen, char * sms)
Existe un proceso Gi asociado a cada uno de los mdems, que son los responsables del envo de mensajes.
Estos procesos recogern los mensajes pendientes de envo del buffer y se lo mandarn a su correspondiente
mdem GSM. Cada uno de estos procesos G i debe abrir el dispositivo /dev/modemi y, por cada mensa-
je dado, llamar a la siguiente funcin bloqueante (suponer que ya est implementada):
enviarMensaje(int descriptorModem, long int destino, long int origen, char * sms)
Lgicamente, cada uno de los mensajes del buffer debe ser enviado a travs de un nico mdem (el que se en-
cuentre disponible en cada momento).
Se debe suponer que todos los procesos anteriormente descritos son independientes entre s y que todos ellos co-
nocen los nombres de los mecanismos de comunicacin y sincronizacin creados por el proceso P.
Se pide:
a) Como se ve en la Figura, los punteros al primer hueco libre y al siguiente mensaje a enviar estn localizados
en el objeto de memoria compartido. Sera ms eficiente tener almacenados estos valores en cada uno de
los procesos?, en cules de ellos? Qu problemas plantea tener los punteros en el objeto de memoria com-
partida?
b) Implementar el proceso P siguiendo el esquema de la figura .
c) Implementar uno de los procesos Pi siguiendo el esquema de la figura .
d) Implementar uno de los procesos Gi siguiendo el esquema de la figura .
233
e) Se desea realizar un log de todos los mensajes enviados por los procesos G i. Para ello, se debe crear el fi-
chero /usr/enviados.txt y, cada vez que uno de los procesos G i enva un mensaje, se debe aadir
al final de dicho fichero el mensaje enviado y el nmero de mdem utilizado para su envo. Implementar los
cambios necesarios en los procesos anteriores para dar soporte a esta nueva funcionalidad.

Solucin
a) Los punteros deben ser compartidos por todos los procesos. Es necesario, por tanto, que estn almacenados o bien
en el propio objeto de memoria compartida, o bien en algn otro objeto que pueda ser compartido por todos los pro -
cesos. En ningn caso deben estar en los propios procesos ya que de esa forma sera muy difcil actualizar los valo -
res de los punteros en todos los procesos.
Tener los punteros en el objeto de memoria compartida exige que los procesos accedan en exclusin mutua al ob-
jeto al modificar los punteros. Por ltimo, el objeto de memoria compartida estar en el mapa de memoria de todos
los procesos, y en cada proceso en una direccin diferente, por lo que los punteros deben contener valores relativos,
no pudindose utilizar direccionamiento absoluto.
b) Cdigo del proceso P. Los mecanismos de comunicacin y sincronizacin que crea P deben ser con nombre, ya
que todos procesos de la solucin son independientes entre s. Este proceso no destruye ningn mecanismo ya que
no se pide en el enunciado.
#define MAX_BUFFER 1000
struct mensaje{
long int destino; // Nmero telfono de destino
long int origen; // Nmero de telfono de origen
char sms[160]; // Cuerpo del mensaje
}
struct compartido{
mensaje buffer[MAX_BUFFER];
int pHueco; int pElemento;
}
sem_t *elementos; /* elementos en el buffer */
sem_t *huecos; /* huecos en el buffer */
sem_t *sembin; /* acceso a compartido */

int main (void)


{
int shd;
struct compartido * objCompartido;

/* El proceso P crea el fichero para memoria compartida */


shd = open("BUFFER", O_CREAT, 0700);
/* Le asigna el tamao necesario */
ftruncate(shd, sizeof(struct compartido));

/* Proyectar el objeto de memoria compartida en su espacio


de direcciones */
objCompartido = (struct compartido *) mmap(NULL,

sizeof(struct compartido), PROT_WRITE, MAP_SHARED, shd, 0);


/* Inicializa punteros de huecos y elementos */
objCompartido->pHueco = 0;
objCompartido->pElemento = 0;
/* Desproyecta el objeto de memoria compartida */
munmap (objCompartido, sizeof(struct compartido) );

elementos = sem_open("ELEMENTOS", O_CREAT, 0700, 0);


huecos = sem_open("HUECOS", O_CREAT, 0700, MAX_BUFFER);
sembin = sem_open("SEMBIN", O_CREAT, 0700, 1);
}
c) Cdigo del proceso Pi.
234 Problemas de sistemas operativos
#define MAX_BUFFER 1000
struct mensaje{
long int destino; // Nmero telfono de destino
long int origen; // Nmero de telfono de origen
char sms[160]; // Cuerpo del mensaje
}
struct compartido{
mensaje buffer[MAX_BUFFER];
int pHueco;
int pElemento;
}
sem_t *elementos; /* elementos en el buffer */
sem_t *huecos; /* huecos en el buffer */
sem_t *sembin; /* acceso a compartido */
void Pi (void)
{
int i, shd;
struct compartido * objCompartido;
long int origen, destino;
char sms[160];
/* Abre el fichero para memoria compartida */
shd = open("BUFFER", O_RDONLY);

/*Proyectar el objeto de memoria compartida en su espacio de direcciones */


objCompartido = (struct compartido *) mmap(NULL,

sizeof(struct compartido), PROT_WRITE|PROT_READ, MAP_SHARED, shd, 0);

/* Abre los semforos */


elementos = sem_open("ELEMENTOS", 0);
huecos = sem_open("HUECOS", 0);
sembin = sem_open("SEMBIN", 0);

for(i=0; i < SMS_A_PRODUCIR; i++ ) {


/* Se obtiene mensaje a enviar */
datosMensaje(&destino, &origen, sms);
sem_wait(huecos); /* Un hueco menos */
sem_wait(sembin); /* Acceso con exclusin mutua */
(objCompartido -> buffer[objCompartido->pHueco]).destino = destino;
(objCompartido -> buffer[objCompartido->pHueco]).origen = origen;
strcpy(sms ,(objCompartido -> buffer[objCompartido->pHueco]).sms);
objCompartido->pHueco = (objCompartido->pHueco + 1) % MAX_BUFFER;
sem_post(sembin);
sem_post(elementos); /* Un elemento mas */
}

/* Desproyecta el objeto de memoria compartida */


munmap (objCompartido, sizeof(struct compartido) );
}
d) Cdigo del proceso Gi.
#define MAX_BUFFER 1000
struct mensaje{
long int destino; // Nmero telfono de destino
long int origen; // Nmero de telfono de origen
char sms[160]; // Cuerpo del mensaje
}
struct compartido{
mensaje buffer[MAX_BUFFER];
int pHueco;
int pElemento;
235
}
sem_t *elementos; /* elementos en el buffer */
sem_t *huecos; /* huecos en el buffer */
sem_t *sembin; /* acceso a compartido */

void Gi (void)
{
int i, shd, dm;
struct compartido * objCompartido;
long int origen, destino;
char sms[160];
/* Abre el fichero para memoria compartida */
shd = open("BUFFER", O_RDONLY);

/*Proyectar el objeto de memoria compartida en su espacio de direcciones */


objCompartido = (struct compartido *) mmap(NULL,

sizeof(struct compartido), PROT_READ|PROT_WRITE, MAP_SHARED, shd, 0);

/* Abre los semforos */


elementos = sem_open("ELEMENTOS", 0);
huecos = sem_open("HUECOS", 0);
sembin = sem_open("SEMBIN", 0);
/* Abre el modem con el que est relacionado */
md = open("/dev/modemi");

for(i=0; i < SMS_A_CONSUMIR; i++ ) {


sem_wait(elementos); /* Un elemento menos */
sem_wait(sembin); /* acceso con exclusin mutua */
/* Lee mensaje a enviar */
destino = (objCompartido -> buffer[objCompartido->pElemento]).destino;
origen = (objCompartido -> buffer[objCompartido->pElemento]).origen;
strcpy(sms ,(objCompartido -> buffer[objCompartido->pElemento]).sms);
objCompartido->pElemento = (objCompartido->pElemento + 1) % MAX_BUFFER;
sem_post(sembin);
sem_post(huecos); /* Un hueco mas */
/* Ya liberados los recursos, enva el mensaje */
enviarMensaje (md, destino, origen, sms);
}

/* Desproyecta el objeto de memoria compartida */


munmap (objCompartido, sizeof(struct compartido) );
}
e) El proceso P puede ser el encargado de crear el fichero de log. Los procesos Gi, cada vez que enven un mensaje,
deben aadir al final del fichero de log dicho mensaje. La opcin O_APPEND de la llamada open es suficiente, ya
que garantiza que en todo momento estamos escribiendo al final del fichero, aunque ste est compartido por varios
procesos.
Para esta solucin se debe aadir:
En el programa P
creat("/usr/enviados.txt", 0700);
En los programas Gi
/* Al principio del programa */
fd = open("/usr/enviados.txt", O_WRONLY|O_APPEND);
/* Justo despus de enviar mensaje */
sprintf(fd, "MODEM: %d Mensaje: %s\n", md, sms);
write(fd, buffer, strlen(buffer));
Son posibles otras soluciones sin abrir el fichero con el flag O_APPEND. En este caso, antes de cada escritura,
habra que hacer un lseek al final del fichero. En esta solucin habra que garantizar que el lseek y el fprintf se hacen
236 Problemas de sistemas operativos
de forma atmica, ya que en caso contrario se podra meter otro proceso en medio de esas operaciones. Para garanti-
zar esta atomicidad, o bien utilizamos un semforo adicional con valor inicial 1 que bloquee el acceso al fichero, o
bien realizamos las operaciones enviarMensaje, lseek y fprintf, antes de las llamadas sem_post que se realizan en Gi
(esta solucin, aunque factible, implica tener bloqueado el objeto de memoria compartida ms tiempo del debido).

Problema 4.11 (mayo 2005)

Sean las siguientes dos utilidades cuyo cdigo se adjunta, respectivamente diseadas para ser invocadas como:
emite segundos texto
filtro mandato [args...]

emite.c
/* 1*/ int main(int argc, char *argv[])
/* 2*/ {
/* 3*/ int secs=atoi(argv[1]);
/* 4*/ while(secs > 0) {
/* 5*/ sleep(4); /*segundos*/
/* 6*/ secs -= 4;
/* 7*/ write(1, argv[2], strlen(argv[2]));
/* 8*/ }
/* 9*/ return 0;
/*10*/ }

filtro.c
/* 1*/ int main(int argc, char **argv)
/* 2*/ {
/* 3*/ int pp[2], ret, cnt=0, ttl=0;
/* 4*/ char buff[10];
/* 5*/ pipe(pp);
/* 6*/ switch(fork()) {
/* 7*/ case 0:
/* 8*/ close(1);
/* 9*/ dup(pp[1]);
/*10*/ argv++;
/*11*/ execvp(*argv, argv);
/*12*/ case -1:
/*13*/ perror(*argv);
/*14*/ exit(1);
/*15*/ default:
/*16*/ close(0);
/*17*/ dup(pp[0]);
/*18*/ while((ret = read(0, buff, 10)) > 0) {
/*19*/ cnt++; ttl += ret; /* Filtrado */
/*20*/ write(1, buff, ret);
/*21*/ }
/*22*/ }
/*23*/ fprintf(stderr, "%d,%d\n", cnt, ttl);
/*24*/ return 0;
/*25*/ }
Responda:
a) Cul es el objetivo aparente de filtro?
b) Si se invocase a filtro inexistente indicando un mandato inexistente, qu lneas se ejecutaran
tras la invocacin a execvp de la lnea 11?
237
c) Desgraciadamente, la implementacin dada de filtro contiene un error que (independientemente de
quin est al otro lado del pipe) impide que concluya la que debera ser ltima invocacin a read. Indique
qu descriptores deberan haberse cerrado, como mnimo, para corregir el problema.
Suponiendo corregidas las deficiencias anteriormente indicadas y considerando la ejecucin de:
filtro emite 12 AEIOU
d) Cuntos segundos, aproximadamente, tardar en completarse la primera llamada a read?
e) Qu dos valores (separados por una coma) se mostrarn finalmente por el stderr?

Solucin
a) filtro ejecuta el mandato indicado como un proceso hijo, conectndose padre e hijo con una tubera. El hijo
escribe en la tubera y el padre lee de ella. Luego filtro filtrar la salida del mandato.
b) Si la llamada a exec falla, y dado que en C los diferentes casos de la sentencia switch no son excluyentes, la
llamada a exec retornar (devolviendo un -1) y el hilo continuar ejecutando las lneas 13 y 14. Esta ltima lnea
hace un exit, donde el proceso terminar.
c) Para que la ltima llamada a read (que est leyendo de un pipe) concluya y devuelva un cero, deben darse dos
circunstancias, que no haya datos en el pipe y que no queden descriptores asociados al extremo de escritura del pipe.
Las anteriores llamadas a read irn consumiendo los datos que el mandato introduzca en el pipe, pero la ltima no
concluir mientras existan descriptores asociados al extremo de escritura. Ni el padre ni el hijo cerraron pp[1], no
obstante, cuando el hijo concluya se cerrarn automticamente todos sus descriptores. De manera que lo mnimo que
deberamos hacer para que el programa filtro concluya correctamente es que el padre cierre pp[1], descriptor
que, desde luego, no va ha usar.
d) El mandato emite emite el texto indicado como segundo argumento (AEIOU) cada 4 segundos y hasta que se
cumplan el nmero de segundos (12) especificado en su invocacin. Por otro lado, el mandato filtro esta inten-
tando leer del pipe de 10 en 10 bytes. Hay que recordar que la lectura de un pipe devuelve la cantidad de informa -
cin disponible en ese preciso instante hasta el mximo especificado en como tercer argumento a read. Dicho lo
cual, la primera llamada a read leer los 5 caracteres AEIOU al cabo de 4 segundos aproximadamente.
e) Para un total de 12 segundos, el mandato emite realizar 3 iteraciones, en cada una de las cuales emitir los 5
caracteres AEIOU. Los dos valores cnt y ttl que filtro muestra finalmente por la salida estndar de error conta-
bilizan respectivamente el nmero de llamadas a read que devolvieron un valor mayor que cero y el nmero total
de caracteres ledos. Luego la informacin finalmente mostrada ser 3,15.

Problema 4.12 (septiembre 2005)

Se desea implementar un nuevo mecanismo de comunicacin de procesos denominado tubito con las siguientes ca-
ractersticas:
Con este mecanismo se pueden leer y escribir cadenas de caracteres.
Slo puede ser utilizado por dos procesos relacionados en la jerarqua.
Es un mecanismo de comunicacin unidireccional. El padre puede realizar operaciones de escritura y el hijo
de lectura.
Es un mecanismo de comunicacin asncrono.
El tubito tendr las siguientes operaciones no bloqueantes:

1. void *crear_tubito ()
Esta operacin la invocar el proceso padre, antes de hacer un fork para crear al proceso hijo con el que se
desea comunicar, y crear las estructuras necesarias para la implementacin de un tubito de tamao 10 KiB.
La operacin devuelve un descriptor que deber ser utilizado en las funciones de lectura y escritura.

2. int leer_tubito (void *descriptor, int tam, char *datos)


238 Problemas de sistemas operativos
Esta operacin la invocar el proceso hijo para leer tam bytes del tubito. La funcin devolver el nmero de
bytes que se han conseguido leer y en datos el resultado de la lectura.

3. int escribir_tubito (void *descriptor, int tam, char *resul)


Esta operacin la invocar el proceso padre para escribir tam bytes en el tubito. La funcin devolver el n -
mero de bytes que se han conseguido escribir.
La implementacin de los tubitos se realizar de la siguiente manera.
La operacin crear_tubito proyectar el fichero /dev/zero como MAP_SHARED en memoria y de-
volver la direccin a partir de la cual se ha proyectado el fichero. Esta direccin ser la que se utilice como
descriptor del mecanismo de comunicacin.
La zona proyectada contiene esta estructura de datos:
struct tubito{
int pos_lect; //Posicin actual de lectura
int pos_escr; //Posicin actual de escritura
char bufferCircular[10000]; // buffer con la informacin
int escritos_pendientes_leer; // num de caracteres pendientes de leer
}
El manejo del buffer es circular, por la que cuando se llega al tamao mximo se debe comenzar a leer/escri-
bir desde la posicin 0 del buffer.
La operacin de escritura rellenar los siguientes caracteres vacos del buffer e incrementar pos_escr.
La operacin de lectura leer del buffer e incrementar el ndice pos_lect.
La variable escritos_pendientes_leer llevar la cuenta del nmero de bytes que ha escrito el padre
y que el hijo todava no ha ledo. Lgicamente esta variable nunca podr ser mayor de 10.000, ya que el bu-
ffer estara lleno, ni ms pequea que 0, situacin en la que el buffer estara vaco. Con esta variable se debe
controlar que el hijo slo lee cosas que ha escrito el padre, y que el padre no escribe ms datos de los posi -
bles.
No es necesario controlar quin lee o escribe en el tubito (se supondr que el padre siempre escribe y que el
hijo siempre lee).
Se pide:
a) Realice un ejemplo sencillo donde se vea cmo el padre y el hijo pueden utilizar el tubito.
b) En caso de que existir la operacin void destruir_tubito (void *descriptor), quin debe-
ra invocarla?
c) Es necesario el uso de algn mecanismo de sincronizacin para la implementacin de los tubitos?
d) Suponiendo que el descriptor es vlido, implemente las tres operaciones descritas de los tubitos.
e) Las operaciones de lectura y escritura reciben un descriptor a travs del cual son capaces de acceder a la
estructura de memoria. Cmo se podra verificar que este descriptor es correcto y que, por tanto, estamos
accediendo a una zona de memoria correcta?
f) Podra hacerse bidireccional el mecanismo? Describir brevemente (no ms de 10 lneas) cmo podra im-
plementarse.

SOLUCIN
a) El programa propuesto es el siguiente.
int main(void)
{
void *descriptor;
pid_t pid;
int status;

char cadena[4]="hola";
char resul[4];
239

descriptor=crear_tubito();
pid = fork();

if (pid!=0)
{
escribir_tubito(descriptor,sizeof(cadena),cadena);
wait(&status);
}
else
{
leer_tubito(descriptor,sizeof(resul),resul);
return 0;
}

return 0;
}
Tal y como est implementado este programa y debido a que es un mecanismo de comunicacin no bloqueante,
no se puede asegurar que el hijo reciba lo que ha escrito el padre, ya que depende del orden de ejecucin de los pro -
cesos.
b) Debido a que es un mecanismo basado en una zona de memoria compartida, debern ser tanto el proceso padre
como el hijo los que cierren el mecanismo, a fin de liberar todos los recursos.
c) En principio, al ser un mecanismo de comunicacin no bloqueante, no se debe considerar el uso de variables con-
dicionales a la hora de la lectura y escritura en el buffer compartido. Es decir, cuando el buffer est lleno o vaco, e
intentemos escribir o leer respectivamente, la llamada no se bloquea. Simplemente, las funciones leer_tubito y
escribir_tubito devolvern el nmero de caracteres que han podido leer o escribir.
Por otra parte, es posible que un proceso padre y un proceso hijo estn realizando una operacin al mismo tiem-
po, situacin en la cual debemos proteger la variable escritos_pendientes_leer, a la que se debe acceder
en exclusin mutua. Esta exclusin mutua se podra implementar con semforos.
e) Se podra utilizar un mecanismo similar al que se usa para saber que un fichero ejecutable es vlido, es decir, se
podra aadir a la estructura del mecanismo un nmero mgico, de forma que cada vez que nos manden un descrip -
tor verifiquemos el nmero mgico para ver que es un descriptor correcto.
f) No hay ningn problema en hacer este mecanismo bidireccional tal y como est definido, siempre y cuando las
operaciones de lectura y escritura sobre el buffer se hagan en exclusin mutua. Es decir, no slo es necesario prote -
ger la variable escritos_pendientes_leer, sino que hay que proteger todo el acceso a la estructura compar-
tida en memoria.
Otra posible opcin sera tener un sistema de doble buffer, de forma que cada buffer fuera utilizado en una direc-
cin.
d) El programa propuesto es el siguiente.
#define TAM_BUFFER 10000
void *crear_tubito()
{
int fd,

fd = open("/dev/zero", O_RDWR);
t = mmap(NULL, (sizeof(int)*3)+10000 , PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);
t->pos_lect=0;
t->pos_escr=0;
t->escritos_pendientes_leer=0;

return(t);
}

int leer_tubito(void *descriptor, int tam, char *datos)


{
240 Problemas de sistemas operativos
int leidos=0;

while(descriptor->escritos_pendientes_leer)
{
datos[leidos]=descriptor->bufferCircular[descriptor->pos_lect];
descriptor->pos_lect = (descriptor->pos_lect+1) % TAM_BUFFER;
leidos++;
// Inicio exclusion mutua
descriptor->escritos_pendientes_leer--;
// Fin exclusion mutua
if (descriptor->escritos_pendientes_leer==0)
break;
}
return(leidos);
}

int escribir_tubito(void *descriptor, int tam, char *datos)


{
int escritos=0;

while(descriptor->escritos_pendientes_leer)
{
descriptor->bufferCircular[descriptor->pos_escr]=datos[escritos];
descriptor->pos_escr = (descriptor->pos_escr+1) % TAM_BUFFER;
escritos++;
// Inicio exclusion mutua
descriptor->escritos_pendientes_leer++;
// Fin exclusion mutua
if (descriptor->escritos_pendientes_leer==TAM_BUFFER)
break;
}
return(escritos);
}

Problema 4.13 (junio 2006)

En una prctica que se realiza por parejas se pide desarrollar el programa cifrar con las siguientes caractersticas:
Toma como entrada un fichero que contiene nmeros enteros en binario y lo cifra, dando como salida ese
mismo fichero transformado. El nombre del fichero ser el primer argumento del programa.
La ejecucin de este programa se debe realizar a travs de una serie de procesos, cada uno de los cuales se
encargar de parte del fichero. El nmero de procesos vendr definido por el segundo argumento del progra-
ma. La jerarqua de procesos ser un proceso padre y tantos procesos hijo como se nos indique.
A fin de mejorar el rendimiento de este programa se ha decidido usar la tcnica de proyeccin de ficheros en
memoria.
Tu compaero de prcticas ha realizado el siguiente cdigo, que segn te cuenta, compila y ejecuta correcta-
mente, pero no realiza la labor para la que ha sido diseado.
int func_transform(int entrada)
{ ... } // Cdigo correcto para el cifrado de un entero

void tratar_parte(int *p, int inicio, int n_enteros)


{
int i;
p = p + inicio;
for (i = 0; i < n_enteros ;i++){
*p = func_transform(*p);
p++;
}
241
}

int main(int argc, char **argv)


{
int fd, n, size_proc, tam, resto, j, size_of_file, num_enteros;
int *p, *q;

fd = open(argv[1],O_RDWR);
n = atoi(argv[2]); // Conversin de una cadena de caracteres a un entero

size_of_file = lseek(fd, 0, SEEK_END);


p = mmap(NULL, size_of_file, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);

num_enteros = size_of_file/sizeof(int);
size_proc = num_enteros / n;
resto = num_enteros % n;

// Punto A

for (j = 0; j < n; j++){


if (fork() == 0){
tam = size_proc;
if (j == (n-1))
tam = tam + resto;
tratar_parte(p, j * size_proc, tam);
}
}

while (n > 0){


wait(NULL);
n ;
}
}
Se pide:
a) La jerarqua de procesos creada por este cdigo, es la especificada en el enunciado de la prctica? Razona
la respuesta.
b) Centrndose en las llamadas al sistema, encuentras algn fallo en este cdigo?
c) Para hacer el cdigo ms seguro y profesional, qu recomendaciones haras a tu compaero?
d) En la segunda parte de la prctica nos piden una pequea mejora. La mejora consiste en aadir al final del
fichero un nmero mgico predefinido, el 999. Para ello, nuestro compaero nos propone aadir las siguien-
tes lneas en el Punto A:
// Aadido en Punto A
q = p + num_enteros;
*q = 999;
Esta modificacin funcionara correctamente? Razona la respuesta.
e) Suponiendo que el nmero mgico ya funciona correctamente, nos piden una nueva mejora del programa.
Esta vez, cuando un proceso hijo finalice, ste sumar uno al valor del nmero mgico. Para realizar esta
labor nuestro compaero nos propone el siguiente cdigo:
/* Suponiendo que el puntero q apunta correctamente al ltimo entero del
fichero proyectado */
for (j = 0; j < n; j++){
if (fork() == 0) {
...
tratar_parte(p, j * size_proc, tam);
*q = *q + 1;
...
}
}
242 Problemas de sistemas operativos
Est consiguiendo nuestro compaero realizar la labor correctamente? En caso de existir algn problema,
describe brevemente cmo lo solucionaras.
f) Ya est todo funcionando, cuando nuestro compaero decide hacer ms potente la funcin func_transform,
con tanta torpeza que la funcin se llama a s misma infinitamente de forma recursiva:
int func_transform(int entrada)
{
char buffer[50000];
...
return (func_transform(aux));
}
Suponiendo que el programa tiene tiempo ilimitado para ejecutar, qu error se producira en el sistema?
g) Tu compaero te pregunta qu cambios seran necesarios en el cdigo para que, en vez de estar basado en
procesos, estuviera basado en hilos. Qu cambios le explicaras a tu compaero?, qu llamadas al sistema
necesitaras para su implementacin? No hace falta codificar esos cambios, slo indicar qu debera hacer -
se.

SOLUCIN
a) La jerarqua de procesos creada por nuestro compaero no es la correcta. La jerarqua especificada en el enuncia-
do consiste en un proceso padre con n procesos hijos. Sin embargo, en el cdigo de nuestro compaero los procesos
hijos, despus de llamar a la funcin tratar_parte, no finalizan y se vuelven a meter en el bucle for, creando a
su vez otros procesos. La solucin es tan sencilla como incluir un exit despus de la llamada a la funcin tra-
tar_parte.
La solucin del hacer un break en vez de un exit no es la ms correcta, ya que el proceso hijo seguira eje-
cutando y realizara la llamada wait, cuando realmente no tiene ningn hijo por el que esperar. En este caso la llama
wait nos devuelve un error indicando que no tenemos procesos hijo.
b) La llamada mmap no se est haciendo correctamente, ya que el fichero se est proyectando como MAP_PRIVA-
TE cuando debera proyectarse como MAP_SHARED para que los cambios realizados se graben a fichero cuando se
desproyecta. El resto de llamadas al sistema estn perfectamente definidas, incluyendo el lseek, que adems de
posicionar el puntero en la ltima posicin del fichero (ya que se especifica SEEK_END) nos devuelve dicha posi-
cin.
c) Respecto a la seguridad y profesionalidad del cdigo de nuestro compaero se le podran hacer dos recomenda-
ciones: (i) es muy importante realizar una correcta verificacin de los posibles errores de las llamadas al sistema, y
(ii) es muy recomendable liberar todos los recursos asignados. Continuando con este ltimo punto se debera cerrar
el fichero abierto una vez proyectado llamada close(fd) y el proceso padre debera cerrar la proyeccin del
fichero al final del programa llamada munmap(p, size_of_file).
d) La modificacin de nuestro compaero no funciona correctamente. Si bien la aritmtica de punteros es correcta,
es decir, q = p + num_enteros nos posiciona al final del fichero proyectado, al realizar la sentencia *q =
999 estamos escribiendo fuera de la proyeccin. De esta forma, no estamos consiguiendo lo que se nos indica en el
enunciado. La solucin pasa por cambiar el tamao del fichero antes su proyeccin, por ejemplo con la llamada
ftruncate(fd, size_of_file + sizeof(int)).
e) Suponiendo que el puntero q apunta correctamente al ltimo entero del fichero, la sentencia *q = *q + 1 fun-
ciona correctamente. Sin embargo, tenemos que tener en cuenta que se podran producir condiciones de carrera al in-
tentar actualizar el valor del nmero mgico. La solucin es sencilla, nos bastara con utilizar un mecanismo de
sincronizacin como los semforos.
f) Cada vez que se realiza una llamada a una funcin en la pila del proceso se crea su registro de activacin. Por tan-
to, si hacemos llamadas recursivas de manera infinita a una funcin, estaremos haciendo crecer la regin de pila del
mapa de memoria del proceso de manera infinita. Las llamadas se podran seguir realizando mientras la pila pueda
seguir creciendo.
g) Comentara a mi compaero que en este caso cambiar nuestro programa para que use hilos es muy sencillo. En
primer lugar debemos establecer el estado de terminacin de los hilos. En nuestro caso, es conveniente usar hilos de
tipo JOINABLE, para que el hilo principal pueda saber cuando finalizan los hilos trabajadores. El resto de los cam -
bios es casi inmediato. Debemos crear la estructura para los atributos de los hilos ( pthread_attr_t) y las varia-
bles para almacenar los identificadores de los hilos (pthread_t). A continuacin, debemos inicializar los hilos
243
(pthread_attr_init). De esta forma, ya estamos preparados para crear los hilos, sustituyendo la llamada
fork por la llamada pthread_create en donde debemos especificar que cada hilo debe ejecutar la funcin -
tratar_parte. Por ltimo, se debe sustituir la llamada wait por la llamada pthread_join para esperar la fi-
nalizacin de los hilos y destruir el objeto de tipo atributo previamente creado con la llamada
pthread_attr_destroy. Para no dejarnos ningn detalle, la funcin tratar_parte debe finalizar con la
llamada pthread_exit para que los hilos trabajadores finalicen correctamente.

Problema 4.14 (junio 2007)

Se tiene un fichero de 1000 bytes sobre el que interactan procesos lectores y escritores cuyo cdigo se adjunta. Se
observar que cada uno de ellos lee o escribe 8 bytes, empezando en argv[1]*8. Considere que los lectores y escri-
tores nunca son llamados con argv[1] > 124.
a) Cul sera el tamao del fichero despus de realizar 20 operaciones de lectura (lector B) seguido de 5 es-
crituras (escritor A)?
b) Suponiendo que existe un mximo de 25.000 posibles lectores y que tenemos un escritor que est modifican-
do val, Cuantos lectores que estn leyendo val se podra llegar tener?
c) Partiendo del fichero inicial, indicar el mximo nmero de escritores A simultneos (todos ellos modificando
val) que se podran tener, en caso de existir tres lectores B que han recibido argv[1] = 0, argv[1] = 40 y
argv[1] = 120 y que estn leyendo val.

Cdigo escritor A Cdigo lector B

int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
int fd, val, argv1; int fd, val, argv1;
struct flock fl; struct flock fl;
argv1 = atoi(argv[1])*8 argv1 = atoi(argv[1])*8
fd = open("BD", O_RDWR); fd = open("BD", O_RDONLY);
fl.l_whence = SEEK_SET; fl.l_whence = SEEK_SET;
fl.l_start = argv1; fl.l_start = argv1;
fl.l_len = 8; fl.l_len = 8;
fl.l_pid = getpid(); fl.l_pid = getpid();
fl.l_type = F_WRLCK; fl.l_type = F_RDLCK;
fcntl(fd, F_SETLKW, &fl); fcntl(fd, F_SETLKW, &fl);
lseek(fd, argv1, SEEK_SET); lseek(fd, argv1, SEEK_SET);
read(fd, &val, 8); read(fd, &val, 8);
<<se modifica val>> <<se manipula val>>
lseek(fd, argv1, SEEK_SET); printf("%d\n", val);
write(fd, &val, 8); fl.l_type = F_UNLCK;
fl.l_type = F_UNLCK; fcntl(fd, F_SETLK, &fl);
fcntl(fd, F_SETLK, &fl); return 0;
return 0; }
}

Solucin
Primero observemos que el fichero est compuesto por 1000/8 = 125 datos que pueden ser utilizados de forma inde -
pendiente. Cada lector establece un cerrojo de tipo compartido sobre un dato de 8 bytes y cada escritor establece un
cerrojo exclusivo tambin sobre un dato de 8 bytes.
a) Dado que las operaciones de escritura siempre se realizan dentro del fichero su tamao no se modifica, por lo que
seguir siendo de 1000 bytes.
b) El escritor tendr establecido un cerrojo exclusivo sobre el dato que est escribiendo (8 bytes), en el resto del fi-
chero podemos tener tantos lectores como se quiera. Nos dicen que el hay un mximo de 25.000 lectores, por lo que
se podra llegar a tener esos 25.000 lectores, cada uno de los cuales estar leyendo uno de los 124 datos sobre los
que no ha cerrojo exclusivo. Ntese que todos ellos podran llegar a estar leyendo el mismo dato.
244 Problemas de sistemas operativos
c) Al existir tres lectores sobre datos distintos, existen tres cerrojos de tipo compartido sobre los que no se puede ha -
cer cerrojo exclusivo. Se pueden establecer cerrojos exclusivo (necesarios para los escritores) sobre 125 - 3 = 122
datos, por tanto, se pueden tener hasta 122 escritores.

Problema 4.15 (junio 2007)

Sea el siguiente cdigo, que compila correctamente.


#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

int *p;
int tamThread=0;

void escribir_pares(void)
{
int i, dato=0;

for (i=0; i < tamThread; i++ ) {


*p=dato;
dato=dato+2;
p++;
}
}

void escribir_impares(void)
{
int i, dato=1;

for (i=0; i < tamThread; i++ ) {


*p=dato;
dato=dato+2;
p++;
}
}

int main(void)
{
int fd, tam;
pthread_attr_t attr;
pthread_t th1, th2;

fd=open("numbers.dat", O_RDWR);
// Obtencin del tamao del fichero
tam = ...;
p=mmap(NULL, tam, PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
tamThread = tam/sizeof(int)/2;
pthread_attr_init(&attr);
pthread_create(&th1, &attr, escribir_pares, NULL);
pthread_create(&th2, &attr, escribir_impares, NULL);
pthread_join(th1, NULL);
pthread_join(th2, NULL);
pthread_attr_destroy (&attr);

munmap(p,tam);
return(0);
245
}
Suponer que el fichero numbers.dat tiene un tamao de 56 bytes y que est localizado en el mismo
directorio que el programa ejecutable. Adems, se debe suponer que un nmero entero ocupa 4 bytes.
Se pide:
a) Codificar dos formas diferentes de obtener el tamao del fichero numbers.dat antes de su pro-
yeccin.
b) Enumera los segmentos que tendr el mapa de memoria del proceso justo antes de la primera lla -
mada a pthread_join teniendo en cuenta que el programa hace uso de bibliotecas estticas. In-
dicar, para cada uno de los segmentos identificados, su proteccin correspondiente (RWX).
c) Qu valor devolver la llamada al sistema munmap? En caso de que la llamada devuelva un cdi-
go de error, realizar las correcciones necesarias al cdigo para solucionarlo.
d) Dado que p es una variable compartida, qu estrategia de uso es necesaria para que el resulta-
do sea el esperado?
e) Tras una ejecucin del programa, se verifica que en el fichero numbers.dat se quedan almace-
nados los siguientes nmeros enteros:
0 2 4 6 8 10 12 1 3 5 7 9 11 13
Sin embargo, se desea que el programa escriba el siguiente contenido:
0 1 2 3 4 5 6 7 8 9 10 11 12 13
Introducir los cambios necesarios en el programa de forma que los threads escribir_pares y
escribir_impares se turnen en el acceso al fichero proyectado. Plantear una solucin basada
en alguna tcnica de sincronizacin.
La solucin codificada, corrige tambin el problema del apartado d)?

SOLUCIN
a) Solucin con fstat:
struct stat bstat;
fstat(fd, &bstat);
tam = bstat.st_size;
Solucin con lseek:
tam = lseek(fd, 0, SEEK_END);
b) El mapa de memoria del proceso tendr los siguientes segmentos:
Cdigo (RX)
Datos con valor inicial (RW)
Datos sin valor inicial (RW)
Pila (RW)
Fichero proyectado numbers.dat (W)
Pila del thread escribir_pares (RW)
Pila del thread escribir_impares (RW)
c) La llamada al sistema munmap fallar, devolviendo -1, ya que estamos intentando desproyectar desde la direc-
cin p, que los threads escribir_pares y escribir_impares estn modificando en sus respectivas sentencias p++. La
solucin es muy sencilla, se puede realizar una copia del puntero p justo despus de la proyeccin (q=p), y realizar
el munmap usando esta nueva variable (munmap(q,tam)).
d) Los threads escribir_pares y escribir_impares estn accediendo a la misma variable compartida sin ningn tipo de
control, por lo que se pueden producir problemas de carrera. Para evitar este problema, se deben convertir las zonas
conflictivas en zonas de exclusin mutua, con alguna de las tcnicas de sincronizacin vistas: semforos o mutex y
condiciones.
e) En este caso se necesita alternar entre los threads escribir_pares y escribir_impares. Una posible solucin sera
con semforos.
Declarar dos variables globales (los semforos)
246 Problemas de sistemas operativos
sem_t pares;
sem_t impares;
Estos semforos deben ser inicializados en el thread principal
sem_init(&pares,0,1);
sem_init(&impares,0,0);
El thread escribir_pares quedara
for(...) {
sem_wait(&pares);
*p=dato;
dato=dato+2;
p++;
sem_post(&impares);
}
El thread escribir_impares quedara
for(...) {
sem_wait(&impares);
*p=dato;
dato=dato+2;
p++;
sem_post(&pares);
}
Justo antes de finalizar la ejecucin debemos liberar los recursos:
sem_destroy(&par);
sem_destroy(&impar);
Por ltimo, el acceso al dato compartido p quedara protegido por los semforos, por lo que ya no podra darse el
problema de carrera que se analiz en el apartado d).
Se podra plantear otra solucin con mutex y variables condicionales.
Declaracin de variables globales
int par=0;
pthread_mutex_t mutex;
pthread_cond_t toca_par, toca_impar;

Inicializaciones en el thread principal


pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&toca_par, NULL);
pthread_cond_init(&toca_impar, NULL);

El thread escribir_pares quedara


for(...) {
pthread_mutex_lock(&mutex);
while (par==0){
pthread_cond_wait(&toca_par,&mutex);
}
*p=dato;
dato=dato+2;
p++;
par = 0;
pthread_cond_signal(&toca_impar);
pthread_mutex_unlock(&m);
}

El thread escribir_impares quedara


for(...) {
247
pthread_mutex_lock(&mutex);
while (par==1){
pthread_cond_wait(&toca_impar,&mutex);
}
*p=dato;
dato=dato+2;
p++;
par = 1;
pthread_cond_signal(&toca_par);
pthread_mutex_unlock(&m);
}

Por ltimo, debemos liberar los recursos


pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&toca_par);
pthread_cond_destroy(&toca_impar);

Problema 4.16 (junio 2010)

Parte A. Sea el programa siguiente:


#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {


int dp[2];

pipe(dp);
if (fork()==0) {
int yo, mipadre;

fork();
close(dp[0]);
yo = getpid();
mipadre = getppid();
write(dp[1], &yo, sizeof(int));
write(dp[1], &mipadre, sizeof(int));
}
else {
int dato;
if (fork()==0)
if (fork()==0)
fork();
read(dp[0], &dato, sizeof(int));
/* .Se realiza el procesamiento del dato......*/
}

return 0;
}
1.- Existe alguna circunstancia por la cual alguno de los procesos generados por dicho cdigo quedase bloqueado
de forma indefinida? En caso afirmativo indique dicha circunstancia y plantee una solucin.
2.- Suponiendo que el programa ejecuta sin que se produzca ningn error, indicar, justificadamente, el nmero m-
ximo de lectores simultneos que puede llegar a tener el pipe.
3.- Se desea que los procesos lectores ejecuten en un determinado orden. Explicar qu solucin utilizara para al-
canzar dicho objetivo.
248 Problemas de sistemas operativos
Parte B. Sea, ahora, un sistema de venta en el que coexisten distintos compradores, implementados como threads,
que siguen la secuencia siguiente:
Se selecciona el producto a comprar
Se analiza la disponibilidad del mismo as como su precio
Se determina el nmero de unidades a comprar (funcin seleccion_usuario)
Se realiza la compra.
El precio del producto vara con el nmero de ellos en stock, de acuerdo a la siguiente frmula: Precio = a +
b(c n), siendo a, b y c constantes que dependen del producto y n el nmero de productos que quedan en stock. Los
valores a, b, c y n de cada producto se almacenan en un registro en memoria.
Se plantea el siguiente esquema para resolver dicho problema:
#include <pthread.h>

struct registro { /* Registro de producto */


pthread_mutex_t cerrojo;
int n;
float a, b, c;
};
/* Una vez seleccionado el producto en una fase anterior, se ejecuta la compra */
void compra(struct registro * r) {
int disponibles;
float precio;
int p;

pthread_mutex_lock(&(r->cerrojo));
disponibles = r->n;
precio = r->a + r->b * (r->c - r->n);
pthread_mutex_unlock(&(r->cerrojo));

muestra_usuario(disponibles, precio);
/* El usuario selecciona el nmero p de unidades del producto a comprar, siendo p
disponible */
if ((p = seleccion_usuario(disponibles)) > 0) {
pthread_mutex_lock(&(r->cerrojo));
precio = r->a + r->b * (r->c - r->n);
r->n -= p;
pthread_mutex_unlock(&(r->cerrojo));
realizar_cargo(precio * p);
}
}
4.- Cuntos compradores simultneos podramos llegar a tener un producto del que quedan n unidades?
5.- Es posible que el precio aplicado sea distinto del precio mostrado? En caso afirmativo explique la secuencia
que producira dicha situacin.
6.- Es posible llegar a vender ms unidades de las disponibles? En caso afirmativo desarrolle una solucin para
evitarlo.

SOLUCIN
1.- Si alguno de los procesos escritores no se crea o muere antes de escribir, hay lectores que se quedaran esperando
eternamente. La solucin consiste en cerrar en los lectores el descriptor de escritura del pipe. Por ejemplo, poniendo
close(dp[1]); despus del else, lo que hara que se un escritor no se crea o muere antes de escribir, el read se comple-
tara devolviendo 0 bytes.
2.- Se crean cuatro lectores. Todos ellos pueden quedar bloqueados en el read, si ninguno de los dos escritores ha es -
crito nada.
3.- Hay muchas formas de conseguir que los lectores ejecuten en un cierto orden. Si queremos que ejecute primero
el padre luego el primer hijo y as sucesivamente, pondramos el read y el tratamiento de los datos antes de cada
fork:
else {
249
int dato;
read(dp[0], &dato, sizeof(int));
/* .Se realiza el procesamiento del dato......*/
if (fork()==0)
read(dp[0], &dato, sizeof(int));
/* .Se realiza el procesamiento del dato......*/
if (fork()==0)
read(dp[0], &dato, sizeof(int));
/* .Se realiza el procesamiento del dato......*/
if (fork()==0)
read(dp[0], &dato, sizeof(int));
/* .Se realiza el procesamiento del dato......*/
}
Si queremos que ejecute primero el ltimo hijo, podemos poner un waitpid en cada proceso para que espere a
que termine su hijo.
Finalmente, tambin podramos utilizar tres semforos (no mutex, puesto que se trata de procesos). Dichos sem-
foros deben ser creados con valor 0 por el padre. Tres procesos lectores quedan esperando cada uno en un semforo.
Al finalizar el lector sin semforo debe hacer un post del semforo del lector siguiente, y as sucesivamente.
4.- Puede haber un nmero ilimitado de lectores que han obtenido el precio y disponibles, pero que no han termi-
nado la compra.
5.- Dado que una vez obtenido el precio se libera el mutex, otros compradores pueden modificar el nmero de
unidades disponibles y, por tanto, el precio que se ha de aplicar.
6.- Efectivamente, se pueden vender ms unidades de las disponibles, puesto que el lmite que puede solicitar un
comprador viene en funcin del valor de la variable disponible, que puede estar obsoleta si otro comprador ha reali-
zado la compra mientras tanto.
No es buena solucin englobar todo el proceso en el mutex puesto que se producira una gran contencin. Sola -
mente podra haber un comprador en cada instante.
Tampoco es buena solucin meter el if ((p = seleccion_usuario(disponibles)) > 0) dentro de
la zona protegida por el mutex. Tambin se producira gran contencin. Por ejemplo, si un cliente no termina de
completar la seleccion_usuario el sistema de venta se quedara bloqueado.
Lo ms sencillo es justo antes de la sentencia r->n -= p; aadir if (r->n < p) {p = r->n; }
Es de destacar que la solucin general propuesta en este problema no es nada satisfactoria para el cliente, puesto
que se le puede aplicar un precio superior al mostrado inicialmente y se le pueden vender menos unidades de las pe-
didas por el cliente.

Problema 4.17 (junio 2010)

Sea un encaminador de red (switch) que puede tener n puertos de entrada y m de salida. Por sencillez considerare-
mos que cada trama contiene solamente un paquete.
Cada puerto est manejado por un thread, por lo que existen n+m threads, creados por el mismo proceso. Los
threads de entrada repetidamente reciben una trama, obtienen el paquete, determinan su puerto de salida y lo depo-
sitan en memoria. Los threads de salida repetidamente van tomando cada paquete, forman la trama y la envan por
su puerto.
Cada thread de entrada tiene un buffer capaz de almacenar una trama y cada thread de salida tiene un buffer
capaz de almacenar un paquete. Cada thread de entrada guarda la trama en su buffer y copia, cuando pueda, el pa-
quete en el correspondiente buffer del thread de salida.
a) Determinar la(s) estructura(s) de informacin a utilizar.
b) Codificar las regiones crticas de los threads de entrada y salida.

SOLUCIN
a) Consideraciones previas:
250 Problemas de sistemas operativos
Cada buffer de entrada no tiene necesidad de regin crtica (slo tiene 1 escritor), pero la escritura re-
quiere esperar a que el buffer est vaco
Cada buffer de salida tiene 1 nico lector, pero puede tener n escritores. Slo puede escribir 1 escritor en
cada momento, y con acceso exclusivo (ni lector ni otros escritores). Dado que la capacidad de cada bu -
ffer es 1 nico elemento (paquete), solo tiene 2 estados posibles, vaco o lleno. El estado vaco permite
escribir al primer escritor que est esperando, y transitar a lleno. El estado lleno permite leer al thread
lector, y transitar de nuevo a vaco.
Se necesitan:
n buffers de entrada (locales a su thread) y m buffers de salida (globales)
m mutex para gestionar el acceso exclusivo en escritura a los buffers de salida, as como diversos vecto-
res de m flags para control de sincronizacin.
Variables globales:
<<tipo_puerto_entrada>> puerto_e[n];
<<tipo_puerto_salida>> puerto_s[m];
<<tipo_paquete>> buffer_salida[m]; /* m buffers de salida */
int buffer_lleno[m]; /* (booleano 0,1) 1 = buffer lleno */
int escribiendo[m]; /* (booleano 0,1) 1 = escribiendo este buffer */
pthread_mutex_t mutex[m]; /* Control de acceso */
pthread_cond_t a_leer[m], a_escribir[m]; /* Condiciones de espera */
b)
void *Thread_de_Entrada(void *p)
/* RECIBE TRAMA POR PUERTO #i Y ESCRIBE SU PAQUETE EN BUFFER DE SALIDA #j*/
{
int i = (int)p; /*n puerto de entrada*/
int j; /*n puerto salida */
while(1){
buftrama = recibe_trama(puerto_e[i]);
j = puerto_de_salida(buftrama);

/* 1) ESPERA CONDICIN buffer #j vaco AND no escribiendo buffer #j */


pthread_mutex_lock(&mutex[j]);
while(buffer_lleno[j] || escribiendo[j])
pthread_cond_wait(&a_escribir[j], &mutex[j]);
escribiendo[j] = 1;
pthread_mutex_unlock(&mutex[j]);

/* 2) ESCRITURA EN EXCLUSIVA EN EL BUFFER DE SALIDA #j*/


buffer_salida[j] = paquete(buftrama);

/* 3) LIBERA EL RECURSO (BUFFER #j) */


pthread_mutex_lock(&mutex[j]);
/* Avisa que puede leer el lector #j*/
buffer_lleno[j] = 1;
escribiendo[j] = 0;
pthread_cond_signal(&a_leer[j]);
pthread_mutex_unlock(&mutex[j]);
}
}

void *Thread_de_salida(void *p)


/* LEE PAQUETE DEL BUFFER DE SALIDA #j Y ENVA TRAMA POR PUERTO #j*/
{
int j = (int)p; /*n puerto de salida*/
while(1){
/* 1) ESPERA CONDICIN buffer #j lleno */
pthread_mutex_lock(&mutex[j]);
while(!buffer_lleno[j]) //condicin espera
pthread_cond_wait(&a_leer[j], &mutex[j]);
pthread_mutex_unlock(&mutex[j]);
251

/* 2) LECTURA EN EXCLUSIVA DEL BUFFER DE SALIDA #j Y ENVO DE TRAMA*/


envia_trama(puerto[j], buffer_salida[j]);

/* 3) LIBERA EL RECURSO (BUFFER #j) */


pthread_mutex_lock(&mutex[j]);
/* Avisa que puede escribir cualquier escritor en #j*/
buffer_lleno[j] = 0;
pthread_cond_signal(&a_escribir[j]);
pthread_mutex_unlock(&mutex[j]);
}
}

NOTA. Funciones auxiliares (pseudocdigo):


recibe_trama(puerto[i]): Lee el puerto i
envia_trama(puerto[j], paquete): Compone y enva trama por el puerto j
puerto_de_salida(buftrama): Extrae el puerto de salida de una trama
paquete(buftrama): Extrae el paquete de una trama

Problema 4.18 (enero 2011)


Un sistema de ficheros de tipo UNIX, (diseado para un pequeo sistema porttil) presenta las siguientes carac-
tersticas:
Representacin de ficheros mediante nodos-i con 12 direcciones directas a bloque, un indirecto simple y
un indirecto doble. Direcciones de bloque de 4 bytes.
El tamao del bloque del sistema de ficheros es de 2 KiB y emplea una cache de 1 MiB con una poltica
de reemplazo LRU.
Sobre este sistema se ejecutan un proceso Escritor y cuatro procesos Lectores con los fragmentos de cdigo indi-
cados ms adelante.
Escritor Lector
#define DATA_SIZE 1024 #define DATA_SIZE 1024
#define N 2048 #define N 2048
char buffer[DATA_SIZE]; char buffer[DATA_SIZE];
int fd, i; int fd, i;
struct flock fl; struct flock fl;
fl.l_whence = SEEK_SET; fl.l_whence = SEEK_SET;
fl.l_start = 0; fl.l_len = DATA_SIZE;
fl.l_len = DATA_SIZE; fl.l_pid = getpid();
fl.l_pid = getpid(); fd = open(fich, O_RDONLY);
fl.l_type = F_WRLCK;
fd = open(fich, O_CREAT | O_WRONLY, for (i = 0 ; i < N; i++) {
0666); fl.l_start = DATA_SIZE*i;
ftruncate(fd, DATA_SIZE*N); fl.l_type = F_RDLCK;
fcntl(fd, F_SETLKW, &fl); fcntl(fd, F_SETLKW, &fl);
read(fd, buffer, DATA_SIZE);
for (i = 0 ; i < N; i++) { fl.l_type = F_UNLCK;
write(fd, buffer, DATA_SIZE); fcntl(fd, F_SETLK, &fl);
if (i < N-1) { }
fl.l_type = F_WRLCK; close(fd);
fl.l_start = DATA_SIZE*(i+1);
fcntl(fd, F_SETLKW, &fl);
}
fl.l_type = F_UNLCK;
fl.l_start = DATA_SIZE*i;
fcntl(fd, F_SETLK, &fl);
252 Problemas de sistemas operativos

}
close(fd);

a) Calcule el tamao mximo que puede tener un fichero en dicho sistema.


Teniendo en cuenta que la cache se encuentra inicialmente vaca, que no se realiza ninguna operacin de lim-
pieza de cache (sync) durante la ejecucin del mencionado programa, que el escritor ejecuta su primer fcntl
antes que ningn cdigo lector ejecute el open y que el fichero fich exista previamente con un tamao de 16
MiB, se pide:
b) Puede un lector adelantar al escritor, es decir, leer una zona todava no escrita por el escritor? Justifique la
respuesta.
c) Cuntos accesos a disco se producen durante la ejecucin de los programas en cada uno los bucles de lectu-
ra y escritura utilizando una poltica de actualizacin write-through (escritura inmediata)? Justifique la respuesta.
El resultado sera distinto si el fichero fich no existiese previamente?
d) Repetir la pregunta anterior para la poltica de actualizacin write-back (escritura diferida).
e) Calcule la mejor y la peor tasa de aciertos en la cache que se puede producir durante un bucle de lectura,
para los dos casos siguientes: cache de 1 MiB y cache de 16 MiB.
f) Seleccionar el mecanismo de sincronizacin adecuado y plantear el cdigo necesario para garantizar que el
cdigo escritor ejecuta su primer fcntl antes que ningn cdigo lector ejecute el open.

SOLUCIN
a) Cada bloque permite 2 KiB / 4 B = 512 direcciones.
nodo_i almacena 12 direcciones
Indirecto simple almacena 512 direcciones = 0,5 K direcciones
Indirecto doble almacena 512*512 direcciones = 256 K direcciones
Tamao mximo del fichero = 2 KiB(12 + 0,5 K + 256 K) = 513 MiB + 24 KiB
b) El Escritor da al fichero un tamao de DATA_SIZE*N y establece un cerrojo de tamao DATA_SIZE, antes de
que cualquier Lector pueda establecer su cerrojo. Seguidamente escribe en el bloque y ampla el cerrojo al siguiente
bloque de tamao DATA_SIZE. Finalmente, libera el cerrojo del bloque escrito. Por tanto, los Lectores no pueden
adelantar al Escritor.
c) Al truncar el fichero se le da un tamao de 2 MiB, lo que supone 1 K bloque. El Escritor escribe de forma secuen -
cial todo el fichero en bloques de 1K.
Analizaremos primero el bucle del escritor.
Accesos debidos a los datos: Dada la poltica write-through, cada escritura produce un acceso al disco, por
tanto, se producen 2.048 accesos a disco. Adems, como el fichero tiene inicialmente 8 bloques las escrituras
0, 2, 4, 6, 8, 10, 12 y 14 procen una lectura del bloque afectado, para su modificacin.Por tanto, hay 1.056 ac-
cesos.
Accesos debidos a metadatos:
Mapa nodos_i: Cada bloque que se va asignado supone una escritura en el mapa de nodos_i. Como se ac-
ceden a 1024 bloques, pero el fichero ya tena 8 bloques, se realizan 1.016 accesos para modificar el
mapa de bits.
Direcciones de bloques. Los 12 primeros estn el nodo_i. Se modifica el nodo_i por cada nuevo bloque
asignado, es decir, hay 12-8 = 4 escrituras. La direccin de los siguientes 512 bloques se almacena en el
indirecto simple, por tanto, son 513 accesos: uno para incluir en el nodo_i la direccin de indirecto sim-
ple y 512 para ir inclyendo en dicho bloque indirecto los nuevos bloques de datos asignados. Las direc -
ciones de los 1024 512 -8 = 504 bloques restantes implican un bloque indirecto doble, por tanto, hay
que modificar su direccin en el nodo_1, el indirecto doble, para el nuevo bloque de direcciones y este
ltimo 504 veces para los nuevos 504 nuevos bloque de datos, es decir, 506 acceso
El total es de 4 + 513 + 506 = 1.023 accesos.
Por tanto, el total de accesos debidos a los metadatos es de: 1.016 + 1.023 = 2.029.
En relacin con los accesos debidos a los bucles lectores hay que hacer notar que se dispone de una cache que
permite almacenar aproximadamente la mitad del fichero (habra que descontar los tres bloques de direcciones, as
como los bloques del mapa de bits afectados y el del nodos_i, que teambin residirn en memoria). Esto significa,
253
que, a menos que se retrasase mucho en su ejecucin algn lector con respecto al escritor, los lecotres siempre en -
contrarn en memoria tanto los blques de datos como la metainformacin necesaria, no generando accesos al disco.
d) Si la escritura es write-back, las escrituras de datos no se trasladan al disco hasta que los bloques sean expulsados
de la cache (sin embargo, se leeran los 8 primeros bloques que tiene el fichero inicialmente). Como las escrituras
afectan a 1024 bloques de datos, se necesitan 1024 accesos a disco. Es de notar que hasta que no se ha llegado apro-
ximadamente a la miltad del fichero no se producen escrituras de datos al disco, puesto que se dispone de suficiente
memoria cahe. A partir de ese momento se irn expuldando bloques, producindose los consiguientes accesos a dis-
co. Ms adelante, cuando se produzca un sync se volcarn las dems.
Normalmente la metainformacin utiliza, por seguridad, una tcnica write-through, por lo que se aplica lo visto
en la seccin anterior. En caso de aplicar write-back a la metainformacin los accesos seran uno por le nodo_i, otro
por cada uno de los tres bloques dedicados a direcciones y el o los bloques del mapa debits afectado. Se observa que
es mucho menor que en el caso anterior, pero mucho menos fiable frente a una cada del sistema.
e) Con una cache de 16 MiB cabe toda la informacin del fichero y de su metainformacin en la misma, por lo que
la tasa de aciertos en el bucle de lectura es del 100%.
Con una cache de 1 MiB, como se ha indicado anteriormente es muy probable que la tasa de aciertos tambin sea
del 100%. Sin embargo, si un lector se retrasa mucho frente al escritor y a los otros lectores, puede encontrar en la
cache la ltima mitad del fichero, por lo que ir sustituyendo bloques continuamente, dando una tasa de aciertos del
0%. En relacin con la metainformacin la tasa de aciertos ser sin embargo del 100% o muy prxima al 100%,
puesto que el bloque indirecto simple puede haber sido expulsado.
f) El mecanismo a utilizar depende que lectores y escritores ejecuten como procesos o como threads. En el pri-
mer caso utilizaremos semforos y el en el segundo mutex y condiciones. Una solucin para el caso primero sera la
siguiente:
Cada proceso escritor o lector crea un semforo con el contador a cero:
sem_t * inicio;
inicio = sem_open("INIT", O_CREAT, 0700, 0);
Realmente, el nico que crea el semforo es el primero que ejecuta el sem_open. Los demas simplemente lo
abren, con los valores que tenga.
El proceso Escritor incrementa el contador en una unidad justo despus de su primer fcntl
....
fcntl(fd, F_SETLKW, &fl);
sem_post(inicio); //el proceso Escritor abre el semforo
sem_close(inicio); //el proceso Escritor cierra el semforo, puesto que yo no
//lo vuelve a utilizar
....
Cada proceso lector se queda esperando en el semforo justo antes de abrir el fichero. Seguidamente incrementa
el samforo para que otro lector pueda entar:
....
fl.l_pid = getpid();
sem_wait(inicio); //el proceso Lector espera por el semforo
sem_post(inicio); //el proceso Lector abre el semforo
sem_close(inicio); //el proceso Lector cierra el semforo, puesto que yo no
//lo vuelve a utilizar
fd = open(fich, O_RDONLY);
//lo vuelve a utilizar
....
La solucin planteada no contempla la eliminacin del semforo, es dicir no inclue el sem_unlink. El semforo
se queda en el sistema y con valor 1. Una alternativa de diseo sera plantear un proceso que crease el semforo con
valor 0, que luego generase los procesos Escritor y Lectores, que esperase por la finalizacin de todos ellos y que, fi-
nalmente, eliminase el semforo.

Problema 4.19 (junio 2011)

Estamos desarrollando la aplicacin de un cajero automtico. En concreto nos ha encargado la operacin de reti-
rada de dinero. Se descompone la operacin en los siguientes pasos:
254 Problemas de sistemas operativos
Pasos genricos del cajero, (partiendo de la pantalla de bienvenida):
Deteccin y lectura de la tarjeta de crdito.
Lectura del pin de acceso y validacin con la propia tarjeta. Al tercer error el cajero da por finalizada la se -
sin, se queda con la tarjeta y muestra un mensaje de que el usuario debe ponerse en contacto con su banco.
Seleccin de la operacin por parte del usuario.
Pasos de la operacin de retirada de dinero (esta parte es la que nos han encargado):
Seleccin del importe.
Confirmacin de la orden por el usuario.
Comprobacin de que existen fondos en el cajero y en la cuenta del usuario.
Emisin del dinero.
Emisin del recibo.
Desea realizar una nueva operacin?
.
Expulsin de la tarjeta de crdito.
Pantalla de bienvenida
a) Indique justificadamente qu mecanismo de comunicacin utilizara entre la aplicacin del cajero y la aplica-
cin del sistema central que atiende a los cajeros.
b) Indique justificadamente la estructura de la aplicacin del sistema central que atiende a los cajeros del ban-
co.
c) Indique justificadamente qu mecanismo de sincronizacin utilizara para resolver los problemas de carrera
que pueden surgir por parte de los cajeros.
d) Indique en qu puntos de la operacin del cajero indicados en el enunciado incluira los mecanismos anterio-
res.

SOLUCIN
a) Deseamos una comunicacin fiable entre el programa del cajero y el del sistema central, por lo que se utilizarn
sockets TCP (stream).
b) Se trata de una clsica aplicacin cliente-servidor en la que el servidor ejecuta en el sistema central y el cliente en
el cajero. Dado que deseamos que se puedan atender a muchos cajeros simultneamente, el servidor deber generar
un proceso o thread por cliente, que se encargue de atenderle de forma dedicada.
c) En esta aplicacin nos encontramos con los siguientes problemas de carrera:
1. Hay que comprobar que hay suficiente dinero en el cajero. Como solamente hay un usuario por cajero,
esta comprobacin no es concurrente, por lo que no plantea problemas de carrera.
2. Dado que se han de modificar los datos bancarios de la cuenta afectada por la operacin, hay que garan-
tizar que el acceso a los mismos se haga de forma exclusiva: desde el momento en el que se leen, para
comprobar que hay saldo, hasta el momento en el que quedan modificados.
3. Durante la emisin de los billetes se pueden producir problemas, puesto que la mquina expendedora se
puede atascar o la corriente se puede cortar. Por tanto, es necesario garantizar que quedan anotados los
billetes que realmente se expiden.
4. El que se imprima o no el recibo no es excesivamente problemtico, por tanto esta operacin no es ne-
cesario protegerla.
El mecanismo de sincronizacin ms adecuado en esta aplicacin es la transaccin para resolver los problemas 2
y 3. Se trata, en este caso, de una transaccin distribuida, puesto que afecta tanto al cajero como al sistema central.
d) El inicio de la transaccin se debe establecer al recibirse la confirmacin de la operacin por parte del usuario
(cuando se lea que ha pulsado la tecla de confirmacin). El final de la transaccin se debe producir cuando se ha ter-
minado de expender todos los billetes. Ahora bien, como nos podemos quedar a mitad de la emisin de los billetes,
la solucin real tiene mayor complejidad.
Una alternativa es anidar, dentro de la transaccin anterior, una transaccin adicional por cada billete. En caso de
fracasar una de esas transacciones se aborta la emisin de ms billetes y se completa la transaccin principal refle-
jando solamente el dinero realmente emitido.
255

Problema 4.20 (junio 2011)

Sea un sistema de reservas de consultas mdicas por red para un centro mdico. El servidor de reservas generar
un proceso reserva de acceso a la tabla de reservas especfico por cada cliente que se conecte para hacer una re-
serva y que le atiende durante toda la reserva. Las consultas tienen una duracin de 10 minutos y la tabla de reser -
vas est organizada por mdico, mes, da, y hora.
El proceso de reservas es el siguiente:
El usuario selecciona un mdico.
Se le presenta una tabla con las horas de consulta libres, desde el primer da que tiene un hueco libre
hasta 5 das laborables ms.
El usuario selecciona un hueco y pulsa Reservar.
Se le confirma la reserva con una interfaz que incluye un botn para imprimir la reserva.
a) Indicar justificadamente en qu puntos de la secuencia anterior debern introducirse los mecanismos de sin-
cronizacin que eviten las condiciones de carrera.
b) Supondremos primero que la tabla de reservas se almacena en un conjunto de ficheros, teniendo cada fichero
las reservas de un mdico y un mes, y que el mecanismo de sincronizacin es el cerrojo. Plantear una solucin en
pseudocdigo, destacando el uso de los mecanismos de sincronizacin.
c) Supondremos ahora que la tabla de reservas se almacena en memoria. Seleccionar los mecanismos de sincro -
nizacin ms adecuados y plantear una solucin en pseudocdigo, destacando el uso de los mecanismos de sincro -
nizacin.

SOLUCIN
a) Para decidir en qu punto se deben poner los mecanismos de sincronizacin primero hay que determinar el com -
portamiento que queremos. Por un lado, queremos que el sistema tenga la mnima contencin, es decir, que permita
la mxima concurrencia. Por otro lado, tenemos que garantizar que dos usuarios no obtienen la misma cita (mdico,
da y hora).
El proceso tiene dos partes. Primero hay que leer uno o ms ficheros para encontrar las consultas libres (puede
que se tenga que acceder a dos ficheros de meses consecutivos para completar los 6 das laborables necesarios para
construir la tabla que se presenta al usuario). Seguidamente, una vez realizada la seleccin por el usuario, hay que
escribir el fichero para marcar la cita como comprometida.
Dado que un usuario puede tardar mucho en decidir qu cita seleccionar (incluso puede dejar la aplicacin colga-
da), no parece que tenga sentido el considerar que la seccin crtica incluya las lecturas ms la posible escritura.
Es de destacar que si protegemos la seccin de lectura del programa de forma independiente de la escritura (si -
guiendo el clsico modelo lector-escritor) no tenemos ninguna garanta, a la hora de formalizar la reserva, de que
otro usuario ms rpido no haya seleccionado ya la misma consulta. La proteccin de la lectura en el modelo lector-
escritor evita simplemente que el lector pueda leer un estado inconsistente de la informacin. En nuestro caso, la
proteccin de la lectura no es necesaria puesto que no puede retornar un estado inconsistente, dado que la operacin
de reserva consiste en una nica escritura en el fichero de consultas incluyendo en el registro de una consulta el
nombre del paciente, escritura que se hace de forma atmica.
Se propone entonces proteger el fichero correspondiente desde que se recibe el Reservar hasta que se graba en
el mismo. El problema que puede aparecer es que la consulta haya sido asignada a otro usuario que no se demor
tanto en la seleccin. Para garantizar un funcionamiento correcto, la seccin crtica, antes de escribir en el fichero,
deber volver a leerlo, para comprobar que la consulta sigue estando libre. En caso contrario deber informar al
usuario para que realice otra seleccin.
Igual ocurre en el caso de que la tabla de consultas resida en memoria.
b) Al haber seleccionado una consulta, ya queda definido el fichero a modificar (definido por el conjunto mdico-
mes) as como la regin dentro del fichero (definido por el conjunto da-hora). La secuencia del proceso Reserva
quedara como sigue:
struct consult{
long fechahora;
char paciente[50];
};
256 Problemas de sistemas operativos

En el servidor principal
.....
struct consult consulta;
<<Mediante un fork se crea el servidor particular de cada cliente>>
.....

En cada servidor particular


.....
<<Seleccin del mdico y de la consulta a reservar>>
MedicoMes = <<Nombre del fichero con la reserva deseada>>
PosicionConsulta = <<Posicin de la reserva deseada dentro del fichero>>
<<El usuario pulsa aceptar>>
fd = open(MedicoMes, O_RDWR);
fl.l_whence = SEEK_SET;
fl.l_start = PosicionConsulta;
fl.l_len = sizeof(consulta);
fl.l_pid = getpid();
fl.l_type = F_WRLCK
fcntl(fd, F_SETLKW, &fl); //Se espera por el cerrojo si est cogido
lseek (fd, PosicionConsulta, SEEK_SET);
read (fd, &consulta, sizeof(consulta));
if (strcmp(consulta.paciente,"") == 0) { //Sigue libre la consulta
lseek (fd,PosicionConsulta, SEEK_SET);
strcpy(consulta.paciente, usuario); //se aade el nombre del paciente a la consulta
write (fd, &consulta, sizeof(consulta));
reserva = 1; //Indicamos que la reserva se ha realizado correctamente
} else { //La consulta ha sido ocupada y no se puede hacer
reserva = 0; //Indicamos que la reserva NO se ha realizado
};
fl.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &fl); //se libera el cerrojo
<<Si reserva == 0 hay que repetir la seleccin>>
.....
c) Dado que nos dicen que el servidor genera un proceso por cada cliente, dichos procesos son emparentados, por lo
que se pueden utilizar semforos sin nombre. Por lo mencionado en la seccin a), necesitaremos un semforo para
proteger cada zona de la tabla que pueda ser accedida al tiempo, de forma que solamente un proceso reserva pueda
realizar las operaciones de comprobar que la consulta sigue libre y de asignarla.
Dependiendo del grado de granularidad que queramos debemos generar ms o menos semforos. Por ejemplo, si el
grano es toda la tabla, necesitaremos un solo semforo. Si el grano es la tabla de un mdico y un mes, necesitamos
un semforo por mdico-mes. Si el grano es mdico da, necesitamos un semforo por mdico y da. Evidentemente,
mientras mayor sea el grano mayor ser la contencin. En nuestro caso, el cdigo de la seccin crtica (comproba -
cin-reserva) es muy rpido, por lo que los procesos permanecen en la seccin crtica muy poco tiempo, producien-
do muy poca contencin, por lo que consideremos que la granularidad mdico-mes es la adecuada.
Vamos a suponer que la informacin en memoria cubre 6 meses (habr una operacin mensual que corra la ven-
tana de tiempo de la aplicacin, de forma que siempre se tengan residentes en memoria el mes actual y los cinco me -
ses siguientes). Tambin supondremos que tenemos MEDICOS mdicos y que seleccionamos granularidad mensual.
Por simplicidad consideraremos que todas las tablas de mes tienen 31 das y 60 consultas por da.
struct consult{
long fechahora;
char paciente[50];
};

En el servidor principal
.....
struct consult *consulta;
sem_t MedMes[6][MEDICOS];
for (i=0; i<6; i++) //Inicializamos los semforos
for(j=0; j<MEDICOS; j++)
257
sem_init(&MedMes[i][j], 1, 1); //Los semforos son compartido y binarios
tamagno = 6*MEDICOS*31*60*sizeof(struct consult);
//Generamos la regin compartida con las tablas de reservas. El uso de MAP_ANON evita el tener que
//utilizar un fichero y resulta en una regin sin nombre.
org=mmap(NULL, tamagno, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1,0);
<<Se inicializa la tabla de reservas>>
.....
<<Mediante un fork se crea el servidor particular de cada cliente>>
.....

En cada servidor particular


....
<<Seleccin del mdico y de la consulta a reservar>>
Medico=<<Nombre del mdico seleccionado>>
Mes=<<Mes de la consulta seleccionada>>
Dia=<<Da de la consulta seleccionada>>
Hora=<<ndice de la hora de la consulta seleccionada>>
<<El usuario pulsa aceptar>>
sem_wait (&MedMes[Mes][Medico]);
posicion=org + Medico*Mes*Dia*Hora*sizeof(struct consult);
consulta = (struct consult *) posicion; //Seleccionamos la consulta
if (strcmp(consulta->paciente,"") == 0) { //Sigue libre la consulta
strcpy(consulta->paciente,usuario); //se aade el nombre del paciente a la consulta
reserva = 1; //Indicamos que la reserva se ha realizado correctamente
} else { //La consulta ha sido ocupada y no se puede hacer
reserva = 0; //Indicamos que la reserva NO se ha realizado
};
sem_post (&MedMes[Mes][Medico]);
<<Si reserva == 0 hay que repetir la seleccin>>
....

Problema 4.21 (junio 2011)

Queremos disear un sistema para jugar a los barquitos. Cada usuario primeramente posicionar sus barcos en su
consola. Seguidamente, y en turnos alternos, cada usuario marcar su disparo y, automticamente, se le indicar si
ha cado en agua o ha tocado un barco contrario. Se plantean tres posibles soluciones:
La primera consiste en un computador con dos consolas. Cada consola est atendida por un proceso.
a. Determinar los mecanismos de comunicacin se podran utilizar entre los dos procesos.
b. Indicar las ventajas e inconvenientes que tendra cada uno de ellos para esta aplicacin.
c. Para cada mecanismo de comunicacin anterior indicar cmo se consigue alternancia en los turnos.
La segunda es como la primera pero cada consola est atendida por un thread.
d. Seleccionar el mecanismo de comunicacin ms adecuado en este caso.
e. Indicar cmo se consigue la alternancia.
La tercera solucin consiste en tener dos computadores personales comunicados por wifi.
f. Seleccionar el mecanismo de comunicacin ms adecuado en este caso.
g. Indicar cmo se consigue la alternancia.
h. Indicar, adicionalmente, el esquema de procesos involucrados en la aplicacin.

SOLUCIN
a, b y c) Se podra utilizar uno cualquiera de los siguientes mecanismos:
Pipes. Exige que los que los procesos que atienden ambas consolas estn emparentados, bien porque el de la
consola B es hijo del de la consola A, bien porque ambos sean hijos del proceso que inicia el juego. Es la solucin
258 Problemas de sistemas operativos
ms adecuada, ya que es un mecanismo simple y bloqueante. El pipe permite sincronizar los procesos que los utili -
zan, en este caso para garantizar la alternancia de los jugadores, con un esquema como el siguiente:
Consola A que inicia el juego
primeravez = 1;
while (jugando){
if (!primeravez) {
read (pipe2[0], &jugada, sizeof(jugada));
<<Genera resultado jugada del contrario>>
write (pipe1[1], &resultado, sizeof(resultado));
}
primeravez = 0;
<<Compone jugada>>
write (pipe1[1], &jugada, sizeof(jugada));
read (pipe2[0], &resultado, sizeof(resultado));
<<Muestra resultado jugada y comprueba si ha terminado el juego>>
}
Consola B
while (jugando) {
read (pipe1[0], &jugada, sizeof(jugada));
<<Genera resultado jugada del contrario>>
write (pipe2[1], &resultado, sizeof(resultado));
<<Compone jugada>>
write (pipe2[1], &jugada, sizeof(jugada));
read (pipe1[0], &resultado, sizeof(resultado));
<<Muestra resultado jugada y comprueba si ha terminado el juego>>
}
FIFOS. Se puede utilizar aunque los procesos no estn emparentados. La forma de sincronizar los procesos sera
muy similar a la propuesta para los pipes, puesto que utilizan igualmente los servicios read y write bloqueantes.
Memoria compartida. En este caso es necesario utilizar un mecanismo de sincronizacin para ir garantizando el
turno. Como se trata de procesos podemos utilizar semforos (con o sin nombre dependiendo de que los procesos es-
tn emparentados. Nos basta un semforo binario por consola, que est inicializado a 1 para el caso de la consola
que juega primero y a cero para la otra. El esquema sera el siguiente:
Para iniciar el juego
struct jug{
int X;
int Y;
int Resultado;
};
.....
struct jug *JugadaA, *JugadaB;
sem_t ConsolaA;
sem_t ConsolaB;
sem_init(&ConsolaA, 1, 1); //Los semforos son compartido y binarios
sem_init(&ConsolaB, 1, 0);
//Generamos la regin compartida con espacio para dos jugadas
jugadas = sizeof(struct jug);
JugadaA=mmap(NULL, 2*jugadas, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1,0);
JugadaB = JugadaA + 1; //Avanzamos el tamao de la estructura jug
.....
<<Mediante un fork se crea el proceso de la otra consola>>
.....
Consola A que inicia el juego
primeravez = 1;
while (jugando) {
sem_wait (&ConsolaA);
if (!primeravez) {
<<Muestra resultado jugada de acuerdo al valor de JugadaA->Resultado>>
<<Calcula el resultado de la jugada de A y lo pone en JugadaB->Resultado>>
<<Comprueba si ha terminado el juego>>
<<Compone jugada generando MiX y MiY>>
}
259
primeravez = 0;
JugadaA->X = MiX;
JugadaA->Y = MiY;
sem_post (&ConsolaB);
}
Consola B
while (jugando) {
sem_wait (&ConsolaB);
<<Muestra resultado jugada de acuerdo al valor de JugadaB->Resultado>>
<<Calcula el resultado de la jugada de B y lo pone en JugadaA->Resultado>>
<<Comprueba si ha terminado el juego>>
<<Compone jugada generando MiX y MiY>>
JugadaB->X = MiX;
JugadaB->Y = MiY;
sem_post (&ConsolaA);
}
Sockets de cualquiera de los tipos stream o datagrama en dominio UNIX o INET. Dentro de un computador no
hay problema sde prdidas de paquetes, ni de duplicados, por lo que los sockets datagrama constituyen una buena
solucin, siendo ms ligeros que los stream. Dado que las operaciones de send y recv pueden ser bloqueantes, se
puede lograr la alternancia con un esquema similar al planteado para los pipes. En la seccin f se detalla la solucin.
Ficheros. Evidentemente podemos comunicar los procesos a travs de un fichero, pero no tiene ninguna ventaja,
puesto que exige sincronizacin como en el caso de memoria compartida y es menos eficiente que sta, puesto que
hay que hacer ms llamadas al sistema operativo (operaciones lseek, read y write).
d y e) En el caso de los threads el mecanismo natural de comunicacin es la memoria, dado que se comparte toda la
imagen de memoria. Se definen dos estructuras jug, JugadaA y JugadaB como variables globales.
Para sincronizar los threads se podran utilizar semforos sin nombre con el esquema planteado en la seccin c).
Sin embargo, podemos utilizar tambin mutex y condiciones con el esquema siguiente.
pthread_mutex_t mutex; //Control de acceso
pthread_cond_t a_jugarA, a_jugarB;//Condiciones de espera
int juegaA =1, juegaB=0; //Estado del acceso, empieza a jugar el A
Consola A que inicia el juego
primeravez = 1;
while (jugando) {
pthread_mutex_lock(&mutex);
while(juegaA !=0) //cond. espera
pthread_cond_wait(&a_jugarA, &mutex);
juegaA=0;
pthread_mutex_unlock(&mutex);
if (!primeravez) {
<<Muestra resultado jugada de acuerdo al valor de JugadaA->Resultado>>
<<Calcula el resultado de la jugada de B y lo pone en JugadaB->Resultado>>
<<Comprueba si ha terminado el juego>>
<<Compone jugada generando MiX y MiY>>
}
primeravez = 0;
JugadaA->X = MiX;
JugadaA->Y = MiY;
pthread_mutex_lock(&mutex);
juegaB=1;
pthread_cond_signal(&a_jugarB);
pthread_mutex_unlock(&mutex);
}
Consola B
while jugando {
pthread_mutex_lock(&mutex);
while(juegaB !=0) //cond. espera
pthread_cond_wait(&a_jugarB &mutex);
juegaB=0;
pthread_mutex_unlock(&mutex);
260 Problemas de sistemas operativos
<<Muestra resultado jugada de acuerdo al valor de JugadaB->Resultado>>
<<Calcula el resultado de la jugada de A y lo pone en JugadaA->Resultado>>
<<Comprueba si ha terminado el juego>>
<<Compone jugada generando MiX y MiY>>
JugadaB->X = MiX;
JugadaB->Y = MiY;
pthread_mutex_lock(&mutex);
juegaA=1;
pthread_cond_signal(&a_jugarA);
pthread_mutex_unlock(&mutex);
}
f, g y h) Al tratarse de un sistema distribuido, el mecanismo de comunicacin es el socket en el dominio INET. Aun-
que estemos en una red local (wifi) la fiabilidad de estas redes no es muy grande, por lo que se seleccionan los so -
cket de tipo stream.
En este caso, los procesos son totalmente independientes, por lo que primero se tienen que poner de acuerdo para
realizar la conexin TCP. La solucin ms simple es que uno de ellos sea el maestro y acte como servidor, estando
a la espera de que se conecte el otro proceso que actuar como cliente. Llamando A al que hace de servidor, el es -
quema de procesos que podemos plantear es uno de los dos mostrados en la figura Error: Reference source not
found. En este caso, puesto que solamente hay un posible cliente, la solucin 2, que implica un servidor exclusivo,
es la ms adecuada, aunque la solucin clsica del servidor delegado tambin servira.
Una vez establecido el socket, la alternancia se consigue de forma similar a con pipes, utilizando servicios blo-
queantes:
Consola A que inicia el juego
primeravez = 1;
while (jugando) {
if (!primeravez) {
recv(cd, &jugadaB, sizeof(jugada), 0); //Bloqueante
<<Genera resultado jugada del contrario>>
send(cd, &jugadaB, sizeof(jugada), 0); //Bloqueante
}
primeravez = 0;
<<Compone jugada>>
send(cd, &jugadaA, sizeof(jugada), 0); //Bloqueante
recv(cd, &jugadaA, sizeof(jugada), 0); //Bloqueante
<<Muestra resultado jugada y comprueba si ha terminado el juego>>
}
Consola B
while (jugando) {
recv(cd, &jugadaA, sizeof(jugada), 0); //Bloqueante
<<Genera resultado jugada del contrario>>
send(cd, &jugadaA, sizeof(jugada), 0); //Bloqueante
}
<<Compone jugada>>
send(cd, &jugadaB, sizeof(jugada), 0); //Bloqueante
recv(cd, &jugadaB, sizeof(jugada), 0); //Bloqueante
<<Muestra resultado jugada y comprueba si ha terminado el juego>>
}
261

IP 1 IP 2 IP 1 IP 2
Servidor Cliente Servidor Cliente
exclusivo
A B A B

Servidor
delegado
A

Solucin 1 Solucin 2
Figura 4.5

Problema 4.22 (enero 2012)

Se desea disear una aplicacin de trabajo colaborativo por Internet. Una de las funciones a incluir es una aplica -
cin de edicin que permita trabajar a varios usuarios simultneamente sobre un mismo documento. Otra es una
agenda para reserva de la sala de reuniones.
a) Uno de los diseadores plantea una solucin cliente-servidor, mientras que otro aboga por una solucin P2P.
Indicar las ventajas e inconvenientes que presentan estas dos soluciones para implementar la aplicacin de edicin.
b) El grupo de diseadores opta por la solucin cliente-servidor, que debemos concretar. Realizar una grfica
que represente la mquina que albergue al servidor as como tres mquinas con clientes, mostrando los procesos
involucrados en la aplicacin de edicin y la relacin entre ellos. Justificar la solucin planteada.
Para la agenda de reservas se decide utilizar un fichero en la mquina servidora que contenga el mes en curso y
los dos meses siguientes. El da uno de cada mes se lanza un proceso de mantenimiento que rescribe el fichero, eli -
minado el mes que acaba de terminar y aadiendo otro mes.
c) Un diseador sugiere emplear un proceso servidor con funcionamiento serie (hay un solo proceso que atiende
de forma secuencial a los clientes), mientras que otro plantea la solucin clsica que utiliza un proceso para aten-
der a cada cliente. Indicar las razones que alega el diseador que sugiere el servidor con funcionamiento serie y la
replica que realiza el que sugiere la solucin clsica.
El grupo se decanta por la solucin clsica.
d) Indicar el mecanismo de comunicacin a utilizar entre los clientes y el servicio.
e) Determinar los posibles problemas de concurrencia que existen (tener en cuenta tanto los clientes como el
proceso de mantenimiento).
f) Seleccionar los mecanismos de sincronizacin a utilizar para resolver los problemas de concurrencia, indi-
cando las razones en las que se basa la seleccin.
g) Plantear el pseudocdigo de sincronizacin necesario en los distintos procesos, incluyendo el de manteni-
miento.

Solucin
a) Tanto la edicin de un documento como la gestin de la agenda de reservas exige que los usuarios tengan acceso
a las actualizaciones que realicen otros usuarios. Esto es mucho ms fcil de conseguir en una solucin centralizada,
en la que se dispone de un servidor que almacena dichos documentos, que con un esquema P2P. Por otro lado, el n-
mero de usuarios que se puede esperar no es muy elevado, dado que es impensable tener cientos de personas traba-
jando sobre un mismo documento en un instante determinado o accediendo a la agenda, por lo que la solucin
centralizada cliente-servidor no plantea problemas de congestin.
b) Se podran plantear soluciones diferentes para el editor y para la agenda. Seleccionamos la solucin de servidor
dedicado, por lo que el esquema de procesos involucrados es la de la figura 4.6. Es de notar que los servidores dedi-
cados deben sincronizarse entre s, puesto que acceden al mismo documento.
262 Problemas de sistemas operativos
c) El uso de un servidor serie para la agenda elimina la concurrencia, puesto que tendramos un solo cliente acce -
diendo a la misma en cada instante. Sin embargo, esta solucin no parece aceptable puesto que introduce una con -
tencin inadmisible (el cliente que est accediendo a la agenda puede dejar al servidor esperando indefinidamente).
La solucin clsica evita la contencin, pero, al presentar concurrencia, debe ser diseada de forma que se imposibi -
liten las condiciones de carrera.
d) Nos indican que es una solucin Internet, es decir, distribuida en WAN. Por lo tanto, se requiere un mecanismo de
comunicacin distribuido y fiable, es decir, sockets AF_INET de tipo Stream, es decir con protocolo TCP.
e) Los problemas de concurrencia ocurren entre los servidores dedicados de la agenda y entre estos y el proceso de
mantenimiento. Es necesario evitar que dos clientes puedan reservar la misma hora del mismo da, y es necesario
que el proceso de mantenimiento acceda de forma exclusiva a todo el fichero para reescribirlo correctamente.

Servidor
Documento

Servidor Servidor Servidor


dedicado dedicado dedicado

Cliente Cliente Cliente

Figura 4.6
e) Los problemas de concurrencia ocurren entre los servidores dedicados de la agenda y entre estos y el proceso de
mantenimiento. Es necesario evitar que dos clientes puedan reservar la misma hora del mismo da, y es necesario
que el proceso de mantenimiento acceda de forma exclusiva a todo el fichero para reescribirlo correctamente.
f) Los servidores dedicados de la agenda afectan a una pequea parte del fichero de reservas, la parte donde se mar -
ca la reserva de la hora y da. Por otro lado, se trata de procesos y no threads, que acceden a un fichero compartido,
por lo que los mecanismos de sincronizacin disponibles son fundamentalmente dos: el semforo y el cerrojo. Dado
que cada reserva afecta a una pequea zona del fichero, la solucin ms adecuada para sincronizar los servidores de-
dicados es la basada en cerrojos. Por otro lado, el proceso de mantenimiento requiere acceso exclusivo a todo el fi-
chero, por lo que es incompatible con los servidores dedicados. En este caso, se podra utilizar indistintamente como
mecanismo de sincronizacin un semforo o un cerrojo sobre todo el fichero. Otra solucin consistira en parar el
servicio de agenda cuando se lance el proceso de mantenimiento, operacin que se podra plantear durante el fin de
semana.
g) Los servidores dedicados leen y escriben en el fichero. Hay que determinar si se trata de un problema tipo lector-
escritor o es un problema simplemente de escritores.
La secuencia de reserva es la siguiente: el usuario deber ser presentado con las reservas disponibles (por ejem -
plo, las de una semana), lo que requiere leer del fichero (el proceso acta como lector). El usuario selecciona la hora
y pulsa aceptar. Se modifica el fichero (el proceso acta como escritor) y se informa al usuario del xito de la opera -
cin.
Si utilizamos una solucin tipo lector-escritor en la fase de lectura debemos establecer un cerrojo compartido so -
bre toda la semana leda. Dos o ms procesos podran leer la misma semana. Sin embargo, a la hora de seleccionar
una reserva se veran imposibilitados de realizar la escritura hasta que ningn proceso tuviera un cerrojo sobre la
zona (la escritura requiere un cerrojo exclusivo). Esto producira una alta contencin en el sistema, lo que no es re-
comendable.
Si utilizamos una solucin de escritores, en donde solamente los escritores solicitan cerrojos, se puede dar la si -
tuacin de que varios usuarios reserven simultneamente una misma hora, que se les ha presentado como libre. Sin
embargo, solamente uno ser capaz de establecer el cerrojo exclusivo, realizando realmente la reserva. Para todos
los dems la reserva fracasar. Un aspecto adicional a considerar es si en esta solucin la operacin de lectura puede
producir informacin inconsistente. Este no es el caso, puesto que la reserva se puede hacer con una sola operacin
de escritura en el disco, que es una operacin atmica, por lo que la informacin contenida en el fichero ser siem-
pre consistente.
Por otro lado, el proceso de mantenimiento requiere acceso exclusivo a todo el fichero, puesto que se modifica
en su totalidad. Es muy posible que la actualizacin del fichero se realice mediante una serie de operaciones de lec -
263
tura y escritura, lo que podra producir estados inconsistentes en el mismo durante algunos instantes, por lo que sera
conveniente parar el servicio mientras se hace el mantenimiento. En el caso de parar el servicio no sera necesario
incluir en proceso de mantenimiento ningn mecanismo de sincronizacin adicional.
El cdigo de sincronizacin de los servidores dedicados es el siguiente:
int fd;
struct flock fl;
fd = open("Agenda", O_RDWR);
<<Presentacin de agenda>>
<<Seleccin reserva>>
fl.l_whence = SEEK_SET;
fl.l_start = comienzo; //comienzo:variable que indica el comienzo de la seccin a modificar
fl.l_len = longitud; //longitud: variable con el n de bytes a modificar
fl.l_pid = getpid();
fl.l_type = F_WRLCK; //cerrojo exclusivo
fcntl(fd, F_SETLKW , &fl); //establece cerrojo de forma sncrona
//Comprobamos si sigue libre la reserva
lseek(fd, comienzo, SEEK_SET);
read (fd, &val, longitud); //obtenemos la reserva. val: contiene la informacin de la reserva
if (reservalibre(val)) { //Reserva libre u ocupada
<<Se introducen los datos de la reserva en val>>
lseek(fd, comienzo, SEEK_SET);
write(fd, &val, longitud);
fl.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &fl); //se libera el cerrojo
<<Avisar que la operacin se ha realizado>>
} else {
<<Avisar que la operacin ha fracasado>>
}
El cdigo de sincronizacin del proceso de mantenimiento, para el caso de usar cerrojos, sera el siguiente:
int fd;
struct flock fl;
fd = open("Agenda", O_RDWR);
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0; //cerrojo sobre todo el fichero
fl.l_pid = getpid();
fl.l_type = F_WRLCK; //cerrojo exclusivo
fcntl(fd, F_SETLKW, &fl); //establece cerrojo de forma sncrona
<<operacin de mantemiento>>
fl.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &fl); //se libera el cerrojo

Problema 4.23 (enero 2012)

Un sistema de ficheros de tipo UNIX, (diseado para un pequeo sistema porttil) presenta las siguientes carac -
tersticas:
Representacin de ficheros mediante nodos-i con 12 direcciones directas a bloque, un indirecto simple y un
indirecto doble. Direcciones de bloque de 4 bytes.
El tamao del bloque del sistema de ficheros es de 2 KiB y emplea una cache de 1 MiB con una poltica de
reemplazo LRU.
Sobre este sistema se ejecutan un proceso escritor y cuatro procesos lectores con los fragmentos de cdigo indi-
cados ms adelante.

Escritor Lector
264 Problemas de sistemas operativos

#define DATA_SIZE 1024 #define DATA_SIZE 1024


#define N 2048 #define N 2048
char buffer[DATA_SIZE]; char buffer[DATA_SIZE];
int fd, i; int fd, i;
struct flock fl; struct flock fl;
fl.l_whence = SEEK_SET; fl.l_whence = SEEK_SET;
fl.l_start = 0; fl.l_len = DATA_SIZE;
fl.l_len = DATA_SIZE; fl.l_pid = getpid();
fl.l_pid = getpid(); fd = open(fich, O_RDONLY);
fl.l_type = F_WRLCK;
fd = open(fich, O_CREAT | O_WRONLY, for (i = 0 ; i < N; i++) {
0666); fl.l_start = DATA_SIZE*i;
ftruncate(fd, DATA_SIZE*N); fl.l_type = F_RDLCK;
fcntl(fd, F_SETLKW, &fl); fcntl(fd, F_SETLKW, &fl);
read(fd, buffer, DATA_SIZE);
for (i = 0 ; i < N; i++) { fl.l_type = F_UNLCK;
write(fd, buffer, DATA_SIZE); fcntl(fd, F_SETLK, &fl);
if (i < N-1) { }
fl.l_type = F_WRLCK; close(fd);
fl.l_start = DATA_SIZE*(i+1);
fcntl(fd, F_SETLKW, &fl);
}
fl.l_type = F_UNLCK;
fl.l_start = DATA_SIZE*i;
fcntl(fd, F_SETLK, &fl);
}
close(fd);

a) Calcule el tamao mximo que puede tener un fichero en dicho sistema.


Suponiendo que la cache se encuentra inicialmente vaca, que no se realiza ninguna operacin de limpieza de ca -
che (sync) durante la ejecucin del mencionado programa, que el escritor ejecuta su primer fcntl antes que ningn
cdigo lector ejecute el open y que el fichero fich exista previamente con un tamao de 16 MiB, se pide:
b) Puede un lector adelantar al escritor, es decir, leer una zona todava no escrita por el escritor? Justifique la
respuesta.
c) Cuntos accesos a disco se producen durante la ejecucin del bucle for del proceso escritor? Suponer una
poltica de actualizacin write-through (escritura inmediata). Justificar la respuesta, e indicar cuntos accesos son de
lectura y cuntos son de escritura.
d) Responder a la pregunta anterior, pero para el bucle for de cada proceso lector.

SOLUCIN
a) (K, M, G = Prefijos binarios)
(Como no se indica otra cosa, suponemos 1 agrupacin = 1 bloque y lo denominaremos 'bloque' en adelante)
Primero tenemos que calcular el nmero de punteros que caben en 1 bloque = 2 KiB / 4B = 1/2 K = 512 punte-
ros por bloque = 2^9
12 directos: 12 bloques
1 indirecto simple: 512 bloques
1 indirecto doble: (512)^2 bloques = (2^9)^2 = 2^18 = 2^8 * 2^10 = 256 Ki bloques
TOTAL: (256 K + 512 + 12)blq * 2 KiB/blq = 512 MiB + 1 MiB + 24 KiB = 513 MiB + 24 KiB
b) El escritor da al fichero un tamao de DATA_SIZE*N y establece un cerrojo de tamao DATA_SIZE, antes
de que cualquier lector pueda establecer su cerrojo. Seguidamente escribe en el bloque y ampla el cerrojo al si -
guiente bloque de tamao DATA_SIZE. Finalmente, libera el cerrojo del bloque escrito. Por tanto, los lectores no
pueden adelantar al escritor.
c) ftruncate trunca el fichero de 16 MiB a 2 MiB. El bucle del escritor realiza 2048 iteraciones y en cada una
escribe 1 KiB de datos (secuencialmente desde el principio de fichero), por lo que sobrescribe los 2 MiB. As, cada
265
iteracin escribe 1/2 bloque de disco. La primera iteracin (i=0) requiere 1 acceso a disco de lectura (la cach est
vaca y el bloque tena datos) y 1 acceso a disco de escritura (por la poltica write-through). La segunda iteracin
(i=1) solo requiere 1 acceso a disco de escritura, pues el bloque ya ha sido trado a cach en la iteracin anterior. As,
las iteraciones con i impar provocan 1 acceso y las de i par 2 accesos.
Esto no se ve afectado por que la memoria cach sea menor (1 MiB) que el total a escribir. Cuando la cach est
llena, el siguiente bloque se reemplaza sin accesos adicionales a los mencionados, debido a la poltica write-through.
En conclusin, el bucle del escritor provoca 1024 accesos de lectura a disco y 2048 accesos de escritura.
d) El bucle de cada proceso lector realiza tambin 2048 iteraciones y en cada una lee secuencialmente 1 KiB
de datos. En condiciones normales de carga del sistema, cada proceso lector recibir rodaja de procesador con una
frecuencia similar al proceso escritor, por lo que la ejecucin de cada iteracin de lector no debe retrasarse mucho
respecto de la del escritor. As, cada lectura pide un bloque que se encuentra en la cach por lo que no provocar
ningn acceso a disco.

Problema 4.24 (junio 2012)

Una aplicacin se compone de dos procesos pesados cuyo cdigo se adjunta, que comparten un buffer circular for-
mado por registros de tamao fijo y que utilizan un FIFO (llamado miFIFO).
a) Indicar los problemas que tiene el cdigo propuesto.
b) Plantear una o varias soluciones a los problemas encontrados.
c) Qu cambios se deberan introducir en la solucin propuesta si en vez de procesos se utilizasen threads?
d) Qu cambios habra que hacer si se quisiese que los procesos ejecutasen en mquinas distintas?
Ahora tenemos varios productores y varios consumidores que comparten el buffer circular utilizado en los cdi -
gos adjuntos, con la siguiente modificacin: el buffer incluye una cabecera con informacin de gestin y/o sincroni-
zacin del buffer.
e) Cmo resolveramos el problema de sincronizacin para procesos pesados? Plantear la informacin a in-
cluir en la cabecera as como el o los mecanismos de sincronizacin a utilizar.

// Definicin del registro


struct registro {
int numero;
char texto[280];
};

int main(void) // Programa productor.


{
int fd_buff,fd_fifo;
struct stat bstat;
int i, NR;
struct registro * tabla;
struct registro * registro;

fd_buff = open ("buffer", O_RDWR);


fstat(fd_buff, &bstat);
tabla = mmap(0,bstat.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd_buff,0);
close(fd_buff);
fd_fifo = creat("miFIFO", 0666); // Se supone que miFIFO es un FIFO y que ya est creado.
NR = bstat.st_size / sizeof(struct registro);

i = 0;
while(1) {
// buffer circular
registro = &(tabla[i%NR]);
registro->numero = i;
<<se genera el contenido del registro en el buffer, lo que puede tardar bastante>>
write(fd_fifo,&registro,sizeof(struct registro *)); // envio de la direccin del registro
i++;
}
return 0;
266 Problemas de sistemas operativos
}

int main(void) // Programa consumidor.


{
int fd_buff, fd_fifo;
struct stat bstat;
int i;
struct registro * tabla;
struct registro * registro;

fd_buff = open ("buffer", O_RDWR);


fstat(fd_buff, &bstat);
tabla = mmap(0,bstat.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd_buff,0);
close(fd_buff);
fd_fifo = open("miFIFO", O_RDONLY);

i = 0;
while(1) {
read(fd_fifo,&registro,sizeof(struct registro *));
<< se procesa el registro, lo que puede tardar bastante>>
printf("%d %d\n", i, registro->numero);
i++;
}
return 0;
}

SOLUCIN
a) El cdigo propuesto tiene dos problemas principales. Por un lado, el productor pasa una direccin absoluta al con-
sumidor. Dado que las dos regiones de memoria no tienen por qu estar en la misma direccin, se producir un error
al acceder al registro por parte del consumidor.
Por otro lado, la sincronizacin entre ambos procesos no es correcta. Si bien, el consumidor se sincroniza correc -
tamente con el productor mediante miFIFO, cada direccin indica que est disponible un nuevo registro. El produc-
tor desconoce el avance del productor, lo que puede llevar a que llene completamente el buffer y llegue a
sobrescribir registros no consumidos, si el consumidor es ms lento que l.
b) Para que el consumidor haga correctamente el acceso al buffer el productor debe enviar una direccin relativa al
comienzo de la regin compartida. Por ejemplo, puede enviar el ndice i.
Para evitar que el productor sobrescriba registros no consumidos podemos utilizar varias soluciones.
Una primera alternativa podra consistir en que el consumidor, una vez consumido el registro, pusiese regis-
tro->numero = 0, de esta forma el productor pudiese comprobar que el registro est vaco antes de escribirlo.
Sin embargo, el productor tiene el siguiente problema: si encuentra el registro lleno, porque ha adelantado al consu -
midor, cmo se entera de que queda vaco? Una espera activa leyendo reiteradamente el registro para ver que ya
est vaco no es muy recomendable.
Una segunda alternativa consiste en utilizar otro FIFO, que llamaremos suFIFO. En suFIFO introduciremos ini-
cialmente NR bytes (NR es el nmero de registros de buffer). Cada byte se utilizar como testigo de un registro, in -
dicando que est libre. Esta operacin inicial la hace el productor antes de producir ningn registro.
El productor antes de escribir en el buffer lee un byte del suFIFO, captando el testigo del registro sobre el que va
a escribir. Por su parte, el consumidor escribe un byte en suFIFO cuando ha terminado con el registro, devolviendo
el testigo.
Si el buffer est lleno, no habr ningn byte en suFIFO, por lo que el productor se quedar esperando en la lectu-
ra de suFIFO hasta que el consumidor escriba un byte. Por otro lado, las direcciones que contenga miFIFO indica el
nmero de registros tiles que todava no han sido consumidos. Si no hay ninguno, el consumidor se queda esperan-
do a que el productor escriba una direccin.
Una tercera alternativa consiste en crear un semforo con nombre, inicializado a NR (nmero de registros de bu -
ffer). El productor har un sem_wait sobre dicho semforo antes de escribir en el registro y el consumidor har un
sem_post despus de recoger un registro.
Una cuarta alternativa sera eliminar la regin de memoria compartida y enviar el registro completo por el FIFO.
267
Finalmente se podra haber planteado el uso de cerrojos sobre ficheros. Una primera consideracin es que lo que
se comparte es una regin de memoria compartida no un fichero. Sin embargo, si no se cierra el descriptor del fiche -
ro proyectado, se podran usar los cerrojos, puesto que de forma indirecta nos protegeran las mismas zonas de la re -
gin de memoria.
Una segunda consideracin es que no es un mecanismo adecuado para nuestro problema, que consiste en que el
productor reescriba un registro que todava no ha sido consumido. Puesto que si no est siendo consumido no habr
cerrojo sobre el mismo.
c) Para el caso de threads lo primero que hay que hacer es cambiar la regin de memoria compartida por una decla-
racin de buffer como variable global. Tampoco tiene sentido plantear una comunicacin mediante un FIFO, por lo
que la sincronizacin se debera hacer mediante mutex y condiciones.
La informacin de control que necesitamos es el nmero de registros libres en el buffer. Dicha informacin se
inicia a NR. El productor la decrementa cada vez que produce un registro y el consumidor la incrementa cada vez
que consume uno. Los cdigos de sincronizacin seran los siguientes:
//Bucle del productor
while(1) {
<<se genera el contenido del registro en el buffer, lo que puede tardar bastante>>
mutex_lock(mimutex);
while (numeroregistroslibre < 0)
condition_wait(cond, mimutex);
numeroregistroslibres--
//se guarda en el registro i%NR del buffer
registro = &(tabla[i%NR]);
registro->numero = i;
strcpy(registro->texto, mitexto); //Se supone que mitexto es un string vlido
condition_signal(cond);
mutex_unlock(mimutex);

i++;
}

//Bucle del consumidor


while(1) {
mutex_lock(mimutex);
while (numeroregistroslibres < NR)
condition_wait(cond, mimutex);
numeroregistroslibres++
//se lee el registro i%NR del buffer
registro = &(tabla[i%NR]);
mii= registro->numero;
strcpy(mitexto, registro->texto);
condition_signal(cond);
mutex_unlock(mimutex);
<<se consume el registro, lo que puede tardar bastante>>

i++;
}
La contencin producida por la solucin propuesta es pequea, puesto que el cdigo ejecutado en la seccin cr-
tica es muy pequeo.
d) Para el caso de mquinas distintas los procesos no pueden compartir regiones de memoria ni FIFO.
La solucin ms simple sera utilizar sockets de dominio AF_INET. Como la comunicacin debe ser segura usa-
remos el tipo Stream y protocolo el IPPROTO_TCP. A travs del socket se enva el registro completo, de forma si -
milar a la cuarta alternativa del apartado b).
Si se desea conservar la filosofa propuesta, se podra utilizar un fichero en red en vez de la regin de memoria
compartida y valdra la segunda alternativa del apartado b), pero usando dos socket en vez de dos FIFO. Sin embar-
go, esta solucin puede tener problemas debido a la semntica de los sistemas de ficheros distribuidos.
e) En caso de haber varios productores y consumidores, se podra utilizar la alternativa cuarta del apartado b). Un
FIFO en el que los productores escriben el registro completo y del que los consumidores leen el registro completo.
268 Problemas de sistemas operativos
Si conservamos la filosofa de la regin de memoria compartida, necesitamos saber qu registros estn ocupados
en cada instante, de forma que un productor elija de forma exclusiva uno libre y un consumidor elija uno relleno y
no consumido.
Podemos aadir una cabecera que sea un mapa de bits que nos indique los registros libres del buffer. El acceso a
esta cabecera debe ser exclusivo, lo que podemos conseguir mediante un semforo binario inicializado a 1. Por otro
lado, hay que evitar que un productor haga espera activa si no hay registros libres as como que un consumidor haga
espera activa si no hay registros rellenos. Para eso podemos utilizar un semforo que indique los registros libres, que
estar inicializado a NR y otro que indique los ocupados, inicializado a 0. Las secuencias son las siguientes:
//Bucle del productor
while(1) {
<<se genera el contenido del registro en el buffer, lo que puede tardar bastante>>
sem_wait(registroslibres)
sem_wait(cabecera)
<<Se busca un registro libre reglibre y se marca como ocupado en la cabecera>>
//se guarda en el registro
registro = &(tabla[reglibre]);
registro->numero = reglibre;
registro->texto = mitexto;
sem_post(cabecera)
sem_post(registrosocupados)

i++;
}

//Bucle del consumidor


while(1) {
sem_wait(registrosocupados)
sem_wait(cabecera)
<<Se busca un registro ocupado regocupado y se marca como libre en la cabecera>>
//Se lee el registro
registro = &(tabla[regocupado]);
mii= registro->numero;
mitexto = registro->texto;
sem_post(cabecera)
sem_post(registroslibres)
<<se consume el registro, lo que puede tardar bastante>>

i++;
}
La contencin producida por la solucin propuesta es pequea, puesto que el cdigo ejecutado en la seccin cr -
tica es pequeo.
En este caso el uso de un cerrojo sobre la cabecera tendra la misma funcionalidad que el semforo cabecera. Sin
embargo, por lo comentado en la seccin b) nos parece mucho ms adecuado el semforo.

Problema 4.25 (junio 2012)

Se quiere escribir un programa que con un nico proceso pesado implemente una cadena de procesamiento com-
puesta por N etapas concurrentes en serie, implementadas con procesos ligeros. Cada etapa debe recibir (rol de
consumidor) un nmero entero procedente de la etapa anterior, procesarlo y enviar (rol de productor) el resultado
(otro entero) a la etapa siguiente. No se permite el uso de pipes ni FIFOs para comunicar las etapas. El procesa -
miento del dato en cada etapa consiste en la invocacin de la funcin de biblioteca " int realiza_opera-
cion(int Netapa, int dato)", siendo Netapa el nmero de etapa de la cadena y dato el dato a
procesar. No programar esta funcin. La entrada de la primera etapa debe ser la entrada estndar (flujo de nme -
ros enteros) y la salida de la ltima etapa la salida estndar. Elegir libremente la representacin (binario, ascii...)
de los datos en la entrada y salida estndar. Se pide:
a) Escribir una primera versin del programa sin proteccin de secciones crticas. Sugerencia: Programar en
primer lugar una etapa intermedia genrica y despus aadir el cdigo adicional para las etapas de los ex -
tremos.
269
b) Indicar (en el programa escrito) qu tramos de cdigo son secciones crticas y por qu.
c) Aadir el cdigo necesario para proteger las secciones crticas.
d) Explicar brevemente (2-5 lneas) si el mecanismo de sincronizacin empleado produce esperas activas. Si es
as, Qu otro mecanismo podra evitarlo?

SOLUCIN
a)
(NOTA: Se han omitido las comprobaciones de retornos de error)
Cuerpo de texto Times New Roman 10pt. Siguientes prrafos

/* N-1 buffers entre etapas (1 int/buffer):


Etapa#0 -> buffer[0] -> Etapa#1 -> buffer[1]...
*/
int buffer[N-1];

int realiza_operacion(int Netapa, int dato);

void *etapa(void *p){


int dato_in, dato_out;
int i = (int)p; //Restaura i de main

while(1){
if(i == 0){//Etapa #0 lee de stdin
scanf("%i", &dato_in);
}else{
dato_in = buffer[i-1]; // CONSUMIDOR de buffer[i-1]
}

dato_out = realiza_operacion(i, dato_in);

if(i == N-1){//Etapa #N-1 escribe en stdout


printf("%i\n", dato_out);
}
else{
buffer[i] = dato_out; // PRODUCTOR de buffer[i]
}
}
}

int main(void){
int i;
pthread_t thid[N];

pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

for(i = 0; i < N; i++){


ret = pthread_create(&thid[i], &attr, &etapa, (void *)i);
}
while(1);
}

b)
Seccin crtica 1 (compite con productor de etapa anterior):
270 Problemas de sistemas operativos
dato_in = buffer[i-1]; // CONSUMIDOR de buffer[i-1]

Seccin crtica 2 (compite con consumidor de etapa siguiente):


buffer[i] = dato_out; // PRODUCTOR de buffer[i]

c) (En negrita el cdigo aadido)


(NOTA: Se han omitido las comprobaciones de retornos de error)

pthread_mutex_t mutex[N-1];
pthread_cond_t dato_disponible[N-1], vacio[N-1];
int n_datos[N-1]; //1= buffer[.] tiene dato valido

/* N-1 buffers entre etapas (1 int/buffer):


Etapa#0 -> buffer[0] -> Etapa#1 -> buffer[1]...
*/
int buffer[N-1];

int realiza_operacion(int Netapa, int dato);

void *etapa(void *p){


int dato_in, dato_out;
int i = (int)p; //Restaura i de main

while(1){
if(i == 0){//Etapa #0 lee de stdin
scanf("%i", &dato_in);
}else{
pthread_mutex_lock(&mutex[i-1]);
while(n_datos[i-1] == 0) //ESPERA mientras buffer vaco
pthread_cond_wait(&dato_disponible[i-1], &mutex[i-1]);
pthread_mutex_unlock(&mutex[i-1]); //(Para ESPERA PASIVA de PRODUCTOR)
dato_in = buffer[i-1]; // CONSUMIDOR de buffer[i-1]
n_datos[i-1] = 0; //Consumidor ha vaciado buffer
pthread_mutex_lock(&mutex[i-1]); //(Para ESPERA PASIVA de PRODUCTOR)
pthread_cond_signal(&vacio[i-1]);
pthread_mutex_unlock(&mutex[i-1]);
}

dato_out = realiza_operacion(i, dato_in);

if(i == N-1){//Etapa #N-1 escribe en stdout


printf("%i\n", dato_out);
}
else{
pthread_mutex_lock(&mutex[i]);
while(n_datos[i] == 1) //ESPERA mientras buffer ocupado
pthread_cond_wait(&vacio[i], &mutex[i]);
pthread_mutex_unlock(&mutex[i]); //(Para ESPERA PASIVA de CONSUMIDOR)
buffer[i] = dato_out; // PRODUCTOR de buffer[i]
n_datos[i] = 1; //Productor ha llenado buffer
pthread_mutex_lock(&mutex[i]); //(Para ESPERA PASIVA de CONSUMIDOR)
pthread_cond_signal(&dato_disponible[i]);
pthread_mutex_unlock(&mutex[i]);
}
}
}
271

int main(void){
int i;
pthread_t thid[N];

//Inicia estado de N-1 buffers = VACOS


for(i = 0; i < N-1; i++){
n_datos[i] = 0;
}

pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

for(i = 0; i < N; i++){


ret = pthread_create(&thid[i], &attr, &etapa, (void *)i);
}

while(1);
}

Problema 4.26 (dic 2012)

Se desea disear un sencillo dispositivo H que haga de puente entre un conjunto de puertos USB y los computado-
res de una nube. El hardware del dispositivo H incluye n controladores USB, un puerto Ethernet, un microprocesa -
dor, memoria RAM y memoria FLASH para el programa y los parmetros de configuracin. El objetivo es poder
establecer puertos USB virtuales en las mquinas de la nube, de forma que los programas de estas puedan acceder
a los clientes USB como si fuesen locales, con una relacin biunvoca entre cada puerto real USB y un puerto vir -
tual. En dicho dispositivo H consideraremos dos aplicaciones: ApA y ApB.
ApA aplicacin encargada de las comunicaciones. Recoge lo que cada cliente USB escribe en el puerto USB f -
sico y lo enva a la mquina con el puerto USB virtual asociado. Inversamente, debe recoger lo que le enve la m-
quina de la nube y debe retransmitirlo por el puerto fsico.
ApB aplicacin encargada de asignar puertos USB. Cuando una mquina de la nube quiere acceder a un clien-
te USB debe contactar con esta aplicacin para ver si est libre y para que se le asigne.
Adems, es necesario incluir una aplicacin ApC en cada uno de los computadores de la nube para que presen-
ten los correspondientes puertos USB virtuales, de forma que se acceda a ellos como si fuesen locales.
a) Para la aplicacin ApA, teniendo especialmente en cuenta las comunicaciones, exponer detalladamente las
ventajas e inconvenientes que presentan las siguientes arquitecturas: a1) Un nico proceso sin threads, a2) Un ni-
co proceso con threads, a3) Varios procesos sin threads.

Cliente
Mquina A USB
USB 1 virtual USB 1
USB 4 virtual
Dispositivo H

USB 2 Cliente
Mquina D Ethernet
USB 12 virtual USB 3 USB
Mquina C
USB 7 virtual
USB 5 virtual
USB 2 virtual USB n
Cliente
USB

Figura 4.7

b) Para la solucin a3, seleccionar el mecanismo de comunicacin ms adecuado, justificando dicha seleccin.
Indicar las instancias de dicho mecanismo que seran necesarias, explicando el uso de cada de ellas as como su re-
lacin con los procesos del dispositivo H.
c) Es previsible que aparezcan problemas de concurrencia en la aplicacin ApA? y en la aplicacin ApC? Ex -
plicar las razones en las que basa su respuesta.
272 Problemas de sistemas operativos
d) Tendra sentido plantear la aplicacin ApB como un servicio serie? En este supuesto, qu mecanismos de
comunicacin se utilizaran entre las aplicaciones ApC y la aplicacin ApB? Justifique ambas respuestas.
e) La aplicacin ApA detectar que un cliente de la nube ha cerrado el puerto virtual y deber anotar dicho
puerto como libre, plantendose un problema de concurrencia con ApB. Indicar la informacin que se comparte, su
ubicacin y el mecanismo de comunicacin a utilizar entre ApA y ApB.
f) Plantear una solucin al problema anterior, indicando la informacin de control que se necesita. Justificar el
tipo de mecanismo a utilizar, as como las instancias del mismo y las caractersticas de cada una de ellas.
g) Codificar el acceso a la informacin compartida de acuerdo a la solucin anterior.

Solucin

a) Evidentemente se necesitar una comunicacin fiable que garantice que la informacin que circula entre las m -
quinas de la nube y los dispositivos USB no se pierda y llegue ordenada. Pora ello nos harn falta sockets del domi -
nio AF_INET, de tipo Stream y protocolo TCP. Es decir, sockets con conexin.
Un nico proceso sin threads con servicios bloqueantes no sera una buena solucin. El problema de ApA apare-
ce con las lecturas de los sockets o de los puertos USB locales, puesto que se quedara bloqueado en una de las lec -
turas, dejando de atender a las dems, hasta que se llegara algo que leer. Es una solucin de servidor serie, que
solamente atiende a un cliente, dejando bloqueados a los dems. Si usamos servicios no bloqueantes (por ejemplo,
select o poll), s puede ser una buena solucin.
Un proceso con threads, siempre que estos sean KLT, permite tener threads dedicados que se quedarn bloquea-
dos en las lecturas, pero sin afectar a los dems, por lo que se puede atender simultneamente a todos los clientes.
La solucin clsica de tener un proceso por puerto presenta la misma ventaja que los threads KLT.
Para construir la aplicacin ApA mediante primitivas de comunicacin sncronas se necesitar un proceso o thread
por cada primitiva de lectura, puesto que dejar bloqueado a dicho proceso o thread. La aplicacin ApA, por un
lado, tiene que leer del puerto USB local, para reenviar lo ledo al puerto virtual USB y, por otro, tiene que leer lo
que le llega del puerto virtual USB para re-envirselo al local. Por lo tanto, la aplicacin ApA ha de generar dos pro -
cesos o threads por puerto USB, tal y como indica la figura 4.8.
ApA Dispositivo H

Mquina C ApA h1 Cliente


USB

ApA h2

Figura 4.8
El proceso maestro ApA estar esperando peticiones de conexin de las mquinas de la nube. Una vez aceptada
una solicitud generar un socket especfico para la misma y crear dos procesos. Uno se quedar bloqueado leyendo
del socket, mientras que el otro se queda bloqueado leyendo del descriptor de fichero asociado al puerto USB local.
b) Como se ha indicado anteriormente, se necesita un socket del dominio AF_INET, de tipo Stream y protocolo TCP
por cada USB virtual que se establezca. Dichos sockets son bidireccionales, por lo que cada uno ser atendido por
dos procesos en el dispositivo H (uno por cada sentido de la comunicacin). Por otro lado, la comunicacin con el
puerto local se realizar mediante un descriptor de fichero resultado de abrir el dispositivo (/dev) correspondiente.
c) Los procesos derivados del ApA que hacen de puente entre el puerto fsico USB y el virtual, no comparten ningu-
na informacin entre ellos, por lo que no presentan ningn problema de concurrencia entre ellos. Solamente presen -
tan un problema con la aplicacin ApB, como se dice en el apartado e. Igualmente, los procesos ApC no comparten
informacin entre s, por lo que, aunque tengamos N puertos virtuales en la misma mquina, los N procesos ApC
son totalmente independientes sin ningn problema de concurrencia.
d) Teniendo en cuenta que la operacin de solicitar una conexin USB no ser muy frecuente y que la funcin que
ejecuta ApB es muy simple, pues consiste en comprobar en una tabla si est libre el puerto USB correspondiente, la
solucin de un servidor serie parece muy adecuada por su simplicidad. Como necesitamos una conexin segura en -
tre ApC y ApB, utilizaremos un nico socket del dominio AF_INET, de tipo Stream y protocolo TCP. El proceso
ApB estar normalmente bloqueado en el servicio accept.
e) y f) La informacin que se comparte es el estado de ocupado o libre de los puertos USB locales, que llamaremos
tabla Ocupa. La comunicacin entre ApA y ApB se puede realizar bsicamente de dos formas: mediante memoria
compartida o mediante un FIFO (o pipe en el caso de que estn emparentados). Mediante memoria compartida sera
necesario implementar un mecanismo de sincronizacin. Podemos utilizar un semforo para proteger toda la tabla
273
Ocupa, puesto que las acciones sobre la misma son muy breves e infrecuentes, por lo que no se producir contencin
apreciable. La utilizacin de un FIFO sera, en principio, adecuada, puesto que lo que necesita ApA es informar a
ApB que se ha quedado libre un puerto USB, lo que se puede hacer mediante un mensaje. Sin embargo, presenta un
problema en ApB, que deber estar atendiendo simultneamente las peticiones que le lleguen a travs del socket y
los mensajes que le lleguen por el FIFO, lo que no se puede hacer usando exclusivamente servicios sncronos.
g) Vamos a suponer que el proceso ApB es hijo del ApA, por lo que puede heredar el semforo y la regin de memo -
ria compartida. Consideraremos que Ocupa dedica un byte en vez de un bit por USB, dado que simplifica la progra-
macin y que el nmero de puertos USB en una misma mquina no puede ser muy grande. Siendo Nusb el nmero
total de puertos USB, los fragmentos de cdigo son los siguientes:
//Inicializacin
sem_t sem_Ocupa;
char *p;
sem_init (&sem_Ocupa, 1, 1); //Iniciamos a 1
p = mmap(0, Nusb, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON , -1, 0);

//Acceso a Ocupa por ApA


sem_wait(sem_Ocupa);
p[n] = 0; //marca como libre el USB n
sem_post(sem_Ocupa);

//Acceso a Ocupa por ApB


sem_wait(sem_Ocupa);
if (p[n] == 0) {
p[n] = 1; //marca como ocupado el USB n
sem_post(sem_Ocupa);
<<contesta asignando el puerto y avisa a ApA para que establezca la conexin>>
}
else {
sem_post(sem_Ocupa);
<<contesta avisando que el puerto est ocupado>>
}
5
PROBLEMAS PROPUESTOS
A.- Tema de introduccin
Problema .1
Sea un sistema multitarea sin memoria virtual que tiene una memoria principal de 24 MiB. Conociendo que la parte
residente del SO ocupa 5 MiB y que cada proceso ocupa 3 MiB, calcular el grado de multiprogramacin que se
puede alcanzar.
Problema .2
Un sistema con memoria virtual y pginas de 2 KiB tiene 24 MiB de memoria principal y 300 MiB de zona de swap.
El SO residente ocupa 6 MiB y cada proceso requiere 6 MiB. Calcular el grado de multiprogramacin que se puede
alcanzar. Para este grado de multiprogramacin calcular la memoria principal en marcos y en KiB que le corres-
ponde a cada proceso.
Problema .3
En un sistema multiprogramado se van a ejecutar tres procesos, que tienen las caractersticas de ejecucin de la ta -
bla siguiente, expresada en ms:
Proceso UCP E/S UCP E/S UCP
A 5 25 13 27 3
B 2 43 3 36 4
C 68 12 54
El proceso A empieza su ejecucin en el instante t = 0. El proceso B se crea en el instante t = 10 ms y el C en el
instante t = 25 ms. Considerando que el SO utiliza 3 ms de UCP cada vez que entra a ejecutar y que el planificador
da ms prioridad a los procesos ms antiguos, hacer un diagrama de tiempos que indique el avance de los proce -
sos.
Problema .4
Repetir el problema anterior considerando que el sistema tiene un reloj que interrumpe cada 10 ms.
Problema .5
Un fichero almacena enteros de 32 bits, desde el 1 hasta el 4G. El planificador del SO va d ando control a los pro -
cesos de forma que se produce la siguiente secuencia de operaciones:
Proceso A fd = open("/tmp/felipe/datos.txt", O_RDONLY);
Proceso A fork(); Crea el proceso C
Proceso B fd = open("/tmp/felipe/datos.txt", O_RDONLY);
Proceso C lseek(fd, 1000, SEEK_SET); Posiciona desde el origen
Proceso B read(fd, buff, 20);
Proceso A read(fd, buff, 20);
Proceso C read(fd, buff, 20);

274
276 Problemas de sistemas operativos
(Considerar que buff se ha definido como un vector de 100 bytes)
Indicar los contenidos de buff despus de esta secuencia de operaciones.
Problema .6
Un sistema de ficheros utiliza el esquema de nodos-i para almacenar la estructura de los ficheros y reserva el nodo-
i = 2 para el directorio raz. La informacin del sistema de ficheros incluye lo siguiente:
Contenido de algunos nodos-i
Nodo-i Tipo Dueo (uid/gid) Proteccin Tamao en bytes Agrupaciones del fichero
2 directorio 0/0 rwx rx rx 160 7644
23 directorio 34/20 rwx x 80 173
87 usuario 65/20 rw rw 2532 79752, 4739, 119
273 directorio 0/0 rwx rx x 120 7635
324 directorio 6/5 rwx rx rx 40 23
753 usuario 34/20 rw rw 856 4546
823 usuario 4/5 rw rw 3848 12764, 2384, 8763, 6635
876 directorio 0/0 rwx rx rx 40 453
2073 usuario 6/5 rw rw r 852 56
3784 usuario 58/20 rw rw 796 443
8234 usuario 0/0 rw 374 4413
9835 usuario 34/20 rw 535 569
Contenido de algunas agrupaciones (el ; se ha utilizado para separar las lneas de los directorios)
Agrupacin Contenido de la agrupacin
23 udjri 2073
56 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 .......
173 dec 753; Jfuie 9835
443 qwei oier oeiru qoieruower qpoweioru qpwoeir....
453 iue 8234
569 1234567890 ...
4413 abcdefghijklme.....
4546 Esto es un fichero de texto........
7635 pri1 823; Pro2 87; Pro3 324
7644 doc 273; Proa 876; Prob 23; Proc 3784
12764 ieqo ieoqo eir93o3hnf wqn34209e rn349 wheew.....
79752 ABCDEFGHIJKLM.......
Se pide:
a) Hacer un diagrama con el esquema de directorios.
b) Indicar las operaciones que tiene que hacer el sistema de ficheros para interpretar el nombre:
/doc/Pro3/udjri
c) Indicar el contenido del fichero antes mencionado.
B.- Procesos y llamadas al sistema
Problema .7
Se dispone de un sistema que tiene 128 MiB de memoria principal y 500 MiB de swap. El SO residente ocupa 32
MiB y los procesos ocupan una media de 23 MiB.
Suponiendo que el sistema opera con preasignacin de swap y con expulsin, indicar el mximo nivel de multi -
programacin que se puede alcanzar y la cantidad de memoria principal que se asignar a cada proceso.
Problema .8
Un proceso Unix tiene la tabla de pginas de la figura 5.1, siendo stas de 1 KiB.
277
374 364
216 swap Texto
745 swap
2542
swap
swap Pila
37
4742
swap
swap Datos
swap
swap
Figura 5.1

Suponer que el proceso solicita 3 KiB de memoria al SO, Cmo quedar la tabla de pginas?
Problema .9
Un sistema operativo tiene 4 niveles de prioridad, siendo el 3 el de menor prioridad, y planifica de acuerdo a las si-
guientes reglas:
Los procesos de cada nivel se planifican en Round-Robin con rodajas de 50 ms de UCP.
Cuando un proceso lleva ms de 500 ms en un nivel sin ejecutar es elevado de nivel.
Suponer que los procesos slo desean UCP y no E/S, y que se parte de una situacin en t = 0 en la cual hay 1
proceso de nivel 0, 2 de nivel 1, 2 de nivel 2 y 1 de nivel 3.
Indicar el tiempo que transcurre hasta que se ejecute por primera vez el proceso de nivel 3.
Problema .10
El planificador de un SO va dando control de forma que se produce la siguiente secuencia de eventos.
Proceso A fork (); crea el proceso B
Proceso B fork (); crea el proceso C
Proceso B exit (estado);
Proceso A wait (&sta);
Proceso C exit (status)
Proceso A exit (stat)
Indicar, mediante una serie de esquemas de Jerarqua de procesos, las distintas fases por las que se pasa al pro-
ducirse los eventos anteriores.
Problema .11
Un proceso multihebra produce la siguiente secuencia de ejecucin:
Hebra principal A p_create Crea hebra B
Hebra B p_create Crea hebra C
Hebra B p_create Crea hebra D
Hebra D p_join sobre la hebra B
Hebra B p_create Crea hebra E
Hebra E read (.......)
Indicar en este momento cul es la situacin del proceso desde el punto de vista de sus posibilidades de ejecu -
cin. (Hebra = proceso ligero)
Problema .12
Un sistema dispone de 10 recursos que llamaremos R0 a R9. Sea la situacin siguiente:
Proceso A tiene R2 y desea R4
Proceso B tiene R5 y desea R1
Proceso C tiene R3 y R4 y desea R9
Proceso D tiene R9 y desea R2 y R1
Indicar en que situacin se encuentran estos procesos. Verificar que con una poltica de asignacin adecuada se
podra haber evitado el problema.
278 Problemas de sistemas operativos
Problema .13
Dos procesos se comunican a travs de un fichero, de forma que uno escribe en el fichero y el otro lee del mismo.
Para sincronizarse el proceso escritor enva una seal al lector. Proponer un esquema del cdigo de ambos proce -
sos.
Problema .14
Consideremos procesos que tiene un perfil de ejecucin repetitivo con 30 ms de UCP seguido por 60 Ms de E/S. Su-
pondremos que se dispone de un sistema operativo para trabajos batch con un nivel de multiprogramacin de 3. En
el instante t = 0 no hay ningn proceso en memoria y se tiene la cola de procesos pendientes siguiente:
P1 (160), P2 (1000), P3(600), P4 (30), P5 (100) y P6 (60)
donde el nmero entre parntesis indica el tiempo de UCP necesario para que ejecute el proceso.
Calcular el tiempo medio de espera para los dos casos de planificacin FIFO y planificacin segn el ms cor-
to.

C.- Comunicacin y sincronizacin entre procesos


Problema .15
El siguiente fragmento de cdigo intenta resolver el problema de la seccin crtica para dos procesos
P0 y P1.
while (turno != i)
;
SECCIN CRTICA;
turno = j;
La variable turno tiene valor inicial 0. La variable i vale 0 en el proceso P0 y 1 en el proceso P1. La variable j
vale 1 en el proceso P0 y 0 en el proceso P1. Resuelve este cdigo el problema de la seccin crtica?
Problema .16
Cmo podra implementarse un semforo utilizando un pipe?
Problema .17
Considrese un sistema en el que existe un proceso agente y tres procesos fumadores. Cada fumador est continua -
mente liando un cigarrillo y fumndolo. Para fumar un cigarrillo son necesarios tres ingredientes: papel, tabaco y
cerillas. Cada fumador tiene reserva de un solo ingrediente distinto de los otros dos. El agente tiene infinita reserva
de los otros tres ingredientes, poniendo cada vez dos de ellos sobre la mesa. De esta forma, el fumador con el tercer
ingrediente puede liar el cigarrillo y fumrselo, indicando al agente cuando ha terminado, momento en que el agen-
te pone en la mesa otro par de ingredientes y continua el ciclo.
a) Escribir un programa que sincronice al agente y a los tres fumadores utilizando semforos.
b) Resolver el mismo problema utilizando mensajes.
D.- E/S y sistema de ficheros
Problema .18
Dado el siguiente programa:
int fd;
pid_t pid;

fd = creat("fichero", 0755);
pid = fork();
if (pid == 0)
{
write(fd, "Soy yo\n", 7);
lseek(fd, 1024, SEEK_CUR);
exit(1);
}
else
{
write(fd, "Soy otro\n", 10);
write(fd, "Adios\n", 6);
279
exit(1);
}
Conteste a las siguientes preguntas:
a) Cul sera el tamao final del fichero? Sera distinto si la operacin lseek() se realizara en ltimo lugar?
b) Cul ser el contenido final del fichero?
Problema .19
Escribir un programa que implemente el mandato tee. Este mandato escribe lo que lee por la entrada estndar en
su salida estndar y en un fichero especificado como argumento.
Problema .20
Un usuario con uid=12 y gid=1 es el dueo de un fichero con modo de proteccin rwxr-x---. Otro usuario con
uid=3 y gid=1 quiere ejecutar el fichero. Puede hacerlo?
Problema .21
Cuntos nodos-i estarn ocupados en un sistema de ficheros UNIX que contiene nicamente los siguientes fiche-
ros: "/f1", "/f2" (enlace simblico a "/f1"), "/f3" (enlace no simblico a "/f1"), y "/dir" que es un directorio vaco?
Problema .22
Considrese la siguiente poltica de backup de los ficheros contenidos en un disco: el primer lunes de cada mes se
realiza una copia de seguridad completa, el resto de lunes se realiza una copia de seguridad incremental con res-
pecto a la ltima copia completa, y el resto de los das se hace una copia incremental con respecto a la ltima copia
incremental. Si se rompe el disco el jueves de la tercera semana, qu pasos habra que seguirse para restaurar el
sistema?
Problema .23
Considere el siguiente fragmento de programa
int fd;
char buf[512];
fd1 = open("fichero1", O_RDONLY);
fd2 = creat("fichero2", 0755);
while ((n_bytes = read(fd1, buf, 512)) > 0)
write(fd2, buf, n_bytes);
Suponiendo que el fichero fichero1 tiene un tamao de 512 KiB y que el sistema de ficheros emplea un tamao
de bloque de 4 KiB, se pide:
a) Cuntos accesos a disco se realizaran durante la ejecucin del bucle anterior en un sistema que no emplea
cache de bloques?
b) Cuntos accesos a disco se realizaran durante la ejecucin del bucle anterior en un sistema que emplea un
cache de 2 MiB y una poltica de escritura inmediata?

También podría gustarte