如何使用 perf 分析 splice 中 pipe 的容量变化

这个文章为了填上一篇文章的坑的,跟踪内核函数本来是准备使用 ebpf 的,但是涉及到了低内核版本,只能使用 kprobe 了。

恰好,在搜索东西的时候又看到了 perf,可以使用 perf probe 来完成对内核函数的跟踪,使用相对写内核模块简单很多,对于排查问题如何能解决就应该尽量挑简单的方案,所以就它了。

提到 perf 那么 Brendan Gregg 是绕不过去的,这里对 perf 只记一些本文使用到的一些东西。

perf 的一些东西

需要先添加探测点,探测点可以通过 /proc/kallsyms 进行查询,以 splice_to_pipe 为例

perf probe --add 'splice_to_pipe'

# 如何系统内有 kernel-debuginfo 那么就可以直接检测变量的值
perf probe --add 'splice_to_pipe pipe->nrbufs pipe->buffers spd->nr_pages'

在添加探测点后,进行记录。可以指定对应的 pid 和记录的时间 30s(等待的过程可以中断,并且不影响结果)

perf record -e 'probe:splice_to_pipe' -p $(pidof a.out) -gR sleep 30

# 也可以记录多个事件
perf record -e 'probe:tcp_splice_data_recv,probe:kill_fasync,probe:pipe_wait,probe:sock_spd_release,probe:splice_to_pipe' -p $(pidof a.out) -gR sleep 30

在完成记录后,将结果展示在命令行中

perf report --stdio

其它的可能用到的

# 查询已经添加过的探测点
perf probe --list
probe:splice_to_pipe (on splice_to_pipe@fs/splice.c with nrbufs buffers nr_pages)
probe:tcp_splice_data_recv (on tcp_splice_data_recv@net/ipv4/tcp.c with count len)
probe:tcp_splice_data_recv__return (on tcp_splice_data_recv%return@net/ipv4/tcp.c with arg1) # 删除已添加的探测点,从 perf probe --list 中获取
perf probe --del probe:splice_to_pipe # 查看准确的探测点(颜色区分)
perf probe -L splice_to_pipe

探测点要捕获变量,需要安装 kernel-debuginfo,Centos7.9 可以直接从阿里云下载,速度非常快(有的镜像源没有debuginfo,官方的速度太慢)

问题背景

在数据在 24k 字节左右时,低版本内核 3.10.0 调用 splice 会被阻塞,但是在高版本内核 6.1 可以直接返回。

这个问题只需要对 3.10.0版本内核的 splice_to_pipe 做分析(6.1 不会被阻塞),确认 24k 字节数据下 skbuff 的 PAGE 数量

以及引出来的一个问题,调用 splice 只做 fd -> pipe 而不做 pipe -> fd,这个情况都会发生阻塞,但是阻塞触发的大小不相同

  • 3.10.0 大概在 24k 字节就发生阻塞
  • 6.1.0 大概 200k 字节才发生阻塞,远大于 65536

这个问题聚焦点在

  • 3.10.0 下和上面那个问题相同,判断 PAGE 数量,是否大于了 pipe size
  • 6.1.0 需要判断阻塞之前的两个点
    • splice 入口的 wait_for_space 是否满足
    • splice_to_pipe 判断 PAGE 数量,观察挂载了几个页的数据

分析

问题在 3.10.0 的内核上体现明显,先对 3.10.0 进行分析。

本机环境

  • 宿主机 Debian12 (6.1.0-10-amd64), CPU i7-12700
  • 虚拟机 CentOS7.9 (3.10.0-1160.62.1.el7.x86_64)
  • QEMU 7.2.4 virt-io

分析 splice 3.10.0内核上阻塞的情况

先对 3.10.0内核入手,大概分析一下 splice_to_pipe 的源码

// fs/splice.c splice_to_pipe
186 ssize_t splice_to_pipe(struct pipe_inode_info *pipe,
187 struct splice_pipe_desc *spd)
188 {
198 for (;;) {
206 if (pipe->nrbufs < pipe->buffers) {
218 pipe->nrbufs++;
219 page_nr++;
220 ret += buf->len;
221
222 if (pipe->files)
223 do_wakeup = 1;
224
225 if (!--spd->nr_pages)
226 break;
227 if (pipe->nrbufs < pipe->buffers)
228 continue;
229
230 break;
231 }
232
233 if (spd->flags & SPLICE_F_NONBLOCK) {
234 if (!ret)
235 ret = -EAGAIN;
236 break;
237 }
244
245 if (do_wakeup) {
246 smp_mb();
247 if (waitqueue_active(&pipe->wait))
248 wake_up_interruptible_sync(&pipe->wait);
249 kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN);
250 do_wakeup = 0;
251 }
252
253 pipe->waiting_writers++;
254 pipe_wait(pipe);
255 pipe->waiting_writers--;
256 }
257
260 if (do_wakeup)
261 wakeup_pipe_readers(pipe);
262
263 while (page_nr < spd_pages)
264 spd->spd_release(spd, page_nr++);
265
266 return ret;
267 }

之前是怀疑 if (pipe->nrbufs < pipe->buffers) 不满足而又不满足 if (spd->flags & SPLICE_F_NONBLOCK),在 pipe_wait(pipe) 中被阻塞。

所以要看的就是

  • pipe->nrbufs, pipe 中已使用的 buffer 数量
  • pipe->buffers, pipe 中总的 buffer 数量
  • spd->nr_pages, socket 中读取出来数据页的数量

perf 追踪单次 splice 24k 字节数据的调用情况

调整测试数据的大小,生成 24k 字节的数据

$ dd if=/dev/zero of=/tmp/1.txt bs=1k count=24
$ ncat -nv 192.168.32.245 10022 < /tmp/1.txt

开始 perf 记录

[root@localhost ~]# perf probe --add 'splice_to_pipe pipe->nrbufs pipe->buffers spd->nr_pages'
Added new event:
probe:splice_to_pipe (on splice_to_pipe with nrbufs=pipe->nrbufs buffers=pipe->buffers nr_pages=spd->nr_pages) [root@localhost ~]# perf record -e 'probe:splice_to_pipe' -p $(pidof a.out) -gR sleep 30
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.017 MB perf.data (1 samples) ] [root@localhost ~]# perf report --stdio
# Samples: 2 of event 'probe:splice_to_pipe'
# Event count (approx.): 2
# Children Self Trace output
# ........ ........ ......................................................
50.00% 50.00% (ffffffffa9a811e0) nrbufs=0x0 buffers=0x10 nr_pages=17
...
50.00% 50.00% (ffffffffa9a811e0) nrbufs=0x10 buffers=0x10 nr_pages=2

通过 perf 观察到 splice_to_pipe 调用了两次,从 nrbufs 看第一次调用后 pipe 就没有空间了,再看一次代码,第一次调用在在 L230 返回,没有执行后续的逻辑。

// fs/splice.c splice_to_pipe
227 if (pipe->nrbufs < pipe->buffers)
228 continue;
229
230 break;

并且在 L263 while (page_nr < spd_pages) 这个条件是满足的,我们完整的追踪一下这个调用的链路,主要跟踪可能出现循环的逻辑,包括 tcp_read_sock, tcp_splice_data_recv, sock_spd_release 以及阻塞的逻辑 pull_wait

---splice
system_call_fastpath
sys_splice
do_splice_to
sock_splice_read
tcp_splice_read
tcp_read_sock
tcp_splice_data_recv
skb_splice_bits
skb_socket_splice
splice_to_pipe
kill_fasync

通过增加观测点来进行验证,

perf probe --add 'tcp_read_sock desc->count'
perf probe --add 'tcp_read_sock%return $retval' perf probe --add 'tcp_splice_data_recv rd_desc->count len offset'
perf probe --add 'tcp_splice_data_recv%return $retval' perf probe --add 'splice_to_pipe pipe->nrbufs pipe->buffers spd->nr_pages pipe->files pipe->waiting_writers pipe->readers'
perf probe --add 'splice_to_pipe%return $retval' perf probe --add 'pipe_wait pipe->nrbufs pipe->buffers pipe->files pipe->waiting_writers pipe->readers'
perf probe --add 'sock_spd_release spd->nr_pages i' perf record -e "$(perf probe --list | awk '{print $1}' | sed ':a;N;$!ba;s/\n/,/g')" -p $(pidof a.out) -gR sleep 30

输出结果为:

# Samples: 1  of event 'probe:pipe_wait'
# Children Self Trace output
# ........ ........ .....................................................................................
100.00% 100.00% (ffffffffa9a57760) nrbufs=0x10 buffers=0x10 files=0x2 waiting_writers=0x1 readers=0x1 # Samples: 1 of event 'probe:sock_spd_release'
# Children Self Trace output
# ........ ........ ....................................
100.00% 100.00% (ffffffffa9e418a0) nr_pages=1 i=0x10 # Samples: 2 of event 'probe:splice_to_pipe'
# Children Self Trace output
# ........ ........ ................................................................................................
50.00% 50.00% (ffffffffa9a811e0) nrbufs=0x0 buffers=0x10 nr_pages=17 files=0x2 waiting_writers=0x0 readers=0x1
50.00% 50.00% (ffffffffa9a811e0) nrbufs=0x10 buffers=0x10 nr_pages=2 files=0x2 waiting_writers=0x0 readers=0x1 # Samples: 1 of event 'probe:tcp_read_sock'
# Children Self Trace output
# ........ ........ .................................
100.00% 100.00% (ffffffffa9eb2e50) count=0x100000 # Samples: 2 of event 'probe:tcp_splice_data_recv'
# Children Self Trace output
# ........ ........ ........................................................
50.00% 50.00% (ffffffffa9eb2a10) count=0x100000 len=0x6000 offset=0x0
50.00% 50.00% (ffffffffa9eb2a10) count=0xfa770 len=0x770 offset=0x5890 # Samples: 1 of event 'probe:splice_to_pipe__return'
# Children Self Trace output
# ........ ........ ..................................................
100.00% 100.00% (ffffffffa9a811e0 <- ffffffffa9e481b7) arg1=0x5890 # Samples: 0 of event 'probe:tcp_read_sock__return'
# Children Self Trace output
# ........ ........ ............ # Samples: 1 of event 'probe:tcp_splice_data_recv__return'
# Children Self Trace output
# ........ ........ ..................................................
100.00% 100.00% (ffffffffa9eb2a10 <- ffffffffa9eb2efb) arg1=0x5890

通过测试结果分析代码

splice_to_pipe

splice_to_pipe 被调用两次,返回(splice_to_pipe__return)一次,poll_wait 调用一次,sock_spd_release 调用一次

  • 第一次调用的时候在 fs/splice.c L230 break 返回,没有进入 poll_wait 逻辑,但是由于数据没有全部写入 pipe 中,fs/splice.c L263 while (page_nr < spd_pages) 被调用,观察 nr_pages=1 i=0x10 到写入了 16 页,剩余 1 页。观察 tcp_splice_data_recv__return 写入 pipe 的数据为 0x5890.

  • 然后出现了第二次调用,由于没有空间(nrbufs=0x10 buffers=0x10)再进行写入 fs/splice.c 206 if (pipe->nrbufs < pipe->buffers) 条件不满足,直接进入了阻塞逻辑 pull_wait.

  • 第二次调用是第一次剩余的页数,重试导致阻塞,观察代码发现只要写入数据至 pipe 中,就会跳出循环不进入阻塞中

    225       if (!--spd->nr_pages)
    226 break;
    227 if (pipe->nrbufs < pipe->buffers)
    228 continue;
    230 break;
tcp_splice_data_recv

tcp_splice_data_recv 出现在 tcp_read_sock 的循环中,我们对其调用参数进行分析。

// net/ipv4/tcp.c tcp_splice_data_recv
634 static int tcp_splice_data_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
635 unsigned int offset, size_t len)
636 {
637 struct tcp_splice_state *tss = rd_desc->arg.data;
638 int ret;
639
640 ret = skb_splice_bits(skb, offset, tss->pipe, min(rd_desc->count, len),
641 tss->flags);
642 if (ret > 0)
643 rd_desc->count -= ret;
644 return ret;
645 } // net/ipv4/tcp.c tcp_read_sock
1458 int tcp_read_sock(struct sock *sk, read_descriptor_t *desc,
1459 sk_read_actor_t recv_actor)
1460 {
1469 while ((skb = tcp_recv_skb(sk, seq, &offset)) != NULL) {
1470 if (offset < skb->len) {
1471 int used;
1472 size_t len;
1473
1474 len = skb->len - offset;
1475 /* Stop reading if we hit a patch of urgent data */
1476 if (tp->urg_data) {
1477 u32 urg_offset = tp->urg_seq - seq;
1478 if (urg_offset < len)
1479 len = urg_offset;
1480 if (!len)
1481 break;
1482 }
1483 used = recv_actor(desc, skb, offset, len);
1484 if (used <= 0) {
1485 if (!copied)
1486 copied = used;
1487 break;
1488 } else if (used <= len) {
1489 seq += used;
1490 copied += used;
1491 offset += used;
1492 } // 50.00% 50.00% (ffffffffa9eb2a10) count=0x100000 len=0x6000 offset=0x0
// 50.00% 50.00% (ffffffffa9eb2a10) count=0xfa770 len=0x770 offset=0x5890

第一次调用为 count 为 0x100000,是 splice 的 max 参数,从套接字读出来的字节为 0x6000,一次性从套接字把数据读完了,写入 pipe 的长度为 0x5890,剩余 0x770

看起来第二次调用 splice 的情况下,0x770 的数据占用了两个 PAGE(nr_pages=2

看起来是 tcp_recv_skb 从套接字读取的数据没有把每个 PAGE 占满,24576 字节的数据占用 PAGE 数量为 18,直接写入 pipe 就发生了阻塞。

perf 追踪多次 splice 4k 字节数据的调用情况

这种情况的阻塞是正常的,是为了观测 splice 持续可以写多少数据至 pipe 中

测试数据量保持不变,修改 splice 最大的长度为 4096,并且不再从 pipe 消费数据。得到的结果如下

ssize_t n = splice(fd, NULL, pipefd, NULL, 1<<20, 0);
调整为 ->
ssize_t n = splice(fd, NULL, pipefd, NULL, 1<<12, 0); ssize_t n = splice_pump(pipefd[0], dstfd, in_pipe);
if (n > 0) {
remain -= n;
written += n;
}
调整为 ->
// ssize_t n = splice_pump(pipefd[0], dstfd, in_pipe);
// if (n > 0) {
// remain -= n;
// written += n;
// }

使用 perf 跟踪得到的结果如下:

[root@localhost ~]# perf report --stdio
# Samples: 1 of event 'probe:pipe_wait'
# Children Self Trace output
# ........ ........ .....................................................................................
100.00% 100.00% (ffffffffa9a57760) nrbufs=0x10 buffers=0x10 files=0x2 waiting_writers=0x1 readers=0x1 # Samples: 2 of event 'probe:sock_spd_release'
# Children Self Trace output
# ........ ........ ...................................
50.00% 50.00% (ffffffffa9e418a0) nr_pages=2 i=0x2
50.00% 50.00% (ffffffffa9e418a0) nr_pages=2 i=0x3 # Samples: 6 of event 'probe:splice_to_pipe'
# Children Self Trace output
# ........ ........ ................................................................................................
16.67% 16.67% (ffffffffa9a811e0) nrbufs=0x0 buffers=0x10 nr_pages=3 files=0x2 waiting_writers=0x0 readers=0x1
16.67% 16.67% (ffffffffa9a811e0) nrbufs=0x10 buffers=0x10 nr_pages=2 files=0x2 waiting_writers=0x0 readers=0x1
16.67% 16.67% (ffffffffa9a811e0) nrbufs=0x3 buffers=0x10 nr_pages=4 files=0x2 waiting_writers=0x0 readers=0x1
16.67% 16.67% (ffffffffa9a811e0) nrbufs=0x7 buffers=0x10 nr_pages=3 files=0x2 waiting_writers=0x0 readers=0x1
16.67% 16.67% (ffffffffa9a811e0) nrbufs=0xa buffers=0x10 nr_pages=4 files=0x2 waiting_writers=0x0 readers=0x1
16.67% 16.67% (ffffffffa9a811e0) nrbufs=0xe buffers=0x10 nr_pages=4 files=0x2 waiting_writers=0x0 readers=0x1 # Samples: 5 of event 'probe:tcp_read_sock'
# Children Self Trace output
# ........ ........ ...............................
100.00% 100.00% (ffffffffa9eb2e50) count=0x1000 # Samples: 10 of event 'probe:tcp_splice_data_recv'
# Children Self Trace output
# ........ ........ ........................................................
10.00% 10.00% (ffffffffa9eb2a10) count=0x0 len=0x2000 offset=0x4000
10.00% 10.00% (ffffffffa9eb2a10) count=0x0 len=0x3000 offset=0x3000
10.00% 10.00% (ffffffffa9eb2a10) count=0x0 len=0x4000 offset=0x2000
10.00% 10.00% (ffffffffa9eb2a10) count=0x0 len=0x5000 offset=0x1000
10.00% 10.00% (ffffffffa9eb2a10) count=0x1000 len=0x2000 offset=0x4000
10.00% 10.00% (ffffffffa9eb2a10) count=0x1000 len=0x3000 offset=0x3000
10.00% 10.00% (ffffffffa9eb2a10) count=0x1000 len=0x4000 offset=0x2000
10.00% 10.00% (ffffffffa9eb2a10) count=0x1000 len=0x5000 offset=0x1000
10.00% 10.00% (ffffffffa9eb2a10) count=0x1000 len=0x6000 offset=0x0
10.00% 10.00% (ffffffffa9eb2a10) count=0x868 len=0x1868 offset=0x4798 # Samples: 5 of event 'probe:splice_to_pipe__return'
# Children Self Trace output
# ........ ........ ..................................................
80.00% 80.00% (ffffffffa9a811e0 <- ffffffffa9e481b7) arg1=0x1000
20.00% 20.00% (ffffffffa9a811e0 <- ffffffffa9e481b7) arg1=0x798 # Samples: 4 of event 'probe:tcp_read_sock__return'
# Children Self Trace output
# ........ ........ ..................................................
100.00% 100.00% (ffffffffa9eb2e50 <- ffffffffa9eb3128) arg1=0x1000 # Samples: 9 of event 'probe:tcp_splice_data_recv__return'
# Children Self Trace output
# ........ ........ ..................................................
44.44% 44.44% (ffffffffa9eb2a10 <- ffffffffa9eb2efb) arg1=0x0
44.44% 44.44% (ffffffffa9eb2a10 <- ffffffffa9eb2efb) arg1=0x1000
11.11% 11.11% (ffffffffa9eb2a10 <- ffffffffa9eb2efb) arg1=0x798

总共 24k 的数据,splice 被调用了5次,4次返回,阻塞了1次。观察 pipe 的变化,同样是最后 nrbufs=0x10 buffers=0x10 nr_pages=2 pipe 已满导致被阻塞,PAGE 数量也是 18.

自顶向下分析的话,每次调用 splice 会调用一次 tcp_read_sock,然后调用两次 tcp_splice_data_recv(观察 probe:tcp_splice_data_recv__returnprobe:tcp_splice_data_recv 里面 count 的变化),最后一次在 L1487 之前就被阻塞了。

// net/ipv4/tcp.c tcp_read_sock
1484 if (used <= 0) {
1485 if (!copied)
1486 copied = used;
1487 break;

结论

3.10.0 在数据远小于 65536 的情况下被阻塞的原因就是 tcp_read_sock 用于读取数据的页没有写满 4096 字节,导致占用的页数大于 pipe 的容量(16)

TODO

由于 debian12 没有找到对应的 debuginfo(ubuntu 的 dbgsyms),这里再挖个坑,后面准备用 fedora39 再跟踪一波

参考

如何使用 perf 分析 splice 中 pipe 的容量变化的更多相关文章

  1. JavaScript中pipe实战

    JavaScript中pipe原理 代码示例 const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x); 原理 一行代码 ...

  2. 【Win 10 应用开发】分析 URI 中的查询字符串

    分析URI中的字符有K种方法(K >= 2),如果查询字符串中的参数比较简单,可以通过子字符串查找的方式来处理:如果查询字符串相对复杂,你可以使用正则表达式来匹配 key1=value1 ,  ...

  3. [转]DllMain中不当操作导致死锁问题的分析——DllMain中要谨慎写代码(完结篇)

    在CSDN中发现这篇文章,讲解的比较详细,所以在这里备份一个.原文链接:http://blog.csdn.net/breaksoftware/article/details/8167641 DllMa ...

  4. 一个使用C#的TPL Dataflow Library的例子:分析文本文件中词频

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:一个使用C#的TPL Dataflow Library的例子:分析文本文件中词频.

  5. 从虚拟机指令执行的角度分析JAVA中多态的实现原理

    从虚拟机指令执行的角度分析JAVA中多态的实现原理 前几天突然被一个"家伙"问了几个问题,其中一个是:JAVA中的多态的实现原理是什么? 我一想,这肯定不是从语法的角度来阐释多态吧 ...

  6. Linux的nmon监控结果分析文件中网络分析NET

    1.首先,使用# ifconfig查看Linux系统中的网卡名称,有的是eth0,有的是em1,以查看结果为准,下图为em1 2.先试试Linux系统中有没有安装ethtool工具,没有的话,下载et ...

  7. 【Java入门提高篇】Day23 Java容器类详解(六)HashMap源码分析(中)

    上一篇中对HashMap中的基本内容做了详细的介绍,解析了其中的get和put方法,想必大家对于HashMap也有了更好的认识,本篇将从了算法的角度,来分析HashMap中的那些函数. HashCod ...

  8. 数据库表设计时一对一关系存在的必要性 数据库一对一、一对多、多对多设计 面试逻辑题3.31 sql server 查询某个表被哪些存储过程调用 DataTable根据字段去重 .Net Core Cors中间件解析 分析MySQL中哪些情况下数据库索引会失效

    数据库表设计时一对一关系存在的必要性 2017年07月24日 10:01:07 阅读数:694 在表设计过程中,我无意中觉得一对一关系觉得好没道理,直接放到一张表中不就可以了吗?真是说,网上信息什么都 ...

  9. 初识quartz 并分析 项目中spring整合quartz的配置【原创+转载】

    初识quartz 并分析 项目中spring整合quartz的配置[原创+转载]2018年01月29日 12:08:07 守望dfdfdf 阅读数:114 标签: quartz 更多个人分类: 工具 ...

  10. TRIZ发明问题解决理论——本质是分析问题中的矛盾,利用资源(时间空间物质能量功能信息等)来解决矛盾从而解决问题——抽象出来:问题是什么,为什么?

    TRIZ意译为发明问题的解决理论.TRIZ理论成功地揭示了创造发明的 内在规律和原理,着力于澄清和强调系统中存在的矛盾,其目标是完全解决矛盾,获得最终的理想解.它不是采取折衷或者妥协的做法,而且它是基 ...

随机推荐

  1. centos8上安装中文字符集

    https://www.cnblogs.com/kaishirenshi/p/12669353.html yum install glibc-common yum install -y langpac ...

  2. 容器方式运行Mysql8.0.26的方法

    容器化运行Mysql8.0.26测试环境的方法 1. 前言 之前为了好处理,都是二进制包的方式安装mysql,但是有时候需要下载和安装也比较费时费力, 今天中午在弄Oracle RAC时想着以后能够容 ...

  3. 数据结构与算法 第二章线性表(48课时课程笔记)Data Structure and Algorithms

    2.1 线性表的类型定义 一个线性表是n个数据元素的有限序列. (1)结构初始化 InitList(&L) 构造一个空的线性表L. (2)销毁结构 DestroyList(&L) (3 ...

  4. 华为云DTSE携手“灵康宜”构造一站式智慧健康检测云平台

    本文分享自华为云社区<华为云DTSE携手"灵康宜"构造一站式智慧健康检测云平台>,作者: HuaweiCloudDeveloper. 打破传统健康监测方式桎梏--非接触 ...

  5. fasthttp 中如何使用 linux 系统调用 `sendfile`

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 接上一篇:fasthttp 中如何使用Transfer-E ...

  6. 【验证码逆向专栏】某验深知 V2 业务风控逆向分析

    声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容.敏感网址.数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未经许 ...

  7. word文档删除空白页

    记住两个快捷键 CTRL+Backspace Shift+Backspace 鼠标箭头放在空白的页面 按住键盘上的快捷键 就可以成功删除了不要天天看营销号设置什么磅值,全选删除啥的 效果如下

  8. 4.2 Inline Hook 挂钩技术

    InlineHook 是一种计算机安全编程技术,其原理是在计算机程序执行期间进行拦截.修改.增强现有函数功能.它使用钩子函数(也可以称为回调函数)来截获程序执行的各种事件,并在事件发生前或后进行自定义 ...

  9. Linux的守护进程 [补档-2023-08-10]

    12-1守护进程 12-1-1介绍 ​   Daemom是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或者事件.这些进 程一般不直接和用户交互,不受用户的登录,注销等影响.没 ...

  10. [XXL-JOB] 分布式调度XXL-JOB快速上手

    1.概述 1.1什么是任务调度 我们可以思考一下下面业务场景的解决方案: 某电商平台需要每天上午10点,下午3点,晚上8点发放一批优惠券 某银行系统需要在信用卡到期还款日的前三天进行短信提醒 某财务系 ...