Sei sulla pagina 1di 41

Concepto de proceso

Un programa es un conjunto de instrucciones en un lenguaje en


particular. Cuando un programa es compilado, linkeado y cargado en
memoria se transforma en un proceso

En términos simples, un proceso es


un programa en ejecución
una instancia de un programa corriendo en un computador
unidad de actividad caracterizada por la ejecución de una secuencia
de instrucciones, un estado actual, y un conjunto asociado de
recursos del sistema

La labor principal del SO está basada en el control de los procesos


De programa a proceso
Considere el siguiente programa en C, hello.c:
#include <stdio.h>
#define N 10
main() {
int i;
for (i=0; i < N; i++)
printf("Hola %d\n", i);
}
Las etapas que transforman este código en un programa ejecutable son:

Usando gcc podemos obtener los resultados de algunas de estas etapas:


$ gcc -o hello -save-temps hello.c
$ ls hello*
hello hello.c hello.i hello.o hello.s
El preprocesador

El preprocesador de gcc, llamado cpp se encarga de expandir los


#include<> y reemplazar la macros y #define<>, entre otras cosas.
El archivo transformado tiene extension .i
$ cat hello.i
...
typedef long unsigned int size_t;
...
extern int printf (__const char *__restrict __format, ...);

main() {
int i;

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


printf("Hola %d\n", i);
}
El compilador

EL compilador trasforma el código a lenguaje assembler

$ cat hello.s .L3:


.file "main.c" movl $.LC0, %eax
.section .rodata movl -4(%rbp), %edx
.LC0: movl %edx, %esi
.string "Hola %d\n" movq %rax, %rdi
.text movl $0, %eax
.globl main call printf
.type main, @function addl $1, -4(%rbp)
main: .L2:
.LFB0: cmpl $9, -4(%rbp)
.cfi_startproc jle .L3
pushq %rbp leave
.cfi_def_cfa_offset 16 .cfi_def_cfa 7, 8
.cfi_offset 6, -16 ret
movq %rsp, %rbp .cfi_endproc
.cfi_def_cfa_register 6 .LFE0:
subq $16, %rsp .size main, .-main
movl $0, -4(%rbp) .ident "GCC: (Ubuntu/Linaro 4.6.3-1ub
jmp .L2 .section .note.GNU-stack,"",@progbits
El ensamblador

El ensamblador traduce el programa assembler a lenguaje de


máquina
El producto resultante es llamado objeto reubicable, o simplemente
objeto.
El módulo aún no contiene el código de printf
El linkeador

Finalmente, el linker ld se encarga se resolver todas las referencias


pendientes
En este ejemplo, el linker mezcla los módulos hello.o con el
módulo de printf.o
El resultado es un código ejecutable
Este objeto está listo para ser cargado en memoria y transformarse
en un proceso

Un programa ejecutable NO es un proceso


Creación de procesos

Situaciones tı́picas que crean nuevos procesos:


1 Solicitud de un usuario mediante la ejecución de un comando:
$ ./hello
2 Solicitud de un proceso ya existente, mediante la invocación de un
llamado al sistema
main() {
...
pid = fork();
...
}
3 Cuando se abre un aplicación mediante la interacción en una GUI
4 El SO puede crear varios procesos al momento de bootear
El SO se encarga de crear a los procesos y administralos. Debe:
1 Procurar memoria principal para alojar al proceso, y
2 Crear una estructura o registro que contenga información acerca del
proceso
Bloque de control de proceso (PCB)
El PCB es el nombre genérico dado a la estructura de datos que el Kernel
maneja acerca del proceso. Contiene toda la información que describe al
proceso en el sistema.

PID: Identificador del proceso


Otros identificadores (padre, grupo,
etc)
Estado: qué está haciendo el
proceso
Prioridad
Tiempos de procesador
Información de I/O
Información de memoria
Contexto
El PCB de Linux
El descriptor de procesos en Linux es task struct
(include/linux/sched.h). Algunos de los elementos más
importantes son:
struct task_struct {
volatile long state; // estado del proceso
long counter; // Usada junto con prioridad
long priority; // prioridad
unsigned long blocked; // señales mascaradas
struct task_struct *next_task, *prev_task; // punteros, anterior y p
int pid; // Process ID
struct task_struct *p_opptr, *p_pptr, *p_cptr;
// punteros al padre original, actual
// hijo más joven, etc
unsigned short uid,euid,suid,fsuid; // ID usuarios
long utime, stime, cutime, cstime, start_time; // usos de procesador
struct thread_struct tss; // Thread state stack
struct fs_struct *fs; // info de sistema de archivos
struct files_struct *files; // info sobre archivos abiertos
struct mm_struct *mm; // punero a estructura de admin memoria
struct signal_struct *sig; // manejadores de señales

};
La lista de descriptores

Linux mantiene una lista dóblemente enlazada de task struct


Componentes de un proceso en memoria
En memoria, un proceso se organiza en segmentos. Los principales son:

Memoria

Texto

Datos globales
Texto: código ejecutable
Datos globales: inicializados y Heap
no inicializados
Heap: memoria dinámica
Stack de usuario: usado para
paso de parámetros en llamados
a funciones y variables locales

Stack usuario
Intel Task State Segment (TSS)

Independientemente del PCB definido por un SO, éste debe finalmente


ajustarse al descriptor de tareas del procesador estructura.

struct tss_struct {
unsigned short back_link,__blh;
unsigned long esp0;
unsigned short ss0,__ss0h;
unsigned long esp1;
unsigned short
unsigned long
ss1,__ss1h;
esp2;
EAX, ECX, EDX, EBX:
unsigned short
unsigned long
ss2,__ss2h;
__cr3;
registros propósito general
unsigned long
unsigned long
eip;
eflags; EFLAS: registros de status
unsigned long eax,ecx,edx,ebx;
unsigned long
unsigned long
esp;
ebp;
CS: Code segment
unsigned long
unsigned long
esi;
edi; DS: Data segment
unsigned short es, __esh;
unsigned short
unsigned short
cs, __csh;
ss, __ssh;
SS: Stack segment
unsigned short
unsigned short
ds, __dsh;
fs, __fsh; ES, FS, FS: otros Data segment
unsigned short gs, __gsh;
unsigned short ldt, __ldth;
unsigned short trace, bitmap;
unsigned long io_bitmap[IO_BITMAP_SIZE+1];
};
Proceso

Luego, el proceso se realiza con tres componentes principales:


1 Su descriptor
2 Su espacio de direcciones
3 Su contexto, que generalmente es parte del descriptor
Primer modelo de estados de un proceso

En términos simples, un proceso puede estar en uno de dos estados:


1 Corriendo, es decir ejecutándose en el procesador
2 No corriendo; en el sistema, pero fuera del procesador
Modelo de proceso de 5 estados

El estado no-corriendo se divide en dos: listo y bloqueado


Por conveniencia, se agregan dos estados más: Nuevo y Salir.

Despachar
Entrar Termino
Nuevo No corriendo Corriendo Salir

Time-out

Evento Esperar
ocurre evento

Bloqueado
Modelo de 5 estados

Corriendo (running): el proceso está corriendo, es decir está en el


procesador
Listo (ready): el proceso está listo para ejecutarse
Bloqueado (blocked): no puede ejecutarse hasta que un evento
dado ocurra. Ej: operación de I/O termine
Nuevo (new): está recién creado, pero aún no ha sido admitido en
la cola de listos. Ej. Aún no ha sido cargado en memoria
Fin (exit): un proceso que, por algún motivo, ya no sigue
ejecutándose en el sistema
Ejemplo transición entre estados

Proceso A

Proceso B

Proceso C

Dispatcher

0 2 4 6 8 10 12 14 16 18
Uso de múltiples colas de espera

El SO puede organizar los procesos en colas de espera, según sus


estados
Por ejemplo una cola de listos, otra de espera por I/O de disco, otra
para I/O de interface de red, etc.
Procesos suspendidos

El procesador es mucho más veloz que los dispositivos de I/O, y


serı́a posible que todos los procesos en memoria estuvieran
esperando por I/O
“Swapear” temporalmente algunos de estos procesos a disco y ası́
liberar memoria.
Procesos que han sido swapeados a disco desde el estado de
bloqueados, pasan al estado Suspendido (suspended)
Note que swapping es una operación de I/O
Modelo de estados con un estado suspendido

Despachar
Entrar Termino
Nuevo No corriendo Corriendo Salir

Time-out

Activar Evento
Esperar
ocurre
evento

Suspendido Bloqueado
Suspender
Modelo de estados con dos estados suspendidos

Bloqueado: proceso en
memoria principal y
esperando por un evento
Nuevo

Bloqueado/Suspendido: Suspender

Admitir Admitir

proceso en memoria Despachar


Activar Termino

secundaria y esperando Listo/


Suspendido
No corriendo Corriendo Salir

Suspender
por un evento Time-out

Evento Evento

Listo/Suspendido: ocurre ocurre


Esperar
evento

proceso en memoria Bloqueado/


Suspender
Bloqueado
Suspendido

secundaria, pero listo Activar

para ejecución en
cuanto se cargue en
memoria principal
Cambio de contexto (process switch)

Un cambio de contexto ocurre cada vez que el procesador comienza o


reanuda la ejecución de un proceso distinto (al actual).

¿Cuándo hacer un cambio de contexto?


1 Trap: interrupción asociada a un error de la ejecución de la
instrucción actual
2 Interrupción: evento externo a la ejecución del proceso
3 Interrupción del reloj por término de tiempo
4 Fallo de memoria. La dirección de memoria no se encuentra
actualmente en memoria principal.
5 Llamado al sistema (system call): por ejemplo instrucción de I/O

Un cambio de contexto o cambio de proceso no es lo mismo que un


cambio del modo de ejecución del procesador
Labores en un cambio de contexto

Guardar el contexo del proceso incluyendo el PC y otros registros


Actualizar el PCB del proceso que actualmente está en ejecución
Mover el PCB de la cola de listo a la cola apropiada.
Seleccionar otro proceso para ejecución
Actualizar el PCB del proceso seleccionado
Actualizar las estructuras de administración de memoria
Restaurar el estado del procesador con el contexto del proceso
elegido
Ejecución del sistema operativo

1 Ejecución en el contexto de un proceso usuario


Cada vez que se solicita un servivio al SO, simplemente se realiza un
llamado a una rutina del SO, pero no hay cambio de contexto.
Hay cambio de modo usuario a modo kernel
Un número pequeño de tareas, como por ejemplo cambio de
contexto, pueden ejecutarse fuera del contexto usuario
2 Basado en procesos
Implementa las tareas del SO como una colección de procesos del
sistema
Útil en sistemas multiprocesadores y multicomputadores, pues
permiten que los servicios prestados por los procesos puedan ser
ejecutados exclusivamante en algunos procesadores y ası́ mejorar
rendimiento.
Cambio del modo de ejecución

Existen dos modos principales para la ejecución de procesos:


1 Modo usuario : Es un modo no privilegiado, en el cual el proceso no
puede ejecutar instrucciones reservadas al kernel
2 Modo kernel : En este modo, el proceso puede ejecutar cualquier
tipo de instrucciones y tiene el control absoluto del sistema
En SO donde el kernel se ejecuta en el contexto de procesos usuarios
(Unix, por ejemplo) es necesario realizar cambio en el modo de
ejecución cada vez que un proceso invoca algún llamado al sistema
Recordar que el procesador chequea por interrupciones durante el
ciclo fetch.
Si hay alguna interrupción pendiente, el procesador realiza lo
siguiente:
1 Setea el PC con la dirección inicial del manejador de interrupciones
2 Cambia el modo del procesador de modo usuario a modo kernel, de
manera que el manejador de interrupciones pueda ejecutar
instrucciones privilegiadas.
Modelo de estados en Unix
Modelo de estados en Linux

TASK RUNNING: en la cola de listos o


en el procesador
TASK INTERRUPTABLE: proceso
TASK ZOMBIE
bloqueado esperando un evento: fin de
TASK STOPPED
I/O, o alguna señal de otro procesos
TASK RUNNING TASK RUNNING (in
TASK UNINTERRUPTABLE: proceso
start
(ready) processor)
bloqueado esperando alguna condición
de hardware. No recibe señales de otros
TASK INTERRUPTIBLE
procesos
TASK ZOMBIE: proceso finalizado
TASK UNINTERRUPTIBLE
TASK STOPPED: proceso detenido y
sólo puede reanudarse mediante una
señal de otro proceso (ejemplo
debugger)
Comando ps

Código Estado
R Runnable (TASK RUNNING)
S Sleeping (TAKS INTERRUPTABLE)
D Sleeping (TASK UNINTERRUPTABLE)
T Stopped (TASK STOPPED).
Z Zombie (TASK ZOMBIE).
l Multi-thread
s Session leader
< Alta prioridad
N Baja prioridad
Creación de procesos en Unix

fork() es el llamado al sistema para crear un nuevo proceso


#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

fork() crea un proceso hijo, copia exacta del proceso padre. Lo


único diferente es el identificador de proceso o pid
fork() retorna:
1 el pid del hijo, en el proceso padre
2 0, en el proceso hijo
3 -1 si hubo algún error (sólo en el padre)
fork() es llamado una vez, pero retorna dos veces
Ejemplo 1 de fork()
#include <sys/types.h>
#include <unistd.h>

main()
{
int pid;
if ( (pid = fork()) == -1) {
printf("No se pudo crear hijo\n");
exit(-1);
}
if (pid == 0) { // soy el hijo
printf("Soy el hijo y mi pid = %d\n", getpid());
printf("Hijo: el pid de mi padre es = %d\n", getppid());
exit(0);
}
else { // soy el padre
printf("Soy el padre y mi pid = %d\n", getpid());
printf("Padre: el pid de mi hijo es %d\n", pid);
printf("Padre: el pid de mi padre es %d\n", getppid());
exit(0);
}
}
Ejemplo 2 de fork()
Identificadores

En sistemas Unix-like cada proceso tiene al menos seis


identificadores asociados con el.
pid t getpid(void): ID del proceso (el que invoca)
pid t getppid(void): ID del padre del proceso
uid t getuid(void): ID del usuario real del proceso
uid t geteuid(void): ID del usuario efectivo del proceso
gid t getgid(void): ID del grupo real del proceso
gid t getegid(void): ID del grupo efectivo del proceso
El usuario y grupo real identifican quién realmente somos y son
seteados al momento de hacer login al sistema (archivo password
/etc/passwd)
El usuario y grupo efectivo definen los permisos de acceso del
proceso a los archivos.
El 1/2 ambiente de un proceso

El medio ambiente de un proceso se refiere a todos aquellos elementos


que permite que un proceso se ejecute; incluye
Cómo comienza la ejecución de un proceso;
Los recursos asociados y sus lı́mites;
El layout en memoria principal;
Los argumentos de linea de comando;
Las variables de medio ambiente.
La función main()

Un programa en C comienza su ejecución cuando la funciı́on main()


es invocada.
int main(int argc, char *argv[]);

argc es el número de argumentos en lı́nea de comandos


*argv[] es el vector de punteros los argumentos de lı́nea de
comandos Ejemplo:
$ sort -n 100 -o file.txt
argc es 5

argv[] = {‘‘sort’’, ‘‘-n’’, ‘‘100’’, ‘‘-o’’, ‘‘file.txt


En realidad, el kernel llama a una rutina start-up la cual finalmente
invoca a main
Inicio, ejecución y término de un proceso

Cuando el proceso invoca exit(), el proceso realiza operaciones de


“limpieza” antes de retornar al kernel.
Cuando el proceso invoca exit(), el proceso retorna
inmediatamente al kernel.
La función atexit()

Esta función le permite al proceso registrar manejadores de salida, es


decir funciones que se ejecutan cuando el proceso termina.
#include <stdlib.h>
int atexit(void (*func)(void));

Cuando el proceso termina, se invocan las funciones registradas con


atexit() en el orden inverso al que fueron registradas

static void limpieza(void);


int main(int argc, char *argv[]) {
if (atexit(limpieza) != 0)
err_sys("error registrar limpieza()");

printf("eso es todo, amigos\n");


return(0);
}

static void limpieza(void) {


printf("limpiando...\n");
}
Variables de 1/2 ambiente
Un proceso puede acceder a sus variables de 1/2 ambiente mediante
el puntero environ
extern char **environ;

Cada variable consiste de un par nombre=valor y pueden ser leı́das


por el proceso usando la función
char *getenv(const char *name);

Estas “variables” no tiene valor para el kernel; el significado es dado


por las aplicaciones

environ "HOME=/home/batman"
"PATH=/bin:/usr/bin"

"SHELL=/bin/bash"
"USER=batman"
"PWD=/home/batman/lab1"

NULL
Layout de un proceso Linux en memoria

$ cat /proc/self/maps
001c2000-001c3000 r-xp 001c2000 00:00 0 [vdso]
002b8000-002d1000 r-xp 00000000 08:01 66950 /lib/ld-2.5.so
002d1000-002d2000 r-xp 00019000 08:01 66950 /lib/ld-2.5.so
002d2000-002d3000 rwxp 0001a000 08:01 66950 /lib/ld-2.5.so
00c87000-00dc4000 r-xp 00000000 08:01 67366 /lib/i686/nosegneg/libc-2.5.so
00dc4000-00dc6000 r-xp 0013d000 08:01 67366 /lib/i686/nosegneg/libc-2.5.so
00dc6000-00dc7000 rwxp 0013f000 08:01 67366 /lib/i686/nosegneg/libc-2.5.so
00dc7000-00dca000 rwxp 00dc7000 00:00 0
08048000-0804d000 r-xp 00000000 08:01 7294713 /bin/cat
0804d000-0804e000 rw-p 00004000 08:01 7294713 /bin/cat
0987f000-098a0000 rw-p 0987f000 00:00 0
b7db6000-b7fb6000 r--p 00000000 08:01 7264522 /usr/lib/locale/locale-archive
b7fb6000-b7fb7000 rw-p b7fb6000 00:00 0
b7fcf000-b7fd0000 rw-p b7fcf000 00:00 0
bfaf8000-bfb0d000 rw-p bfaf8000 00:00 0 [stack]

$ size /bin/cat
text data bss dec hex filename
18431 1036 0 19467 4c0b /bin/cat
Familia exec()

Si fork() crea un clon de un proceso, ¿cómo serı́a posible crear


procesos hijos totalmente distintos a un padre?
execve() ejecuta un nuevo programa en el proceso que realiza la
invocación
execve() no crea un nuevo proceso, sino que sobre-escribe el
programa actual con uno nuevo
#include <unistd.h>

int execve(const char *filename, char *const argv [], char *const

filename es el camino absoluto del programa ejecutable


argv[] es un arreglo con los argumentos de lı́nea de comandos
envp[] es el medio ambiente de ejecución del proceso
execve() no retorna si hubo éxito
Familia exec()

Las siguientes funciones son front-ends de execve():


#include <unistd.h>

int execl(const char *path, const char *arg, ...);


int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg , ..., char * const envp[
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

Para recordar:
”l” : lista uno a uno los argumentos
”v” : entrega un vector con los argumentos
”p” : usa el PATH definido en el medio ambiente del proceso para
encontrar el ejecutable
”e” : asigna un environment, es decir medio ambiente
Ejemplo de execve()

#include <sys/types.h>
#include <unistd.h>
main()
{
int pid;
if ( (pid = fork()) == -1) {
printf("No se pudo crear hijo\n");
exit(-1);
}
if (pid == 0) { // soy el hijo
if (execlp("ls", "ls", "/home/rannou", 0) == -1) {
printf("no se pudo hacer exec\n");
exit(-1);
}
printf("bye");
}
// soy el padre
wait(pid);
printf("mi hijo finalizo\n");
exit(0);
}

Potrebbero piacerti anche