Programación en modo de usuario
Advertencia
A partir de Windows 11, no se admite la programación en modo de usuario. Todas las llamadas producen el error ERROR_NOT_SUPPORTED
.
La programación en modo de usuario (UMS) es un mecanismo ligero que las aplicaciones pueden usar para programar sus propios subprocesos. Una aplicación puede cambiar entre subprocesos umS en modo de usuario sin implicar al programador del sistema y recuperar el control del procesador si un subproceso umS se bloquea en el kernel. Los subprocesos de UMS difieren de las fibras en que cada subproceso de UMS tiene su propio contexto de subproceso en lugar de compartir el contexto de subproceso de un único subproceso. La capacidad de cambiar entre subprocesos en modo de usuario hace que UMS sea más eficaz que los grupos de subprocesos para administrar grandes cantidades de elementos de trabajo de corta duración que requieren pocas llamadas del sistema.
UMS se recomienda para las aplicaciones con requisitos de alto rendimiento que necesitan ejecutar eficazmente muchos subprocesos simultáneamente en sistemas multiprocesador o de varios núcleos. Para aprovechar las ventajas de UMS, una aplicación debe implementar un componente de programador que administre los subprocesos de UMS de la aplicación y determine cuándo deben ejecutarse. Los desarrolladores deben tener en cuenta si sus requisitos de rendimiento de la aplicación justifican el trabajo implicado en el desarrollo de este tipo de componente. Las aplicaciones con requisitos de rendimiento moderados pueden servirse mejor al permitir que el programador del sistema programe sus subprocesos.
UMS está disponible para aplicaciones de 64 bits que se ejecutan en versiones AMD64 e Itanium de Windows 7 y Windows Server 2008 R2 a través de Windows 10 versión 21H2 y Windows Server 2022. Esta característica no está disponible en las versiones arm64 de 32 bits de Windows o en Windows 11.
Para obtener más información, consulte las secciones siguientes:
- Programador de UMS
- Subproceso del programador de UMS
- Subprocesos de trabajo de UMS, contextos de subprocesos y listas de finalización
- Función de punto de entrada del programador de UMS
- Ejecución de subprocesos de UMS
- Procedimientos recomendados de UMS
Programador de UMS
El programador de UMS de una aplicación es responsable de crear, administrar y eliminar subprocesos de UMS y determinar qué subproceso de UMS se va a ejecutar. El programador de una aplicación realiza las tareas siguientes:
- Crea un subproceso del programador de UMS para cada procesador en el que la aplicación ejecutará subprocesos de trabajo de UMS.
- Crea subprocesos de trabajo de UMS para realizar el trabajo de la aplicación.
- Mantiene su propia cola de subprocesos listos para ejecutarse y selecciona los subprocesos que se van a ejecutar en función de las directivas de programación de la aplicación.
- Crea y supervisa una o varias listas de finalización en las que los subprocesos del sistema ponen en cola los subprocesos después de que terminen de procesarse en el kernel. Estos incluyen subprocesos de trabajo recién creados y subprocesos bloqueados previamente en una llamada del sistema que se desbloquean.
- Proporciona una función de punto de entrada del programador para controlar las notificaciones del sistema. El sistema llama a la función de punto de entrada cuando se crea un subproceso de programador, un subproceso de trabajo se bloquea en una llamada del sistema o un subproceso de trabajo produce explícitamente un control.
- Realiza tareas de limpieza para subprocesos de trabajo que han terminado de ejecutarse.
- Realiza un apagado ordenado del programador cuando lo solicite la aplicación.
Subproceso del programador de UMS
Un subproceso del programador de UMS es un subproceso normal que se ha convertido en UMS mediante una llamada a la función EnterUmsSchedulingMode . El programador del sistema determina cuándo se ejecuta el subproceso del programador de UMS en función de su prioridad relativa a otros subprocesos listos. El procesador en el que se ejecuta el subproceso del programador se ve influenciado por la afinidad del subproceso, igual que para los subprocesos que no son ums.
El autor de la llamada de EnterUmsSchedulingMode especifica una lista de finalización y una función de punto de entrada UmsSchedulerProc que se va a asociar al subproceso del programador de UMS. El sistema llama a la función de punto de entrada especificada cuando termina de convertir el subproceso que realiza la llamada a UMS. La función de punto de entrada del programador es responsable de determinar la siguiente acción adecuada para el subproceso especificado. Para obtener más información, vea Función de punto de entrada del programador de UMS más adelante en este tema.
Una aplicación puede crear un subproceso del programador de UMS para cada procesador que se usará para ejecutar subprocesos de UMS. La aplicación también puede establecer la afinidad de cada subproceso del programador de UMS para un procesador lógico específico, que tiende a excluir los subprocesos no relacionados de la ejecución en ese procesador, reservándolo eficazmente para ese subproceso del programador. Tenga en cuenta que establecer la afinidad de subprocesos de esta manera puede afectar al rendimiento general del sistema al morir de hambre otros procesos que se pueden ejecutar en el sistema. Para obtener más información sobre la afinidad de subproceso, vea Varios procesadores.
Subprocesos de trabajo de UMS, contextos de subprocesos y listas de finalización
Un subproceso de trabajo de UMS se crea llamando a CreateRemoteThreadEx con el atributo PROC_THREAD_ATTRIBUTE_UMS_THREAD y especificando un contexto de subproceso de UMS y una lista de finalización.
Un contexto de subproceso de UMS representa el estado del subproceso ums de un subproceso de trabajo y se usa para identificar el subproceso de trabajo en las llamadas a funciones de UMS. Se crea mediante una llamada a CreateUmsThreadContext.
Se crea una lista de finalización mediante una llamada a la función CreateUmsCompletionList . Una lista de finalización recibe subprocesos de trabajo de UMS que han completado la ejecución en el kernel y están listos para ejecutarse en modo de usuario. Solo el sistema puede poner en cola los subprocesos de trabajo en una lista de finalización. Los nuevos subprocesos de trabajo de UMS se ponen automáticamente en cola en la lista de finalización especificada cuando se crearon los subprocesos. Los subprocesos de trabajo bloqueados anteriormente también se ponen en cola en la lista de finalización cuando ya no están bloqueados.
Cada subproceso del programador de UMS está asociado a una sola lista de finalización. Sin embargo, la misma lista de finalización se puede asociar a cualquier número de subprocesos del programador de UMS y un subproceso de programador puede recuperar contextos umS de cualquier lista de finalización para la que tenga un puntero.
Cada lista de finalización tiene un evento asociado que indica el sistema cuando pone en cola uno o varios subprocesos de trabajo en una lista vacía. La función GetUmsCompletionListEvent recupera un identificador del evento para una lista de finalización especificada. Una aplicación puede esperar más de un evento de lista de finalización junto con otros eventos que tienen sentido para la aplicación.
Función de punto de entrada del programador de UMS
La función de punto de entrada del programador de una aplicación se implementa como una función UmsSchedulerProc . El sistema llama a la función de punto de entrada del programador de la aplicación en las horas siguientes:
- Cuando un subproceso que no es UMS se convierte en un subproceso del programador de UMS llamando a EnterUmsSchedulingMode.
- Cuando un subproceso de trabajo de UMS llama a UmsThreadYield.
- Cuando un subproceso de trabajo de UMS se bloquea en un servicio del sistema, como una llamada del sistema o un error de página.
El parámetro Reason de la función UmsSchedulerProc especifica el motivo por el que se llamó a la función de punto de entrada. Si se llamó a la función de punto de entrada porque se creó un nuevo subproceso del programador de UMS, el parámetro SchedulerParam contiene los datos especificados por el llamador de EnterUmsSchedulingMode. Si se llamó a la función de punto de entrada porque se produjo un subproceso de trabajo de UMS, el parámetro SchedulerParam contiene los datos especificados por el autor de la llamada de UmsThreadYield. Si se llamó a la función de punto de entrada porque un subproceso de trabajo de UMS bloqueado en el kernel, el parámetro SchedulerParam es NULL.
La función de punto de entrada del programador es responsable de determinar la siguiente acción adecuada para el subproceso especificado. Por ejemplo, si se bloquea un subproceso de trabajo, la función de punto de entrada del programador podría ejecutar el siguiente subproceso de trabajo de UMS listo disponible.
Cuando se llama a la función de punto de entrada del programador del programador, el programador de la aplicación debe intentar recuperar todos los elementos de su lista de finalización asociada llamando a la función DequeueUmsCompletionListItems . Esta función recupera una lista de contextos de subprocesos de UMS que han terminado de procesarse en el kernel y están listos para ejecutarse en modo de usuario. El programador de la aplicación no debe ejecutar subprocesos umS directamente desde esta lista, ya que esto puede provocar un comportamiento impredecible en la aplicación. En su lugar, el programador debe recuperar todos los contextos de subprocesos de UMS mediante una llamada a la función GetNextUmsListItem una vez para cada contexto, inserte los contextos de subprocesos umS en la cola de subprocesos listos del programador y, a continuación, ejecute subprocesos UMS desde la cola de subprocesos lista.
Si el programador no necesita esperar en varios eventos, debe llamar a DequeueUmsCompletionListItems con un parámetro de tiempo de espera distinto de cero para que la función espere al evento de lista de finalización antes de devolver. Si el programador necesita esperar en varios eventos de lista de finalización, debe llamar a DequeueUmsCompletionListItems con un parámetro de tiempo de espera de cero para que la función devuelva inmediatamente, incluso si la lista de finalización está vacía. En este caso, el programador puede esperar explícitamente en los eventos de lista de finalización, por ejemplo, mediante WaitForMultipleObjects.
Ejecución de subprocesos de UMS
Un subproceso de trabajo umS recién creado se pone en cola en la lista de finalización especificada y no comienza a ejecutarse hasta que el programador de UMS de la aplicación lo selecciona para ejecutarse. Esto difiere de los subprocesos que no son UMS, que el programador del sistema programa automáticamente para ejecutarse a menos que el autor de la llamada cree explícitamente el subproceso suspendido.
El programador ejecuta un subproceso de trabajo mediante una llamada a ExecuteUmsThread con el contexto umS del subproceso de trabajo. Un subproceso de trabajo de UMS se ejecuta hasta que se produce llamando a la función UmsThreadYield , bloquea o finaliza.
Procedimientos recomendados de UMS
Las aplicaciones que implementan UMS deben seguir estos procedimientos recomendados:
- El sistema administra las estructuras subyacentes para los contextos de subprocesos de UMS y no se debe modificar directamente. En su lugar, use QueryUmsThreadInformation y SetUmsThreadInformation para recuperar y establecer información sobre un subproceso de trabajo de UMS.
- Para evitar interbloqueos, el subproceso del programador de UMS no debe compartir bloqueos con subprocesos de trabajo de UMS. Esto incluye bloqueos creados por la aplicación y bloqueos del sistema adquiridos indirectamente por operaciones como la asignación del montón o la carga de archivos DLL. Por ejemplo, supongamos que el programador ejecuta un subproceso de trabajo de UMS que carga un archivo DLL. El subproceso de trabajo adquiere el bloqueo y los bloques del cargador. El sistema llama a la función de punto de entrada del programador, que luego carga un archivo DLL. Esto provoca un interbloqueo, ya que el bloqueo del cargador ya se mantiene y no se puede liberar hasta que se desbloquea el primer subproceso. Para evitar este problema, delega el trabajo que podría compartir bloqueos con subprocesos de trabajo de UMS a un subproceso de trabajo de UMS dedicado o a un subproceso que no sea UMS.
- UMS es más eficaz cuando la mayoría del procesamiento se realiza en modo de usuario. Siempre que sea posible, evite realizar llamadas del sistema en subprocesos de trabajo de UMS.
- Los subprocesos de trabajo de UMS no deben suponer que se está usando el programador del sistema. Esta suposición puede tener efectos sutiles; por ejemplo, si un subproceso en el código desconocido establece una prioridad o afinidad de subproceso, el programador de UMS todavía podría invalidarlo. Es posible que el código que supone que se está usando el programador del sistema no se comporte según lo esperado y que se interrumpa cuando lo llama un subproceso de UMS.
- Es posible que el sistema necesite bloquear el contexto del subproceso de un subproceso de trabajo de UMS. Por ejemplo, una llamada de procedimiento asincrónico en modo kernel (APC) podría cambiar el contexto del subproceso de UMS, por lo que el contexto del subproceso debe bloquearse. Si el programador intenta ejecutar el contexto del subproceso de UMS mientras está bloqueado, se producirá un error en la llamada. Este comportamiento es por diseño y el programador debe diseñarse para reintentar el acceso al contexto del subproceso de UMS.