Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1599849
  • 博文数量: 204
  • 博客积分: 2215
  • 博客等级: 大尉
  • 技术积分: 4427
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-06 08:03
个人简介

气质,源于心灵的自信!

文章存档

2018年(1)

2017年(1)

2016年(1)

2015年(18)

2014年(20)

2013年(30)

2012年(119)

2011年(14)

分类:

2012-12-12 16:40:25

一:前言
经过上一节的分析,对pci有了一个大概的了解.我们今天来讨论一下pci设备的中断号确定,顺便解决上节中遗留的问题.
二:pci interrupt routing
我们在上面的分析过程中可以看到.pci的配置空间有两个寄存器,一个是IRQ_PIN.表示该pci设备所有的中断连接引脚.如果为0.说明没有引脚与中断线相连,也就是不支持中断. 另外一个是IRQ_LINE.表示该pci设备的引脚所连的中断线.也就是该设备对应的中断号.
其实在pci架构中,pci设备都是经过一个中断请求路径互联器的设备与8259A相联的.下图是摘自<>上的图):
由上图可以看到.所有中线引脚都是连在中断请求路径互连器(下面简称为PIR)上的,然后再由它控制到8259A的连接.有一点要特别注意.在APIC中是没有PIR的,因为APIC本身就是一个可编程控制器.在本节的讨论中,我们只讨论8259A的情况.
至于中断控制器的输入引脚是和哪一个8259A的IRQ线相联.这是由PIR芯片所控制的.
闲言少述,我们从代码中看一下是怎样处理这个IRQ号的.相应的处理是在pcibios_irq_init()中完成的.先看下它的调用方式:
subsys_initcall(pcibios_irq_init);
pcibios_irq_init()是在subsys_initcall宏被调用的.在编译的时候被放至了init区域,kernel启动的时候会调用此函数.
在分析函数之前,我们先要有以下几点基本的概念:
1: PIR其实也是一个PCI设备.所以它也有对应的总线号,设备号等.也可以通过PCI寻址方式对它进行配置
2: 在确定PCI设备的中断号之前,我们需要知道.PCI设备是连在PIR的哪一个输入线上,这也是一个复杂的过程,幸好bios为我们提供了这样的功能.
 
好了,转入具体的代码:
static int __init pcibios_irq_init(void)
{
         DBG(KERN_DEBUG "PCI: IRQ init\n");
 
         if (pcibios_enable_irq || raw_pci_ops == NULL)
                   return 0;
 
         dmi_check_system(pciirq_dmi_table);
pirq_table = pirq_find_routing_table();
 
#ifdef CONFIG_PCI_BIOS
         if (!pirq_table && (pci_probe & PCI_BIOS_IRQ_SCAN))
                   pirq_table = pcibios_get_irq_routing_table();
#endif
         if (pirq_table) {
                   pirq_peer_trick();
                   pirq_find_router(&pirq_router);
                   if (pirq_table->exclusive_irqs) {
                            int i;
                            for (i=0; i<16; i++)
                                     if (!(pirq_table->exclusive_irqs & (1 << i)))
                                               pirq_penalty[i] += 100;
                   }
                   /* If we're using the I/O APIC, avoid using the PCI IRQ routing table */
                   if (io_apic_assign_pci_irqs)
                            pirq_table = NULL;
         }
 
         pcibios_enable_irq = pirq_enable_irq;
 
         pcibios_fixup_irqs();
         return 0;
}
函数先调用pirq_find_routing_table()来寻找bios提供的中断路径表,代码如下 :
static struct irq_routing_table * __init pirq_find_routing_table(void)
{
         u8 *addr;
         struct irq_routing_table *rt;
 
         if (pirq_table_addr) {
                   rt = pirq_check_routing_table((u8 *) __va(pirq_table_addr));
                   if (rt)
                            return rt;
                   printk(KERN_WARNING "PCI: PIRQ table NOT found at pirqaddr\n");
         }
         for(addr = (u8 *) __va(0xf0000); addr < (u8 *) __va(0x100000); addr += 16) {
                   rt = pirq_check_routing_table(addr);
                   if (rt)
                            return rt;
         }
         return NULL;
}
如果指定了中断路径表的地址.那直接判断它是否合法就可以了.如果没有指定,那就需要搜索bios的映射区了.
转入pirq_check_routing_table().代码如下:
static inline struct irq_routing_table * pirq_check_routing_table(u8 *addr)
{
         struct irq_routing_table *rt;
         int i;
         u8 sum;
 
         rt = (struct irq_routing_table *) addr;
         if (rt->signature != PIRQ_SIGNATURE ||
             rt->version != PIRQ_VERSION ||
             //是否低四位对齐
             rt->size % 16 ||                                                
             rt->size < sizeof(struct irq_routing_table))
                   return NULL;
         sum = 0;
         //所有的值加起来必须为零.因为它后面多了一个checksum
         for (i=0; i < rt->size; i++)
                   sum += addr[i];
         if (!sum) {
                   DBG(KERN_DEBUG "PCI: Interrupt Routing Table found at 0x%p\n", rt);
                   return rt;
         }
         return NULL;
}
Bios提供的中断路径表有自己的格式.对应格式的结构如下所示 :
struct irq_routing_table {
         //恒定义为PIRQ_SIGNATURE
         u32 signature;                    /* PIRQ_SIGNATURE should be here */
         //恒为PIRQ_VERSION
         u16 version;                        /* PIRQ_VERSION */
         //表的大小
         u16 size;                     /* Table size in bytes */
         //PIR的总线号和设备功能号
         u8 rtr_bus, rtr_devfn;                   /* Where the interrupt router lies */
         //分配给PIR专用的IRQ
         u16 exclusive_irqs;            /* IRQs devoted exclusively to PCI usage */
         //PIR芯片的厂商和设备号
         u16 rtr_vendor, rtr_device;         /* Vendor and device ID of interrupt router */
         //没有用到
         u32 miniport_data;            /* Crap */
         //保留区
         u8 rfu[11];
         //检验和.表头与检验之和为0
         u8 checksum;                     /* Modulo 256 checksum must give zero */
         //中断路径表项,包含了每个设备对应的输入引脚和可用的IRQ号等信息
         struct irq_info slots[0];
}
注意上面的结构中.最后一项是一个0项的数组,这个是为以后做扩展用的,
Struct irq_info结构如下 :
struct irq_info {
         //设备的总线号和设备功能号
         u8 bus, devfn;                     /* Bus, device and function */
         //pci设备的每个引脚对应的输入线和可能的IRQ号
         struct {
                   u8 link;               /* IRQ line ID, chipset dependent, 0=not routed */
                   u16 bitmap;                /* Available IRQs */
         } __attribute__((packed)) irq[4];
         u8 slot;                        /* Slot number, 0=onboard */
         u8 rfu;
} __attribute__((packed));
 
在pirq_check_routing_table()中判断给定的地址是否存放中断路径表.它从它的固定标识,版本号和检验和进行判断.
回到pcibios_irq_init()中.它将在bios中寻找到的中断路径表存放于全局变量pirq_table中.然后再调用pirq_peer_trick().
现在这个中断路径表里已经包含和所有pci设备的信息了.可以从这些信息里找出.有那些根总线是我们没有枚举过的.这也就是pirq_peer_trick()要处理的事情.
static void __init pirq_peer_trick(void)
{
         struct irq_routing_table *rt = pirq_table;
         u8 busmap[256];
         int i;
         struct irq_info *e;
 
         memset(busmap, 0, sizeof(busmap));
         for(i=0; i < (rt->size - sizeof(struct irq_routing_table)) / sizeof(struct irq_info); i++) {
                   e = &rt->slots[i];
#ifdef DEBUG
                   {
                            int j;
                            DBG(KERN_DEBUG "%02x:%02x slot=%02x", e->bus, e->devfn/8, e->slot);
                            for(j=0; j<4; j++)
                                     DBG(" %d:%02x/%04x", j, e->irq[j].link, e->irq[j].bitmap);
                            DBG("\n");
                   }
#endif
                   busmap[e->bus] = 1;
         }
         for(i = 1; i < 256; i++) {
                   if (!busmap[i] || pci_find_bus(0, i))
                            continue;
                   if (pci_scan_bus_with_sysdata(i))
                            printk(KERN_INFO "PCI: Discovered primary peer "
                                   "bus %02x [IRQ]\n", i);
         }
         pcibios_last_bus = -1;
}
先在中断路径表里找到所用到的总线号,如果该总线出现在我们之前遍历出的pci_root_buses中.那它就是属于新增的.枚举它.
在这里要注意一点,可能我们还有其余的根总线没有枚举到.那它之下的次总线也不会出现在pci_root_buses中.我们应该先遍历根总线,然后再来遍来其下的次总线.
所以上面的后一个循环是从总线的小值向大值循环,因为根总线的总线号会小于它下面的子层总线号.
 
转入pci_scan_bus_with_sysdata() :
struct pci_bus *__devinit pci_scan_bus_with_sysdata(int busno)
{
         struct pci_bus *bus = NULL;
         struct pci_sysdata *sd;
 
         /*
          * Allocate per-root-bus (not per bus) arch-specific data.
          * TODO: leak; this memory is never freed.
          * It's arguable whether it's worth the trouble to care.
          */
         sd = kzalloc(sizeof(*sd), GFP_KERNEL);
         if (!sd) {
                   printk(KERN_ERR "PCI: OOM, skipping PCI bus %02x\n", busno);
                   return NULL;
         }
         sd->node = -1;
         bus = pci_scan_bus(busno, &pci_root_ops, sd);
         if (!bus)
                   kfree(sd);
 
         return bus;
}
pci_scan_bus()我们在上节已经分析过了.它以深度优先搜索算法枚举该总线下的设备.
总线全部都遍历完之后,在pirq_peer_trick()中会将pcibios_last_bus设为-1.表示所有总线都已经遍历完了.
 
在pcibios_irq_init()中,调用完pirq_peer_trick()之后还会调用pirq_find_router().它为PIR设备取得驱动类信息,我们要知道哪一条输入线是对应哪一个IRQ号,这是跟具体的芯片相关的,
代码如下:
static void __init pirq_find_router(struct irq_router *r)
{
         struct irq_routing_table *rt = pirq_table;
         struct irq_router_handler *h;
 
#ifdef CONFIG_PCI_BIOS
         if (!rt->signature) {
                   printk(KERN_INFO "PCI: Using BIOS for IRQ routing\n");
                   r->set = pirq_bios_set;
                   r->name = "BIOS";
                   return;
         }
#endif
 
         /* Default unless a driver reloads it */
         r->name = "default";
         r->get = NULL;
         r->set = NULL;
        
         DBG(KERN_DEBUG "PCI: Attempting to find IRQ router for %04x:%04x\n",
             rt->rtr_vendor, rt->rtr_device);
 
         pirq_router_dev = pci_get_bus_and_slot(rt->rtr_bus, rt->rtr_devfn);
         if (!pirq_router_dev) {
                   DBG(KERN_DEBUG "PCI: Interrupt router not found at "
                            "%02x:%02x\n", rt->rtr_bus, rt->rtr_devfn);
                   return;
         }
 
         for( h = pirq_routers; h->vendor; h++) {
                   /* First look for a router match */
                   if (rt->rtr_vendor == h->vendor && h->probe(r, pirq_router_dev, rt->rtr_device))
                            break;
                   /* Fall back to a device match */
                   if (pirq_router_dev->vendor == h->vendor && h->probe(r, pirq_router_dev, pirq_router_dev->device))
                            break;
         }
         printk(KERN_INFO "PCI: Using IRQ router %s [%04x/%04x] at %s\n",
                   pirq_router.name,
                   pirq_router_dev->vendor,
                   pirq_router_dev->device,
                   pci_name(pirq_router_dev));
 
         /* The device remains referenced for the kernel lifetime */
}
PIR也是一个PCI设备.因此在pci_devices链表中应该会找到它的信息.如果设备确实存在的话.就会根据它的厂商ID和设备ID来匹配相关的驱动.然后将驱动相关结构保存在struct irq_router结构中.该结构有get ,set两个指针.分别用来获取和设置关于输入线的中断号信息.
Linux暂支持的PIR厂商和设备信息包含在下面的数组中:
static __initdata struct irq_router_handler pirq_routers[] = {
         { PCI_VENDOR_ID_INTEL, intel_router_probe },
         { PCI_VENDOR_ID_AL, ali_router_probe },
         { PCI_VENDOR_ID_ITE, ite_router_probe },
         { PCI_VENDOR_ID_VIA, via_router_probe },
         { PCI_VENDOR_ID_OPTI, opti_router_probe },
         { PCI_VENDOR_ID_SI, sis_router_probe },
         { PCI_VENDOR_ID_CYRIX, cyrix_router_probe },
         { PCI_VENDOR_ID_VLSI, vlsi_router_probe },
         { PCI_VENDOR_ID_SERVERWORKS, serverworks_router_probe },
         { PCI_VENDOR_ID_AMD, amd_router_probe },
         { PCI_VENDOR_ID_PICOPOWER, pico_router_probe },
         /* Someone with docs needs to add the ATI Radeon IGP */
         { 0, NULL }
}
 
再次返回pcibios_irq_init().将剩余的代码列出来,方便分析 :
static int __init pcibios_irq_init(void)
{
         ......
         ......
if (pirq_table) {
                   pirq_peer_trick();
                   pirq_find_router(&pirq_router);
                   if (pirq_table->exclusive_irqs) {
                            int i;
                            for (i=0; i<16; i++)
                                     if (!(pirq_table->exclusive_irqs & (1 << i)))
                                               pirq_penalty[i] += 100;
                   }
                   /* If we're using the I/O APIC, avoid using the PCI IRQ routing table */
                   if (io_apic_assign_pci_irqs)
                            pirq_table = NULL;
         }
 
         pcibios_enable_irq = pirq_enable_irq;
 
         pcibios_fixup_irqs();
         return 0;
}
将下来,就要来处理在PIR中,中断号的定向问题了.linux的处理方案中,因为有很多IRQ是共享的,因此尽量将它平均分配中各个IRQ线.另外,有些IRQ线是专用的.不能被打扰.这种平衡机制是通过pirq_penalty[ ]数组来完成的.它有16项,分别对应对16个IRQ号.定义如下 :
static int pirq_penalty[16] = {
         1000000, 1000000, 1000000, 1000, 1000, 0, 1000, 1000,
         0, 0, 0, 0, 1000, 100000, 100000, 100000
};
从它的定义中可以看到,它有三个值 : 100000, 1000,0.
定义为100000的IRQ根本就不会被PCI用到.相应IRQ为,0,1,2,14,15,16
定义为1000的使用机率较小
定义为0的是最可能会用到的.
 
在pcibios_irq_init()中的代码 :
pirq_table->exclusive_irqs表示的是由PIR芯片专用的IRQ掩码.它有16位.分别对应16个IRQ.如果相应位被置,则对应的IRQ是属于PIR专用的.
在上面的代码中看出.如果不是被PIR专用的IRQ号,都会被加上100.
转入到pcibios_fixup_irqs().代码较长,除去APIC的相关部份.如下 :
 
static void __init pcibios_fixup_irqs(void)
{
         struct pci_dev *dev = NULL;
         u8 pin;
 
         DBG(KERN_DEBUG "PCI: IRQ fixup\n");
         while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
                   /*
                    * If the BIOS has set an out of range IRQ number, just ignore it.
                    * Also keep track of which IRQ's are already in use.
                    */
                   if (dev->irq >= 16) {
                            DBG(KERN_DEBUG "%s: ignoring bogus IRQ %d\n", pci_name(dev), dev->irq);
                            dev->irq = 0;
                   }
                   /* If the IRQ is already assigned to a PCI device, ignore its ISA use penalty */
                   if (pirq_penalty[dev->irq] >= 100 && pirq_penalty[dev->irq] < 100000)
                            pirq_penalty[dev->irq] = 0;
                   pirq_penalty[dev->irq]++;
         }
 
         dev = NULL;
         while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
                   pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
/*
                    * Still no IRQ? Try to lookup one...
                    */
                   if (pin && !dev->irq)
                            pcibios_lookup_irq(dev, 0);
         }
}
它遍历所有的PCI设备,如果IRQ号非法,就将其设为0.然后,如果PCI已经被指定了IRQ,且pirq_penalty[ ]中的对应项超过了100.则复为0.对应这种情况,只有在该IRQ号没有被PIR设备专用的且在pirq_penalty[ ]中原始定义为0才能”享受”这样的待遇.
最后,在第二次遍历的时候,如果有引脚连到PIR,但是中断号又非法,就会调用pcibios_lookup_irq()进行修正.
这个函数有两个参数,一个是要修正的pci_dev.另外的一个参数是assign.如果为1.在没有为设备分配的IRQ的情况下,它会为之分配一下(没有分配IRQ.对应也就是输入引脚没有跟8259A相联).如果为0.就暂时不去管它.我们在下面的情况,把参数为0和1的情况全部分析.
代码比较长,分段分析如下:
 
static int pcibios_lookup_irq(struct pci_dev *dev, int assign)
{
         u8 pin;
         struct irq_info *info;
         int i, pirq, newirq;
         int irq = 0;
         u32 mask;
         struct irq_router *r = &pirq_router;
         struct pci_dev *dev2 = NULL;
         char *msg = NULL;
 
         /* Find IRQ pin */
         pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
         if (!pin) {
                   DBG(KERN_DEBUG " -> no interrupt pin\n");
                   return 0;
         }
         pin = pin - 1;
 
         /* Find IRQ routing entry */
 
         if (!pirq_table)
                   return 0;
        
         DBG(KERN_DEBUG "IRQ for %s[%c]", pci_name(dev), 'A' + pin);
         info = pirq_get_info(dev);
         if (!info) {
                   DBG(" -> not found in routing table\n" KERN_DEBUG);
                   return 0;
         }
         pirq = info->irq[pin].link;
         mask = info->irq[pin].bitmap;
         if (!pirq) {
                   DBG(" -> not routed\n" KERN_DEBUG);
                   return 0;
         }
         DBG(" -> PIRQ %02x, mask %04x, excl %04x", pirq, mask, pirq_table->exclusive_irqs);
         mask &= pcibios_irq_mask;
 
首先,在设备的IRQ_LIN寄存器中取得设备所用的中断线.之后再到之前找到的中断路径表去寻找该设备的相关信息.因为在中断路径表中,irq_info数组项是以0开始计数.因此需要把取得的引脚值减1.
中断路径表中包含设备中断线对应的输入引脚和所允许的IRQ值.
pcibios_irq_mask被定义成0xfff8.也就是说不允许使用0,1,2,3这四个IRQ号
 
         /* Work around broken HP Pavilion Notebooks which assign USB to
            IRQ 9 even though it is actually wired to IRQ 11 */
 
         if (broken_hp_bios_irq9 && pirq == 0x59 && dev->irq == 9) {
                   dev->irq = 11;
                   pci_write_config_byte(dev, PCI_INTERRUPT_LINE, 11);
                   r->set(pirq_router_dev, dev, pirq, 11);
         }
 
         /* same for Acer Travelmate 360, but with CB and irq 11 -> 10 */
         if (acer_tm360_irqrouting && dev->irq == 11 && dev->vendor == PCI_VENDOR_ID_O2) {
                   pirq = 0x68;
                   mask = 0x400;
                   dev->irq = r->get(pirq_router_dev, dev, pirq);
                   pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq);
         }
这两段是对应HP和Acer计算机的特殊处理,忽略
 
         /*
          * Find the best IRQ to assign: use the one
          * reported by the device if possible.
          */
         newirq = dev->irq;
         if (newirq && !((1 << newirq) & mask)) {
                   if ( pci_probe & PCI_USE_PIRQ_MASK) newirq = 0;
                   else printk("\n" KERN_WARNING
                            "PCI: IRQ %i for device %s doesn't match PIRQ mask "
                            "- try pci=usepirqmask\n" KERN_DEBUG, newirq,
                            pci_name(dev));
         }
         if (!newirq && assign) {
                   for (i = 0; i < 16; i++) {
                            if (!(mask & (1 << i)))
                                     continue;
                            if (pirq_penalty[i] < pirq_penalty[newirq] && can_request_irq(i, IRQF_SHARED))
                                     newirq = i;
                   }
         }
将设备指定使用的IRQ存放在newirq中,如果设备指定的IRQ不可用,则将newirq设为0. 如果newirq为零且调用参数为1,则为它分配一个新的IRQ.这个IRQ必须要是可共享的
 
         DBG(" -> newirq=%d", newirq);
 
         /* Check if it is hardcoded */
         if ((pirq & 0xf0) == 0xf0) {
                   irq = pirq & 0xf;
                   DBG(" -> hardcoded IRQ %d\n", irq);
                   msg = "Hardcoded";
         } else if ( r->get && (irq = r->get(pirq_router_dev, dev, pirq)) && \
         ((!(pci_probe & PCI_USE_PIRQ_MASK)) || ((1 << irq) & mask)) ) {
                   DBG(" -> got IRQ %d\n", irq);
                   msg = "Found";
                   eisa_set_level_irq(irq);
         } else if (newirq && r->set && (dev->class >> 8) != PCI_CLASS_DISPLAY_VGA) {
                   DBG(" -> assigning IRQ %d", newirq);
                   if (r->set(pirq_router_dev, dev, pirq, newirq)) {
                            eisa_set_level_irq(newirq);
                            DBG(" ... OK\n");
                            msg = "Assigned";
                            irq = newirq;
                  }
         }
1:如果输入引脚线的高四位为零,说明是一个硬连接.也就是说,从N号引脚进来,就连在8259A的N号引脚上.因此它的IRQ也就是输入引脚的低四位
2:到芯片中取得输入引脚对应的IRQ.如果返回失败,则说明该输入引脚线还没有连接上
3:调用芯片的set方法,将对应输入引脚连接到8259A的newirq号中断线上
由此可以看到,只有在设备指定IRQ为0的情况下,才会更改它的IRQ号,.否则,就会尽量使用它指定的IRQ.从这里也可以看到,函数的第二个参数,只有在设备指定的IRQ为0的时候才会有区别
 
         if (!irq) {
                   DBG(" ... failed\n");
                   if (newirq && mask == (1 << newirq)) {
                            msg = "Guessed";
                            irq = newirq;
                   } else
                            return 0;
         }
         printk(KERN_INFO "PCI: %s IRQ %d for device %s\n", msg, irq, pci_name(dev));
 
         /* Update IRQ for all devices with the same pirq value */
         while ((dev2 = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev2)) != NULL) {
                   pci_read_config_byte(dev2, PCI_INTERRUPT_PIN, &pin);
                   if (!pin)
                            continue;
                   pin--;
                   info = pirq_get_info(dev2);
                   if (!info)
                            continue;
                   if (info->irq[pin].link == pirq) {
                            /* We refuse to override the dev->irq information. Give a warning! */
                       if ( dev2->irq && dev2->irq != irq && \
                            (!(pci_probe & PCI_USE_PIRQ_MASK) || \
                            ((1 << dev2->irq) & mask)) ) {
#ifndef CONFIG_PCI_MSI
                                  printk(KERN_INFO "IRQ routing conflict for %s, have irq %d, want irq %d\n",
                                            pci_name(dev2), dev2->irq, irq);
#endif
                                  continue;
                       }
                            dev2->irq = irq;
                            pirq_penalty[irq]++;
                            if (dev != dev2)
                                     printk(KERN_INFO "PCI: Sharing IRQ %d with %s\n", irq, pci_name(dev2));
                   }
         }
         return 1;
}
由此上面可能更改了输入引脚线的对应的IRQ,如果有其它设备的输出引脚对应在这个输入引脚上,则同样需要更新它对应的IRQ.
 
到这里,我们能够得出每个设备对应的IRQ.事实上,到这个地方,只是更新了配置错误的pci设备,以及跟他们在同一条输入信上的设备的IRQ.其它的设备并没有更新.在linux中,把pci设备的IRQ更新推迟到设备要被使用的时候再来更新.因为有可能设备之后不会用到.
其它设备的IRQ更新是由其驱动完成的.一般都会在驱动程序里调用pirq_enable_irq()接口.对其的中断功能进行初始化.这部份我们在分析pci驱动架构的时候来统一分析.
 
三:小结
在这一小节里,讨论了怎样确定PCI设备的中断号.在下一节里,我们来讨论一下关于PCI设备的I/O和设备内存的分配问题.
阅读(1634) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~