精华内容
下载资源
问答
  • 6410之次设备的作用

    2013-07-17 11:00:17
    次设备的作用是什么呢? 举个例子: 假如有两个串口,它们共用个设备驱动(主设备号),当具体的操作的时候,如何判断操作的是哪个串口呢?这就使用到了次设备号。 下面给出相关次设备号使用的相关代码: ...

    一个设备节点的属性有:

    设备类型,读写权限,主设备号,次设备号等等。次设备号的作用是什么呢?

    举个例子:

    假如有两个串口,它们共用一个设备驱动(主设备号),当具体的操作的时候,如何判断操作的是哪一个串口呢?这就使用到了次设备号。


    下面给出相关次设备号使用的相关代码:

    功能如下:

    1.次设备号为0的设备节点功能为操作所有的LED灯

    2.测设备号为1,2,3,4分别代表各个单独的LED灯,可以对他们单独进行操作。


    代码如下:

    #include <linux/types.h>
    #include <linux/errno.h>
    #include <linux/kernel.h>
    #include <linux/slab.h>
    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/mm.h>
    #include <linux/sched.h>
    #include <linux/smp_lock.h>
    #include <linux/adb.h>
    #include <linux/cuda.h>
    #include <linux/pmu.h>
    #include <linux/notifier.h>
    #include <linux/wait.h>
    #include <linux/init.h>
    #include <linux/delay.h>
    #include <linux/spinlock.h>
    #include <linux/completion.h>
    #include <linux/device.h>
    #include <linux/kthread.h>
    #include <linux/platform_device.h>
    #include <linux/mutex.h>
    #include <asm/io.h>
    #include <asm/uaccess.h>
    
    
    
    struct class *first_dev_class;
    //struct device *first_dev;
    struct device *led_devs[5];
    
    volatile unsigned long *gpmcon = NULL;
    volatile unsigned long *gpmdat = NULL;
    int major;
    int minor;
    
    static int first_drv_open(struct inode *inode, struct file *file)
    {
    	printk("first_drv_open!!\n");
    
    	minor = MINOR(inode->i_rdev);
    
    	switch(minor){
    		case 0:
    		{	//操作leds
    			printk("first_drv_open : minor == 0 !!\n");
    			*gpmcon &= ~(0x3 | 0x3 << 4 | 0x3 << 8 | 0x3 << 12);
    			*gpmcon |= (0x1 | 0x1 << 4 | 0x1 << 8 | 0x1 << 12);
    			break;
    		}
    		
    		case 1:
    		{ 	//操作led1
    			printk("first_drv_open : minor == 1 !!\n");
    			*gpmcon &= ~(0x3 | 0x3 << 4 | 0x3 << 8 | 0x3 << 12);
    			*gpmcon |= 0x1;
    			break;
    		}
    		
    		
    		case 2:
    		{
    			*gpmcon &= ~(0x3 | 0x3 << 4 | 0x3 << 8 | 0x3 << 12);
    			*gpmcon |= 0x1 << 4;
    			break;
    		}
    		case 3:
    		{
    			*gpmcon &= ~(0x3 | 0x3 << 4 | 0x3 << 8 | 0x3 << 12);
    			*gpmcon |= 0x1 << 8;
    			break;
    		}
    		case 4:
    		{
    			*gpmcon &= ~(0x3 | 0x3 << 4 | 0x3 << 8 | 0x3 << 12);
    			*gpmcon |= 0x1 << 12;
    			break;
    		}
    		
    	}
    	
    	
    	return 0;
    }
    
    static int first_drv_write(struct file *filp, char __user *buff, 
                                             size_t count, loff_t *offp)
    {
    	printk("first_drv_write!!\n");
    	int val ;
    	minor = MINOR(filp->f_dentry->d_inode->i_rdev);
    
    	copy_from_user(&val,buff,count);
    	switch(minor){
    		case 0:
    			{
    				printk("first_drv_write minor == 0  !!\n");
    				if(val == 1){
    					//点灯
    					*gpmdat &= ~(1 << 0 | 1 << 1 | 1 << 2 | 1 << 3);
    				}else if(val == 0){
    					//灭灯
    					*gpmdat |= (1 << 0 | 1 << 1 | 1 << 2 | 1 << 3);
    				}
    				break;
    			}
    		
    		case 1:
    			{
    				printk("first_drv_write minor == 1 !!\n");
    				if(val == 1){
    					//点灯
    					*gpmdat &= ~(1 << 0);
    				}else if(val == 0){
    					//灭灯
    					*gpmdat |= (1 << 0);
    				}
    				break;			
    			}
    		
    		
    		case 2:
    			{
    				if(val == 1){
    					//点灯
    					*gpmdat &= ~(1 << 1);
    				}else if(val == 0){
    					//灭灯
    					*gpmdat |= (1 << 1);
    				}
    				break;
    			}
    		case 3:
    			{
    				if(val == 1){
    					//点灯
    					*gpmdat &= ~(1 << 2);
    				}else if(val == 0){
    					//灭灯
    					*gpmdat |= (1 << 2);
    				}
    				break;
    			}
    		case 4:
    			{
    				if(val == 1){
    					//点灯
    					*gpmdat &= ~(1 << 3);
    				}else if(val == 0){
    					//灭灯
    					*gpmdat |= (1 << 3);
    				}
    				break;
    			}
    		}
    	
    	return 0;
    }
    
    static struct file_operations first_drv_fops = {
        .owner   =   THIS_MODULE,    
        .open    =   first_drv_open,
        .write    =  first_drv_write,
    };
    
    
    static int __init first_drv_init(void)
    {
    	printk("first_drv_init\n");
    
    	major = register_chrdev(0,"first_drv",&first_drv_fops);
    	first_dev_class = class_create(THIS_MODULE, "first_drv_class");
    //	first_dev= device_create(first_dev_class, NULL, MKDEV(major,0), NULL, "xyz");
    	led_devs[0] = device_create(first_dev_class, NULL, MKDEV(major,0),NULL,"leds");
    	for(minor = 1 ; minor < 5 ; minor++){
    		led_devs[minor] = device_create(first_dev_class, NULL, MKDEV(major,minor),NULL,"led%d",minor);
    	}
    	gpmcon = (volatile unsigned long *)ioremap(0x7F008820, 16);
    	gpmdat = gpmcon + 1;
    
    //	register_chrdev(111,"first_drv",&first_drv_fops);
    	return 0;
    }
    
    static void __exit first_drv_exit(void)
    {
    	printk("first_drv_exit\n");
    	
    	for(minor = 0 ; minor < 5 ; minor++){
    		device_destroy(first_dev_class, MKDEV(major,minor));
    	}
    //	device_destroy(first_dev_class, MKDEV(major,0));
    	class_destroy(first_dev_class);
    	unregister_chrdev(major,"first_drv");
    
    	iounmap(gpmcon);
    }
    
    module_init(first_drv_init);
    module_exit(first_drv_exit);
    
    MODULE_LICENSE("GPL"); 

    测试程序:

    #include <stdio.h>
    #include <fcntl.h>
    
    
    int main(int argc ,char **argv)
    {
    	int fd ;
    	int val;
    	char *filename = argv[1];
    	fd = open(filename,O_RDWR);
    	if(fd < 0){
    		printf("Can not open devices\n");
    		return -1;
    	}
    	if(argc != 3){
    		printf("Usage : %s <leds/led1/led2/led3/led4> <on/off> \n",argv[0]);
    		return 0;
    	}
    	if(strcmp(argv[2],"on") == 0){
    		val = 1;
    	}else{
    		val = 0;
    	}
    
    	write(fd,&val,4);
    	return 0;
    }
    

    从上面的驱动程序可以看到,在open,read,write函数中获取次设备号的方法为:

    open函数: 通过MINOR(inode->i_rdev);获取
    read,write函数: 通过MINOR(filp->f_dentry->d_inode->i_rdev);获取

    展开全文
  • 一次设备进行控制,保护作用的设备叫做二次设备,如继电器,控制开关,指示灯,测量仪表等。 另解: 一次设备通常包含以下五类: (1)、能量转换设备(如发电机、变压器、电动机等) (2)、开关设备(断路器...
    完成发电-输电-配电功能的设备叫做一次设备,如发电机,断路器,电流电压互感器,变压器,避雷器等;
    对一次设备进行控制,保护作用的设备叫做二次设备,如继电器,控制开关,指示灯,测量仪表等。
    另解:
    一次设备通常包含以下五类:
    (1)、能量转换设备(如发电机、变压器、电动机等)
    (2)、开关设备(断路器、熔断器、负荷开关、隔离开关等)
    (3)、载流导体(母线、绝缘子和电缆等)
    (4)、互感器(电压、电流等互感器)
    (5)、电抗器和避雷器(电抗器主要用于限制电路中的短路电流;避雷器则用于限制电气设备的过电压)
    二次设备:对电气一次设备的工作状态进行监测、控制和保护的辅助性电气设备称为二次设备。例如各种电气仪表、继电器、自动控制设备、信号电缆和控制电缆等。
    
    展开全文
  • 设备的作用

    2017-04-14 19:39:57
    在前一节中,我讲述了当WDM驱动程序被第一次装入时如何初始化。通常,一个驱动程序可以被多个设备利用。WDM驱动程序有一个特殊AddDevice函数,PnP管理器为每个设备实例调用该函数。该函数原型如下: ...

    在前一节中,我讲述了当WDM驱动程序被第一次装入时如何初始化。通常,一个驱动程序可以被多个设备利用。WDM驱动程序有一个特殊的AddDevice函数,PnP管理器为每个设备实例调用该函数。该函数的原型如下:

    NTSTATUSAddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo)
    {
    }

    DriverObject参数指向一个驱动程序对象,就是你在DriverEntry例程中初始化的那个驱动程序对象。pdo参数指向设备堆栈底部的物理设备对象。

    对于功能驱动程序,其AddDevice函数的基本职责是创建一个设备对象并把它连接到以pdo为底的设备堆栈中。相关步骤如下:

    调用IoCreateDevice创建设备对象,并建立一个私有的设备扩展对象。

    寄存一个或多个设备接口,以便应用程序能知道设备的存在。另外,还可以给出设备名并创建符号连接。

    初始化设备扩展和设备对象的Flag成员。

    调用IoAttachDeviceToDeviceStack函数把新设备对象放到堆栈上。

    下面我将详细解释这些步骤。

    创建设备对象

    调用IoCreateDevice函数创建设备对象,例如:

    PDEVICE_OBJECT fdo;
    NTSTATUS status = IoCreateDevice(DriverObject,
    sizeof(DEVICE_EXTENSION),
    NULL,
    FILE_DEVICE_UNKNOWN,
    FILE_DEVICE_SECURE_OPEN,
    FALSE,
    &fdo);

    第一个参数(DriverObject) 就是AddDevice的第一个参数。该参数用于在驱动程序和新设备对象之间建立连接,这样I/O管理器就可以向设备发送指定的IRP。

    第二个参数是设备扩展结构的大小。正如我在本章前面讲到的,I/O管理器自动分配这个内存,并把设备对象(DEVICE_OBJECT)中的DeviceExtension指针指向这块内存。

    C++ CopyCode 

    typedef struct _DEVICE_OBJECT{

      CSHORT                  Type;

      USHORT                  Size;

      LONG                   ReferenceCount;

      struct_DRIVER_OBJECT  *DriverObject;

      struct_DEVICE_OBJECT  *NextDevice;

      struct_DEVICE_OBJECT  *AttachedDevice;

      struct _IRP *CurrentIrp;

      PIO_TIMER               Timer;

      ULONG                   Flags;

      ULONG                   Characteristics;

      __volatilePVPB           Vpb;

      PVOID                  DeviceExtension;

      DEVICE_TYPE              DeviceType;

      CCHAR                   StackSize;

      union{

       LIST_ENTRY        ListEntry;

       WAIT_CONTEXT_BLOCK Wcb;

      }Queue;

      ULONG                  AlignmentRequirement;

      KDEVICE_QUEUE            DeviceQueue;

      KDPC                   Dpc;

      ULONG                   ActiveThreadCount;

     PSECURITY_DESCRIPTOR       SecurityDescriptor;

      KEVENT                  DeviceLock;

      USHORT                  SectorSize;

      USHORT                  Spare1;

      struct_DEVOBJ_EXTENSION  * DeviceObjectExtension;

      PVOID                   Reserved;

    } DEVICE_OBJECT,*PDEVICE_OBJECT;

     

    第三个参数在本例中为NULL。它可以是命名该设备对象的UNICODE_STRING串的地址。决定是否命名设备对象以及以什么名字命名还需要仔细考虑,我将在本节后面深入讨论这个问题。

    第四个参数(FILE_DEVICE_UNKNOWN) 是表中列出的设备类型。这个值可以被设备硬件键或类键中的超越值所替代,如果这两个键都含有该参数的超越值,那么硬件键中的超越值具有更高的优先权。对于属于某个已存在类的设备,必须在这些地方指定正确的值,因为驱动程序与外围系统的交互需要依靠这个值。另外,设备对象的默认安全设置也依靠这个设备类型值。

    #define FILE_DEVICE_8042_PORT 0x00000027
    #define FILE_DEVICE_ACPI 0x00000032
    #define FILE_DEVICE_BATTERY 0x00000029
    #define FILE_DEVICE_BEEP 0x00000001
    #define FILE_DEVICE_BUS_EXTENDER 0x0000002a
    #define FILE_DEVICE_CD_ROM 0x00000002
    #define FILE_DEVICE_CD_ROM_FILE_SYSTEM 0x00000003
    #define FILE_DEVICE_CHANGER 0x00000030
    #define FILE_DEVICE_CONTROLLER 0x00000004
    #define FILE_DEVICE_DATALINK 0x00000005
    #define FILE_DEVICE_DFS 0x00000006
    #define FILE_DEVICE_DFS_FILE_SYSTEM 0x00000035
    #define FILE_DEVICE_DFS_VOLUME 0x00000036
    #define FILE_DEVICE_DISK 0x00000007
    #define FILE_DEVICE_DISK_FILE_SYSTEM 0x00000008
    #define FILE_DEVICE_DVD 0x00000033
    #define FILE_DEVICE_FILE_SYSTEM 0x00000009
    #define FILE_DEVICE_FIPS 0x0000003a
    #define FILE_DEVICE_FULLSCREEN_VIDEO 0x00000034
    #define FILE_DEVICE_INPORT_PORT 0x0000000a
    #define FILE_DEVICE_KEYBOARD 0x0000000b
    #define FILE_DEVICE_KS 0x0000002f
    #define FILE_DEVICE_KSEC 0x00000039
    #define FILE_DEVICE_MAILSLOT 0x0000000c
    #define FILE_DEVICE_MASS_STORAGE 0x0000002d
    #define FILE_DEVICE_MIDI_IN 0x0000000d
    #define FILE_DEVICE_MIDI_OUT 0x0000000e
    #define FILE_DEVICE_MODEM 0x0000002b
    #define FILE_DEVICE_MOUSE 0x0000000f
    #define FILE_DEVICE_MULTI_UNC_PROVIDER 0x00000010
    #define FILE_DEVICE_NAMED_PIPE 0x00000011
    #define FILE_DEVICE_NETWORK 0x00000012
    #define FILE_DEVICE_NETWORK_BROWSER 0x00000013
    #define FILE_DEVICE_NETWORK_FILE_SYSTEM 0x00000014
    #define FILE_DEVICE_NETWORK_REDIRECTOR 0x00000028
    #define FILE_DEVICE_NULL 0x00000015
    #define FILE_DEVICE_PARALLEL_PORT 0x00000016
    #define FILE_DEVICE_PHYSICAL_NETCARD 0x00000017
    #define FILE_DEVICE_PRINTER 0x00000018
    #define FILE_DEVICE_SCANNER 0x00000019
    #define FILE_DEVICE_SCREEN 0x0000001c
    #define FILE_DEVICE_SERENUM 0x00000037
    #define FILE_DEVICE_SERIAL_MOUSE_PORT 0x0000001a
    #define FILE_DEVICE_SERIAL_PORT 0x0000001b
    #define FILE_DEVICE_SMARTCARD 0x00000031
    #define FILE_DEVICE_SMB 0x0000002e
    #define FILE_DEVICE_SOUND 0x0000001d
    #define FILE_DEVICE_STREAMS 0x0000001e
    #define FILE_DEVICE_TAPE 0x0000001f
    #define FILE_DEVICE_TAPE_FILE_SYSTEM 0x00000020
    #define FILE_DEVICE_TERMSRV 0x00000038
    #define FILE_DEVICE_TRANSPORT 0x00000021
    #define FILE_DEVICE_UNKNOWN 0x00000022
    #defineFILE_DEVICE_VDM 0x0000002c
    #define FILE_DEVICE_VIDEO 0x00000023
    #define FILE_DEVICE_VIRTUAL_DISK 0x00000024
    #define FILE_DEVICE_WAVE_IN 0x00000025
    #define FILE_DEVICE_WAVE_OUT 0x00000026

    第五个参数(FILE_DEVICE_SECURE_OPEN) 为设备对象提供Characteristics标志。这些标志主要关系到块存储设备(如软盘、CDROM、Jaz等等)。未公开标志位FILE_AUTOGENERATED_DEVICE_NAME仅用于内部使用,并不是DDK文档忘记提到该标志。这个参数同样也能被硬件键或类键中的对应值超越,如果两个值都存在,那么硬件键中的超越值具有更高的优先权。

    FILE_DEVICE_SECURE_OPEN

    FILE_FLOPPY_DISKETTE

    FILE_READ_ONLY_DEVICE

    FILE_REMOVABLE_MEDIA

    FILE_WRITE_ONCE_MEDIA

    第六个参数(FALSE) 指出设备是否是排斥的。通常,对于排斥设备,I/O管理器仅允许打开该设备的一个句柄。这个值同样也能被注册表中硬件键和类键中的值超越,如果两个超越值都存在,硬件键中的超越值具有更高的优先权。

    注意


    排斥属性仅关系到打开请求的目标是命名设备对象。如果你遵守Microsoft推荐的WDM驱动程序设计方针,没有为设备对象命名,那么打开请求将直接指向PDO。PDO通常不能被标记为排斥,因为总线驱动程序没有办法知道设备是否需要排斥特征。把PDO标为排斥的唯一的机会在注册表中,即设备硬件键或 类键的Properties子键含有Exclusive超越值。为了完全避免依赖排斥属性,你应该利 用IRP_MJ_CREAT例程弹出任何有违规行为的打开请求。

    第七个参数(&fdo) 是存放设备对象指针的地址,IoCreateDevice函数使用该变量保存刚创建设备对象的地址。

    如果IoCreateDevice由于某种原因失败,则它返回一个错误代码,不改变fdo中的值。如果IoCreateDevice函数返回成功代码,那么它同时也设置了fdo指针。然后我们进行到下一步,初始化设备扩展(DeviceExtension),做与创建新设备对象相关的其它工作,如果在这之后又发现了错误,那么在返回前应先释放刚创建的设备对象并返回状态码。见下面例子代码:

    NTSTATUS status =IoCreateDevice(...);
    if (!NT_SUCCESS(status))
    return status;
    ...
    if (<some other errordiscovered>)
    {
    IoDeleteDevice(fdo);
    return status;
    }

    NTSTATUS状态代码和NT_SUCCESS宏的解释见下一章。

    为设备命名

    WindowsNT使用对象管理器集中管理大量的内部数据结构,包括我们讨论过的驱动程序对象和设备对象。David Solomon在《Inside Windows NT, SecondEdition (Microsoft Press, 1998)》的第三章“System Mechanisms”中给出了关于Windows NT对象管理器和命名空间的一个比较完整的阐述。对象都有名称,对象管理器用一个层次化的命名空间来管理这些名称。图2-16是DevView显示的顶层对象名。图中以文件夹形式显示的对象是目录对象,它可以包含子目录或常规对象,其它图标则代表正常对象。(从这一点上看,DevView与平台SDK中的WINOBJ工具相类似,但WINOBJ不能给出设备对象和驱动程序的相关信息)

    图2-16. 用DevView观察命名空间

    通常设备对象都把自己的名字放到\Device目录中。在Windows 2000中,设备的名称有两个用途。

    第一个用途,设备命名后,其它内核模式部件可以通过调用IoGetDeviceObjectPointer函数找到该设备,找到设备对象后,就可以向该设备的驱动程序发送IRP。

    第二个用途,允许应用程序打开命名设备的句柄,这样它们就可以向驱动程序发送IRP。应用程序可以使用标准的CreateFile API打开命名设备句柄,然后用ReadFile、WriteFile,和DeviceIoControl向驱动程序发出请求。应用程序打开设备句柄时使用\\.\路径前缀而不是标准的UNC(统一命名约定)名称,如C:\MYFILE.CPP或\\FRED\C-Drive\HISFILE.CPP。在内部,I/O管理器在执行名称搜索前自动把\\.\转换成\??\。为了把\??目录中的名字与名字在其它目录(例如,在\Device目录)中的对象相连接,对象管理器实现了一种称为符号连接(symbolic link)的对象。

    符号连接

    符号链接又叫软链接,是一类特殊的文件,这个文件包含了另一个文件的路径名(绝对路径或者相对路径)。路径可以是任意文件或目录,可以链接不同文件系统的文件。(链接文件可以链接不存在的文件,这就产生一般称之为”断链”的现象),链接文件甚至可以循环链接自己(类似于编程中的递归)。在对符号文件进行读或写操作的时候,系统会自动把该操作转换为对源文件的操作,但删除链接文件时,系统仅仅删除链接文件,而不删除源文件本身。

    快捷方式(shortcut)是一种功能上类似符号链接的文件对象,但与符号链接有本质的不同。快捷方式是普通的文件(拥有扩展名 .lnk),而非符号,因此,快捷方式可以被复制、移动、更改(某些特殊的快捷方式无法更改所有信息)或删除。快捷方式可以指向文件、文件夹或其他任何系统中合法的位置 (包括控制面板桌面等)。

      快捷方式如果快捷方式指向可执行程序,则可以同时指定启动的命令行参数以及启动位置(对于非可执行程序的快捷方式也能指定这些信息,但无意义)。同时,可以为快捷方式单独选择图标(如果没有选择图标,则使用目标的图标),以方便用户个性化。  删除快捷方式完全不会影响被链接到的目标。如果删除了目标,则会使快捷方式失效。由于快捷方式可以使用相对路径或绝对路径,因此移动快捷方式或目标可能导致快捷方式失效,也可能不会有影响。在某些Windows操作系统(包括但不限于Windows XP、Windows Vista)中,操作系统会对文件路径的改变作一定程度的自动处理,使得快捷方式将在大多数情况下能保持有效:当快捷方式指向的目标失效而目标被移动过时,系统将自动修改快捷方式的目标到新的位置。

    符号连接有点象桌面上的快捷方式,符号连接在Windows NT中的主要用途是把处于列表前面的DOS形式的名称连接到设备上。图2-17显示了\??目录的部分内容,这里就有一些符号名,例如,“C:”和其它一 些用DOS命名方案命名的驱动器名称,它们被连接到\Device目录中,而这些设备对象的真正名称就放在\Device目录中。符号连接可以使对象管理 器在分析一个名称时能跳到命名空间的某个地方。例如,如果我用CreateFile打开名称为“C:\MYFILE.CPP”的对象,对象管理器将以下面 过程打开该文件:

    内核模式代码最开始看到的名称是\??\C:\MYFILE.CPP。对象管理器在根目录中查找“??”。

    找到\??目录后,对象管理器在其中查找“C:”。它发现找到的对象是一个符号连接,所以它就用这个符号连接组成一个新的内核模式路径名:\Device\HarddiskVolume1\MYFILE.CPP,然后析取它。

    使用新路径名后,对象管理器重新在根目录中查找“Device”。

    找到\Device目录后,对象管理器在其中查找“HarddiskVolume1”,最后它找到一个以该名字命名的设备。

    图2-17. \??目录和部分符号连接

    现在,对象管理器要创建一个IRP,然后把它发到HarddiskVolume1设备的驱动程序。该IRP最终将使某个文件系统驱动程序或其它驱动 程序定位并打开一个磁盘文件。描述文件系统驱动程序的工作过程已经超出了本书的范围。如果我们使用设备名COM1,那么最终收到该IRP的将是\Device\Serial0的驱动程序。

    用户模式程序可以调用DefineDosDevice创建一个符号连接,如下例:

    BOOL okay =DefineDosDevice(DDD_RAW_TARGET_PATH, "barf","\\Device\\SECTEST_0");

    图2-17中显示了上面调用的结果。

    如果你需要在WDM驱动程序中创建一个符号连接,可以调用IoCreateSymbolicLink函数:

    IoCreateSymbolicLink(linkname, targname);

    linkname是要创建的符号连接名,targname是要连接的名字。顺便说一下,对象管理器并不关心targname是否是已存在对象的名字,如果连接到一个未定义的符号名,那么访问该符号连接将简单地收到一个错误。如果你想允许用户模式程序能超越这个连接而转到其它地方,应使用IoCreateUnprotectedSymbolicLink函数替代上面函数。

    ARC名字

    在ARC(Advanced RISC Computing)架构的计算机中,Windows 2000需要依赖一个称为ARC命名的概念。你可以在BOOT.INI文件中看到使用中的ARC名字,BOOT.INI文件位于引导驱动器的根目录。下面是该文件的一个例子:

    [boot loader]
    timeout=30
    default=c:\
    [operating systems]
    C:\="Microsoft Windows 98"
    scsi(0)disk(1)rdisk(0)partition(1)\BETA2F="Win2k Beta-2 (FreeBuild)" /fastdetect /noguiboot
    scsi(0)disk(1)rdisk(0)partition(1)\WINNT ="Win2K Beta-3 (FreeBuild)" /fastdetect /noguiboot

    在Intel平台上,象scsi(0)disk(1)rdisk(0)partition(1)的ARC名其实是符号连接,它们存在于内核的\ArcName目录中。

    除了硬盘之外的块存储设备,它们的驱动程序在初始化时需要调用IoAssignArcName建立一个这样的符号连接。由于系统引导的需要,I/O管理器自动为硬盘设备建立ARC名。

    应该命名设备对象吗?

    决定为设备对象命名之前,你应该多想一想。如果命名了设备对象,那么任何内核模式程序都可以打开该设备的句柄。另外,任何内核模式或用户模式程序都能创建连接到该设备的符号连接,并可以使用这个符号连接打开设备的句柄。你可能允许也可能不允许这种事情发生。

    是否命名设备对象的主要考虑是安全问题。当有人打开一个命名对象的句柄时,对象管理器将检查他是否有权这样做。当IoCreateDevice为你 创建设备对象时,它也为设备对象设置了一个默认安全描述符(基于第四个参数中的设备类型)。下面是三个基本分类,I/O管理器基于这些分类来选择安全描述 符。(参考表2-4中的第二列)

    大部分文件系统设备对象(磁盘、CD-ROM、文件、磁带)将得到“public default unrestricted”ACL(访问控制表)。该表对系统(SYSTEM)和管理员(administrator)之外的所有账户给予了 SYNCHRONIZE、READ_CONTROL、FILE_READ_ATTRIBUTES、FILE_TRAVERSE访问权限。顺便说一下,文件 系统设备对象就是作为CreateFile函数的目标而存在,CreateFile函数将打开一个由文件系统管理的文件。

    磁盘设备和网络文件系统对象将得到与文件系统对象相同的ACL,但做了一些修改。例如,任何人对命名软磁盘设备对象都有全部访问权,管理员有足够的权限运行ScanDisk。(用户模式的网络支持DLL需要更大的权限来访问其对应文件系统驱动程序的设备对象,这就是网络文件系统需要与其它文件系统区别对待的原因)

    所有其它的设备对象将得到“public openunrestricted”ACL,它允许任何有设备句柄的人不受限制地使用该设备。

    可以看出,如果非磁盘设备的驱动程序在调用IoCreateDevice时给出设备对象名,那么任何人都可以读写这个设备,因为默认安全设置几乎允许用户有全部的访问权限,而且在创建符号连接时根本不进行安全检查。安全检查仅发生在对设备的打开操作上,基于命名对象的安全描述符。这对于在同一堆栈中的有更严格安全限制的其它设备对象也是这样。

    DevView可以显示设备对象的安全属性。你可以通过测试一个文件系统、一个磁盘设备、或者任何其它随机存取设备了解到我刚描述过的默认操作规则。

    PDO也得到一个默认安全描述符,但这个安全描述符可能被存储在硬件键或类键的Properties子键中的安全描述符超越(当两者都存在时,硬件键中的超越值有更高的优先权)。即使没有指定安全描述符超越,如果硬件键或类键的Properties子键中有设备类型或特征的超越值,那么I/O管理器 也会基于新类型为对象构造一个新的默认安全描述符。但I/O管理器不会超越PDO上面的任何其它设备对象的安全设置。因此,由于超越的影响,你不应该命名你的设备对象。但不要失望,应用程序仍可以使用注册的接口(interface)访问你的设备。

    关于安全问题的最后一点:当对象管理器析取对象名时,对于名字的中间部分仅需要具有FILE_TRAVERSE访问权,它仅在最终对象名上执行全部 的安全检查。所以,假设某个设备对象可以通过\Device\SECTEST_0名或符号连接\??\SecurityTest_0名到达,那么,如果设 备对象的安全描述符设置为拒绝写,则试图以写方式打开\\.\SecurityTest_0的用户模式应用程序将被阻塞。但如果应用程序试图打开名为\\.\SecurityTest_0\ExtraStuff的对象,那么打开请求(IRP_MJ_CREATE形式)将被允许,而此时用户对\\. \SecurityTest_0\仅有FILE_TRAVERSE权限。I/O管理器希望设备驱动程序自己去处理额外名称部件的安全检查。

    为了避免涉及到我刚描述过的安全问题,你可以在调用IoCreateDevice时指定设备特征参数为FILE_DEVICE_SECURE_OPEN。该标志将使Windows 2000在额外名称部件存在的情况下仍检查调用者是否有权限打开设备句柄。

    设备名称

    如果你决定命名设备对象,通常应该把对象名放在名称空间的\Device分支中。为了命名设备对象,首先应该创建一个UNICODE_STRING结构来存放对象名,然后把该串作为调用IoCreateDevice的参数:

    UNICODE_STRINGdevname;
    RtlInitUnicodeString(&devname,L"\\Device\\Simple0");
    IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION),&devname, ...);

    我将在下一章中讨论RtlInitUnicodeString的用法。

    通常,驱动程序用设备类型串后加上一个以0开始的实例号作为设备对象名(如上面的Simple0)。一般,你不希望象我上面做的那样使用带有硬编码性质的名称。你希望用串操作函数动态地合成一个名字:

    UNICODE_STRINGdevname;
    static LONG lastindex = -1;
    LONG devindex =InterlockedIncrement(&lastindex);
    WCHAR name[32];
    _snwprintf(name, arraysize(name), L"\\Device\\SIMPLE%2.2d",devindex);
    RtlInitUnicodeString(&devname, name);
    IoCreateDevice(...);

    我将在后两章中解释上面代码中出现的服务函数。如上面代码所示,从私有设备类型得出的实例号应该是一个静态变量。

    设备命名的注意事项

    如果仅仅想在开发过程中为应用程序打开设备句柄提供一个快速方法,你应该在\??目录中为设备赋予一个名字。然而,对于一个产品级的驱动程序来说,最好把设备对象名放到\Device目录中。

    \??目录以前叫做\DosDevices。实际上,\DosDevice仍可以使用,但它本身是\??目录的符号连接。这种改变将使经常查找的用 户模式目录名能位于字母排序的目录列表前面。如果你要在命名中使用\??,应该先参考本章“Windows 98兼容问题”节中的注意事项。

    注意,上面提到的把设备对象名放到\??目录中可能不适用于Windows 2000的Terminal Server版本。由于设备对象不能复制到控制台事务的外边,而符号连接可以,因此你应该在\Device目录中保存设备命名,而在 \DosDevices目录中放一个符号连接。

    在以前版本的Windows NT中,某些种类设备(特别是磁盘、磁带、串行口,和并行口)的驱动程序通过调用IoGetConfigurationInformation来 获得一个全局表的指针,该表包含这些类中的设备计数。驱动程序应使用当前计数值来合成设备名称,例如Harddisk0、Tape1,等等,并同时增加该 计数器的值。然而,WDM驱动程序并不需要使用这个服务函数以及它返回的计数器表,为这些类中的设备构造名称现在是Microsoft专有的类驱动程序的 责任,如DISK.SYS。

    设备接口

    用旧的命名方法命名设备对象,并创建一个应用程序能够使用的符号连接,存在着两个主要问题。命名设备对象所带来的潜在安全问题我们已经讨论过。此外,访问设备的应用程序需要先知道设备采用的命名方案。如果你的硬件仅由你的应用程序访问,那么不会有什么问题。但是,如果有其它公司想为你的硬件写应用程序,并且有许多硬件公司想制作相似的设备,那么设计一个合适的命名方案是困难的。最后,许多命名方案将依赖于程序员所说的自然语言,这不是一个好的选择。

    为了解决这些问题,WDM引入了一个新的设备命名方案,该方案是语言中立的、易于扩展的、可用于许多硬件和软件厂商,并且易于文档化。该方案依靠一个设备接口(device interface)的概念,它基本上是软件如何访问硬件的一个说明。一个设备接口被一个128位的GUID唯一标识。你可以用平台SDK中的UUIDGEN工具或者GUIDGEN工具生成GUID,这两个工具输出同一种数,但格式不同。这个想法就象某些工业组织联合起来共同制定某种硬件的标准访问方法一样。在标准制作过程中,产生了一些GUID,这些GUID将永远关联到某些接口上。

    关于GUID

    GUID用于标识软件接口,它与COM(部件对象模型)中用于标识COM接口的标识符相同,它还用于OSF(开放软件基金)的DCE(分布式计算环 境)中,标识RPC(远程过程调用)目标。如果你想了解GUID如何生成以及为什么能在统计意义上唯一,请参考Kraig Brockschmidt的《Inside OLE, Second Edition (MicrosoftPress, 1995)》第66页,原始算法规范由OSF制定,相关部分见http://www.opengroup.org/onlinepubs/9629399/apdxa.htm

    为了在设备驱动程序中使用GUID,首先需要使用UUIDGEN或者GUIDGEN生成GUID,然后把结果放到头文件中。GUIDGEN更易于使用,它允许选择GUID的输出格式,并把结果送到剪贴板。图2-18显示了GUIDGEN的运行窗口。你可以把它的输出粘贴到头文件中:

    //{CAF53C68-A94C-11D2-BB4A-00C04FA330A6}
    DEFINE_GUID(<<name>>, 
    0xCAF53C68, 0xA94C, 0x11D2, 0xBB, 0x4A, 0x00, 0xC0, 0x4F, 0xA3,0x30, 0xA6);

    然后,用有意义的名字换掉<<name>>,如GUID_SIMPLE,并把这个定义包含到驱动程序或应用程序中。

    图2-18. 使用GUIDGEN生成GUID

    我想接口类似于蛋白质合成器,它能制作活细胞的细胞膜。访问特定种类设备的应用程序有自己的蛋白质合成器,它就象一把钥匙,可以插入到所有有匹配合成器的设备驱动程序中。如图2-19。

    图2-19. 用设备接口匹配应用程序和设备

    注册设备接口 调用IoRegisterDeviceInterface函数,功能驱动程序的AddDevice函数可以注册一个或多个设备接口:

    #include<initguid.h><--1
    #include "guids.h" <--2
    ...
    NTSTATUS AddDevice(...)
    {
    ...
    IoRegisterDeviceInterface(pdo, &GUID_SIMPLE, NULL,&pdx->ifname);<--3
    ...
    }

    我们包含了GUIDS.H头文件,那里定义了DEFINE_GUID宏。DEFINE_GUID通常声明一个外部变量。在驱动程序的某些地方,我们不得不为将要引用的每个GUID保留初始化的存储空间。系统头文件INITGUID.H利用某些预编译指令使DEFINE_GUID宏在已经定义的情况下仍能保留该存储空间。

    我使用单独的头文件来保存我要引用的GUID定义。这是一个好的想法,因为用户模式的代码也需要包含这些定义,但它们不需要那些仅与内核模式驱动程序有关的声明。

    IoRegisterDeviceInterface的第一个参数必须是设备PDO的地址。第二个参数指出与接口关联的GUID,第三个参数指出额外的接口细分类名。只有Microsoft的代码才使用名称细分类方案。第四个参数是个UNICODE_STRING串的地址,该串用于接收设备对象的符号连接名。

    IoRegisterDeviceInterface的返回值是一个Unicode串,这样在不知道驱动程序编码的情况下,应用程序能用该串确定并打开设备句柄。顺便说一下,这个名字比较丑陋;后面例子是我在Windows 98中为Sample设备生成的名字:\DosDevices\0000000000000007#{CAF53C68-A94C-11d2-BB4A-00C04FA330A6}。

    注册过程实际就是先创建一个符号连接名,然后再把它存入注册表。之后,当响应PnP请求IRP_MN_START_DEVICE时,驱动程序将调用IoSetDeviceInterfaceState函数“使能”该接口:

    IoSetDeviceInterfaceState(&pdx->ifname,TRUE);

    在响应这个调用过程中,I/O管理器将创建一个指向设备PDO的符号连接对象。以后,驱动程序会执行一个功能相反的调用禁止该接口(用FALSE做参数调用IoSetDeviceInterfaceState)。最后,I/O管理器删除符号连接对象,但它保留了注册表项,即这个名字将总与设备的这个实例关联;但符号连接对象与硬件一同到来或消失。

    因为接口名最终指向PDO,所以PDO的安全描述符将最终控制设备的访问权限。这样比较好,因为只有管理员才可以通过控制台控制PDO的安全属性。

    枚举设备接口 内核模式代码和用户模式代码都能定位含有支持它们感兴趣接口的设备。下面我将解释如何在用户模式中枚举所有含有特定接口的设备。枚举代码写起来十分冗长,最后我不得不写一个C++类来实现。你可以在DEVICELIST.CPP和DEVICELIST.H文件中找到这些代码,这些文件是第八章“电源管理” 中WDMIDLE例子的一部分。它们声明并实现了一个CDeviceList类,该类包含一个CDeviceListEntry对象数组。这两个类声明如下:

    class CDeviceListEntry
    {
    public:
    CDeviceListEntry(LPCTSTR linkname, LPCTSTR friendlyname);
    CDeviceListEntry(){}
    CString m_linkname;
    CString m_friendlyname;
    };

    class CDeviceList
    {
    public:
    CDeviceList(const GUID& guid);
    ~CDeviceList();
    GUID m_guid;
    CArray<CDeviceListEntry,CDeviceListEntry&> m_list;
    int Initialize();
    };

    该类使用了CString类和CArray模板,它们都是MFC的一部分。这两个类的构造函数仅简单地把它们的参数复制到数据成员中:

    CDeviceList::CDeviceList(const GUID&guid)
    {
    m_guid = guid;
    }

    CDeviceListEntry::CDeviceListEntry(LPCTSTR linkname, LPCTSTRfriendlyname)
    {
    m_linkname = linkname;
    m_friendlyname = friendlyname;
    }

    所有实际的工作都发生在CDeviceList::Initialize函数中。其执行过程大致是这样:先枚举所有接口GUID与构造函数得到的GUID相同的设备,然后确定一个“友好”名,我们希望向最终用户显示这个名字。最后返回找到的设备号。下面是这个函数的代码:

    intCDeviceList::Initialize()
    {
    HDEVINFO info = SetupDiGetClassDevs(&m_guid, NULL,NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);<--1
    if (info == INVALID_HANDLE_VALUE)
    return 0;
    SP_INTERFACE_DEVICE_DATA ifdata;
    ifdata.cbSize = sizeof(ifdata);
    DWORD devindex;
    for (devindex = 0; SetupDiEnumDeviceInterfaces(info, NULL,&m_guid, devindex, &ifdata);++devindex) <--2
    {
    DWORD needed;
    SetupDiGetDeviceInterfaceDetail(info, &ifdata,NULL, 0, &needed, NULL); <--3

    PSP_INTERFACE_DEVICE_DETAIL_DATA detail =(PSP_INTERFACE_DEVICE_DETAIL_DATA) malloc(needed);
    detail->cbSize =sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
    SP_DEVINFO_DATA did = {sizeof(SP_DEVINFO_DATA)};
    SetupDiGetDeviceInterfaceDetail(info, &ifdata,detail, needed, NULL, &did));

    TCHAR fname[256]; <--4
    if (!SetupDiGetDeviceRegistryProperty(info,
    &did,
    SPDRP_FRIENDLYNAME,
    NULL,
    (PBYTE) fname,
    sizeof(fname),
    NULL)
    &&!SetupDiGetDeviceRegistryProperty(info,
    &did,
    SPDRP_DEVICEDESC,
    NULL,
    (PBYTE) fname,
    sizeof(fname),
    NULL)
    )
    _tcsncpy(fname, detail->DevicePath, 256);

    CDeviceListEntry e(detail->DevicePath, fname);<--5
    free((PVOID) detail);

    m_list.Add(e);
    }

    SetupDiDestroyDeviceInfoList(info);
    return m_list.GetSize();
    }

    该语句打开一个枚举句柄,我们用它寻找寄存了指定GUID接口的所有设备。

    循环调用SetupDiEnumDeviceInterfaces以寻找每个匹配的设备。

    有两项信息是我们需要的,接口的“细节”信息和设备实例信息。这个“细节”信息就是设备的符号名。因为它的长度可变,所以我们两次调用了SetupDiGetDeviceInterfaceDetail。第一次调用确定了长度,第二次调用获得了名字。

    通过询问注册表中的FriendlyName键或DeviceDesc键,我们获得了设备的“友好”名称。

    我们用设备符号名同时作为连接名和友好名创建了类CDeviceListEntry的一个临时实例e。

     
     

    友好名

    你可能会疑惑,注册表怎么会有设备的FriendlyName名。安装设备驱动程序的INF文件中有一个指定设备参数的HW段,这些参数将被添加到注册表中。通常我们可以在这里为设备提供一个FriendlyName名。

    其它全局性的设备初始化操作

    在AddDevice中还需要加入其它一些步骤来初始化设备对象,下面我将按顺序描述这些步骤。

    初始化设备扩展

    设备扩展的内容和管理全部由用户决定。该结构中的数据成员应直接反映硬件的专有细节以及对设备的编程方式。大多数驱动程序都会在这里放入一些数据项,下面代码声明了一个设备扩展结构:

    typedef struct_DEVICE_EXTENSION { <--1
    PDEVICE_OBJECT DeviceObject; <--2
    PDEVICE_OBJECT LowerDeviceObject; <--3
    PDEVICE_OBJECT Pdo; <--4
    UNICODE_STRING ifname; <--5
    IO_REMOVE_LOCK RemoveLock; <--6
    DEVSTATE devstate; <--7
    DEVSTATE prevstate;
    DEVICE_POWER_STATE devpower;
    SYSTEM_POWER_STATE syspower;
    DEVICE_CAPABILITIES devcaps; <--8
    ...
    } DEVICE_EXTENSION, *PDEVICE_EXTENSION;

    我模仿DDK中官方的结构声明模式声明了这个结构。

    我们可以用设备对象中的DeviceExtension指针定位自己的设备扩展。同样,我们有时也需要在给定设备扩展时能定位设备对象。因为某些函数的逻辑参数就是设备扩展本身(这里有设备每个实例的全部信息)。所以,我认为这里应该有一个DeviceObject指针。

    我在一些地方曾提到过,在调用IoAttachDeviceToDeviceStack函数时,应该把紧接着你下面的设备对象的地址保存起来。LowerDeviceObject成员用于保存这个地址。

    有一些服务例程需要PDO的地址,而不是堆栈中某个高层设备对象的地址。由于定位PDO非常困难,所以最好的办法是在AddDevice执行时在设备扩展中保存一个PDO地址。

    无论你用什么方法(符号连接或设备接口)命名你的设备,都希望能容易地获得这个名字。所以,这里我用一个Unicode串成员ifname来保存设备接口名。如果你使用一个符号连接名而不是设备接口,应该使用一个有相关含义的成员名,例如“linkname”。

    当你调用IoDeleteDevice删除这个设备对象时,需要使用一个自旋锁来解决同步安全问题,我将在第六章中讨论同步问题。因此,需要在设备扩展中分配一个IO_REMOVE_LOCK对象。AddDevice有责任初始化这个对象。

    你可能需要一个成员来记录设备当前的PnP状态和电源状态。DEVSTATE和POWERSTATE是枚举类型变量,我假设事先已经在头文件中声明了这些变量类型。我将在后面章节中讨论这些状态变量的用途。

    电源管理的另一个部分涉及电源能力设置的恢复,设备扩展中的devcaps结构用于保存这些设置。

    下面是AddDevice中的初始化语句(着重设备扩展部分的初始化):

    NTSTATUSAddDevice(...)
    {
    PDEVICE_OBJECT fdo;
    IoCreateDevice(..., sizeof(DEVICE_EXTENSION), ...,&fdo);
    PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
    pdx->DeviceObject = fdo;
    pdx->Pdo = pdo;
    IoInitializeRemoveLock(&pdx->RemoveLock,...);
    pdx->devstate = STOPPED;
    pdx->devpower = PowerDeviceD0;
    pdx->syspower = PowerSystemWorking;
    IoRegisterDeviceInterface(...,&pdx->ifname);
    pdx->LowerDeviceObject =IoAttachDeviceToDeviceStack(...);
    }

    初始化默认的DPC对象

    许多设备使用中断来报告操作完成。我将在第七章“读写数据”中讨论中断处理,其中对中断服务例程(ISR)能做什么做了严格的限定。特别是ISR不能调用用于报告IRP完成的例程(IoCompleteRequest)。利用DPC(推迟过程调用)可以绕过这个限制。你的设备对象中应包含一个辅助DPC对象,它可以调度你的DPC例程,该对象应该在设备对象创建后不久被初始化。

    NTSTATUSAddDevice(...)
    {
    IoCreateDevice(...);
    IoInitializeDpcRequest(fdo, DpcForIsr);
    }

    设置缓冲区对齐掩码

    执行DMA传输的设备直接使用内存中的数据缓冲区工作。HAL要求DMA传输中使用的缓冲区必须按某个特定界限对齐,而且设备也可能有更严格的对齐需求。设备对象中的AlignmentRequirement域表达了这个约束,它是一个位掩码,等于要求的地址边界减一。下面语句可以把任何地址圈入这个界限:

    PVOID address = ...;
    SIZE_T ar = fdo->AlignmentRequirement;
    address = (PVOID) ((SIZE_T) address &~ar);

    还可以把任意地址圈入下一个对齐边界:

    PVOID address = ...;
    SIZE_T ar = fdo->AlignmentRequirement;
    address = (PVOID) (((SIZE_T) address + ar) &~ar);

    在这两段代码中,我使用了SIZE_T把指针类型(它可以是32位也可以是64位,这取决于编译的目标平台)转化成一个整型,该整型与原指针有同样的跨度范围。

    IoCreateDevice把新设备对象中的AlignmentRequirement域设置成HAL要求的值。例如,Intel的x86芯片没有对齐需求,所以AlignmentRequirement的默认值为0。如果设备需要更严格的缓冲区对齐(例如设备有总线主控的DMA能力,要求对齐数据缓冲区),应该修改这个默认值,如下:

    if (MYDEVICE_ALIGNMENT - 1> fdo->AlignmentRequirement)
    fdo->AlignmentRequirement = MYDEVICE_ALIGNMENT -1;

    我假设你在驱动程序某处已定义了一个名为MYDEVICE_ALIGNMENT的常量,它是2的幂,代表设备的数据缓冲区对齐需求。

    其它对象

    设备可能还有其它一些需要在AddDevice中初始化的对象。这些对象可能包括各种同步对象,各种队列头(queue anchors),聚集/分散列表缓冲区,等等。事实上,在本书的其它地方讨论这些对象的初始化更合适。

    初始化设备标志

    设备对象中有两个标志位需要在AddDevice中初始化,并且它们在以后也不会改变,它们是DO_BUFFERED_IO和 DO_DIRECT_IO标志。你只能设置并使用其中一个标志,它将决定你以何种方式处理来自用户模式的内存缓冲区。(我将在第七章中讨论这两种缓冲模式 的不同,以及你如何选择) 由于任何在后面装入的上层过滤器驱动程序将复制你的标志设置,所以在AddDevice中做这个选择十分重要。如果你在过滤器驱动程序装入后改变了设置,它们可能会不知道。

    设备对象中有两个标志位属于电源管理范畴。与前两个缓冲区标志不同,这两个标志在任何时间都可以被改变。我将在第八章中详细讨论它们,但这里我先介绍一下。DO_POWER_PAGABLE意味着电源管理器将在PASSIVE_LEVEL级上向你发送IRP_MJ_POWER请求。 DO_POWER_INRUSH意味着你的设备在上电时将汲取大量电流,因此,电源管理器将确保没有其它INRUSH设备同时上电。

    设置初始电源状态

    大部分设备一开始就进入全供电状态。如果你知道你的设备的初始电源状态,应该告诉电源管理器:

    POWER_STATE state;
    state.DeviceState = PowerDeviceD0;
    PoSetPowerState(fdo, DevicePowerState, state);

    电源管理的细节请见第八章。

    建立设备堆

    每个过滤器驱动程序和功能驱动程序都有责任把设备对象放到设备堆栈上,从PDO开始一直向上。你可以调用IoAttachDeviceToDeviceStack完成你那部分工作:

    NTSTATUS AddDevice(...,PDEVICE_OBJECT pdo)
    {
    PDEVICE_OBJECT fdo;
    IoCreateDevice(..., &fdo);
    pdx->LowerDeviceObject =IoAttachDeviceToDeviceStack(fdo, pdo);
    }

    IoAttachDeviceToDeviceStack的第一个参数是新创建的设备对象的地址。第二个参数是PDO地址。AddDevice的第二个参数也是这个地址。返回值是紧接着你下面的任何设备对象的地址,它可以是PDO,也可以是其它低级过滤器设备对象。如果该函数失败则返回一个NULL 指针,因此你的AddDevice函数也是失败的,应返回STATUS_DEVICE_REMOVED。

    清除DO_DEVICE_INITIALIZING标志

    在AddDevice中最后一件需要做的事是清除设备对象中的DO_DEVICE_INITIALIZING标志:

    fdo->Flags&= ~DO_DEVICE_INITIALIZING;

    当这个标志设置时,I/O管理器将拒绝任何打开该设备句柄的请求或向该设备对象上附着其它设备对象的请求。在驱动程序完成初始化后,必须清除这个标志。在以前版本的Windows NT中,大部分驱动程序在DriverEntry中创建所有需要的设备对象。当DriverEntry返回时,I/O管理器自动遍历设备对象列表并清除该 标志。但在WDM驱动程序中,设备对象在DriverEntry返回后才创建,所以I/O管理器不会自动清除这个标志,驱动程序必须自己清除它。

    展开全文
  • 主设备号和次设备

    2014-03-03 19:39:00
    驱动程序是UNIX系统内核代码中的部分,其作用是用来控制种特殊设备的输入输出。大多数的UNIX系统都有多种设备驱动程序,每个设备文件名中的主设备号就代表这个设备使用的是哪个设备驱动程序。 次设备号:每...

    image

    上图中,红色方块内为设备的主设备号,黄色方块内为设备的次设备号。

    主设备号是表示系统存取这个设备的“内核驱动”。驱动程序是UNIX系统内核代码中的一部分,其作用是用来控制一种特殊设备的输入输出。大多数的UNIX系统都有多种设备驱动程序,每一个设备文件名中的主设备号就代表这个设备使用的是哪个设备驱动程序。

    次设备号:每一个设备文件都有一个次设备号。“次设备号”是一个24位的十六进制数字,其定义了这个设备在系统中的物理位置

    其他参考资料:http://www.cnblogs.com/nufangrensheng/p/3505113.html

    展开全文
  • IOS7 第一次接触ALAssetsLibrary,简单介绍一下ALAssetsLibrary的作用 ALAssetsLibrary介绍 ALAssetsLibrary提供了访问iOS设备下”照片”应用下所有照片和视频的接口; 从ALAssetsLibrary中可读取所有的...
  • 本人刚开始使用蓝牙耳机连接还是挺管用,也不知道啥时候开始就不行了在“设置”蓝牙栏,删除设备后再重新连接还是不行,显示已连接,但蓝牙耳机实际根本没有连接上,网上也找到了很多方法都没用,叫我更新...
  • 主设备号与次设备号概念

    千次阅读 2012-07-09 11:36:08
    主设备号标志设备驱动程序,次设备号标志特定的子设备。这么想就理解了:块有着不同分区的磁盘,那么主设备号就标志着这块磁盘,此设备号就标志着这块磁盘上的不同分区,也...由此可以类推主设备号和次设备的作用
  • 传统系统采用定期停电监测方式,无法发现突发性设备故障缺点,导致监测效果较差,为了解决这问题,对智能变电站二次设备的新型在线监测系统进行设计和应用分析。采用IEC61850标准对模拟信号进行长时间监视,根据...
  • 不过,这一次引领潮流并不是已经糊了“三大俗”:智能手表、智能手环、智能眼镜。而是其他诸如增加了生物识别跟踪技术智能耳机、能发出振动信号腕带等等。 新兴可穿戴设备画风似乎出奇一致:那就是作为...
  •  某, 我客户端要去连接个位于nat之后嵌入式设备, 结果connect失败, 对方跟我说, 抓包发现没有建立三握手。后来我才学会了三握手在实际工作中分析方法, 呵呵哒。  对了, 上述情况是行不通
  • 微机保护消谐装置是种高容量非线性电阻器,在6~35Kv中性点非有效接地电网中电压互感器中,消谐装置可以解决铁磁谐振产生过电压会使设备内绝缘击穿、外绝缘放电,以及因事故处理不及时或者事故扩大而造成大面积...
  • 设备驱动角色就是将这些调用映射到作用于实际硬件设备相关操作上 机制与策略 机制:提供什么功能,策略:如何使用这些功能。我理解:机制应该是提供最小基础功能集合,策略是在机制之上组合和扩展。...
  • 从这点看隔离变压器更具有保护设备的作用。  所谓保护人身安全,即对操作此设备的人的一个保护。因为隔离变压器的电动势是通过二次感应获得,与一次侧(与大地形成回路)不形成一个回路,因此不会造成触电。  欢迎...
  • 创建字符设备节点方法 mknod /dev/xxx c 252 0 在编写驱动程序时: ...linux kernel中有很大一部分代码是设备驱动代码,这些驱动代码都有初始化和反初始化函数,这些代码一般都只执行一次,为了有
  • 用过国标流媒体服务器朋友们应该都知道,GB28181协议是公安部提...最近有开发者在使用我们国标流媒体平台时候反馈个问题,是国标设备注册到平台显示在线,但是播放时候就变成了提示离线。 这样问题乍
  • Maven的作用

    2020-12-26 17:00:43
    重复多次的工作。 测试人员,测试项目功能是否符合要求. 测试开发人员提交代码-如果测试有问题–需要开发人员修改–在提交代码给测试 测试人员在测试代码-如果还有问题-在交给开发人员-开发人员在提交-在测试直到-...
  • 网关的作用

    2015-02-06 14:39:00
    与网桥仅仅是简单地传达信息不同,网关对收到信息要又一次打包,以适应目的系统需求。同一时候,网关也能够提供过滤和安全功能。大多数网关执行在OSI 7层协议顶层--应用层。 大家都知道,从一个房间走到...
  • 如果做到个驱动程序管理多个硬件设备个体,通过次设备号,共享个主设备号; 答:struct inode { dev_t i_rdev; struct cdev *icdev; ... }; 作用: 描述个文件物理上信息 如果文件创建,内核为这个文件...
  • 软件程序开发中,从下往上每一次的作用依次为: Bootloader:将硬件初始化,并将内核引导起来; Linux Kernel:定制、裁剪之后的Linux内核; 根文件系统:提供文件系统,更好的使用存储空间; 设备驱动:完成设备...
  • 当二次负载阻抗减小时,二次电流增大,使得 一次电流自动增大一个分量来满足一、二次侧之间电磁平衡关系。可以说,电压互感器是一个被限定结构和使用形式特殊变压器。  电压互感器是发电厂、变电所等输电和...
  • 思路: 1.在登录controller层中先判断用户是否登录成功,...2.创建一个自定义拦截器SysInterceptor,使用每次请求登录都去拦截对比登录信息,如果第一次登录用户和第二次登录用户两个登录信息一致并且sessi...
  • 连接电脑,直接复制,随着复制进行会报以下错误,照片复制一般没有问题(一次不要复制太多,一两百张没问题),视频会频繁报错(即使单个复制还是报错) 报错原因:设置-照片-传输到MAC或PC选择了自动,连接WIN
  • 近年来,煤炭企业电气设备的网络化状态监测及故障诊断都是具有挑战性的问题,文章在应用中对电力设备状态检测所需要获取的信息、状态监测的任务和可以采用的方法进行了分析,然后对于主要的一次设备和二次设备及其回路...
  • 1、网络概述 1.1、计算机网络 计算机网络是用通信线路和通信...网络编程从大的方面说就是对信息在不同网络设备中的发送到接收,中间传输为物理线路的作用。 2、网络协议 2.1、网络请求过程 我们在浏览器的地址栏...
  • 通过它可以在充分安全的条件下,完成对一次回路高电压、大电流的测量,以达到监控一次设备的运行是否正常的目的。 (一)互感器种类和作用 互感器包括电流互感器和电压互感器。它们将一次回路的高电压、大电流,按既定...
  • 第三看了LDD3了(虽然现在已经是kernel3.0但...驱动程序的作用: 简单来说 驱动程序就是使计算机与设备通信的特殊的代码,在作单片机时候(无OS)我们自己定义接口及自定义的结构来操作相关硬件,而在有OS的
  • 我们都知道ARP是数据链路层个很正常的地址解析协议,负责将ip地址和MAC地址进行转换和解析,在网络传输中发挥着非常重要的作用。 IP数据包常通过以太网发送。以太网设备并不识别32位IP地址:它们是以48位以太网...
  • 运维第一次

    2020-06-05 16:13:45
    Linux常用目录及其作用 /root root目录主(家)目录 /home 普通用户主(家)目录 /etc 配置文件目录 /bin 可执行文件 ls cd /var 可变目录,用来存放经常变化目录,如日志文件 /dev 设备文件,任何设备与...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 855
精华内容 342
关键字:

一次设备的作用