第4章 WIN2000中文输入法
- 格式:ppt
- 大小:709.00 KB
- 文档页数:26
1第十四章.系统线程驱动程序执行的工作不可能总是响应请求﹐至少不是在请求的时间动作﹒一些工作必须与调用者同步﹐可能与其它的驱动程序活动在不同的优先级﹒WIN2000允许单独的执行线程的创建﹐每个允许一个独立的代码路径﹒它们被适当的事件触发运行﹒本章介绍创建内核模式线程的过程﹒也介绍不同线程间的同步问题﹒定义和使用系统线程线程是执行的单位﹒每个线程保持一个独立的程序计数器和包含一个私有的CPU寄存器设置的上下文环境﹒每个线程有一个优先权﹐这个优先权决定何时线程得到系统处理器的控制权﹒通常﹐优先权越高的线程﹐越有可能得到控制权﹒线程可以在用户模式或者内核模式操作﹒系统线程运行在专用的内核模式﹒它没有用户模式上下文﹐也不能访问用户地址空间﹒像Win32线程﹐系统线程运行在或者低于APC_LEVEL IRQL﹐根据调度优先权竞争CPU的使用权﹒何时使用线程有几个使用线程的原因﹕1. 设备很慢﹐且很少访问﹒2. 设备转换状态需要很长时间(多于50毫秒)﹐且驱动程序必须等待转换发生﹒3. 设备为了一个简单的操作需要转换多个状态且驱动程序必须查询设备一段时间﹒4. 设备的一些状态变化不产生中断且驱动程序必须查询设备一段时间﹒应该使用一个CustomTimerDpc例程管理这样的设备﹒根据设备的活动量﹐这个方法可能充满DPC队列﹐使其它驱动程序变得缓慢﹒另一方面﹐运行在PASSIVE_LEVEL水平上不会影响到DPC例程﹒幸运的是﹐大多数现代的硬件不会像上面一样运行﹒遗留下来的硬件的状态变化需要驱动程序查询和重试﹒一个重要的例子是软驱和其它与软盘控制器连系的设备﹒另一个需要线程的是执行过多时间初始化的设备﹒驱动程序必须侦测初始化过程﹐查询状态的变化﹒因为服务控制管理器仅仅给驱动程序30秒来执行DriverEntry例程﹒否则服务控制管理器强制卸载驱动程序﹒解决办法是放置一个长时间运行的设备开始代码在一个单独的线程中﹐迅速使用STATUS_SUCCESS从DriverEntry例程中返回﹒最后﹐可能有一些只能运行在PASSIVE_LEVEL IRQL的操作﹒例如﹐如果驱动程序必须访问注册表或者执行文件操作﹐就必须考虑多线程了﹒创建和结束系统线程驱动程序使用PsCreateSystemThread函数来创建系统线程﹒因为只能在PASSIVE_LEVEL IRQL上调用这个函数﹐所以通常线程在DriverEntry或者AddDevice例程创建﹒当一个驱动程序被卸载﹐必须保证所有的它创建的线程都被结束﹒PsTerminateSystemThread函数就是系统线程结束自己的方法﹒不像Win32用户模式线程﹐系统线程不能强制结束﹒这就是说﹐需要建立一个信号机制﹐来让线程知道它应该结束﹒表14.1. NTSTATUS PsCreateSystemThread函数原型表14.2. NTSTATUS PsTerminateSystemThread函数原型管理线程优先权通常﹐系统线程应该运行在实时范围的低端﹒VOID ThreadStartRoutine( PVOID pContext ){ ...KeSetPriorityThread( KeGetCurrentThread(), LOW_REALTIME_PRIORITY );... }实时线程没有定量超时时间﹒所以只有当线程自愿进入等待状态的时候才会放弃CPU﹐或者被另一个优先权更高的线程抢占﹒由此﹐驱动程序不能依赖时间片轮转﹒系统工作者线程有些时候﹐运行在PASSIVE_LEVEL IRQL上的线程的创建和结束不是非常有效的﹒一种替代的方法是﹐使用一个回调机制来代表驱动程序工作﹒使用系统工作者线程是容易的﹒先为一个WORK_QUEUE_ITEM结构分配地址空间﹒系统使用这个块来保持工作请求的跟踪﹒再使用WORK_QUEUE_ITEM参数调用ExInitializeWorkItem函数联系一个回调函数﹒稍后﹐当一个系统线程需要执行回调函数的时候﹐调用ExQueueWorkItem函数来插入请求块到一个系统工作请求中﹒这个请求可以工作在实时优先权的系统线程或者一个可变优先权的线程﹒要知道所有的驱动程序分享同一组系统工作者线程﹒请求一个长时间的执行可能延迟来自其它驱动程序请求的执行﹒执行长时间的操作应该使用一个私有的线程﹐不要使用系统工作者线程﹒线程同步像Win32应用程序一样﹐系统线程也需要悬挂自己直到一些条件满足﹒这个部分描述系统线程可以使用的同步技术﹒时间同步最簡單的同步是停止線程的執行直到指定的時間間隔過去﹒內核提供KeDelayExecutionThread函數來方便的使用定時器對象﹒一般的同步系统线程可以通过等待派遣对象来同步它们的活动﹒派遣对象有两个状态﹐发信号状态和静止状态﹒当线程等待一个静止状态的派遣对象﹐线程就会停止直到对象变成发信号状态﹒有两个函数用来等待一个派遣对象﹒KeWaitForSingleObject函数这个函数使线程成为等待状态直到一个特殊的派遣对象被设置为发信号状态﹒timeout值应该被指定为如果派遣对象是静止状态是时﹐线程的唤醒时间﹒如果这个值设置为NULL﹐等待是不确定的﹒表14.3. NTSTATUS KeDelayExecutionThread函数原型表14.4. NTSTATUS KeWaitForSingleObject函数原型KeWaitForMultipleObjects函数这个函数将调用者线程变为等待状态直到任何或者所有的派遣对象被设置为发信号状态﹒表14.5. NTSTATUS KeWaitForMultipleObjects函数原型要知道﹐一次线程可以等待的对象数目是有限的﹒每个线程有一个内嵌的用作当前等待操作的等待块数组﹒线程可以使用这个数组等待最多THREAD_WAIT_OBJECTS个对象﹒如果THREAD_WAIT_OBJECTS表示的数目不够了﹐驱动程序提供的另外的等待块数组必须被包含在调用KeWaitForMultipleObjects函数中﹒然而﹐等待的对象数目不能超过MAXIMUM_WAIT_OBJECTS个﹒KeWaitForXxx函数可能从PASSIVE_ LEVEL或者DISPATCH_LEVEL IRQL被调用﹐然而﹐对于DISPATCH_LEVEL IRQL﹐必须指定timeout值为0(0与NULL是不同的)﹐在DISPATCH_LEVEL IRQL﹐这个调用对于单个对象用作查询是很有效的﹒使用派遣对象除了线程对象自己之外﹐驱动程序必须为任何可能用到的派遣对象分配存储空间﹒派遣对象必须是永久存储﹐所以﹐通常分配到设备或者控制器的Extension中﹒且必须在非分页的存储空间中﹒派遣对象必须在使用之前使用合适的KeInitializeXxx函数初始化一次﹒因为初始化函数只能在PASSIVE_LEVEL IRQL上被调用﹐所以﹐派遣对象常常在DriverEntry或者AddDevice例程中被初始化﹒下面部分详细描述派遣对象的各个方面﹒事件对象事件是必须被明确的设置为静止或者发信号状态的派遣对象﹒事件类似于一个二进制标志﹐允许一个线程通过设置为发信号状态来通知另一个线程﹒这些对象只有两种行为:通知事件和同步事件﹒在对象初始化的时候选择类型﹐这两个事件类型在被设置为发信号状态后的行为是不同的﹒只要一个通知事件保持发信号状态﹐所有的等待这个事件的线程就退出等待状态﹒通知事件必须被明确的设置为静止状态﹒图14.1. 事件对象同步系统线程当一个同步事件被设置为发信号状态﹐它保持发信号状态直到另一个线程调用KeWaitForXxx函数的时候﹐才自动的设置自己为静止状态﹒使用一个事件对象之前﹐要分配一个KEVENT数据类型的存储空间﹒两个使事件对象变为静止状态的函数是不同的﹒KeResetEvent函数返回事件变为静止状态之前的状态﹐KeClearEvent的速度比较快﹐因为它没有返回值﹐所以它用在之前的状态已知的情况下﹒本章的驱动程序提供一个使用系统线程的例子﹐它有一个需要暂停直到一个中断到达的工作者线程﹐因此这个线程等待一个事件对象﹒驱动程序的DpcForIsr例程设置事件为发信号状态﹐唤醒工作者线程﹒表14.6. 事件对象操作函数多驱动程序共享事件使用KeInitializeEvent函数创建的事件对象是很难被两个不相干的驱动程序共享﹒事件对象只能被指针引用﹐没有直接的协议(例如﹐内部的IOCTL)﹐没有一个简单的方法将一个驱动程序的指针传递到另一个驱动程序﹒尽管这样﹐当另一个驱动程序使用对象的时候﹐驱动程序要保证创建的这个对象没有卸出的问题﹒IoCreateSynchronizationEvent和IoCreateNotificationEvent函数允许创建命名的事件对象﹐只要两个驱动程序使用相同的事件名字﹐它们每个都可以获得指向同一个事件对象的指针﹒这两个函数的行为像WIN32的CreateEvent函数一样﹒换句话说﹐第一个驱动程序使用事件名调用函数﹐这将创建一个事件对象﹒后来的尝试创建一个事件对象的复制品的调用将仅仅返回一个存在的事件对象句柄﹒IoCreateXxxEvent函数有两个值得注意的性质:第一﹐KEVENT对象的存储器不是被驱动程序分配﹐是系统提供的存储空间﹒当最后一个用户将它释放的时候﹐系统自动删除对象﹒第二﹐IoCreateXxxEvent函数返回事件对象的句柄﹐不是存储空间指针﹒必须执行下列步骤来将句柄转变成为指针:1. 调用ObReferenceObjectByHandle函数﹒这个函数获得事件对象的指针﹐且将对象的指针引用计数增加1﹒2. 当句柄不再需要的时候﹐调用ZwClose函数将它释放﹒这个函数减小对象的句柄参考计数﹐在指针参考计数加1之后﹐再调用这个函数﹐否则对象将被删除﹒3. 当事件对象不再需要的时候﹐调用ObDereferenceObject函数减少指针参考计数﹐且可能删除事件对象﹒这些函数仅仅在PASSIVE_LEVEL IRQL被调用﹐它限制了驱动程序使用它们的范围﹒Mutex 对象互斥对象(Mutex)在同一时间只能被一个线程拥有的派遣对象﹒当一个线程拥有对象的时候﹐它变得静止﹐当它变得可以被线程利用的时候﹐它在发信号状态﹒互斥对象提供一个简单的机制来让多个线程互斥的访问共享的资源﹐例如﹐存储器﹒图14.2表示了线程B﹐C和D等待线程A拥有的互斥对象﹒当A释放了互斥对象﹐等待线程的其中之一获得互斥对象的拥有权﹒图14.2. 互斥对象同步系统线程使用互斥对象﹐必须在非分页的存储空间中保留一个KMUTEX类型的空间﹒要知道当一个互斥对象的初始化﹐总是设置它为发信号状态﹒如果一个线程在已经拥有互斥对象的情况下调用KeWaitForXxx函数﹐线程不会等待﹒而互斥对象增加引用计数器﹐事实上它是递归的拥有请求﹒当线程想要释放互斥对象﹐要调用KeReleaseMutex函数的次数和请求拥有的次数相等﹒这样互斥对象才会进入发信号状态﹒这个行为和Win32应用程序的互斥对象是相同的﹒在控制由驱动程序转变到用户模式之前驱动程序释放任何的互斥对象是非常重要的﹒内核将确认是否驱动程序线程在拥有互斥对象的情况下尝试返回控制给I/O管理器﹒例如﹐DriverEntry或者派遣例程是不允许请求一个可能被其它派遣例程或者系统线程释放的互斥对象表14.7. 互斥对象的操作函数信号量对象信号量(Semaphore)是一个保持一个计数的派遣对象﹒这个对象在它的引用计数器大于零的情况下保持发信号状态﹐当它的引用计数器为零的时候为静止状态﹒换句话说﹐信号量是一个计数互斥对象﹒图14.3显示了信号量的操作﹒线程B﹐C和D等待一个当前计数为零的信号量﹒当线程A调用两次KeReleaseSemaphore函数﹐计数增加到2﹐有两个线程被允许恢复执行﹒唤醒两个线程也导致信号量的引用计数器减小到0﹒图14.3. 信号量对象同步系统线程本章的后面将提供一个简单的驱动程序实例﹐在每次增加一个IRP到工作队列它的派遣例程就增加一个信号量计数﹐当线程从队列中移除IRP﹐它将减小计数﹒当队列为空的时候就进入等待状态﹒使用信号量之前﹐必须分配一个KSEMAPHORE结构的存储空间定时器对象定时器(Timer)是一个有超时值的派遣对象﹒当一个定时器被打开﹐它进入静止状态直到时间到﹐这时它转变为发信号状态﹐在前面介绍的定时器对象是用来强制一个CustomTimerDpc例程执行﹐因为它们仅仅是内核派遣对象﹐它们也可以用来在KeWaitForXxx调用中使用﹒图14.4描述了定时器对象的动作﹒线程A开始定时器然后调用KeWaitForSingleObject函数﹐线程阻塞直到定时时间到﹒那时﹐定时器进入发信号状态﹐线程被唤醒﹒表14.8. 操作信号量对象的函数图14.4. 定时器对象同步系统线程定时器对象有两种﹕通知定时器和同步定时器﹒在对象被初始化的时候决定它的类型﹐虽然两种类型的定时器都是在超时时间到的情况下转变为发信号状态﹐但是﹐保持发信号状态的时期是不同的﹒当一个通知定时器到期﹐它保持发信号状态直到它被明确的复位﹒在定时器变为发信号状态的时候﹐所有的等待定时器的线程都被唤醒﹒当一个同步定时器到期﹐它仅仅保持发信号状态到满足一个单个的KeWaitForXxx请求为止的一段时间﹐在这之后﹐定时器对象自动变为静止状态﹒在使用定时器对象之前﹐必须为它分配一个KTIMER结构的存储空间﹒表14.9. 操作定时器对象的函数线程对象系统线程也是有信号状态的派遣对象﹒当一个系统线程终止时﹐它的线程对象从静止状态转变为发信号状态﹐这允许驱动程序通过等待线程对象来同步它的cleanup操作﹒当PsCreateSystemThread函数被调用﹐它返回一个线程对象的句柄﹒调用KeWaitForXxx函数来使用线程对象﹐函数需要一个线程对象的指针而不是它的句柄﹐为了将句柄转变成为指针﹐必须执行下列步骤﹕1. 调用ObReferenceObjectByHandle函数﹒这个函数获得事件对象的指针﹐且将对象的指针引用计数增加1﹒2. 当句柄不再需要的时候﹐调用ZwClose函数将它释放﹒这个函数减小对象的句柄参考计数﹐在指针参考计数加1之后﹐再调用这个函数﹐否则对象将被删除﹒3. 当事件对象不再需要的时候﹐调用ObDereferenceObject函数减少指针参考计数﹐且可能删除事件对象﹒这些函数仅仅在PASSIVE_LEVEL IRQL被调用﹐它限制了驱动程序使用它们的范围﹒互斥对象的变化WIN2000执行部件支持两种互斥对象﹒下面将简要的介绍它们﹒通常使用使用这些对象代替内核互斥对象可以使驱动程序更好的执行﹒快速互斥对象快速互斥对象(Fast Mutex)是同步对象﹐它与内核互斥对象相似﹐只是不允许递归拥有﹒通过移除这个性质来提高它的执行速度﹒快速互斥对象是一个FAST_MUTEX类型的对象﹐它联系一个或者多个需要保护的数据项﹒任何访问这个数据项必须先获得相应的FAST_MUTEX的拥有权﹒注意﹐这些对象有自己的请求拥有权的函数﹒以前的KeWaitForXxx函数不能用来请求快速互斥对象﹒表14.10. 操作快速互斥对象的函数决策资源决策资源(Executive Resource)是一个非常像内核互斥对象的同步对象﹒主要的不同是决策资源可以被一个线程独占或者由多个线程读访问共享﹒因为它可以公共的被多个读操作同时访问一个资源﹐决策资源对象比标准的内核互斥对象提供更大的吞吐量﹒决策资源仅仅是一个ERESOURCE结构类型的对象﹒任何需要访问它保护的数据的操作必须先请求相应的ERESOURCE的拥有权﹒注意这些对象有自己的函数﹒表14.11. 操作决策对象的函数同步死锁死锁可能在任何多个线程同时竞争多个资源的时候产生﹒图14.5. 死锁图解1. 线程A请求资源X﹒2. 线程B请求资源Y﹒3. 线程A请求资源Y﹐所以进入等待线程B释放Y的状态﹒4. 线程B请求资源X﹐所以进入等待线程A释放X的状态﹐结果就是死锁﹒在使用事件﹐互斥对象或者信号量时都可能引起死锁﹒甚至是线程对象也可能引起死锁﹒有两种方法解决死锁问题:1. 使用超时参数的KeWaitForXxx函数来限制等待时间﹒因为这个方法可能有助于发现一个死锁﹐但它不能解决根本的问题﹒2. 强制所有的线程使用固定的顺序获得拥有权﹒如果线程A和B都先访问资源X后访问资源Y﹐就不会产生死锁了﹒互斥对象使用级数﹐提供一些对死锁的防护﹒当一个互斥对象被初始化﹐一个级数就被分配了﹒当一个线程尝试获得互斥对象的时候﹐如果线程拥有任何低的级数的互斥对象的话﹐内核不会给于拥有权﹒通过强制执行这个策略﹐内核避免了涉及多个互斥对象的死锁﹒基于线程的驱动实例这个部分提供基于包的辅助DMA驱动程序的修改版本﹒这个驱动程序的不同是使用一个系统线程来完成大多数的I/O处理﹒结果﹐它使用非常少的时间在DISPATCH_LEVEL IRQL﹐且对其它系统组件没有多少干扰﹒驱动程序怎样工作注意﹐驱动程序没有Start I/O例程﹒当用户模式I/O请求到达的时候﹐驱动程序的一个派遣例程简单的添加与设备对象相关联的IRP到工作队列中﹐然后这个派遣例程调用KeReleaseSemaphore函数增加一个跟踪工作队列中IRPs数目的信号量对象﹐一个非零的信号量计数表示工作队列中有IRP需要处理﹒图14.6. 基于线程的DMA驱动程序构架每个设备对象有自己的处理这些I/O请求的系统线程﹐这个线程在一个KeWaitForSingleObject函数为开始的无限循环中﹐如果信号量对象是一个非零值﹐线程从工作队列中移出IRP和执行这个I/O操作﹐也减小一个计数﹐另一方面﹐如果计数是零﹐线程进入等待状态直到派遣例程插入另一个IRP到队列中﹒当线程需要执行数据传输﹐它开始设备﹐然后使用KeWaitForSingleObject函数来等待事件对象﹐驱动程序的DpcForIsr例程在一个中断到达之后设置事件为发信号状态﹒事件对象有效地使用出列IRP的工作者线程同步了中断服务代码(实际上是DPC)﹒当驱动程序的RemoveDevice例程需要取消系统线程的时候﹐它设置设备Extension中的一个标记﹐并且增加信号量对象的计数﹐它就唤醒和终止线程﹒如果线程在执行I/O操作的过程中﹐只有在完成当前的IRP之后再终止线程﹒DEVICE_EXTENSION结构这个文件包含所有的常用的驱动过程定义的数据结构﹒下面的片断显示管理系统线程和它的工作队列的代码﹒typedef struct _DEVICE_EXTENSION{ ...PETHREAD pThreadObj; // 指向工作者线程对象的指针BOOLEAN bThreadShouldStop; // 这是一个当它被设置为TRUE的时候线程退出的标志KEVENT evAdapterObjectIsAcquired; // 发信号给Adapter对象已经拥有的事件KEVENT evDeviceOperationComplete; // 发最后的操作完成的信号的事件KSEMAPHORE semIrpQueue; // 信号量和自旋锁管理的IRP队列KSPIN_LOCK lkIrpQueue;LIST_ENTRY IrpQueueListHead;} DEVICE_EXTENSION, *PDEVICE_EXTENSION;AddDevice例程这段程序演示了线程对象﹐工作队列和各种各样的用来处理I/O请求的同步对象的初始化代码﹒记得对于每个设备对象﹐AddDevice例程仅仅调用一次﹒NTSTATUS AddDevice( IN PDRIVER_OBJECT pDriverObj, IN PDEVICE_OBJECT pdo ){ ... // 初始化工作队列自旋锁KeInitializeSpinLock( &pDevExt->lkIrpQueue );//初始化工作队列InitializeListHead( &pDevExt->IrpQueueListHead );// 初始化工作队列信号量KeInitializeSemaphore( &pDevExt->semIrpQueue, 0, MAXLONG);// 为Adapter对象初始化时间KeInitializeEvent( &pDevExt-> evAdapterObjectIsAcquired,SynchronizationEvent, FALSE );// 为操作完成初始化事件KeInitializeEvent( &pDevExt->evDeviceOperationComplete,SynchronizationEvent, FALSE );// 初始化工作者线程pDevExt->bThreadShouldStop = FALSE;HANDLE hThread = NULL; // 开始工作者线程status = PsCreateSystemThread( &hThread, (ACCESS_MASK)0, NULL,(HANDLE)0, NULL, WorkerThreadMain, pDevExt );if (!NT_SUCCESS(status)) { IoDeleteSymbolicLink( &linkName );IoDeleteDevice( pfdo ); return status; }// 获得真正的指向线程对象的指针ObReferenceObjectByHandle( hThread, THREAD_ALL_ACCESS,NULL, KernelMode, (PVOID*)&pDevExt->pThreadObj, NULL );ZwClose( hThread ); // 不需要处理... }DispatchReadWrite例程这个例程响应用户的读写设备的请求﹒在检测是否是一个零长度的数据传输之后﹐放置IRP为悬挂状态﹐然后插入到工作队列的信号量对象中﹒注意没有调用IoStartPakcet函数﹐因为没有Start I/O例程﹒NTSTATUS DispatchReadWrite( IN PDEVICE_OBJECT pDO, IN PIRP pIrp ){ PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp );PDEVICE_EXTENSION pDE = pDO->DeviceExtension;// 检查是否零字节传输if( pIrpStack->Parameters.Read.Length == 0 ){ pIrp->IoStatus.Status = STATUS_SUCCESS;pIrp->rmation = 0;IoCompleteRequest( pIrp, IO_NO_INCREMENT );return STATUS_SUCCESS; }IoMarkIrpPending( pIrp ); // 开始设备操作// 添加IRP到线程的工作队列ExInterlockedInsertTailList( &pDE->IrpQueueListHead,&pIrp->Tail.Overlay.ListEntry, &pDE->lkIrpQueue );KeReleaseSemaphore( &pDE->semIrpQueue,0, // 没有优先权推进1, //将信号量计数增加1Increment semaphore by 1FALSE ); // 在这个函数后没有WaitForXxxreturn STATUS_PENDING; }Thread.cpp这个模块包含主要的线程函数和需要的管理线程的例程﹒WorkerThreadMain这是IRP的处理引擎﹐它的工作是从设备Extension的工作队列中取出I/O请求﹐并且执行数据传输操作﹐这个函数继续等待新的IRP直到RemoveDevice例程告诉它关闭﹒VOID WorkerThreadMain( IN PVOID pContext ) {PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pContext;PDEVICE_OBJECT pDeviceObj = pDevExt->pDevice;PLIST_ENTRY ListEntry;PIRP pIrp;CCHAR PriorityBoost;// 工作者线程运行在比用户线程高的优先级KeSetPriorityThread( KeGetCurrentThread(), LOW_REALTIME_PRIORITY );while( TRUE ) // 进入主要的IRP处理循环{ // 等待一个IRP出现在工作队列中或者RemoveDevice例程停止线程KeWaitForSingleObject( &pDevExt->semIrpQueue, Executive,KernelMode, FALSE, NULL );// 检查是否因为设备被移除而导致线程唤醒if( pDevExt->bThreadShouldStop )sTerminateSystemThread(STATUS_SUCCESS);// 它一定是一个真正的请求﹐得到IRPListEntry = ExInterlockedRemoveHeadList(&pDevExt->IrpQueueListHead, &pDevExt->lkIrpQueue);pIrp = CONTAINING_RECORD( ListEntry,IRP, Tail.Overlay.ListEntry );// 处理IRP﹐因为这是一个同步操作﹐所以这个函数没有返回直到移除IRP的时间到PriorityBoost = PerformDataTransfer( pDeviceObj, pIrp );// 释放IRP和回到循环的顶部﹐看是否有另一个正在等待的请求IoCompleteRequest( pIrp, PriorityBoost ); }} // 结束循环KillThread这个函数通知与一个特殊的设备对象相关联的线程该结束了﹐为了简单化﹐这个函数停止和等待直到目标线程完成﹐因此﹐这个函数只能运行在PASSIVE_LEVEL IRQL﹒VOID KillThread( IN PDEVICE_EXTENSION pDE ) {pDE->bThreadShouldStop = TRUE; //设置停止标志// 确定线程被唤醒KeReleaseSemaphore( &pDE->semIrpQueue,0, // 没有优先级推进1, // 将信号量计算增加1TRUE ); // 在本函数之后有WaitForXxx函数调用// 等待线程停止KeWaitForSingleObject(&pDE->pThreadObj, Executive, KernelMode,FALSE, NULL );ObDereferenceObject( &pDE->pThreadObj ); }Transfer.C这个部分包含执行I/O操作的支持例程﹒这部分代码是从基于包的辅助DMA驱动程序继承来的﹐所以只介绍差别大的代码﹒值得注意的是非常具体的Adapter Control或者DpcForIsr例程的工作﹒这些函数仅仅设置事件对象来发线程的数据传输例程可以继续进行的信号﹒PerformDataTransfer这个函数移动整个数据缓冲区到或者从设备﹒这可能包含在没有足够的映射寄存器的情况下将整个传输分成几个独立的设备操作﹒这个例程运行在PASSIVE_LEVEL IRQL而且不返回控制给调用者直到所有的事情做完﹒CCHAR PerformDataTransfer( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ){ PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp );PDEVICE_EXTENSION pDE = (PDEVICE_EXTENSION) pDevObj->DeviceExtension;PMDL pMdl = pIrp->MdlAddress;ULONG MapRegsNeeded; NTSTATUS status;// 设置I/O方向标志if( pIrpStack->MajorFunction == IRP_MJ_WRITE )pDE->bWriteToDevice = TRUE;else pDE->bWriteToDevice = FALSE;// 设置纪录信息pDE->bytesRequested = MmGetMdlByteCount( pMdl );pDE->bytesRemaining = pDE->bytesRequested;pDE->transferVA = (PCHAR) MmGetMdlVirtualAddress( pMdl );// 清理CPU高速缓冲存储器﹐如果需要KeFlushIoBuffers( pIrp->MdlAddress, !pDE->bWriteToDevice, TRUE );// 计算传输的第一部分的尺寸pDE->transferSize = pDE->bytesRemaining;MapRegsNeeded = ADDRESS_AND_SIZE_TO_SPAN_PAGES(pDE->transferVA, pDE->transferSize ); if( MapRegsNeeded > pDE->mapRegisterCount ){ MapRegsNeeded = pDE->mapRegisterCount;pDE->transferSize = MapRegsNeeded * PAGE_SIZE- MmGetMdlByteOffset( pMdl );}// 获得adapter对象status = AcquireAdapterObject( pDE, MapRegsNeeded );if( !NT_SUCCESS( status )) {pIrp->IoStatus.Status = status;pIrp->rmation = 0;return IO_NO_INCREMENT; }// 尝试执行第一部分的数据传输status = PerformSynchronousTransfer( pDevObj, pIrp ); if( !NT_SUCCESS( status )) {pDE->pDmaAdapter->DmaOperations->FreeAdapterChannel ( pDE->pDmaAdapter );pIrp->IoStatus.Status = status;pIrp->rmation = 0;return IO_NO_INCREMENT; }// 处理完成﹐刷新纪录信息pDE->transferVA += pDE->transferSize;pDE->bytesRemaining -= pDE->transferSize;// 循环完成请求中所有的部分传输操作while( pDE->bytesRemaining >0 ){ // 尝试传输所有的数据pDE->transferSize = pDE->bytesRemaining;MapRegsNeeded = ADDRESS_AND_SIZE_TO_SPAN_PAGES(pDE->transferVA, pDE->transferSize );// 如果一次不能传输所有的数据﹐减小这次传输的数据量if (MapRegsNeeded > pDE->mapRegisterCount) {MapRegsNeeded = pDE->mapRegisterCount;pDE->transferSize = MapRegsNeeded * PAGE_SIZE -BYTE_OFFSET(pDE->TransferVA); }// 尝试执行设备操作status = PerformSynchronousTransfer( pDevObj, pIrp );if( !NT_SUCCESS( status )) break;// 完成﹐为下一次传输刷新纪录信息pDE->transferVA += pDE->transferSize;pDE->bytesRemaining -= pDE->transferSize; }// 在所有的数据传输完成之后﹐释放DMA Adapter对象pDE->pDmaAdapter->DmaOperations->FreeAdapterChannel ( pDE->pDmaAdapter ); // 发送IRP返回给调用者﹐它的最后的状态是最后一次传输操作的状态pIrp->IoStatus.Status = status;pIrp->rmation = pDE->bytesRequested- pDE->bytesRemaining;// 因为至少有一个I/O操作﹐所以﹐给IRP一个优先级推进return IO_DISK_INCREMENT; }AcquireAdapterObject和AdapterControl这两个函数一起给线程获得adapter对象的拥有权设置一个同步机制﹐AcquireAdapterObject函数运行在系统线程的上下文环境中﹐所以它可以停止和等待一个非零的时间间隔﹒static NTSTATUS AcquireAdapterObject( IN PDEVICE_EXTENSION pDE,IN ULONG MapRegsNeeded ){ KIRQL OldIrql; NTSTATUS status;// 为了请求Adapter对象﹐必须运行在DISPATCH_LEVELKeRaiseIrql( DISPATCH_LEVEL, &OldIrql );pDE->pDmaAdapter->DmaOperations->AllocateAdapterChannel ( pDE->pDmaAdapter, pDE->pDevice,MapRegsNeeded, AdapterControl, pDE );KeLowerIrql( OldIrql );// 如果调用失败﹐是因为没有足够的映射寄存器if( !NT_SUCCESS( status )) return status;// 停止和等待Adapter Control例程设置事件对象//这是我们得到Adapter对象的信号KeWaitForSingleObject( &pDE->evAdapterObjectIsAcquired,Executive, KernelMode, FALSE, NULL );return STATUS_SUCCESS; }IO_ALLOCATION_ACTION AdapterControl(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp, IN PVOID MapRegisterBase, IN PVOID pContext ){ PDEVICE_EXTENSION pDE = (PDEVICE_EXTENSION) pContext;// 存储映射寄存器的句柄﹐线程需要它设置数据传输pDE->mapRegisterBase = MapRegisterBase;// 让线程知道它可以使用Adapter对象。
word 2000Word 2000Word 2000是Microsoft Office套件中的一款文字处理软件,于1999年发布。
它是Word系列软件的第九代,是在Word 97的基础上进行升级和改进的。
Word 2000引入了许多新功能和改进,使得用户能够更轻松地创建和格式化文档。
下面将介绍Word 2000的一些主要功能和改进。
1. 用户界面改进:与之前的版本相比,Word 2000的用户界面进行了一系列的改进。
在菜单栏和工具栏上添加了更多的命令和选项,使得用户能够更快速地访问所需的功能。
此外,Word 2000还引入了自定义工具栏的功能,允许用户根据自己的需求创建和定制工具栏。
2. 支持HTML格式:Word 2000首次引入了对HTML格式的支持,用户可以将Word 文档保存为HTML格式,方便在网页上进行展示和共享。
这为用户提供了更多的选择,使得他们能够更灵活地利用Word文档。
3. 协作功能:Word 2000还引入了一些协作功能,方便多人对同一份文档进行编辑和注释。
用户可以设置文档的保护密码,以控制其他用户对文档的修改权限,还可以跟踪文档的修改历史和接受或拒绝其他人的修改建议。
4. 自动纠错和智能扩展:Word 2000内置了智能纠错功能,能够自动检测和纠正拼写和语法错误。
此外,它还具备智能扩展功能,可以根据用户输入的内容自动完成单词和短语,提高输入速度和准确性。
5. 强大的格式化功能:Word 2000提供了许多强大的格式化功能,使用户能够更好地呈现文档。
用户可以应用不同的字体、颜色和大小,设置段落的对齐方式和缩进,插入图片、表格和图形等。
此外,还可以使用页眉和页脚、目录和脚注等功能,更好地组织文档内容。
6. 强大的插图工具:Word 2000还引入了一系列强大的插图工具,用户可以从图库中选择图片、图形和图标,并将其插入到文档中。
这些插图工具可以帮助用户更好地表达和传达信息,使得文档更具吸引力和可读性。
荆州职业技术学院计算机应用技术课程教案(实验)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(实验)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(实验)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(实验)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(实验)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(实验)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(实验)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(实验)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(实验)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(理论)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(理论)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(理论)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(理论)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(理论)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(理论)专业班级 KI0916 教师张魏授课时间
荆州职业技术学院计算机应用技术课程教案(理论)专业班级 KI0916 教师张魏授课时间。
∙一、在Windows 98中安装日语输入法∙二、在Windows 2000中安装日语输入法∙三、在Windows XP中安装日语输入法一、在Windows 98中添加日语输入法1、先点击这里下载并安装微软的日语输入法,安装完毕要求重新启动电脑2、在屏幕右下角系统托盘中输入法选择中就可以切换到日语输入法了。
3、切换到日语输入法后可以点击日语输入法工具条上的“A”按钮在平假名、全/半角片假名、全/半角英数字等之间切换。
二、在Windows 2000中安装日语输入法1、选择系统[开始]菜单中的[设置]-[控制面板]菜单进入系统控制面板。
2、在系统控制面板中选择[区域选项]。
3、在区域选项设置画面的下部,找到日语并打上勾4、点击[确定]按钮后系统会自动安装,安装过程中会要求你放入Windows2000的安装盘,安装完毕后会要求你重新启动系统。
在安装过程中如果提示找不到相关文件的话,说明你使用的安装盘里没有包含日语输入法。
5、重新启动后再次进入区域选项设置,这次选上方的[输入法区域设置]标签6、点击[添加]按钮7、找到日语选项8、确认无误后点击[确定]按钮,退出区域设置画面。
9、在屏幕右下角系统托盘中的输入法选择中就可以切换到日语输入法了。
10、切换到日语输入法后可以点击日语输入法工具条上的“A”按钮在平假名、全/半角片假名、全/半角英数字等之间切换。
三、在Windows XP中安装日语输入法1、选择系统[开始]菜单中的[控制面板]菜单进入系统控制面板。
2、在系统的控制面板中选择[日期、时间、语言和区域设置]3、选[添加其他语言]4、在设置画面点击[详细信息]按钮5、点击[添加]按钮6、在选项中找到日语选项7、确认无误后点击[确定]按钮,退出设置画面。
8、在屏幕右下角系统托盘中的输入法选择中点击输入法工具条上的[CH]或[JP]就可以切换中日文输入法了。
(默认情况下您也可以通过按击键盘的Alt+Shift键在中日文输入法间切换)9、切换到日语输入法后可以点击日语输入法工具条上的“A”按钮在平假名、全/半角片假名、全/半角英数字等之间切换。
Windows 2000柳长顺林富荣昌盛编著北京大学出版社出版知识点整理DX:单项选择题PD:判断题TK:填空题JD:简答题第1章:Windows 2000中文版的特点1、操作系统是计算机系统中的核心系统软件。
DX2、 Windows 2000、Windows NT、Novell是网络操作系统,MS-DOS 不是网络操作系统。
DX3、Windows操作系统区别于DOS和Windows 3.X的最显著的特点是它__是真正32位的操作系统____。
DX第2章:Windows 2000中文版的安装1、在启动Windows的过程中,应按__ F4或F8__键可以直接进入MS-DOS系统。
TK2.登陆window 2000时输入的密码不区分大小写。
(不正确)PD3.administator帐户对计算机具有不受限制的完全控制权。
(正确)PD4.在启动系统时,当内存检查结束后,立即按__ F8___键,可以不启动windows而直接进入MS—DOS系统。
TK 5.在Windows的“关闭系统”对话框中,选择“注销”是希望__以其他用户的身份重新登录TK第3章:浏览Windows 2000中文版1、在Windows2000中,Windows任务栏可以放在桌面四个边的任意边上。
DX2、Windows 2000缺省环境中,在文档窗口之间切换的组合键是Alt+Tab 。
DX3、Windows 2000文件名的最大长度是225字符。
DX4、下列关于Windows 2000菜单的说法中DXA.命令前有“·”记号的菜单选项,表示该项已经选用(正确)B.当鼠标指向带有黑色箭头符号(?)的菜单选项时,弹出一个子菜单(正确)C.带省略号(…)的菜单选项执行后会打开一个对话框(正确)D.用灰色字符显示的菜单选项表示相应的程序被破坏(不正确)5、在Windows 2000中,“关闭Windows”对话框中包含的选项是重新启动计算机、关闭计算机、将您的计算机转入睡眠状态,不包含的选项是注销。
一、选择题1.Windows2000是一个_____。
A)多用户多任务操作系统B)单用户单任务操作系统C)单用户多任务操作系统D)多用户分时操作系统2.Windows2000操作系统是一个真正 32位系统,它能对_____内存实施动态管理。
(A)32MB()64MB(C)1GB(D)4GB3.下列叙述中,正确的一条是?(A)“开始”菜单只能用鼠标单击“开始”按钮才能打开(B)Windows的任务栏的大小是不能改变的(C)“开始”菜单是系统生成的,用户不能再设置它(D)Wndows的任务栏可以放在桌面的四个边的任意边上4.Wndows2000支持长文件名,一个文件名的最大长度可达_____个字符。
(A)225 B)256(C)255(D)1285.下列文件名中,____是非法的Windows2000文件名。
(A)This is my file (B)关于改进服务的报告(C)*帮助信息* (D)studsat,dbf6.“开始”菜单中的“文档”选项中列出了最近使用过的文档清单,其数目最多可达___个。
(A)4 (B)15(C)10 ()127.按组合键____可以打开“开始”菜单。
(A)<Ctrl>+<O> (B)<Ctrl>+<Esc>(C) <Ctrl>十<空格键> (D)<Ctrl>+<Tab>8.在“资源管理器”的文件夹内容窗口中,如果需要选定多个非连续排列的文件,应按组合键____。
(A)<Ctrl>十单击要选定的文件对象(B)<Alt>十单击要选定的文件对象(C)<Shift>十单击要选定的文件对象(D)<Ctrl>十双击要选定的文件对象9.若Windows2000的菜单命令后面有省略号(...),就表示系统在执行此菜单命令时需要通过___询问用户,获取更多的信息。
(A)窗口 (B)文件 (C)对话框 (D)控制面板10.在Windows2000中,下列不能用“资源管理器”对选定的文件或文件夹进行更名操作的是____。
win10如何切换输入法Win10如何切换输入法在使用计算机时,输入法是我们与电脑之间进行文字输入的主要方式之一。
而对于使用Windows 10操作系统的用户来说,切换输入法是一个常见的需求。
本文将介绍在Win10系统中如何快速、方便地切换输入法。
Windows 10作为微软公司最新的操作系统,提供了多种输入法选择,以满足不同地区和使用者的需求。
常见的输入法包括中文输入法、英文输入法、日语输入法等。
用户可以根据自己的需要随时切换输入法。
在Win10系统中,切换输入法的方法有多种,下面将详细介绍几种常见的切换输入法的方法。
1. 使用快捷键切换输入法Win10系统提供了一组预设的快捷键,可以方便地切换输入法。
默认的快捷键是“Ctrl + Space”,通过按下这个组合键,用户可以在各个输入法之间来回切换。
用户也可以自定义快捷键,具体方法是右键点击任务栏上的输入法图标,选择“设置输入法选项”,然后在弹出的窗口中找到“切换输入法”的选项,点击“高级键位设置”,就可以自定义切换输入法的快捷键了。
2. 利用任务栏切换输入法Win10系统的任务栏上通常会显示当前正在使用的输入法图标。
用户可以直接点击该图标,然后在弹出的菜单中选择需要切换的输入法。
如果任务栏上没有显示输入法图标,可以通过以下步骤进行设置:右键点击任务栏空白处,选择“任务栏设置”,在打开的窗口中找到“选择要在任务栏上显示的项目”的选项,找到并开启“显示语言栏”选项,就可以在任务栏上显示输入法图标了。
3. 使用语言选择器切换输入法Win10系统还提供了一个语言选择器,可以简化切换输入法的操作。
用户可以通过点击任务栏上的语言图标,直接在弹出的菜单中切换输入法。
点击语言图标时,会显示当前使用的输入法的快速切换选项,用户只需点击相应的输入法即可切换。
如果需要切换到其他输入法,可以通过点击“设置语言首选项”打开系统设置界面,然后点击“语言”选项,就可以选择其他输入法了。
505.在启动程序或打开文档时,如果记不清某个文件或文件夹位于何处,则可以使用Windows 2000操作系统提供的( C )功能。
A.设置B.帮助C.查找D.浏览506.在任何时候想得到关于当前打开菜单或对话框处内容的帮助信息,可( A )。
A.按F1键B.按F2键C.借助菜单帮助D.单击工具栏帮助按钮507.在使用键盘操作时,可以同时按下( D )键和菜单项中带下划线的字母来选取某个菜单项。
A.InsB.DelC.CtrlD.Alt508.关闭一个应用程序窗口后,该应用程序将( B )。
A.被暂停执行B.被终止执行C.继续执行D.转入后台执行509.将一个项目从“Windows资源管理器”窗口拖到“开始”菜单,是将( A )。
A.该项目放到“开始”菜单中B.该项目从所在的文件夹放到StartMenu文件夹中C.该项目的一个副本(快捷方式)放入“开始”菜单D.该项目从所在的文件夹复制到StartMenu文件夹中510.用剪贴板复制文件,一般适用于(D )情况。
A.距离短,复制一次B.距离长,复制一次C.距离短,复制多次D.距离长,复制多次511.用键盘打开菜单项,必须按住( B),再按菜单项括号中的字母即可。
A.CtrlB.AltC.ShiftD.Tab512.用键盘对文档中的图片进行剪切操作,应先按住( A)键。
A.CtrlB.AltC.ShiftD.Del513.若将文本从一个地方移动到另一个地方,需要先执行( B )。
A.复制命令B.剪切命令C.删除命令D.粘贴命令514.使用Windows 2000“资源管理器”工具栏上的(D )按钮会将操作对象删除,并存放到剪切板上。
A.删除B.复制C.粘贴D.剪切515.使用键盘选择文本,只要按( C )键同时进行光标定位操作就行了。
A.AltB.CtrlC.ShiftD.Ctrl+Alt516.下列操作中,( D )不能启动一个应用程序。
A.用“开始”菜单中的“运行”命令B.用鼠标左键双击查找到的文件名C.用“开始”菜单中的“文档”命令D.用鼠标右键单击“任务栏”中该程序的图标517.要显示以前打开过的文档清单,应该选择“系统”菜单中的(C )选项。
前言:由于微软对中国产品不付责任的态度,使得安装了终端服务和全拼的w2000 服务器存在着远程登陆并能获取超级用户权限的严重漏洞。
3389端口简介:WIN2000中文简体版存在的输入法漏洞,可以使本地用户绕过身份验证机制而进入系统内部。
经实验,WIN2000中文简体版的终端服务,在远程操作时仍然存在这一漏洞,而且危害更大。
WIN2000的终端服务功能,能使系统管理员对WIN2000进行远程操作,采用的是图形界面,用户在远程控制计算机时其功能与在本地使用一样,其默认端口为3389,用户只要装了WIN2000的客户端连接管理器就能与开启了该服务的计算机相联。
因此这一漏洞使终端服务成为WIN2000的合法木马。
这次我们要用到的工具有:SUPERSCAN2.03 WIN2000终端服务客户端。
推荐使用流光4.6下面我们来看看是怎么入侵的。
第一步:我们现在要做的是找到一台存在3389端口漏洞的主机,这是我们可爱的SUPERSCAN上场的时候了,如果您对SUPERSCAN不太熟悉的话,你按照上图的配置就行了,在开始处填上你要扫描的IP地址段,结束处填上结束的IP地址段,如果你的网速和机器配置并不高的话,请不要扫描多个IP地址段。
第二步:假设我们现在已经得到一台存在3389漏洞的主机,现在我们要做的是进入到他的计算机中。
这里我们就要用到上面所说到的终端客户端连接器了连接了。
如下图所示:我们需要在服务器处填上我们刚才扫描得到的3389端口主机,屏幕区域:640*480 800*600 1024*768……这个是你连接上去以后在你机器上显示的分辨率,建议为:800*600。
在启用数据压缩处打上勾。
其他的就不用调整了!就使用默认了!点连接,我们就可以看到熟悉的WIN2000登陆界面了!第三步:我们要做的是利用输入法漏洞为我们创建一个用户,并加入到administrators中去,或者将guset激活。
建议激活guset,因为这样不容易被网管发现。
关于Win系统如何启用IME输入法1. 简介IME(Input Method Editor,输入法编辑器)是Windows系统中的一种输入法工具,可以通过它实现多种不同语言的输入。
IME输入法系统支持中文、日文、韩文等多种语言输入,使得用户能够更加方便地在Windows系统上进行文字输入。
本文将介绍在Win系统中如何启用IME输入法,以及如何切换和管理不同的语言输入设置。
2. 启用IME输入法方法在Win系统中,启用IME输入法非常简单,以下是几种常见的方法:2.1 控制面板启用IME输入法•步骤1:点击Windows左下角的“开始”按钮,选择“控制面板”。
•步骤2:在控制面板中,选择“时钟、语言和区域”。
•步骤3:在下一级菜单中,选择“区域和语言”。
•步骤4:在“区域和语言”窗口中,选择“键盘和语言”。
•步骤5:在“键盘和语言”选项卡下,点击“更改键盘”。
•步骤6:在“文本服务和输入语言”窗口中,点击“添加”。
•步骤7:选择你需要启用的IME输入法,点击“确定”。
•步骤8:在“键盘和语言”选项卡下,点击“应用”和“确定”按钮,保存设置。
2.2 使用快捷键切换IME输入法在Win系统中,还可以使用快捷键方便地切换IME输入法。
以下是常见的快捷键:•Alt + Shift:切换不同的IME输入法。
•Ctrl + Shift:在不同的IME输入法之间切换。
3. 管理语言输入设置在Win系统中,你可以通过管理语言输入设置,对已启用的IME输入法进行切换和管理。
3.1 切换输入法•步骤1:在开始菜单右下方的任务栏上,找到你当前使用的IME输入法。
•步骤2:点击输入法图标,弹出的下拉菜单中选择你想切换的IME 输入法。
3.2 管理输入法•步骤1:打开“设置”(可以通过Win键 + I快捷键快速打开)。
•步骤2:在“设置”窗口中,选择“时间和语言”。
•步骤3:在“时间和语言”选项卡下,选择“区域与语言”。
•步骤4:在“区域与语言”界面中,找到已添加的IME输入法,点击“选项”按钮。