第九篇:万丈高楼平地起-驱动编程基础知识点

Windows驱动开发,其中有一个最大的问题,就是微软没有太多公开的技术细节.

很多时候,会让开发者模棱两可, 不知所谓. 所以, Windows驱动的开发, 不是特别容易上手.


我就遇到过一些开发者, 有的是两三年, 有的是六七年的开发经验.

但是, 当你问他一个非常重要但是基本的问题,PDO代表什么, 其作用是什么, 与FDO的差别在哪, 能回答上来三分之一的, 少之又少!


纠其原因,就是应项目需要, 在WDK中找一个类似的例子改一改, 差不多能用了, 就认为已经成功开发过一个Windows驱动了.

而事实上, 其注重的是功能的实现, 而不是根本的基础原理.

所谓的"根基不牵,地动山摇", 就是这种表现, 越到后期, 对驱动的性能优化, 对关键问题的解决, 对代码的针对性的修改, 往往只能是不知所措, 无从下手.


另外一个方面, 现在开发Windows驱动, 很多都直接使用WDF取代了WDM, 对驱动原理,细节的理解,就更加不够深入了.

2006年开始接触Windows驱动开发, 开发过AVStream/BDA, Filter, Function Driver.

但要把一个东西搞清楚明白的角度来讲, 个人感觉差距还很远.

于是,决定以经典的Toaster为范本, 写一点我的个人理解, 同时也还有我的一些疑问, 以供大家参考与指正.



Drivers execute in the context of other threads in the system. They do not

    receive their own threads. This is different than with applications. After
    DriverEntry returns, the Toaster function driver executes in an asynchronous,

    event driven, manner.

驱动是被动型的, 事件驱动型的. 

异步的? (这个"异步"两字,如何理解?)

驱动程序运行在系统进程中. (与此对应的是, 应用程序是应用进程及其包括的相应线程的集合)

运行在任意线程的环境下?(不能一概而论, 比如最高层的驱动, 运行在应用程序线程环境下)

事实上, 驱动程序在设计过程中, 要考虑多核,多线程,不同IRQL, 以及中断情况.

最高层驱动的派遣例程运行在发出派遣请求的应用程序的线程环境下. 而下层的驱动, 就不能保证这个条件.(这是WDK帮助文档中所描述的,如何理解, 更重要的是,微软如何给出这样的结论?)


    The system schedules other threads to execute on behalf of the driver. Every
    thread has a user-mode stack and a kernel-mode stack. When the system schedules
    a thread to execute in a driver, that thread's kernel stack is used.

驱动程序运行过程中, 是运行在别的线程环境下的?(我的理解, 要么就是最高层的驱动派遣例程,运行在发出请求的应用程序线程环境下, 即线程没有切换, 只是切换了栈)

中断处理例程(ISR), 运行在任意线程环境下.

驱动程序可以建立自己的线程(PsCreateSystemThread), 也可以使用(system worker threads).

栈的两种形式



    The I/O manager calls the function driver's routines to process events and I/O
    operations as needed. Multiple threads can execute simultaneously throughout the
    driver, thus special attention to how the driver operates and processes events

    and I/O operations is required

驱动代码编写与设计中, 需要考虑同步问题

同样一个派遣例程,可能被多个线程(可能运行在多个核上)所调用, 所以,必须考虑同步问题(数据保护).


Call the PAGED_CODE macro because this routine must only execute at
    IRQL = PASSIVE_LEVEL. The macro halts the system in the checked build
    of Windows if IRQL >= DISPATCH_LEVEL, because then the Dispatcher cannot
    be invoked, causing a system crash.

不解释, 对PAGED_CODE使用方法与原理的文字


In the past, applications have usually communicated with a hardware instance
    using the FDO's name. However, the FDO created in the call to IoCreateDevice is
    not given a name because that creates a security risk. Instead applications
    use the Toaster device interface. A new instance of the Toaster device
     interface is registered after the FDO is created.

所谓的安全问题(为何使用命名设备对象不安全, 曾经在OSR上看过一篇文章, 有兴趣的同学可以查找翻阅)

安全问题的解决办法(interface)



 // Set the DO_POWER_PAGABLE bit to indicate to the power manager that it must
    // only send power IRPs to the function driver at IRQL = PASSIVE_LEVEL. In later
    // stages of the function driver the ToasterDispatchPower must only execute at
    // IRQL = PASSIVE_LEVEL. If this bit is not set, then the power manager can send
    // power IRPs at IRQL >= DISPATCH_LEVEL, which can cause a system crash because
    // the Dispatcher cannot be invoked when a driver executes at
    // IRQL >= DISPATCH_LEVEL.
    //
    deviceObject->Flags |= DO_POWER_PAGABLE;

给出DO_POWER_PAGABLE 这个BIT的作用

这是给一个给我邮件的同学的回答


    // NextLowerDriver and UnderlyingPDO are not necessarily the same. They can be
    // different when other device objects, such as those created by filter drivers,
    // are layered between the PDO and the FDO.

NextLowerDriver and UnderlyingPDO并不一定是同一个

这是给一个给我邮件的同学的回答, 原因就是可能中间有Filter driver创建的FiDO



The
    // DeviceObject member of DriverObject is a single-linked list of all FDOs
    // created earlier by ToasterAddDevice. DriverObject->DeviceObject should equal
    // NULL when the system calls ToasterUnload. All FDOs should have been deleted
    // earlier when the function driver processed IRP_MN_REMOVE_DEVICE for each
    // hardware instance as the hardware was removed.
    //

驱动对象与设备对象的关系



        // Fail the incoming IRP if the hardware instance has been removed. A driver
        // can receive an IRP to process after it's hardware is removed because
        // multiple threads might execute simultaneously. One thread might process
        // IRP_MN_REMOVE_DEVICE (and change the hardware state to Deleted) and then
        // be suspended before completing IRP_MN_REMOVE_DEVICE, while the system
        // dispatches another IRP to be processed by a different thread. Because the
        // first thread updated the hardware state in the device extension, the
        // second thread will fail it's IRP.

设备对象已经被删除, 系统再次发出IRP的情况的处理



        // Note that the return statement does not dereference the Irp pointer.
        // Instead, the return statement returns the same value that the
        // IoStatus.Status member is set to. Dereferencing the Irp pointer after
        // calling IoCompleteRequest can cause a system crash. For example, the
        // thread that completed the IRP might be suspended between the call to
        // IoCompleteRequest and the return statement. While the thread is suspended,
        // the I/O manager may release the memory associated with the IRP. When the
        // thread later resumes and executes the return statement and attempts to
        // dereference the Irp pointer, a system crash results because the memory is
        // no longer valid.

为何在调用IoCompleteRequest后,不能再dereference该IRP的原因

这是给一个给我邮件的同学的回答


        // The system sends IRP_MN_START_DEVICE to start the hardware instance.
        // The system sends IRP_MN_START_DEVICE after the system has called
        // ToasterAddDevice. The system also sends IRP_MN_START_DEVICE if the
        // PnP hardware was connected when the computer booted. The system also
        // sends IRP_MN_START_DEVICE after it sends a previous IRP_MN_STOP_DEVICE.

IRP_MN_START_DEVICE系统发出的几种可能情况

设备刚接上

系统刚启动

设备被停止后


        // The underlying bus driver must process IRP_MN_START_DEVICE before the
        // function driver can because the bus driver must assign the hardware
        // resources, such as the I/O ports, I/O memory ranges, and interrupts,
        // that the toaster instance can use before the function driver can use
        // them.

为什么必须由下层驱动先处理 IRP_MN_START_DEVICE

这是给一个给我邮件的同学的回答



            // If IoSetDeviceInterfaceState returns STATUS_OBJECT_NAME_EXISTS then
            // the interface is already enabled. The interface might already be
            // enabled if the system sent "and" earlier IRP_MN_STOP_DEVICE to
            // rebalance hardware resources.

不太理解

感觉是作者注释的时候,将an笔误成了and



        // Update the variable that indicates the hardware state of the toaster
        // instance to StopPending. When the hardware state is set to StopPending,
        // any new IRPs that the system dispatches to the function driver are added
        // to the driver-managed IRP queue for later processing. The driver-managed
        // IRP queue is implemented in later stages of the function driver.
        //
        // Otherwise, if the system subsequently sends IRP_MN_CANCEL_STOP_DEVICE then
        // any IRPs the system dispatched between IRP_MN_QUERY_STOP_DEVICE and
        // IRP_MN_CANCEL_STOP_DEVICE would be lost.

IRP queue的作用之一



        // Failing this IRP does not prevent the system from sending a subsequent
        // IRP_MN_REMOVE_DEVICE, it just informs the system that the function driver
        // is unable to remove without losing data.

 IRP_MN_QUERY_REMOVE_DEVICE与IRP_MN_QUERY_STOP_DEVICE的不同之处


        // Pass KernelMode as the wait mode in the call to KeWaitForSingleObject to
        // prevent the memory manager from paging out the device stack.
        //
        KeWaitForSingleObject(&event,
                             Executive,
                             KernelMode,
                             FALSE,
                             NULL
                             );

KernelMode的用法


    The system calls ToasterDispatchPower when the computer shuts down,
    hibernates, suspends, or resumes power while instances of toaster hardware
    remain connected to the computer.

系统调用电源管理IRP的时机


        // A driver must call PoStartNextPowerIrp while the IRP's current I/O
        // stack location points to the current driver. That is,
        // PoStartNextPowerIrp must be called before IoSkipCurrentIrpStackLocation
        // or IoCopyIrpStackLocationToNext. In addition, a driver must call
        // PoStartNextPowerIrp before IoCompleteRequest or PoCallDriver, otherwise
        // a system deadlock could result.

调用方法, 但不明原因?


    // Start the next power IRP. It would not be safe to call PoStartNextPowerIrp
    // before checking to see if the DevicePnPState equals Deleted (above) because
    // a driver should only call PoStartNextPowerIrp immediately before it completes
    // the current power IRP. Because additional logic appears in the Featured1 stage
    // for ToasterDispatchPower the safe method requires these separate calls to
    // PoStartNextPowerIrp.

不明白其意思?


    // Pass the power IRP down the device stack. Drivers must call PoCallDriver
    // instead of IoCallDriver to pass power IRPs down the device stack. PoCallDriver
    // ensures that power IRPs are properly synchronized throughout the system.

PoCallDriver保证同步?




    The system calls ToasterSystemControl to return metrics about the operation
    and status of a toaster hardware instance.

Irp represents the WMI operation associated with the hardware instance
    described by the DeviceObject parameter, such as querying the number of
    failures that the hardware instance has recorded.

ToasterSystemControl例程的作用



今天先写到这里, 事实就是如此, 我也仍然有某些不明白的地方.

学海无涯!

敬请达人,牛人, 指导斧正.

敬请专家能给出一些不明白地方的点拨!


以上知识点来自Toaster Fun Incomplete 1.



你可能感兴趣的:(thread,kernel,软件,驱动开发,Windows驱动)