Tema 2.
Administracion de procesos
2.1 Procesos
Un programa consiste en código, datos y su pila.
Un proceso es un programa en ejecución y administrado por el sistema operativo. Cada
programa tiene sus propios datos y pila, y el proceso tiene la información sobre el estado
o contexto del programa que se ejecuta.
Desde la perspectiva del sistema operativo Xinu, un proceso ejecuta un segmento de codigo,
teniendo una pila y una prioridad determinada.
La llamada al sistema operativo, que permite crear un proceso, se denomina create.
Dicha llamada recibe los siguientes parametros
Direccion o apuntador del codigo a ejecutar
Tamaño en words de la pila
Prioridad del proceso, mayor que 0
Nombre del proceso
argumentos a la funcion
numero de argumentos
El resultado de esta funcion es el PID del proceso creado
Esta funcion crea un proceso, pero aun no lo pone listo para ejecucion
La llamada resume permite aplicar esto, recibe como parametro el PID del proceso que
debe poner listo para ejecucion
El siguiente codigo muestra el uso de ambas llamadas
#include
#include
codigo()
{
printf("hola Xinu \n");
}
xmain()
{
int pid=0;
pid=create(codigo,INITSTK,INITPRIO,"proc 1",0);
resume(pid);
xdone();
return 0;
}
2.2 Listas ligadas de procesos.
El administrador de procesos se encarga manipular los procesos del sistema operativo.
A dichos procesos los forma en una lista.
A cada proceso se le asigna un identificador de procesos (process id), que lo hace unico
en todo el sistema operativo.
Las listas que maneja el sistema operativo pueden ser colas FIFO, otras ordenadas por una
llave, unas uniligadas o doblemente ligadas.
Se puede crear un estructura de datos con una lista doblemente ligada (cada nodo
apunta a su predecesor y sucesor), cada nodo contiene una lave y la lista tiene una cabeza
y cola (head y tail).
El campo de llave puede quedar determinado por un número entero, de tal manera que el
nodo de la cabeza tiene la llave entera mas pequeña y la cola tiene la llave entera mas
grande.
El sucesor de la cola y el predecesor de la cabeza son nulos. Cuando la lista está vacia,
el sucesor de la cabeza es la cola, y el predecesor de la cola es la cabeza.
El siguiente código en C muestra la implantación en Xinu. (Ver archivo q.h)
/* q.h - firstid, firstkey, isempty, lastkey, nonempty */
/* q structure declarations, constants, and inline procedures */
#define NQENT NPROC + NSEM + NSEM + 4 /* for ready & sleep */
struct qent { /* one for each process plus two for */
/* each list */
int qkey; /* key on which the queue is ordered */
int qnext; /* pointer to next process or tail */
int qprev; /* pointer to previous process or head */
};
extern struct qent q[];
extern int nextqueue;
/* inline list manipulation procedures */
#define isempty(list) (q[(list)].qnext >= NPROC)
#define nonempty(list) (q[(list)].qnext < NPROC)
#define firstkey(list) (q[q[(list)].qnext].qkey)
#define lastkey(tail) (q[q[(tail)].qprev].qkey)
#define firstid(list) (q[(list)].qnext)
#define EMPTY -1 /* equivalent of null pointer */
La estructura qent representa un nodo de la lista doblemente ligada. El campo qkey
tiene el dato, que basicamente es el identificador de proceso. El campo qnext es el
apuntador al siguiente proceso y qprev al anterior.
La lista tiene un tamaño NQENT, que es igual al numero máximo de procesos NPROC
(definido como 30 en el archivo proc.h) y tambien permite manipular semaforos con un
total de NSEM (45) y espacio para aquellos procesos que estan en estado de listos y
dormidos.
Las macros isempty, nonempty, firstkey y firstid permiten, dando la cabeza
de la lista obtener información sobre la misma y lastkey permite tomar el ultimo elemento
de la lista.
La cabeza de la lista tiene un campo qkey igual al minimo entero y la cola de la lista
tiene un campo qkey igual al maximo entero.
El siguiente programa, que es un proceso que se inserta en el propio Xinu, muestra el estado
de la cola.
#include
#include
extern int rdytail;
extern int rdyhead;
extern int numproc;
extern int currpid;
void imprimircola(void)
{
int i;
/*imprimir la cola q*/
for (i=0;i
#include
#include
/*------------------------------------------------------------------------
* enqueue -- insert an item at the tail of a list
*------------------------------------------------------------------------
*/
int enqueue(item, tail)
int item; /* item to enqueue on a list */
int tail; /* index in q of list tail */
{
struct qent *tptr; /* points to tail entry */
struct qent *mptr; /* points to item entry */
tptr = &q[tail];
mptr = &q[item];
mptr->qnext = tail;
mptr->qprev = tptr->qprev;
q[tptr->qprev].qnext = item;
tptr->qprev = item;
return(item);
}
/*------------------------------------------------------------------------
* dequeue -- remove an item from a list and return it
*------------------------------------------------------------------------
*/
int dequeue(item)
int item;
{
struct qent *mptr; /* pointer to q entry for item */
mptr = &q[item];
q[mptr->qprev].qnext = mptr->qnext;
q[mptr->qnext].qprev = mptr->qprev;
return(item);
}
La funcion enqueue, dado un numero de elemento de la lista, arregla los apuntadores para
poner el elemento al final de la lista.
La funcion dequeue elimina un elemento de la lista, simplemente moviendo los apuntadores
del antecesor y sucesor.
enqueue se utiliza para que un proceso que este en espera de un semaforo se quede en
la lista de espera
dequeue se utiliza cuando un proceso se suspende, se elimina de la cola de procesos
El siguiente programa pone a un proceso al final de la cola de listos
#include
#include
#include
extern int rdytail;
void imprimircola(void)
{
int i;
/*imprimir la cola q*/
for (i=0;i
#include
#include
/*------------------------------------------------------------------------
* insert -- insert a process into a q list in key order
*------------------------------------------------------------------------
*/
int insert(proc, head, key)
int proc; /* process to insert */
int head; /* q index of head of list */
int key; /* key to use for this process */
{
int next; /* runs through list */
int prev;
next = q[head].qnext;
while (q[next].qkey < key) /* tail has MAXINT as key */
next = q[next].qnext;
q[proc].qnext = next;
q[proc].qprev = prev = q[next].qprev;
q[proc].qkey = key;
q[prev].qnext = proc;
q[next].qprev = proc;
return(OK);
}
getfirst - remueve y retorna el primero proceso en la cola de prioridad
getlast - remueve y retorna el ultimo proceso de la lista
(ver archivo getitem.c)
/* getitem.c - getfirst, getlast */
#include
#include
#include
/*------------------------------------------------------------------------
* getfirst -- remove and return the first process on a list
*------------------------------------------------------------------------
*/
int getfirst(head)
int head; /* q index of head of list */
{
int proc; /* first process on the list */
if ((proc=q[head].qnext) < NPROC)
return( dequeue(proc) );
else
return(EMPTY);
}
/*------------------------------------------------------------------------
* getlast -- remove and return the last process from a list
*------------------------------------------------------------------------
*/
int getlast(tail)
int tail; /* q index of tail of list */
{
int proc; /* last process on the list */
if ((proc=q[tail].qprev) < NPROC)
return( dequeue(proc) );
else
return(EMPTY);
}
El siguiente programa muestra el uso de la funcion insert, que equivale a poner en la cola de procesos listos
a los 3 procesos creados.
#include
#include
#include
extern int rdytail;
extern int rdyhead;
void imprimircola(void)
{
int i;
/*imprimir la cola q*/
for (i=0;i
#include
#include
/*------------------------------------------------------------------------
* newqueue -- initialize a new list in the q structure
*------------------------------------------------------------------------
*/
int newqueue()
{
struct qent *hptr; /* address of new list head */
struct qent *tptr; /* address of new list tail */
int hindex, tindex; /* head and tail indexes */
hptr = &q[ hindex=nextqueue++ ];/* nextqueue is global variable */
tptr = &q[ tindex=nextqueue++ ];/* giving next used q pos. */
hptr->qnext = tindex;
hptr->qprev = EMPTY;
hptr->qkey = MININT;
tptr->qnext = EMPTY;
tptr->qprev = hindex;
tptr->qkey = MAXINT;
return(hindex);
}
Se puede observar que el elemento nextqueue se supone inicializado (el sistema operativo lo pone a NPROC) y se
insertan dos entradas para el head y tail. El sistema operativo pone los valores rdyhead y rdytail en NPROC+1
y NPROC+2
2.6 Bloque de control de procesos
El sistema operativo guarda la informacion de todos los procesos en una estructura de datos denominada la tabla de
procesos, por cada proceso existe una entrada a dicha tabla.
En cada entrada de la tabla se guarda
- el estado del proceso
- su prioridad
- semaforo y mensaje si esta usando
- la direccion del proceso del stack
- la longitud del stack
- el numero de argumentos que recibe el proceso
- el nombre del proceso
- la direccion inicial del codigo que se ejecuta
En el siguiente archivo de encabezado (proc.h) se muestra la definicion del PCB
/* proc.h - isbadpid */
/* 8088 version */
/* process table declarations and defined constants */
#ifndef NPROC /* set the number of processes */
#define NPROC 30 /* allowed if not already done */
#endif
/* process state constants */
#define PRCURR '\01' /* process is currently running */
#define PRFREE '\02' /* process slot is free */
#define PRREADY '\03' /* process is on ready queue */
#define PRRECV '\04' /* process waiting for message */
#define PRSLEEP '\05' /* process is sleeping */
#define PRSUSP '\06' /* process is suspended */
#define PRWAIT '\07' /* process is on semaphore queue*/
/* miscellaneous process definitions */
#define PNMLEN 9 /* length of process "name" */
#define NULLPROC 0 /* id of the null process; it */
/* is always eligible to run */
#define isbadpid(x) (x<=0 || x>=NPROC)
/* process table entry */
struct pentry {
char pstate; /* process state: PRCURR, etc. */
int pprio; /* process priority */
int psem; /* semaphore if process waiting */
int pmsg; /* message sent to this process */
int phasmsg; /* nonzero iff pmsg is valid */
char *pregs; /* saved environment */
char *pbase; /* base of run time stack */
word plen; /* stack length in bytes */
char pname[PNMLEN+1]; /* process name */
int pargs; /* initial number of arguments */
int (*paddr)(); /* initial code address */
/* Additional user field for the OS labs */
/* Added by Eli Biham and Rivki Matosevitch 18/10/93 */
int user_int1;
int user_int2;
int user_int3;
int user_int4;
int user_int5;
int user_int6;
int user_int7;
long user_long1;
long user_long2;
long user_long3;
};
extern struct pentry proctab[];
extern int numproc; /* currently active processes */
extern int nextproc; /* search point for free slot */
extern int currpid; /* currently executing process */
El siguiente programa imprime la tabla de procesos
#include
#include
xmain(){
struct pentry *pcurr=0;
int i;
for (i=0;ipstate == PRCURR || pcurr->pstate == PRREADY)
printf("PID %d Estado %d Prioridad %d Contexto %x Base %x Longitud %d Nombre %s NArgs %d Codigo %x\n", i,
pcurr->pstate,pcurr->pprio,pcurr->pregs,pcurr->pbase,
pcurr->plen,pcurr->pname,pcurr->pargs,pcurr->paddr);
}
xdone();
return 0;
}
2.7 Cambio de contexto
Cuando un proceso es seleccionado por el planificador para no ejecutarse, debe de abandonar el CPU. Y el CPU
debe otorgar la ejecución a un proceso que está en la cola de procesos listos (con estado READY).
El cambio o conmutación de contexto consiste en el hecho de conservar el estado del proceso en ejecución y cargar
el estado guardado del nuevo proceso.
El contexto de un proceso incluye el valor de los registros de la CPU, el estado del proceso y la información
sobre la administración de la memoria. Cuando ocurre una conmutación de contexto, el kernel del sistema operativo
guarda el contexto del proceso anterior en su PCB y carga el del nuevo proceso programado para ejecución.
El tiempo que tarda el sistema operativo en aplicar la conmutación es trabajo adicional del kernel y mientras
el sistema no realiza trabajo util mientras se efectua el cambio de contexto. Su velocidad varia de máquina a
máquina, dependiendo de la velocidad de memoria, el numero de registros del CPU que deben de copiarse. La
velocidad hoy en dia va de 1 a 1000 microsegundos.
Algunos procesadores tienen hardware especial para soportar el cambio de contexto.
En el caso de Xinu, el cambio de contexto consiste en guardar el estado del proceso y aplicar un cambio de
pilas. Dicha rutina se escribio en lenguaje ensamblador y es dependiente de la computadora a donde se porte.
Su logica es la siguiente:
1. Recibe dos parametros, el apuntador a la pila (SP) del proceso en el CPU y el apuntador a la pila (SP) del
proceso a ser ubicado en el CPU (esto es, el campo pregs de la entrada del PCB)
2. Se guarda como siempre el registro bp en la pila (push bp)
3. Se asigna el registro sp al registro bp, para empezar a tomar parametros
4. Guarda el registro FLAGS del CPU en la pila (pushf)
5. Desactiva las interrupciones para evitar que algun otra rutina tome control del CPU (cli)
6. Guarda los registros SI,DI en la pila, debido a que las rutinas en lenguaje C asume que dichos registros
no cambian a lo largo de las llamadas a los procedimientos. (push si, push di)
El estado de la pila hasta el paso 6 es
BP Anterior| FLAGS| SI | DI | | |Stack Anterior|Nuevo Stack|
^ ^ ^ ^
| | | |
SP BP BP+4 BP+6
adonde apunta SP se puede denominar como el estado del proceso guardado.
7. Se guarda en el registro BX la direccion del pila anterior (mov bx,[bp+4])
8. Se guarda en el apuntador de la pila de la rutina (SP) en la celda dada por bx (mov [bx],sp). Esto permite
que el PROCESO ANTERIOR registre en que parte de la rutina que estaba ejecutando se habia quedado, dejando
almacendado en la entrada del PCB la direccion actual de la pila
9. Se guarda en el registro BX la direccion del stack nuevo (mov bx,[bp+6])
10. Se asigna al apuntador de la pila actual (SP) la direccion de la pila nueva (mov sp,[bx]). Esto permite
que de manera inmediata la computadora quede referenciando a otra pila
11. Se desapilan los registros DI, SI, FLAGS y BP (pop di, pop si, popf, pop bp)
12. Se retorna de la rutina ensamblador. Dado que es otra pila distinta, la direccion de retorno sera diferente
a la de la llamada original.
El truco consiste entonces en que en el PCB se almacene la dirección de la pila como quedo en el cambio de
contexto. Cuando el proceso que se quito del CPU necesita ponerse en el mismo otra vez, basta con indicar
la dirección del apuntador de la pila para recuperar su estado original y volver al punto donde se habia quedado
en ejecución el proceso.
El codigo del cambio de contexto se puede encontar en el archivo ctxsw.asm
; ctxsw.asm - _ctxsw
include dos.asm
dseg
; null data segment
endds
pseg
public _ctxsw
;-------------------------------------------------------------------------
; _ctxsw -- context switch
;-------------------------------------------------------------------------
; void ctxsw(opp,npp)
; char *opp, *npp;
;---------------------------------------------------------------------
; Stack contents upon entry to ctxsw:
; SP+4 => address of new context stack save area
; SP+2 => address of old context stack save area
; SP => return address
; The addresses of the old and new context stack save areas are
; relative to the DS segment register, which must be set properly
; to access the save/restore locations.
;
; The saved state consists of the current BP, SI and DI registers,
; and the FLAGS register
;---------------------------------------------------------------------
_ctxsw proc near
push bp
mov bp,sp ; frame pointer
pushf ; flags save interrupt condition
cli ; disable interrupts just to be sure
push si
push di ; preserve registers
mov bx,[bp+4] ; old stack save address
mov [bx],sp
mov bx,[bp+6] ; new stack save address
mov sp,[bx]
pop di
pop si
popf ; restore interrupt state
pop bp
ret
_ctxsw endp
endps
end
El siguiente programa en C muestra el uso del cambio de contexto
#include
#include
#include
extern int currpid;
struct pentry *pactual;
struct pentry *pnuevo;
codigo1()
{
printf("cambio a nuevo contexto\n");
/*cambiar al contexto viejo*/
ctxsw(&pnuevo->pregs,&pactual->pregs);
printf("Este mensaje nunca se despliega");
return 0;
}
xmain()
{
int pid,i;
pid=create(codigo1,INITSTK,15,"proc 1",0);
pactual = &proctab[currpid];
pnuevo = &proctab[pid];
/*cambiar contexto*/
printf("viejo contexto %x nuevo contexto %x\n",pactual->pregs,pnuevo->pregs);
ctxsw(&pactual->pregs,&pnuevo->pregs);
printf("Recuperando contexto inicial \n");
printf("viejo contexto %x nuevo contexto %x\n",pactual->pregs,pnuevo->pregs);
/*un cambio de contexto a si mismo debe dejarlo en un estado sin cambio*/
ctxsw(&pactual->pregs,&pactual->pregs);
printf("Fin de proceso");
xdone();
return 0;
}
2.8 Planificación de procesos.
En todo sistema operativo tipicamente existe un solo proceso en estado de ejecucion y los demas en estado de
listo. Los procesos son clasificados al estado de listos (READY) cuando son elegibles para los servicios del CPU
pero no están actualmente ejecutandose.
Para aplicar el cambio de contexto de un proceso, previamente se debe seleccionar un proceso de aquellos que
están en estado de listo y darle el control del CPU.
El software que implanta la politica usada para seleccionar un proceso entre aquellos que estan listos se
denomina planificador (scheduler).
La politica que sigue el planificador de Xinu es:
En cualquier tiempo, el proceso que se puede elegir para servicio del CPU es el de más alta prioridad. Entre
aquellos procesos de igual prioridad, la planificación es de tipo round-robin.
La planificación round-robin significa que los procesos que son seleccionados uno despues del otro son aquellos
miembros que fueron llegando de una manera ordenada e insertados según el orden de llegada.
En la cola de prioridad de Xinu, con la rutina insert asegura este orden.
Además una planificación round robin no solo se limita a la política a del primero en llegar es el primero en
ser servido (First Come, First Served,FCFS); también se introduce el concepto de apropiación para poder lograr
la conmutación de procesos.
La apropiación se da cuando se define una unidad de tiempo, denominada quantum o porción de tiempo, y que
es una medida de cuanto tiempo de procesamiento tiene asignado un CPU antes de que sea elegido para ser quitado
del CPU. El quantum tipicamente puede ser de 10 a 100 milisegundos.
La planifiación Round-Robin mantiene la cola de listo como una cola de prioridad. Los procesos nuevos, que
tipicamente tienen la prioridad más alta, se agregan al final de la lista. El planificador de la CPU toma
el primero proceso de la cola de listo, fija el valor de un temporizador para interrumpir después de que
transcurra 1 Quantum y despacha el proceso.
Si el proceso ocupa menos de 1 quantum en tiempo de CPU, entrega de manera voluntaria al mismo y por tanto
el planificador pasará al siguiente proceso en la cola de listos. En caso de que ocupe mas de 1 quantum, el
temporizador se apagará y provocará una interrupción que se comunica al sistema operativo. Se ejecuta una
conmutación de contexto y el proceso será puesto en la cola de prioridades, según la prioridad que tenga.
El planificador pondra en el CPU el siguiente proceso en la cola de listos.
En el caso de Xinu, siempre tiene la referencia al proceso ejecutandos, por medio de la variable global currpid.
Cuando el planificador tiene que aplicar un cambio de contexto, utiliza currpid para indexar la tabla de procesos
y aplicar el criterio de planificación round-robin.
El planificador solo bajo una condición no puede ser aplicado y es cuando el kernel esta realizando actividades
criticas del sistema y en esta idea Xinu debe tener control total del CPU. Xinu utiliza una variable globlal
denominada pcxflag la cual es 0 cuando no se habilita la planificación (se define en el archivo intmap.asm y
las rutinas que la manejan estan en el archivo xeidi.asm)
El codigo del planificador es aplicado por la rutina resched (archivo resched.c)
/* resched.c - resched */
#include
#include
#include
#include
/*------------------------------------------------------------------------
* resched -- reschedule processor to highest priority ready process
*
* Notes: Upon entry, currpid gives current process id.
* Proctab[currpid].pstate gives correct NEXT state for
* current process if other than PRCURR.
*------------------------------------------------------------------------
*/
int resched()
{
register struct pentry *optr; /* pointer to old process entry */
register struct pentry *nptr; /* pointer to new process entry */
optr = &proctab[currpid];
if ( optr->pstate == PRCURR ) {
/* no switch needed if current prio. higher than next */
/* or if rescheduling is disabled ( pcxflag == 0 ) */
if ( sys_pcxget() == 0 || lastkey(rdytail) < optr->pprio )
return;
/* force context switch */
optr->pstate = PRREADY;
insert(currpid,rdyhead,optr->pprio);
} else if ( sys_pcxget() == 0 ) {
kprintf("pid=%d state=%d name=%s",
currpid,optr->pstate,optr->pname);
panic("Reschedule impossible in this state");
}
/* remove highest priority process at end of ready list */
nptr = &proctab[ (currpid = getlast(rdytail)) ];
nptr->pstate = PRCURR; /* mark it currently running */
preempt = QUANTUM; /* reset preemption counter */
ctxsw(&optr->pregs,&nptr->pregs);
/* The OLD process returns here when resumed. */
return;
}
El planificador manipula una variable global, denominada preempt, que contiene el numero de unidades del
Quantum
El siguiente código muestra el uso de la función resched(), dando 3 procesos con distintas prioridades
#include
#include
#include
extern int rdytail;
extern int rdyhead;
void imprimircola(void)
{
int i;
/*imprimir la cola q*/
for (i=0;i
#include
#include
#include
/*------------------------------------------------------------------------
* ready -- make a process eligible for CPU service
*------------------------------------------------------------------------
*/
int ready (pid)
int pid; /* id of process to make ready */
{
register struct pentry *pptr;
if (isbadpid(pid))
return(SYSERR);
pptr = &proctab[pid];
pptr->pstate = PRREADY;
insert(pid,rdyhead,pptr->pprio);
return(OK);
}
Al siguiente codigo crea un proceso, lo pone en estado de ready via la funcion ready() e imprime
la cola de procesos.
#include
#include
#include
extern int rdytail;
extern int rdyhead;
void imprimircola(void)
{
int i;
/*imprimir la cola q*/
for (i=0;i
#include
#include
/*------------------------------------------------------------------------
* resume -- unsuspend a process, making it ready; return the priority
*------------------------------------------------------------------------
*/
SYSCALL resume(pid)
int pid;
{
int ps; /* saved processor status */
struct pentry *pptr; /* pointer to proc. tab. entry */
int prio; /* priority to return */
disable(ps);
if (isbadpid(pid) || (pptr = &proctab[pid])->pstate != PRSUSP) {
restore(ps);
return(SYSERR);
}
prio = pptr->pprio;
ready(pid);
resched();
restore(ps);
return(prio);
}
El primer ejemplo de este capitulo muestra el uso de resume()
2.12 Suspensión de un proceso.
Un proceso se puede suspender si está en estado de READY o CURRENT (en ese caso el mismo ordeno su suspension).
Cuando está en estado de READY, se debe eliminar de la cola de procesos listos (usando la función dequeue).
Cuando está en estado de ejecución, CURRENT, se debe invocar al planificador.
En ambos casos, el proceso debe tener un estado distinto, suspendido, por lo que el campo pstate del PCB tiene
el valor constante PRSUSP.
Al aplicar la suspensión de un proceso, también se debe aplicar la activación y desactiviación de interrupciones.
La función que aplica la suspension se denomina suspend, que recibe un pid (archivo suspend.c)
/* suspend.c - suspend */
/* 8086 version */
#include
#include
#include
/*------------------------------------------------------------------------
* suspend -- suspend a process, placing it in hibernation
*------------------------------------------------------------------------
*/
SYSCALL suspend(pid)
int pid; /* id of process to suspend */
{
struct pentry *pptr; /* pointer to proc. tab. entry */
int ps; /* saved processor status */
int prio; /* priority returned */
disable(ps);
if (isbadpid(pid) || pid==NULLPROC ||
((pptr= &proctab[pid])->pstate!=PRCURR && pptr->pstate!=PRREADY)) {
restore(ps);
return(SYSERR);
}
if (pptr->pstate == PRREADY) {
dequeue(pid);
pptr->pstate = PRSUSP;
} else {
pptr->pstate = PRSUSP;
resched();
}
prio = pptr->pprio;
restore(ps);
return(prio);
}
El siguiente código muestra como se usa la función suspend de Xinu
#include
#include
#include
extern int rdytail;
extern int rdyhead;
extern int currpid;
void imprimircola(void)
{
int i;
/*imprimir la cola q*/
for (i=0;i
#include
#include
#include
#include
/*------------------------------------------------------------------------
* kill -- kill a process and remove it from the system
*------------------------------------------------------------------------
*/
SYSCALL kill(pid)
int pid; /* process to kill */
{
struct pentry *pptr; /* points to proc. table for pid*/
int ps; /* saved processor status */
disable(ps);
if (isbadpid(pid) || (pptr = &proctab[pid])->pstate==PRFREE) {
restore(ps);
return(SYSERR);
}
if (--numproc == 0)
xdone();
freestk(pptr->pbase, pptr->plen);
switch (pptr->pstate) {
case PRCURR: pptr->pstate = PRFREE; /* suicide */
resched();
case PRWAIT: semaph[pptr->psem].semcnt++;
/* fall through */
case PRSLEEP:
case PRREADY: dequeue(pid);
/* fall through */
default: pptr->pstate = PRFREE;
}
restore(ps);
return(OK);
}
2.14 Creación de un proceso.
Cuando un proceso se crea, se debe indicar el código a ejecutar, el tamaño sugerido de la pila, su prioridad
inicial, nombre y argumentos.
La creación de un proceso es basicamente llenar la entrada del PCB asignado, con una busqueda previa de una
entrada libre y pedir a las rutinas de manejo de memoria espacio para la pila. Un proceso recien creado
tiene el estado de suspendido. Llena la dirección del apuntador de la pila e indica que al concluir un proceso
debe ejecutar una rutina que lo finalice y que llama a la rutina kill().
La creación también se hace bajo el contexto de desactivar y activar interrupciones.
El código se encuentra en el archivo create.c, que contiene la funcione create y una función denominada
newpid, que retorna el PID de la entrada del PCB que este libre
/* create.c - create, newpid */
#include
#include
#include
#include
#define INITF 0x0200 /* initial flag register - set interrupt flag, */
/* clear direction and trap flags */
extern int INITRET(); /* location to return upon termination */
/*------------------------------------------------------------------------
* create -- create a process to start running a procedure
*------------------------------------------------------------------------
*/
SYSCALL create(procaddr,ssize,priority,namep,nargs,args)
int (*procaddr)(); /* procedure address */
word ssize; /* stack size in words */
short priority; /* process priority > 0 */
char *namep; /* name (for debugging) */
int nargs; /* number of args that follow */
int args; /* arguments (treated like an array) */
{
int pid; /* stores new process id */
struct pentry *pptr; /* pointer to proc. table entry */
int i; /* loop variable */
int *a; /* points to list of args */
char *saddr; /* start of stack address */
int *sp; /* stack pointer */
int ps; /* saved processor status */
disable(ps);
ssize = roundew(ssize);
if ( ssize < MINSTK || priority < 1 ||
(pid=newpid()) == SYSERR ||
((saddr=getstk(ssize)) == NULL ) ) {
restore(ps);
return(SYSERR);
}
numproc++;
pptr = &proctab[pid];
pptr->pstate = PRSUSP;
for (i=0 ; ipname[i] = (*namep ? *namep++ : ' ');
pptr->pname[PNMLEN]='\0';
pptr->pprio = priority;
pptr->phasmsg = 0; /* no message */
pptr->pbase = saddr;
pptr->plen = ssize;
sp = (int *) (saddr+ssize); /* simulate stack pointer */
sp -= 4; /* a little elbow room */
pptr->pargs = nargs;
a = (&args) + nargs; /* point past last argument */
for ( ; nargs > 0 ; nargs--) /* machine dependent; copy args */
*(--sp) = *(--a); /* onto created process' stack */
*(--sp) = (int)INITRET; /* push on return address */
*(--sp) = (int)procaddr; /* simulate a context switch */
--sp ; /* 1 word for bp */
*(--sp) = INITF; /* FLAGS value */
sp -= 2; /* 2 words for si and di */
pptr->pregs = (char *)sp; /* save for context switch */
pptr->paddr = procaddr;
restore(ps);
return(pid);
}
/*------------------------------------------------------------------------
* newpid -- obtain a new (free) process id
*------------------------------------------------------------------------
*/
LOCAL newpid()
{
int pid; /* process id to return */
int i;
for (i=0 ; i
#include
/*------------------------------------------------------------------------
* userret -- entered when a process exits by return
*------------------------------------------------------------------------
*/
userret()
{
int pid;
kill( pid=getpid() );
kprintf("Fatal system error - unable to kill process %d",pid);
}
2.15 Habilitar y deshabilitar interrupciones
Las rutinas para habilitar y deshabilitar interrupciones se escriben en ensambaldor. Aunque en el código
se manejan como disable() y restore(), son realmente macros definidas en el archivo de encabezado del kernel
(kernel.h) que se presenta a continuación.
/* kernel.h - isodd, disable, restore, enable, pause, halt, xdisable, xrestore */
/* 8086/8 PC-Xinu version - for IBM PC/XT/AT and Clones */
/* Symbolic constants used throughout Xinu */
typedef char Bool; /* Boolean type */
typedef unsigned int word; /* word type */
#define FALSE 0 /* Boolean constants */
#define TRUE 1
#define NULL (char *)0 /* Null pointer for linked lists*/
#define SYSCALL int /* System call declaration */
#define LOCAL static /* Local procedure declaration */
#define INTPROC int /* Interrupt procedure */
#define PROCESS int /* Process declaration */
#define WORD word /* 16-bit word */
#define MININT 0100000 /* minimum integer (-32768) */
#define MAXINT 0077777 /* maximum integer (+32767) */
#define MINSTK 256 /* minimum process stack size */
#define NULLSTK 256 /* process 0 stack size */
#define OK 1 /* returned when system call ok */
#define SYSERR -1 /* returned when sys. call fails*/
/* initialization constants */
#define INITARGC 2 /* initial process argc */
#define INITSTK 512 /* initial process stack */
#define INITPRIO 20 /* initial process priority */
#define INITNAME "xmain" /* initial process name */
#define INITRET userret /* processes return address */
#define INITREG 0 /* initial register contents */
#define QUANTUM 1 /* clock ticks until preemption */
/* misc. utility functions */
#define isodd(x) (01&(int)(x))
#define disable(x) (x)=sys_disabl() /* save interrupt status */
#define restore(x) sys_restor(x) /* restore interrupt status */
#define enable() sys_enabl() /* enable interrupts */
#define pause() sys_wait() /* machine "wait for interr." */
#define halt() sys_hlt() /* halt PC-Xinu */
#define xdisable(x) (x)=sys_xdisabl() /* save int & dosflag status */
#define xrestore(x) sys_xrestor(x) /* restore int & dosflag status */
/* system-specific functions and variables */
extern int sys_disabl(); /* return flags & disable ints */
extern void sys_restor(); /* restore the flag register */
extern void sys_enabl(); /* enable interrupts */
extern void sys_wait(); /* wait for an interrupt */
extern void sys_hlt(); /* halt the processor */
extern int sys_xdisabl(); /* Return interrupts to MS-DOS */
extern void sys_restor(); /* Interrupts back to Xinu */
/* process management variables */
extern int rdyhead, rdytail;
extern int preempt;
El codigo ensamblador que aplica la deshabilitacion de las interrupciones de denomina sys_disabl y el
codigo que restaura las interrupciones se denomina sys_restor. Está definido en el archivo eidi.asm y contiene
otras rutinas que posteriormente se estudiaran
; eidi.asm - _sys_disabl, _sys_enabl, _sys_restor, _sys_wait, _sys_hlt
include dos.asm ; segment macros
dseg
; null data segment
endds
pseg
public _sys_disabl,_sys_restor,_sys_enabl,_sys_wait,_sys_hlt
;-------------------------------------------------------------------------
; _sys_disabl -- return interrupt status and disable interrupts
;-------------------------------------------------------------------------
; int sys_disabl()
_sys_disabl proc near
pushf ; put flag word on the stack
cli ; disable interrupts!
pop ax ; deposit flag word in return register
ret
_sys_disabl endp
;-------------------------------------------------------------------------
; _sys_restor -- restore interrupt status
;-------------------------------------------------------------------------
; void sys_restor(ps)
; int ps;
_sys_restor proc near
push bp
mov bp,sp ; C calling convenion
push [bp+4]
popf ; restore flag word
pop bp
ret
_sys_restor endp
;-------------------------------------------------------------------------
; _sys_enabl -- enable interrupts unconditionally
;-------------------------------------------------------------------------
; void sys_enabl()
_sys_enabl proc near
sti ; enable interrupts
ret
_sys_enabl endp
;-------------------------------------------------------------------------
; _sys_wait -- wait for interrupt
;-------------------------------------------------------------------------
; void sys_wait()
_sys_wait proc near
pushf
sti ; interrupts must be enabled here
hlt
popf
ret
_sys_wait endp
;-------------------------------------------------------------------------
; _sys_hlt -- halt the current program and return to host
;-------------------------------------------------------------------------
; void sys_hlt()
_sys_hlt proc near
mov ah,4ch ; terminate function
xor al,al ; OK return code
int 21h ; MS-DOS function call
ret
_sys_hlt endp
endps
end
La rutina sys_disabl, guarda en la pila de la rutina actual el registro FLAGS e invoca a la instrucción cli,
que permite deshabilitar interrupciones. Pone en el registro AX el valor de las banderas, la cual se retorna
a la rutina que le invoca.
La rutina sys_restor restuara via la funcion popf el registro FLAG
2.16 Temporizador.
Cuando se hablo del planificador se mencionó el concepto de un temporizador. Un temporizador es una rutina que
se dispara al momento que recibe una interrupción del reloj de la computadora. Cuando se estudie el manejo del
reloj se verá a mas detalle, pero es interesante mostrar la rutina que es activada por el temporizador.
/* clkint.c - clkint */
#include
#include
#include
#include
/*------------------------------------------------------------------------
* clkint -- clock service routine
* called at every clock tick and when starting the deferred clock
*------------------------------------------------------------------------
*/
INTPROC clkint(mdevno)
int mdevno; /* minor device number */
{
int i;
tod++;
if (defclk) {
clkdiff++;
return;
}
if (slnempty)
if ( (--*sltop) <= 0 )
wakeup();
if ( (--preempt) <= 0 )
resched();
}
Como se puede ver, la variable global preempt es disminuida en una unidad y si llega a un valor menor o igual
que 0, se pide la planificación de procesos. De esta manera, es posible lograr que exista un proceso ejecutandose
y que se interrumpa despues de un tiempo definido.
               (
geocities.com/gusdelact)