Es.KernelKewbies


 

> Parches
> Estado actual del kernel
> Encargados del Kernel
> Registros IRC
> Registros de cambios
> Vendedores del kernel
> Libros recomendados
> Documentacion
> Proyectos para el kernel
> Enlaces externos
> Lista de correo
> KernelNewbies Wiki
> Scripts utiles



<Manual CVS>


<Estadisticas>





 
  

Scheduling en UNIX y Linux

Hay una necesidad por procesos en un sistema los cuales ocasionalmente piden servicios del kernel. Algunnos sistemas operativos antiguos tuvieron un estilo citado de proveer esos servicios - El proceso requiere un servicio y espera un punto particular, hasta que la tarea venga del kernel y sirva por parte del proceso.

UNIX trabaja muy distinto. Mejor que tener tareas del kernel sirviendo las peticiones de un proceso , el proceso entra por si mismo a Espacio de Kernel. Esto significa, mejor que tener el proceso esperando "fuera" del kernel, este entra al kernel por si mismo (i.e. el proceso comenzara a ejecutar codigo de kernel por si mismo).

Esto puede sonar como un recipiente para un desastre, pero la capacidad del proceso de entrar a espacio de kernel esta estrictamente crontrolado (requiere soporte de hardware). Por ejemplo, en x86, un proceso entra a espacio de kernel a traves de llamadas de sistema - puntos bien conocidos a los cuales el proceso debe invokar para entrar al kernel.

Cuando un proceso invoca una llamada de sistema, el hardware es cambiado a configuraciones de kernel (por ejemplo, en x86, entre otras cosas, el nivel de proteccion esta establecido a ring 0 en vez de ring 3). En este punto, el proceso estara ejecutando codigo de la imagen del kernel. Tiene poder completo para hacer estragos en este punto, no como cuando estuvo en espacio de usuario. Ademas, el proceso no es mas pre-emptible.

Pre-emptibilidad

Los procesos en espacio de usuario son pre-emptibles - esto significa que los procesos pueden tomar la CPU arbitrariamente. Asi es como funcionan las multitarea pre-emptive: la rutina de scheduling peridocamente suspendera el proceso actual en ejecucion, y posiblemente schedule otra tarea para correr esa CPU. Esto significa teoricamente, que un proceso puede estar en la situacion donde este nunca devuelve la CPU. En relialidad el codigo de scheduling tiene interes en la viabilidad y tratara de dar la CPU a cada proceso con un nivel debil de viabilidad, pero no hay garantias.

En contraste, toda tarea (todos los objetos schedulables son referidos como "tareas" para claridad) corriendo en espacio de kernel No puede ser pre-empted. Esto significa que la CPU nunca sera scheduled away de la tarea. Este hecho es complicado por dos aspectos :

Interrupciones

A menos que se hayan deshabilitado las interrupciones (y algunas interrupciones no pueden ser deshabilitadas), una interrupcion puede ocurrir que durante la cual temporalmente interrumpira la tarea en ejecucion. Esto puede pasar en tareas en ambos espacios, de usuario y de kernel. La diferencia es que aqui, para tareas en espacio de kernel, la interrupcion garantiza retorno de la CPU a la tarea tarde o temprano. Para tareas en espacio de usuario, la interrupcion puede causar que otra tarea sea scheduled en la CPU, y puede tambien puede que se ejecuten otras tareas, - aqui en espacio de usuario la tarea debe ser escogida de nuevo por el scheduler. Por supuesto que esta no es la explicacion completa (como es usual) - Primero, hay subsistemas en espacio de kernel que pueden registrar codigo para ser ejecutado al volver de una interrupcion, tal como bottom halves y los tasklets. Esto no cambia el hecho de que, sin embargo, de que el scheduler no estara envuelto en la interrupcion de una tarea en espacio de kernel. Segundo, las interrupciones pueden interrumpir interrupciones - un ejemplo saliente es la arquitectura ARM, donde las interrupciones rapidas (FIQs) tienen prioridad mas alta de hardware que las interrupciones normales (IRQs). Por eso de hecho de una FIQ puede retornar a otro manejador de interrupciones. Tarde o temprano a traves, de la interrupcion original se completara y returnara la CPU a la tarea en espacio de kernel.

Multi-tarea co-operativa

Ya hemos dicho que una tarea en espacio de kernel space no puede tener la CPU scheduled away desde esta. Sin embargo, puede escoger ser schedule() con proposito (y por razones de latencia toda buena llamada de sistema hace esto usualmente). Notese que el termino "con proposito" es un poco engañoso - ya que actualmente significa que una tarea en espacio de kernel, lleno de propositos incluye codigo que puede causar que pase un scheduling. Por ejemplo una tarea puede establecer la politica SCHED_YIELD, entonces se llama volunariamente a la rutina para darle la CPU. Pero tambien puede causar un schedule() to happen usando rutinas que puedan dormir. Un ejemplo comun es kmalloc(), la cual duerme cuando es llamada con una prioridad GFP_KERNEL. La diferencia aqui pienso es que el codigo en espacio de kernel "sabe" que esto puede causar que ocurra un schedule, por eso tiene la oportunidad de mantener o retener la CPU si quiere. Una tarea en espacio de usuario no tiene oportunidad de eleccion de escoger en el asunto. Una consecuencias esta descrita mas abajo en la seccion "Scheduling y Locks".

Contextos de usuario e hilos del kernel

Recuerda, para el scheduler y para el kernel en grande, todo objecto que sea schedulable (i.e. cualquiera que pueda ser escogido por la rutina schedule()) es conocido como tarea. No se hace distinction entre alguno de esos objectos, por eso usualmente son llamados procesos, LWPs, hilos del kernel , fibras, hilos, etc. son todos solo tareas para el kernel, cada uno de ellos con sus propias caracteristicas particulares. Esto es una gran ganancia en terminos de limpieza de kernel - no hay razon real para separarlos fuera de caso.

Estas caracteristicas particulares estan pensadas interesantemente . Por ejemplo algunas tareas pueden tener mapeos de memoria y pila en espacio de usuario - un ejemplo tipico es un proceso que esta en espacio de usuario. El termino contexto de procesoes usado para referirse a una de esas tareas que se ejecutan en espacio de kernel - ellas tienen de mapeos de espacio, y los (posiblemente temporales) mapeos de kernel y pila. En este contexto se tiene sentido de la copia desde/hacia memoria de usuario.

Una vez mas, lo que a veces se conoce como "hilos del kernel" o "fibras" no se tratan diferente a las otras tareas. Ellas pueden tener mapeos de memoria en espacio de usuario solo como procesos "normales". La unica caracateristica distinguida aqui es el codigo que se ejecuta por los hilos de kernel, que vienen del kernel o de la imagen de un modulo, mejor que desde las imagenes de procesos binarios.

El termino contexto de interrrupcion se usa usualmente para denotar al codigo que actualmente se ejecuta como resultado de una interrupcion de hardware. Esto rodea bottom divide en dos, ISRs, softirqs, y tasklets. Aqui no hay tarea asociada tal como de menos significado para el schedule (y de hecho panicking bug). Esto tambien significa que no puedes dormir aqui, esto implica un schedule.

Algoritmo para Scheduling

El scheduler tiene un problema - hay una contradiccion entre latencia/igualdad (permitiendo que cada tarea se ejecute tan pronto como sea posible) y el costo del cambio de contexto (las operaciones necesarias para cambiar una tarea por otra). Que se asigne demasiado tiempo a una tarea significa que los procesos tendrar que esperar mas por la CPU - esto no es bueno si uno de los procesos esta tratando de brindar facilidades interactivas, por ejemplo. Hacer demasiados cambios usualmente se toma demasiado tiempo en este proceso, dejando menos CPU para hacer algo util en el momento. Cada tarea pre-emptible es ubicada en un periodo de tiempo - un pequeñisimo periodo de tiempo durante el cual se puede ejecutar. La Interrupcion temporizador es invocada peridicamente y esta decidira si la tarea deberia ser pre-empted en favor de otra tarea en la lista de ejecución (la lista de ejecucion es una lista que tiene todas las tareas que estan listas para ejecutarse). Adicionalmente, en el scheduling puede ocurrir cuando sea pedido por el codigo del kernel, y tambien despues que finalizan las llamadas a sistema, en la ruta de retorno de la llamada de sistema del codigo de kernel a espacio de usuario. Mas detalles: aqui.

Algun codigo

FIXME

Scheduling y Locking

Ya hemos mencionado que las tareas del kernel no pueden ser pre-empted a menos de que ellas escogan perimitirlo, en puntos bien conocidos (tal como kmalloc() llamada de prioridad GFP_KERNEL). Pero en sistemas SMP, esto aun significa que muchas tareas pueden ejecutarse en espacio de kernel al mismo tiempo (ademas se necesita protección adicional contra interrupciones , igual en sistemas ARRIBA).
Una consecuencia obia es que las tareas necesitan ser "sincronizadas", i.e. recursos compartidos deben darse exclusivamente a una tarea alterando esos resultados. La falta de sincronización adecuada de recursos compartidos se conoce como "condicion de carrera" - llamada de la nocion de que una tarea "esta en carrera" con otra en acceso al recurso. Esto es imparcialmente obio algo malo. Unos de los mecanismos de sincronizacion es el spinlock. Esta es simplemente es una estructura de datos la cual es adquirida automaticamente, y solo puede ser matenida por una sola tarea al tiempo. El spinlock es ya adquirido sobre (la sección de codigo que modifica el recurso compartido de un "estado conocido" a otro). Si la tarea trata de adquirir un spinlock ya ocupado (con spin_lock(&lock) o funcion similar) este "girara", i.e. ejecuta un pequeño bucle hasta que se lanza el spinlock .
Esto es por que es una mala idea (lee: ilegal) llamar a una funcion que puede dormir mientras un spinlock: Mantendras la CPU para otra tarea que puede tratar de aquirir el mismo spinlock. Esto puede facilmente direcciona a un deadlock donde tienes una tarea A esperando por un spinlock entonces este puede dejar libre un recurso necesitado por la tarea B, la cual actualmente mantiene el spinlock que quiere la tarea A. (Adicionalmente el scheduling con un spinlock ya adquirido significa que los apuntadores rotos pueden seguirse cuando se manipula la lista de ejecucion (runqueue)).
Pero, escuche que preguntas, seguramente esto es usualmente necesario para dormir mientras aun se manrtiene la exclusion mutual en alguna estructura de datos ? y en efecto, es. El metodo que generalmente se usa aqui es el semaforo. Hay mucho esfuerzo de diferentes locking primitives , tal como los tipos atomic_t. Lee el documento sobre el kernel-locking en Documentation/DocBook/ en las fuentes del arbol del kernel para mas informacion sobre esto. Algo menor que notar es que el big kernel lock (BKL), usado por lock_kernel() y unlock_kernel(), no es un spinlock normal. Puedes dormir con el BKL ya adquirido, y este se liberara cuando se realize un schedule.

Glossario

Pre-emptible
Una tarea es pre-emptible si la CPU puede ser scheduled y deja de ejecutarse y pasa a otra tarea del proceso. Esto es diferente a una interrupción, donde la interrupcion usa temporalmente la CPU.
Espacio de kernel
Es el codigo que se ejecuta el cual proviene de una imagen de Kernel o de una imagen de un modulo. Esto codigo tiene permisos completos para hacer lo que el quiera (en x86, el codigo esta en ring 0). Esta puede ser una tarea o una interrupcion invocada. Si es una tarea, no es pre-emptible. El acceso a memoria en espacio de usuario usualmente requiere que los datos sean copidados.
Espacio de Usuario
El codigo ejecutable que proviene de la imagen de un proceso normal. Este se ejecuta en nivel de ring 3 en x86 y tienen derechos limitados. Esta protegido para no afectar a otros procesos o la direccion de espacio del kernel y no tiene acceso directo al hardware (a menos que sea otorgado por el kernel explicitamente).
Contexto de proceso
Codigo ejecutable del kernel por parte de un proceso. El codigo puede schedule (durmiendo, o explicitamente) pero aun no es pre-emptible.

*** Correcciones a moz@compsoc.man.ac.uk.