前面介绍的是最底层的硬件控制,这部分将介绍高级总线架构的一些综述,总线由电气接口和编程接口够成。下面将重点介绍PCI总线的编程接口以及对应的内核函数。

PCI(外围设备互联)接口

  PCI总线是当今普遍使用在桌面以及更大型计算机上的外设总线,而且该总线是内核中得到最好支持的总线。尽管许多计算机用户将PCI看成是一种布置电子线路的方式,但实际上它是一组完整的规范,定义了计算机的各个不同部分之间该如何交互。
  PCI规范涵盖了与计算机接口相关的大部分问题。这里详细介绍PCI驱动程序如何寻找其硬件和获得对它的访问。
  PCI架构的三个主要目标

  • 获得在计算机和外设之间传输数据时更好的性能;
  • 尽可能的平台无关;
  • 简化往系统中添加和删除外设的工作。

PCI总线与配置空间

  PCI 总线体系结构是一种层次式的体系结构。在这种层次式体系结构中,PCI 桥设备占据着重要的地位,它将父总线与子总线连接在一起,从而使整个系统看起来像一颗倒置的树型结构。树的顶端是系统的 CPU,它通过一个较为特殊的 PCI 桥设备——Host/PCI 桥设备与根 PCI 总线连接起来。
  作为一种特殊的 PCI 设备,PCI 桥包括以下几种

  • Host/PCI 桥:用于连接 CPU 与 PCI 根总线,第 1 个根总线的编号为0,内存控制器也通常被集成到 Host/PCI 桥设备芯片中,桥通常也被称为“北桥芯片组(North Bridge Chipset)”。
  • PCI/ISA 桥:用于连接旧的 ISA 总线。通常,PCI 中的类似i8359A 中断控制器这样的设备也会被集成到 PCI/ISA 桥设备中,因此,PCI/I称为“南桥芯片组(South Bridge Chipset)”。
  • PCI-to-PCI 桥:用于连接 PCI 主总线(primary bus)与次总线(sPCI 桥所处的 PCI 总线称为“主总线”(即次总线的父总线),桥设备所线称为“次总线”(即主总线的子总线)。

  在 Linux 系统中,PCI 总线用 pci_bus 来描述,这个结构体记录了本 PCI 总线的信息以及本 PCI 总线的父总线、子总线、桥设备信息,这个结构体的定义:

struct pci_bus{struct list_head node;       /* 链表元素 node */struct pci_bus * parent;     /*指向该 PCI 总线的父总线,即 PCI 桥所在的总线 */struct list_head children;   /* 描述了这条 PCI 总线的子总线链表的表头 */struct list_head devices;    /* 描述了这条 PCI 总线的逻辑设备链表的表头 */struct pci_dev * self;     /* 指向引出这条 PCI 总线的桥设备的 pci_dev 结构 */struct resource * resource[PCI_BUS_NUM_RESOURCES];/* 指向应路由到这条 PCI 总线的地址空间资源 */struct pci_ops * ops; /* 这条 PCI 总线所使用的配置空间访问函数 */void *sysdata;                    /* 指向系统特定的扩展数据 */struct proc_dir_entry * procdir;     /*该 PCI 总线在/proc/bus/pci 中对应目录项*/unsigned char number;           /* 这条 PCI 总线的总线编号 */16 unsigned char primary;         /* 桥设备的主总线 */unsigned char   secondary;          /* PCI 总线的桥设备的次总线号 */18 unsigned char subordinate;    /*PCI 总线的下属 PCI 总线的总线编号最大值*/19 char name[48];unsigned short bridge_ctl;unsigned short pad2;struct device * bridge;struct class_device class_dev;struct bin_attribute * legacy_io;struct bin_attribute * legacy_mem;};

  系统中当前存在的所有根总线都通过其 pci_bus 结构体中的 node 成员链接成一条全局的根总线链表,其表头由 list 类型的全局变量 pci_root_buses 来描述。而根总线下面的所有下级总线则都通过其 pci_bus 结构体中的 node 成员链接到其父总线的children 链表中。这样,通过这两种 PCI 总线链表,Linux 内核就将所有的 pci_bus 结构体以一种倒置树的方式组织起来。

PCI设备

  在 Linux 系统中,所有种类的 PCI 设备都可以用 pci_dev 结构体来描述,由于一个 PCI 接口卡上可能包含多个功能模块,每个功能被当作一个独立的逻辑设备,因此,每一个 PCI 功能,即 PCI 逻辑设备都唯一地对应一个 pci_dev 设备描述符。该结构体为:

struct pci_dev{struct list_head global_list;      /* 全局链表元素 */struct list_head bus_list;        /* 总线设备链表元素 */struct pci_bus * bus;           /* 这个 PCI 设备所在的 PCI 总线的 pci_bus 结构 */struct pci_bus * subordinate;    /* 指向这个 PCI 设备所桥接的下级总线 */void *sysdata;                  /* 指向一片特定于系统的扩展数据 */struct proc_dir_entry * procent;  /* 该 PCI 设备在/proc/bus/pci 中对应的目录项 */unsigned int  devfn;                          /* 这个 PCI 设备的设备功能号 */unsigned short vendor;                         /* PCI 设备的厂商 ID*/unsigned short device;                         /* PCI 设备的设备 ID */unsigned short    subsystem_vendor;               /* PCI 设备的子系统厂商 ID */unsigned short subsystem_device;               /* PCI 设备的子系统设备 ID */unsigned int   class;       /* 32 位的无符号整数,表示该 PCI 设备的类别, bit[7∶0]为编程接口,bit[15∶8]为子类别代码,bit[23∶16]为基类别代码,bit[31∶24]无意义 */u8 hdr_type;                        /* PCI 配置空间头部的类型 */u8 rom_base_reg;                 /* 表示 PCI 配置空间中的 ROM 基地址寄存器在 PCI 配置空间中的位置 */struct pci_driver * driver;   /* 指向这个 PCI 设备所对应的驱动 pci_driver结构 */u64 dma_mask;            /* 该设备支持的总线地址位掩码,通常是 0xffffffff */pci_power_t    current_state;                  /* 当前的操作状态 */struct device dev;      /* 通用的设备接口 */
/* 定义这个 PCI 设备与哪些设备相兼容 */unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];unsigned short   device_compatible[DEVICE_COUNT_COMPATIBLE];int  cfg_size;           /* 配置空间大小 */unsigned int    irq;struct resource resource[DEVICE_COUNT_RESOURCE];/*表示该设备可能用到的资源,包括:I/O 端口区域、设备内存地址区域以及扩展 ROM 地址区域 */unsigned int   transparent     : 1;            /* 透明 PCI 桥 */unsigned int  multifunction   : 1;            /* 多功能设备 *//* keep track of device state */unsigned int is_enabled      : 1;            /* pci_enable_device 已经被调用? */unsigned int   is_busmaster    : 1;                /* 设备是主设备? */unsigned int    no_msi          : 1;            /* 设备可不使用 msi? */u32 saved_config_space[16];                 /* 挂起事保存的配置空间 */struct bin_attribute * rom_attr;              /* sysfs ROM 入口的属性描述 */int            rom_attr_enabled;struct bin_attribute * res_attr[DEVICE_COUNT_RESOURCE];   /*资源的sysfs 文件*/};

  在 Linux 系统中,所有的 PCI 设备都通过其 pci_dev 结构体中的 global_list 成员链接一条全局 PCI 设备链表pci_devices。另外,同属一条 PCI 总线上的所有 PCI 设备也通过其 pci_dev 结构体中的 bus_list 成员链接成一个属于这条 PCI 总线的总线设备链表,表头则由该 PCI 总线的 pci_bus 结构中的 devices 成员所定义。

PCI配置空间访问

  PCI设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。CPU可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给设备驱动程序使用,而配置空间则由Linux内核中的PCI初始化代码使用,这些代码用于配置 PCI 设备,比如中断号以及 I/O 或内存基地址
PCI 规范定义了 3 种类型的 PCI 配置空间头部,其中 type 0 用于标准的 PCI 设备,type 1 用于 PCI 桥,type 2 用于 PCI CardBus 桥:

    /* PCI 头类型 */
#define PCI_HEADER_TYPE 0x0e        /* 8 位头类型 */
#define PCI_HEADER_TYPE_NORMAL     0
#define PCI_HEADER_TYPE_BRIDGE      1
#define PCI_HEADER_TYPE_CARDBUS     2

  pci_bus 结构体中的 pci_ops 类型成员指针 ops 指向该 PCI 总线所使用的配置空间访问操作的具体实现,pci_ops 结构体的定义:

 struct pci_ops{int(*read) (struct pci_bus * bus, unsigned int devfn, int where, int size, u32 * val); //读配置空间int(*write) (struct pci_bus * bus, unsigned int devfn, int where, int size, u32  val); //写配置空间};

  read()和 write()成员函数中的 size 表示访问的是字节、2字节还是4字节,对于write()而言,val 是要写入的值;对于 read()而言,val 是要返回的读取到的值的指针。通过 bus 参数的成员以及 devfn 可以定位相应 PCI 总线上相应 PCI 逻辑设备的配置空间。在 Linux 设备驱动中,可用如下一组函数来访问配置空间:

int pci_bus_read_config_byte (struct pci_bus *bus, unsigned int devfn, int where, u8 *val); //读字节
int pci_bus_read_config_word (struct pci_bus *bus, unsigned int devfn, int where, u16 *val); //读字
int pci_bus_read_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32 *val); //读双字
int pci_bus_write_config_byte (struct pci_bus *bus, unsigned int devfn, int where, u8 val);   //写字节
int pci_bus_write_config_word (struct pci_bus *bus, unsigned int devfn, int where, u16 val);  //写字
int pci_bus_write_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32 val);  //写双字

PCI设备驱动结构

  从本质上讲 PCI 只是一种总线,具体的 PCI 设备可以是字符设备、网络设备、USB主机控制器等,因此,一个通过 PCI 总线与系统连接的设备的驱动至少包含以下两部分:

  • PCI 设备驱动
  • 设备本身的驱动

  PCI 驱动只是为了辅助设备本身的驱动,它不是目的,只是手段,PCI 设备本身含有双重以上的身份。

pci_driver结构体

  在 Linux 内核中,用 pci_driver 结构体来定义 PCI 驱动,该结构体中包含了 PCI设备的探测/移除、挂起/恢复等函数,其定义如下:

 struct pci_driver{struct list_head node;char *name;struct module * owner;const struct pci_device_id * id_table; /*不能为 NULL,以便 probe 函数调用*//* 新设备添加 */int(*probe) (struct pci_dev * dev, const struct pci_device_id * id);void(*remove) (struct pci_dev * dev);           /* 设备移出 */int(*suspend) (struct pci_dev * dev, pm_message_t state); /* 设备挂起 */int(*resume) (struct pci_dev * dev);          /* 设备唤醒 *//* 使能唤醒事件 */int(*enable_wake) (struct pci_dev * dev, pci_power_t state, int enable);void(*shutdown) (struct pci_dev * dev);struct device_driver driver;struct pci_dynids dynids;};

  对 pci_driver 的注册和注销通过如下函数来实现:

int pci_register_driver(struct pci_driver *driver);      //注册
void pci_unregister_driver(struct pci_driver *driver);   //销毁

  pci_driver 的 probe()函数要完成 PCI 设备的初始化及其设备本身身份(字符、TTY、网络等)驱动的注册。当 Linux 内核启动并完成对所有 PCI 设备进行扫描、登录和分配资源等初始化操作的同时,会建立起系统中所有 PCI 设备的拓扑结构,probe()函数将负责硬件的探测工作并保存配置信息。

pci_driver_id结构体

  在 PCI 设备驱动中,也需要定义一个 pci_device_id 结构体数组并导出到用户空间,使热插拔和模块装载系统知道驱动模块所针对的硬件设备。pci_device_id结构体的定义:

 struct pci_device_id { __u32 vendor, device;          /* 厂商和设备 ID或 PCI_ANY_ID*/ __u32 subvendor, subdevice;    /* 子系统 ID 或 PCI_ANY_ID */ __u32 class, class_mask;        /* (类、子类、prog-if) 三元组 */ kernel_ulong_t driver_data;      /* 驱动私有数据 */ };

  pci_device_id 结构体数组使用宏 MODULE_DEVICE_TABLE 导出到用户空间:

 static struct pci_device_id netdrv_pci_tbl[] = { {0x10ec, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 }, {0x10ec, 0x8138, PCI_ANY_ID, PCI_ANY_ID, 0, 0, NETDRV_CB }, {0x1113, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, SMC1211TX },{0,}
}
MODULE_DEVICE_TABLE (pci, netdrv_pci_tbl);

PCI驱动设备程序的实现

  在用模块方式实现PCI设备驱动程序时,通常至少要实现以下几个部分:初始化设备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块,下面是一个典型的PCI设备驱动程序的基本框架:

/* 指明该驱动程序适用于哪一些 PCI 设备 */
static struct pci_device_id xxx_pci_tbl [] __initdata = {{PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO,PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO},{0,}
};
MODULE_DEVICE_TABLE(pci, xxx_pci_tbl);
module_init(xxx_init_module);
module_exit(xxx_cleanup_module);
/* 中断处理函数 */static void xxx_interrupt(int irq, void * dev_id, struct pt_regs * regs){/*PC的中断资源比较有限,只有0~15的中断号,因此大部分外部设备都是以共享的形式申请中断号的。当中断发生的时候,中断处理程序首先负责对中断进行识别,然后再做进一步的处理。*/}/* 字符设备 file_operations open 成员函数 */static int xxx_open(struct inode * inode, struct file * file){/* 在这个模块里主要实现申请中断、检查读写模式以及申请对设备的控制权等。在申请控制权的时候,非阻塞方式遇忙返回,否则进程主动接受调度,进入睡眠状态,等待其它进程释放对设备的控制权。*/request_irq(xxx_irq, &xxx_interrupt, ...));...}  /* 字符设备 file_operations ioctl 成员函数 */static int xxx_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg){...}/* 字符设备 file_operations read、write、mmap 等成员函数 *//* 设备文件操作接口 ,PCI设备驱动程序可以通过xxx_fops 结构中的函数xxx_ioctl( ),向应用程序提供对硬件进行控制的接口。*/static struct file_operations xxx_fops =
{owner:THIS_MODULE,                 /* xxx_fops 所属的设备模块 */read:xxx_read,                            /* 读设备操作*/write:xxx_write,                          /* 写设备操作*/ioctl:xxx_ioctl,                          /* 控制设备操作*/mmap:xxx_mmap,                       /* 内存重映射操作*/open:xxx_open,                          /* 打开设备操作*/release:xxx_release                      /* 释放设备操作*/};/* pci_driver 的 probe 成员函数probe探测例程将负责完成对硬件的检测工作*/static int _ _init xxx_probe(struct pci_dev * pci_dev, const struct pci_device_id * pci_id){pci_enable_device(pci_dev);                  //启动 PCI 设备 /* 读取 PCI 配置信息 */Iobase = pci_resource_start(pci_dev, 1);... pci_set_master(pci_dev);                  //设置成总线主 DMA 模式 pci_request_regions(pci_dev);                   //申请 I/O 资源 /* 注册字符设备 */cdev_init(xxx_cdev, &xxx_fops);register_chrdev_region(xxx_dev_no, 1, ...);cdev_add(xxx_cdev);return 0;}/* pci_driver 的 remove 成员函数 */static int _ _init xxx_release(struct pci_dev * pdev){pci_release_regions(pdev);                       //释放 I/O 资源pci_disable_device(pdev);                        //禁止 PCI 设备unregister_chrdev_region(xxx_dev_no, 1);         //释放占用的设备号cdev_del(&xxx_dev.cdev);                      //注销字符设备... return 0;}/* 设备模块信息 */static struct pci_driver xxx_pci_driver ={name:xxx_MODULE_NAME,              /* 设备模块名称 */id_table:xxx_pci_tbl,                       /* 能够驱动的设备列表 */probe:xxx_probe,                     /* 查找并初始化设备 */remove:xxx_remove                     /* 卸载设备模块 */};

  在Linux系统下,想要完成对一个PCI设备的初始化,需要完成以下工作:

  • 检查PCI总线是否被Linux内核支持;
  • 检查设备是否插在总线插槽上,如果在的话则保存它所占用的插槽的位置等信息。
  • 读出配置头中的信息提供给驱动程序使用。
      当Linux内核启动并完成对所有PCI设备进行扫描、登录和分配资源等初始化操作的同时,会建立起系统中所有PCI设备的拓扑结构:
  static int _ _init xxx_init_module(void)      //加载模块{if (!pci_present())     /*驱动程序首先调用函数pci_present( )检查PCI总线是否已经被Linux内核支持,如果系统支持PCI总线结构,这个函数的返回值为0,如果驱动程序在调用这个函数时得到了一个非0的返回值,那么驱动程序就必须得中止自己的任务了*/return -ENODEV; if (!pci_register_driver(&demo_pci_driver)) { //注册pci_driver,自动调用xxx_probe方法pci_unregister_driver(&demo_pci_driver);return -ENODEV;}  return 0; }static void _ _exit xxx_cleanup_module(void)  //卸载模块{pci_unregister_driver(&xxx_pci_driver);  //注销pci_driver}

  假设用树来表示PCI总线,那么树根就是主机/PCI桥,树叶就是具体的PCI设备,树叶与树枝通过pci_driver连接,而树叶本身的驱动,读写、控制树叶则需要通过其树叶设备本身所属类设备驱动来完成。

ISA

  ISA总线在设计上相当陈旧而且其差劲的性能臭名昭著,但是在支持老主板而速度不是很重要的时候,ISA比PCI要更有优势。

  一个ISA设备可配备有I/O端口,内存区域以及中断线:

  • 尽管 x86 处理器支持 64 KB I/O 端口内存(即处理器有 16 条地址线), 一些老 PC 硬件仅解码最低的 10 位地址线,这限制可用的地址空间为 1024 个端口。
  • 如果 I/O 端口的可用性被限制, 内存存取更加麻烦. 一个 ISA 设备可只使用 640KB 到 1 MB 之间的内存范围和 15 MB 和 16MB 之间的范围给 I/O 寄存器和设备控制。
  • 对 ISA 设备板第 3 个可用资源是中断线. 一个有限数目的中断线被连接到 ISA 总线, 并且它们由所有接口板共享. 结果是, 如果设备不被正确配置, 它们可能发现它们自己在使用同一个中断线。

ISA编程

  对于编程, 内核中没有特别的帮助来易于存取 ISA 设备(像对 PCI 那样有). 你可使用的唯一工具是 I/O 端口和 IRQ 线的注册, 只能通过中断处理来实现, 驱动可探测 I/O 端口, 并且中断线必须被自动探测, 这要通过"自动探测 IRQ 号"技术来实现。

其它PC总线

   PCI 和 ISA 是在 PC 世界中最常用的外设接口, 但是它们不是唯一的. 这里简单介绍一下 PC 市场上的其他总线的特性:

MCA总线

  微通道结构(MCA)是,用在 PS/2 计算机和一些笔记本电脑的IBM 标准. 在硬件层次上, 微通道比 ISA 有更多特性. 它支持多主 DMA, 32-位地址和数据线, 共享中断线, 和地理式寻址来存取每块板的配置寄存器. 这样的寄存器被称为可编程选项选择(POS), 但是它们没有 PCI 寄存器的全部特点. Linux 对 微通道的支持包括输出给模块的函数。

EISA总线

  扩展 ISA (EISA) 总线是一个对 ISA 的 32-位 扩展, 带有一个兼容的接口连接器; ISA 设备板可被插入一个 EISA 连接器. 增加的线在 ISA 接触之下被连接.

VLB总线

  另一个对 ISA 的扩展是 VESA Local Bus(VLB) 接口总线, 它扩展了 ISA 连接器, 通过添加第 3 个知道长度的槽位。一个设备可只插入这个额外的连接器(不用插入 2 个关联的 ISA 连接器), 因为 VLB 槽位从 ISA 连接器复制了所有的重要信号. 这样"独立"的 VLB 外设不使用 ISA 槽位是少见的, 因为大部分设备需要伸到后面板, 使它们的外部连接器是可用的。
  VESA 总线比 EISA , MCA, 和 PCI 总线在它的能力方面更加限制, 并且正在从市场上消失. 没有特殊的内核支持位 VLB 而存在。

Linux设备驱动程序学习(十)——PCI驱动程序相关推荐

  1. linux设备驱动学习,linux设备驱动学习4

    Linux设备驱动程序学习(4) -高级字符驱动程序操作[(1)ioctl and llseek] 今天进入<Linux设备驱动程序(第3版)>第六章高级字符驱动程序操作的学习. 一.io ...

  2. linux设备驱动学习笔记(1)

    学习了将近半个月的设备驱动程序的编写,也有一些体会,这里写下来也给学习做一个总结,为后面的学习做更好的准备. 首先,个人感觉驱动程序的设计是很有套路的,最基本的要求就是要掌握这些套路.所谓的套路就是一 ...

  3. linux设备驱动学习(四)——阻塞与非阻塞I/O

    1.阻塞与非阻塞I/O介绍 阻塞和非阻塞I/O是设备访问的两种不同模式.阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作.被挂起的进程进入睡眠状态,被从调度器的 ...

  4. linux设备驱动学习(二)——字符设备编写及测试

    一.字符设备体结构介绍 1.字符设备作为linux内核三大驱动设备之一,主要完成字节的读写操作,常见的应用有鼠标.键盘等,结构体形式如下所示: struct cdev{ struct kobject ...

  5. linux 设备驱动学习总结

    从熟悉的开始. 设备驱动模型. device device driver class bus dra7xx pcie驱动 bus:platform bus device:platform_device ...

  6. Linux设备树学习2 - DTB文件格式

    一. DTB文件简介 DTB文件是由DTS文件通过dtc命令编译生成的二进制文件.DTS文件不能直接被内核解析,需要编译成DTB文件才可以直接被内核识别并解析使用的. 二. DTB文件内容布局 从上图 ...

  7. linux设备驱动学习(三)——并发控制

    1.并发介绍 一般来说,操作系统都是支持并发执行能力的,多个执行单元访问同一个模块时,如果不能支持并发,则会让这个模块功能紊乱,像读写操作时.两个用户同时读写,那么可能一个用户执行读操作时,另一个用户 ...

  8. linux设备驱动学习(一)——简单的helloworld模块

    在内核驱动中新建hello文件夹 1.需要.c文件与Makefile文件 在..../drivers/hello目录下新建xxx.c 和Makefile文件 .c文件样例: #include < ...

  9. linux父设备,linux 设备模型---学习记录(二)

    通过bus_register()函数可以弄清楚整个设备模型的骨架.大致的架构弄清楚之后就可以继续填充这个模型了.根据总线.设备.驱动由高层到底层的顺序,可以以设备为入口点来继续探索. 设备的入口在de ...

  10. Linux下PCI设备驱动程序开发 --- PCI驱动程序实现(三)

    <script type="text/javascript"> </script><script type="text/javascript ...

最新文章

  1. Leangoo研发管理软件甘特图6.0.2 版发布
  2. 局域网语音通话demo
  3. php上传图片到非项目目录,前端页面的读取问题
  4. 行转列(FOR XML PATH)
  5. MyEclipse运行tomcat提示严重错误 严重: Error starting endpoint java.lang.Exception: Socket
  6. 非GUI模式运行Jmeter脚本
  7. 如何有效利用SD-WAN改善网络性能?
  8. 为什么和平精英无响应_什么和为什么
  9. MRP的数据处理-华北水利水电大学(作业)
  10. ArcEngine开发 退出系统报错
  11. 怎么理解anchor
  12. 戴机械手表有哪些事情就不能做了?
  13. Android应用程序的Activity启动过程简要介绍和学习计划 .
  14. Spring4学习笔记 - Bean的生命周期
  15. android图片模糊处理
  16. fme坐标转换器_利用FME做坐标转换
  17. 数据分析处理之词频统计
  18. 微信小程序一键连接已知wifi
  19. 发现一个微博图床API和图片上传代码
  20. AutoCAD、Revit、Maya、3dsMax出现许可管理器不起作用

热门文章

  1. HTML5期末考核大作业 使用HTML+CSS技术制作学生动漫网页源码
  2. RFID门禁FM1702单片机程序
  3. 惠州将建中国高校科技成果大数据中心
  4. Pulsar Geo Replication
  5. 视口锁定解锁lisp_CAD基础知识汇总第十四期:如何创建异形视口和解除视口锁定?...
  6. 【IT】福昕pdf保持工具选择
  7. liunx防火墙开通指定端口
  8. MK-TD环保型铝合金脱模剂
  9. 为什么你睡了11个小时仍然觉得疲累?
  10. Android感应检测Sensor(简单介绍),Android系统面试题