Anotaciones IRQL para controladores

Todos los desarrolladores de controladores deben tener en cuenta los niveles de solicitud de interrupción (IRQLs). Un IRQL es un entero entre 0 y 31; PASSIVE_LEVEL, DISPATCH_LEVEL y APC_LEVEL normalmente se conocen simbólicamente y los demás por sus valores numéricos. La elevación y reducción del IRQL debe seguir una disciplina estricta de pila. Una función debe tener como objetivo devolver en el mismo IRQL en el que se llamó. Los valores irQL deben no disminuir en la pila. Y una función no puede bajar el IRQL sin generarlo primero. Las anotaciones IRQL están diseñadas para ayudar a aplicar esas reglas.

Cuando el código del controlador tiene anotaciones IRQL, las herramientas de análisis de código pueden hacer una mejor inferencia sobre el intervalo de niveles en los que se debe ejecutar una función y pueden encontrar errores con mayor precisión. Por ejemplo, puede agregar anotaciones que especifiquen el IRQL máximo en el que se puede llamar a una función; Si se llama a una función en un IRQL superior, las herramientas de análisis de código pueden identificar las incoherencias.

Las funciones de controlador deben anotarse con tanta información sobre el IRQL que pueda ser adecuado. Si la información adicional está disponible, ayuda a las herramientas de análisis de código en la comprobación posterior de la función de llamada y la función llamada. En algunos casos, agregar una anotación es una buena manera de suprimir un falso positivo. Algunas funciones, como una función de utilidad, se pueden llamar en cualquier IRQL. En este caso, no tener ninguna anotación IRQL es la anotación adecuada.

Al anotar una función para IRQL, es especialmente importante tener en cuenta cómo puede evolucionar la función, no solo su implementación actual. Por ejemplo, una función como implementada podría funcionar correctamente en un IRQL mayor que el diseñador previsto. Aunque resulta tentador anotar la función en función de lo que realmente hace el código, es posible que el diseñador tenga en cuenta los requisitos futuros, como la necesidad de reducir el irQL máximo para alguna mejora futura o requisito del sistema pendiente. La anotación debe derivarse de la intención del diseñador de funciones, no de la implementación real.

Puede usar las anotaciones de la tabla siguiente para indicar el IRQL correcto para una función y sus parámetros. Los valores IRQL se definen en Wdm.h.

Anotación IRQL Descripción
_IRQL_requires_max_(irql) Irql es el irQL máximo en el que se puede llamar a la función.
_IRQL_requires_min_(irql) Irql es el IRQL mínimo en el que se puede llamar a la función.
_IRQL_requires_(irql) La función debe escribirse en el IRQL especificado por irql.
_IRQL_raises_(irql) La función sale en el irql especificado, pero solo se puede llamar para generar (no inferior) el IRQL actual.
_IRQL_saves_ El parámetro anotado guarda el IRQL actual para restaurarlo más adelante.
_IRQL_restores_ El parámetro anotado contiene un valor IRQL de IRQL_saves que se va a restaurar cuando la función devuelve.
_IRQL_saves_global_(kind, param) El IRQL actual se guarda en una ubicación interna para las herramientas de análisis de código desde las que se va a restaurar IRQL. Esta anotación se usa para anotar una función. La ubicación se identifica por tipo y más refinado por param. Por ejemplo, OldIrql podría ser el tipo y FastMutex podría ser el parámetro que tenía ese valor IRQL antiguo.
_IRQL_restores_global_(kind, param) El IRQL guardado por la función anotada con IRQL_saves_global se restaura desde una ubicación interna a las herramientas de análisis de código.
_IRQL_always_function_min_(value) El valor IRQL es el valor mínimo al que la función puede reducir el IRQL.
_IRQL_always_function_max_(value) El valor IRQL es el valor máximo al que la función puede generar irQL.
_IRQL_requires_same_ La función anotada debe entrar y salir en el mismo IRQL. La función puede cambiar IRQL, pero debe restaurar irQL a su valor original antes de salir.
_IRQL_uses_cancel_ El parámetro anotado es el valor IRQL que debe restaurar una función de devolución de llamada DRIVER_CANCEL. En la mayoría de los casos, use la anotación IRQL_is_cancel en su lugar.

Anotaciones para DRIVER_CANCEL

Hay una diferencia entre las anotaciones _IRQL_uses_cancel_ y _IRQL_is_cancel_. La anotación _IRQL_uses_cancel_ simplemente especifica que el parámetro anotado es el valor IRQL que debe restaurar una función de devolución de llamada DRIVER_CANCEL. La anotación _IRQL_is_cancel_ es una anotación compuesta que consta de _IRQL_uses_cancel_ más otras anotaciones que garantizan un comportamiento correcto de una función de utilidad de devolución de llamada DRIVER_CANCEL. Por sí mismo, la anotación _IRQL_uses_cancel_ solo es útil ocasionalmente; por ejemplo, si el resto de las obligaciones descritas por _IRQL_is_cancel_ ya se han cumplido de alguna otra manera.

Anotación IRQL Descripción
_IRQL_is_cancel_ El parámetro anotado es el IRQL pasado como parte de la llamada a una función de devolución de llamada de DRIVER_CANCEL. Esta anotación indica que la función es una utilidad a la que se llama desde rutinas Cancel y que completa los requisitos para DRIVER_CANCEL funciones, incluida la liberación del bloqueo de número de cancelación.

Cómo interactúan las anotaciones irQL

Las anotaciones de parámetros IRQL interactúan entre sí más que otras anotaciones porque el valor irQL se establece, restablece, guarda y restaura las distintas funciones llamadas.

Especificación de IRQL máximo y mínimo

Las anotaciones _IRQL_requires_max_ y _IRQL_requires_min_ especifican que no se debe llamar a la función desde un IRQL que sea mayor o menor que el valor especificado. Por ejemplo, cuando PREfast ve una secuencia de llamadas de función que no cambian el IRQL, si encuentra uno con un valor de _IRQL_requires_max_ que está por debajo de un _IRQL_requires_min_ cercano, notifica una advertencia en la segunda llamada que encuentra. El error puede producirse realmente en la primera llamada; el mensaje indica dónde se produjo la otra mitad del conflicto.

Si las anotaciones de una función mencionan irQL y no aplican explícitamente _IRQL_requires_max_, la herramienta De análisis de código aplica implícitamente la anotación _IRQL_requires_max_(DISPATCH_LEVEL), que suele ser correcta con excepciones poco frecuentes. La aplicación implícita de esto como el valor predeterminado elimina una gran cantidad de desorden de anotaciones y hace que las excepciones sean mucho más visibles.

La anotación _IRQL_requires_min_(PASSIVE_LEVEL) siempre está implícita porque el IRQL no puede ir menos; por lo tanto, no hay ninguna regla explícita correspondiente sobre irQL mínimo. Muy pocas funciones tienen un límite superior distinto de DISPATCH_LEVEL y un límite inferior distinto de PASSIVE_LEVEL.

Se llama a algunas funciones en un contexto en el que la función llamada no puede generar de forma segura el IRQL por encima de un máximo o, con más frecuencia, no puede reducirlo de forma segura por debajo de algún mínimo. Las anotaciones _IRQL_always_function_max_ y _IRQL_always_function_min_ ayudan a PREfast a encontrar casos en los que esto ocurre involuntariamente.

Por ejemplo, las funciones de tipo DRIVER_STARTIO se anotan con _IRQL_always_function_min_(DISPATCH_LEVEL). Esto significa que durante la ejecución de una función de DRIVER_STARTIO, es un error reducir el IRQL por debajo de DISPATCH_LEVEL. Otras anotaciones indican que la función debe escribirse y salir en DISPATCH_LEVEL.

Especificar un IRQL explícito

Use la anotación _IRQL_raises_ o _IRQL_requires_ para ayudar a PREfast a notificar mejor una incoherencia detectada con _IRQL_requires_max_ o anotaciones de _IRQL_requires_min_ porque luego conoce el IRQL.

La anotación _IRQL_raises_ indica que una función devuelve con el IRQL establecido en un nuevo valor. Al usar la anotación _IRQL_raises_, también establece eficazmente la anotación _drv_maxFunctionIRQL en el mismo valor IRQL. Sin embargo, si la función genera irQL más alto que el valor final y, a continuación, lo reduce al valor final, debe agregar una anotación de _IRQL_always_function_max_ explícita después de la anotación de _IRQL_raises_ para permitir el valor irQL superior.

Elevación o reducción de IRQL

La anotación _IRQL_raises_ indica que la función solo se debe usar para generar IRQL y no debe usarse para reducir IRQL, incluso si la sintaxis de la función lo permitiría. KeRaiseIrql es un ejemplo de una función que no se debe usar para reducir IRQL.

Guardar y restaurar IRQL

Use las anotaciones _IRQL_saves_ y _IRQL_restores_ para indicar que el IRQL actual (si se conoce exactamente o solo aproximadamente) se guarda o restaura desde el parámetro anotado.

Algunas funciones guardan y restauran IRQL implícitamente. Por ejemplo, la función del sistema ExAcquireFastMutex guarda el IRQL en una ubicación opaca asociada al objeto de exclusión mutua rápida que identifica el primer parámetro; el IRQL guardado se restaura mediante la función ExReleaseFastMutex correspondiente para ese objeto de exclusión mutua rápida. Para indicar estas acciones explícitamente, use las anotaciones _IRQL_saves_global_ y _IRQL_restores_global_. Los parámetros kind y param indican dónde se guarda el valor IRQL. La ubicación donde se guarda el valor no tiene que especificarse con precisión, siempre y cuando las anotaciones que guarden y restaure el valor sean coherentes.

Mantener el mismo IRQL

Debe anotar las funciones que cree el controlador que cambien el IRQL mediante la anotación _IRQL_requires_same_ o una de las demás anotaciones IRQL para indicar que se espera el cambio en IRQL. En ausencia de anotaciones que indican cualquier cambio en IRQL, las herramientas de análisis de código emitirán una advertencia para cualquier función que no salga en el mismo IRQL en el que se especificó la función. Si el cambio en IRQL está previsto, agregue la anotación adecuada para suprimir el error. Si el cambio en IRQL no está previsto, se debe corregir el código.

Guardar y restaurar IRQL para rutinas de cancelación de E/S

Use la anotación _IRQL_uses_cancel_ para indicar que el parámetro anotado es el valor IRQL que debe restaurar una función de devolución de llamada DRIVER_CANCEL. Esta anotación indica que la función es una utilidad a la que se llama desde rutinas de cancelación y que completa los requisitos realizados en DRIVER_CANCEL funciones (es decir, descarga la obligación del autor de la llamada).

Por ejemplo, lo siguiente es la declaración del tipo de función de devolución de llamada DRIVER_CANCEL. Uno de los parámetros es el IRQL que debe restaurar esta función. Las anotaciones indican todos los requisitos de una función cancel.

// Define driver cancel routine type.  //    
__drv_functionClass(DRIVER_CANCEL)  
_Requires_lock_held_(_Global_cancel_spin_lock_)  
_Releases_lock_(_Global_cancel_spin_lock_)  
_IRQL_requires_min_(DISPATCH_LEVEL)  
_IRQL_requires_(DISPATCH_LEVEL)  
typedef  
VOID  
DRIVER_CANCEL (  
    _Inout_ struct _DEVICE_OBJECT *DeviceObject,  
    _Inout_ _IRQL_uses_cancel_ struct _IRP *Irp  
    );  
  
typedef DRIVER_CANCEL *PDRIVER_CANCEL;  

Anotaciones sal 2.0 para controladores