精华内容
下载资源
问答
  • Cisco Packet Tracer汉化

    万次阅读 2020-01-01 15:15:16
    Cisco Packet Tracer 汉化 ...PTUI可以翻译成不同的语言 将Simplified Chinese.ptl 复制粘贴到C:\Program Files\Cisco Packet Tracer 7.0\languages 打开Packet Tracer 在菜单栏中点击 Options | Pref...

    Cisco Packet Tracer 汉化

    Cisco Packet Tracer User 用户界面(PTUI)默认语言为英语。PTUI可以翻译成不同的语言

    1. Simplified Chinese.ptl 复制粘贴到C:\Program Files\Cisco Packet Tracer 7.0\languages
    2. 打开Packet Tracer
    3. 在菜单栏中点击 Options | Preferences在这里插入图片描述
    4. 在页面底部的“选择语言”框中选择Packet Tracer启动时将成为默认语言的语言在这里插入图片描述
    5. 单击 Change Language 按钮,然后点击Ok,退出程序,重新打开程序即可在这里插入图片描述
    展开全文
  • function tracer的插桩点几乎大于trace event插桩点两个数量级,而且trace event的插桩点是每一个插桩点都是独立控制的,而function tracer的插桩点默认是一起控制的(也可以通过set_ftrace_filter、set_ftrace_...

    如trace event一章的描述,任何一种trace都离不开以下流程:

    • 函数插桩。使用各种插桩方式把自己的trace函数插入到需要跟踪的probe point上;
    • input trace数据。在trace的probe函数被命中时,会存储数据到ringbuffer当中;这里还包括filter和trigger功能;
    • ouput trace数据。用户或者程序需要读出trace数据,根据需要输出ram数据或者是方面用户阅读的数据;对数据的解析,遵循谁存储谁提供解析规则的原则;

    以function tracer为首的tracer也遵循同样的流程。

    1、fucntion tracer

    function tracer最大的难点在于:dynamic ftrace时(如不做说明本文默认dynamic)对大量插桩点的管理。

    root:/d/tracing # cat available_filter_functions | wc -l
    49099
    root:/d/tracing #
    root:/d/tracing # cat available_events | wc -l
    987

    可以看到上述一个普通的系统,function tracer的插桩点为49099个,trace event的插桩点为987个。function tracer的插桩点几乎大于trace event插桩点两个数量级,而且trace event的插桩点是每一个插桩点都是独立控制的,而function tracer的插桩点默认是一起控制的(也可以通过set_ftrace_filter、set_ftrace_notrace来分开控制)。

    这么庞大的数量的插桩点。如果独立控制会消耗大量的内存,如果集中控制又怎么能实现部分的filter和command控制?一旦桩函数的实现不简洁,开销会迅速放大。对这部分感兴趣可以直接跳转到1.2.2、插桩点的动态管理

    1.1、插桩原理

    1.1.1、_mcount()

    众所周知function trace是利用_mcount()函数进行插桩的。在gcc使用了“-pg”选项以后,会在每个函数的入口插入以下语句:

    function:
        ...
        mov x0, x30
        bl _mcount
        [function's body ...]

    默认的_mcount函数是空操作:

    ENTRY(_mcount)
        ret
    ENDPROC(_mcount)

    每个函数入口插入对_mcount()函数的调用,就是gcc提供的插桩机制。我们可以重新定义_mcount()函数中的内容,调用想要执行的内容。

    1.1.2、static ftrace插桩

    static ftrace就是使用重定义_mcount()函数的方法来实现插桩的:

    这里写图片描述

    arch/arm64/kernel/entry-ftrace.S:

    #ifndef CONFIG_DYNAMIC_FTRACE
    /*
     * void _mcount(unsigned long return_address)
     * @return_address: return address to instrumented function
     *
     * This function makes calls, if enabled, to:
     *     - tracer function to probe instrumented function's entry,
     *     - ftrace_graph_caller to set up an exit hook
     */
    ENTRY(_mcount)
        mcount_enter
    
        adrp    x0, ftrace_trace_function
        ldr x2, [x0, #:lo12:ftrace_trace_function]
        adr x0, ftrace_stub
        cmp x0, x2          // if (ftrace_trace_function
        b.eq    skip_ftrace_call    //     != ftrace_stub) {
    
        mcount_get_pc   x0      //       function's pc
        mcount_get_lr   x1      //       function's lr (= parent's pc)
        blr x2          //   (*ftrace_trace_function)(pc, lr);
    
    #ifndef CONFIG_FUNCTION_GRAPH_TRACER
    skip_ftrace_call:           //   return;
        mcount_exit         // }
    #else
        mcount_exit         //   return;
                        // }
    skip_ftrace_call:
        adrp    x1, ftrace_graph_return
        ldr x2, [x1, #:lo12:ftrace_graph_return]
        cmp x0, x2          //   if ((ftrace_graph_return
        b.ne    ftrace_graph_caller //        != ftrace_stub)
    
        adrp    x1, ftrace_graph_entry  //     || (ftrace_graph_entry
        adrp    x0, ftrace_graph_entry_stub //     != ftrace_graph_entry_stub))
        ldr x2, [x1, #:lo12:ftrace_graph_entry]
        add x0, x0, #:lo12:ftrace_graph_entry_stub
        cmp x0, x2
        b.ne    ftrace_graph_caller //     ftrace_graph_caller();
    
        mcount_exit
    #endif /* CONFIG_FUNCTION_GRAPH_TRACER */
    ENDPROC(_mcount)
    
    #else /* CONFIG_DYNAMIC_FTRACE */

    对应的伪代码如下:

    void _mcount(void)
    {
            /* save any bare state needed in order to do initial checking */
    
            /* (1) 如果函数指针ftrace_trace_function不等于默认的桩函数ftrace_stub, 
                调用function tracer的回调函数:ftrace_trace_function()
             */
            extern void (*ftrace_trace_function)(unsigned long, unsigned long);
            if (ftrace_trace_function != ftrace_stub)
                    goto do_trace;
    
            /* (2) 如果 (ftrace_trace_function = = ftrace_stub) && 
                (ftrace_graph_return != ftrace_stub ||
                ftrace_graph_entry != ftrace_graph_entry_stub)
                调用function_graph tracer的回调函数:ftrace_graph_caller()
             */
    +#ifdef CONFIG_FUNCTION_GRAPH_TRACER
            extern void (*ftrace_graph_return)(...);
            extern void (*ftrace_graph_entry)(...);
            if (ftrace_graph_return != ftrace_stub ||
                ftrace_graph_entry != ftrace_graph_entry_stub)
                    ftrace_graph_caller();
    +#endif
    
            /* restore any bare state */
    
            return;
    
    do_trace:
    
            /* save all state needed by the ABI (see paragraph above) */
    
            unsigned long frompc = ...;
            unsigned long selfpc = <return address> - MCOUNT_INSN_SIZE;
    
            /* (1.1) 调用function tracer的回调函数:ftrace_trace_function() */
            ftrace_trace_function(frompc, selfpc);
    
            /* restore all state needed by the ABI */
    }

    问:static ftrace下,怎么保准notrace的函数不被跟踪?

    1.1.3、dynamic ftrace插桩

    static ftrace一旦使能,对kernel中所有的函数(除开notrace、online、其他特殊函数)进行插桩,这带来的性能开销是惊人的,有可能导致人们弃用ftrace功能。

    为了解决这个问题,内核开发者推出了dynamic ftrace。因为实际上调试者一般不需要对所有函数进行追踪,只会对感兴趣的一部分函数进行追踪。dynamic ftrace把不需要追踪的函数入口处指令“bl _mcount”替换成“nop”,这样基本对性能无影响;对需要追踪的函替换入口处“bl _mcount”为需要调用的函数。

    1、ftrace在初始化时,根据编译时“scripts/recordmcount.pl”脚本记录的所有函数入口处插桩位置的“bl _mcount”,将其替换成“nop”指令:

    这里写图片描述

    2、在tracer enable的时候,把需要跟踪的函数的插桩位置“nop”替换成“bl ftrace_caller”。

    为什么不使用“bl _mcount”?主要原因是开发者不喜欢在_mcount()中使用宏来区分两种情况,索性重新创建一个新函数ftrace_caller()。

    这里写图片描述

    arch/arm64/kernel/entry-ftrace.S:

    #ifndef CONFIG_DYNAMIC_FTRACE
    #else /* CONFIG_DYNAMIC_FTRACE */
    /*
     * _mcount() is used to build the kernel with -pg option, but all the branch
     * instructions to _mcount() are replaced to NOP initially at kernel start up,
     * and later on, NOP to branch to ftrace_caller() when enabled or branch to
     * NOP when disabled per-function base.
     */
    ENTRY(_mcount)
        ret
    ENDPROC(_mcount)
    
    /*
     * void ftrace_caller(unsigned long return_address)
     * @return_address: return address to instrumented function
     *
     * This function is a counterpart of _mcount() in 'static' ftrace, and
     * makes calls to:
     *     - tracer function to probe instrumented function's entry,
     *     - ftrace_graph_caller to set up an exit hook
     */
    ENTRY(ftrace_caller)
        mcount_enter
    
        mcount_get_pc0  x0      //     function's pc
        mcount_get_lr   x1      //     function's lr
    
        .global ftrace_call
    ftrace_call:                // tracer(pc, lr);
        nop             // This will be replaced with "bl xxx"
                        // where xxx can be any kind of tracer.
    
    #ifdef CONFIG_FUNCTION_GRAPH_TRACER
        .global ftrace_graph_call
    ftrace_graph_call:          // ftrace_graph_caller();
        nop             // If enabled, this will be replaced
                        // "b ftrace_graph_caller"
    #endif
    
        mcount_exit
    ENDPROC(ftrace_caller)
    #endif /* CONFIG_DYNAMIC_FTRACE */

    对应的伪代码为:

    void ftrace_caller(void)
    {
            /* save all state needed by the ABI (see paragraph above) */
    
            unsigned long frompc = ...;
            unsigned long selfpc = <return address> - MCOUNT_INSN_SIZE;
    
            /* This will be replaced with "bl xxx" where xxx can be any kind of tracer. */
            /* (1) function tracer的桩位置
                放弃了函数指针,使用nop指令占位,在运行时动态修改指令到"bl xxx"
             */
    ftrace_call:
            nop     
    
    #ifdef CONFIG_FUNCTION_GRAPH_TRACER
            /* If enabled, this will be replaced "b ftrace_graph_caller" */
            /* (2) function_graph tracer的桩位置
                放弃了函数指针,使用nop指令占位,在运行时动态修改指令到"b ftrace_graph_caller"
             */
    ftrace_graph_call:
            nop    
    #endif
    
            /* restore all state needed by the ABI */
    
            return;
    }

    1.2、插桩点管理

    1.2.1、插桩点的初始化

    1、在编译的时候调用recordmcount.pl搜集所有_mcount()函数的调用点,并且所有的调用点地址保存到section __mcount_loc。

    include/asm-generic/vmlinux.lds.h:

    #ifdef CONFIG_FTRACE_MCOUNT_RECORD
    #define MCOUNT_REC()    . = ALIGN(8);               \
                VMLINUX_SYMBOL(__start_mcount_loc) = .; \
                *(__mcount_loc)             \
                VMLINUX_SYMBOL(__stop_mcount_loc) = .;
    #else
    #define MCOUNT_REC()
    #endif

    并没有详细去研究其原理,感兴趣可以具体研究一下“scripts/recordmcount.pl、scripts/recordmcount.c”。

    2、在初始化时,遍历section __mcount_loc的调用点地址,默认给所有“bl _mcount”替换成“nop”。

    kernel/trace/ftrace.c:

    void __init ftrace_init(void)
    {
        extern unsigned long __start_mcount_loc[];
        extern unsigned long __stop_mcount_loc[];
        unsigned long count, flags;
        int ret;
    
        local_irq_save(flags);
        ret = ftrace_dyn_arch_init();
        local_irq_restore(flags);
        if (ret)
            goto failed;
    
        count = __stop_mcount_loc - __start_mcount_loc;
        if (!count) {
            pr_info("ftrace: No functions to be traced?\n");
            goto failed;
        }
    
        pr_info("ftrace: allocating %ld entries in %ld pages\n",
            count, count / ENTRIES_PER_PAGE + 1);
    
        last_ftrace_enabled = ftrace_enabled = 1;
    
        /* 遍历section __mcount_loc,处理其中保存的调用地址 */
        ret = ftrace_process_locs(NULL,
                      __start_mcount_loc,
                      __stop_mcount_loc);
    
        ret = register_module_notifier(&ftrace_module_exit_nb);
        if (ret)
            pr_warning("Failed to register trace ftrace module exit notifier\n");
    
        set_ftrace_early_filters();
    
        return;
     failed:
        ftrace_disabled = 1;
    }
    
    |→
    
    static int ftrace_process_locs(struct module *mod,
                       unsigned long *start,
                       unsigned long *end)
    {
        struct ftrace_page *start_pg;
        struct ftrace_page *pg;
        struct dyn_ftrace *rec;
        unsigned long count;
        unsigned long *p;
        unsigned long addr;
        unsigned long flags = 0; /* Shut up gcc */
        int ret = -ENOMEM;
    
        count = end - start;
    
        if (!count)
            return 0;
    
        /* (1) 对地址进行排序 */
        sort(start, count, sizeof(*start),
             ftrace_cmp_ips, NULL);
    
        /* (2) 对每个地址分配新的dyn_ftrace结构来存储 
            在section __mcount_loc中,只是简单的存储了unsigned long类型的调用地址
            dyn_ftrace结构除了使用->ip来存储地址,还使用->flags来存储当前的状态和被引用计数
         */
        start_pg = ftrace_allocate_pages(count);
        if (!start_pg)
            return -ENOMEM;
    
        mutex_lock(&ftrace_lock);
    
        /*
         * Core and each module needs their own pages, as
         * modules will free them when they are removed.
         * Force a new page to be allocated for modules.
         */
        if (!mod) {
            WARN_ON(ftrace_pages || ftrace_pages_start);
            /* First initialization */
            ftrace_pages = ftrace_pages_start = start_pg;
        } else {
            if (!ftrace_pages)
                goto out;
    
            if (WARN_ON(ftrace_pages->next)) {
                /* Hmm, we have free pages? */
                while (ftrace_pages->next)
                    ftrace_pages = ftrace_pages->next;
            }
    
            ftrace_pages->next = start_pg;
        }
    
        /* (3) 更新dyn_ftrace新结构中的->ip字段 */
        p = start;
        pg = start_pg;
        while (p < end) {
            addr = ftrace_call_adjust(*p++);
            /*
             * Some architecture linkers will pad between
             * the different mcount_loc sections of different
             * object files to satisfy alignments.
             * Skip any NULL pointers.
             */
            if (!addr)
                continue;
    
            if (pg->index == pg->size) {
                /* We should have allocated enough */
                if (WARN_ON(!pg->next))
                    break;
                pg = pg->next;
            }
    
            rec = &pg->records[pg->index++];
            rec->ip = addr;
        }
    
        /* We should have used all pages */
        WARN_ON(pg->next);
    
        /* Assign the last page to ftrace_pages */
        ftrace_pages = pg;
    
        /*
         * We only need to disable interrupts on start up
         * because we are modifying code that an interrupt
         * may execute, and the modification is not atomic.
         * But for modules, nothing runs the code we modify
         * until we are finished with it, and there's no
         * reason to cause large interrupt latencies while we do it.
         */
        if (!mod)
            local_irq_save(flags);
        /* (4) 更新dyn_ftrace新结构中的->flags字段 
            默认给所有调用点替换成“nop”指令
         */
        ftrace_update_code(mod, start_pg);
        if (!mod)
            local_irq_restore(flags);
        ret = 0;
     out:
        mutex_unlock(&ftrace_lock);
    
        return ret;
    }
    
    ||→
    
    static int ftrace_update_code(struct module *mod, struct ftrace_page *new_pgs)
    {
        struct ftrace_page *pg;
        struct dyn_ftrace *p;
        cycle_t start, stop;
        unsigned long update_cnt = 0;
        unsigned long ref = 0;
        bool test = false;
        int i;
    
        /*
         * When adding a module, we need to check if tracers are
         * currently enabled and if they are set to trace all functions.
         * If they are, we need to enable the module functions as well
         * as update the reference counts for those function records.
         */
        if (mod) {
            struct ftrace_ops *ops;
    
            for (ops = ftrace_ops_list;
                 ops != &ftrace_list_end; ops = ops->next) {
                if (ops->flags & FTRACE_OPS_FL_ENABLED) {
                    if (ops_traces_mod(ops))
                        ref++;
                    else
                        test = true;
                }
            }
        }
    
        start = ftrace_now(raw_smp_processor_id());
    
        /* (4.1) 逐个遍历dyn_ftrace结构 */
        for (pg = new_pgs; pg; pg = pg->next) {
    
            for (i = 0; i < pg->index; i++) {
                int cnt = ref;
    
                /* If something went wrong, bail without enabling anything */
                if (unlikely(ftrace_disabled))
                    return -1;
    
                p = &pg->records[i];
                /* (4.2) 根据有多少个filter操作对调用点地址的引用, 
                    给dyn_ftrace->flags字段中的引用count部分赋值
                 */
                if (test)
                    cnt += referenced_filters(p);
                p->flags = cnt;
    
                /*
                 * Do the initial record conversion from mcount jump
                 * to the NOP instructions.
                 */
                /* (4.3) 默认把所有的调用点修改成“nop” */
                if (!ftrace_code_disable(mod, p))
                    break;
    
                update_cnt++;
    
                /*
                 * If the tracing is enabled, go ahead and enable the record.
                 *
                 * The reason not to enable the record immediatelly is the
                 * inherent check of ftrace_make_nop/ftrace_make_call for
                 * correct previous instructions.  Making first the NOP
                 * conversion puts the module to the correct state, thus
                 * passing the ftrace_make_call check.
                 */
                if (ftrace_start_up && cnt) {
                    /* (4.4) 如果tracing已经使能,且引用计数不为0 
                        使能这个调用点
                     */
                    int failed = __ftrace_replace_code(p, 1);
                    if (failed)
                        ftrace_bug(failed, p);
                }
            }
        }
    
        stop = ftrace_now(raw_smp_processor_id());
        ftrace_update_time = stop - start;
        ftrace_update_tot_cnt += update_cnt;
    
        return 0;
    }
    

    1.2.2、插桩点的动态管理

    为了应对本文最开始处提出的难题,内核使用了插桩点控制链表+命令hash表的形式。

    这里写图片描述

    function tracer插桩点动态管理的原理:

    • 1、每个“bl _mcount”的插桩点ip对应一个每个插桩点对应一个dyn_ftrace结构。dyn_ftrace->flags的低26bit用来表示引用计数,如果有ftrcae_ops会操作到该ip,引用计数会加1。如果ref_cnt大于0,插桩点就需要使能了,把其替换为“bl ftrace_caller”;
    • 2、ftrcae_ops采用hash表来表达对ip的引用,一个ftrcae_ops有两张hash表filter_hash和notrace_hash,综合的结果就是对ip的引用。function tracer模式时,有两个可能的ftrcae_ops:global_ops、trace_probe_ops。两个中任意一个引用到ip,都会造成ip的1级插桩点被替换为“bl ftrace_caller”;
    • 3、通过设置“set_ftrace_filter/set_ftrace_notrace”会造成对global_ops的filter_hash/notrace_hash的配置,它的作用是配置function tracer的filter;
    • 4、通过设置“set_ftrace_filter”会造成对trace_probe_ops的filter_hash的配置,它的作用是配置filter command,并且会把command和对应的ip加入到另外一张另外一张hash表ftrace_func_hash表中;
    • 5、ftrace_caller()有两个2级插桩点:ftrace_call、ftrace_graph_call。在function tracer模式时,只有ftrace_call被设置成“bl ftrace_ops_list_func”;
    • 6、ftrace_ops_list_func()的工作就是遍历ftrace_ops_list链表,逐个执行其中的ftrace_ops->func()。
    • 7、global_ops的->func()函数为function_trace_call(),记录trace数据:ip + parent_ip;
    • 8、trace_probe_ops的->func()函数为function_trace_probe_call(),逐个执行ftrace_func_hash表中,ip为本ip的filter command。

    有以下几种场景,涉及到插桩点的动态管理:

    • tracer的使能。当使用“echo xxx_tracer > current_tracer”时,会关闭旧的current tracer并使能新的tracer。典型的包括function tracer合入function_graph tracer;
    • filter的配置。使用“echo function_name > set_ftrace_filter/set_ftrace_notrace”,可以配置部分function被trace,而不是所有function被trace;
    • filter command的配置。使用“echo ‘!__schedule_bug:traceoff’ > set_ftrace_filter”,类似命令可以配置条件触发的command,当条件满足后command会被执行;

    下面我们看看具体场景下的代码实现细节:

    1.2.3、tracer注册

    1、tracer注册。tracer可以通过register_tracer()进行注册。

    以function tracer为例,kernel/trace/trace_functions.c:

    static struct tracer function_trace __tracer_data =
    {
        .name       = "function",
        .init       = function_trace_init,
        .reset      = function_trace_reset,
        .start      = function_trace_start,
        .flags      = &func_flags,
        .set_flag   = func_set_flag,
        .allow_instances = true,
    #ifdef CONFIG_FTRACE_SELFTEST
        .selftest   = trace_selftest_startup_function,
    #endif
    };
    
    static __init int init_function_trace(void)
    {
        init_func_cmd_traceon();
        return register_tracer(&function_trace);
    }

    register_tracer()的实现,kernel/trace/trace.c:

    int __init register_tracer(struct tracer *type)
    {
        struct tracer *t;
        int ret = 0;
    
        if (!type->name) {
            pr_info("Tracer must have a name\n");
            return -1;
        }
    
        if (strlen(type->name) >= MAX_TRACER_SIZE) {
            pr_info("Tracer has a name longer than %d\n", MAX_TRACER_SIZE);
            return -1;
        }
    
        mutex_lock(&trace_types_lock);
    
        tracing_selftest_running = true;
    
        /* (1) 如果tracer已经注册,直接返回 */
        for (t = trace_types; t; t = t->next) {
            if (strcmp(type->name, t->name) == 0) {
                /* already found */
                pr_info("Tracer %s already registered\n",
                    type->name);
                ret = -1;
                goto out;
            }
        }
    
        /* (2) tracer对应的相关trace_option */
        if (!type->set_flag)
            type->set_flag = &dummy_set_flag;
        if (!type->flags)
            type->flags = &dummy_tracer_flags;
        else
            if (!type->flags->opts)
                type->flags->opts = dummy_tracer_opt;
    
        ret = run_tracer_selftest(type);
        if (ret < 0)
            goto out;
    
        /* (3) 将新的tracer加入到trace_types链表中 */
        type->next = trace_types;
        trace_types = type;
        add_tracer_options(&global_trace, type);
    
     out:
        tracing_selftest_running = false;
        mutex_unlock(&trace_types_lock);
    
        if (ret || !default_bootup_tracer)
            goto out_unlock;
    
        if (strncmp(default_bootup_tracer, type->name, MAX_TRACER_SIZE))
            goto out_unlock;
    
        printk(KERN_INFO "Starting tracer '%s'\n", type->name);
        /* Do we want this tracer to start on bootup? */
        /* (4) 设置boot默认的tracer */
        tracing_set_tracer(&global_trace, type->name);
        default_bootup_tracer = NULL;
    
        apply_trace_boot_options();
    
        /* disable other selftests, since this will break it. */
        tracing_selftest_disabled = true;
    #ifdef CONFIG_FTRACE_STARTUP_TEST
        printk(KERN_INFO "Disabling FTRACE selftests due to running tracer '%s'\n",
               type->name);
    #endif
    
     out_unlock:
        return ret;
    }

    1.2.4、tracer使能

    2、tracer使能。我们可以使用“echo function > current_tracer”命令来使能或者切换tracer。

    来具体看看其代码实现:

    trace_create_file("current_tracer", 0644, d_tracer,
            tr, &set_tracer_fops);
    
    ↓
    
    static const struct file_operations set_tracer_fops = {
        .open       = tracing_open_generic,
        .read       = tracing_set_trace_read,
        .write      = tracing_set_trace_write,
        .llseek     = generic_file_llseek,
    };
    
    ↓
    
    static ssize_t
    tracing_set_trace_write(struct file *filp, const char __user *ubuf,
                size_t cnt, loff_t *ppos)
    {
        /* (1) 当前文件所在trace buffer,默认是global_trace 
            初始化时:
            init_tracer_tracefs(&global_trace, d_tracer); -> 
            trace_create_file("current_tracer", 0644, d_tracer, tr, &set_tracer_fops);
         */
        struct trace_array *tr = filp->private_data;
        char buf[MAX_TRACER_SIZE+1];
        int i;
        size_t ret;
        int err;
    
        ret = cnt;
    
        if (cnt > MAX_TRACER_SIZE)
            cnt = MAX_TRACER_SIZE;
    
        if (copy_from_user(&buf, ubuf, cnt))
            return -EFAULT;
    
        buf[cnt] = 0;
    
        /* strip ending whitespace. */
        for (i = cnt - 1; i > 0 && isspace(buf[i]); i--)
            buf[i] = 0;
    
        /* (2) 使能新的tracer */
        err = tracing_set_tracer(tr, buf);
        if (err)
            return err;
    
        *ppos += ret;
    
        return ret;
    }
    
    |→
    
    static int tracing_set_tracer(struct trace_array *tr, const char *buf)
    {
        struct tracer *t;
    #ifdef CONFIG_TRACER_MAX_TRACE
        bool had_max_tr;
    #endif
        int ret = 0;
    
        mutex_lock(&trace_types_lock);
    
        if (!ring_buffer_expanded) {
            ret = __tracing_resize_ring_buffer(tr, trace_buf_size,
                            RING_BUFFER_ALL_CPUS);
            if (ret < 0)
                goto out;
            ret = 0;
        }
    
        /* (2.1) 根据名字,在trace_types链表中找到对应的tracer */
        for (t = trace_types; t; t = t->next) {
            if (strcmp(t->name, buf) == 0)
                break;
        }
        if (!t) {
            ret = -EINVAL;
            goto out;
        }
        if (t == tr->current_trace)
            goto out;
    
        /* Some tracers are only allowed for the top level buffer */
        if (!trace_ok_for_array(t, tr)) {
            ret = -EINVAL;
            goto out;
        }
    
        /* If trace pipe files are being read, we can't change the tracer */
        if (tr->current_trace->ref) {
            ret = -EBUSY;
            goto out;
        }
    
        trace_branch_disable();
    
        /* (2.2) 去使能旧的当前tracer */
        tr->current_trace->enabled--;
    
        if (tr->current_trace->reset)
            tr->current_trace->reset(tr);
    
        /* Current trace needs to be nop_trace before synchronize_sched */
        tr->current_trace = &nop_trace;
    
    #ifdef CONFIG_TRACER_MAX_TRACE
        had_max_tr = tr->allocated_snapshot;
    
        if (had_max_tr && !t->use_max_tr) {
            /*
             * We need to make sure that the update_max_tr sees that
             * current_trace changed to nop_trace to keep it from
             * swapping the buffers after we resize it.
             * The update_max_tr is called from interrupts disabled
             * so a synchronized_sched() is sufficient.
             */
            synchronize_sched();
            free_snapshot(tr);
        }
    #endif
    
    #ifdef CONFIG_TRACER_MAX_TRACE
        if (t->use_max_tr && !had_max_tr) {
            ret = alloc_snapshot(tr);
            if (ret < 0)
                goto out;
        }
    #endif
    
        /* (2.3) 调用新tracer的init函数 */
        if (t->init) {
            ret = tracer_init(t, tr);
            if (ret)
                goto out;
        }
    
        /* (2.4) 把新的tracer设置为当前tracer */
        tr->current_trace = t;
        tr->current_trace->enabled++;
        trace_branch_enable(tr);
     out:
        mutex_unlock(&trace_types_lock);
    
        return ret;
    }
    
    ||→
    
    int tracer_init(struct tracer *t, struct trace_array *tr)
    {
        tracing_reset_online_cpus(&tr->trace_buffer);
        return t->init(tr);
    }

    1.2.5、function tracer使能

    3、function tracer的使能。本质上是global_ops的注册后使能。

    在初始化的时候,把global_trace的->ops初始化成了global_ops。

    start_kernel() -> trace_init() -> tracer_alloc_buffers:
    
    tracer_alloc_buffers
    {
    
        ftrace_init_global_array_ops(&global_trace);
    
    }
    
    ↓
    
    __init void ftrace_init_global_array_ops(struct trace_array *tr)
    {
        tr->ops = &global_ops;
        tr->ops->private = tr;
    }
    
    static struct ftrace_ops global_ops = {
        .func               = ftrace_stub,
        .local_hash.notrace_hash    = EMPTY_HASH,
        .local_hash.filter_hash     = EMPTY_HASH,
        INIT_OPS_HASH(global_ops)
        .flags              = FTRACE_OPS_FL_RECURSION_SAFE |
                          FTRACE_OPS_FL_INITIALIZED |
                          FTRACE_OPS_FL_PID,
    };

    继续分析t->init(),以function tracer为例,调用到function_trace_init():

    static int function_trace_init(struct trace_array *tr)
    {
        ftrace_func_t func;
    
        /*
         * Instance trace_arrays get their ops allocated
         * at instance creation. Unless it failed
         * the allocation.
         */
        /* (2.4.1) 这里的tr默认就是global_trace 
            这里的tr->ops默认是global_ops
         */
        if (!tr->ops)
            return -ENOMEM;
    
        /* (2.4.2) func为保存trace信息到ringbuffer的函数 */
        /* Currently only the global instance can do stack tracing */
        if (tr->flags & TRACE_ARRAY_FL_GLOBAL &&
            func_flags.val & TRACE_FUNC_OPT_STACK)
            func = function_stack_trace_call;
        else
            func = function_trace_call;
    
        /* (2.4.3) tr->ops->func = func */
        ftrace_init_array_ops(tr, func);
    
        tr->trace_buffer.cpu = get_cpu();
        put_cpu();
    
        tracing_start_cmdline_record();
    
        /* (2.4.4) 启动function tracer 
            将tr->ops也加入到ftrace_ops_list当中
         */
        tracing_start_function_trace(tr);
        return 0;
    }
    
    ↓
    
    static void tracing_start_function_trace(struct trace_array *tr)
    {
        tr->function_enabled = 0;
        register_ftrace_function(tr->ops);
        tr->function_enabled = 1;
    }
    
    |→
    
    int register_ftrace_function(struct ftrace_ops *ops)
    {
        int ret = -1;
    
        /* (2.4.4.1) 初始化hash表 
            ops->func_hash = &ops->local_hash;
         */
        ftrace_ops_init(ops);
    
        mutex_lock(&ftrace_lock);
    
        /* (2.4.4.2) 将global_ops加入ftrace_ops_list 
            并且根据情况,修改各个桩位置的指令
         */
        ret = ftrace_startup(ops, 0);
    
        mutex_unlock(&ftrace_lock);
    
        return ret;
    }
    
    ||→
    
    static int ftrace_startup(struct ftrace_ops *ops, int command)
    {
        int ret;
    
        if (unlikely(ftrace_disabled))
            return -ENODEV;
    
        /* (2.4.4.2.1) 1、把global_ops加入ftrace_ops_list 
            2、根据ftrace_ops_list链表中成员的情况给ftrace_trace_function指针赋值:
                ftrace_ops_list链表为空,= ftrace_stub
                ftrace_ops_list链表有1个成员,= ftrace_ops_get_list_func(ftrace_ops_list)
                ftrace_ops_list链表有多个成员,= ftrace_ops_list_func
            3、更新ftrace_graph_entry
        */
        ret = __register_ftrace_function(ops);
        if (ret)
            return ret;
    
        ftrace_start_up++;
        command |= FTRACE_UPDATE_CALLS;
    
        /*
         * Note that ftrace probes uses this to start up
         * and modify functions it will probe. But we still
         * set the ADDING flag for modification, as probes
         * do not have trampolines. If they add them in the
         * future, then the probes will need to distinguish
         * between adding and updating probes.
         */
        ops->flags |= FTRACE_OPS_FL_ENABLED | FTRACE_OPS_FL_ADDING;
    
        /* (2.4.4.2.2) 遍历全部_mcount插桩点ftrace_rec 
            根据ip在新、旧hash表中的变化,设置对应rec->flags中的FTRACE_FL_IPMODIFY
         */
        ret = ftrace_hash_ipmodify_enable(ops);
        if (ret < 0) {
            /* Rollback registration process */
            __unregister_ftrace_function(ops);
            ftrace_start_up--;
            ops->flags &= ~FTRACE_OPS_FL_ENABLED;
            return ret;
        }
    
        /* (2.4.4.2.3) 遍历全部_mcount插桩点ftrace_rec 
            根据filter_hash、notrace_hash是否match ip,给对应rec->flags中ref_cnt进行加1/减1操作
         */
        ftrace_hash_rec_enable(ops, 1);
    
        /* (2.4.4.2.4) 更新插桩点: 
            FTRACE_UPDATE_CALLS被设置,更新_mcount插桩点:ref_cnt大于0的插桩点,更新成ftrace_caller()
            FTRACE_UPDATE_TRACE_FUNC被设置,更新ftrace_call插桩点:更新成ftrace_trace_function指向的函数
            FTRACE_START_FUNC_RET被设置,更新ftrace_graph_call插桩点:更新成ftrace_graph_caller()
         */
        ftrace_startup_enable(command);
    
        ops->flags &= ~FTRACE_OPS_FL_ADDING;
    
        return 0;
    }
    
    ↓
    
    ftrace_startup_enable() -> ftrace_run_update_code() -> arch_ftrace_update_code() -> ftrace_modify_all_code() -> 
    
    void ftrace_modify_all_code(int command)
    {
        int update = command & FTRACE_UPDATE_TRACE_FUNC;
        int err = 0;
    
        /*
         * If the ftrace_caller calls a ftrace_ops func directly,
         * we need to make sure that it only traces functions it
         * expects to trace. When doing the switch of functions,
         * we need to update to the ftrace_ops_list_func first
         * before the transition between old and new calls are set,
         * as the ftrace_ops_list_func will check the ops hashes
         * to make sure the ops are having the right functions
         * traced.
         */
        /* (2.4.4.2.4.1) 如果FTRACE_UPDATE_TRACE_FUNC被设置,对于ftrace_call插桩点,
            直接调用ftrace_ops_list链表中某个ftrace_ops的操作需要谨慎 
            保险起见,默认还是使用ftrace_ops_list_func(),它会轮询ftrace_ops_list链表中所有ftrace_ops
         */
        if (update) {
            err = ftrace_update_ftrace_func(ftrace_ops_list_func);
            if (FTRACE_WARN_ON(err))
                return;
        }
    
        /* (2.4.4.2.4.2) 如果FTRACE_UPDATE_CALLS被设置,对于_mcount插桩点,
            遍历全部ftrace_rec,ref_cnt大于0的插桩点,更新成ftrace_caller()
         */
        if (command & FTRACE_UPDATE_CALLS)
            ftrace_replace_code(1);
        else if (command & FTRACE_DISABLE_CALLS)
            ftrace_replace_code(0);
    
        /* (2.4.4.2.4.3) 如果FTRACE_UPDATE_TRACE_FUNC被设置,对于ftrace_call插桩点,
            如果ftrace_trace_function确实不等于ftrace_ops_list_func()
            更新成ftrace_trace_function指向的函数
         */
        if (update && ftrace_trace_function != ftrace_ops_list_func) {
            function_trace_op = set_function_trace_op;
            smp_wmb();
            /* If irqs are disabled, we are in stop machine */
            if (!irqs_disabled())
                smp_call_function(ftrace_sync_ipi, NULL, 1);
            err = ftrace_update_ftrace_func(ftrace_trace_function);
            if (FTRACE_WARN_ON(err))
                return;
        }
    
        /* (2.4.4.2.4.4) 如果FTRACE_START_FUNC_RET被设置,对于ftrace_graph_call插桩点,
            更新成ftrace_graph_caller()
         */
        if (command & FTRACE_START_FUNC_RET)
            err = ftrace_enable_ftrace_graph_caller();
        else if (command & FTRACE_STOP_FUNC_RET)
            err = ftrace_disable_ftrace_graph_caller();
        FTRACE_WARN_ON(err);
    }
    
    ↓
    
    void __weak ftrace_replace_code(int enable)
    {
        struct dyn_ftrace *rec;
        struct ftrace_page *pg;
        int failed;
    
        if (unlikely(ftrace_disabled))
            return;
    
        /* 遍历ftrace_rec */
        do_for_each_ftrace_rec(pg, rec) {
            failed = __ftrace_replace_code(rec, enable);
            if (failed) {
                ftrace_bug(failed, rec);
                /* Stop processing */
                return;
            }
        } while_for_each_ftrace_rec();
    }
    
    ↓
    
    static int
    __ftrace_replace_code(struct dyn_ftrace *rec, int enable)
    {
        unsigned long ftrace_old_addr;
        unsigned long ftrace_addr;
        int ret;
    
        /* (2.4.4.2.4.2.1) _mcount插桩点使能后的地址为: 
            ops->trampoline or ftrace_caller
         */
        ftrace_addr = ftrace_get_addr_new(rec);
    
        /* This needs to be done before we call ftrace_update_record */
        ftrace_old_addr = ftrace_get_addr_curr(rec);
    
    
        /* (2.4.4.2.4.2.2) 根据rec->flags中的flag和refcnt,以及enable 
            得出操作是:FTRACE_UPDATE_IGNORE/FTRACE_UPDATE_MAKE_CALL/FTRACE_UPDATE_MAKE_NOP
         */
        ret = ftrace_update_record(rec, enable);
    
        /* (2.4.4.2.4.2.3) 修改_mcount插桩点处的指令为新的桩函数跳转指令 */
        switch (ret) {
        case FTRACE_UPDATE_IGNORE:
            return 0;
    
        case FTRACE_UPDATE_MAKE_CALL:
            return ftrace_make_call(rec, ftrace_addr);
    
        case FTRACE_UPDATE_MAKE_NOP:
            return ftrace_make_nop(NULL, rec, ftrace_old_addr);
    
        case FTRACE_UPDATE_MODIFY_CALL:
            return ftrace_modify_call(rec, ftrace_old_addr, ftrace_addr);
        }
    
        return -1; /* unknow ftrace bug */
    }

    1.2.6、配置function tracer的filter

    4、通过“set_ftrace_filter”、“set_ftrace_notrace”设置function trcer的filter。本质上是操作global_ops的filter_hash、notrace_hash。

    /* global_ops注册成inode->i_private */
    ftrace_create_filter_files(&global_ops, d_tracer);
    
    ↓
    
    void ftrace_create_filter_files(struct ftrace_ops *ops,
                    struct dentry *parent)
    {
    
        trace_create_file("set_ftrace_filter", 0644, parent,
                  ops, &ftrace_filter_fops);
    
        trace_create_file("set_ftrace_notrace", 0644, parent,
                  ops, &ftrace_notrace_fops);
    }
    
    static const struct file_operations ftrace_filter_fops = {
        .open = ftrace_filter_open,
        .read = seq_read,
        .write = ftrace_filter_write,
        .llseek = tracing_lseek,
        .release = ftrace_regex_release,
    };

    “set_ftrace_filter”、“set_ftrace_notrace”的文件操作有个技巧,就是在open的时候分配一个临时hash表iter->hash来拷贝global_ops的filter_hash/notrace_hash的内容,在write操作实际设置filter时对iter->hash操作,在close的时候使用新的hash表iter->hash来更新global_ops。

    open文件操作中对global_ops hash表的备份操作:

    static int
    ftrace_filter_open(struct inode *inode, struct file *file)
    {
        /* (1) 得到global_ops结构 */
        struct ftrace_ops *ops = inode->i_private;
    
        /* (2) */
        return ftrace_regex_open(ops,
                FTRACE_ITER_FILTER | FTRACE_ITER_DO_HASH,
                inode, file);
    }
    
    ↓
    
    int
    ftrace_regex_open(struct ftrace_ops *ops, int flag,
              struct inode *inode, struct file *file)
    {
        struct ftrace_iterator *iter;
        struct ftrace_hash *hash;
        int ret = 0;
    
        ftrace_ops_init(ops);
    
        if (unlikely(ftrace_disabled))
            return -ENODEV;
    
        iter = kzalloc(sizeof(*iter), GFP_KERNEL);
        if (!iter)
            return -ENOMEM;
    
        if (trace_parser_get_init(&iter->parser, FTRACE_BUFF_MAX)) {
            kfree(iter);
            return -ENOMEM;
        }
    
        iter->ops = ops;
        iter->flags = flag;
    
        mutex_lock(&ops->func_hash->regex_lock);
    
        if (flag & FTRACE_ITER_NOTRACE)
            hash = ops->func_hash->notrace_hash;
        else
            hash = ops->func_hash->filter_hash;
    
        if (file->f_mode & FMODE_WRITE) {
            const int size_bits = FTRACE_HASH_DEFAULT_BITS;
    
            /* (2.1) 使用iter->hash备份global_ops中的filter_hash/notrace_hash */
            if (file->f_flags & O_TRUNC)
                iter->hash = alloc_ftrace_hash(size_bits);
            else
                iter->hash = alloc_and_copy_ftrace_hash(size_bits, hash);
    
            if (!iter->hash) {
                trace_parser_put(&iter->parser);
                kfree(iter);
                ret = -ENOMEM;
                goto out_unlock;
            }
        }
    
        if (file->f_mode & FMODE_READ) {
            iter->pg = ftrace_pages_start;
    
            ret = seq_open(file, &show_ftrace_seq_ops);
            if (!ret) {
                struct seq_file *m = file->private_data;
                m->private = iter;
            } else {
                /* Failed */
                free_ftrace_hash(iter->hash);
                trace_parser_put(&iter->parser);
                kfree(iter);
            }
        } else
            file->private_data = iter;
    
     out_unlock:
        mutex_unlock(&ops->func_hash->regex_lock);
    
        return ret;
    }

    write文件操作中对iter->hash表的更新操作:

    ftrace_filter_write() -> ftrace_regex_write() 
    
    ↓
    
    static ssize_t
    ftrace_regex_write(struct file *file, const char __user *ubuf,
               size_t cnt, loff_t *ppos, int enable)
    {
        struct ftrace_iterator *iter;
        struct trace_parser *parser;
        ssize_t ret, read;
    
        if (!cnt)
            return 0;
    
        if (file->f_mode & FMODE_READ) {
            struct seq_file *m = file->private_data;
            iter = m->private;
        } else
            iter = file->private_data;
    
        if (unlikely(ftrace_disabled))
            return -ENODEV;
    
        /* iter->hash is a local copy, so we don't need regex_lock */
    
        parser = &iter->parser;
        read = trace_get_user(parser, ubuf, cnt, ppos);
    
        if (read >= 0 && trace_parser_loaded(parser) &&
            !trace_parser_cont(parser)) {
            /* (1) 解析filter配置命令,配置到iter->hash中 */
            ret = ftrace_process_regex(iter->hash, parser->buffer,
                           parser->idx, enable);
            trace_parser_clear(parser);
            if (ret < 0)
                goto out;
        }
    
        ret = read;
     out:
        return ret;
    }
    
    ↓
    
    static int ftrace_process_regex(struct ftrace_hash *hash,
                    char *buff, int len, int enable)
    {
        char *func, *command, *next = buff;
        struct ftrace_func_command *p;
        int ret = -EINVAL;
    
        func = strsep(&next, ":");
    
        /* (1.1) filter配置,更新iter->hash */
        if (!next) {
            ret = ftrace_match_records(hash, func, len);
            if (!ret)
                ret = -EINVAL;
            if (ret < 0)
                return ret;
            return 0;
        }
    
        /* command found */
    
        command = strsep(&next, ":");
    
        /* (1.2) filter command配置,最后实际会操作到trace_probe_ops 
            和iter->hash、global_ops无关,下一小节中详述
        */
        mutex_lock(&ftrace_cmd_mutex);
        list_for_each_entry(p, &ftrace_commands, list) {
            if (strcmp(p->name, command) == 0) {
                ret = p->func(hash, func, command, next, enable);
                goto out_unlock;
            }
        }
     out_unlock:
        mutex_unlock(&ftrace_cmd_mutex);
    
        return ret;
    }
    
    ↓
    
    ftrace_match_records() -> match_records()
    
    static int
    match_records(struct ftrace_hash *hash, char *func, int len, char *mod)
    {
        struct ftrace_page *pg;
        struct dyn_ftrace *rec;
        struct ftrace_glob func_g = { .type = MATCH_FULL };
        struct ftrace_glob mod_g = { .type = MATCH_FULL };
        struct ftrace_glob *mod_match = (mod) ? &mod_g : NULL;
        int exclude_mod = 0;
        int found = 0;
        int ret;
        int clear_filter;
    
        /* (1.1.1) 解析filter命令字符串 
            clear_filter = 命令中的“!”
         */
        if (func) {
            func_g.type = filter_parse_regex(func, len, &func_g.search,
                             &clear_filter);
            func_g.len = strlen(func_g.search);
        }
    
        if (mod) {
            mod_g.type = filter_parse_regex(mod, strlen(mod),
                    &mod_g.search, &exclude_mod);
            mod_g.len = strlen(mod_g.search);
        }
    
        mutex_lock(&ftrace_lock);
    
        if (unlikely(ftrace_disabled))
            goto out_unlock;
    
        /* (1.1.2) 遍历ftrcae_rec */
        do_for_each_ftrace_rec(pg, rec) {
            /* 如果ip在filter中存在,将其加入/删除到iter->hash中 */
            if (ftrace_match_record(rec, &func_g, mod_match, exclude_mod)) {
                ret = enter_record(hash, rec, clear_filter);
                if (ret < 0) {
                    found = ret;
                    goto out_unlock;
                }
                found = 1;
            }
        } while_for_each_ftrace_rec();
     out_unlock:
        mutex_unlock(&ftrace_lock);
    
        return found;
    }
    
    ↓
    
    static int
    enter_record(struct ftrace_hash *hash, struct dyn_ftrace *rec, int clear_filter)
    {
        struct ftrace_func_entry *entry;
        int ret = 0;
    
        entry = ftrace_lookup_ip(hash, rec->ip);
        if (clear_filter) {
            /* Do nothing if it doesn't exist */
            if (!entry)
                return 0;
    
            free_hash_entry(hash, entry);
        } else {
            /* Do nothing if it exists */
            if (entry)
                return 0;
    
            ret = add_hash_entry(hash, rec->ip);
        }
        return ret;
    }

    close文件操作中对global_ops hash表的更新操作:

    int ftrace_regex_release(struct inode *inode, struct file *file)
    {
        struct seq_file *m = (struct seq_file *)file->private_data;
        struct ftrace_ops_hash old_hash_ops;
        struct ftrace_iterator *iter;
        struct ftrace_hash **orig_hash;
        struct ftrace_hash *old_hash;
        struct trace_parser *parser;
        int filter_hash;
        int ret;
    
        if (file->f_mode & FMODE_READ) {
            iter = m->private;
            seq_release(inode, file);
        } else
            iter = file->private_data;
    
        parser = &iter->parser;
        if (trace_parser_loaded(parser)) {
            parser->buffer[parser->idx] = 0;
            ftrace_match_records(iter->hash, parser->buffer, parser->idx);
        }
    
        trace_parser_put(parser);
    
        mutex_lock(&iter->ops->func_hash->regex_lock);
    
        if (file->f_mode & FMODE_WRITE) {
            filter_hash = !!(iter->flags & FTRACE_ITER_FILTER);
    
            if (filter_hash)
                orig_hash = &iter->ops->func_hash->filter_hash;
            else
                orig_hash = &iter->ops->func_hash->notrace_hash;
    
            mutex_lock(&ftrace_lock);
            old_hash = *orig_hash;
            old_hash_ops.filter_hash = iter->ops->func_hash->filter_hash;
            old_hash_ops.notrace_hash = iter->ops->func_hash->notrace_hash;
            /* (1) 使用iter->hash来更新global_ops的filter_hash/notrace_hash */
            ret = ftrace_hash_move(iter->ops, filter_hash,
                           orig_hash, iter->hash);
            /* (2) 根据最新hash表的内容,更新_mcount插桩点 
                遍历全部ftrace_rec:
                    ref_cnt大于0的插桩点,更新成ftrace_caller()
                    ref_cnt等于0的插桩点,更新成nop
             */
            if (!ret) {
                ftrace_ops_update_code(iter->ops, &old_hash_ops);
                free_ftrace_hash_rcu(old_hash);
            }
            mutex_unlock(&ftrace_lock);
        }
    
        mutex_unlock(&iter->ops->func_hash->regex_lock);
        free_ftrace_hash(iter->hash);
        kfree(iter);
    
        return 0;
    }
    
    ↓
    
    static void ftrace_ops_update_code(struct ftrace_ops *ops,
                       struct ftrace_ops_hash *old_hash)
    {
        struct ftrace_ops *op;
    
        if (!ftrace_enabled)
            return;
    
        if (ops->flags & FTRACE_OPS_FL_ENABLED) {
            ftrace_run_modify_code(ops, FTRACE_UPDATE_CALLS, old_hash);
            return;
        }
    
        /*
         * If this is the shared global_ops filter, then we need to
         * check if there is another ops that shares it, is enabled.
         * If so, we still need to run the modify code.
         */
        if (ops->func_hash != &global_ops.local_hash)
            return;
    
        do_for_each_ftrace_op(op, ftrace_ops_list) {
            if (op->func_hash == &global_ops.local_hash &&
                op->flags & FTRACE_OPS_FL_ENABLED) {
                ftrace_run_modify_code(op, FTRACE_UPDATE_CALLS, old_hash);
                /* Only need to do this once */
                return;
            }
        } while_for_each_ftrace_op(op);
    }

    1.2.7、配置function tracer的filter command

    5、通过“set_ftrace_filter”设置function trcer的filter command。本质上向trace_probe_ops注册cmd,以及操作trace_probe_ops的filter_hash、notrace_hash。

    虽然同样是操作set_ftrace_filter,但是配置filter和配置filter command是操作到不同的实体:

    • 配置filter。操作的是global_ops的filter_hash/notrace_hash的内容;
    • 配置filter command。是把command向trace_probe_ops注册,并且操作trace_probe_ops的filter_hash/notrace_hash的内容;

    在配置filter command之前首先得注册command:

    init_function_trace() -> init_func_cmd_traceon()
    
    static int __init init_func_cmd_traceon(void)
    {
        int ret;
    
        /* 注册:把command加入到ftrace_commands链表 */
        ret = register_ftrace_command(&ftrace_traceoff_cmd);
        if (ret)
            return ret;
    
        ret = register_ftrace_command(&ftrace_traceon_cmd);
        if (ret)
            goto out_free_traceoff;
    
        ret = register_ftrace_command(&ftrace_stacktrace_cmd);
        if (ret)
            goto out_free_traceon;
    
        ret = register_ftrace_command(&ftrace_dump_cmd);
        if (ret)
            goto out_free_stacktrace;
    
        ret = register_ftrace_command(&ftrace_cpudump_cmd);
        if (ret)
            goto out_free_dump;
    
        return 0;
    }
    
    static struct ftrace_func_command ftrace_traceon_cmd = {
        .name           = "traceon",
        .func           = ftrace_trace_onoff_callback,
    };
    

    我们以”traceon”command为例,继续分析上一节对“set_ftrace_filter”的文件操作:

    static int ftrace_process_regex(struct ftrace_hash *hash,
                    char *buff, int len, int enable)
    {
        char *func, *command, *next = buff;
        struct ftrace_func_command *p;
        int ret = -EINVAL;
    
        func = strsep(&next, ":");
    
        /* (1.1) filter配置,更新iter->hash */
        if (!next) {
            ret = ftrace_match_records(hash, func, len);
            if (!ret)
                ret = -EINVAL;
            if (ret < 0)
                return ret;
            return 0;
        }
    
        /* command found */
    
        command = strsep(&next, ":");
    
        /* (1.2) filter command配置,最后实际会操作到trace_probe_ops 
            和iter->hash、global_ops无关,下一小节中详述
        */
        mutex_lock(&ftrace_cmd_mutex);
        list_for_each_entry(p, &ftrace_commands, list) {
            if (strcmp(p->name, command) == 0) {
                ret = p->func(hash, func, command, next, enable);
                goto out_unlock;
            }
        }
     out_unlock:
        mutex_unlock(&ftrace_cmd_mutex);
    
        return ret;
    }
    
    ↓
    
    static int
    ftrace_trace_onoff_callback(struct ftrace_hash *hash,
                    char *glob, char *cmd, char *param, int enable)
    {
        struct ftrace_probe_ops *ops;
    
        /* we register both traceon and traceoff to this callback */
        /* (1.2.1) 配置command的执行ops */
        if (strcmp(cmd, "traceon") == 0)
            ops = param ? &traceon_count_probe_ops : &traceon_probe_ops;
        else
            ops = param ? &traceoff_count_probe_ops : &traceoff_probe_ops;
    
        /* (1.2.2) 注册command到trace_probe_ops */
        return ftrace_trace_probe_callback(ops, hash, glob, cmd,
                           param, enable);
    }
    
    ↓
    
    static int
    ftrace_trace_probe_callback(struct ftrace_probe_ops *ops,
                    struct ftrace_hash *hash, char *glob,
                    char *cmd, char *param, int enable)
    {
        void *count = (void *)-1;
        char *number;
        int ret;
    
        /* hash funcs only work with set_ftrace_filter */
        if (!enable)
            return -EINVAL;
    
        /* (1.2.2.1) 如果命令是“!”,注销filter command */
        if (glob[0] == '!') {
            unregister_ftrace_function_probe_func(glob+1, ops);
            return 0;
        }
    
        if (!param)
            goto out_reg;
    
        number = strsep(&param, ":");
    
        if (!strlen(number))
            goto out_reg;
    
        /*
         * We use the callback data field (which is a pointer)
         * as our counter.
         */
        /* (1.2.2.2) 解析到filter command中的“count”字段 */
        ret = kstrtoul(number, 0, (unsigned long *)&count);
        if (ret)
            return ret;
    
     out_reg:
        /* (1.2.2.3) 继续注册filter command */
        ret = register_ftrace_function_probe(glob, ops, count);
    
        return ret < 0 ? ret : 0;
    }
    
    ↓
    
    int
    register_ftrace_function_probe(char *glob, struct ftrace_probe_ops *ops,
                      void *data)
    {
        struct ftrace_ops_hash old_hash_ops;
        struct ftrace_func_probe *entry;
        struct ftrace_glob func_g;
        struct ftrace_hash **orig_hash = &trace_probe_ops.func_hash->filter_hash;
        struct ftrace_hash *old_hash = *orig_hash;
        struct ftrace_hash *hash;
        struct ftrace_page *pg;
        struct dyn_ftrace *rec;
        int not;
        unsigned long key;
        int count = 0;
        int ret;
    
        /* (1.2.2.3.1) filter command中函数名部分的解析 */
        func_g.type = filter_parse_regex(glob, strlen(glob),
                &func_g.search, &not);
        func_g.len = strlen(func_g.search);
    
        /* we do not support '!' for function probes */
        if (WARN_ON(not))
            return -EINVAL;
    
        mutex_lock(&trace_probe_ops.func_hash->regex_lock);
    
        old_hash_ops.filter_hash = old_hash;
        /* Probes only have filters */
        old_hash_ops.notrace_hash = NULL;
    
        /* (1.2.2.3.1) 将trace_probe_ops的filter_hash拷贝到临时hash表 */
        hash = alloc_and_copy_ftrace_hash(FTRACE_HASH_DEFAULT_BITS, old_hash);
        if (!hash) {
            count = -ENOMEM;
            goto out;
        }
    
        if (unlikely(ftrace_disabled)) {
            count = -ENODEV;
            goto out;
        }
    
        mutex_lock(&ftrace_lock);
    
        /* 遍历ftrace_rec */
        do_for_each_ftrace_rec(pg, rec) {
    
            if (!ftrace_match_record(rec, &func_g, NULL, 0))
                continue;
    
            entry = kmalloc(sizeof(*entry), GFP_KERNEL);
            if (!entry) {
                /* If we did not process any, then return error */
                if (!count)
                    count = -ENOMEM;
                goto out_unlock;
            }
    
            count++;
    
            entry->data = data;
    
            /*
             * The caller might want to do something special
             * for each function we find. We call the callback
             * to give the caller an opportunity to do so.
             */
            if (ops->init) {
                if (ops->init(ops, rec->ip, &entry->data) < 0) {
                    /* caller does not like this func */
                    kfree(entry);
                    continue;
                }
            }
    
            /* (1.2.2.3.3) 如果当前ip符合当前函数filter规则,加入到临时hash表 */
            ret = enter_record(hash, rec, 0);
            if (ret < 0) {
                kfree(entry);
                count = ret;
                goto out_unlock;
            }
    
    
            /* (1.2.2.3.4) 把command的操作和对应ip组合成entry,加入到另外一张hash表ftrace_func_hash */
            entry->ops = ops;
            entry->ip = rec->ip;
    
            key = hash_long(entry->ip, FTRACE_HASH_BITS);
            hlist_add_head_rcu(&entry->node, &ftrace_func_hash[key]);
    
        } while_for_each_ftrace_rec();
    
        /* (1.2.2.3.5) 把临时hash表更新到trace_probe_ops的filter_hash中 */
        ret = ftrace_hash_move(&trace_probe_ops, 1, orig_hash, hash);
    
        /* (1.2.2.3.6) 如果trace_probe_ops已经注册,根据hash表的更新来更新_mcount插桩点 
            如果trace_probe_ops没有注册,注册并更新_mcount插桩点
         */
        __enable_ftrace_function_probe(&old_hash_ops);
    
        if (!ret)
            free_ftrace_hash_rcu(old_hash);
        else
            count = ret;
    
     out_unlock:
        mutex_unlock(&ftrace_lock);
     out:
        mutex_unlock(&trace_probe_ops.func_hash->regex_lock);
        free_ftrace_hash(hash);
    
        return count;
    }
    

    trace_probe_ops在被调用的时候,执行ftrace_func_hash中的filter command:

    ftrace_caller() -> ftrace_ops_list_func() -> __ftrace_ops_list_func() 
    
    static inline void
    __ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip,
                   struct ftrace_ops *ignored, struct pt_regs *regs)
    {
        struct ftrace_ops *op;
        int bit;
    
        bit = trace_test_and_set_recursion(TRACE_LIST_START, TRACE_LIST_MAX);
        if (bit < 0)
            return;
    
        /*
         * Some of the ops may be dynamically allocated,
         * they must be freed after a synchronize_sched().
         */
        preempt_disable_notrace();
        /* 遍历ftrace_ops_list链表,
            在当前ip满足hash的情况下,逐个执行ftrace_ops->func()
         */
        do_for_each_ftrace_op(op, ftrace_ops_list) {
            if (ftrace_ops_test(op, ip, regs)) {
                if (FTRACE_WARN_ON(!op->func)) {
                    pr_warn("op=%p %pS\n", op, op);
                    goto out;
                }
                op->func(ip, parent_ip, op, regs);
            }
        } while_for_each_ftrace_op(op);
    out:
        preempt_enable_notrace();
        trace_clear_recursion(bit);
    }
    
    ↓
    
    static struct ftrace_ops trace_probe_ops __read_mostly =
    {
        .func       = function_trace_probe_call,
        .flags      = FTRACE_OPS_FL_INITIALIZED,
        INIT_OPS_HASH(trace_probe_ops)
    };
    
    static void function_trace_probe_call(unsigned long ip, unsigned long parent_ip,
                          struct ftrace_ops *op, struct pt_regs *pt_regs)
    {
        struct ftrace_func_probe *entry;
        struct hlist_head *hhd;
        unsigned long key;
    
        key = hash_long(ip, FTRACE_HASH_BITS);
    
        hhd = &ftrace_func_hash[key];
    
        if (hlist_empty(hhd))
            return;
    
        /*
         * Disable preemption for these calls to prevent a RCU grace
         * period. This syncs the hash iteration and freeing of items
         * on the hash. rcu_read_lock is too dangerous here.
         */
        preempt_disable_notrace();
        /* 逐个执行ftrace_func_hash表中,ip为本ip的filter command */
        hlist_for_each_entry_rcu_notrace(entry, hhd, node) {
            if (entry->ip == ip)
                entry->ops->func(ip, parent_ip, &entry->data);
        }
        preempt_enable_notrace();
    }
    
    

    1.3、数据存入

    function tracer的数据存入的路径为:

    ftrace_caller() -> ftrace_ops_list_func() -> __ftrace_ops_list_func() -> global_ops->func() -> function_trace_call():

    static void
    function_trace_call(unsigned long ip, unsigned long parent_ip,
                struct ftrace_ops *op, struct pt_regs *pt_regs)
    {
        struct trace_array *tr = op->private;
        struct trace_array_cpu *data;
        unsigned long flags;
        int bit;
        int cpu;
        int pc;
    
        if (unlikely(!tr->function_enabled))
            return;
    
        pc = preempt_count();
        preempt_disable_notrace();
    
        bit = trace_test_and_set_recursion(TRACE_FTRACE_START, TRACE_FTRACE_MAX);
        if (bit < 0)
            goto out;
    
        cpu = smp_processor_id();
        data = per_cpu_ptr(tr->trace_buffer.data, cpu);
        if (!atomic_read(&data->disabled)) {
            local_save_flags(flags);
            trace_function(tr, ip, parent_ip, flags, pc);
        }
        trace_clear_recursion(bit);
    
     out:
        preempt_enable_notrace();
    }
    
    ↓
    
    void
    trace_function(struct trace_array *tr,
               unsigned long ip, unsigned long parent_ip, unsigned long flags,
               int pc)
    {
        struct trace_event_call *call = &event_function;
        struct ring_buffer *buffer = tr->trace_buffer.buffer;
        struct ring_buffer_event *event;
        struct ftrace_entry *entry;
    
        /* (1) 从ringbuffer中分配空间 
            type = TRACE_FN
         */
        event = trace_buffer_lock_reserve(buffer, TRACE_FN, sizeof(*entry),
                          flags, pc);
        if (!event)
            return;
        entry   = ring_buffer_event_data(event);
        /* (2) 存入function tracer自定义的trace数据:ip、parent_ip */
        entry->ip           = ip;
        entry->parent_ip        = parent_ip;
    
        if (!call_filter_check_discard(call, entry, buffer, event))
            __buffer_unlock_commit(buffer, event);
    }

    1.3.1、数据格式

    function tracer自定义的trace数据非常简单:ip、parent_ip

    这里写图片描述

    1.3.2、filter

    关于filter和filter command的配置在1.2.2、插桩点的动态管理一节已经讲得非常详细了,这里就不再重复。

    1.4、数据读出

    从trace文件读出的function tracer默认数据格式为:

    # cat trace
    # tracer: function
    #
    # entries-in-buffer/entries-written: 36/36   #P:8
    #
    #                              _-----=> irqs-off
    #                             / _----=> need-resched
    #                            | / _---=> hardirq/softirq
    #                            || / _--=> preempt-depth
    #                            ||| /     delay
    #           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
    #              | |       |   ||||       |         |
                  sh-18361 [001] ...1 663922.156238: schedule_hrtimeout_range <-poll_schedule_timeout
                  sh-18361 [001] ...1 663922.156251: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
              <idle>-0     [003] .n.2 663922.156660: schedule_preempt_disabled <-cpu_startup_entry
         ->transport-5191  [002] ...1 663922.157592: schedule_timeout <-wait_for_common
                adbd-5189  [002] ...1 663922.158219: schedule_hrtimeout_range <-poll_schedule_timeout
                adbd-5189  [002] ...1 663922.158222: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
              <idle>-0     [001] .n.2 663922.158342: schedule_preempt_disabled <-cpu_startup_entry
         ->transport-5191  [001] ...1 663922.159407: schedule_timeout <-unix_stream_read_generic
         <-transport-5192  [001] ...1 663922.159904: schedule_timeout <-wait_for_common
         ->transport-5191  [001] ...1 663922.160413: schedule_timeout <-unix_stream_read_generic
                adbd-5189  [001] ...1 663922.160895: schedule_hrtimeout_range <-poll_schedule_timeout
                adbd-5189  [001] ...1 663922.160898: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
              <idle>-0     [002] .n.2 663922.163694: schedule_preempt_disabled <-cpu_startup_entry
              <idle>-0     [003] .n.2 663922.175958: schedule_preempt_disabled <-cpu_startup_entry
      ndroid.systemu-2200  [003] ...1 663922.179945: schedule_hrtimeout_range <-SyS_epoll_wait
      ndroid.systemu-2200  [003] ...1 663922.179950: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
              <idle>-0     [003] .n.2 663922.181418: schedule_preempt_disabled <-cpu_startup_entry
              <idle>-0     [002] .n.2 663922.181584: schedule_preempt_disabled <-cpu_startup_entry
              <idle>-0     [002] .n.2 663922.200878: schedule_preempt_disabled <-cpu_startup_entry
              <idle>-0     [001] .n.2 663922.203506: schedule_preempt_disabled <-cpu_startup_entry
         rcu_preempt-7     [002] ...1 663922.203519: schedule_timeout <-rcu_gp_kthread
      ndroid.systemu-2200  [002] ...1 663922.205619: schedule_hrtimeout_range <-SyS_epoll_wait
      ndroid.systemu-2200  [002] ...1 663922.205624: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
              <idle>-0     [003] .n.2 663922.208538: schedule_preempt_disabled <-cpu_startup_entry
              <idle>-0     [002] .n.2 663922.213243: schedule_preempt_disabled <-cpu_startup_entry
              <idle>-0     [003] .n.2 663922.217986: schedule_preempt_disabled <-cpu_startup_entry
              <idle>-0     [003] .n.2 663922.227857: schedule_preempt_disabled <-cpu_startup_entry
      ndroid.systemu-2200  [003] ...1 663922.231819: schedule_hrtimeout_range <-SyS_epoll_wait
      ndroid.systemu-2200  [003] ...1 663922.231824: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
              <idle>-0     [002] .n.2 663922.234282: schedule_preempt_disabled <-cpu_startup_entry
              <idle>-0     [001] .n.2 663922.246355: schedule_preempt_disabled <-cpu_startup_entry
              <idle>-0     [002] .n.2 663922.251360: schedule_preempt_disabled <-cpu_startup_entry
      ndroid.systemu-2200  [002] ...1 663922.255168: schedule_hrtimeout_range <-SyS_epoll_wait
      ndroid.systemu-2200  [002] ...1 663922.255172: schedule_hrtimeout_range_clock <-schedule_hrtimeout_range
              <idle>-0     [003] .n.2 663922.256775: schedule_preempt_disabled <-cpu_startup_entry
              <idle>-0     [001] .n.2 663922.257253: schedule_preempt_disabled <-cpu_startup_entry
    

    在kernel/trace/trace_ouput.c文件中,注册了系统默认的几种trace_event。function tracer使用TRACE_FN类型的trace_fn_event:

    static struct trace_event *events[] __initdata = {
        &trace_fn_event,
        &trace_graph_ent_event,
        &trace_graph_ret_event,
        &trace_ctx_event,
        &trace_wake_event,
        &trace_stack_event,
        &trace_user_stack_event,
        &trace_bputs_event,
        &trace_bprint_event,
        &trace_print_event,
        NULL
    };
    
    __init static int init_events(void)
    {
        struct trace_event *event;
        int i, ret;
    
        for (i = 0; events[i]; i++) {
            event = events[i];
    
            ret = register_trace_event(event);
            if (!ret) {
                printk(KERN_WARNING "event %d failed to register\n",
                       event->type);
                WARN_ON_ONCE(1);
            }
        }
    
        return 0;
    }
    
    |→
    
    static struct trace_event_functions trace_fn_funcs = {
        .trace      = trace_fn_trace,
        .raw        = trace_fn_raw,
        .hex        = trace_fn_hex,
        .binary     = trace_fn_bin,
    };
    
    static struct trace_event trace_fn_event = {
        .type       = TRACE_FN,
        .funcs      = &trace_fn_funcs,
    };

    在数据读出时,会调用到event对应的event->funcs->trace()函数,seq_read() -> s_show() -> print_trace_line() -> print_trace_fmt() -> event->funcs->trace():

    TRACE_FN,event->funcs->trace()对应trace_fn_trace():

    /* TRACE_FN */
    static enum print_line_t trace_fn_trace(struct trace_iterator *iter, int flags,
                        struct trace_event *event)
    {
        struct ftrace_entry *field;
        struct trace_seq *s = &iter->seq;
    
        trace_assign_type(field, iter->ent);
    
        /* (1) 打印出本ip对应的符号 */
        seq_print_ip_sym(s, field->ip, flags);
    
        /* (2) 如果trace option运行,打印出父ip对应的符号 */
        if ((flags & TRACE_ITER_PRINT_PARENT) && field->parent_ip) {
            trace_seq_puts(s, " <-");
            seq_print_ip_sym(s, field->parent_ip, flags);
        }
    
        trace_seq_putc(s, '\n');
    
        return trace_handle_return(s);
    }

    2、function_graph tracer

    function_graph tracer从function tracer发展而来,function tracer使用“_mcount”插桩可以跟踪到每个函数的调用入口,而function_graph tracer即可以跟踪到函数的入口还可以跟踪到函数的返回。

    2.1、插桩原理

    这里写图片描述

    如上图:一切的关键是在入口桩函数被调用时,修改了func()的返回地址,不是返回到func’s parent()函数继续去执行,而是返回到reurn桩函数return_to_handler()中。return_to_handler()中执行完自己的return处理函数以后,再把返回地址恢复成func’s parent中的地址,返回继续执行原有的路径。

    原本的入口处插桩,只能追踪到函数的切换。现在入口、出口同时插桩,还能获得函数的执行时长,做更多的分析。

    2.2、插桩点管理

    这里写图片描述

    function_graph tracer插桩点动态管理的原理:

    • 1、每个“bl _mcount”的插桩点ip对应一个每个插桩点对应一个dyn_ftrace结构。dyn_ftrace->flags的低26bit用来表示引用计数,如果有ftrcae_ops会操作到该ip,引用计数会加1。如果ref_cnt大于0,插桩点就需要使能了,把其替换为“bl ftrace_caller”;
    • 2、ftrcae_ops采用hash表来表达对ip的引用,一个ftrcae_ops有两张hash表filter_hash和notrace_hash,综合的结果就是对ip的引用。function_graph tracer模式时,有一个的ftrcae_ops:graph_ops。并且graph_ops的hash表是直接引用global_ops的hash表,这样做的目的是可以保留function tracer模式时的filter配置。如果引用到ip,都会造成ip的1级插桩点被替换为“bl ftrace_caller”;
    • 3、通过设置“set_ftrace_filter/set_ftrace_notrace”会造成对global_ops的filter_hash/notrace_hash的配置,它的作用相当于1级过滤。在function_graph tracer模式时,通过2级过滤的函数才能记录trace信息:1级过滤(函数必须是“set_ftrace_filter/set_ftrace_notrace”配置中允许的函数),2级过滤(函数必须是“set_graph_function/set_graph_notrace”配置中允许的函数或者是被它们调用的子函数);
    • 4、通过设置“set_graph_function/set_graph_notrace”可以配置2级过滤,实际上是配置到ftrace_graph_funcs[]/ftrace_graph_notrace_funcs[]表中。
    • 5、ftrace_caller()有两个2级插桩点:ftrace_call、ftrace_graph_call。在function tracer模式时,只有ftrace_graph_call被设置成“bl ftrace_graph_caller”;
    • 6、ftrace_graph_caller()的工作就是设置环境,让函数在入口处调用trace_graph_entry(),在返回处调用trace_graph_return();
    • 7、在trace_graph_entry()中会进行2级过滤的合法性判断,函数必须是“set_graph_function/set_graph_notrace”配置中允许的函数或者是被它们调用的子函数才能继续执行,否则出错返回后面的trace_graph_return()都不会被执行。
    • 8、trace_graph_entry()、trace_graph_return()的工作都是记录trace信息:ip+时间戳;

    在以下场景时,function_graph会涉及到对各个插桩点的动态修改:

    • tracer的使能。当使用“echo xxx_tracer > current_tracer”时,会关闭旧的current tracer并使能新的tracer。典型的包括function tracer合入function_graph tracer;
    • function_graph filter的配置。使用“echo function_name > set_ftrace_filter/set_ftrace_notrace”,可以配置部分function被trace,而不是所有function被trace;
    • function filter的配置。使用“echo function_name > set_graph_function/set_graph_notrace”,可以配置部分function被trace,而不是所有function被trace;

    下面我们看看具体场景下的代码实现细节:

    2.2.1、function_graph tracer使能

    参考“1.2.5、function tracer使能”这一节,tracer的使能最后调用的是tracer->init()函数。

    对function_graph tracer来说,调用的是graph_trace_init():

    static struct tracer graph_trace __tracer_data = {
        .name       = "function_graph",
        .update_thresh  = graph_trace_update_thresh,
        .open       = graph_trace_open,
        .pipe_open  = graph_trace_open,
        .close      = graph_trace_close,
        .pipe_close = graph_trace_close,
        .init       = graph_trace_init,
        .reset      = graph_trace_reset,
        .print_line = print_graph_function,
        .print_header   = print_graph_headers,
        .flags      = &tracer_flags,
        .set_flag   = func_graph_set_flag,
    #ifdef CONFIG_FTRACE_SELFTEST
        .selftest   = trace_selftest_startup_function_graph,
    #endif
    };
    
    ↓
    
    static int graph_trace_init(struct trace_array *tr)
    {
        int ret;
    
        set_graph_array(tr);
        if (tracing_thresh)
            ret = register_ftrace_graph(&trace_graph_thresh_return,
                            &trace_graph_thresh_entry);
        else
            ret = register_ftrace_graph(&trace_graph_return,
                            &trace_graph_entry);
        if (ret)
            return ret;
        tracing_start_cmdline_record();
    
        return 0;
    }
    
    ↓
    
    /* function_graph tracer的专用ftrace_ops:graph_ops */
    static struct ftrace_ops graph_ops = {
        .func           = ftrace_stub,
        .flags          = FTRACE_OPS_FL_RECURSION_SAFE |
                       FTRACE_OPS_FL_INITIALIZED |
                       FTRACE_OPS_FL_PID |
                       FTRACE_OPS_FL_STUB,
    #ifdef FTRACE_GRAPH_TRAMP_ADDR
        .trampoline     = FTRACE_GRAPH_TRAMP_ADDR,
        /* trampoline_size is only needed for dynamically allocated tramps */
    #endif
        ASSIGN_OPS_HASH(graph_ops, &global_ops.local_hash)  /* graph_ops共用global_ops的hash表,这样可以支持"set_ftrace_filter"的配置 */
    };
    
    
    int register_ftrace_graph(trace_func_graph_ret_t retfunc,
                trace_func_graph_ent_t entryfunc)
    {
        int ret = 0;
    
        mutex_lock(&ftrace_lock);
    
        /* we currently allow only one tracer registered at a time */
        if (ftrace_graph_active) {
            ret = -EBUSY;
            goto out;
        }
    
        register_pm_notifier(&ftrace_suspend_notifier);
    
        ftrace_graph_active++;
        ret = start_graph_tracing();
        if (ret) {
            ftrace_graph_active--;
            goto out;
        }
    
        /* (1) 给ftrace_graph_entry、ftrace_graph_return指针赋值 */
        ftrace_graph_return = retfunc;
    
        /*
         * Update the indirect function to the entryfunc, and the
         * function that gets called to the entry_test first. Then
         * call the update fgraph entry function to determine if
         * the entryfunc should be called directly or not.
         */
        __ftrace_graph_entry = entryfunc;
        ftrace_graph_entry = ftrace_graph_entry_test;
        update_function_graph_func();
    
        /* (2) 注册graph_ops:
            1、将graph_ops加入到ftrace_ops_list链表;
            2、根据graph_ops的hash表,更新_mcount插桩点;
            3、更新ftrace_graph_call插桩点为ftrace_graph_caller()
         */
        ret = ftrace_startup(&graph_ops, FTRACE_START_FUNC_RET);
    out:
        mutex_unlock(&ftrace_lock);
        return ret;
    }
    

    2.2.2、配置function filter(1级过滤)

    因为function_graph tracer的graph_ops继续共用global_ops的hash表,使用“set_ftrace_filter/set_ftrace_notrace”接口可以配置global_ops的filter_hash/notrace_hash表。所以可以继续使用“set_ftrace_filter/set_ftrace_notrace”来配置function_graph tracer的filter。

    function_graph tracer还可以使用“set_graph_function/set_graph_notrace”接口来配置过滤,需要两种过滤条件都满足的函数才能被trace。所以我们命名当前过滤为1级过滤。

    具体的代码解析参考: 1.2.6、配置function tracer的filter

    2.2.3、配置function_graph filter(2级过滤)

    通过设置“set_graph_function/set_graph_notrace”可以配置2级过滤,实际上是配置到ftrace_graph_funcs[]/ftrace_graph_notrace_funcs[]表中。

    trace_create_file("set_graph_function", 0444, d_tracer,
                    NULL,
                    &ftrace_graph_fops);
    trace_create_file("set_graph_notrace", 0444, d_tracer,
                    NULL,
                    &ftrace_graph_notrace_fops);
    
    static const struct file_operations ftrace_graph_fops = {
        .open       = ftrace_graph_open,
        .read       = seq_read,
        .write      = ftrace_graph_write,
        .llseek     = tracing_lseek,
        .release    = ftrace_graph_release,
    };
    
    ↓
    
    static int
    ftrace_graph_open(struct inode *inode, struct file *file)
    {
        struct ftrace_graph_data *fgd;
    
        if (unlikely(ftrace_disabled))
            return -ENODEV;
    
        fgd = kmalloc(sizeof(*fgd), GFP_KERNEL);
        if (fgd == NULL)
            return -ENOMEM;
    
        fgd->table = ftrace_graph_funcs;
        fgd->size = FTRACE_GRAPH_MAX_FUNCS;
        fgd->count = &ftrace_graph_count;
        fgd->seq_ops = &ftrace_graph_seq_ops;
    
        return __ftrace_graph_open(inode, file, fgd);
    }
    
    
    static ssize_t
    ftrace_graph_write(struct file *file, const char __user *ubuf,
               size_t cnt, loff_t *ppos)
    {
        struct trace_parser parser;
        ssize_t read, ret = 0;
        struct ftrace_graph_data *fgd = file->private_data;
    
        if (!cnt)
            return 0;
    
        if (trace_parser_get_init(&parser, FTRACE_BUFF_MAX))
            return -ENOMEM;
    
        read = trace_get_user(&parser, ubuf, cnt, ppos);
    
        if (read >= 0 && trace_parser_loaded((&parser))) {
            parser.buffer[parser.idx] = 0;
    
            mutex_lock(&graph_lock);
    
            /* we allow only one expression at a time */
            /* 根据filter设置条件,设置ftrace_graph_funcs[]表 */
            ret = ftrace_set_func(fgd->table, fgd->count, fgd->size,
                          parser.buffer);
    
            mutex_unlock(&graph_lock);
        }
    
        if (!ret)
            ret = read;
    
        trace_parser_put(&parser);
    
        return ret;
    }

    在trace_graph_entry()中会进行2级过滤的合法性判断,函数必须是“set_graph_function/set_graph_notrace”配置中允许的函数或者是被它们调用的子函数才能继续执行,否则出错返回后面的trace_graph_return()都不会被执行。

    int trace_graph_entry(struct ftrace_graph_ent *trace)
    {
        struct trace_array *tr = graph_array;
        struct trace_array_cpu *data;
        unsigned long flags;
        long disabled;
        int ret;
        int cpu;
        int pc;
    
        if (!ftrace_trace_task(current))
            return 0;
    
        /* trace it when it is-nested-in or is a function enabled. */
        /*  ftrace_graph_addr(trace->func) // 函数必须是“set_graph_function/set_graph_notrace”配置中允许的函数
            trace->depth // 或者是被它们调用的子函数
         */
        if ((!(trace->depth || ftrace_graph_addr(trace->func)) ||
             ftrace_graph_ignore_irqs()) || (trace->depth < 0) ||
            (max_depth && trace->depth >= max_depth))
            return 0;
    
        /*
         * Do not trace a function if it's filtered by set_graph_notrace.
         * Make the index of ret stack negative to indicate that it should
         * ignore further functions.  But it needs its own ret stack entry
         * to recover the original index in order to continue tracing after
         * returning from the function.
         */
        if (ftrace_graph_notrace_addr(trace->func))
            return 1;
    
        local_irq_save(flags);
        cpu = raw_smp_processor_id();
        data = per_cpu_ptr(tr->trace_buffer.data, cpu);
        disabled = atomic_inc_return(&data->disabled);
        if (likely(disabled == 1)) {
            pc = preempt_count();
            ret = __trace_graph_entry(tr, trace, flags, pc);
        } else {
            ret = 0;
        }
    
        atomic_dec(&data->disabled);
        local_irq_restore(flags);
    
        return ret;
    }
    
    ↓
    
    static inline int ftrace_graph_addr(unsigned long addr)
    {
        int i;
    
        if (!ftrace_graph_count)
            return 1;
    
        /* 查找地址是否在ftrace_graph_funcs[]表中 */
        for (i = 0; i < ftrace_graph_count; i++) {
            if (addr == ftrace_graph_funcs[i]) {
                /*
                 * If no irqs are to be traced, but a set_graph_function
                 * is set, and called by an interrupt handler, we still
                 * want to trace it.
                 */
                if (in_irq())
                    trace_recursion_set(TRACE_IRQ_BIT);
                else
                    trace_recursion_clear(TRACE_IRQ_BIT);
                return 1;
            }
        }
    
        return 0;
    }

    2.3、数据存入

    和function tracer不一样的是,function_graph在进入函数和返回函数时都有trace数据存入。

    2.3.1、trace_graph_entry()

    这里写图片描述

    函数入口的存入:trace_graph_entry() -> __trace_graph_entry():

    int __trace_graph_entry(struct trace_array *tr,
                    struct ftrace_graph_ent *trace,
                    unsigned long flags,
                    int pc)
    {
        struct trace_event_call *call = &event_funcgraph_entry;
        struct ring_buffer_event *event;
        struct ring_buffer *buffer = tr->trace_buffer.buffer;
        struct ftrace_graph_ent_entry *entry;
    
        event = trace_buffer_lock_reserve(buffer, TRACE_GRAPH_ENT,
                          sizeof(*entry), flags, pc);
        if (!event)
            return 0;
        entry   = ring_buffer_event_data(event);
        entry->graph_ent            = *trace;
        if (!call_filter_check_discard(call, entry, buffer, event))
            __buffer_unlock_commit(buffer, event);
    
        return 1;
    }

    2.3.2、trace_graph_return()

    这里写图片描述

    函数返回的存入:trace_graph_return() -> __trace_graph_return():

    void __trace_graph_return(struct trace_array *tr,
                    struct ftrace_graph_ret *trace,
                    unsigned long flags,
                    int pc)
    {
        struct trace_event_call *call = &event_funcgraph_exit;
        struct ring_buffer_event *event;
        struct ring_buffer *buffer = tr->trace_buffer.buffer;
        struct ftrace_graph_ret_entry *entry;
    
        event = trace_buffer_lock_reserve(buffer, TRACE_GRAPH_RET,
                          sizeof(*entry), flags, pc);
        if (!event)
            return;
        entry   = ring_buffer_event_data(event);
        entry->ret              = *trace;
        if (!call_filter_check_discard(call, entry, buffer, event))
            __buffer_unlock_commit(buffer, event);
    }

    2.3.3、filter

    参见“2.2.2、配置function filter(1级过滤)”和“2.2.3、配置function_graph filter(2级过滤)”。

    2.4、数据读出

    从trace文件读出的function_graph tracer默认数据格式为:(显示function_graph中设置的函数或者是被它们调用的子函数)

    # cat trace
    # tracer: function_graph
    #
    # CPU  DURATION                  FUNCTION CALLS
    # |     |   |                     |   |   |   |
     0)               |  tty_open() {
     0)   1.563 us    |    nonseekable_open();
     0)               |    tty_alloc_file() {
     0)               |      kmem_cache_alloc_trace() {
     0)               |        __might_sleep() {
     0)   0.417 us    |          ___might_sleep();
     0)   4.791 us    |        }
     0)   1.302 us    |        preempt_count_add();
     0)   1.250 us    |        preempt_count_sub();
     0)               |        __slab_alloc.isra.60.constprop.62() {
     0)               |          ___slab_alloc.constprop.63() {
     0)               |            _raw_spin_lock() {
     0)   1.250 us    |              preempt_count_add();
     0)   0.520 us    |              do_raw_spin_trylock();
     0)   9.531 us    |            }
     0)   0.781 us    |            preempt_count_add();
     0)   0.469 us    |            preempt_count_sub();
     0)               |            _raw_spin_unlock() {
     0)   0.521 us    |              do_raw_spin_unlock();
     0)   1.614 us    |              preempt_count_sub();
     0)   9.584 us    |            }
     0)               |            alloc_debug_processing() {
     0)               |              check_slab() {
     0)   1.198 us    |                slab_pad_check.part.52();
     0)   5.990 us    |              }
     0)               |              check_object() {
     0)   1.719 us    |                check_bytes_and_report();
     0)   0.521 us    |                check_bytes_and_report();
     0)   2.448 us    |                check_bytes_and_report();
     0)   0.469 us    |                check_bytes_and_report();
     0)   1.927 us    |                check_bytes_and_report();
     0) + 29.843 us   |              }
     0)               |              set_track() {
    

    在通过trace文件读出数据时,如果tracer的print_line()函数有定义,则会调用print_line()函数进行解析。而不再去使用event->funcs->trace()函数。

    1、读操作时,seq_read() -> s_show() -> print_trace_line() -> tracer->print_line():

    static struct tracer graph_trace __tracer_data = {
        .name       = "function_graph",
        .update_thresh  = graph_trace_update_thresh,
        .open       = graph_trace_open,
        .pipe_open  = graph_trace_open,
        .close      = graph_trace_close,
        .pipe_close = graph_trace_close,
        .init       = graph_trace_init,
        .reset      = graph_trace_reset,
        .print_line = print_graph_function,
        .print_header   = print_graph_headers,
        .flags      = &tracer_flags,
        .set_flag   = func_graph_set_flag,
    #ifdef CONFIG_FTRACE_SELFTEST
        .selftest   = trace_selftest_startup_function_graph,
    #endif
    };
    
    ↓
    
    static enum print_line_t
    print_graph_function(struct trace_iterator *iter)
    {
        return print_graph_function_flags(iter, tracer_flags.val);
    }
    
    ↓
    
    enum print_line_t
    print_graph_function_flags(struct trace_iterator *iter, u32 flags)
    {
        struct ftrace_graph_ent_entry *field;
        struct fgraph_data *data = iter->private;
        struct trace_entry *entry = iter->ent;
        struct trace_seq *s = &iter->seq;
        int cpu = iter->cpu;
        int ret;
    
        if (flags & TRACE_GRAPH_PRINT_FLAT)
            return TRACE_TYPE_UNHANDLED;
    
        if (data && per_cpu_ptr(data->cpu_data, cpu)->ignore) {
            per_cpu_ptr(data->cpu_data, cpu)->ignore = 0;
            return TRACE_TYPE_HANDLED;
        }
    
        /*
         * If the last output failed, there's a possibility we need
         * to print out the missing entry which would never go out.
         */
        if (data && data->failed) {
            field = &data->ent;
            iter->cpu = data->cpu;
            ret = print_graph_entry(field, s, iter, flags);
            if (ret == TRACE_TYPE_HANDLED && iter->cpu != cpu) {
                per_cpu_ptr(data->cpu_data, iter->cpu)->ignore = 1;
                ret = TRACE_TYPE_NO_CONSUME;
            }
            iter->cpu = cpu;
            return ret;
        }
    
        switch (entry->type) {
        /* (1) 打印TRACE_GRAPH_ENT类型的entry */
        case TRACE_GRAPH_ENT: {
            /*
             * print_graph_entry() may consume the current event,
             * thus @field may become invalid, so we need to save it.
             * sizeof(struct ftrace_graph_ent_entry) is very small,
             * it can be safely saved at the stack.
             */
            struct ftrace_graph_ent_entry saved;
            trace_assign_type(field, entry);
            saved = *field;
            return print_graph_entry(&saved, s, iter, flags);
        }
        /* (2) 打印TRACE_GRAPH_RET类型的entry */
        case TRACE_GRAPH_RET: {
            struct ftrace_graph_ret_entry *field;
            trace_assign_type(field, entry);
            return print_graph_return(&field->ret, s, entry, iter, flags);
        }
    
        /* (3) TRACE_STACK、TRACE_FN这两种类型的entry,graph无法处理,返回交由系统打印 */
        case TRACE_STACK:
        case TRACE_FN:
            /* dont trace stack and functions as comments */
            return TRACE_TYPE_UNHANDLED;
    
        /* (4) 打印其他类型的entry */
        default:
            return print_graph_comment(s, entry, iter, flags);
        }
    
        return TRACE_TYPE_HANDLED;
    }

    2、在第一次读操作时,会打印header。seq_read() -> s_show() -> print_trace_line() -> tracer->print_header():

    static void print_graph_headers(struct seq_file *s)
    {
        print_graph_headers_flags(s, tracer_flags.val);
    }
    
    ↓
    
    void print_graph_headers_flags(struct seq_file *s, u32 flags)
    {
        struct trace_iterator *iter = s->private;
        struct trace_array *tr = iter->tr;
    
        if (flags & TRACE_GRAPH_PRINT_FLAT) {
            trace_default_header(s);
            return;
        }
    
        if (!(tr->trace_flags & TRACE_ITER_CONTEXT_INFO))
            return;
    
        if (tr->trace_flags & TRACE_ITER_LATENCY_FMT) {
            /* print nothing if the buffers are empty */
            if (trace_empty(iter))
                return;
    
            print_trace_header(s, iter);
        }
    
        __print_graph_headers_flags(tr, s, flags);
    }
    

    3、在open的时候,也会调用到tracer的open函数。tracing_open() -> __tracing_open() -> tracer->open():

    void graph_trace_open(struct trace_iterator *iter)
    {
        /* pid and depth on the last trace processed */
        struct fgraph_data *data;
        gfp_t gfpflags;
        int cpu;
    
        iter->private = NULL;
    
        /* We can be called in atomic context via ftrace_dump() */
        gfpflags = (in_atomic() || irqs_disabled()) ? GFP_ATOMIC : GFP_KERNEL;
    
        data = kzalloc(sizeof(*data), gfpflags);
        if (!data)
            goto out_err;
    
        data->cpu_data = alloc_percpu_gfp(struct fgraph_cpu_data, gfpflags);
        if (!data->cpu_data)
            goto out_err_free;
    
        for_each_possible_cpu(cpu) {
            pid_t *pid = &(per_cpu_ptr(data->cpu_data, cpu)->last_pid);
            int *depth = &(per_cpu_ptr(data->cpu_data, cpu)->depth);
            int *ignore = &(per_cpu_ptr(data->cpu_data, cpu)->ignore);
            int *depth_irq = &(per_cpu_ptr(data->cpu_data, cpu)->depth_irq);
    
            *pid = -1;
            *depth = 0;
            *ignore = 0;
            *depth_irq = -1;
        }
    
        iter->private = data;
    
        return;
    
     out_err_free:
        kfree(data);
     out_err:
        pr_warning("function graph tracer: not enough memory\n");
    }

    3、irqsoff tracer

    irqsoff tracer用来追踪最大关中断时间。它的trace会提供几部分信息:

    • 1、irqoff的最大时长:latency;
    • 2、在最大irqoff这期间所有的function trace信息;
    • 3、最后的irqon的函数回调信息;

    3.1、插桩

    这里写图片描述

    irqsoff tracer的插桩方法,是直接在local_irq_enable()、local_irq_disable()中直接插入钩子函数trace_hardirqs_on()、trace_hardirqs_off()。

    #ifdef CONFIG_TRACE_IRQFLAGS
    #define local_irq_enable() \
        do { trace_hardirqs_on(); raw_local_irq_enable(); } while (0)
    #define local_irq_disable() \
        do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)
    #define local_irq_save(flags)               \
        do {                        \
            raw_local_irq_save(flags);      \
            trace_hardirqs_off();           \
        } while (0)
    
    
    #define local_irq_restore(flags)            \
        do {                        \
            if (raw_irqs_disabled_flags(flags)) {   \
                raw_local_irq_restore(flags);   \
                trace_hardirqs_off();       \
            } else {                \
                trace_hardirqs_on();        \
                raw_local_irq_restore(flags);   \
            }                   \
        } while (0)
    
    #define safe_halt()             \
        do {                    \
            trace_hardirqs_on();        \
            raw_safe_halt();        \
        } while (0)
    
    
    #else /* !CONFIG_TRACE_IRQFLAGS */

    3.1.1、trace_hardirqs_on/off()

    local_irq_disable() -> trace_hardirqs_off():

    void trace_hardirqs_off(void)
    {
        if (!preempt_trace() && irq_trace())
            start_critical_timing(CALLER_ADDR0, CALLER_ADDR1);
    }
    
    ↓
    
    static inline void
    start_critical_timing(unsigned long ip, unsigned long parent_ip)
    {
        int cpu;
        struct trace_array *tr = irqsoff_trace;
        struct trace_array_cpu *data;
        unsigned long flags;
    
        if (!tracer_enabled || !tracing_is_enabled())
            return;
    
        cpu = raw_smp_processor_id();
    
        /* (1) 如果已经开始tracing,中间的重复关中断操作被忽略 */
        if (per_cpu(tracing_cpu, cpu))
            return;
    
        data = per_cpu_ptr(tr->trace_buffer.data, cpu);
    
        if (unlikely(!data) || atomic_read(&data->disabled))
            return;
    
        atomic_inc(&data->disabled);
    
        /* (2) 记录trace的开始时间 */
        data->critical_sequence = max_sequence;
        data->preempt_timestamp = ftrace_now(cpu);
        data->critical_start = parent_ip ? : ip;
    
        local_save_flags(flags);
    
        /* (3) 记录一下当前的function trace信息 */
        __trace_function(tr, ip, parent_ip, flags, preempt_count());
    
        /* (4) 设置开始tracing标志 
            这是一个非常重要的标准,打开所有function trace的记录
         */
        per_cpu(tracing_cpu, cpu) = 1;
    
        atomic_dec(&data->disabled);
    }

    local_irq_enable() -> trace_hardirqs_on():

    void trace_hardirqs_on(void)
    {
        if (!preempt_trace() && irq_trace())
            stop_critical_timing(CALLER_ADDR0, CALLER_ADDR1);
    }
    
    ↓
    
    static inline void
    stop_critical_timing(unsigned long ip, unsigned long parent_ip)
    {
        int cpu;
        struct trace_array *tr = irqsoff_trace;
        struct trace_array_cpu *data;
        unsigned long flags;
    
        cpu = raw_smp_processor_id();
        /* Always clear the tracing cpu on stopping the trace */
        /* (1) 清除开始tracing标志 
            这是一个非常重要的标准,停止所有function trace的记录
         */
        if (unlikely(per_cpu(tracing_cpu, cpu)))
            per_cpu(tracing_cpu, cpu) = 0;
        else
            return;
    
        if (!tracer_enabled || !tracing_is_enabled())
            return;
    
        data = per_cpu_ptr(tr->trace_buffer.data, cpu);
    
        if (unlikely(!data) ||
            !data->critical_start || atomic_read(&data->disabled))
            return;
    
        atomic_inc(&data->disabled);
    
        /* (2) 记录一下当前的function trace信息 */
        local_save_flags(flags);
        __trace_function(tr, ip, parent_ip, flags, preempt_count());
    
        /* (3) check当前的irqoff持续时长是不是最大的latency */
        check_critical_timing(tr, data, parent_ip ? : ip, cpu);
        data->critical_start = 0;
        atomic_dec(&data->disabled);
    }
    
    ↓
    
    static void
    check_critical_timing(struct trace_array *tr,
                  struct trace_array_cpu *data,
                  unsigned long parent_ip,
                  int cpu)
    {
        cycle_t T0, T1, delta;
        unsigned long flags;
        int pc;
    
        /* (3.1) 当前irqoff的持续时长 */
        T0 = data->preempt_timestamp;
        T1 = ftrace_now(cpu);
        delta = T1-T0;
    
        local_save_flags(flags);
    
        pc = preempt_count();
    
        /* (3.2) 判断是否是新的最大latency */
        if (!report_latency(tr, delta))
            goto out;
    
        raw_spin_lock_irqsave(&max_trace_lock, flags);
    
        /* check if we are still the max latency */
        if (!report_latency(tr, delta))
            goto out_unlock;
    
        /* (3.3) 如果是最大latency: 
            记录一下当前的function trace信息
            记录当前的stack信息
         */
        __trace_function(tr, CALLER_ADDR0, parent_ip, flags, pc);
        /* Skip 5 functions to get to the irq/preempt enable function */
        __trace_stack(tr, flags, 5, pc);
    
        if (data->critical_sequence != max_sequence)
            goto out_unlock;
    
        data->critical_end = parent_ip;
    
        /* (3.4) ---------重要----------------- 
            这一步非常重要,主要做了以下工作:
            1、把当前的trace buffer备份到max_buffer中
            2、更新max_buffer中max_buf->time_start时间戳,这个记录了最大latency trace信息的起始位置
         */
        if (likely(!is_tracing_stopped())) {
            tr->max_latency = delta;
            update_max_tr_single(tr, current, cpu);
        }
    
        max_sequence++;
    
    out_unlock:
        raw_spin_unlock_irqrestore(&max_trace_lock, flags);
    
    out:
        data->critical_sequence = max_sequence;
        data->preempt_timestamp = ftrace_now(cpu);
        __trace_function(tr, CALLER_ADDR0, parent_ip, flags, pc);
    }
    
    ↓
    
    void
    update_max_tr_single(struct trace_array *tr, struct task_struct *tsk, int cpu)
    {
        int ret;
    
        if (tr->stop_count)
            return;
    
        WARN_ON_ONCE(!irqs_disabled());
        if (!tr->allocated_snapshot) {
            /* Only the nop tracer should hit this when disabling */
            WARN_ON_ONCE(tr->current_trace != &nop_trace);
            return;
        }
    
        arch_spin_lock(&tr->max_lock);
    
        /* (3.4.1) swap trace_buffer和max_buffer,把包含最大latency过程的trace信息备份到max_buffer中 */
        ret = ring_buffer_swap_cpu(tr->max_buffer.buffer, tr->trace_buffer.buffer, cpu);
    
        if (ret == -EBUSY) {
            /*
             * We failed to swap the buffer due to a commit taking
             * place on this CPU. We fail to record, but we reset
             * the max trace buffer (no one writes directly to it)
             * and flag that it failed.
             */
            trace_array_printk_buf(tr->max_buffer.buffer, _THIS_IP_,
                "Failed to swap buffers due to commit in progress\n");
        }
    
        WARN_ON_ONCE(ret && ret != -EAGAIN && ret != -EBUSY);
    
        /* (3.4.2) 已经把包含最大latency过程的trace信息备份到max_buffer中了, 
            但是这个信息中包含了多次irqoff的trace信息,哪一次才是最大latency的信息呢?
            使用max_buf->time_start来记录最大latency的起始时间
            max_buf->time_start = data->preempt_timestamp;
         */
        __update_max_tr(tr, tsk, cpu);
        arch_spin_unlock(&tr->max_lock);
    }
    

    3.1.2、irqoff trace信息的读取

    参考上一节,最大latency的trace信息被备份到了max_buffer中了,且max_buf->time_start来记录最大latency的起始时间。

    所以在trace读取irqoff trace信息时,需要从max_buffer读取。tracing_open() -> __tracing_open():

    static struct trace_iterator *
    __tracing_open(struct inode *inode, struct file *file, bool snapshot)
    {
    
    #ifdef CONFIG_TRACER_MAX_TRACE
        /* Currently only the top directory has a snapshot */
        /* (1) 如果tracer的print_max被定义,从max_buffer从读取数据 */
        if (tr->current_trace->print_max || snapshot)
            iter->trace_buffer = &tr->max_buffer;
        else
    #endif
            iter->trace_buffer = &tr->trace_buffer;
    
    }
    
    static struct tracer irqsoff_tracer __read_mostly =
    {
        .name       = "irqsoff",
        .init       = irqsoff_tracer_init,
        .reset      = irqsoff_tracer_reset,
        .start      = irqsoff_tracer_start,
        .stop       = irqsoff_tracer_stop,
        .print_max  = true,     /* irqoff tracer的print_max为true */
        .print_header   = irqsoff_print_header,
        .print_line     = irqsoff_print_line,
        .flag_changed   = irqsoff_flag_changed,
    #ifdef CONFIG_FTRACE_SELFTEST
        .selftest    = trace_selftest_startup_irqsoff,
    #endif
        .open           = irqsoff_trace_open,
        .close          = irqsoff_trace_close,
        .allow_instances = true,
        .use_max_tr = true,
    };

    在读取时,还需要做时间同步只读取max_buf->time_start时间戳以后的数据。tracing_open() -> __tracing_open() -> tracing_iter_reset():

    void tracing_iter_reset(struct trace_iterator *iter, int cpu)
    {
        struct ring_buffer_event *event;
        struct ring_buffer_iter *buf_iter;
        unsigned long entries = 0;
        u64 ts;
    
        per_cpu_ptr(iter->trace_buffer->data, cpu)->skipped_entries = 0;
    
        buf_iter = trace_buffer_iter(iter, cpu);
        if (!buf_iter)
            return;
    
        ring_buffer_iter_reset(buf_iter);
    
        /*
         * We could have the case with the max latency tracers
         * that a reset never took place on a cpu. This is evident
         * by the timestamp being before the start of the buffer.
         */
        /* (1) 对于时间戳小于time_start的trace数据全部丢弃掉, 
            只读取time_start时间戳以后的数据
         */
        while ((event = ring_buffer_iter_peek(buf_iter, &ts))) {
            if (ts >= iter->trace_buffer->time_start)
                break;
            entries++;
            ring_buffer_read(buf_iter, NULL);
        }
    
        per_cpu_ptr(iter->trace_buffer->data, cpu)->skipped_entries = entries;
    }

    3.1.3、irqsoff tracer的使能

    • irqsoff tracer需要使用function trace或者function_graph trace来记录最大latency的中间函数调用过程。所以它会注册一个global_ops->func,或者使用graph_ops注册。
    • trace数据的写入受trace_hardirqs_off()、trace_hardirqs_on()中配置的开关per_cpu(tracing_cpu, cpu)控制。在trace_hardirqs_off()时打开开关,在trace_hardirqs_on()时关闭开关,这样就记录下了irqoff过程中的具体函数调用过程。

    参考“1.2.5、function tracer使能”这一节,tracer的使能最后调用的是tracer->init()函数。

    对irqsoff tracer来说,调用的是irqsoff_tracer_init():

    static struct tracer irqsoff_tracer __read_mostly =
    {
        .name       = "irqsoff",
        .init       = irqsoff_tracer_init,
        .reset      = irqsoff_tracer_reset,
        .start      = irqsoff_tracer_start,
        .stop       = irqsoff_tracer_stop,
        .print_max  = true,
        .print_header   = irqsoff_print_header,
        .print_line     = irqsoff_print_line,
        .flag_changed   = irqsoff_flag_changed,
    #ifdef CONFIG_FTRACE_SELFTEST
        .selftest    = trace_selftest_startup_irqsoff,
    #endif
        .open           = irqsoff_trace_open,
        .close          = irqsoff_trace_close,
        .allow_instances = true,
        .use_max_tr = true,
    };
    
    ↓
    
    static int irqsoff_tracer_init(struct trace_array *tr)
    {
        trace_type = TRACER_IRQS_OFF;
    
        return __irqsoff_tracer_init(tr);
    }
    
    ↓
    
    static int __irqsoff_tracer_init(struct trace_array *tr)
    {
        if (irqsoff_busy)
            return -EBUSY;
    
        save_flags = tr->trace_flags;
    
        /* non overwrite screws up the latency tracers */
        set_tracer_flag(tr, TRACE_ITER_OVERWRITE, 1);
        set_tracer_flag(tr, TRACE_ITER_LATENCY_FMT, 1);
    
        tr->max_latency = 0;
        irqsoff_trace = tr;
        /* make sure that the tracer is visible */
        smp_wmb();
        tracing_reset_online_cpus(&tr->trace_buffer);
    
        /* (1) 如果采用function trace模式来记录最大latency过程中的trace信息 
            global_ops->func = irqsoff_tracer_call()
         */
        ftrace_init_array_ops(tr, irqsoff_tracer_call);
    
        /* Only toplevel instance supports graph tracing */
        /* (2) 选择使用 function trace模式 or function_graph trace模式 注册
            默认是function trace模式
         */
        if (start_irqsoff_tracer(tr, (tr->flags & TRACE_ARRAY_FL_GLOBAL &&
                          is_graph(tr))))
            printk(KERN_ERR "failed to start irqsoff tracer\n");
    
        irqsoff_busy = true;
        return 0;
    }
    
    ↓
    
    start_irqsoff_tracer() -> register_irqsoff_function()
    
    static int register_irqsoff_function(struct trace_array *tr, int graph, int set)
    {
        int ret;
    
        /* 'set' is set if TRACE_ITER_FUNCTION is about to be set */
        if (function_enabled || (!set && !(tr->trace_flags & TRACE_ITER_FUNCTION)))
            return 0;
    
        if (graph)
            /* (2.1) function_graph trace模式注册 */
            ret = register_ftrace_graph(&irqsoff_graph_return,
                            &irqsoff_graph_entry);
        else
            /* (2.2) function trace模式注册 */
            ret = register_ftrace_function(tr->ops);
    
        if (!ret)
            function_enabled = true;
    
        return ret;
    }

    在记录trace数据时,需要判断per_cpu(tracing_cpu, cpu)开关。以function trace模式为例,我们来看看其irqsoff_tracer_call()函数:

    static void
    irqsoff_tracer_call(unsigned long ip, unsigned long parent_ip,
                struct ftrace_ops *op, struct pt_regs *pt_regs)
    {
        struct trace_array *tr = irqsoff_trace;
        struct trace_array_cpu *data;
        unsigned long flags;
    
        if (!func_prolog_dec(tr, &data, &flags))
            return;
    
        trace_function(tr, ip, parent_ip, flags, preempt_count());
    
        atomic_dec(&data->disabled);
    }
    
    ↓
    
    static int func_prolog_dec(struct trace_array *tr,
                   struct trace_array_cpu **data,
                   unsigned long *flags)
    {
        long disabled;
        int cpu;
    
        /*
         * Does not matter if we preempt. We test the flags
         * afterward, to see if irqs are disabled or not.
         * If we preempt and get a false positive, the flags
         * test will fail.
         */
        cpu = raw_smp_processor_id();
        /* 对tracing_cpu开关的判断,由trace_hardirqs_off()、trace_hardirqs_on()函数控制 */
        if (likely(!per_cpu(tracing_cpu, cpu)))
            return 0;
    
        local_save_flags(*flags);
        /*
         * Slight chance to get a false positive on tracing_cpu,
         * although I'm starting to think there isn't a chance.
         * Leave this for now just to be paranoid.
         */
        if (!irqs_disabled_flags(*flags) && !preempt_count())
            return 0;
    
        *data = per_cpu_ptr(tr->trace_buffer.data, cpu);
        disabled = atomic_inc_return(&(*data)->disabled);
    
        if (likely(disabled == 1))
            return 1;
    
        atomic_dec(&(*data)->disabled);
    
        return 0;
    }

    3.2、数据存入

    它的trace会提供几部分信息:

    • 1、irqoff的最大时长:latency;
    • 2、在最大irqoff这期间所有的function trace信息;
    • 3、最后的irqon的函数回调信息;

    具体的存入方法参考“3.1、插桩”这一节的描述。

    3.3、数据读出

    从trace文件读出的irqoff tracer默认数据格式为:(显示最大latency以及过程中的详细函数调用)

    # cat trace
    # tracer: irqsoff
    #
    # irqsoff latency trace v1.1.5 on 4.4.21-g6678ed0-01979-g8d8eeb5-cIcd1f2d8-dirty
    # --------------------------------------------------------------------
    # latency: 1703 us, #184/184, CPU#3 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:8)
    #    -----------------
    #    | task: swapper/3-0 (uid:0 nice:0 policy:0 rt_prio:0)
    #    -----------------
    #  => started at: lpm_cpuidle_enter
    #  => ended at:   cpuidle_enter_state
    #
    #
    #                  _------=> CPU#
    #                 / _-----=> irqs-off
    #                | / _----=> need-resched
    #                || / _---=> hardirq/softirq
    #                ||| / _--=> preempt-depth
    #                |||| /     delay
    #  cmd     pid   ||||| time  |   caller
    #     \   /      |||||  \    |   /
      <idle>-0       3dn.1    3us : psci_enter_sleep <-lpm_cpuidle_enter
      <idle>-0       3dn.2    9us : update_debug_pc_event <-psci_enter_sleep
      <idle>-0       3dn.2   11us : _raw_spin_lock <-update_debug_pc_event
      <idle>-0       3dn.2   13us : preempt_count_add <-_raw_spin_lock
      <idle>-0       3dn.3   17us : do_raw_spin_trylock <-_raw_spin_lock
      <idle>-0       3dn.3   20us : _raw_spin_unlock <-update_debug_pc_event
      <idle>-0       3dn.3   23us : do_raw_spin_unlock <-_raw_spin_unlock
      <idle>-0       3dn.3   25us : preempt_count_sub <-_raw_spin_unlock
      <idle>-0       3dn.2   28us : ktime_get <-lpm_cpuidle_enter
      <idle>-0       3dn.2   33us : arch_counter_read <-ktime_get
      <idle>-0       3dn.2   35us : lpm_stats_cpu_exit <-lpm_cpuidle_enter
      <idle>-0       3dn.2   37us : update_level_stats.part.1 <-lpm_stats_cpu_exit
      <idle>-0       3dn.2   41us : cluster_unprepare <-lpm_cpuidle_enter
      <idle>-0       3dn.2   43us : _raw_spin_lock <-cluster_unprepare
      <idle>-0       3dn.2   44us : preempt_count_add <-_raw_spin_lock
      <idle>-0       3dn.3   46us : do_raw_spin_trylock <-_raw_spin_lock
      <idle>-0       3dn.3   50us : lpm_stats_cluster_exit <-cluster_unprepare
      <idle>-0       3dn.3   52us : update_level_stats.part.1 <-lpm_stats_cluster_exit
      <idle>-0       3dn.3   57us : update_debug_pc_event <-cluster_unprepare
      <idle>-0       3dn.3   59us : _raw_spin_lock <-update_debug_pc_event
      <idle>-0       3dn.3   60us : preempt_count_add <-_raw_spin_lock
      <idle>-0       3dn.4   62us : do_raw_spin_trylock <-_raw_spin_lock
      <idle>-0       3dn.4   65us : _raw_spin_unlock <-update_debug_pc_event
      <idle>-0       3dn.4   66us : do_raw_spin_unlock <-_raw_spin_unlock
      <idle>-0       3dn.4   69us : preempt_count_sub <-_raw_spin_unlock
      <idle>-0       3dn.3   71us : sched_set_cluster_dstate <-cluster_unprepare
      <idle>-0       3dn.3   74us : cpu_cluster_pm_exit <-cluster_unprepare
      <idle>-0       3dn.3   76us : _raw_read_lock <-cpu_cluster_pm_exit
      <idle>-0       3dn.3   78us : preempt_count_add <-_raw_read_lock
      <idle>-0       3dn.4   80us : do_raw_read_trylock <-_raw_read_lock
      <idle>-0       3dn.4   82us : cpu_pm_notify <-cpu_cluster_pm_exit
      <idle>-0       3dn.4   84us : __raw_notifier_call_chain <-cpu_pm_notify
      <idle>-0       3dn.4   86us : notifier_call_chain <-__raw_notifier_call_chain
      <idle>-0       3dn.4   88us : gic_cpu_pm_notifier <-notifier_call_chain
      <idle>-0       3dn.4   90us : arch_timer_cpu_pm_notify <-notifier_call_chain
      <idle>-0       3dn.4   92us : jtag_cpu_pm_callback <-notifier_call_chain
      <idle>-0       3dn.4   95us : gladiator_erp_pm_callback <-notifier_call_chain
      <idle>-0       3dn.4   97us : cpu_pm_pmu_notify <-notifier_call_chain
      <idle>-0       3dn.4   99us : cpu_pm_pmu_common <-cpu_pm_pmu_notify
      <idle>-0       3dn.4  102us : cti_cpu_pm_callback <-notifier_call_chain
      <idle>-0       3dn.4  104us : coresight_cti_ctx_restore <-cti_cpu_pm_callback
      <idle>-0       3dn.4  109us : _raw_spin_lock_irqsave <-coresight_cti_ctx_restore
      <idle>-0       3dn.4  111us : preempt_count_add <-_raw_spin_lock_irqsave
      <idle>-0       3dn.5  113us!: do_raw_spin_trylock <-_raw_spin_lock_irqsave
      <idle>-0       3dn.5  222us : _raw_spin_unlock_irqrestore <-coresight_cti_ctx_restore
      <idle>-0       3dn.5  224us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.5  226us : preempt_count_sub <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.4  228us : _raw_spin_lock_irqsave <-coresight_cti_ctx_restore
      <idle>-0       3dn.4  230us : preempt_count_add <-_raw_spin_lock_irqsave
      <idle>-0       3dn.5  232us!: do_raw_spin_trylock <-_raw_spin_lock_irqsave
      <idle>-0       3dn.5  337us : _raw_spin_unlock_irqrestore <-coresight_cti_ctx_restore
      <idle>-0       3dn.5  339us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.5  341us : preempt_count_sub <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.4  343us : _raw_spin_lock_irqsave <-coresight_cti_ctx_restore
      <idle>-0       3dn.4  346us : preempt_count_add <-_raw_spin_lock_irqsave
      <idle>-0       3dn.5  348us!: do_raw_spin_trylock <-_raw_spin_lock_irqsave
      <idle>-0       3dn.5  492us : _raw_spin_unlock_irqrestore <-coresight_cti_ctx_restore
      <idle>-0       3dn.5  494us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.5  497us : preempt_count_sub <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.4  499us : _raw_spin_lock_irqsave <-coresight_cti_ctx_restore
      <idle>-0       3dn.4  501us : preempt_count_add <-_raw_spin_lock_irqsave
      <idle>-0       3dn.5  503us+: do_raw_spin_trylock <-_raw_spin_lock_irqsave
      <idle>-0       3dn.5  603us : _raw_spin_unlock_irqrestore <-coresight_cti_ctx_restore
      <idle>-0       3dn.5  605us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.5  607us : preempt_count_sub <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.4  613us : fpsimd_cpu_pm_notifier <-notifier_call_chain
      <idle>-0       3dn.4  615us : _raw_read_unlock <-cpu_cluster_pm_exit
      <idle>-0       3dn.4  617us : do_raw_read_unlock <-_raw_read_unlock
      <idle>-0       3dn.4  619us : preempt_count_sub <-_raw_read_unlock
      <idle>-0       3dn.3  621us : update_cluster_history <-cluster_unprepare
      <idle>-0       3dn.3  623us : ktime_get <-update_cluster_history
      <idle>-0       3dn.3  626us : arch_counter_read <-ktime_get
      <idle>-0       3dn.3  629us : cluster_unprepare <-cluster_unprepare
      <idle>-0       3dn.3  631us : _raw_spin_lock <-cluster_unprepare
      <idle>-0       3dn.3  633us : preempt_count_add <-_raw_spin_lock
      <idle>-0       3dn.4  635us : do_raw_spin_trylock <-_raw_spin_lock
      <idle>-0       3dn.4  638us : _raw_spin_unlock <-cluster_unprepare
      <idle>-0       3dn.4  640us : do_raw_spin_unlock <-_raw_spin_unlock
      <idle>-0       3dn.4  642us : preempt_count_sub <-_raw_spin_unlock
      <idle>-0       3dn.3  644us : _raw_spin_unlock <-cluster_unprepare
      <idle>-0       3dn.3  645us : do_raw_spin_unlock <-_raw_spin_unlock
      <idle>-0       3dn.3  647us : preempt_count_sub <-_raw_spin_unlock
      <idle>-0       3dn.2  649us : tick_broadcast_oneshot_control <-lpm_cpuidle_enter
      <idle>-0       3dn.2  651us : __tick_broadcast_oneshot_control <-tick_broadcast_oneshot_control
      <idle>-0       3dn.2  653us : _raw_spin_lock <-__tick_broadcast_oneshot_control
      <idle>-0       3dn.2  654us : preempt_count_add <-_raw_spin_lock
      <idle>-0       3dn.3  657us : do_raw_spin_trylock <-_raw_spin_lock
      <idle>-0       3dn.3  660us : clockevents_switch_state <-__tick_broadcast_oneshot_control
      <idle>-0       3dn.3  663us : ktime_get <-__tick_broadcast_oneshot_control
      <idle>-0       3dn.3  667us : arch_counter_read <-ktime_get
      <idle>-0       3dn.3  668us : tick_program_event <-__tick_broadcast_oneshot_control
      <idle>-0       3dn.3  670us : clockevents_program_event <-tick_program_event
      <idle>-0       3dn.3  672us : ktime_get <-clockevents_program_event
      <idle>-0       3dn.3  676us : arch_counter_read <-ktime_get
      <idle>-0       3dn.3  678us : arch_timer_set_next_event_virt <-clockevents_program_event
      <idle>-0       3dn.3  679us : _raw_spin_unlock <-__tick_broadcast_oneshot_control
      <idle>-0       3dn.3  681us : do_raw_spin_unlock <-_raw_spin_unlock
      <idle>-0       3dn.3  683us : preempt_count_sub <-_raw_spin_unlock
      <idle>-0       3dn.2  685us : cpu_pm_exit <-lpm_cpuidle_enter
      <idle>-0       3dn.2  686us : _raw_read_lock <-cpu_pm_exit
      <idle>-0       3dn.2  688us : preempt_count_add <-_raw_read_lock
      <idle>-0       3dn.3  689us : do_raw_read_trylock <-_raw_read_lock
      <idle>-0       3dn.3  691us : cpu_pm_notify <-cpu_pm_exit
      <idle>-0       3dn.3  693us : __raw_notifier_call_chain <-cpu_pm_notify
      <idle>-0       3dn.3  694us : notifier_call_chain <-__raw_notifier_call_chain
      <idle>-0       3dn.3  696us : gic_cpu_pm_notifier <-notifier_call_chain
      <idle>-0       3dn.3  698us : gic_cpu_sys_reg_init <-gic_cpu_pm_notifier
      <idle>-0       3dn.3  700us : arch_timer_cpu_pm_notify <-notifier_call_chain
      <idle>-0       3dn.3  702us : jtag_cpu_pm_callback <-notifier_call_chain
      <idle>-0       3dn.3  704us : msm_jtag_restore_state <-jtag_cpu_pm_callback
      <idle>-0       3dn.3  708us : msm_jtag_etm_restore_state <-msm_jtag_restore_state
      <idle>-0       3dn.3  716us : raw_notifier_call_chain <-msm_jtag_etm_restore_state
      <idle>-0       3dn.3  717us!: notifier_call_chain <-raw_notifier_call_chain
      <idle>-0       3dn.3 1524us : gladiator_erp_pm_callback <-notifier_call_chain
      <idle>-0       3dn.3 1526us : cpu_pm_pmu_notify <-notifier_call_chain
      <idle>-0       3dn.3 1529us : armv8pmu_reset <-cpu_pm_pmu_notify
      <idle>-0       3dn.3 1531us : cpu_pm_pmu_common <-cpu_pm_pmu_notify
      <idle>-0       3dn.3 1533us : cpu_pm_pmu_setup <-cpu_pm_pmu_common
      <idle>-0       3dn.3 1538us : rcu_irq_enter <-cpu_pm_pmu_setup
      <idle>-0       3dn.3 1542us : rcu_eqs_exit_common.isra.48 <-rcu_irq_enter
      <idle>-0       3dn.3 1545us : rcu_try_advance_all_cbs <-rcu_eqs_exit_common.isra.48
      <idle>-0       3dn.3 1553us : armpmu_start <-cpu_pm_pmu_setup
      <idle>-0       3dn.3 1558us : armpmu_event_set_period <-armpmu_start
      <idle>-0       3dn.3 1562us : __rcu_read_lock <-perf_event_update_userpage
      <idle>-0       3dn.3 1566us : __rcu_read_unlock <-perf_event_update_userpage
      <idle>-0       3dn.3 1568us : armv8pmu_enable_event <-armpmu_start
      <idle>-0       3dn.3 1570us : _raw_spin_lock_irqsave <-armv8pmu_enable_event
      <idle>-0       3dn.3 1572us : preempt_count_add <-_raw_spin_lock_irqsave
      <idle>-0       3dn.4 1574us : do_raw_spin_trylock <-_raw_spin_lock_irqsave
      <idle>-0       3dn.4 1576us : _raw_spin_unlock_irqrestore <-armv8pmu_enable_event
      <idle>-0       3dn.4 1578us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.4 1581us : preempt_count_sub <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.3 1584us : rcu_irq_exit <-cpu_pm_pmu_setup
      <idle>-0       3dn.3 1586us : rcu_eqs_enter_common.isra.46 <-rcu_irq_exit
      <idle>-0       3dn.3 1591us : rcu_irq_enter <-cpu_pm_pmu_setup
      <idle>-0       3dn.3 1593us : rcu_eqs_exit_common.isra.48 <-rcu_irq_enter
      <idle>-0       3dn.3 1596us : rcu_try_advance_all_cbs <-rcu_eqs_exit_common.isra.48
      <idle>-0       3dn.3 1598us : armpmu_start <-cpu_pm_pmu_setup
      <idle>-0       3dn.3 1601us : armpmu_event_set_period <-armpmu_start
      <idle>-0       3dn.3 1603us : __rcu_read_lock <-perf_event_update_userpage
      <idle>-0       3dn.3 1605us : __rcu_read_unlock <-perf_event_update_userpage
      <idle>-0       3dn.3 1606us : armv8pmu_enable_event <-armpmu_start
      <idle>-0       3dn.3 1608us : _raw_spin_lock_irqsave <-armv8pmu_enable_event
      <idle>-0       3dn.3 1611us : preempt_count_add <-_raw_spin_lock_irqsave
      <idle>-0       3dn.4 1615us : do_raw_spin_trylock <-_raw_spin_lock_irqsave
      <idle>-0       3dn.4 1617us : _raw_spin_unlock_irqrestore <-armv8pmu_enable_event
      <idle>-0       3dn.4 1619us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.4 1622us : preempt_count_sub <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.3 1623us : rcu_irq_exit <-cpu_pm_pmu_setup
      <idle>-0       3dn.3 1627us : rcu_eqs_enter_common.isra.46 <-rcu_irq_exit
      <idle>-0       3dn.3 1629us : rcu_irq_enter <-cpu_pm_pmu_setup
      <idle>-0       3dn.3 1632us : rcu_eqs_exit_common.isra.48 <-rcu_irq_enter
      <idle>-0       3dn.3 1634us : rcu_try_advance_all_cbs <-rcu_eqs_exit_common.isra.48
      <idle>-0       3dn.3 1637us : armpmu_start <-cpu_pm_pmu_setup
      <idle>-0       3dn.3 1639us : armpmu_event_set_period <-armpmu_start
      <idle>-0       3dn.3 1642us : __rcu_read_lock <-perf_event_update_userpage
      <idle>-0       3dn.3 1644us : __rcu_read_unlock <-perf_event_update_userpage
      <idle>-0       3dn.3 1646us : armv8pmu_enable_event <-armpmu_start
      <idle>-0       3dn.3 1647us : _raw_spin_lock_irqsave <-armv8pmu_enable_event
      <idle>-0       3dn.3 1649us : preempt_count_add <-_raw_spin_lock_irqsave
      <idle>-0       3dn.4 1651us : do_raw_spin_trylock <-_raw_spin_lock_irqsave
      <idle>-0       3dn.4 1653us : _raw_spin_unlock_irqrestore <-armv8pmu_enable_event
      <idle>-0       3dn.4 1655us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.4 1657us : preempt_count_sub <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.3 1659us : rcu_irq_exit <-cpu_pm_pmu_setup
      <idle>-0       3dn.3 1662us : rcu_eqs_enter_common.isra.46 <-rcu_irq_exit
      <idle>-0       3dn.3 1665us : armv8pmu_start <-cpu_pm_pmu_common
      <idle>-0       3dn.3 1667us : _raw_spin_lock_irqsave <-armv8pmu_start
      <idle>-0       3dn.3 1670us : preempt_count_add <-_raw_spin_lock_irqsave
      <idle>-0       3dn.4 1672us : do_raw_spin_trylock <-_raw_spin_lock_irqsave
      <idle>-0       3dn.4 1674us : _raw_spin_unlock_irqrestore <-armv8pmu_start
      <idle>-0       3dn.4 1676us : do_raw_spin_unlock <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.4 1679us : preempt_count_sub <-_raw_spin_unlock_irqrestore
      <idle>-0       3dn.3 1680us : cti_cpu_pm_callback <-notifier_call_chain
      <idle>-0       3dn.3 1683us : fpsimd_cpu_pm_notifier <-notifier_call_chain
      <idle>-0       3dn.3 1684us : _raw_read_unlock <-cpu_pm_exit
      <idle>-0       3dn.3 1686us : do_raw_read_unlock <-_raw_read_unlock
      <idle>-0       3dn.3 1688us : preempt_count_sub <-_raw_read_unlock
      <idle>-0       3dn.2 1691us : sched_set_cpu_cstate <-lpm_cpuidle_enter
      <idle>-0       3dn.2 1693us : ktime_get <-lpm_cpuidle_enter
      <idle>-0       3dn.2 1697us : arch_counter_read <-ktime_get
      <idle>-0       3dn.1 1702us : lpm_cpuidle_enter <-cpuidle_enter_state
      <idle>-0       3dn.1 1708us+: trace_hardirqs_on <-cpuidle_enter_state
      <idle>-0       3dn.1 1720us : <stack trace>
     => trace_hardirqs_on
     => lpm_cpuidle_enter
     => cpuidle_enter_state
     => cpuidle_enter
     => cpu_startup_entry
     => secondary_start_kernel
     =>
    

    在通过trace文件读出数据时:

    • 具体函数调用信息是使用trace_function()或者trace_graph_function()函数进行记录的,调用相应的方法解析即可;
    • 最后的irqon的函数回调信息是使用__trace_stack()函数进行记录的,调用相应的方法解析即可;
    • 最大latency信息的解析,使用tracer->print_header();

    在第一次读操作时,会打印header。seq_read() -> s_show() -> print_trace_line() -> tracer->print_header():

    static void irqsoff_print_header(struct seq_file *s)
    {
        trace_default_header(s);
    }

    参考资料:

    1、Function Tracer Design
    2、ftrace function_graph分析

    展开全文
  • 基于 Cisco Packet Tracer 7.3 汉化中文语言包 ,翻译错误可直接邮件给我,bapilong@163.com
  • Cisco Packet Tracer 界面介绍+使用介绍

    万次阅读 多人点赞 2013-10-21 16:23:53
    Cisco Packet Tracer 界面介绍+使用介绍 Packet Tracer 版本 5.3   Routers 路由器(Ctrl+Alt+R) Switches 交换机(Ctrl+Alt+S) Hubs 分线器(Ctrl+Alt+U) Wieless Devices 无线设备(Ctrl+Alt+W...
    Routers 路由器(Ctrl+Alt+R)

    Switches 交换机(Ctrl+Alt+S)

    Hubs 分线器(集线器)(Ctrl+Alt+U)

    Wieless Devices 无线设备(Ctrl+Alt+W)

    Connections 通讯链路(Ctrl+Alt+O)

    End Devices 终端设备(Ctrl+Alt+V)

    WAN Emualtion WAN仿真(Ctrl+Alt+N)

    Custom Made Devices 定制设备(Ctrl+Alt+T)

    Multiuser Connection 多用户连接器(Ctrl+Alt+N)



    通讯链路
    Automatically Choose Connection Type 自动选择链路
    Console 配置线
    Copper Straight-Through 直连线
    Copper Croos-Over 交叉线
    Fiber 光纤
    Phone 电话线
    Coaxial 同轴电缆
    Serial DCE(Data Communication Equipment,数据通讯设备)串口线
    Serial DTE(Data Terminal Equipment,数据终端设备)串口线


    设备编辑区域
    Select(Esc)选定/取消 拖动选定设备
    Move layout(M) 移动画布
    Place note(N) 放置书签\贴便条
    Delete(Delete) 删除
    Inspect(I) 插入
    Resize Shape (Alt+R)调整形状大小
    Add Simple PDU(P)添加简单协议数据单元
    Add Complex PDU(C)添加复杂协议数据单元


    Realtime Mode(Shift+R)实时模式 可以操作

    Shimulation Mode(Shift+S)模拟模式 显示过程




    建立拓扑图



    单击PC-PT PC0的图标


    Physical 硬件 
    Modules(模块)模块拖动到右图的黑块(插口)即可使用
    Physical Device View 物理设备视图
    Zoom In(聚焦)也就是放大
    Original Size(原始大小)复原
    Zoom Out(散焦)缩小
    电源开关 
    模块插口


    Conflg 配置信息


    Desktop 桌面 包含IP地址、VPN、浏览器等常用功能的配置


    路由器配置
    “Physical”选项卡主要用于配置设备接口模块,选择左侧的模块,将其拖动到右侧设备插槽即可(在添加
    或删除模块时,必须保持设备开关处于关闭状态)。



    “Config”选项卡主要用于图形化配置路由器的基本参数和配置文件的相关操作



    “CLI” CLI 命令行 就是模拟真实设备的仿真界面,可以在此对设备进行所需要的配置操作







    一个实例:研究应用层和传输层协议

    一、创建一个 End Devices (终端)里的 PC-PT 
    【创建方法】:(点击图标 Event list)然后在空白地区放置


    二、创建一个 Server-PT

    三、用  Copper Croos-Over 交叉线 连接他们

    四、点击PC1(Conflg界面下) 单击选项DHCP

    五、切换到模拟模式,如果弹出一个窗(事件表),点击Event List 事件表关闭

    六、点击PC1 进入Desktop选项 

    点击Desktop选项的Web Browser互联网浏览器

    URL输入baidu.com点击GO(或回车)然后关闭PC1窗口 回到界面

    七、点击Auto Capture自动捕获,就可以看到动画

    【动画】

    八、点击Event List进入事件表 可以看见模拟传输过程

    【过程】

    展开全文
  • 三层交换机下利用DHCP—SERVER给不同VLAN分配IP地址。
  • Cisco Packet Tracer 实验

    2021-10-15 18:50:12
    二、用交换机构建 LAN三、交换机接口地址列表四、生成树协议(Spanning Tree Protocol)五、路由器配置初步六、静态路由七、动态路由RIP八、动态路由OSPF九、基于端口的网络地址翻译PAT十、虚拟局域网VLAN十一、虚拟...


    一、直接连接两台 PC 构建 LAN

    将两台 PC 直接连接构成一个网络。注意:直接连接需使用交叉线。
    进行两台 PC 的基本网络配置,只需要配置 IP 地址即可,然后相互 ping 通即成功。
    在这里插入图片描述
    配置两台PC.
    在这里插入图片描述
    在这里插入图片描述

    PC0PC1
    192.168.10.1192.168.10.2

    互相ping
    在这里插入图片描述

    搞定。

    二、用交换机构建 LAN

    构建如下拓扑结构的局域网:
    在这里插入图片描述
    各PC的基本网络配置如下表:

    在这里插入图片描述
    构建并按照前面的ip进行配置:
    在这里插入图片描述
    在这里插入图片描述
    1.PC0能够ping通PC1,不能ping通PC2和PC3
    在这里插入图片描述
    2.PC3能够ping通PC2,不能ping通PC0和PC1
    在这里插入图片描述
    3.这四台PC之间都能相互ping通;因为子网掩码为255.255.255.0时,PC0、PC1与PC2、PC3处于两个不同的子网下面,而改成255.255.0.0后这四台PC处于同一子网下,因而能够互相ping通。
    在这里插入图片描述
    4.使用二层交换机连接的网路需要配置网关,二层交换机配置好网关后可在2个网络间建立传输连接,使不同网络上的主机间建立起跨越多个网络的级联的、点对点的传输连接。

    在这里插入图片描述
    集线器Hub对于接收到的帧只能从其他端口进行广播,而交换机会对接收到的帧进行判断并转发到相应的端口。

    三、交换机接口地址列表

    二层交换机是一种即插即用的多接口设备,它对于收到的帧有 3 种处理方式:广播、转发和丢弃(请弄清楚何时进行何种操作)。那么,要转发成功,则交换机中必须要有接口地址列表即 MAC 表,该表是交换机通过学习自动得到的!

    仍然构建上图的拓扑结构,并配置各计算机的 IP 在同一个一个子网,使用工具栏中的放大镜点击某交换机如左边的 Switch3,选择 MAC Table,可以看到最初交换机的 MAC 表是空的,也即它不知道该怎样转发帧(那么它将如何处理?),用 PC0 访问(ping)PC1 后,再查看该交换机的 MAC 表,现在有相应的记录,请思考如何得来。随着网络通信的增加,各交换机都将生成自己完整的 MAC 表,此时交换机的交换速度就是最快的!

    最初情况:
    在这里插入图片描述
    用pc0访问pc1之后再次查看
    在这里插入图片描述
    在这里插入图片描述
    从上图可以看出,最初交换机的 MAC 表是空的,即它不知道该怎样转发帧,那么它将把接收到的帧进行广播;若收到某一端口发回的回复即可知道该帧的MAC地址并存到其MAC表中。
    用 PC0 访问(ping)PC1 后,再查看该交换机的 MAC 表,现在有相应的记录,这是通过ARP(地址解析协议)得来的MAC地址。

    在这里插入图片描述
    我把这个称之为仿真模式,可以一步一步运行,可以便于我们观察中间的过程。

    四、生成树协议(Spanning Tree Protocol)

    交换机在目的地址未知或接收到广播帧时是要进行广播的。如果交换机之间存在回路/环路,那么就会产生广播循环风暴,从而严重影响网络性能。

    而交换机中运行的 STP 协议能避免交换机之间发生广播循环风暴。

    只使用交换机,构建如下拓扑:
    在这里插入图片描述
    自己构建如下:
    在这里插入图片描述

    这是初始时的状态。我们可以看到交换机之间有回路,这会造成广播帧循环传送即形成广播风暴,严重影响网络性能。

    随后,交换机将自动通过生成树协议(STP)对多余的线路进行自动阻塞(Blocking),以形成一棵以 Switch4 为根(具体哪个是根交换机有相关的策略)的具有唯一路径树即生成树!

    经过一段时间,随着 STP 协议成功构建了生成树后,Switch5 的两个接口当前物理上是连接的,但逻辑上是不通的,处于Blocking状态(桔色)如下图所示:
    在这里插入图片描述
    自己的图如下:
    在这里插入图片描述

    在网络运行期间,假设某个时候 Switch4 与 Switch5 之间的物理连接出现问题(将 Switch4 与 Switch5 的连线剪掉),则该生成树将自动发生变化。Switch5 上方先前 Blocking 的那个接口现在活动了(绿色),但下方那个接口仍处于 Blocking 状态(桔色)。如下图所示:
    在这里插入图片描述
    自己的图如下:
    在这里插入图片描述

    在这里插入图片描述

    五、路由器配置初步

    我们模拟重庆交通大学和重庆大学两个学校的连接,构建如下拓扑:
    在这里插入图片描述
    在这里插入图片描述
    为交通大学的路由器配置,先关闭开关,拖入WIC-1T到指定位置后,再打开开关。
    对其进行配置
    在这里插入图片描述
    配置整个拓扑图:
    在这里插入图片描述

    说明一
    交通大学与重庆大学显然是两个不同的子网。在不同子网间通信需通过路由器。

    路由器的每个接口下至少是一个子网,图中我们简单的规划了 3 个子网:

    1. 左边路由器是交通大学的,其下使用交换机连接交通大学的网络,分配网络号
      192.168.1.0/24,该路由器接口也是交通大学网络的网关,分配 IP 为 192.168.1.1
    2. 右边路由器是重庆大学的,其下使用交换机连接重庆大学的网络,分配网络号
      192.168.3.0/24,该路由器接口也是重庆大学网络的网关,分配 IP 为 192.168.3.1
    3. 两个路由器之间使用广域网接口相连,也是一个子网,分配网络号 192.168.2.0/24

    说明二
    现实中,交通大学和重庆大学的连接是远程的。该连接要么通过路由器的光纤接口,要么通过广域网接口即所谓的 serial 口(如拓扑图所示)进行,一般不会通过双绞线连接(为什么?)。
    答:双绞线传输距离有限

    下面我们以通过路由器的广域网口连接为例来进行相关配置。请注意:我们选用的路由器默认没有广域网模块(名称为 WIC-1T 等),需要关闭路由器后添加,然后再开机启动。

    说明三
    在模拟的广域网连接中需注意 DCE 和 DTE 端(连线时线路上有提示,带一个时钟标志的是 DCE 端。有关 DCE 和 DTE 的概念请查阅相关资料。),在 DCE 端需配置时钟频率 64000

    说明四
    路由器有多种命令行配置模式,每种模式对应不同的提示符及相应的权限。

    请留意在正确的模式下输入配置相关的命令。

    • User mode:用户模式
    • Privileged mode:特权模式
    • Global configuration mode:全局配置模式
    • Interface mode:接口配置模式
    • Subinterface mode:子接口配置模式

    说明五
    在现实中,对新的路由器,显然不能远程进行配置,我们必须在现场通过笔记本的串口与路由器的 console 接口连接并进行初次的配置(注意设置比特率为9600)后,才能通过网络远程进行配置。这也是上图左上画出笔记本连接的用意。

    说明六
    在路由器的 CLI 界面中,可看到路由器刚启动成功后,因为无任何配置,将会提示是否进行对话配置(Would you like to enter the initial configuration dialog?),因其步骤繁多,请选择 NO

    比如交通大学路由器的初步配置可以如下:
    在这里插入图片描述
    拓扑图中路由器各接口配置数据如下:
    在这里插入图片描述
    交通大学路由器基本配置如下:
    以太网口:
    在这里插入图片描述
    广域网口:
    在这里插入图片描述
    重庆大学路由器基本配置如下:
    以太网口:
    在这里插入图片描述
    广域网口:
    在这里插入图片描述
    至此,路由器基本的配置完成。请按照上面 PC 配置表继续配置各个 PC 。

    配置完成后进行ping测试:
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    答:这是因为交通大学内的PC属于同一个子网同时使用交换机连接,重庆大学内的PC也属于另一个子网同时也是使用交换机连接;但在交通大学和重庆大学之间是通过路由器连接,由于路由器的路由表没有初始化,因此交通大学内的PC不能与重庆大学内的PC通信。

    六、静态路由

    静态路由是非自适应性路由协议,是由网络管理人员手动配置的,不能够根据网络拓扑的变化而改变。 因此,静态路由简单高效,适用于结构非常简单的网络。

    在当前这个简单的拓扑结构中我们可以使用静态路由,即直接告诉路由器到某网络该怎么走即可。

    在前述路由器基本配置成功的情况下使用以下命令进行静态路由协议的配置:

    交通大学路由器静态路由配置:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#ip route 192.168.3.0 255.255.255.0 192.168.2.2   // 告诉交通大学路由器到 192.168.3.0 这个网络的下一跳是 192.168.2.2
    Router(config)#exit   //退到特权模式
    Router#show ip route    //查看路由表
    

    重庆大学路由器静态路由配置:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#ip route 192.168.1.0 255.255.255.0 192.168.2.1   // 告诉重庆大学路由器到 192.168.1.0 这个网络的下一跳是 192.168.2.1
    Router(config)#exit   //退到特权模式
    Router#show ip route    //查看路由表
    

    查看路由表你可看到标记为 S 的一条路由,S 表示 Static
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    至此,这些 PC 能全部相互 ping 通!
    在这里插入图片描述

    七、动态路由RIP

    动态路由协议采用自适应路由算法,能够根据网络拓扑的变化而重新计算机最佳路由。

    RIP 的全称是 Routing Information Protocol,是距离矢量路由的代表(目前虽然淘汰,但可作为我们学习的对象)。使用 RIP 协议只需要告诉路由器直接相连有哪些网络即可,然后 RIP 根据算法自动构建出路由表。

    因为我们模拟的网络非常简单,因此不能同时使用静态和动态路由,否则看不出效果,所以我们需要把刚才配置的静态路由先清除掉。

    清除静态路由配置:

    1. 直接关闭路由器电源。相当于没有保存任何配置,然后各接口再按照前面基本配置所述重新配置 IP
      等参数(推荐此方法,可以再熟悉一下接口的配置命令);
    2. 使用 no 命令清除静态路由。在全局配置模式下,交通大学路由器使用:no ip route 192.168.3.0 255.255.255.0 192.168.2.2,重庆大学路由器使用:no ip route 192.168.1.0 255.255.255.0 192.168.2.1 。相当于使用 no 命令把刚才配置的静态路由命令给取消。

    交通大学路由器 RIP 路由配置:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#router rip   // 启用 RIP 路由协议,注意是 router 命令
    Router(config-router)#network 192.168.1.0   // 网络 192.168.1.0 与我直连
    Router(config-router)#network 192.168.2.0   // 网络 192.168.2.0 与我直连
    Router(config-router)#^z   //直接退到特权模式
    Router#show ip route    //查看路由表
    

    重庆大学路由器 RIP 路由配置:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#router rip   // 启用RIP路由协议,注意是 router 命令
    Router(config-router)#network 192.168.3.0   // 网络 192.168.3.0 与我直连
    Router(config-router)#network 192.168.2.0   // 网络 192.168.2.0 与我直连
    Router(config-router)#^z   //直接退到特权模式
    Router#show ip route    //查看路由表
    

    查看路由表你可看到标记为 R 的一条路由,R 表示 RIP
    在这里插入图片描述
    在这里插入图片描述

    至此,这些 PC 也能全部相互 ping 通!
    在这里插入图片描述

    八、动态路由OSPF

    OSPF(Open Shortest Path First 开放式最短路径优先)是一个内部网关协议(Interior Gateway Protocol,简称 IGP), 用于在单一自治系统(Autonomous System,AS)内决策路由。OSPF 性能优于 RIP,是当前域内路由广泛使用的路由协议。

    同样的,我们需要把刚才配置的 RIP 路由先清除掉。
    清除 RIP 路由配置:

    1. 直接关闭路由器电源。相当于没有保存任何配置,然后各接口再按照前面基本配置所述重新配置 IP 等参数
    2. 使用 no 命令清除 RIP 路由。在全局配置模式下,各路由器都使用:no router rip 命令进行清除

    交通大学路由器 OSPF 路由配置:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#router ospf 1   // 启用 OSPF 路由协议,进程号为1(可暂不理会进程号概念)
    Router(config-router)#network 192.168.1.0 0.0.0.255 area 0   // 自治域0中的属于 192.168.1.0/24 网络的所有主机(反向掩码)参与 OSPF
    Router(config-router)#network 192.168.2.0 0.0.0.255 area 0   // 自治域0中的属于 192.168.2.0/24 网络的所有主机(反向掩码)参与 OSPF
    Router(config-router)#^z   //直接退到特权模式
    Router#show ip route    //查看路由表
    

    重庆大学路由器 OSPF 路由配置:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#router ospf 1   // 启用 OSPF 路由协议,进程号为1
    Router(config-router)#network 192.168.3.0 0.0.0.255 area 0   // 自治域0中的属于 192.168.3.0/24 网络的所有主机(反向掩码)参与 OSPF
    Router(config-router)#network 192.168.2.0 0.0.0.255 area 0   // 自治域0中的属于 192.168.2.0/24 网络的所有主机(反向掩码)参与 OSPF
    Router(config-router)#^z   //直接退到特权模式
    Router#show ip route    //查看路由表
    

    查看路由表你可看到标记为 O 的一条路由,O 表示 OSPF 。
    在这里插入图片描述

    在这里插入图片描述

    至此,这些 PC 能全部相互 ping 通!

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    九、基于端口的网络地址翻译PAT

    网络地址转换(NAT,Network Address Translation)被各个 Internet 服务商即 ISP 广泛应用于它们的网络中,也包括 WiFi 网络。 原因很简单,NAT 不仅完美地解决了 lP 地址不足的问题,而且还能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机。

    NAT 的实现方式一般有三种:

    • 静态转换: Static NAT
    • 动态转换: Dynamic NAT
    • 端口多路复用: OverLoad

    端口多路复用使用最多也最灵活。OverLoad 是指不仅改变发向 Internet 数据包的源 IP 地址,同时还改变其源端口,即进行了端口地址转换(PAT,Port Address Translation)。

    采用端口多路复用方式,内部网络的所有主机均可共享一个合法外部 IP 地址实现对 Internet 的访问,从而可以最大限度地节约IP地址资源。 同时,又可隐藏网络内部的所有主机,有效避免来自 Internet 的攻击。因此,目前网络中应用最多的就是端口多路复用方式。

    我们仍然使用重庆交通大学和重庆大学两个学校的拓扑进行 PAT 实验。我们需要保证两个学校的路由已经配置成功,无论使用静态路由还是动态路由,以下我们给出完整的配置过程:设定这两个学校的路由器使用 OSPF 协议,模拟交通大学使用内部 IP 地址(192.168.1.0/24),模拟重庆大学使用外部 IP 地址(8.8.8.0/24),两个路由器之间使用外部 IP 地址(202.202.240.0/24),在交通大学的出口位置即广域网口实施 PAT。

    拓扑图中各 PC 配置数据如下:
    在这里插入图片描述
    在这里插入图片描述
    拓扑图中路由器各接口配置数据如下:
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述
    交通大学路由器接口配置如下:
    以太网口:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#int f0/0   // 进入配置以太网口模式
    Router(config-if)#ip address 192.168.1.1 255.255.255.0   // 配置 IP
    Router(config-if)#no shutdown   // 激活接口
    

    广域网口:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#int s0/0   // 进入配置广域网口模式
    Router(config-if)#ip address 202.202.240.1 255.255.255.0   //配置 IP
    Router(config-if)#clock rate 64000    // 其为 DCE 端,配置时钟频率
    Router(config-if)#no shutdown   // 激活接口
    

    重庆大学路由器接口配置如下:
    以太网口:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#int f0/0   // 进入配置以太网口模式
    Router(config-if)#ip address 8.8.8.1 255.255.255.0   // 配置 IP
    Router(config-if)#no shutdown   // 激活接口
    

    广域网口:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#int s0/0   // 进入配置广域网口模式
    Router(config-if)#ip address 202.202.240.2 255.255.255.0   // 配置 IP
    Router(config-if)#no shutdown   // 激活接口
    

    交通大学路由器 OSPF 路由配置:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#router ospf 1   // 启用 OSPF 路由协议,进程号为1(可暂不理会进程号概念)
    Router(config-router)#network 192.168.1.0 0.0.0.255 area 0   // 自治域0中的属于192.168.1.0/24网络的所有主机(反向掩码)参与 OSPF
    Router(config-router)#network 202.202.240.0 0.0.0.255 area 0   // 自治域0中的属于202.202.240.0/24网络的所有主机(反向掩码)参与 OSPF
    

    重庆大学路由器 OSPF 路由配置:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#router ospf 1   // 启用 OSPF 路由协议,进程号为1
    Router(config-router)#network 202.202.240.0 0.0.0.255 area 0   // 自治域0中的属于202.202.240.0/24网络的所有主机(反向掩码)参与 OSPF
    Router(config-router)#network 8.8.8.0 0.0.0.255 area 0   // 自治域0中的属于8.8.8.0/24网络的所有主机(反向掩码)参与 OSPF
    

    此时,这些 PC 能全部相互 ping 通!如在交通大学内部使用 PC0(192.168.1.2)来 ping 重庆大学的PC2(8.8.8.2)应该成功。

    下面我们将重庆大学的路由器看着 Internet 中的骨干路由器,那么这些路由器将不会转发内部/私有 IP 地址的包(直接丢弃)。我们通过在重庆大学路由器上实施访问控制 ACL ,即丢弃来自交通大学(私有 IP 地址)的包来模拟这个丢包的过程。

    重庆大学路由器丢包的配置:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#access-list 1 deny 192.168.1.0 0.0.0.255  // 创建 ACL 1,丢弃/不转发来自 192.168.1.0/24 网络的所有包
    Router(config)#access-list 1 permit any  // 添加 ACL 1 的规则,转发其它所有网络的包
    Router(config)#int s0/0   // 配置广域网口
    Router(config-if)#ip access-group 1 in  // 在广域网口上对进来的包实施 ACL 1 中的规则,实际就是广域网口如果收到来自 192.168.1.0/24 IP的包即丢弃
    

    此时,再使用交通大学内部的 PC0(192.168.1.2)来 ping 重庆大学的 PC2(8.8.8.2)就不成功了,会显示目的主机不可到达(Destination host unreachable)信息。
    在这里插入图片描述

    在这里插入图片描述
    下面,我们就开始实施 PAT。即:我们将会在交通大学路由器的出口上将内部/私有 IP 地址转换为外部/公开 IP,从而包的源 IP 发生了改变,就不会被重庆大学路由器丢弃,因此网络连通。

    交通大学路由器 PAT 配置:

    Router>en   // 从普通模式进入特权模式
    Router#conf t   // 进入全局配置模式
    Router(config)#access-list 1 permit 192.168.1.0 0.0.0.255  // 创建 ACL 1,允许来自 192.168.1.0/24 网络的所有包
    Router(config)#ip nat inside source list 1 interface s0/0 overload  // 来自于 ACL 中的 IP 将在广域网口实施 PAT
    Router(config)#int f0/0   // 配置以太网口
    Router(config-if)#ip nat inside   // 配置以太网口为 PAT 的内部
    Router(config)#int s0/0   // 配置广域网口
    Router(config-if)#ip nat outside   // 配置广域网口为 PAT 的外部
    

    现在,再次使用交通大学内部的 PC0(192.168.1.2)来 ping 重庆大学的PC2(8.8.8.2)则OK。

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    十、虚拟局域网VLAN

    在实际网络中,你可看到路由器一般位于网络的边界,而内部几乎全部使用交换机连接。

    前面我们分析过,交换机连接的是同一个子网! 显然,在这样一个大型规模的子网中进行广播甚至产生广播风暴将严重影响网络性能甚至瘫痪。

    另外我们也已经知道,其实学校是划分了 N 多个子网的,那么这些交换机连接的就绝不是一个子网!这样矛盾的事情该如何解释呢?我们实际上使用了支持 VLAN 的交换机!而前述的交换机只是普通的 2 层交换机(或者我们把它当作 2 层交换机在使用。

    VLAN(Virtual Local Area Network)即虚拟局域网。通过划分 VLAN,我们可以把一个物理网络划分为多个逻辑网段即多个子网。

    划分 VLAN 后可以杜绝网络广播风暴,增强网络的安全性,便于进行统一管理等。

    在 CPT 中构建如下图所示拓扑:
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    Cisco 2960 交换机是支持 VLAN 的交换机,共有 24 个 100M 和 2 个 1000M 以太网口。默认所有的接口都在 VLAN 1 中,故此时连接上来的计算机都处于同一 VLAN,可以进行通信。

    下面我们就该交换机的 24 个 100M 接口分为 3 个部分,划分到 3 个不同的 VLAN 中,id 号分别设为 10、20、30,且设置别名(computer、communication、electronic)以利于区分和管理。

    交换机 VLAN 配置:

    Switch>en
    Switch#conf t
    Switch(config)#vlan 10    // 创建 id 为 10 的 VLAN(缺省的,交换机所有接口都属于VLAN 1,不能使用)
    Switch(config-vlan)#name computer    // 设置 VLAN 的别名
    Switch(config-vlan)#exit
    Switch(config)#int vlan 10    // 该 VLAN 为一个子网,设置其 IP,作为该子网网关
    Switch(config-if)#ip address 192.168.0.1 255.255.255.0
    Switch(config-if)#exit
    Switch(config)#vlan 20    // 创建 id 为 20 的 VLAN
    Switch(config-vlan)#name communication    //设置别名
    Switch(config-vlan)#exit
    Switch(config)#int vlan 20
    Switch(config-if)#ip addr 192.168.1.1 255.255.255.0
    Switch(config-if)#exit
    Switch(config)#vlan 30    // 创建 id 为 20 的 VLAN
    Switch(config-vlan)#name electronic    // 设置别名
    Switch(config-vlan)#exit
    Switch(config)#int vlan 30
    Switch(config-if)#ip add 192.168.2.1 255.255.255.0
    Switch(config-if)#exit
    Switch(config)#int range f0/1-8    // 成组配置接口(1-8)
    Switch(config-if-range)#switchport mode access    // 设置为存取模式
    Switch(config-if-range)#switchport access vlan 10    // 划归到 VLAN 10 中
    Switch(config-if-range)#exit
    Switch(config)#int range f0/9-16
    Switch(config-if-range)#switchport mode access
    Switch(config-if-range)#switchport access vlan 20
    Switch(config-if-range)#exit
    Switch(config)#int range f0/17-24
    Switch(config-if-range)#switchport mode access
    Switch(config-if-range)#switchport access vlan 30
    Switch(config-if-range)#^Z
    Switch#show vlan // 查看 VLAN 的划分情况
    

    至此,在该交换机上我们就划分了 3 个 VLAN(不包括缺省的 VLAN 1)。

    各 VLAN 下 PC 的网络配置及连接的交换机接口如下表:

    在这里插入图片描述
    此时可以使用 ping 命令进行测试,你会发现只有在同一 VLAN 中的 PC 才能通信,且广播也局限于该 VLAN。

    在这里插入图片描述

    在这里插入图片描述
    答:不通VLAN中的PC的网段不同,因而它们之间不能相互通信;VLAN是数据链路层的协议,负责划分广播域,而不需要考虑IP;网关是用来进行协议转换的,不同的网段之间要想通信一定需要网关。如果需要发起广播测试则需要三层交换机。

    十一、虚拟局域网管理VTP

    前一个实验我们在交换机上进行了 VLAN 的规划和划分。但在实际应用中,我们绝不允许在这些支持VLAN的交换机上进行随意的 VLAN 划分,如此将造成管理混乱!VLAN的划分必须得到统一的规划和管理,这就需要 VTP 协议。

    VTP(VLAN Trunk Protocol)即 VLAN 中继协议。VTP 通过 ISL 帧或 Cisco 私有 DTP 帧(可查阅相关资料了解)保持 VLAN 配置统一性,也被称为虚拟局域网干道协议,它是思科私有协议。 VTP 统一管理、增加、删除、调整VLAN,自动地将信息向网络中其它的交换机广播。

    此外,VTP 减小了那些可能导致安全问题的配置,只要在 VTP Server 做相应设置,VTP Client 会自动学习 VTP Server 上的 VLAN 信息。

    为演示 VTP,重新构建如下拓扑结构:
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    目前该网络都属于 VLAN 1,也即这些 PC 是可以相互通信的。前面说过,无论对于性能、管理还是安全等而言,现实中我们必须进行 VLAN 划分。

    现在我们的要求是:新建两个 VLAN,然后让 PC0 和 PC1 属于 VLAN 2,PC1 和 PC3 属于 VLAN 3

    我们将在核心交换机 3560上进行如下工作:

    1. 设置为 server 模式,VTP 域为 cqjtu
    2. 新建 VLAN 2,网络号 192.168.1.0/24,网关 192.168.1.1
    3. 新建 VLAN 3,网络号 192.168.2.0/24,网关 192.168.2.1

    3560 VTP Server 配置:

    Switch>en
    Switch#conf t
    Switch(config)#hostname 3560    // 更改交换机名称(可选)
    3560(config)#vtp domain cqjtu   // 设置 VTP 域名称为 cqjtu
    3560(config)#vtp mode server    // 设置其为 VTP 服务器模式
    3560(config)#vlan 2             // 新建VLAN 2
    3560(config-vlan)#name computer // 设置 VLAN 2 的别名(可选)
    3560(config-vlan)#exit
    3560(config)#vlan 3             // 再建 VLAN 3
    3560(config-vlan)#name communication    //设置 VLAN 2 的别名(可选)
    3560(config-vlan)#exit
    3560(config)#int vlan 2    // 配置接口 VLAN 2,它将是该子网(左边)的网关
    3560(config-if)#ip address 192.168.1.1 255.255.255.0
    3560(config-if)#exit
    3560(config)#int vlan 3    // 配置接口 VLAN 3,它将是该子网(右边)的网关
    3560(config-if)#ip address 192.168.2.1 255.255.255.0
    

    在这里插入图片描述

    我们将在左边交换机 2960A 上进行如下工作:

    1. 加入名为 cqjtu 的 VTP 域
    2. 配置与核心交换机 3560 连接的千兆接口 g0/1 为 trunk 模式
    3. 将接口 f0/1 划分到 VLAN 2 中
    4. 将接口 f0/2 划分到 VLAN 3 中

    2960A(左边) VTP Client 配置:

    Switch>en
    Switch#conf t
    Switch(config)#hostname 2960A    // 更改交换机名称(可选)
    2960A(config)#vtp domain cqjtu   // 加入名为 cqjtu 的 VTP 域
    2960A(config)#vtp mode client    // 设置模式为 VTP 客户
    2960A(config)#int g0/1    // 配置与核心交换机 3560 连接的 g0/1 千兆接口
    2960A(config-if)#switchport mode trunk    // 设置该接口为中继(trunk)模式
    2960A(config-if)#switchport trunk allowed vlan all  // 允许为所有的 VLAN 中继
    2960A(config-if)#exit
    2960A(config)#int f0/1    // 配置接口 1
    2960A(config-if)#switchport mode access    // 设置该接口为正常访问模式
    2960A(config-if)#switchport access vlan 2  // 将接口划分到 VLAN 2
    2960A(config-if)#exit
    2960A(config)#int f0/2    // 配置接口 2
    2960A(config-if)#switchport mode access    // 设置该接口为正常访问模式
    2960A(config-if)#switchport access vlan 3  // 将接口划分到 VLAN 3
    

    在这里插入图片描述

    我们将在右边交换机 2960B 上进行同样的工作:

    1. 加入名为 cqjtu VTP 域
    2. 配置与核心交换机 3560 连接的千兆接口 g0/1 为 trunk 模式
    3. 将接口 f0/1 划分到VLAN 2 中
    4. 将接口 f0/2 划分到 VLAN 3 中

    2960B(右边) VTP Client 配置:

    Switch>en
    Switch#conf t
    Switch(config)#hostname 2960B    // 更改交换机名称(可选)
    2960B(config)#vtp domain cqjtu   // 加入名为 cqjtu 的 VTP 域
    2960B(config)#vtp mode client    // 设置模式为 VTP 客户
    2960B(config)#int g0/1    // 配置与核心交换机 3560 连接的 g0/1 千兆接口
    2960B(config-if)#switchport mode trunk    // 设置该接口为中继(trunk)模式
    2960B(config-if)#switchport trunk allowed vlan all  // 允许为所有的 VLAN 中继
    2960B(config-if)#exit
    2960B(config)#int f0/1    // 配置接口 1
    2960B(config-if)#switchport mode access    // 设置该接口为正常访问模式
    2960B(config-if)#switchport access vlan 2  // 将接口划分到 VLAN 2
    2960B(config-if)#exit
    2960B(config)#int f0/2    // 配置接口 2
    2960B(config-if)#switchport mode access    // 设置该接口为正常访问模式
    2960B(config-if)#switchport access vlan 3  // 将接口划分到 VLAN 3
    

    在这里插入图片描述

    至此,各交换机配置完毕。

    在这里插入图片描述
    各 PC 连接的交换机和接口以及网络配置如下:
    在这里插入图片描述
    至此,VTP 配置完成。同 VLAN 可以 ping 通,而不同 VLAN 不行(即使在同一交换机下,如从 PC0 到 PC1),且能够方便的统一规划和管理。
    在这里插入图片描述
    一定要将多层交换机的1和2接口切换为trunk,不然一直无法连通。

    在这里插入图片描述
    在这里插入图片描述

    十二、VLAN间的通信

    VTP 只是给我们划分和管理 VLAN 提供了方便,由上面的测试得知,目前我们仍然不能在 VLAN 间通信。

    因为默认的,VLAN 间是不允许进行通信,此时我们需要所谓的独臂路由器在 VLAN 间为其进行转发!

    我们使用的核心交换机 3560 是个 3 层交换机,可工作在网络层,也称路由交换机,即具有路由功能,能进行这种转发操作。

    3560 交换机配置:

    3560>en
    3560#conf t
    3560(config)#int g0/1    // 配置连接左边 2960A 交换机的接口
    3560(config-if)#switchport trunk encapsulation dot1q    // 封装 VLAN 协议
    3560(config-if)#switchport mode trunk     // 设置为中继模式
    3560(config-if)#switchport trunk allowed vlan all     // 在所有 VLAN 间转发
    3560(config-if)#exit
    3560(config)#int g0/2    // 配置连接右边 2960B 交换机的接口
    3560(config-if)#switchport trunk encapsulation dot1q    //封装 VLAN 协议
    3560(config-if)#switchport mode trunk     // 设置为中继模式
    3560(config-if)#switchport trunk allowed vlan all     // 在所有 VLAN 间转发
    3560(config-if)#exit
    3560(config)#ip routing    // 启用路由转发功能
    

    在这里插入图片描述

    至此,各 VLAN 中的 PC 可以正常通信。
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    当使用 CPT 的模拟方式进行上面的测试时( PC0 ping PC1),我们可以清楚的看到 ICMP 包全部都由 3560 交换机在转发,非常容易形成瓶颈。

    十三、DHCP、DNS及Web服务器简单配置

    动态主机配置 DHCP、域名解析 DNS 以及 Web 服务在日常应用中作用巨大,我们构建如下简单的拓扑来进行练习。
    在这里插入图片描述
    该拓扑中,服务器及客户机都连在同一交换机上。为简单起见,服务器 Server-PT 同时作为 DHCP、DNS 以及 Web 服务器,各客户机无需配置,将自动获取网络配置。

    点击 CPT 拓扑图中的 Server 图标,设置其静态 IP 地址为 19.89.6.4/24,然后选择 Service 进行如下相关配置:

    在这里插入图片描述
    配置如下:
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    先查看各 PC,看看是否获得网络配置
    可以看到,PC0和PC1电脑都由DHCP自动分配了IP地址、掩码、默认网关和DNS服务器等网络配置。
    在这里插入图片描述
    在这里插入图片描述

    十四、WLAN初步配置

    WLAN 即 WiFi 当前也是广泛的应用在各种场景。

    我们通过构建如下拓扑的一个家庭 WLAN 来练习一下其相关的配置:

    在这里插入图片描述
    在这里插入图片描述
    一般地,我们需要配置无线路由器的基本网络配置(IP、掩码、网关、DNS 等,现实中多为自动获取),然后再配置无线路由器的无线访问部分如连接密码及加密类型等,并开启 DHCP 功能等。有关配置请参考相关资料。

    笔记本及台式机默认只有有线网卡,请先关机,在关机状态下删除有线网卡,添加无线网卡,然后再开机。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    配置好后的PC0和Laptop-PT无线联网的计算机都能自动获取网络配置并且PC0能够ping通Laptop-PT
    在这里插入图片描述
    在这里插入图片描述


    总结

    通过在CPT网络仿真工具进行计算机网络的仿真实验,从最简单的直接通信到路由通信以及后面的协议等的学习,逐渐了解计算机网络以及现代计算机通信网是如何使用ARP、OSPF、PAT、DHCP等一系列的协议,让我对于计算机网络的学习更加深入,同时也觉得计算机网络的设计确实是很有意思的,加上老师平时的讲解是十分到位的,说实话,这位老师是我读大学以来感受到为数不多可以让我上课不走神反倒被内容吸引的老师。

    展开全文
  • Cisco provides a different level of certification called CCNA, CCNP, CCIE for its products, and general network topics.... 翻译自: https://www.poftut.com/download-install-packet-tracer-windows/
  • Cisco Packet Tracer实验

    2020-12-19 10:59:55
    本文通过Cisco Packet Tracer实验,熟悉网络的一些协议与算法。通过构建网络拓扑结构,以及对网络的配置,得到了一些个人的思考与理解 文章目录摘要::sun_with_face: CPT软件简单使用:sun_with_face: 用交换机构建...
  • access-list 110 deny ip 172.16.0.0 0.0.255.255 192.168.2.0 0.0.0.255 access-list 110 permit ip any any ip nat pool ck 192.1.2.3 192.1.2.5 netmask 255.255.255.0 no ip nat inside source list 110 pool ...
  • Packet Tracer介绍 1.Packet Tracer 是由Cisco公司发布的一个辅助学习工具,为学习思科网络课程的初学者去设计、配置、排除网络故障提供了网络模拟环境。 2.用户可以在软件的图形用户界面上直接使用拖曳方法建立网络...
  • Cisco Packet Tracer初体验

    2020-12-25 16:28:21
    Cisco Packet Tracer初体验了解协议的概念一、直接连接两台 PC 构建 LAN二、用交换机构建 LAN三、交换机接口地址列表四、生成树协议(Spanning Tree Protocol)五、路由器配置初步六、静态路由 了解协议的概念 使用...
  • packet tracer 的switch交换机的常用命令

    千次阅读 2017-06-04 19:58:20
    show vlan用于查看当前各个端口所属的VLANs1#show vlanVLAN Name Status Ports ---- -------------------------------- --------- ------------------------------- 1 default active F
  • cisco packet tracer 实验

    2020-12-25 23:20:21
    Cisco Packet Tracer软件使用 参考视频: 简单操作 实验教程 使用两台PC构建LAN 将两台 PC 直接连接构成一个网络。注意:直接连接需使用交叉线。 进行两台 PC 的基本网络配置,只需要配置 IP 地址即可,然后相互 ...
  • Cisco Packet Tracer

    2020-12-23 21:24:35
    本部分实验共有 15 个,需使用 Cisco Packet Tracer 软件完成。 请大家先了解 VLSM、CIDR、RIP、OSPF、VLAN、STP、NAT 及 DHCP 等概念,以能够进行网络规划和配置。 ???? 参考 Cisco Packet Tracer 教学视频 Cisco ...
  • 步骤如图 ①首先在Cisco文件夹内找到languages文件夹 ②将下载的中文包添加进该文件夹 ③点开软件点击栏内Options的Preferences ④选择添加的中文语音包
  • 计算机网络实验Cisco Packet Tracer使用

    千次阅读 2020-11-30 16:10:08
    Cisco Packet Tracer使用前言1)Cisco Packet Tracer简介2)下载安装一.Cisco Packet Tracer 实验1.直接连接两台 PC 构建 LAN 前言 我们先了解 VLSM、CIDR、RIP、OSPF、VLAN、STP、NAT 及 DHCP 等概念,以能够进行...
  • 思科模拟器packet tracer的坑

    千次阅读 2020-12-13 13:13:32
    原因分析: 于是开始查阅资料,查询英文版(因为中文版的很多地方翻译的不靠谱)的官网,再看过大概三本相关资料,浏览了数不清的官方资料后,依然无法解释这个现象,配置不同的三层为根,或者配置HRSP设置其中一个...
  • 本文目的是通过在Cisco Packet Tracer(CPT)软件平台上进行网络的规划和配置,熟悉计算机网络的搭建过程并对计算机网络有更加深入的了解。 目录(一)Cisco Packet Tracer(CPT)简介 (一)Cisco Packet Tracer...
  • Cisco Packet Tracer路由器的基本配置

    千次阅读 2019-12-30 10:02:11
    2-1,以上命令翻译 进入特权模式 更改路由器密码 进入 fastethernet 0/0接口配置模式 配置IP地址为:192.168.1.100 开启 fastethernet 0/0接口 返回/退出 —————— 进入 fastethernet 0/1接口配置模式 ...
  • Cisco Packet Tracer是cisco公司推出的一款专业路由器模拟器,主要方便学习者进行思科路由器的模拟和学习,主要适用于Cisco网络工程师、管理员和Cisco证书考生等使用,拥有学习IOS的配置、锻炼故障排查能力、建立网络...
  • 一、NAT(Network Address Translations)网络地址转换 ipv4地址严重不够用了 x.x.x.x x0-255 A B C类可以使用 D组播 E科研 IP地址分为公网IP和私网IP 公网IP只能在公网上使用 私网IP只能在内网中使用 ...
  • Span 直译过来是时间跨度的意思,OpenTracing 用其来表达一次服务调用是时间空间上的跨度(目前还没有中文翻译);一次服务调用创建一个新 Span,分为调用 Span 和被调 Span,调用 Span 和被调 Span 合并之后就是一...
  • CCNA Test your Understanding of Packet Tracer

    千次阅读 2018-12-08 17:50:50
    Question 1 Which of following is the default icon for “.pka” file? Question 2 Which of the following statements is NOT true? ... Packet Tracer can replace 100% of real ... With Packet Tracer,...
  • 文章目录Cisco Packet Tracer 实验一、直接连接两台 PC 构建 LAN二、 Cisco Packet Tracer 实验 本部分实验共有 15 个,需使用 Cisco Packet Tracer 软件完成。 一、直接连接两台 PC 构建 LAN 将两台 PC 直接连接...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,047
精华内容 418
关键字:

tracer翻译