Introducción a los objetos de exclusión mutua
Como su nombre sugiere, un objeto de exclusión mutua es un mecanismo de sincronización diseñado para garantizar el acceso mutuamente exclusivo a un único recurso que se comparte entre un conjunto de subprocesos en modo kernel. Solo los controladores de nivel superior, como los controladores de sistema de archivos (FSD) que usan subprocesos de trabajo ejecutivos, probablemente usen un objeto de exclusión mutua.
Posiblemente, un controlador de nivel superior con subprocesos creados por controladores o rutinas de devolución de llamada de subproceso de trabajo podría usar un objeto de exclusión mutua. Sin embargo, cualquier controlador con subprocesos paginables o rutinas de devolución de llamada de subprocesos de trabajo debe administrar las adquisiciones de, esperas y versiones de sus objetos de exclusión mutua con mucho cuidado.
Los objetos de exclusión mutua tienen características integradas que proporcionan subprocesos del sistema (solo en modo kernel) mutuamente excluyentes y sin interbloqueos a recursos compartidos en máquinas SMP. El kernel asigna la propiedad de una exclusión mutua a un único subproceso a la vez.
La adquisición de la propiedad de una exclusión mutua impide la entrega de llamadas a procedimientos asincrónicos (APCs) del modo kernel normal. El subproceso no será reemplazado por un APC a menos que el kernel emite una interrupción de software de APC_LEVEL para ejecutar un APC de kernel especial, como la rutina de finalización de IRP del administrador de E/S que devuelve resultados al solicitante original de una operación de E/S.
Un subproceso puede adquirir la propiedad de un objeto de exclusión mutua que ya posee (propiedad recursiva), pero un objeto de exclusión mutua adquirido recursivamente no se establece en el estado Signaled hasta que el subproceso libera completamente su propiedad. Este subproceso debe liberar explícitamente la exclusión mutua tantas veces como adquirió la propiedad antes de que otro subproceso pueda adquirir la exclusión mutua.
El kernel nunca permite que un subproceso propietario de una exclusión mutua provoque una transición al modo de usuario sin liberar primero la exclusión mutua y establecerla en el estado Señalizado. Si algún subproceso creado por FSD o creado por el controlador que posee una exclusión mutua intenta devolver el control al administrador de E/S antes de liberar la propiedad de la exclusión mutua, el kernel quita el sistema.
Cualquier controlador que use un objeto de exclusión mutua debe llamar a KeInitializeMutex una vez antes de que espere o libere su objeto de exclusión mutua. En la ilustración siguiente se muestra cómo dos subprocesos del sistema pueden usar un objeto de exclusión mutua.
Como se muestra en la ilustración anterior, un controlador que usa un objeto de exclusión mutua debe proporcionar el almacenamiento para el objeto de exclusión mutua, que debe ser residente. El controlador puede usar la extensión de dispositivo de un objeto de dispositivo creado por el controlador, la extensión del controlador si usa un objeto de controlador o un grupo no paginado asignado por el controlador.
Cuando un controlador llama a KeInitializeMutex (normalmente desde su rutina AddDevice ), debe pasar un puntero al almacenamiento del controlador para el objeto de exclusión mutua, que el kernel inicializa en el estado Signaled.
Después de inicializar este controlador de nivel superior, puede administrar el acceso mutuamente exclusivo a un recurso compartido, como se muestra en la ilustración anterior. Por ejemplo, las rutinas de envío de un controlador para operaciones y subprocesos intrínsecamente sincrónicos pueden usar una exclusión mutua para proteger una cola creada por el controlador para IRP.
Dado que KeInitializeMutexsiempre establece el estado inicial de un objeto de exclusión mutua en Signaled (como se muestra en la ilustración anterior):
La llamada inicial de una rutina de distribución a KeWaitForSingleObject con el puntero De exclusión mutua coloca el subproceso actual inmediatamente en el estado listo, proporciona la propiedad del subproceso de la exclusión mutua y restablece el estado de exclusión mutua a Not-Signaled. En cuanto la rutina de distribución se reanuda la ejecución, puede insertar de forma segura un IRP en la cola protegida con exclusión mutua.
Cuando un segundo subproceso (otra rutina de distribución, rutina de devolución de llamada de subproceso de trabajo o subproceso creado por el controlador) llama a KeWaitForSingleObject con el puntero de exclusión mutua, el segundo subproceso se coloca en el estado de espera.
Cuando la rutina de distribución termina de poner en cola el IRP como se describe en el paso 1, llama a KeReleaseMutex con el puntero de exclusión mutua y un valor de espera booleano, que indica si pretende llamar a KeWaitForSingleObject (o KeWaitForMutexObject) con la exclusión mutua tan pronto como KeReleaseMutex devuelve el control.
Suponiendo que la rutina de distribución liberó su propiedad de la exclusión mutua en el paso 3 (Esperar establecido en FALSE), la exclusión mutua se establece en el estado Signaled por KeReleaseMutex. La exclusión mutua no tiene actualmente ningún propietario, por lo que el kernel determina si otro subproceso está esperando esa exclusión mutua. Si es así, el kernel convierte el segundo subproceso (consulte el paso 2) en el propietario de la exclusión mutua, lo que posiblemente aumenta la prioridad del subproceso al valor de prioridad en tiempo real más bajo y cambia su estado a listo.
El kernel envía el segundo subproceso para su ejecución en cuanto un procesador está disponible: es decir, cuando ningún otro subproceso con una prioridad más alta está actualmente en estado listo y no hay rutinas en modo kernel que se ejecuten en una IRQL superior. El segundo subproceso (una rutina de distribución que pone en cola un IRP o la rutina de devolución de llamada del subproceso de trabajo del controlador o el subproceso creado por el controlador que pone en cola un IRP) ahora puede acceder de forma segura a la cola protegida con exclusión mutua de IRP hasta que llame a KeReleaseMutex.
Si un subproceso adquiere la propiedad de un objeto de exclusión mutua de forma recursiva, ese subproceso debe llamar explícitamente a KeReleaseMutex tantas veces como esperaba en la exclusión mutua para establecer el objeto de exclusión mutua en el estado Signaled. Por ejemplo, si un subproceso llama a KeWaitForSingleObject y, a continuación, KeWaitForMutexObject con el mismo puntero de exclusión mutua, debe llamar a KeReleaseMutex dos veces cuando adquiere la exclusión mutua para establecer ese objeto de exclusión mutua en el estado Signaled.
Al llamar a KeReleaseMutex con el parámetro Wait establecido en TRUE , se indica la intención del autor de la llamada de llamar inmediatamente a una rutina de soporte técnico keWaitXxx a partir de KeReleaseMutex.
Tenga en cuenta las siguientes directrices para establecer el parámetro Wait en KeReleaseMutex:
Una rutina de controlador paginable o subproceso paginable que se ejecuta en IRQL PASSIVE_LEVEL nunca debe llamar a KeReleaseMutex con el parámetro Wait establecido en TRUE. Esta llamada provoca un error de página irrecuperable si el autor de la llamada se pagina entre las llamadas a KeReleaseMutex y KeWaitXxxObject(s).
Cualquier rutina de controlador estándar que se ejecute en un IRQL mayor que PASSIVE_LEVEL no puede esperar un intervalo distinto de cero en cualquier objeto distribuidor sin bajar el sistema. Sin embargo, esta rutina puede llamar a KeReleaseMutex si posee la exclusión mutua mientras se ejecuta en un IRQL menor o igual que DISPATCH_LEVEL.
Para obtener un resumen de las IRQL en las que se ejecutan las rutinas de controlador estándar, consulte Administración de prioridades de hardware.