learn.lianglianglee.com/专栏/容器实战高手课/加餐福利 课后思考题答案合集.md.html
2022-08-14 03:40:33 +08:00

434 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>加餐福利 课后思考题答案合集.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/容器实战高手课/00 开篇词 一个态度两个步骤,成为容器实战高手.md.html">00 开篇词 一个态度两个步骤,成为容器实战高手</a>
</li>
<li>
<a href="/专栏/容器实战高手课/01 认识容器:容器的基本操作和实现原理.md.html">01 认识容器:容器的基本操作和实现原理</a>
</li>
<li>
<a href="/专栏/容器实战高手课/02 理解进程1为什么我在容器中不能kill 1号进程.md.html">02 理解进程1为什么我在容器中不能kill 1号进程</a>
</li>
<li>
<a href="/专栏/容器实战高手课/03 理解进程2为什么我的容器里有这么多僵尸进程.md.html">03 理解进程2为什么我的容器里有这么多僵尸进程</a>
</li>
<li>
<a href="/专栏/容器实战高手课/04 理解进程3为什么我在容器中的进程被强制杀死了.md.html">04 理解进程3为什么我在容器中的进程被强制杀死了</a>
</li>
<li>
<a href="/专栏/容器实战高手课/05 容器CPU1怎么限制容器的CPU使用.md.html">05 容器CPU1怎么限制容器的CPU使用</a>
</li>
<li>
<a href="/专栏/容器实战高手课/06 容器CPU2如何正确地拿到容器CPU的开销.md.html">06 容器CPU2如何正确地拿到容器CPU的开销</a>
</li>
<li>
<a href="/专栏/容器实战高手课/07 Load Average加了CPU Cgroup限制为什么我的容器还是很慢.md.html">07 Load Average加了CPU Cgroup限制为什么我的容器还是很慢</a>
</li>
<li>
<a href="/专栏/容器实战高手课/08 容器内存:我的容器为什么被杀了?.md.html">08 容器内存:我的容器为什么被杀了?</a>
</li>
<li>
<a href="/专栏/容器实战高手课/09 Page Cache为什么我的容器内存使用量总是在临界点.md.html">09 Page Cache为什么我的容器内存使用量总是在临界点</a>
</li>
<li>
<a href="/专栏/容器实战高手课/10 Swap容器可以使用Swap空间吗.md.html">10 Swap容器可以使用Swap空间吗</a>
</li>
<li>
<a href="/专栏/容器实战高手课/11 容器文件系统:我在容器中读写文件怎么变慢了.md.html">11 容器文件系统:我在容器中读写文件怎么变慢了</a>
</li>
<li>
<a href="/专栏/容器实战高手课/12 容器文件Quota容器为什么把宿主机的磁盘写满了.md.html">12 容器文件Quota容器为什么把宿主机的磁盘写满了</a>
</li>
<li>
<a href="/专栏/容器实战高手课/13 容器磁盘限速:我的容器里磁盘读写为什么不稳定.md.html">13 容器磁盘限速:我的容器里磁盘读写为什么不稳定</a>
</li>
<li>
<a href="/专栏/容器实战高手课/14 容器中的内存与IO容器写文件的延时为什么波动很大.md.html">14 容器中的内存与IO容器写文件的延时为什么波动很大</a>
</li>
<li>
<a href="/专栏/容器实战高手课/15 容器网络我修改了procsysnet下的参数为什么在容器中不起效.md.html">15 容器网络我修改了procsysnet下的参数为什么在容器中不起效</a>
</li>
<li>
<a href="/专栏/容器实战高手课/16 容器网络配置1容器网络不通了要怎么调试.md.html">16 容器网络配置1容器网络不通了要怎么调试</a>
</li>
<li>
<a href="/专栏/容器实战高手课/17 容器网络配置2容器网络延时要比宿主机上的高吗.md.html">17 容器网络配置2容器网络延时要比宿主机上的高吗</a>
</li>
<li>
<a href="/专栏/容器实战高手课/18 容器网络配置3容器中的网络乱序包怎么这么高.md.html">18 容器网络配置3容器中的网络乱序包怎么这么高</a>
</li>
<li>
<a href="/专栏/容器实战高手课/19 容器安全1我的容器真的需要privileged权限吗.md.html">19 容器安全1我的容器真的需要privileged权限吗</a>
</li>
<li>
<a href="/专栏/容器实战高手课/20 容器安全2在容器中我不以root用户来运行程序可以吗.md.html">20 容器安全2在容器中我不以root用户来运行程序可以吗</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐01 案例分析怎么解决海量IPVS规则带来的网络延时抖动问题.md.html">加餐01 案例分析怎么解决海量IPVS规则带来的网络延时抖动问题</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐02 理解perf怎么用perf聚焦热点函数.md.html">加餐02 理解perf怎么用perf聚焦热点函数</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐03 理解ftrace1怎么应用ftrace查看长延时内核函数.md.html">加餐03 理解ftrace1怎么应用ftrace查看长延时内核函数</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐04 理解ftrace2怎么理解ftrace背后的技术tracepoint和kprobe.md.html">加餐04 理解ftrace2怎么理解ftrace背后的技术tracepoint和kprobe</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐05 eBPF怎么更加深入地查看内核中的函数.md.html">加餐05 eBPF怎么更加深入地查看内核中的函数</a>
</li>
<li>
<a href="/专栏/容器实战高手课/加餐06 BCC入门eBPF的前端工具.md.html">加餐06 BCC入门eBPF的前端工具</a>
</li>
<li>
<a class="current-tab" href="/专栏/容器实战高手课/加餐福利 课后思考题答案合集.md.html">加餐福利 课后思考题答案合集</a>
</li>
<li>
<a href="/专栏/容器实战高手课/结束语 跳出舒适区,突破思考的惰性.md.html">结束语 跳出舒适区,突破思考的惰性</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>加餐福利 课后思考题答案合集</h1>
<p>你好,我是程远,好久不见。</p>
<p>距离我们的专栏更新结束,已经过去了不少时间。我仍然会在工作之余,到这门课的留言区转一转,回答同学的问题。大部分的疑问,我都通过留言做了回复。</p>
<p>除了紧跟更新的第一批同学,也很开心有更多新朋友加入到这个专栏的学习中。那课程的思考题呢,为了给你留足思考和研究的时间,我选择用加餐的方式,给你提供参考答案。</p>
<p>这里我想和你说明的是,我这里给你提供的参考答案,都是我能够直接给你特定答案的问题。至于操作类的题目,有的我引用了同学回复的答案。</p>
<p>另外一类操作题,是为了帮你巩固课程内容知识的,相信你可以从课程正文里找到答案。我还是建议你自己动手实战,这样你的收获会更大。</p>
<h2>必学部分思考题</h2>
<p><strong>第 2 讲</strong></p>
<p>Q对于这一讲的最开始有这样一个 C 语言的 init 进程,它没有注册任何信号的 handler。如果我们从 Host Namespace 向它发送 SIGTERM会发生什么情况呢</p>
<p>A即使在宿主机上向容器 1 号进程发送 SIGTERM在 1 号进程没有注册 handler 的情况下,这个进程也不能被杀死。</p>
<p>这个问题的原因是这样的:开始要看内核里的那段代码,“ !(force &amp;&amp; sig_kernel_only(sig))”,</p>
<p>虽然由不同的 namespace 发送信号, 虽然 force 是 1 了,但是 sig_kernel_only(sig) 对于 SIGTERM 来说还是 0这里是个 &amp;&amp;, 那么 !(1 &amp;&amp; 0) = 1。</p>
<pre><code>#define sig_kernel_only(sig) siginmask(sig, SIG_KERNEL_ONLY_MASK)
#define SIG_KERNEL_ONLY_MASK (\
rt_sigmask(SIGKILL) | rt_sigmask(SIGSTOP))
</code></pre>
<p><strong>第 3 讲</strong></p>
<p>Q如果容器的 init 进程创建了子进程 BB 又创建了自己的子进程 C。如果 C 运行完之后退出成了僵尸进程B 进程还在运行,而容器的 init 进程还在不断地调用 waitpid(),那 C 这个僵尸进程可以被回收吗?</p>
<p>A这道题可以参考下面两位同学的回答。</p>
<p>Geek2014 用户的回答:</p>
<p>这时 C 是不会被回收的,只有等到 B 也被杀死C 这个僵尸进程也会变成孤儿进程,被 init 进程收养,进而被 init 的 wait 机制清理掉。</p>
<p>莫名同学的回答:</p>
<p>C 应该不会被回收waitpid 仅等待直接 children 的状态变化。</p>
<p>为什么先进入僵尸状态而不是直接消失?觉得是留给父进程一次机会,查看子进程的 PID、终止状态退出码、终止原因比如是信号终止还是正常退出等、资源使用信息。如果子进程直接消失那么父进程没有机会掌握子进程的具体终止情况。</p>
<p>一般情况下,程序逻辑可能会依据子进程的终止情况做出进一步处理:比如 Nginx Master 进程获知 Worker 进程异常退出,则重新拉起来一个 Worker 进程。</p>
<p><strong>第 4 讲</strong></p>
<p>Q请你回顾一下基本概念中最后的这段代码你可以想一想在不做编译运行的情况下它的输出是什么</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;signal.h&gt;
typedef void (*sighandler_t)(int);
void sig_handler(int signo)
{
if (signo == SIGTERM) {
printf(&quot;received SIGTERM\n\n&quot;);
// Set SIGTERM handler to default
signal(SIGTERM, SIG_DFL);
}
}
int main(int argc, char *argv[])
{
//Ignore SIGTERM, and send SIGTERM
// to process itself.
signal(SIGTERM, SIG_IGN);
printf(&quot;Ignore SIGTERM\n\n&quot;);
kill(0, SIGTERM);
//Catch SIGERM, and send SIGTERM
// to process itself.
signal(SIGTERM, sig_handler);
printf(&quot;Catch SIGTERM\n&quot;);
kill(0, SIGTERM);
//Default SIGTERM. In sig_handler, it sets
//SIGTERM handler back to default one.
printf(&quot;Default SIGTERM\n&quot;);
kill(0, SIGTERM);
return 0;
}
</code></pre>
<p>A可以参考用户 geek 2014 同学的答案。输出结果如下:</p>
<p>Ignore SIGTERM</p>
<p>Catch SIGTERM</p>
<p>received SIGTERM</p>
<p>Default SIGTERM</p>
<p><strong>第 5 讲</strong></p>
<p>Q我们还是按照文档中定义的控制组目录层次结构图然后按序执行这几个脚本</p>
<p>create_groups.sh</p>
<p>update_group1.sh</p>
<p>update_group4.sh</p>
<p>update_group3.sh</p>
<p>那么,在一个 4 个 CPU 的节点上group1/group3/group4 里的进程,分别会被分配到多少 CPU 呢?</p>
<p>A分配比例是: 2 : 0.5 : 1.5</p>
<p>可以参考 geek 2014 的答案:</p>
<p>group1 的 shares 为 1024quota 3.5,尝试使用 4</p>
<p>group2 的 shares 默认为 1024quota 设置为 -1不受限制也即是如果 CPU 上只有 group2 的话,那么 group2 可以使用完所有的 CPU实际上根据 group3 和 group4group2 最多也就能用到 1.5+3.5 core</p>
<p>故而group1 和 group2 各分配到 2。把 group2 分到的 2CPU看作总量再次分析 group3 和 group4。group3 和 group3 尝试使用的总量超过 2所以按照 shares 比例分配group3 使用 1/(1+3) * 2 = 0.5group4 使用 3/(1+3) * 2 = 1.5</p>
<p><strong>第 6 讲</strong></p>
<p>Q写一个小程序在容器中执行它可以显示当前容器中所有进程总的 CPU 使用率。</p>
<p>A上邪忘川的回答可以作为一个参考。</p>
<pre><code>#!/bin/bash
cpuinfo1=$(cat /sys/fs/cgroup/cpu,cpuacct/cpuacct.stat)
utime1=$(echo $cpuinfo1|awk '{print $2}')
stime1=$(echo $cpuinfo1|awk '{print $4}')
sleep 1
cpuinfo2=$(cat /sys/fs/cgroup/cpu,cpuacct/cpuacct.stat)
utime2=$(echo $cpuinfo2|awk '{print $2}')
stime2=$(echo $cpuinfo2|awk '{print $4}')
cpus=$((utime2+stime2-utime1-stime1))
echo &quot;${cpus}%&quot;
</code></pre>
<p><strong>第 8 讲</strong></p>
<p>Q在我们的例子脚本基础上你可以修改一下在容器刚一启动就在容器对应的 Memory Cgroup 中禁止 OOM看看接下来会发生什么</p>
<p>A通过“memory.oom_control”禁止 OOM 后,在容器中的进程不会发生 OOM但是也无法申请出超过“memory.limit_in_bytes”内存。</p>
<pre><code># cat start_container.sh
#!/bin/bash
docker stop mem_alloc;docker rm mem_alloc
docker run -d --name mem_alloc registry/mem_alloc:v1
sleep 2
CONTAINER_ID=$(sudo docker ps --format &quot;{{.ID}}\t{{.Names}}&quot; | grep -i mem_alloc | awk '{print $1}')
echo $CONTAINER_ID
CGROUP_CONTAINER_PATH=$(find /sys/fs/cgroup/memory/ -name &quot;*$CONTAINER_ID*&quot;)
echo $CGROUP_CONTAINER_PATH
echo 536870912 &gt; $CGROUP_CONTAINER_PATH/memory.limit_in_bytes
echo 1 &gt; $CGROUP_CONTAINER_PATH/memory.oom_control
cat $CGROUP_CONTAINER_PATH/memory.limit_in_bytes
</code></pre>
<p><strong>第 10 讲</strong></p>
<p>Q在一个有 Swap 分区的节点上用 Docker 启动一个容器,对它的 Memory Cgroup 控制组设置一个内存上限 N并且将 memory.swappiness 设置为 0。这时如果在容器中启动一个不断读写文件的程序同时这个程序再申请 1/2N 的内存请你判断一下Swap 分区中会有数据写入吗?</p>
<p>AMemory Cgroup 参数 memory.swappiness 起到局部控制的作用,因为已经设置了 memory.swappiness 参数,全局参数 swappiness 参数失效,那么容器里就不能使用 swap 了。</p>
<p><strong>第 11 讲</strong></p>
<p>Q在这一讲 OverlayFS 的例子的基础上,建立 2 个 lowerdir 的目录,并且在目录中建立相同文件名的文件,然后一起做一个 overlay mount看看会发生什么</p>
<p>A这里引用上邪忘川同学的实验结果。</p>
<p>实验过程如下,结果是 lower1 目录中的文件覆盖了 lower2 中同名的文件, 第一个挂载的目录优先级比较高</p>
<pre><code>[[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="9deff2f2e9ddf1f2fefcf1f5f2eee9">[email&#160;protected]</a> ~]# cat overlay.sh
#!/bin/bash
umount ./merged
rm upper lower1 lower2 merged work -r
mkdir upper lower1 lower2 merged work
echo &quot;I'm from lower1!&quot; &gt; lower1/in_lower.txt
echo &quot;I'm from lower2!&quot; &gt; lower2/in_lower.txt
echo &quot;I'm from upper!&quot; &gt; upper/in_upper.txt
# `in_both` is in both directories
echo &quot;I'm from lower1!&quot; &gt; lower1/in_both.txt
echo &quot;I'm from lower2!&quot; &gt; lower2/in_both.txt
echo &quot;I'm from upper!&quot; &gt; upper/in_both.txt
sudo mount -t overlay overlay \
-o lowerdir=./lower1:./lower2,upperdir=./upper,workdir=./work \
./merged
[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4a3825253e0a2625292b262225393e">[email&#160;protected]</a> ~]# sh overlay.sh
[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="3745585843775b5854565b5f584443">[email&#160;protected]</a> ~]# cat merged/in_lower.txt
I'm from lower1!
</code></pre>
<p>第 12 讲</p>
<p>Q在正文知识详解的部分我们使用&quot;xfs_quota&quot;给目录打了 project ID 并且限制了文件写入的数据量。那么在做完限制之后,我们是否能用 xfs_quota 命令,查询到被限制目录的 project ID 和限制的数据量呢?</p>
<p>Axfs_quota 不能直接得到一个目录的 quota 大小的限制,只可以看到 project ID 上的 quota 限制,不过我们可以用这段程序来获得目录对应的 project ID。</p>
<pre><code># xfs_quota -x -c 'report -h /'
...
Project ID Used Soft Hard Warn/Grace
---------- ---------------------------------
#0 105.6G 0 0 00 [------]
#101 0 0 10M 00 [------]
# ./get_proj /tmp/xfs_prjquota
Dir: /tmp/xfs_prjquota projectid is 101
</code></pre>
<p><strong>第 13 讲</strong></p>
<p>Q这是一道操作题通过这个操作你可以再理解一下 blkio Cgroup 与 Buffered I/O 的关系。</p>
<p>在 Cgroup V1 的环境里,我们在 blkio Cgroup V1 的例子基础上,把 fio 中“-direct=1”参数去除之后再运行 fio同时运行 iostat 查看实际写入磁盘的速率,确认 Cgroup V1 blkio 无法对 Buffered I/O 限速。</p>
<p>A: 这是通过 iostat 看到磁盘的写入速率,是可以突破 cgroup V1 blkio 中的限制值的。</p>
<p><strong>第 17 讲</strong></p>
<p>Q在这节课的最后我提到“由于 ipvlan/macvlan 网络接口直接挂载在物理网络接口上,对于需要使用 iptables 规则的容器,比如 Kubernetes 里使用 service 的容器,就不能工作了”,请你思考一下这个判断背后的具体原因。</p>
<p>Aipvlan/macvlan 工作在网络 2 层,而 iptables 工作在网络 3 层。所以用 ipvlan/macvlan 为容器提供网络接口,那么基于 iptables 的 service 服务就不工作了。</p>
<p><strong>第 18 讲</strong></p>
<p>Q在这一讲中我们提到了 Linux 内核中的 tcp_force_fast_retransmit() 函数,那么你可以想想看,这个函数中的 tp-&gt;recording 和内核参数 /proc/sys/net/ipv4/tcp_reordering 是什么关系?它们对数据包的重传会带来什么影响?</p>
<pre><code>static bool tcp_force_fast_retransmit(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
return after(tcp_highest_sack_seq(tp),
tp-&gt;snd_una + tp-&gt;reordering * tp-&gt;mss_cache);
}
</code></pre>
<p>A: 在 TCP 链接建立的时候tp-&gt;reordering 默认值是从 /proc/sys/net/ipv4/tcp_reordering默认值为 3获取的。之后根据网络的乱序情况进行动态调整最大可以增长到 /proc/sys/net/ipv4/tcp_max_reordering (默认值为 300) 的大小。</p>
<p><strong>第 20 讲</strong></p>
<p>Q我在这一讲里提到了 rootless container不过对于 rootless container 的支持还存在着不少的难点比如容器网络的配置、Cgroup 的配置,你可以去查阅一些资料,看看 podman 是怎么解决这些问题的。</p>
<p>A可以阅读一下这篇文档。</p>
<h2>专题加餐</h2>
<p><strong>专题 03</strong></p>
<p>Q我们讲 ftrace 实现机制时说过内核中的“inline 函数”不能被 ftrace 到你知道这是为什么吗那么内核中的“static 函数”可以被 ftrace 追踪到吗?</p>
<p>Ainline 函数在编译的时候被展开了,所以不能被 ftrace 到。而 static 函数需要看情况,如果加了编译优化参数“-finline-functions-called-once”对于只被调用到一次的 static 函数也会当成 inline 函数处理,那么也不能被 ftrace 追踪到了。</p>
<p><strong>专题 04</strong></p>
<p>Q想想看当我们用 kprobe 为一个内核函数注册了 probe 之后,怎样能看到对应内核函数的第一条指令被替换了呢?</p>
<p>A首先可以参考莫名同学的答案</p>
<p>关于思考题想到一个比较笨拙的方法gdb+qemu 调试内核。先进入虚拟机在某个内核函数上注册一个 kprobe然后 gdb 远程调试内核查看该内核函数的汇编指令disass是否被替换。应该有更简单的方法这方面了解不深。</p>
<p>另外,我们用 gdb 远程调试内核看也可以。还可以通过 /proc/kallsyms 找到函数的地址,然后写个 kernel module 把从这个地址开始后面的几个字节 dump 出来,比较一下 probe 函数注册前后的值。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/容器实战高手课/加餐06 BCC入门eBPF的前端工具.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/容器实战高手课/结束语 跳出舒适区,突破思考的惰性.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709977bdacff3cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>