zoukankan      html  css  js  c++  java
  • printk -go on

    printk简介

        printk是在内核中运行的向控制台输出显示的函数,Linux内核先分配一个静态的临时缓冲区log_buf,然后调用vscnprintf格式化显示字符串,并返回格式化之后字符串的长度,最后调用tty驱动初始化注册绑定好的console.write()实现向串口输出。

    目前新版内核解决一些lock-less问题:见https://lkml.org/lkml/2020/7/9/551

    pdf 文本附件:https://www.linuxplumbersconf.org/event/4/contributions/290/attachments/276/463/lpc2019_jogness_printk.pdf

    其核心函数如下:

    asmlinkage int vprintk_emit(int facility, int level,
                    const char *dict, size_t dictlen,
                    const char *fmt, va_list args)
    {
        static bool recursion_bug;
        static char textbuf[LOG_LINE_MAX];
        char *text = textbuf;
        size_t text_len = 0;
        enum log_flags lflags = 0;
        unsigned long flags;
        int this_cpu;
        int printed_len = 0;
        int nmi_message_lost;
        bool in_sched = false;
        /* cpu currently holding logbuf_lock in this function */
        static unsigned int logbuf_cpu = UINT_MAX;
    
        if (level == LOGLEVEL_SCHED) {
            level = LOGLEVEL_DEFAULT;
            in_sched = true;
        }
    
        boot_delay_msec(level);
        printk_delay();
    
        local_irq_save(flags);
        this_cpu = smp_processor_id();
    
        /*
         * Ouch, printk recursed into itself!
         处理printk中再次调用printk的情况
         */
        if (unlikely(logbuf_cpu == this_cpu)) {
            /*
             * If a crash is occurring during printk() on this CPU,
             * then try to get the crash message out but make sure
             * we can't deadlock. Otherwise just return to avoid the
             * recursion and return - but flag the recursion so that
             * it can be printed at the next appropriate moment:
             */
            if (!oops_in_progress && !lockdep_recursing(current)) {
                recursion_bug = true;
                local_irq_restore(flags);
                return 0;
            }
            zap_locks();
        
    zap_locks()函数主要作用就是在prink运行期间,如果printk local CPU发生了crash,确保不能死锁,并且每隔30s初始化锁logbuf_lock和console_sem,留时间给控制台打印完全的oops信息
    /*
     * Zap console related locks when oopsing.
     * To leave time for slow consoles to print a full oops,
     * only zap at most once every 30 seconds.
     */
    static void zap_locks(void)
    {
        static unsigned long oops_timestamp;
    
        if (time_after_eq(jiffies, oops_timestamp) &&
            !time_after(jiffies, oops_timestamp + 30 * HZ))
            return;
    
        oops_timestamp = jiffies;
    
        debug_locks_off();
        /* If a crash is occurring, make sure we can't deadlock */
        raw_spin_lock_init(&logbuf_lock);
        /* And make sure that we print immediately */
        sema_init(&console_sem, 1);
    }
        }
    
        lockdep_off();
        /* This stops the holder of console_sem just where we want him */
        raw_spin_lock(&logbuf_lock);
        logbuf_cpu = this_cpu;
    
        if (unlikely(recursion_bug)) {
            static const char recursion_msg[] =
                "BUG: recent printk recursion!";
    
            recursion_bug = false;
            /* emit KERN_CRIT message */
            printed_len += log_store(0, 2, LOG_PREFIX|LOG_NEWLINE, 0,
                         NULL, 0, recursion_msg,
                         strlen(recursion_msg));
        }
    
        nmi_message_lost = get_nmi_message_lost();
        if (unlikely(nmi_message_lost)) {
            text_len = scnprintf(textbuf, sizeof(textbuf),
                         "BAD LUCK: lost %d message(s) from NMI context!",
                         nmi_message_lost);
            printed_len += log_store(0, 2, LOG_PREFIX|LOG_NEWLINE, 0,
                         NULL, 0, textbuf, text_len);
        }
    
        /*
         * The printf needs to come first; we need the syslog
         * prefix which might be passed-in as a parameter.
         */
        text_len = vscnprintf(text, sizeof(textbuf), fmt, args);
    
        /* mark and strip a trailing newline */
        if (text_len && text[text_len-1] == '
    ') {
            text_len--;
            lflags |= LOG_NEWLINE;
        }
    
        /* strip kernel syslog prefix and extract log level or control flags */
        if (facility == 0) {
            int kern_level = printk_get_level(text);
    
            if (kern_level) {
                const char *end_of_header = printk_skip_level(text);
                switch (kern_level) {
                case '0' ... '7':
                    if (level == LOGLEVEL_DEFAULT)
                        level = kern_level - '0';
                    /* fallthrough */
                case 'd':    /* KERN_DEFAULT */
                    lflags |= LOG_PREFIX;
                }
                /*
                 * No need to check length here because vscnprintf
                 * put '' at the end of the string. Only valid and
                 * newly printed level is detected.
                 */
                text_len -= end_of_header - text;
                text = (char *)end_of_header;
            }
        }
    
        if (level == LOGLEVEL_DEFAULT)
            level = default_message_loglevel;
    
        if (dict)
            lflags |= LOG_PREFIX|LOG_NEWLINE;
    
        if (!(lflags & LOG_NEWLINE)) {
            /*
             * Flush the conflicting buffer. An earlier newline was missing,
             * or another task also prints continuation lines.
             */
            if (cont.len && (lflags & LOG_PREFIX || cont.owner != current))
                cont_flush(LOG_NEWLINE);
    
            /* buffer line if possible, otherwise store it right away */
            if (cont_add(facility, level, text, text_len))
                printed_len += text_len;
            else
                printed_len += log_store(facility, level,
                             lflags | LOG_CONT, 0,
                             dict, dictlen, text, text_len);
        } else {
            bool stored = false;
    
            /*
             * If an earlier newline was missing and it was the same task,
             * either merge it with the current buffer and flush, or if
             * there was a race with interrupts (prefix == true) then just
             * flush it out and store this line separately.
             * If the preceding printk was from a different task and missed
             * a newline, flush and append the newline.
             */
            if (cont.len) {
                if (cont.owner == current && !(lflags & LOG_PREFIX))
                    stored = cont_add(facility, level, text,
                              text_len);
                cont_flush(LOG_NEWLINE);
            }
    
            if (stored)
                printed_len += text_len;
            else
                printed_len += log_store(facility, level, lflags, 0,
                             dict, dictlen, text, text_len);
        }
    
        logbuf_cpu = UINT_MAX;
        raw_spin_unlock(&logbuf_lock);
        lockdep_on();
        local_irq_restore(flags);
    
        /* If called from the scheduler, we can not call up(). */
        if (!in_sched) {
            lockdep_off();
            /*
             * Try to acquire and then immediately release the console
             * semaphore.  The release will print out buffers and wake up
             * /dev/kmsg and syslog() users.
             */
            if (console_trylock())
                console_unlock();
            lockdep_on();
        }
    
        return printed_len;
    }
    EXPORT_SYMBOL(vprintk_emit);

      asmlinkage表示将函数参数存放在局部栈中,va_start和va_end是获取可变参数的方法,该方法读取可变参数的过程其实就是在 堆栈中,使用 指针,遍历 堆栈段中的 参数列表,从低地址到高地址一个一个地把参数内容读出来的过程.

      vprintk_emit函数调用了console_unlock将log通过串口进行输出的

    所以为了CPU重启后,内核dmesg不丢失,一般固定内核静态缓冲区log_buf地址!!!也就是log_buf 地址一直是某一个物理地址,只要是非断电重启,其数据会一直在

    由于目前使用了 lock;对性能有些影响,安装多核编程法则,此时使用无锁队列-lock-free 比较好

      prink第一个阶段是将log放到buff, 第二阶段是将buff数据送到uart。Dump log到uart console是一个同步的操作过程。在此期间,IRQ是被disabled,CPU也是在wait,一直到dump完成。由于此阶段的速度会很慢, 并且会把local的irq disable. 所以会导致local cpu sched, 如果在此cpu 的thread都会被阻塞,严重的可能导致系统阻塞

    console_unlock函数调用流程如下

    /**
     * console_trylock - try to lock the console system for exclusive use.
     *
     * Try to acquire a lock which guarantees that the caller has exclusive
     * access to the console system and the console_drivers list.
     *
     * returns 1 on success, and 0 on failure to acquire the lock.
     */
    int console_trylock(void)
    {
        if (down_trylock_console_sem())
            return 0;
        if (console_suspended) {
            up_console_sem();
            return 0;
        }
        console_locked = 1;
        /*
         * When PREEMPT_COUNT disabled we can't reliably detect if it's
         * safe to schedule (e.g. calling printk while holding a spin_lock),
         * because preempt_disable()/preempt_enable() are just barriers there
         * and preempt_count() is always 0.
         *
         * RCU read sections have a separate preemption counter when
         * PREEMPT_RCU enabled thus we must take extra care and check
         * rcu_preempt_depth(), otherwise RCU read sections modify
         * preempt_count().
         */
        console_may_schedule = !oops_in_progress &&
                preemptible() &&
                !rcu_preempt_depth();
        return 1;
    }

    logbuf_lock保护日志缓冲区,而console_sem保护对控制台驱动程序列表和实际控制台本身的访问。

    /**
     * console_unlock - unlock the console system
     *
     * Releases the console_lock which the caller holds on the console system
     * and the console driver list.
     *
     * While the console_lock was held, console output may have been buffered
     * by printk().  If this is the case, console_unlock(); emits
     * the output prior to releasing the lock.
     *
     * If there is output waiting, we wake /dev/kmsg and syslog() users.
     *
     * console_unlock(); may be called from any context.
     */
    void console_unlock(void)
    {
        static char ext_text[CONSOLE_EXT_LOG_MAX];
        static char text[LOG_LINE_MAX + PREFIX_MAX];
        static u64 seen_seq;
        unsigned long flags;
        bool wake_klogd = false;
        bool do_cond_resched, retry;
    
        if (console_suspended) {
            up_console_sem();
            return;
        }
    
        /*
         * Console drivers are called under logbuf_lock, so
         * @console_may_schedule should be cleared before; however, we may
         * end up dumping a lot of lines, for example, if called from
         * console registration path, and should invoke cond_resched()
         * between lines if allowable.  Not doing so can cause a very long
         * scheduling stall on a slow console leading to RCU stall and
         * softlockup warnings which exacerbate the issue with more
         * messages practically incapacitating the system.
         */
        do_cond_resched = console_may_schedule;
        console_may_schedule = 0;
    
    again:
        /*
         * We released the console_sem lock, so we need to recheck if
         * cpu is online and (if not) is there at least one CON_ANYTIME
         * console.
         */
        if (!can_use_console()) {
            console_locked = 0;
            up_console_sem();
            return;
        }
    
        /* flush buffered message fragment immediately to console */
        console_cont_flush(text, sizeof(text));
    
        for (;;) {
            struct printk_log *msg;
            size_t ext_len = 0;
            size_t len;
            int level;
    
            raw_spin_lock_irqsave(&logbuf_lock, flags);
            if (seen_seq != log_next_seq) {
                wake_klogd = true; // 控制判断是否唤醒syslogd的标志位
                seen_seq = log_next_seq;
            }
    
            if (console_seq < log_first_seq) {
                len = sprintf(text, "** %u printk messages dropped ** ",
                          (unsigned)(log_first_seq - console_seq));
    
                /* messages are gone, move to first one */
                console_seq = log_first_seq;
                console_idx = log_first_idx;
                console_prev = 0;
            } else {
                len = 0;
            }
    skip:
            if (console_seq == log_next_seq)
                break;
    
            msg = log_from_idx(console_idx);
            if (msg->flags & LOG_NOCONS) {
                /*
                 * Skip record we have buffered and already printed
                 * directly to the console when we received it.
                 */
                console_idx = log_next(console_idx);
                console_seq++;
                /*
                 * We will get here again when we register a new
                 * CON_PRINTBUFFER console. Clear the flag so we
                 * will properly dump everything later.
                 */
                msg->flags &= ~LOG_NOCONS;
                console_prev = msg->flags;
                goto skip;
            }
    
            level = msg->level;
            //计算本次log_buf中打印输出索引的范围
            len += msg_print_text(msg, console_prev, false,
                          text + len, sizeof(text) - len);
            if (nr_ext_console_drivers) {
                ext_len = msg_print_ext_header(ext_text,
                            sizeof(ext_text),
                            msg, console_seq, console_prev);
                ext_len += msg_print_ext_body(ext_text + ext_len,
                            sizeof(ext_text) - ext_len,
                            log_dict(msg), msg->dict_len,
                            log_text(msg), msg->text_len);
            }
            console_idx = log_next(console_idx);
            console_seq++;
            console_prev = msg->flags;
            raw_spin_unlock(&logbuf_lock);
    
            stop_critical_timings();    /* don't trace print latency */
            //con->write()
            call_console_drivers(level, ext_text, ext_len, text, len);
            start_critical_timings();
            local_irq_restore(flags);
    
            if (do_cond_resched)
                cond_resched();
        }
        console_locked = 0;
    
        /* Release the exclusive_console once it is used */
        if (unlikely(exclusive_console))
            exclusive_console = NULL;
    
        raw_spin_unlock(&logbuf_lock);
    
        up_console_sem();
    
        /*
         * Someone could have filled up the buffer again, so re-check if there's
         * something to flush. In case we cannot trylock the console_sem again,
         * there's a new owner and the console_unlock() from them will do the
         * flush, no worries.
         */
        raw_spin_lock(&logbuf_lock);
        retry = console_seq != log_next_seq;
        raw_spin_unlock_irqrestore(&logbuf_lock, flags);
    
        if (retry && console_trylock())
            goto again;
    
        if (wake_klogd)
            wake_up_klogd();// 唤醒sys_logd进程
    }
    EXPORT_SYMBOL(console_unlock);

      将消息放入日志缓冲区后,printk()尝试获取console_sem。它甚至可以在中断上下文中执行此操作,因为down_trylock()不会 hibernate 。如果获取了信号量,则可以继续将日志缓冲区内容发送到控制台。如果不获取信号量,则是控制台信号量持有者的责任,即通过console_unlock()将日志缓冲区内容发送到控制台。
    console_unlock()发送到控制台时,可能会有其他CPU调用printk()。因此console_unlock()循环,直到没有其他要发送到控制台的内容为止。在每个循环中,它都会在logbuf_lock下获取指向日志缓冲区一部分的指针以发送到控制台,然后不再在logbuf_lock下发送输出到控制台。在将内容发送到控制台时,由于未采用logbuf_lock,因此其他CPU可以继续将内容添加到日志缓冲区。

      释放logbuf_lock之后但在up()加上console_sem之前,缓冲区可能已被填满。且logbuf_lockup()console_sem之前被释放,因为up()会引起唤醒,需要唤醒运行队列,这可能会对使用运行队列锁定调用的printk()产生优先级反转问题;可以使用补丁来更改此锁定方案:Jan Kara [PATCH 0/8 v4] printk: Cleanups and softlockup avoidance,除其他事项外,尝试避免CPU在console_unlock()上无限期循环(在大型系统上已通过慢速串行控制台记录了大量启动事件),将工作移交给其他CPU;并尝试最小化在printk()上禁用中断的时间。local_irq_save()可防止在本地CPU上处理中断,这可能会在从ISR返回时最终调用schedule()。 schedule()也可以在其他地方调用,但是由于应该可以在中断上下文中使用printk(),因此它调用的任何内容最终都不会调用schedule()(例如,出于这个原因,使用down_trylock()代替了down())。

    Jan Kara的[PATCH 3/8] printk: Enable interrupts before calling console_trylock_for_printk()尝试最小化禁用中断的时间。在该补丁程序之前,至少出于以下原因,禁用了中断:
    因此,上述补丁在后两种情况下不再禁用中断,而是将它们包装在preempt_disable()/preempt_enable()中,这允许中断,但不能抢占。

      • 保护不受logbuf_lock保护的数据。这种情况通常通过自旋锁的IRQ变体来解决(例如raw_spin_lock_irqsave()),但是在禁用中断的情况下,还有更多事情要运行。
      • 防止当前CPU突然更改。从can_use_console()调用的console_trylock_for_printk()询问:我们现在可以在此cpu上实际使用控制台吗?请注意:控制台驱动程序可能假定已分配了每cpu资源。转移到另一个CPU可能会造成困惑。
      • 虽然console_semdown_trylock()一起使用,所以中断也可以尝试printk()也不是问题,在按住console_sem()的同时被抢占会阻止其他任何人将其打印到控制台。

    您可以共享对该线程的引用吗?您可能会注意到在3.10中有一个cont缓冲区,该缓冲区保存最后一个换行符中的所有字符。当换行到达时,或在其他任务printk()时,它将刷新到“真实”日志缓冲区。
    在该线程上,除了以下摘录之外,我没有发现任何有关日志条目顺序的有效担忧:

    Well, I believe someone got DDetetccctted ed 113223 HHzz CPUCPU

    AFAIK完全是假的日志缓冲区的顺序由logbuf_lock保证,
    写入vprintk_emit()中的日志缓冲区时已禁用的中断会导致此事件,并使用lockt变体来执行此操作,当从raw_spin_lock_irqsave()上读取中断时会禁用该中断(console_unlock())。因此,访问日志缓冲区不受其他CPU干扰或中断的影响是安全的。
      在较新的内核中,仍然存在将日志行拆分为几个printk()调用的情况,这种情况被cont缓冲区覆盖,该缓冲区保存部分行,并在另一个CPU/中断干扰时刷新它们,因此可能会拆分日志行在几行中并且它们之间有不相关的日志行,但是任何日志行都不应有混合输出。
    导致损坏的另一个可能原因是,由于日志缓冲区是环形缓冲区,因此从理论上讲它可能会溢出,这意味着将覆盖先前的消息。
      该线程中的一个有效问题是日志缓冲区的及时输出。这是通过尝试在每个console_unlock()调用中调用vprintk_emit()(调用控制台驱动程序)来实现的。如果无法获取控制台信号,则该消息已经在日志缓冲区中,并且当前信号所有者将其输出到控制台。
      该线程中提到的一个有趣的事情是,在commit a0f1ccfd8d: "lockdep: do not recurse in printk"之前(printk.c beforeafter),中断在调用release_console_sem()(这是console_unlock()的前身)之前被重新启用。显然,启用lockdep(锁定验证程序,能够检测到可能的死锁和其他锁定问题,并启用 print 诊断)时,尝试从printk()进行printk时可能导致锁定。因此,对spin_{,un}lock_irq{save,restore}()的调用分为禁用/启用中断和获取/释放锁,在它们之间添加了lockdep_on/off()调用,并且禁用lockdep和中断已扩展为涵盖整个功能。
    回到:

    I see printk acquires logbuf_lock and writes to the log buffer and then tries to grab the console semaphore and releases logbuf_lock. Then inside console_unlock inside a loop it acquires logbuf_lock and disables interrupts, then releases logbuf_lock and calls the console drivers, and then restores interrupts. What's the purpose of this locking/disabling interrupt sequence?

    不仅从console_unlock()调用vprintk_emit(),还在注册新控制台时,在恢复控制台时,在将CPU挂起且将未决输出热插入控制台时也调用了console_unlock()……这些地方通常启用了中断。因此,logbuf_lock必须考虑到这一点。
      您似乎已经注意到,当在console_unlock()上使用raw_spin_lock_irqsave()时(它调用raw_spin_unlock())禁用了中断,但在释放锁(local_irq_console())时并没有(潜在地)重新启用了它们,但只有在call_console_drivers()之后才可能重新启用了它们(call_console_drivers())。我看到在禁用中断的情况下调用console_unlock()的唯一原因是为了避免CPU在我们下面进行更改(控制台驱动程序可以访问每个CPU变量)。
    一个有趣的数据点是-rt(实时)补丁集在call_console_drivers()中调用migrate_disable()/migrate_enable()之前,以及在console_unlock()(在-rt中受printk()保护,这不允许CPU迁移)之前重新启用中断。这样做是为了最小化PREEMPT_RT_FULL期间的中断等待时间。 ojit_code支持低中断延迟。您可以在printk.c in the linux-stable-rt tree at git.kernel.org上看到它,相关的修补程序是printk-rt-aware

    关于linux - printk中断禁用和锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25873754/

    转载 copy from  https://www.coder.work/article/6676391

    后续对此的改造:参考  https://linuxreviews.org/Linux_5.10_Will_Have_A_Fully_Lock-Less_Ring_Buffer

    console 驱动:

      芯片平台一般有好几个串口,具体选择哪个串口作为printk输出串口是可以配置的。我是通过cmdline配置console=ttyS0,115200,然后传递给内核,内核在启动时会解析cmdline,根据cmdline的解析结果来选择使用哪个串口,并进行串口注册。

      首先从设备树里加载UART的硬件资源,然后在uart模块init的时候,进行uart_register_driver()或者是platform_driver_register()。同时会调用xxx_console_setup()初始化串口模式,主要是初始化struct console,serial21285_console_write()函数在con->write()的时候调用,就完成了往串口数据的打印。

       register_console()首先遍历全局的console_drivers(**决定哪个driver输出printk log)*,然后根据早期硬件串口驱动注册时的struct console*的成员flags是否为CON_BOOT(引导控制台)。接下来获取preferred_console,从preferred_console = selected_console可以获取到串口号。然后接下来使用for循环匹配cmdline里的console name与注册时的newcon->name字段,最后把串口号放到console_drivers列表里,截止就完成了tty端口的指定了。

    / * The console driver calls this routine during kernel initialization
     * to register the console printing procedure with printk() and to
     * print any messages that were printed by the kernel before the
     * console driver was initialized.
     *
     * This can happen pretty early during the boot process (because of
     * early_printk) - sometimes before setup_arch() completes - be careful
     * of what kernel features are used - they may not be initialised yet.
     *
     * There are two types of consoles - bootconsoles (early_printk) and
     * "real" consoles (everything which is not a bootconsole) which are
     * handled differently.
     *  - Any number of bootconsoles can be registered at any time.
     *  - As soon as a "real" console is registered, all bootconsoles
     *    will be unregistered automatically.
     *  - Once a "real" console is registered, any attempt to register a
     *    bootconsoles will be rejected
     */
    void register_console(struct console *newcon)
    {
        int i;
        -----------------------------------
        if (console_drivers && console_drivers->flags & CON_BOOT)
            bcon = console_drivers;
    
        if (preferred_console < 0 || bcon || !console_drivers)
            preferred_console = selected_console;
    
        -------------------
        /*
         *    See if this console matches one we selected on
         *    the command line.
         */
        for (i = 0, c = console_cmdline;
             i < MAX_CMDLINECONSOLES && c->name[0];
             i++, c++) {
            if (!newcon->match ||
                newcon->match(newcon, c->name, c->index, c->options) != 0) {
                ----------------------
            newcon->flags |= CON_ENABLED;
            if (i == selected_console) {
                newcon->flags |= CON_CONSDEV;
                preferred_console = selected_console;
            }
            break;
        }
        ---------------------------------
            /*
             * We're about to replay the log buffer.  Only do this to the
             * just-registered console to avoid excessive message spam to
             * the already-registered consoles.
             */
            exclusive_console = newcon;
        }
        console_unlock();
        console_sysfs_notify();
        
    }
    EXPORT_SYMBOL(register_console);
    /*
     * Call the console drivers, asking them to write out
     * log_buf[start] to log_buf[end - 1].
     * The console_lock must be held.
     */
    static void call_console_drivers(int level,
                     const char *ext_text, size_t ext_len,
                     const char *text, size_t len)
    {
        struct console *con;
    
        trace_console(text, len);
    
        if (level >= console_loglevel && !ignore_loglevel)
            return;
        if (!console_drivers)
            return;
    
        for_each_console(con) {
            if (exclusive_console && con != exclusive_console)
                continue;
            if (!(con->flags & CON_ENABLED))
                continue;
            if (!con->write)
                continue;
            if (!cpu_online(smp_processor_id()) &&
                !(con->flags & CON_ANYTIME))
                continue;
            if (con->flags & CON_EXTENDED)
                con->write(con, ext_text, ext_len);
            else
                con->write(con, text, len);
        }
    }
    http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子
  • 相关阅读:
    第九篇装饰器基本原理
    第八篇迭代器协议和生产器
    python第六篇文件处理类型
    Python开发【第十篇】:模块
    习题练习
    2.2.6 集合
    第七篇 python基础之函数,递归,内置函数
    Python开发【第五篇】:Python基础之杂货铺
    Python开发【第四篇】:Python基础之函数
    详解CSS position属性
  • 原文地址:https://www.cnblogs.com/codestack/p/15228801.html
Copyright © 2011-2022 走看看