免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1331 | 回复: 1
打印 上一主题 下一主题

linux内核中断、异常 3................. [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-12-28 16:28 |只看该作者 |倒序浏览

linux内核中断、异常 3.................








清楚了中断机制和内核中有关数据结构的初始化以后,我们就从中断请求的发生到CPU的相应,再到中断服务程序的调用与返回,沿着CPU所经过的路线走一遍。

当某个外设已经产生了依次中断请求后,该请求通过中断控制器i8259A到达CPU的“中断请求”引线INTR。由于中断时开着的,所以CPU在执行完当前指令后就来相应该次中断请求。

中断向量的设置和初始化主要在setup.s文件中设置了一部分(上面已经介绍),在start_kernel函数中init_IRQ函数最终调用函数


view plaincopy to clipboardprint?
  1. 01.void __init native_init_IRQ(void)  
  2. 02.{  
  3. 03.    int i;  
  4. 04.  
  5. 05.    /* Execute any quirks before the call gates are initialised: */  
  6. 06.    x86_init.irqs.pre_vector_init();  
  7. 07.  
  8. 08.    apic_intr_init();  
  9. 09.  
  10. 10.    /*
  11. 11.     * Cover the whole vector space, no vector can escape
  12. 12.     * us. (some of these will be overridden and become
  13. 13.     * 'special' SMP interrupts)
  14. 14.     *//*更新外部中断(IRQ)的IDT表项*/  
  15. 15.    for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {  
  16. 16.        /* IA32_SYSCALL_VECTOR could be used in trap_init already. */  
  17. 17.        if (!test_bit(i, used_vectors))/*跳过系统调用(trap)使用过的槽位*/  
  18. 18.            set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);  
  19. 19.    }  
  20. 20.  
  21. 21.    if (!acpi_ioapic)  
  22. 22.        setup_irq(2, &irq2);  
  23. 23.  
  24. 24.#ifdef CONFIG_X86_32   
  25. 25.    /*
  26. 26.     * External FPU? Set up irq13 if so, for
  27. 27.     * original braindamaged IBM FERR coupling.
  28. 28.     */  
  29. 29.    if (boot_cpu_data.hard_math && !cpu_has_fpu)  
  30. 30.        setup_irq(FPU_IRQ, &fpu_irq);  
  31. 31.  
  32. 32.    irq_ctx_init(smp_processor_id());  
  33. 33.#endif   
  34. 34.}  
  35. void __init native_init_IRQ(void)
  36. {
  37.         int i;

  38.         /* Execute any quirks before the call gates are initialised: */
  39.         x86_init.irqs.pre_vector_init();

  40.         apic_intr_init();

  41.         /*
  42.          * Cover the whole vector space, no vector can escape
  43.          * us. (some of these will be overridden and become
  44.          * 'special' SMP interrupts)
  45.          *//*更新外部中断(IRQ)的IDT表项*/
  46.         for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {
  47.                 /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
  48.                 if (!test_bit(i, used_vectors))/*跳过系统调用(trap)使用过的槽位*/
  49.                         set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);
  50.         }

  51.         if (!acpi_ioapic)
  52.                 setup_irq(2, &irq2);

  53. #ifdef CONFIG_X86_32
  54.         /*
  55.          * External FPU? Set up irq13 if so, for
  56.          * original braindamaged IBM FERR coupling.
  57.          */
  58.         if (boot_cpu_data.hard_math && !cpu_has_fpu)
  59.                 setup_irq(FPU_IRQ, &fpu_irq);

  60.         irq_ctx_init(smp_processor_id());
  61. #endif
  62. }
复制代码
可以看到这里将interrupt[]数组中的值设置为中断服务函数而i-FIRST_EXTERNAL_VECTOR中的i为中断号,interrupt[]数组在汇编中实现
view plaincopy to clipboardprint?
  1. 01.ENTRY(interrupt)  
  2. 02..text  
  3. 03.    .p2align 5  
  4. 04.    .p2align CONFIG_X86_L1_CACHE_SHIFT  
  5. 05.ENTRY(irq_entries_start)  
  6. 06.    RING0_INT_FRAME  
  7. 07.vector=FIRST_EXTERNAL_VECTOR  
  8. 08..rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7  
  9. 09.    .balign 32  
  10. 10.  .rept 7  
  11. 11.    .if vector < NR_VECTORS  
  12. 12.      .if vector <> FIRST_EXTERNAL_VECTOR  
  13. 13.    CFI_ADJUST_CFA_OFFSET -4  
  14. 14.      .endif  
  15. 15.1:  pushl $(~vector+0x80)   /* Note: always in signed byte range */  
  16. 16.    CFI_ADJUST_CFA_OFFSET 4  
  17. 17.      .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6  
  18. 18.    jmp 2f  
  19. 19.      .endif  
  20. 20.      .previous  
  21. 21.    .long 1b  
  22. 22.      .text  
  23. 23.vector=vector+1  
  24. 24.    .endif  
  25. 25.  .endr  
  26. 26.2:  jmp common_interrupt  
  27. 27..endr  
  28. 28.END(irq_entries_start)  
  29. 29.  
  30. 30..previous  
  31. 31.END(interrupt)  
  32. ENTRY(interrupt)
  33. .text
  34.         .p2align 5
  35.         .p2align CONFIG_X86_L1_CACHE_SHIFT
  36. ENTRY(irq_entries_start)
  37.         RING0_INT_FRAME
  38. vector=FIRST_EXTERNAL_VECTOR
  39. .rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7
  40.         .balign 32
  41.   .rept        7
  42.     .if vector < NR_VECTORS
  43.       .if vector <> FIRST_EXTERNAL_VECTOR
  44.         CFI_ADJUST_CFA_OFFSET -4
  45.       .endif
  46. 1:        pushl $(~vector+0x80)        /* Note: always in signed byte range */
  47.         CFI_ADJUST_CFA_OFFSET 4
  48.       .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
  49.         jmp 2f
  50.       .endif
  51.       .previous
  52.         .long 1b
  53.       .text
  54. vector=vector+1
  55.     .endif
  56.   .endr
  57. 2:        jmp common_interrupt
  58. .endr
  59. END(irq_entries_start)

  60. .previous
  61. END(interrupt)
复制代码
上面的代码相当于下面代码


view plaincopy to clipboardprint?
01
  1. .346 ENTRY(irq_entries_start)   
  2. 02.347 .rept NR_IRQS /*348-354行重复NR_IRQS次,会被gcc编译时展开,不需手写这么多行重复的代码 */  
  3. 03.348 ALIGN   
  4. 04.349 1: pushl $vector-256 /*vector在354行递增 */  
  5. 05.350 jmp common_interrupt /*所有的外部中断处理函数的统一部分,以后再讲述*/   
  6. 06.351 .data   
  7. 07.352 .long 1b /*存储着指向349行的地址,但是随着348行-354被gcc展开,每次的值都不同 */  
  8. 08.353 .text   
  9. 09.354 vector=vector+1   
  10. 10.355 .endr /*与347行呼应*/   
  11. 11.356   
  12. 12.357 ALIGN  
复制代码
13./*首先342行和352行都处于.data段,虽然看起来它们是隔开的,但实际上被gcc安排在了连续的数据段内存中,同理在代码段内存中,354行与350行的指令序列也是连续存储的。另外,348-354行会被gcc展开NR_IRQS次,因此每次352行都会存储一个新的指针,该指针指向每个349行展开的新对象。最后在代码段内存中连续存储了NR_IRQS个代码片断,首地址由irq_entries_start指向。而在数据段内存中连续存储了NR_IRQS个指针,首址存储在interrupt这个全局变量中。这样,例如IRQ号是0 (从init_IRQ()的404行知道,它对应的中断向量是FIRST_EXTERNAL_VECTOR)的中断通过中断门后会触发interrput[0],从而执行:
  1. 14.pushl 0-256  
  2. 15.jmp common_interrupt  
  3. 16.的代码片断,进入到Linux内核安排好的中断入口路径 */  
  4. 346 ENTRY(irq_entries_start)
  5. 347 .rept NR_IRQS /*348-354行重复NR_IRQS次,会被gcc编译时展开,不需手写这么多行重复的代码 */
  6. 348 ALIGN
  7. 349 1: pushl $vector-256 /*vector在354行递增 */
  8. 350 jmp common_interrupt /*所有的外部中断处理函数的统一部分,以后再讲述*/
  9. 351 .data
  10. 352 .long 1b /*存储着指向349行的地址,但是随着348行-354被gcc展开,每次的值都不同 */
  11. 353 .text
  12. 354 vector=vector+1
  13. 355 .endr /*与347行呼应*/
  14. 356
  15. 357 ALIGN
复制代码
/*首先342行和352行都处于.data段,虽然看起来它们是隔开的,但实际上被gcc安排在了连续的数据段内存中,同理在代码段内存中,354行与350行的指令序列也是连续存储的。另外,348-354行会被gcc展开NR_IRQS次,因此每次352行都会存储一个新的指针,该指针指向每个349行展开的新对象。最后在代码段内存中连续存储了NR_IRQS个代码片断,首地址由irq_entries_start指向。而在数据段内存中连续存储了NR_IRQS个指针,首址存储在interrupt这个全局变量中。这样,例如IRQ号是0 (从init_IRQ()的404行知道,它对应的中断向量是FIRST_EXTERNAL_VECTOR)的中断通过中断门后会触发interrput[0],从而执行:
pushl 0-256
jmp common_interrupt
的代码片断,进入到Linux内核安排好的中断入口路径 */
common_interrupt:


view plaincopy to clipboardprint?
  1. 01.common_interrupt:  
  2. 02.    /*将中断向量号减256。内核用负数表示所有的中断*/  
  3. 03.    addl $-0x80,(%esp)  /* Adjust vector into the [-256,-1] range */  
  4. 04.    /*调用SAVE_ALL宏保存寄存器的值*/  
  5. 05.    SAVE_ALL  
  6. 06.    TRACE_IRQS_OFF  
  7. 07.    /*保存栈顶地址*/  
  8. 08.    movl %esp,%eax  
  9. 09.    /*调用do_IRQ函数*/  
  10. 10.    call do_IRQ  
  11. 11.    /*从中断返回*/  
  12. 12.    jmp ret_from_intr  
  13. 13.ENDPROC(common_interrupt)  
  14. 14.    CFI_ENDPROC  
  15. common_interrupt:
  16.         /*将中断向量号减256。内核用负数表示所有的中断*/
  17.         addl $-0x80,(%esp)        /* Adjust vector into the [-256,-1] range */
  18.         /*调用SAVE_ALL宏保存寄存器的值*/
  19.         SAVE_ALL
  20.         TRACE_IRQS_OFF
  21.         /*保存栈顶地址*/
  22.         movl %esp,%eax
  23.         /*调用do_IRQ函数*/
  24.         call do_IRQ
  25.         /*从中断返回*/
  26.         jmp ret_from_intr
  27. ENDPROC(common_interrupt)
复制代码
CFI_ENDPROC保存现场后调用do_IRQ函数进入c语言中执行中断函数


view plaincopy to clipboardprint?
  1. 01.unsigned int __irq_entry do_IRQ(struct pt_regs *regs)  
  2. 02.{  
  3. 03.    /*取得原来的寄存器*/  
  4. 04.    struct pt_regs *old_regs = set_irq_regs(regs);  
  5. 05.  
  6. 06.    /* high bit used in ret_from_ code  */  
  7. 07.    /*取得中断向量号,通过汇编代码中栈中保存
  8. 08.    的寄存器的顺序和前面c函数中的idt的初始化
  9. 09.    可以得知这里返回的值再取补
  10. 10.    正好是我们要的中断号*/  
  11. 11.    unsigned vector = ~regs->orig_ax;  
  12. 12.    unsigned irq;  
  13. 13.    /*退出idle进程*/  
  14. 14.    exit_idle();  
  15. 15.    /*进入中断*/  
  16. 16.    irq_enter();  
  17. 17.    /*中断线号与设备的中断号之间对应关系
  18. 18.    ,由系统分派,分派表是一个per-cpu变量vector_irq*/  
  19. 19.    irq = __get_cpu_var(vector_irq)[vector];  
  20. 20.  
  21. 21.    if (!handle_irq(irq, regs)) {/*处理*/  
  22. 22.        ack_APIC_irq();/*应答apic*/  
  23. 23.  
  24. 24.        if (printk_ratelimit())  
  25. 25.            pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",  
  26. 26.                __func__, smp_processor_id(), vector, irq);  
  27. 27.    }  
  28. 28.  
  29. 29.    irq_exit();  
  30. 30.  
  31. 31.    set_irq_regs(old_regs);  
  32. 32.    return 1;  
  33. 33.}  
  34. unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
  35. {
  36.         /*取得原来的寄存器*/
  37.         struct pt_regs *old_regs = set_irq_regs(regs);

  38.         /* high bit used in ret_from_ code  */
  39.         /*取得中断向量号,通过汇编代码中栈中保存
  40.         的寄存器的顺序和前面c函数中的idt的初始化
  41.         可以得知这里返回的值再取补
  42.         正好是我们要的中断号*/
  43.         unsigned vector = ~regs->orig_ax;
  44.         unsigned irq;
  45.         /*退出idle进程*/
  46.         exit_idle();
  47.         /*进入中断*/
  48.         irq_enter();
  49.         /*中断线号与设备的中断号之间对应关系
  50.         ,由系统分派,分派表是一个per-cpu变量vector_irq*/
  51.         irq = __get_cpu_var(vector_irq)[vector];

  52.         if (!handle_irq(irq, regs)) {/*处理*/
  53.                 ack_APIC_irq();/*应答apic*/

  54.                 if (printk_ratelimit())
  55.                         pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
  56.                                 __func__, smp_processor_id(), vector, irq);
  57.         }

  58.         irq_exit();

  59.         set_irq_regs(old_regs);
  60.         return 1;
  61. }
复制代码
view plaincopy to clipboardprint?
  1. 01./*下面是处理函数。函数根据中断号,查找相应的desc结构*/  
  2. 02.bool handle_irq(unsigned irq, struct pt_regs *regs)  
  3. 03.{  
  4. 04.    struct irq_desc *desc;  
  5. 05.    int overflow;  
  6. 06.  
  7. 07.    overflow = check_stack_overflow();  
  8. 08.  
  9. 09.    desc = irq_to_desc(irq);/*取得irq对应的中断描述符*/  
  10. 10.    if (unlikely(!desc))  
  11. 11.        return false;  
  12. 12.    /*如果是在中断栈上调用,则稍微复杂一点
  13. 13.    ,需要先构造一个中断栈,再调用handle_irq*/  
  14. 14.    if (!execute_on_irq_stack(overflow, desc, irq)) {  
  15. 15.        if (unlikely(overflow))  
  16. 16.            print_stack_overflow();  
  17. 17.        /*handle_irq函数指针,指向了handle_level_irq,
  18. 18.        或者是handle_edge_irq。不论是哪一种,
  19. 19.        中断电流处理函数在会调用handle_IRQ_event
  20. 20.        进一步处理,handle_IRQ_event函数的本质是
  21. 21.        遍历中断号上所有的action,调用其handler。
  22. 22.        这是在设备驱动初始化时向中断子系统注册的
  23. 23.        */  
  24. 24.        desc->handle_irq(irq, desc);  
  25. 25.    }  
  26. 26.  
  27. 27.    return true;  
  28. 28.}  
  29. /*下面是处理函数。函数根据中断号,查找相应的desc结构*/
  30. bool handle_irq(unsigned irq, struct pt_regs *regs)
  31. {
  32.         struct irq_desc *desc;
  33.         int overflow;

  34.         overflow = check_stack_overflow();

  35.         desc = irq_to_desc(irq);/*取得irq对应的中断描述符*/
  36.         if (unlikely(!desc))
  37.                 return false;
  38.         /*如果是在中断栈上调用,则稍微复杂一点
  39.         ,需要先构造一个中断栈,再调用handle_irq*/
  40.         if (!execute_on_irq_stack(overflow, desc, irq)) {
  41.                 if (unlikely(overflow))
  42.                         print_stack_overflow();
  43.                 /*handle_irq函数指针,指向了handle_level_irq,
  44.                 或者是handle_edge_irq。不论是哪一种,
  45.                 中断电流处理函数在会调用handle_IRQ_event
  46.                 进一步处理,handle_IRQ_event函数的本质是
  47.                 遍历中断号上所有的action,调用其handler。
  48.                 这是在设备驱动初始化时向中断子系统注册的
  49.                 */
  50.                 desc->handle_irq(irq, desc);
  51.         }

  52.         return true;
  53. }
复制代码
由上面的注释知,我们直接看handle_IRQ_event函数


view plaincopy to clipboardprint?
  1. 01.irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)  
  2. 02.{  
  3. 03.    irqreturn_t ret, retval = IRQ_NONE;  
  4. 04.    unsigned int status = 0;  
  5. 05.    /*为CPU会禁止中断,这里将其打开,如果没有指定
  6. 06.    IRQF_DISABLED标志的话,它表示处理程序在中断禁止
  7. 07.    情况下运行*/  
  8. 08.    if (!(action->flags & IRQF_DISABLED))  
  9. 09.        local_irq_enable_in_hardirq();  
  10. 10.  
  11. 11.    do {/*遍历当前irq的action链表中的所有action,调用之*/  
  12. 12.        trace_irq_handler_entry(irq, action);/*打开中断跟踪*/  
  13. 13.        ret = action->handler(irq, action->dev_id);/*调用中断函数*/  
  14. 14.        trace_irq_handler_exit(irq, action, ret);/*关闭中断跟踪*/  
  15. 15.  
  16. 16.        switch (ret) {  
  17. 17.        case IRQ_WAKE_THREAD:  
  18. 18.            /*
  19. 19.             * Set result to handled so the spurious check
  20. 20.             * does not trigger.
  21. 21.             */  
  22. 22.            ret = IRQ_HANDLED;  
  23. 23.  
  24. 24.            /*
  25. 25.             * Catch drivers which return WAKE_THREAD but
  26. 26.             * did not set up a thread function
  27. 27.             */  
  28. 28.            if (unlikely(!action->thread_fn)) {  
  29. 29.                warn_no_thread(irq, action);  
  30. 30.                break;  
  31. 31.            }  
  32. 32.  
  33. 33.            /*
  34. 34.             * Wake up the handler thread for this
  35. 35.             * action. In case the thread crashed and was
  36. 36.             * killed we just pretend that we handled the
  37. 37.             * interrupt. The hardirq handler above has
  38. 38.             * disabled the device interrupt, so no irq
  39. 39.             * storm is lurking.
  40. 40.             */  
  41. 41.            if (likely(!test_bit(IRQTF_DIED,  
  42. 42.                         &action->thread_flags))) {  
  43. 43.                set_bit(IRQTF_RUNTHREAD, &action->thread_flags);  
  44. 44.                wake_up_process(action->thread);  
  45. 45.            }  
  46. 46.  
  47. 47.            /* Fall through to add to randomness */  
  48. 48.        case IRQ_HANDLED:  
  49. 49.            status |= action->flags;  
  50. 50.            break;  
  51. 51.  
  52. 52.        default:  
  53. 53.            break;  
  54. 54.        }  
  55. 55.  
  56. 56.        retval |= ret;  
  57. 57.        action = action->next;/*取得下一个action,如果有的话*/  
  58. 58.    } while (action);  
  59. 59.    /*如果指定了标志,则使用中断间隔时间为随机数产生器产生熵*/  
  60. 60.    if (status & IRQF_SAMPLE_RANDOM)  
  61. 61.        add_interrupt_randomness(irq);  
  62. 62.    /*关闭中断,do_IRQ进入下一轮循环——等待新的中断到来*/  
  63. 63.    local_irq_disable();  
  64. 64.  
  65. 65.    return retval;  
  66. 66.}  
  67. irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
  68. {
  69.         irqreturn_t ret, retval = IRQ_NONE;
  70.         unsigned int status = 0;
  71.         /*为CPU会禁止中断,这里将其打开,如果没有指定
  72.         IRQF_DISABLED标志的话,它表示处理程序在中断禁止
  73.         情况下运行*/
  74.         if (!(action->flags & IRQF_DISABLED))
  75.                 local_irq_enable_in_hardirq();

  76.         do {/*遍历当前irq的action链表中的所有action,调用之*/
  77.                 trace_irq_handler_entry(irq, action);/*打开中断跟踪*/
  78.                 ret = action->handler(irq, action->dev_id);/*调用中断函数*/
  79.                 trace_irq_handler_exit(irq, action, ret);/*关闭中断跟踪*/

  80.                 switch (ret) {
  81.                 case IRQ_WAKE_THREAD:
  82.                         /*
  83.                          * Set result to handled so the spurious check
  84.                          * does not trigger.
  85.                          */
  86.                         ret = IRQ_HANDLED;

  87.                         /*
  88.                          * Catch drivers which return WAKE_THREAD but
  89.                          * did not set up a thread function
  90.                          */
  91.                         if (unlikely(!action->thread_fn)) {
  92.                                 warn_no_thread(irq, action);
  93.                                 break;
  94.                         }

  95.                         /*
  96.                          * Wake up the handler thread for this
  97.                          * action. In case the thread crashed and was
  98.                          * killed we just pretend that we handled the
  99.                          * interrupt. The hardirq handler above has
  100.                          * disabled the device interrupt, so no irq
  101.                          * storm is lurking.
  102.                          */
  103.                         if (likely(!test_bit(IRQTF_DIED,
  104.                                              &action->thread_flags))) {
  105.                                 set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
  106.                                 wake_up_process(action->thread);
  107.                         }

  108.                         /* Fall through to add to randomness */
  109.                 case IRQ_HANDLED:
  110.                         status |= action->flags;
  111.                         break;

  112.                 default:
  113.                         break;
  114.                 }

  115.                 retval |= ret;
  116.                 action = action->next;/*取得下一个action,如果有的话*/
  117.         } while (action);
  118.         /*如果指定了标志,则使用中断间隔时间为随机数产生器产生熵*/
  119.         if (status & IRQF_SAMPLE_RANDOM)
  120.                 add_interrupt_randomness(irq);
  121.         /*关闭中断,do_IRQ进入下一轮循环——等待新的中断到来*/
  122.         local_irq_disable();

  123.         return retval;
  124. }
复制代码
可知,在handle_event_IRQ函数中完成了具体的中断函数响应执行工作。也在这里我们看出,多个中断服务函数共享同一个中断号时,在每次中断到来时,在中断向量上的服务函数队列中所有的服务函数将执行一遍。而对于中断线程也会在这里得到执行。

到这里中断的初始化、中断服务的相应的分析完了,对于中断返回的退出,基本和异常的返回是一样的,我们从代码中可以看出


view plaincopy to clipboardprint?
  1. 01.ret_from_exception:  
  2. 02.    preempt_stop(CLBR_ANY)  
  3. 03.ret_from_intr:  
  4. 04.    GET_THREAD_INFO(%ebp)  
  5. 05.      ....  
  6. ret_from_exception:
  7.         preempt_stop(CLBR_ANY)
  8. ret_from_intr:
  9.         GET_THREAD_INFO(%ebp)
复制代码
....
至此,中断和异常的分析基本上就这样了,中间有很多细节是值得去理解的。包括汇编和c的实现技巧。对于软中断和任务队列将在后面给予介绍。

论坛徽章:
0
2 [报告]
发表于 2011-12-28 16:28 |只看该作者
谢谢分享
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP