1.29k likes | 1.45k Views
POSIX IEEE 1003. Sebastián Sánchez Prieto. Introducción. POSIX son un conjunto de normas IEEE/ISO que definen la interfaz entre las aplicaciones y el SSOO POSIX: P ortable O perating S ystem I nterface + Uni X
E N D
POSIX IEEE 1003 Sebastián Sánchez Prieto
Introducción • POSIX son un conjunto de normas IEEE/ISO que definen la interfaz entre las aplicaciones y el SSOO • POSIX: Portable Operating System Interface + UniX • Su objetivo es conseguir la portabilidad de las aplicaciones a nivel de código fuente • La aplicación puede desarrollarse en C, Ada, Fortran y otros lenguajes • Las normas definen los servicios que cada sistema operativo particular puede incluirlos o no • La denominación oficial es IEEE Std. 1003, e ISO/IEC-9945
POSIX: estándares base (C) • POSIX 1, 1a Unix básico sin tiempo real • POSIX 1b, 1d, 1i, 1j Extensiones de tiempo real • POSIX 1c Extensiones de threads • POSIX 1e Seguridad • POSIX 1f Network File System • POSIX 1g Servicios de red (sockets) • POSIX 1h Tolerancia a fallos • POSIX 21 Comunicaciones de TR
POSIX: interfaz otros lenguajes • POSIX 5, 5a, 5b Interfaces con Ada • POSIX 9 Interfaces con Fortran 77
POSIX: perfiles de entornos • POSIX 10 Supercomputadores • POSIX 13 Tiempo real • POSIX 14 Multiprocesadores • POSIX 18 Estación de trabajo POSIX
POSIX de tiempo real ¿para qué? • Existe gran diversidad de sistemas de TR: • Núcleos de TR (LynxOS, VxWorks, QNX, etc.) • Ejecutivos Ada • En sistemas grandes: VMS y otros • Era necesario definir un estándar que asegurase la portabilidad de aplicaciones a nivel de código fuente entre diferentes entornos de tiempo real
Perfiles de entornos de aplicación • PSE50: sistema de tiempo real mínimo • Sólo procesos ligeros, sin gestión de memoria ni archivos ni terminal • PSE51: controlador de tiempo real • Añade el terminal y sistema de archivos • PSE52: sistema de tiempo real dedicado • Soporta procesos pesados y gestión de memoria • PSE53: sistema de tiempo real generalizado • Sistema completo con todos los servicios
Características de los perfiles Perfil Sistema de archivos Múltiples procesos Threads Sistema mínimo NO NO SÍ Controlador SÍ NO SÍ Sistema dedicado NO SÍ SÍ Sistema multipropósito SÍ SÍ SÍ
POSIX: Unix básico • POSIX 1 define los servicios ofrecidos por Unix: • Gestión de procesos: • Creación y destrucción • Sincronización • Temporización • Gestión de archivos • Creación y borrado de archivos y directorios • Trabajo con archivos especiales • Protección de la información • Entrada-salida y control
POSIX: Extensiones de TR • Para obtener determinismo en el comportamiento • Planificación • Gestión de memoria • Señales • Relojes y temporizadores • Para facilitar la concurrencia • Sincronización • Memoria compartida • Colas de mensajes • Entrada-salida síncrona y asíncrona
POSIX: Extensiones de threads • POSIX 1c incorpora funciones para trabajar con hilos. Incluye: • Gestión de hilos • Sincronización de hilos • Planificación de hilos • Creación y destrucción de hilos • Añade reentrada a algunas funciones de POSIX1 • POSIX 1c puede hacer uso de funciones incluidas en POSIX 1 y POSIX 1b
Definiciones • Programa • Archivo ejecutable residente en un dispositivo de almacenamiento permanente • Se ejecuta por medio de la llamada exec() • Proceso • Es un programa en ejecución • Los procesos se crean con la llamada fork() • Servicios del sistema operativo • Invocados por medio de funciones • POSIX no diferencia entre llamadas al sistema y procedimientos de biblioteca
Estructura de un proceso en C Proceso de usuario Funciones main() Rutina de inicio _exit() exit() exec() Llamada al sistema Núcleo
Ejemplo /************************************** * Programa que imprime todos los * * argumentos de línea de órdenes * **************************************/ int main (int argc, char *argv[]) { int i; for (i=0; i<argc; i++) printf (“%s\n”, argv[i]); exit(0); } /* Fin de main */
Características de un proceso • Cada proceso se caracteriza por una estructura de datos conocida como tabla de control de tarea que contiene: • Identificador de proceso o PID • Identificador de proceso padre o PPID • Identificador de usuario o UID • Identificador de grupo o GID • Puntero a la memoria asignada • Puntero a los recursos ... • Cada proceso dispone de un espacio de direccionamiento virtual independiente
Creación de procesos: fork() • La llamada fork() crea una copia (hijo) del proceso que la invoca • El hijo hereda del padre: • Estado • Semáforos • Objetos de memoria • Política de planificación, etc. • El hijo no hereda: • El PID • Alarmas y temporizadores • Operaciones de E/S asíncronas
Interfaz de fork() • Definida en: #include <sys/types.h> pid_t fork(void); • Valores de retorno: • Al padre: el PID del hijo • Al hijo: cero • En caso de error: devuelve -1 y la variable errno contiene el valor asociado al error
Ejemplo con fork() #include <sys/types.h> int main(void) { pid_t id; id = fork(); if (id == -1) { perror (“Error en el fork”); exit (1); } if (id == 0) { while (1) printf (“Hola: soy el hijo\n”); } else { while (1) printf (“Hola: soy el padre\n”); } } /* Fin de main */
Ejecución de programas: exec() • La familia de llamadas exec() se emplea para cargar la imagen de un proceso desde un archivo a memoria • La nueva imagen se carga encima de la del proceso que invoca la llamada, machacándolo • El nuevo proceso hereda: • El PID y el PPID del proceso original • La política de planificación • Las alarmas y señales pendientes
Prototipos de la familia exec() • Definidos en: #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[]); Valores de retorno: • En caso de error exec() devuelve -1
Ejemplo con exec() #include <unistd.h> int main(void) { int ret; char *arg[3]; arg[0] = “ls”; arg[1] = “-l”; arg[2] = (char *)0; printf (“Allá va!\n”); ret = execv (“/bin/ls”, arg); if (ret == -1) { perror (“Error en el exec”); exit (1); } } /* Fin de main */
Finalización de procesos: exit() • La llamada exit() provoca la finalización del proceso que la invoca • Esta llamada nunca retorna • Cuando un proceso termina, sus hijos no mueren y suelen ser adoptados por el proceso init • Prototipo definido en: #include <unistd.h> void _exit (int status); • El valor status es retornado al padre si es que existe
Espera por procesos: wait() • La llamada wait() permite que un proceso quede esperando a que sus hijos terminen • El padre puede conocer el valor de retorno de cada hijo • Prototipos definido en: #include <sys/types.h> #include <sys/wait.h> pid_t wait (int *status); pid_t waitpid(pid_t pid, int *status, int options);
Ejemplo con wait() #include <sys/types.h> #include <sys/wait.h> int main(void) { pid_t id; int estado; id = fork(); if (id == -1) { perror (“Error en el fork”); exit (1); }
Continuación del ejemplo if (id == 0) { printf (“Soy el hijo\n”); sleep(3); printf (“Hijo: despierta y finaliza\n”); exit(0); } else { printf (“Soy el padre y ahora espero ...\n”); wait (&estado); printf (“Padre: el hijo terminó con estado = %d\n”, estado); exit (0); } } /* Fin de main */
Planificación • Los mecanismos clásicos de planificación no son válidos • Es necesario evitar el indeterminismo • POSIX 1b utiliza una planificación expulsora con prioridades fijas (32 como mínimo) con tres políticas diferentes: SCHED_FIFO FIFO para tareas de igual prioridad SCHED_RR Round-Robin en tareas con la misma prioridad. El quanto es fijo Definido por la realización concreta SCHED_OTHER
Planificación (continuación) • Los parámetros de planificación y los prototipos de las funciones se encuentran en: #include <sched.h> • Parámetros de planificación: struct sched_param { int sched_priority; } • Definir política y parámetros (hay que ser root): int sched_setscheduler (pid_t pid,int policy, const struct sched_param *param);
Planificación (continuación) • Leer la política y los parámetros: int sched_getscheduler (pid_t pid); int sched_getparam (pid_t pid, struct sched_param *param); • Ceder el procesador: int sched_yield (void); • Leer los límites de los parámetros: int sched_get_priority_max (int policy); int sched_get_priority_min (int policy); int sched_rr_get_interval (pid_t pid, struct timespec *interval);
Ejemplo #include <sched.h> #include <sys/types.h> int main(void) { pid_t pid; struct sched_param parametros; int i, max_prio; pid = getpid(); max_prio = sched_get_priority_max(SCHED_FIFO); parametros.sched_priority = max_prio; sched_setscheduler(pid, SCHED_FIFO, ¶metros); for (i=0; i<100000000; i++); } /* Fin de main */
Introducción • La memoria virtual introduce NO determinismo • POSIX proporciona la posibilidad de bloquear memoria para evitar la aleatoriedad • Los procesos a pesar de tener un espacio de direccionamiento disjunto pueden compartir objetos de memoria • La compartición se realiza mapeando la zona de memoria que deseamos compartir en los espacios de direccionamiento virtuales de cada proceso • La compartición se realiza a través de páginas de modo que el tamaño de una zona proyectada es un múltiplo del tamaño de la página
Bloqueo de memoria • Los procesos pueden bloquear todas sus páginas en memoria para evitar el intercambio con el disco #include <sys/mman.h> int mlockall (int flags); • El valor de flags puede ser: MCL_CURRENT: afecta a las páginas actuales MCL_FUTURE: afecta a las páginas futuras • Para liberar todas las páginas del proceso: int munlockall (void);
Bloqueo de memoria • Un proceso pueden bloquear también un rango de su espacio de direccionamiento int mlock (const void *addr, size_t len); • Para desbloquear un rango de direcciones: int munlock (const void *addr, size_t len);
Memoria compartida Proceso 1 Proceso 2 Memoria física M. Compartida M. Compartida M. Compartida
Proyección de objetos en memoria • La llamada mmap() permite proyectar objetos en memoria • Estos objetos pueden ser compartidos • Función mmap(): void *mmap (void *addr, size_t len, int prot, int flags, int fildes, off_t off); • El objeto queda identificado por fildes • len y off son la longitud y el offset del objeto en bytes respectivamente • addr es la dirección donde deseamos proyectar el objeto preferiblemente. Es sólo una indicación
Proyección de objetos en memoria • La dirección real donde se proyecta el objeto es devuelta por mmap()y depende del valor de flags: MAP_FIXED: addrse interpreta de forma exacta, sin esteflagsiaddrvaleNULL, el sistema elige la dirección MAP_SHARED: los cambios son compartidos MAP_PRIVATE: no se comparten los cambios • prot especifica el tipo de acceso: PROT_READ: derecho de lectura PROT_WRITE: derecho de escritura PROT_EXEC: derecho de ejecución PROT_NONE: sin derechos
Proyección de objetos en memoria • Para eliminar la proyección de un objeto en memoria emplearemos la función munmap() int munmap (void *addr, size_t len);
Objetos de memoria compartida • Para abrir un objeto de memoria compartida: #include <sys/mman.h> int shm_open (const char *path, int oflag, mode_t mode ); • Se establece una conexión entre el path que identifica al objeto y el descriptor devuelto • oflag determina el modo de acceso • mode determina los derechos de acceso si creamos un nuevo objeto • Es recomendable por razones de portabilidad que el path comience con el carácter /
Objetos de memoria compartida • Fijar el tamaño de un objeto de memoria compartida int ftruncate (int fildes, off_t length); • Para borrar un objeto de memoria compartida: int shm_unlink (const char *path);
Introducción • El reloj sirve para medir el paso del tiempo • Tick: • Unidad del tiempo • El número de ticks por segundo se puede conocer con sysconf (_SC_CLK_TCK) • Resolución: • Mínimo intervalo de tiempo que un reloj puede medir • La Época: CUT (Coordinated Universal Time) • 0 h 0 m 0s del 1 de enero de 1970
Introducción • Temporizador • Es un objeto que puede avisar a los procesos si ha transcurrido cierta cantidad de tiempo o se ha alcanzado cierta hora • Cada temporizador está asociado a un reloj • Reloj del sistema • Mide los segundos desde La Época • Mantiene la hora • Reloj de tiempo real • Se usa para timeouts y temporizadores
Reloj del sistema • Leer la hora: #include <time.h> time_t time (time_t *t); • time() devuelve los segundos transcurridos desde La Época • Si t es distinto de NULL en él se devuelve la hora también • Alarma: #include <unistd.h> unsigned int alarm (unsigned int seconds); • Cuando transcurren los segundos especificados se envía la señal SIGALRM
Ejemplo #include <signal.h> #include <stdio.h> #include <unistd.h> #include <time.h> void manejador (int senal) { time_t seg; printf (“Recibida la señal de alarma ... \n”); seg = time (NULL); printf (“Segundos desde La Época: %d\n”, seg); exit (0); }
Ejemplo (continuación) main() { struct sigaction accion; time_t seg; accion.sa_flags = 0; accion.sa_handler = manejador; sigemptyset (&accion.sa_mask); sigaction (SIGALRM, &accion, NULL); seg = time (NULL); printf (“Segundos desde La Época: %d\n”, seg); alarm (3); while (1); } /* Fin de main */
Reloj de tiempo real • La estructura timespec: typedef struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ } timespec_t; • Tiempo = tv_sec * 109 + tv_nsec
Manejo de relojes • Cambio de hora: #include <time.h> int clock_settime(clockid_t clock_id, const struct timespec *tp); • Obtención de la hora: int clock_gettime(clockid_t clock_id, struct timespec *tp); • Resolución del reloj: int clock_getres(clockid_t clock_id, struct timespec *res); • clockid_t debe valer CLOCK_REALTIME para TR
Ejemplo #include <time.h> main() { struct timespec stime; clock_getres (CLOCK_REALTIME, &stime); printf (“Segundos: %d\n”, stime.tv_sec); printf (“Nanosegundos: %ld\n”, stime.tv_nsec); } /* Fin de main */
Temporizadores • Se utilizan para generar señales en ciertos momentos o para ejecutar acciones periódicas • ¿Cómo crear un temporizador? #include <signal.h> #include <time.h> timer_create(clockid_t clock_id,struct sigevent *evp, timer_t *timerid); • sigevent indica el modo de aviso: ninguno, señal o crear y ejecutar un hilo • timerid es el identificador devuelto • El temporizador inicialmente no está activo