Programando en Linux Gestión de Procesos y Señales Realizado por: Kepa Bengoetxea

1 Programando en Linux Gestión de Procesos y Señales Real...
Author: Pedro Palma Medina
0 downloads 2 Views

1 Programando en Linux Gestión de Procesos y Señales Realizado por: Kepa Bengoetxea [email protected]

2 Referencias Programación en Linux con ejemplos.-Kurt Wall Descripción Funcional de los Sistemas Operativos.- Iñaki Alegria UNIX.Programación Avanzada.-Manuel Márquez

3 Antes de empezar Antes de empezar a programar en c, instalar: sudo apt-get install build-essential  Se instalarán los siguientes paquetes extras: dpkg-dev g++ g++-4.2 libc6-dev libstdc++6-4.2-dev libtimedate-perl linux- libc-dev patch  Paquetes sugeridos: debian-keyring g++-multilib g++-4.2- multilib gcc-4.2-doc libstdc++6-4.2-dbg glibc-doc manpages- dev libstdc++6-4.2-doc diff-doc Para instalar el man de los comandos de usuario del sistema POSIX: sudo apt-get install manpages-posix

4 Creación de procesos Entorno de un proceso consiste en un conjunto de variables que se le pasan al proceso en el momento de su creación. El entorno esta formado por una tabla NOMBRE-VALOR que se incluye en la pila del proceso. Ejm: PATH=/usr/bin/:/home/pepe/bin HOME=/home/pepe Servicios o llamadas a sistema que brinda UNIX para manipular y gestionar procesos.

5 Llamadas a Sistema Podemos agrupar los servicios en las siguientes categorías:  gestión de procesos: Crear: fork Cambiar: familia exec (execl, execv, execle, execve, execlp, execvp)‏ Terminar: exit Esperar: wait,waitpid  identificación de proceso: getpid, getppid, getuid, geteuid, getgid, getegid  entorno de un proceso: las variables de entorno que se pasan a un proceso son accesibles a través de la variable externa environ y las funciones getenv y setenv

6 Gestión de Proceso: Crear un proceso: exec Las llamadas a exec, son una familia de funciones con interfaz diferente pero funcionan de forma igual. La funciones de tipo exec cargan un programa en la zona de memoria del proceso que las ejecuta, sobreescribiendo los segmentos del programa antiguo con los del nuevo. El programa viejo es sustituido y nunca retornaremos a él. Si exec devuelve el control al programa que la invoca, es porque no se ha ejecutado correctamente. Devolviendo -1.

7 Gestión de Proceso: Crear un proceso: exec La declaración de la familia de funciones exec es la siguiente: //l:lista de argumentos int execl(char * path, char * arg0,...,char * argn, (char *)0); //v:vector de argumentos int execv(char * path, char *argv[]); //e:enviroment (variables de entorno)‏ int execle(char * path, char * arg0,.,char * argn, (char*)0, char * envp[]); int execve(char * path, char * argv[], char * envp[]);

8 Gestión de Proceso: Crear un proceso: exec La declaración de la familia de funciones exec es la siguiente: //p:busca un fichero ejecutable si el nombre de fichero especificado no contiene un carácter de barra inclinada int execlp (char * file, char * arg0,..., char * argn,(char*)0); int execvp (char * file, char * argv[])‏ char * file es un puntero que apunta a la ruta del programa que queremos ejecutar char * argv[] es un array de punteros a char que contiene los parámetros que queremos pasar a dicho programa (como si lo hiciéramos desde la linea de comandos), siendo el primer parámetro el nombre del mismo programa, y el último NULL, con lo que decimos que no quedan más parámetros.

9 Gestión de Proceso: Crear un proceso: exec Si el programa esta escrito en C: copy.c int main(int argc, char * argv[])‏ Llamada: copy fic1 fic2 argc argv 3 f\01cif 2cic ypo argv[0]=”copy” argv[1]=”fic1” argv[2]=”fic2”

10 Gestión de Proceso: Crear un proceso: exec La declaración de la familia de funciones exec es la siguiente: char * path es un puntero que apunta al path name (absoluto o relativo) de un fichero ejecutable char * arg0,...*argn son punteros a cadenas de caracteres y constituyen la lista de argumentos que se le pasa al nuevo programa. (char *)0 es un puntero a NULL, indica final de argumentos. char * envp[] es un puntero a un array de punteros a cadenas de caracteres que constituyen el entorno en el que se va ejecutar el nuevo programa. Termina con NULL.

11 Gestión de Proceso: Crear un proceso: exec Exec en detalle:  Función de si aceptan rutas/nombre de fichero: execl,execv,exele y execve: “/bin/ls” execlp,execvp:”ls” busca PATH sino tiene “/” en el nombre  Reciben los parámetros como: una lista de argumentos separados por comas: “/bin/cat”,”/etc/passwd”,”/etc/group”,NULL un vector: char * argv[]={“/bin/cat”,”/etc/passwd”,”/etc/group”};

12 Gestión de Proceso: Crear un proceso: exec Exec en detalle:  Entorno: Execve y execle permiten crear un entorno especializado para el programa: char *envp[]={”PATH=/bin:/usr/bin”,”USUARIO=Juan Perez”} El resto recibe sus entornos de manera implícita a través de la variable environ, esta se puede manipular con las funciones que estan en : int putenv(const char *string); char *getenv(const char *name);

13 Gestión de Proceso: Crear un proceso: exec Ejemplos: execl(“/bin/cat”,“/bin/cat”,”/etc/passwd”,”/etc/group”,NULL); char * argv[]={“/bin/cat”,”/etc/passwd”,”/etc/group”,NULL}; execv(“/bin/cat”,argv); char *argv[] = { "ls", "-l", NULL }; char *envp[] = { "HOME=/home/euiti", "LOGNAME=euiti", NULL}; execve ("/bin/ls", argv, envp);

14 Gestión de Proceso: Crear un proceso: exec #include #include /*man fork,exec */ #include /*man 2 wait */ int main() { int pid,status; pid=fork();

15 Gestión de Proceso: Crear un proceso: exec if (pid == 0) { /* Proceso Hijo */ if (execl("esclavo","esclavo", "nombre", "-a", NULL) == -1) { printf("Error al ejecutar execl\n"); exit(1);} } else { /* Proceso Padre */ wait(&status); printf("\nEl proceso hijo finalizo con el estado %d\n", status); exit(0);} }

16 Gestión de Proceso: Crear un proceso: exec esclavo.c: #include int main(int argc, char *argv[]) { int i = 0; for (i = 0; i < argc; i++) printf("\nArgumento [%d]: %s", i, argv[i]); exit(0);} gcc -o miexec.exe miexec.c gcc -o esclavo esclavo.c./miexec.exe

17 Gestión de Proceso: Crear un proceso: exec./miexec.exe Argumento [0]: esclavo Argumento [1]: nombre Argumento [2]: -a El proceso hijo finalizo con el estado 0

18 Gestión de Proceso: Crear un proceso: fork La uńica forma de crear un proceso en el sistema UNIX es mediante la llamada fork El proceso que invoca se le llama proceso padre y el proceso creado es el hijo fork crea un proceso hijo. El proceso hijo creado es una copia exacta del padre, excepto que recibe un valor diferente de la llamada fork. Devuelve 0 al proceso hijo y el PID del hijo al proceso padre y un valor -1 en caso de no poder crearse el proceso hijo.

19 Gestión de Proceso: Crear un proceso: fork Cuando llamamos a fork, el kernel realiza las siguientes operaciones:  Busca una entrada libre en la tabla de procesos y la reserva para el proceso hijo  Asigna un identificador de proceso para el proceso hijo, es invariante mientras viva, es la clave para controlarlo.  Se crea un nuevo proceso hijo con sus propias copias del texto del programa,datos y segmentos de pila del padre, incluyendo UIDs y GIDs reales y efectivos, archivos abiertos y segmentos de memoria compartida (no hereda las alarmas programadas por alarm, ni las señales pendientes)‏  Retorna al proceso padre el PID del proceso hijo, y al proceso hijo le devuelve el valor de 0.

20 Gestión de Proceso: Crear un proceso: fork El proceso padre, antes de finalizar se suspende hasta que el hijo acaba, para evitar que éste se quede zombie. Para ello, utiliza la llamada al sistema wait(), que recibe en la variable status el estado en que el proceso hijo finalizó. #include #include /*man 2 fork */ #include /*man 2 wait */ #include int main() {

21 Gestión de Proceso: Crear un proceso: fork Int pid = 0, status = 0; if ((pid = fork()) == -1) { printf(''Error al crear proceso hijo\n'');exit(1); } if (pid == 0) { printf(''El PID de mi padre es %d\n'',getppid()); exit(0); } else { printf(''Mi PID es %d y el PID de mi hijo es %d\n'', getpid(), pid); wait(&status); printf(''\nEl proceso hijo finalizo con el estado %d\n'', status); exit(0);}

22 Gestión de Proceso: Crear un proceso: fork $gcc -o mifork.exe fork.c $./mifork.exe El PID de mi proceso padre es 4693 Mi PID es el 4693 y he creado un proceso hijo cuyo pid es 4694 El proceso hijo finalizo con el estado 0 ó Mi PID es el 4693 y he creado un proceso hijo cuyo pid es 4694 El PID de mi proceso padre es 4693 El proceso hijo finalizo con el estado 0

23 Gestión de Proceso: Esperar: wait Un proceso zombie es un proceso que ha completado su ejecución y a liberado todos los recursos, pero aún tiene una entrada en la tabla de procesos, permitiendo al proceso que le ha creado lea el estado de su salida. Al padre se le envía una señal SIGCHLD indicando que el proceso ha muerto; el manejador para esta señal será típicamente ejecutar la llamada al sistema wait, que lee el estado de salida y borra al zombie. El ID del proceso zombie y la entrada en la tabla de procesos pueden volver a usarse. Los zombies pueden ser identificados con ps por la presencia de una Z en la columna de estado.

24 Gestión de Proceso: Esperar: wait En UNIX el kernel asigna a "init" cualquier proceso cuyo padre muera antes que hacer un wait, automáticamente. "init" simplemente hace "wait" esperando que alguno de sus hijos (heredados) muera. No tiene que preocuparse de saber cuales son. "init" simplemente hace un "wait", descarta la notificación y hace otro, en un bucle. Su única misión es "limpiar" los procesos zombie (zombie: proceso muerto a la espera de que su padre haga un wait)). A estos procesos se le llaman procesos huérfanos.

25 Gestión de Proceso: Esperar: wait wait: La función wait suspende la ejecución del proceso actual hasta que un proceso hijo ha terminado, o hasta que se produce una señal cuya acción es terminar el proceso actual. Todos los recursos del sistema reservados por el hijo son liberados. waitpid:Función que espera hasta que termine el proceso con pid #include y #include pid_t wait(int *status) pid_t waitpid(pid_t pid, int *status, int options);

26 Gestión de Proceso: Esperar: wait Parámetros: pid_t pid Si -1: lo que significa que espera por cualquier proceso hijo; este es el mismo comportamiento que tiene wait. Si 0: lo que significa que espera por cualquier proceso hijo cuyo ID es igual al del proceso llamante. Si > 0: lo que significa que espera por el proceso hijo cuyo ID es igual al valor de pid.

27 Gestión de Proceso: Esperar: wait Parámetros: options: WNOHANG (wait if no hang): que significa que vuelve inmediatamente si ningún hijo ha terminado. WUNTRACED (wait if no traced) : que significa que también vuelve si hay hijos parados, y de cuyo estado no ha recibido notificación. Ambas conductas: WNOHANG||WUNTRACED Nínguna:0 (No vuelve, permanece a la espera)‏ variable entera ''status'' :el estado de salida del hijo (si el hijo ha acabado su ejecución sin error, lo normal es que devuelva cero)‏

28 Gestión de Proceso: Esperar: wait Ejm:waitpid #include int main() { int pid = 0, status = 0; if ((pid = fork()) == -1){ printf("Error al crear proceso hijo\n");exit(1); } if (pid == 0) { sleep(30);printf("El PID de mi padre es %d\n",getppid()); exit(0); }

29 Gestión de Proceso: Esperar: wait Ejm:waitpid else { waitpid(pid,&status,WNOHANG); printf("Mi PID es %d y el PID de mi hijo es %d\n", getpid(), pid); printf("\nEl proceso hijo finalizo con el estado %d\n", status);exit(0);}} Al Ejecutar: Mi PID es 17101 y el PID de mi hijo es 17102 El proceso hijo finalizo con el estado 0 $ El PID de mi padre es 1

30 Gestión de Proceso: Esperar: wait Ejm: Si cambiamos por wait else { wait(&status); printf("Mi PID es %d y el PID de mi hijo es %d\n", getpid(), pid); printf("\nEl proceso hijo finalizo con el estado %d\n", status);exit(0);}} Al Ejecutar: El PID de mi padre es 17270 Mi PID es 17270 y el PID de mi hijo es 17271 El proceso hijo finalizo con el estado 0

31 Gestión de Proceso: Identificación de proceso Información del proceso:(Están en unistd.h)‏ getpid:Función que devuelve el identificador del proceso. Ejm: pid_t getpid(void); getppid:Función que devuelve el identificador del proceso padre. Ejm: pid_t getppid(void); getuid:Función que devuelve el identificador de usuario real.Ejm: uid_t getuid(void); geteuid:Función que devuelve el identificador de usuario efectivo. Ejm:uid_t geteuid(void);

32 Gestión de Proceso: Identificación de proceso getgid:Función que devuelve el identificador de grupo real. Ejm: gid_t getgid(void); getegid:Función que devuelve el identificador de grupo efectivo. Ejm:gid_t getegid(void);

33 Gestión de Proceso: Entorno de un proceso Entorno de un proceso: las variables de entorno que se pasan a un proceso son accesibles a través de la variable externa environ (un array de apuntadores a char) extern char **environ; ó extern char *environ[ ];  Obtener el valor de una variable de entorno: #include char * getenv (const char *name);  Definir el entorno de un proceso: #include int putenv (const char *string); Nota:extern: variable global y que está en otro fichero fuente.

34 Gestión de Proceso. Entorno de un proceso:environ Es un programa que nos muestra el contenido de la variable environ, simula el script $env #include int main(void)‏ {extern char **environ; /*char * environ[] while (*environ)‏ puts(*environ++); } Nota:extern: variable global y que está en otro fichero fuente.

35 Gestión de Proceso. Entorno de un proceso:environ $env y./env.exe TERM=xterm SHELL=/bin/bash HISTSIZE=1000...

36 Gestión de Proceso: Entorno de un proceso:getenv y setenv #include int main(void){ char variable_de_entorno[]={"MI_RUTA=/home/kepa/lana"}; if (putenv(variable_de_entorno))‏ puts("putenv fallo"); else puts("putenv tuvo éxito");

37 Gestión de Proceso: Entorno de un proceso:getenv y setenv if (getenv("MI_RUTA"))‏ printf("MI_RUTA=%s \n",getenv("MI_RUTA")); else puts("MI_RUTA sin asignar"); exit(EXIT_SUCCESS);} $gcc entorno.c -o entorno.exe $./entorno.exe putenv tuvo exito MI_RUTA=/home/kepa/lana

38 Gestión de Proceso: Terminar: Un proceso puede terminar por:  Su función main llama a return  llama a exit de la librería estándar de C uso general  llama a _exit declarada en llamada del sistema unix  llama a abort  es finalizado por una señal

39 Gestión de Proceso. Terminar:return return: return expresión; Fuerza la salida inmediata de una función. Y provoca que se salte al punto en que se hizo la llamada a la función. Si hay un valor asociado con return, se trata del valor de vuelta de la función. Si el return es de la función del main() provoca el fin del programa.

40 Gestión de Proceso. Terminar:exit exit: #include void exit(int status); La función exit produce la terminación normal del programa y la devolución de status al proceso padre. Antes de finalizar se llama a todas las funciones registradas con atexit() en orden inverso a su registro. status es el valor que se le devuelve al proceso padre como estado de terminación del proceso, y se puede leer mediante una de las funciones de la familia de wait. Nota: atexit - registra una función para ser llamada al momento de una terminación normal del programa.

41 Gestión de Proceso. Terminar:_exit _exit: #include void _exit(int status); Termina inmediatamente la ejecución del proceso invocador No se ejecutan las funciones que se encuentran registradas con atexit.

42 Gestión de Proceso: Abort abort  #include : void abort(void);  La función abort() causa una terminación anormal y graba una imagen de la memoria. Todos los canales abiertos se cierran y borran. Genera la señal: SIGABRT  Uso: casos extremos Ejm: sin memoria

43 Gestión de Proceso: Enviar señal abort vi abortprueba.c #include int main(void)‏ { abort(); /*el programa no debería llegar aqui*/ exit(EXIT_SUCCESS);}

44 Gestión de Proceso: Enviar señal abort $gcc -o abortprueba abortprueba.c $./abortprueba #permite el volcado de memoria del programa(core dump)‏ $ulimit -c unlimited $./abortprueba $ls -lias core

45 ¿En qué situaciones las usaremos? Programación en Linux:  Cuando uno quiere o necesita utilizar las prestaciones de un programa externo en su própio código.(Ejm: ls)‏  Resulta vital recoger los código de salida del fork ó exec con wait. Y también que los programas llamen a return o exit antes de salir del programa para poder recoger sus códigos de salida.  En caso de que tengamos problemas de código podremos incluir abort para crear una imagen de un programa en ejecución escribirla en disco, para la depuración del mismo.

46 Gestión de Proceso: Enviar señal kill:  #include y : int kill(pid_t pid, int sig);  Función que envía al proceso pid la señal SIGXXXX  devuelve 0 si ha ido bien y sino -1 si no se ha podido ejecutar.  Ejm:kill(pid,SIGTERM)‏

47 Gestión de Proceso: Enviar señal kill: Función que envía al proceso pid la señal SIGXXXX  Su declaración esta en: #include y : int kill(pid_t pid, int sig);  Ejm: #include /*kill y wait*/ #include /*wait*/ #include /*kill*/ #include /*puts y exit*/ #include /*printf*/ #include /*fork*/ int main(void){

48 Gestión de Proceso: Enviar señal kill: pid_t hijo; int condicion,valor_retornado; if ((hijo=fork())==-1){perror("fork");exit(EXIT_FAILURE);} if (hijo==0) {sleep(1000);exit(EXIT_SUCCESS);} else {valor_retornado=kill(hijo,SIGKILL); if (valor_retornado) {perror("kill");wait(&condicion);} else {printf("%d eliminado\n",hijo);} exit(EXIT_SUCCESS); }}

49 Gestión de Proceso: Enviar señal kill: $gcc -o killer killer.c $./killer 6680 eliminado

50 Gestión de Proceso: Enviar señal Señales  Suceso que tiene lugar en cualquier momento durante la ejcución de un proceso (Señal=Interrupción Software)‏  Cada señal tiene un nombre (kill -l ó man 7 signal) y las funciones que las tratan están en  Cuando un proceso recibe una señal, este puede realizar : Puede ignorar la señal Puede permitir que ocurra una acción predeterminada normalmente un exit con/sin volcado de un fichero core. Puede capturar la señal, ejecutando una rutina de tratamiento de señal(handler), y luego exit o volver a un estado anterior o al punto donde se produjo la interrupción.

51 Gestión de Proceso: Enviar señal man 7 signal signal - lista de las señales disponibles Señal Valor Acción Comentario ---------------------------------------------------------------------- SIGHUP 1 A Cuelgue detectado en la terminal de control o muerte del proceso de control SIGINT 2 A Interrupción procedente del teclado(Crtl-C)(salida)‏ SIGQUIT 3 C Terminación procedente del teclado(Ctrl-4)(salida con core)‏ SIGILL 4 C Instrucción ilegal

52 Gestión de Proceso: Enviar señal Señal Valor Acción Comentario ---------------------------------------------------------------------- SIGABRT 6 C Señal de aborto procedente de abort(3)‏ SIGFPE 8 C Excepción de coma flotante SIGKILL 9 AEF Señal de matar SIGSEGV 11 C Referencia inválida a memoria SIGPIPE 13 A Tubería rota: escritura sin lectores SIGALRM 14 A Señal de alarma de alarm(2)‏ SIGTERM 15 A Señal de terminación

53 Gestión de Proceso: Enviar señal Señal Valor Acción Comentario SIGUSR1 30,10,16 A Señal definida por usuario 1 SIGUSR2 31,12,17 A Señal definida por usuario 2 SIGCHLD 20,17,18 B Proceso hijo terminado o parado SIGCONT 19,18,25 Continuar si estaba parado SIGSTOP 17,19,23 DEF Parar proceso SIGTSTP 18,20,24 D Parada escrita en la tty SIGTTIN 21,21,26 D E. de la tty para un proc. de fondo SIGTTOU 22,22,27 D S. a la tty para un proc. de fondo

54 Gestión de Proceso: Enviar señal A-La acción por defecto es terminar el proceso B-La acción por defecto es ignorar la señal C-La acción por defecto es terminar el proceso y hace core dump D-La acción por defecto es parar la ejecución del proceso E-La señal no puede ser capturada por el programa(manipulada)‏ F-La señal no puede ser ignorada *core dump: volcado de memoria del contexto del proceso a la carpeta del proceso, para poder ver con un programa de depuración como gdb,sdb o adb

55 Gestión de Proceso: Enviar señal.Pause pause  Cuando queramos que un proceso espere a que llegue una señal. Esta función provoca que el proceso duerma hasta que llegue la señal.  Declaración: int pause (void)‏  Devuelve -1 cuando la llamada a la función que captura la señal ha terminado.

56 Gestión de Proceso: Enviar señal.Pause Ejm:vi pausa.c #include int main(void){pause(); exit(EXIT_SUCCESS);} $gcc -o pausa pausa.c $./pausa Ctrl-4 ó kill -USR1 Salir

57 Gestión de Proceso: Enviar señal.Signal signal  man signal  La función signal() recibe dos parámetros, el número de señal que queremos capturar (los números en el sistema en concreto en el que nos encontremos los podemos obtener ejecutando “kill –l”, como ya hemos visto), y un puntero a una función que se encargará de tratar la señal especificada.También puede ser una de las siguientes macros: SIG_IGN (No tener en cuenta la señal) ó SIG_DFL( Dejar la señal con su comportamiento predefinido)‏  Signal devuelve SIG_ERR si ocurre un error.

58 Gestión de Proceso: Enviar señal Conceptos básicos: Punteros a funciones.  Cuando una función en C se compila, se crea un punto de entrada en el código objeto.  Cuando se llama a la función se hace una llamada a ese punto de entrada.  El punto de entrada se puede utilizar para llamar a la función.  Declaración de puntero a función: tipo_devuelve_f (*p) ();  La dirección del punto de entrada se obtiene con el nombre de la fución sin paréntesis ni argumentos(Igual que los arrays)‏  Asignación: p=printf  Llamada: (*p)(“kaixo”)‏

59 Gestión de Proceso: Enviar señal ejemplo #include int sig; void funcion (sig)‏ {printf("Recibida la señal %d\n",sig);} int main(){ /*Se habilita la función */ signal(SIGUSR1,funcion); kill(getpid(),SIGUSR1); /*Se ignora la señal*/ signal(SIGUSR1,SIG_IGN); kill(getpid(),SIGUSR1); /*Tratamiento por defecto*/ signal(SIGUSR1,SIG_DFL); kill(getpid(),SIGUSR1); return 0; }

60 Gestión de Proceso: Enviar señal alarm: Función que activa un temporizador en el proceso que efectúa la llamada. Cuando el tiempo asignado al temporizador expira, se envía SIGALRM al proceso que efectuo la llamada. Definida en : unsigned int alarm(unsigned int seconds); sleep:Función que hace que el proceso quede inactivo durante x segundos. Ejm: int sleep(unsigned int seconds); Ejm:sleep(30)‏

61 Gestión de Proceso: Enviar señal.Alarm Ejm:vi alarma.c #include int main(void)‏ { if ((alarm(5))>0) {puts("Ya hay una alarma programada");} sleep(30); puts("¿Como fue que el programa llego aqui?"); exit(EXIT_FAILURE); }

62 Gestión de Proceso: Enviar señal.Alarm $gcc -o alarma alarma.c $./alarma Reloj de alarma

63 Crear y Manipular señales Programación en Linux:  El procedimiento general consiste en: Crear un conjunto de señales Establecer las señales que se quieren interceptar Registrar un handler de señales en el kernel Aguardar a interceptar la señal  Utilizar las siguientes funciones del API que están en /usr/include/signal.h :sigemptyset,sigfillset,sigaddset...