Compartir a través de


Envío de tareas en modo de usuario

Importante

Parte de la información hace referencia a un producto de versión preliminar que puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no ofrece ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

En este artículo se describe la característica de envío de trabajo en modo de usuario (UM) que todavía está en desarrollo a partir de Windows 11, versión 24H2 (WDDM 3.2). El envío de trabajo en modo de usuario permite a las aplicaciones enviar trabajo a la GPU directamente desde el modo de usuario con una latencia muy baja. El objetivo es mejorar el rendimiento de las aplicaciones que envían cargas de trabajo pequeñas con frecuencia a la GPU. Además, se espera que el envío en modo de usuario beneficie significativamente a estas aplicaciones si se ejecutan dentro de un contenedor o una máquina virtual (VM). Esta ventaja se debe a que el controlador en modo de usuario (UMD) que se ejecuta en la máquina virtual puede enviar directamente el trabajo a la GPU sin tener que enviar un mensaje al host.

Los controladores IHV y el hardware que admiten el envío de trabajo en modo de usuario deben seguir admitiendo el modelo tradicional de envío de trabajo en modo kernel simultáneamente. Esta compatibilidad es necesaria para escenarios como un invitado anterior que solo admite colas de KM tradicionales que se ejecutan en un host más reciente.

En este artículo no se describe la interoperabilidad de envío de mensajería unificada con Flip/FlipEx. El envío UM descrito en este artículo se limita a solo la clase de escenarios de renderización/procesamiento. La canalización de presentación sigue basándose en el envío en modo kernel por ahora, ya que tiene una dependencia de las barreras supervisadas nativas. El diseño y la implementación de una presentación basada en el envío de modo de usuario se pueden considerar una vez que las barreras supervisadas nativas y el envío de modo de usuario solo para computación o renderizado estén completamente implementados. Por lo tanto, los controladores deben admitir el envío en modo usuario de manera individual para cada cola.

Timbres

La mayoría de las generaciones actuales o futuras de GPU que admiten la programación de hardware también admiten el concepto de un doorbell de GPU. Un timbre es un mecanismo para indicar a un motor de GPU que el nuevo trabajo se pone en cola en su cola de trabajo. Normalmente, los timbres de puerta se registran en la PCIe BAR (barra de direcciones base) o en la memoria del sistema. Cada IHV de GPU tiene su propia arquitectura que determina el número de timbres, donde se encuentran en el sistema, etc. El sistema operativo Windows utiliza mecanismos de señalización como parte de su diseño para implementar el envío de trabajos en modo usuario.

A nivel general, hay dos modelos diferentes de timbres implementados por distintos IHV y GPUs:

  • Timbres globales

    En el modelo Global Doorbells, todas las colas de hardware entre contextos y procesos comparten un único timbre global. El valor escrito en el timbre informa al programador de la GPU sobre qué alineación de hardware y motor específicos tienen un nuevo trabajo. El hardware de GPU utiliza una forma de mecanismo de sondeo para recuperar trabajo si varias colas de hardware envían trabajo activamente y hacen sonar el mismo timbre global.

  • Timbres especializados

    En el modelo de timbre dedicado, a cada cola de hardware se le asigna su propio timbre de puerta que se suena cada vez que hay un nuevo trabajo que se va a enviar a la GPU. Cuando se suena un timbre, el programador de GPU sabe exactamente qué cola de hardware envió el nuevo trabajo. Hay timbres limitados que se comparten en todas las colas de hardware creadas en la GPU. Si el número de colas de hardware creadas supera el número de timbres disponibles, el controlador debe desconectar el timbre de una cola de hardware más antigua o menos utilizada recientemente y asignar su timbre a una cola recién creada, virtualizando efectivamente los timbres.

Detección de compatibilidad con el envío de trabajo en modo de usuario

DXGK_NODEMETADATA_FLAGS::UserModeSubmissionSupported

Para los nodos de GPU que admiten la característica de envío de trabajo en modo de usuario, el DxgkDdiGetNodeMetadata de KMD establece la marca de metadatos del nodo UserModeSubmissionSupported, que se agrega a DXGK_NODEMETADATA_FLAGS. A continuación, el sistema operativo permite que UMD cree HWQueues y timbres para la entrega en modo de usuario solo en los nodos para los cuales esta bandera está establecida.

DXGK_QUERYADAPTERINFOTYPE::DXGKQAITYPE_USERMODESUBMISSION_CAPS

Para consultar información específica del timbre, el sistema operativo llama a la función DxgkDdiQueryAdapterInfo de KMD con el tipo de consulta de información del adaptador DXGKQAITYPE_USERMODESUBMISSION_CAPS. KMD responde rellenando la estructura DXGK_USERMODESUBMISSION_CAPS con sus detalles de soporte técnico para el envío de trabajos a modo de usuario.

Actualmente, el único límite necesario es el tamaño de memoria del timbre (en bytes). Dxgkrnl necesita el tamaño de memoria del timbre por un par de razones:

  • Durante la creación del timbre (D3DKMTCreateDoorbell), Dxgkrnl devuelve un DoorbellCpuVirtualAddress a UMD. Antes de hacerlo, Dxgkrnl primero debe mapearse internamente a una página ficticia porque la campanilla aún no está asignada ni conectada. El tamaño del timbre de puerta es necesario para la asignación de la página ficticia.
  • Durante la conexión con el timbre (D3DKMTConnectDoorbell), Dxgkrnl debe convertir DoorbellCpuVirtualAddress a un DoorbellPhysicalAddress proporcionado por KMD. De nuevo, Dxgkrnl debe conocer el tamaño del timbre.

D3DDDI_CREATEHWQUEUEFLAGS::UserModeSubmission en D3DKMTCreateHwQueue

UMD establece la marca UserModeSubmission agregada a D3DDDI_CREATEHWQUEUEFLAGS para crear HWQueues que usan el modelo de envío en modo de usuario. HWQueues creado con esta marca no puede usar la ruta de envío del trabajo en modo kernel normal y debe confiar en el mecanismo de timbre para el envío de trabajo en la cola.

API de envío de trabajo en modo de usuario

Se agregan las siguientes API en modo de usuario para admitir el envío de trabajo en modo de usuario.

  • D3DKMTCreateDoorbell crea un timbre para un D3D HWQueue para el envío de trabajo en modo usuario.

  • D3DKMTConnectDoorbell conecta un timbre previamente creado a una cola de hardware D3D para enviar trabajos en modo usuario.

  • D3DKMTDestroyDoorbell destruye un timbre de puerta creado anteriormente.

  • D3DKMTNotifyWorkSubmission notifica al KMD que se envió un nuevo trabajo en un HWQueue. El objetivo de esta función es un proceso de envío de trabajo de baja latencia, donde KMD no está involucrado ni informado cuando se envía el trabajo. Esta API es útil en escenarios en los que el KMD debe recibir una notificación cada vez que se envía el trabajo en un HWQueue. Los controladores deben usar este mecanismo en escenarios específicos e poco frecuentes, ya que implica un recorrido de ida y vuelta de UMD a KMD en cada envío de trabajo, lo que anula el propósito de un modelo de envío en modo usuario de baja latencia.

Modelo de residencia de asignaciones de memoria de timbre y búfer de anillo

  • UMD es responsable de hacer que el búfer de anillo y las asignaciones de control de búfer de anillo residan antes de crear un timbre.
  • UMD administra el ciclo de vida del búfer circular y las asignaciones de control del búfer circular. Dxgkrnl no destruirá estas asignaciones implícitamente incluso si se destruye el timbre correspondiente. UMD es responsable de asignar y destruir estas asignaciones. Sin embargo, para evitar que un programa malintencionado en modo de usuario destruya estas asignaciones mientras el 'doorbell' está activo, Dxgkrnl mantiene una referencia sobre ellas durante la vigencia del 'doorbell'.
  • El único escenario en el que Dxgkrnl destruye las asignaciones de búfer de anillo es durante la terminación del dispositivo. Dxgkrnl destruye todas las asignaciones de búferes HWQueues, timbres y anillos asociados al dispositivo.
  • Siempre que las asignaciones de búfer de anillo estén activas, la CPUVA del búfer de anillo siempre es válida y está disponible para que UMD acceda, independientemente del estado de las conexiones de timbre. Es decir, la residencia del búfer de anillo no está vinculada al timbre.
  • Cuando KMD realiza la devolución de llamada DXG para desconectar un timbre (es decir, llama a DxgkCbDisconnectDoorbell con estado D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY), Dxgkrnl gira la CPUVA del timbre de puerta a una página ficticia. No expulsa ni desasigna las asignaciones del búfer de anillo.
  • En caso de escenarios de pérdida de dispositivo (TDR/GPU Stop/Page, etc.), Dxgkrnl desconecta el doorbell y marca el estado como D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT. El modo de usuario es responsable de destruir el HWQueue, el timbre, el búfer de anillo y volver a crearlos. Este requisito es similar a cómo se destruyen y vuelven a crear otros recursos de dispositivo en este escenario.

Suspensión de contexto de hardware

Cuando el sistema operativo suspende un contexto de hardware, Dxgkrnl mantiene activa la conexión del timbre y residente la asignación del buffer circular (cola de trabajo). De esta manera, UMD puede seguir poniendo en cola tareas en el contexto; este trabajo simplemente no se programa mientras se suspende el contexto. Una vez reanudado y programado el contexto, el procesador de administración de contextos (CMP) de la GPU observa el nuevo puntero de escritura y el envío de trabajo.

Esta lógica es similar a la lógica de envío del modo kernel actual, donde UMD puede llamar a D3DKMTSubmitCommand con un contexto suspendido. Dxgkrnl pone en cola este nuevo comando en HwQueue, pero no se programa hasta un momento posterior.

Durante la suspensión y reanudación del contexto de hardware, ocurre la siguiente secuencia de eventos.

  • Suspender un contexto de hardware:

    1. Dxgkrnl realiza una llamada a DxgkddiSuspendContext.
    2. KMD elimina todas las HWQueues del contexto de la lista del planificador de HW.
    3. Los timbres siguen conectados y las asignaciones de control de búfer circular/anillo se mantienen residentes. El UMD puede escribir nuevos comandos en el HWQueue de este contexto, pero la GPU no las procesará, lo que es similar al envío de comandos en modo kernel de hoy en un contexto suspendido.
    4. Si KMD decide victimizar el timbre de una HWQueue suspendida, UMD pierde su conexión. UMD puede intentar volver a conectar el timbre y KMD asignará un nuevo timbre a esta cola. La intención es no bloquear el UMD, sino permitir que continúe enviando trabajo que el motor de HW pueda procesar eventualmente una vez que se reanude el contexto.
  • Reanudación de un contexto de hardware:

    1. Dxgkrnl llama DxgkddiResumeContext.
    2. KMD agrega todos los "HWQueues" del contexto a la lista del planificador de HW.

Transiciones de estado F del motor

En el envío de trabajo en modo kernel tradicional, Dxgkrnl se encarga de enviar nuevos comandos a la HWQueue y controlar las interrupciones de finalización de KMD. Por este motivo, Dxgkrnl tiene una vista completa de cuándo un motor está activo y inactivo.

En el envío de trabajo en modo de usuario, Dxgkrnl supervisa si un motor de GPU está haciendo progreso usando la cadencia de tiempo de espera de TDR, por lo que, si vale la pena iniciar una transición al estado F1 antes que el tiempo de espera de TDR de dos segundos, el KMD puede solicitar al sistema operativo realizar dicha transición.

Se realizaron los siguientes cambios para facilitar este enfoque:

  • El tipo de interrupción DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE se ha agregado a DXGK_INTERRUPT_TYPE. KMD usa esta interrupción para notificar a Dxgkrnl las transiciones de estado del motor que requieren una acción de energía de GPU o recuperación de tiempo de espera, como Active -> TransitionToF1 y Active -> Hung.

  • La estructura de datos de interrupción EngineStateChange se agrega a DXGKARGCB_NOTIFY_INTERRUPT_DATA.

  • La enumeración DXGK_ENGINE_STATE se agrega para representar las transiciones de estado del motor para EngineStateChange.

Cuando KMD genera una interrupción DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE con EngineStateChange.NewState establecido en DXGK_ENGINE_STATE_TRANSITION_TO_F1, Dxgkrnl desconecta todas las puertas de HWQueues en este motor gráfico y luego inicia una transición de componente de energía de F0 a F1.

Cuando el UMD intenta enviar un nuevo trabajo al motor de GPU en estado F1, debe volver a conectar el timbre de puerta, lo que a su vez hace que Dxgkrnl inicie una transición al estado de energía F0.

Transiciones de estado D del motor

Durante una transición de estado de alimentación del dispositivo D0 a D3, Dxgkrnl suspende el HWQueue, desconecta el timbre de puerta (girando la CPUVA del timbre a una página ficticia) y actualiza el estado del timbre de puerta DoorbellStatusCpuVirtualAddress para D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.

Si UMD llama a D3DKMTConnectDoorbell cuando la GPU está en D3, obliga a Dxgkrnl a reactivar la GPU a D0. Dxgkrnl también es responsable de reanudar el HWQueue y girar la CPUVA del timbre de puerta a una ubicación física del timbre.

Se produce la siguiente secuencia de eventos.

  • Se produce una transición de apagado de la GPU del estado D0 al estado D3.

    1. Dxgkrnl llama a DxgkddiSuspendContext para todos los contextos de HW en la GPU. KMD quita estos contextos de la lista del planificador de hardware.
    2. Dxgkrnl desconecta todas las notificaciones.
    3. Dxgkrnl posiblemente expulsa las asignaciones de búfer de anillo/búfer de control de anillo de VRAM si es necesario. Lo hace una vez que todos los contextos se suspenden y quitan de la lista del programador de hardware para que el hardware no haga referencia a ninguna memoria expulsada.
  • UMD escribe un nuevo comando en un HWQueue cuando la GPU está en estado D3:

    1. UMD detecta que el timbre está desconectado y, por ello, llama a D3DKMTConnectDoorbell.
    2. Dxgkrnl inicia una transición D0.
    3. Dxgkrnl hace que todas las asignaciones de Control de Búfer de Anillo o Búfer de Anillo se conviertan en residentes si fueron desalojadas.
    4. Dxgkrnl llama a la función DxgkddiCreateDoorbell de KMD para solicitar que KMD realice una conexión de timbre para este HWQueue.
    5. Dxgkrnl llama a DxgkddiResumeContext para todos los HWContext. KMD agrega las colas correspondientes a la lista del programador de hardware.

DDIs para el envío de trabajo en modo usuario

DDIs implementados por KMD

Se agregan los siguientes DDIs en modo kernel para que KMD implemente la compatibilidad con el envío de trabajo en modo de usuario.

  • DxgkDdiCreateDoorbell. Cuando UMD llama a D3DKMTCreateDoorbell para crear una "doorbell" para una HWQueue, Dxgkrnl realiza una llamada correspondiente a esta función para que KMD pueda inicializar sus estructuras de "doorbell".

  • DxgkDdiConnectDoorbell. Cuando UMD llama a D3DKMTConnectDoorbell, Dxgkrnl realiza una llamada correspondiente a esta función para que KMD pueda proporcionar una CPUVA asignada a la ubicación física del timbre y, además, realice las conexiones necesarias entre el objeto HWQueue, el objeto doorbell, la dirección física del timbre, el programador de GPU, etc.

  • DxgkDdiDisconnectDoorbell. Cuando el sistema operativo quiere desconectar un timbre específico, llama a KMD con este DDI.

  • DxgkDdiDestroyDoorbell. Cuando UMD llama a D3DKMTDestroyDoorbell, Dxgkrnl realiza una llamada correspondiente a esta función para que KMD pueda destruir sus estructuras de timbre.

  • DxgkDdiNotifyWorkSubmission. Cuando UMD llama a D3DKMTNotifyWorkSubmission, Dxgkrnl realiza una llamada correspondiente a esta función para que el KMD pueda recibir notificaciones de nuevos envíos de trabajo.

DDI implementado por Dxgkrnl

DxgkCbDisconnectDoorbell callback se implementa mediante Dxgkrnl. KMD puede llamar a esta función para notificar a Dxgkrnl que KMD necesita desconectar un timbre particular.

Cambios en la barrera de progreso de la cola de HW

Las colas de hardware que se ejecutan en el modelo de envío de trabajo de UM siguen teniendo el concepto de un valor de la barrera de progreso que aumenta de forma monotónica, que UMD genera y escribe cuando se completa un búfer de comandos. Para que Dxgkrnl sepa si una cola de hardware determinada tiene trabajo pendiente, el UMD debe actualizar el valor de barrera de progreso en cola justo antes de anexar un nuevo búfer de comandos al búfer de anillo y hacer que sea visible para la GPU. CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress es un mapeo de procesos en modo usuario que permite lectura y escritura del valor más reciente en cola.

Es esencial para el UMD asegurarse de que el valor en cola se actualiza justo antes de que el nuevo envío se haga visible para la GPU. Los pasos siguientes son la secuencia de operaciones recomendada. Asumen que la cola de HW está inactiva y el último búfer terminado tenía un valor de barrera de progreso de N.

  • Genere un nuevo valor de barrera de progreso N+1.
  • Rellene el buffer de comandos. La última instrucción del búfer de comandos es una escritura de valor de barrera de progreso en N+1.
  • Informe al sistema operativo del valor recién añadido a la cola asignando *(HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) al valor igual a N+1.
  • Haga que el búfer de comandos sea visible para la GPU agregándolo al búfer de anillo.
  • Llama al timbre.

Finalización normal y anómala del proceso

La siguiente secuencia de eventos tiene lugar durante la finalización normal del proceso.

Para cada HWQueue del dispositivo o contexto:

  1. Dxgkrnl llama a DxgkDdiDisconnectDoorbell para desconectar el timbre.
  2. Dxgkrnl espera hasta que se completen los últimos valores de HwQueueProgressFenceLastQueuedValueCPUVirtualAddress en la GPU. Las asignaciones de control de búfer de anillo/búfer de anillo permanecen permanentes.
  3. Se satisface la espera de Dxgkrnl, y ahora puede destruir las asignaciones de control de búfer de anillo/búfer de anillo, y el timbre y los objetos HWQueue.

La siguiente secuencia de eventos tiene lugar durante la finalización anómala del proceso.

  1. Dxgkrnl marca el dispositivo en error.

  2. Para cada contexto de dispositivo, Dxgkrnl llama a DxgkddiSuspendContext para suspender el contexto. Las asignaciones de control de búfer de anillo/búfer de anillo siguen siendo residentes. KMD anticipa el contexto y lo quita de su lista de ejecución de hardware.

  3. Para cada HWQueue de contexto, Dxglrnl:

    a) Llama a DxgkDdiDisconnectDoorbell para desconectar el timbre.

    b. Destruye las asignaciones de control de búfer de anillo/búfer de anillo, así como los objetos doorbell y HWQueue.

Ejemplos de pseudocódigo

Pseudocódigo de envío de trabajo en UMD

El pseudocódigo siguiente es un ejemplo básico del modelo que debe usar UMD para crear y enviar trabajo a HWQueues mediante las API de timbre. Considere que hHWqueue1 es el identificador de un HWQueue creado con el indicador UserModeSubmission utilizando la API D3DKMTCreateHwQueue existente.

// Create a doorbell for the HWQueue
D3DKMT_CREATE_DOORBELL CreateDoorbell = {};
CreateDoorbell.hHwQueue = hHwQueue1;
CreateDoorbell.hRingBuffer = hRingBufferAlloc;
CreateDoorbell.hRingBufferControl = hRingBufferControlAlloc;
CreateDoorbell.Flags.Value = 0;

NTSTATUS ApiStatus =  D3DKMTCreateDoorbell(&CreateDoorbell);
if(!NT_SUCCESS(ApiStatus))
  goto cleanup;

assert(CreateDoorbell.DoorbellCPUVirtualAddress!=NULL && 
      CreateDoorbell.DoorbellStatusCPUVirtualAddress!=NULL);

// Get a CPUVA of Ring buffer control alloc to obtain write pointer.
// Assume the write pointer is at offset 0 in this alloc
D3DKMT_LOCK2 Lock = {};
Lock.hAllocation = hRingBufferControlAlloc;
ApiStatus = D3DKMTLock2(&Lock);
if(!NT_SUCCESS(ApiStatus))
  goto cleanup;

UINT64* WritePointerCPUVirtualAddress = (UINT64*)Lock.pData;

// Doorbell created successfully. Submit command to this HWQueue

UINT64 DoorbellStatus = 0;
do
{
  // first connect the doorbell and read status
  ApiStatus = D3DKMTConnectDoorbell(hHwQueue1);
  D3DDDI_DOORBELL_STATUS DoorbellStatus = *(UINT64*(CreateDoorbell.DoorbellStatusCPUVirtualAddress));

  if(!NT_SUCCESS(ApiStatus) ||  DoorbellStatus == D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT)
  {
    // fatal error in connecting doorbell, destroy this HWQueue and re-create using traditional kernel mode submission.
    goto cleanup_fallback;
  }

  // update the last queue progress fence value
  *(CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) = new_command_buffer_progress_fence_value;

  // write command to ring buffer of this HWQueue
  *(WritePointerCPUVirtualAddress) = address_location_of_command_buffer;

  // Ring doorbell by writing the write pointer value into doorbell address. 
  *(CreateDoorbell.DoorbellCPUVirtualAddress) = *WritePointerCPUVirtualAddress;

  // Check if submission succeeded by reading doorbell status
  DoorbellStatus = *(UINT64*(CreateDoorbell.DoorbellStatusCPUVirtualAddress));
  if(DoorbellStatus == D3DDDI_DOORBELL_STATUS_CONNECTED_NOTIFY)
  {
      D3DKMTNotifyWorkSubmission(CreateDoorbell.hDoorbell);
  }

} while (DoorbellStatus == D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY);

Victimización del pseudocódigo de timbre en KMD

En el siguiente ejemplo se ilustra cómo KMD podría necesitar "virtualizar" y compartir los timbrales disponibles entre los HWQueues en las GPUs que utilizan timbrales dedicados.

Pseudocódigo de la función de VictimizeDoorbell() KMD:

  • KMD decide que el timbre lógico hDoorbell1 conectado a PhysicalDoorbell1 debe ser desactivado e inhabilitado.
  • KMD llama a Dxgkrnl.DxgkCbDisconnectDoorbellCB(hDoorbell1->hHwQueue)
    • Dxgkrnl gira la CPUVA visible para UMD de este timbre a una página ficticia y actualiza el valor de estado a D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
  • KMD recupera el control y realiza la desconexión efectiva o victimización real.
    • KMD victimiza a hDoorbell1 y lo desconecta de PhysicalDoorbell1.
    • PhysicalDoorbell1 está disponible para su uso

Ahora, tenga en cuenta el siguiente escenario:

  1. Hay un solo timbre físico en PCI BAR con una CPUVA en modo kernel igual a 0xfeedfeee. A un objeto de timbre creado para un HWQueue se le asigna este valor de timbre físico.

    HWQueue KMD Handle: hHwQueue1
    Doorbell KMD Handle: hDoorbell1
    Doorbell CPU Virtual Address: CpuVirtualAddressDoorbell1 =>  0xfeedfeee // hDoorbell1 is mapped to 0xfeedfeee
    Doorbell Status CPU Virtual Address: StatusCpuVirtualAddressDoorbell1 => D3DDDI_DOORBELL_STATUS_CONNECTED
    
  2. El sistema operativo llama DxgkDdiCreateDoorbell a un objeto diferente HWQueue2:

    HWQueue KMD Handle: hHwQueue2
    Doorbell KMD Handle: hDoorbell2
    Doorbell CPU Virtual Address: CpuVirtualAddressDoorbell2 => 0 // this doorbell object isn't yet assigned to a physical doorbell  
    Doorbell Status CPU Virtual Address: StatusCpuVirtualAddressDoorbell2 => D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY
    
    // In the create doorbell DDI, KMD doesn't need to assign a physical doorbell yet, 
    // so the 0xfeedfeee doorbell is still connected to hDoorbell1
    
  3. El sistema operativo llama a DxgkDdiConnectDoorbell en hDoorbell2:

    // KMD needs to victimize hDoorbell1 and assign 0xfeedfeee to hDoorbell2. 
    VictimizeDoorbell(hDoorbell1);
    
    // Physical doorbell 0xfeedfeee is now free and can be used vfor hDoorbell2.
    // KMD makes required connections for hDoorbell2 with HW
    ConnectPhysicalDoorbell(hDoorbell2, 0xfeedfeee)
    
    return 0xfeedfeee
    
    // On return from this DDI, *Dxgkrnl* maps 0xfeedfeee to process address space CPUVA i.e:
    // CpuVirtualAddressDoorbell2 => 0xfeedfeee
    
    // *Dxgkrnl* updates hDoorbell2 status to connected i.e:
    // StatusCpuVirtualAddressDoorbell2 => D3DDDI_DOORBELL_STATUS_CONNECTED
    ``
    
    

Este mecanismo no es necesario si una GPU usa timbres globales. En su lugar, en este ejemplo, ambos hDoorbell1 y hDoorbell2 se asignarían al mismo 0xfeedfeee timbre físico.