高级操作系统实验2进程调度与切换分析(2)
先编写应用程序,使之包含三个进程,分别输出三个字母,不知道从何下手,先研究一下上课老师讲的例子,代码如下
1 #include <stdio.h>2 #include <stdlib.h>
3 #include <pthread.h>
4
5void loop(){
6while(1);
7}
8
9void *p1(){
10 printf("thread-1 starting
");
11 loop();
12}
13
14void *p2(){
15 printf("thread-2 starting
");
16 loop();
17}
18
19void main(){
20int pid1, pid2;
21 pthread_t t1,t2;
22void *thread_result;
23
24 printf("main starting
");
25
26if (!(pid1 = fork())){
27 printf("child-1 starting
");
28 loop();
29 exit(0);
30 }
31
32if (!(pid2 = fork())){
33 printf("child-2 starting
");
34 loop();
35 exit(0);
36 }
37
38 pthread_create(&t1, NULL, p1, NULL);
39 pthread_create(&t2, NULL, p2, NULL);
40
41 pthread_join(t1, &thread_result);
42 pthread_join(t2, &thread_result);
43
44int status;
45 waitpid(pid1, &status, 0);
46 waitpid(pid2, &status, 0);
47 printf("main exiting
");
48 exit(0);
49 }
View Code
运行结果如下
1 / # tmp/do-fork &2main starting
3 child-1 starting
4 child-2 starting
5 thread-1 starting
6 thread-2 starting
7 / #
各种问题来了:
C语言中的语法也不能完全搞懂,指针的地方模糊了,复习一下,感觉*和&就像是逆运算。
pthread_t是用来声明线程ID的。
!(pid1 = fork())是什么意思呢,根据上下文是创建子进程,返回0,才执行if语句,但fork()如果执行成功,是返回两个值,子进程返回0,父进程返回子进程的ID,另,赋值表达式怎么可以取反?由于不影响意思,跳过
fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。引用一位网友的话来解释fpid的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id,因为子进程没有子进程,所以其fpid为0.
pthread_create是类Unix操作系统(Unix、Linux、Mac OS X等)的创建线程的函数。它的功能是创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数。pthread_create的返回值 表示成功,返回0;表示出错,返回-1。第一个参数为指向线程标识符的指针。第二个参数用来设置线程属性。第三个参数是线程运行函数的起始地址。最后一个参数是运行函数的参数。
pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。参数 :thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。返回值 : 0代表成功。 失败,返回的则是错误号。代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
waitpid会暂时停止目前进程的执行,直到有信号来到或子进程结束。
如果执行loop,一直循环,怎么跳出来?
到此,还是不知道程序流是怎样的顺序,不会模仿,再难进展了
无奈,只好请教别人,在他的帮助下,写出了abc.c
下一步要在Ubuntu中编译测试上述程序,怎么编译测试呢?不知道,运行一下吧
【
7.启动gcc对代码进行编译。
gcc helloworld.c -o execFile(此步骤会生成一个execFlie的文件,可用ls查看)
8.如果有错误,系统会提示,按方向键找回gedit helloworld.c 的指令,按下回车,弹出文本框后根据系统提示对代码进行修改,完成后记得保存,
关闭文本框。
9.运行
./execFlie则会开始运行
】
用putty使用ssh连接虚拟机时也总是出问题,连不上,试验之后发现,似乎等一个终端提示连接失败后的信息后,再连就可以了
执行后发现程序没问题,我的疑惑在于像fork()这种明明没有定义的函数,他是怎么可以执行的,可以在windows环境下用vscode试一下老师的程序和abc.c
把扩展包重新加载一下,未解决(横生枝节,在家里遇到这种情况就会有畏难情绪)
在网上搜了一下,似乎有一些方法,但不想试了,现在实验要紧
输入gcc指令,如下
gcc后面一长串是干嘛的,查了一下,-o:指定生成的输出文件
其他的查不到了,只好作罢
耐心经常就是这样被磨没的,但也正是要克服这一点,忍受在各种不确定的未知的环境中去抓住重点
下面将abc放入qemu
将rootfs目录下的文件_install.tgz解压,此目录在哪?【Linux系统中的根文件系统,Root FileSystem,简称为rootfs】但我又找到了qume的footfs似乎,这次用的是这个
解压文件的后面的参数都是干嘛的?【
-x或--extract或--get:从备份文件中还原文件;
-v或--verbose:显示指令执行过程;
-z或--gzip或--ungzip:通过gzip指令处理备份文件;
-f<备份文件>或--file=<备份文件>:指定备份文件;
-C <目录>:这个选项用在解压缩,若要在特定目录解压缩,可以使用这个选项。
】不完全理解,不过有点进步
重新打包生成qume的根文件系统,生成的根文件系统放在了映像文件rootfs.img.gz
运行qume虚拟机,在虚拟机的命令解释器中运行和测试应用程序abc,由于循环执行无法退出,ctrlC不行,esc不行,回车不行,别的不知道怎么样做了
重新开一个终端,奇怪,为什么看不到刚刚运行abc的进程, 把终端关了,可能就是把里面运行的全关了吧,如下图
由于刚刚的教训,所有的脚本都没有保存,更改putty的设置
一试才知道,不用改,默认的复制粘贴最快捷了。可以改一下保存的行数,在window里面
abc没问题,继续下一步
启动调试,设置所有需要的内核断点
鉴于断点重新设置,所以打算把上次用的0.gbd略作修改,首先,复制一份文件,命名为新名字,查【
- i 和f选项相反,在覆盖目标文件之前将给出提示要求用户确认。回答y时目标文件将被覆盖,是交互式拷贝。
- r 若给出的源文件是一目录文件,此时cp将递归复制该目录下所有的子目录和文件。此时目标文件必须为一个目录名。
mv dir1 newdir
//dir1移动到当前目录下,并改名字为newdir
】
只留这启动的几行
1 1 target remote localhost:12342 2 dir ~/aos/lab/busybox
3 3 add-symbol-file ~/aos/lab/busybox/busybox_unstripped 0x8048400
启动
1 nudt@ubuntu:~/aos/lab/cur$ gdb vmlinux -x ~/aos/lab/1.gdb 2 GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.3) 7.7.13 Copyright (C) 2014 Free Software Foundation, Inc.
4 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
5This is free software: you are free to change and redistribute it.
6 There is NO WARRANTY, to the extent permitted by law. Type "show copying"
7 and "show warranty"for details.
8 This GDB was configured as "x86_64-linux-gnu".
9 Type "show configuration"for configuration details.
10For bug reporting instructions, please see:
11 <http://www.gnu.org/software/gdb/bugs/>.
12Find the GDB manual and other documentation resources online at:
13 <http://www.gnu.org/software/gdb/documentation/>.
14For help, type "help".
15 Type "apropos word" to search for commands related to "word"...
16Reading symbols from vmlinux...done.
17 /home/nudt/aos/lab/1.gdb: No such file or directory.
18 (gdb)
可执行文件的执行,只需要跟踪普通进程对此函数的调用,如何体现普通进程,查找上次做的实验【所有内核线程的task_struct结构的成员的mm值都是0,如果该值非0,则说明是普通进程】
调度的开始和结束:函数_schedule的进入和退出,退出如何体现呢?【b 函数所在文件名:函数最后一行的行号,最后一行指的是}所在行】怎么知道所在文件名和最后一行的行号,网上找不到,请教别人,【原来文件名在设置断点时可以看到,这样去对应目录就可以找行号了】。已经运行了一个./run s,这个终端应该不好看吧,因为设置的断点越来越多,还是要再开一个终端./run,去新开的里面看,运行两次是否有问题?请教大神,【到外层的虚拟机去看,不用到qume里面看,据说qume里面什么都看不见,这个有待实践】。kernel文件夹在哪?【linux内核默认存放到/boot下,而/usr/src中可查看内核的信息】但去boot下面查看,并没有【在引导文件夹(/boot)下,用户会看到诸如“vmlinux”或者“vmlinuz”的文件。这两者都是已编译的Linux内核。以“z”结尾的是已压缩的。“vm”代表虚拟内存。在SPARC处理器的系统上,用户可以看见一个zImage文件。一小部分用户可以发现一个bzImage文件,这也是一个已压缩的Linux内核。无论用户有哪个文件,这些引导文件都是不能更改的,除非用户知道他们正在做什么。否则系统会变成无法引导,也就是说系统启动不了了。】
【不是这里,而应该是qume对应的目录】这说明,没有理解,之前按教程进行时,都是自己做的操作
进了core.c,看到了不是代码,而是下面的,眼都瞎了,看起来像版本的更新日志【可以往下翻页】,觉得有点缺乏探索精神,明明试一下就知道,还得问别人
跳转到指定行号【:set nu加行号,:数字行号跳转,/xxx查找】
给函数结束加断点的前期工作终于做完了,正式加断点,文件目录可以有两种写法,一种是模仿他上面写的,另外一个是全的,我先写的全的,报错
换了一种表达,仍然一样的错
去查Make breakpoint pending on future shared library load? 的问题,网上的教程不敢试,因为不知道在做什么,怕乱了。突然想到,语句写错了
总算成功了,一上午从abc放到qume开始,只做到现在,休息一下
下午,继续加断点,进程切换的开始,看来脚本语言不支持注释
中断处理的开始,时钟中断和其他中断,smp_apic_timer_interrupt这个函数网上资料不多。
中断和异常的结束,从两个函数准备结束,真正结束是最后一个,但也可能是系统调用的结束。
软中断处理的开始和结束,函数的进入和退出,这下对退出怎么设置比较有经验了。
缺页异常的开始。
设备不存在异常的开始。
系统调用异常的开始和结束,在参数regs->orig_ax中记录了系统调用号,有时系统调用的结束位置是在restore_all,系统调用结束前,一般会执行函数prepare_exit_to_usermode。什么是系统调用?【系统调用把应用程序的请求传给内核,调用相应的内核函数完成所需的处理,将处理结果返回给应用程序。】do_fast_syscall_32函数没有在kernel里面,但在上层目录找不到对应的entry文件夹,网上找不到。他们说可以用source insight,同时也发现了kernel和arch是同一个目录,别说基础薄弱了,就连粗心也成了坑。
顺利找到结束位置
想查看系统调用号,直接输入display reg->ax不行,这样不知道行不行,先往下继续吧
增加和删除CFS队列中的节点
更新当前进程的vruntime,此函数结束时时钟已经更新,于是在结束处也加断点
准备由核心进入用户态
设置需要剥夺当前进程而重新调度标志
到现场为止,断点设置完毕
运行过程如下
5 Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-32-generic x86_64) 67 * Documentation: https://help.ubuntu.com/
8 New release "16.04.6 LTS" available.
9 Run "do-release-upgrade" to upgrade to it.
10
11 Last login: Thu Mar 19 18:03:31 2020 from 192.168.91.1
12 nudt@ubuntu:~$ cd /home/nudt/aos/lab/cur
13 nudt@ubuntu:~/aos/lab/cur$ ls
14 0.gdb fs modules.builtin tmp.bp
15arch include modules.order tools
16block init Module.symvers ubuntu
17certs ipc net usr
18COPYING Kbuild README virt
19 CREDITS Kconfig REPORTING-BUGS vmlinux
20 crypto kernel samples vmlinux-gdb.py
21debian.master lab scripts vmlinux.o
22Documentation lib security zfs
23drivers MAINTAINERS sound
24dropped.txt Makefile spl
25firmware mm System.map
26 nudt@ubuntu:~/aos/lab/cur$ vi 0.gdb
27 nudt@ubuntu:~/aos/lab/cur$ cp 0.gdb 1.gdb
28 nudt@ubuntu:~/aos/lab/cur$ ls
29 0.gdb firmware mm System.map
30 1.gdb fs modules.builtin tmp.bp
31arch include modules.order tools
32block init Module.symvers ubuntu
33certs ipc net usr
34COPYING Kbuild README virt
35 CREDITS Kconfig REPORTING-BUGS vmlinux
36 crypto kernel samples vmlinux-gdb.py
37debian.master lab scripts vmlinux.o
38Documentation lib security zfs
39drivers MAINTAINERS sound
40dropped.txt Makefile spl
41 nudt@ubuntu:~/aos/lab/cur$ vi 1.gdb
42 nudt@ubuntu:~/aos/lab/cur$ ls
43 0.gdb firmware mm System.map
44 1.gdb fs modules.builtin tmp.bp
45arch include modules.order tools
46block init Module.symvers ubuntu
47certs ipc net usr
48COPYING Kbuild README virt
49 CREDITS Kconfig REPORTING-BUGS vmlinux
50 crypto kernel samples vmlinux-gdb.py
51debian.master lab scripts vmlinux.o
52Documentation lib security zfs
53drivers MAINTAINERS sound
54dropped.txt Makefile spl
55 nudt@ubuntu:~/aos/lab/cur$ gdb vmlinux -x ~/aos/lab/1.gdb
56 GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.3) 7.7.1
57 Copyright (C) 2014 Free Software Foundation, Inc.
58 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
59This is free software: you are free to change and redistribute it.
60 There is NO WARRANTY, to the extent permitted by law. Type "show copying"
61 and "show warranty"for details.
62 This GDB was configured as "x86_64-linux-gnu".
63 Type "show configuration"for configuration details.
64For bug reporting instructions, please see:
65 <http://www.gnu.org/software/gdb/bugs/>.
66Find the GDB manual and other documentation resources online at:
67 <http://www.gnu.org/software/gdb/documentation/>.
68For help, type "help".
69 Type "apropos word" to search for commands related to "word"...
70Reading symbols from vmlinux...done.
71 /home/nudt/aos/lab/1.gdb: No such file or directory.
72 (gdb) b do_execve if$lx_current().mm==0
73 Breakpoint 1 at 0xc114771b: file fs/exec.c, line 1643.
74(gdb)
75 Note: breakpoint 1 also set at pc 0xc114771b.
76 Breakpoint 2 at 0xc114771b: file fs/exec.c, line 1643.
77 (gdb) break __schedule
78 Breakpoint 3 at 0xc16d1192: file kernel/sched/core.c, line 3104.
79 (gdb) b ~/aos/lab/4.4.6-ubuntu1604/ubuntu1604/kernel/sched/core.c 3188
80Function"~/aos/lab/4.4.6-ubuntu1604/ubuntu1604/kernel/sched/core.c 3188" not defined.
81 Make breakpoint pending on future shared library load? (y or [n]) n
82 (gdb) b kernel/sched/core.c 3188
83Function"kernel/sched/core.c 3188" not defined.
84 Make breakpoint pending on future shared library load? (y or [n]) n
85 (gdb) b <kernel/sched/core.c>:<3188>
86 No source file named <kernel/sched/core.c>.
87 Make breakpoint pending on future shared library load? (y or [n]) n
88 (gdb) b kernel/sched/core.c:3188
89 Breakpoint 4 at 0xc16d177b: file kernel/sched/core.c, line 3188.
90 (gdb) break __switch_to//调度时如果切换进程就会调用这个函数
91Function"__switch_to//调度时如果切换进程就会调用这个函数" not defined.
92 Make breakpoint pending on future shared library load? (y or [n]) n
93 (gdb) break __switch_to
94 Breakpoint 5 at 0xc101564a: file arch/x86/kernel/process_32.c, line 243.
95(gdb) b smp_apic_timer_interrupt
96 Breakpoint 6 at 0xc1034cfe: file arch/x86/kernel/apic/apic.c, line 913.
97(gdb) b do_IRQ
98 Breakpoint 7 at 0xc1017a95: file arch/x86/kernel/irq.c, line 215.
99(gdb) b ret_from_intr
100 Breakpoint 8 at 0xc16d4fe0: file arch/x86/entry/entry_32.S, line 254.
101(gdb) b ret_from_exception
102 Note: breakpoint 8 also set at pc 0xc16d4fe0.
103 Breakpoint 9 at 0xc16d4fe0: file arch/x86/entry/entry_32.S, line 254.
104(gdb) b restore_all
105 Breakpoint 10 at 0xc16d5096: file arch/x86/entry/entry_32.S, line 362.
106(gdb) b __do_softirq
107 Breakpoint 11 at 0xc104d9b5: file kernel/softirq.c, line 231.
108 (gdb) b kernel/sched/softirq.c:302
109 No source file named kernel/sched/softirq.c.
110 Make breakpoint pending on future shared library load? (y or [n]) n
111 (gdb) b kernel/softirq.c:302
112 Breakpoint 12 at 0xc104dbf4: file kernel/softirq.c, line 302.
113(gdb) b do_page_fault
114 Breakpoint 13 at 0xc103f260: file arch/x86/mm/fault.c, line 1295.
115(gdb) b do_device_not_available
116 Breakpoint 14 at 0xc1016a68: file arch/x86/kernel/traps.c, line 751.
117(gdb) b do_fast_syscall_32
118 Breakpoint 15 at 0xc100196c: file arch/x86/entry/common.c, line 408.
119 (gdb) b arch/x86/entry/common.c:486
120 Breakpoint 16 at 0xc1001aa9: file arch/x86/entry/common.c, line 486.
121 (gdb) display reg->ax
122 No symbol "reg"in current context.
123 (gdb) commands 15
124 Type commands for breakpoint(s) 15, one per line.
125End with a line saying just "end".
126 >display reg->ax
127 >end
128 (gdb) commands 16
129 Type commands for breakpoint(s) 16, one per line.
130End with a line saying just "end".
131 >display reg->ax
132 >end
133(gdb) b restore_all
134 Note: breakpoint 10 also set at pc 0xc16d5096.
135 Breakpoint 17 at 0xc16d5096: file arch/x86/entry/entry_32.S, line 362.
136(gdb) b prepare_exit_to_usermode
137 Breakpoint 18 at 0xc1001879: prepare_exit_to_usermode. (4 locations)
138(gdb) b enqueue_task_fair
139 Breakpoint 19 at 0xc1075feb: file kernel/sched/fair.c, line 4152.
140(gdb) b dequeue_task_fair
141 Breakpoint 20 at 0xc10748ba: file kernel/sched/fair.c, line 4200.
142(gdb) b update_curr
143 Breakpoint 21 at 0xc1070ad9: file kernel/sched/fair.c, line 702.
144 (gdb) b kernel/sched/fair.c:734
145 Breakpoint 22 at 0xc1070c81: file kernel/sched/fair.c, line 734.
146(gdb) b prepare_exit_to_usermode
147 Note: breakpoint 18 also set at pc 0xc1001879.
148 Note: breakpoint 18 also set at pc 0xc10018b7.
149 Note: breakpoint 18 also set at pc 0xc100194d.
150 Note: breakpoint 18 also set at pc 0xc1001a3c.
151 Breakpoint 23 at 0xc1001879: prepare_exit_to_usermode. (4 locations)
152(gdb) b set_tsk_need_resched
153 Breakpoint 24 at 0xc1069a97: file ./arch/x86/include/asm/bitops.h, line 75.
154 (gdb)
关闭除do_execve之外的所有其他断点【
gdb中的变量从1开始标号,不同的断点采用变量标号同一管理,可以 用enable、disable等命令管理,同时支持断点范围的操作,比如有些命令接受断点范围作为参数。
例如:disable 5-8
】
在应用程序abc的main函数入口处设置断点
创建软链接【建立软链接,只要在ln后面加上选项 –s】
查看abc的.test节的起始虚拟地址【objdump命令是用查看目标文件或者可执行的目标文件的构成的gcc工具。
--disassemble-d
从objfile中反汇编那些特定指令机器码的section。
-l--line-numbers
用文件名和行号标注相应的目标代码,仅仅和-d、-D或者-r一起使用使用-ld和使用-d的区别不是很大,在源码级调试的时候有用,要求编译时使用了-g之类的调试编译选项。
--all-headers-x
显示所可用的头信息,包括符号表、重定位入口。-x 等价于-a -f -h -r -t 同时指定。
】
结果如下
1 abc: file format elf32-i386 2abc 3 architecture: i386, flags 0x00000112: 4EXEC_P, HAS_SYMS, D_PAGED 5 start address 0x08048d0a6
7Program Header:
8 LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
9 filesz 0x000a0457 memsz 0x000a0457 flags r-x
10 LOAD off 0x000a0f40 vaddr 0x080e9f40 paddr 0x080e9f40 align 2**12
11 filesz 0x00001040 memsz 0x000023e4 flags rw-
12 NOTE off 0x000000f4 vaddr 0x080480f4 paddr 0x080480f4 align 2**2
13 filesz 0x00000044 memsz 0x00000044 flags r--
14 TLS off 0x000a0f40 vaddr 0x080e9f40 paddr 0x080e9f40 align 2**2
15 filesz 0x00000010 memsz 0x00000028 flags r--
16 STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4
17 filesz 0x00000000 memsz 0x00000000 flags rw-
18 RELRO off 0x000a0f40 vaddr 0x080e9f40 paddr 0x080e9f40 align 2**0
19 filesz 0x000000c0 memsz 0x000000c0 flags r--
20
21Sections:
22Idx Name Size VMA LMA File off Algn
23 0 .note.ABI-tag 00000020 080480f4 080480f4 000000f4 2**2
24 CONTENTS, ALLOC, LOAD, READONLY, DATA
25 1 .note.gnu.build-id 00000024 08048114 08048114 00000114 2**2
26 CONTENTS, ALLOC, LOAD, READONLY, DATA
27 2 .rel.plt 00000070 08048138 08048138 00000138 2**2
28 CONTENTS, ALLOC, LOAD, READONLY, DATA
29 3 .init 00000023 080481a8 080481a8 000001a8 2**2
30 CONTENTS, ALLOC, LOAD, READONLY, CODE
31 4 .plt 000000e0 080481d0 080481d0 000001d0 2**4
32 CONTENTS, ALLOC, LOAD, READONLY, CODE
33 5 .text 000753e4 080482b0 080482b0 000002b0 2**4
34 :
遇到了问题,如下,地址无效,请教大神【16进制,前面加0x,我费了半天劲,他一句就解决了】
设置好断点
跟踪到main函数入口处
出了点问题,只好重启
把脚本整理好,发现运行中出现了问题,如下,于是分别去找断点5和16,5没什么特殊的,16那里删除了两个显示系统调用号的语句
如上图,还多一个断点,逐个比对一下,原来之前多按回车,导致多了一个断点,再次更正。就是不知道怎么把command14和15的命令加进去
如下图,
在暂时关闭断点时为什么唯独留了do_execve,我现在运行,这个断点特别多,真麻烦【开机之后再设断点,刚刚多是因为开机过程中多】
将脚本整理一下
1 target remote localhost:12342 dir ~/aos/lab/busybox
3 add-symbol-file ~/aos/lab/busybox/busybox_unstripped 0x8048400
4 b do_execve if$lx_current().mm==0
5break __schedule
6 b kernel/sched/core.c:3188
7break __switch_to
8b smp_apic_timer_interrupt
9b do_IRQ
10b ret_from_intr
11b ret_from_exception
12b restore_all
13b __do_softirq
14 b kernel/softirq.c:302
15b do_page_fault
16b do_device_not_available
17b do_fast_syscall_32
18 b arch/x86/entry/common.c:486
19b restore_all
20b prepare_exit_to_usermode
21b enqueue_task_fair
22b dequeue_task_fair
23b update_curr
24 b kernel/sched/fair.c:734
25b prepare_exit_to_usermode
26b set_tsk_need_resched
27 disable 2-23
28dir abc
29 add-symbol-file abc/abc 0x080482b0
30 b abc.c:main
到此,断点设置完毕
下篇开始调试
以上是 高级操作系统实验2进程调度与切换分析(2) 的全部内容, 来源链接: utcz.com/z/514580.html