linux大于号怎么退出去

2011-06-11 16:19:00 dog250 阅读数 12458
  • Linux根目录和常见目录

    区块链游戏开发的入门到深入了解 学到linux命令行;EOS环境安装;C++;HTML;CSS;JavaScript;React;Redux等知识。

    0课时 0分钟 13人学习 张云波
    免费试看

第一部分:Linux负载均衡的设计

一.负载均衡的原则

1.确保每个cpu核心的负载均衡;

2.在cpu和cache以及内存布局的影响下加权执行1。

对于一般多核心cpu情况,以上两个原则可以简述为下面的原则:

1.尽量不执行进程迁移,以确保cache的热度;
2.除非各个cpu的负载已经严重失衡,执行负载均衡

二.系统以及cpu的拓扑结构

这个道理看似简单,然而如果对于一个大型的综合系统,要想设计一个适用于各种情况的负载均衡体系,却不是很简单。Linux内核的负载均衡设计的相当完美。对于负载均衡,可以分为以下几种情况:
以系统复杂度为核心的分类

情况一:cpu无任何cache

这种情况下,需要随时保持各cpu上运行的进程数量的均衡

情况二:多处理器,每个处理器有处理器独享的cache

这种情况下,进行负载均衡会影响处理器的cache利用率,负载均衡带来的效益会被cache刷新的开销抵消掉一部分或大部分。

情况三:多处理器,每个处理器有多个处理器核心,每个核心有独享的一级cache,同一处理器的多个核心有共享的二级,三级cache

这种情况下,情况二是要考虑的,然而对于同一处理器的不同核心之间由于存在共享的二级cache,多个核心之间的负载均衡抵消掉的cache利用率收益远远小于不同处理器之间的负载均衡作同样的事情。

情况四:情况三的前提下,每一个处理器的每一个核心又开启了超线程(Intel术语)。

由于超线程使用同一套计算资源,且共享cache,因此其上的负载均衡几乎不会影响cache利用率。然而如果超线程核的调度算法以及操作系统的调度算法设计的不好,造成一个操作系统线程长期使用超线程核,也会造成上一个切换出的操作系统线程的cache被挤出去,遗憾的是,一般情况下我们无力优化操作系统的调度算法,并且无法接触cpu的smt调度算法。

情况五:情况一到四的前提下,增加不对称内存。

这种情况下,负载均衡对cache利用率的影响显然是不可避免的,同时还会影响访问内存的时间,也就是内存的利用效率,这就是NUMA的情况...
以上五种情况基本就是一个复杂系统从最简单到最复杂的排列,如果我们不以整个系统为核心,而以处理器为核心, 应该是以下四种排列情况:
以处理器为核心的分类:

情况一:单个处理器开启超线程

这是我们熟知的SMT情况

情况二:单个处理器多个核心

这是我们熟知的多核处理器情况

情况三:多个处理器

这是我们熟知的SMP情况

情况四:多处理器,多内存域

这是我们熟知的NUMA情况,内存对于不同的处理器来讲,其访问效率是不同的。

三.负载均衡基础设施以及cpu拓扑结构(静态设施)

进程在不同cpu之间的迁移和cache利用率总的来说是对立的,并且根据源cpu和目的cpu之间的关系不同这种对立的程度 也不同。 由于进程迁移是基于cpu的,而cpu最小级别的就是超线程处理器的一个smt核,次小的一级就是一个多核cpu的核,然后就是一个物理cpu封装,再往后就是cpu阵列,根据这些cpu级别的不同,Linux将所有同一级别的cpu归为一个“调度组”,然后将同一级别的所有的调度组组成一个“调度域”, 负载均衡首先在调度域的各个调度组之间进行,然后再在最低一级的cpu上进行,注意负载均衡是基于最小一级的cpu的。整个架构如下图所示:


四.负载均衡算法分析(动态表现)

迁移一个进程的代价就是削弱cache的作用。 因此,只要在拥有cache的处理器之间迁移进程,势必会付出这个代价,因此在设计中必然需要一种“阻力”来尽量不做进程迁移,除非万不得已!这种“阻力”就是负载均衡原则2中的“加权”系数。

1).历史负载值的影响

为防止处理器负载曲线的上下颠簸,用历史值来加权当前值是一个不错的方式 ,也就是说,所谓的负载曲线不再基于时间点 ,而是基于时间段 然而历史负载值对总负载的影响肯定没有当前的负载值对总负载的影响大,一个时间点的负载值随着时间的流逝,对负载均衡时总负载的计算的影响应该逐渐减小。因此Linux的负载均衡器设计了一个公式专门用于负载均衡过程中对cpu总负载的计算。该公式如下:

total_load=(previous_load*(delta-1)+nowa_load)/delta

其中delta是一个可变的系数,在linux 2.6.18中设置了3个delta,分别为1,2,4,当然还可以更多,比如高一些版本的内核中delta的取值有CPU_LOAD_IDX_MAX种,CPU_LOAD_IDX_MAX由宏来定义。比如当delta为2的时候,上述公式成为:
total_load=(previous_load+nowa_load)/2
相当于历史值占据整个load值一半,而当前值占据另一半。

2).波峰/波谷的平滑化

让历史值参与计算总负载解决了同一条负载曲线颠簸的问题,但是在负载均衡时是比较两条负载曲线同一时间点上的值 ,当二者相差大于一个阀值时,实施进程迁移。 为了做到“尽量不做进程迁移”这个原则,必须将两条负载曲线的波峰和波谷平滑掉。 如果进程迁移源cpu的负载曲线此时正好在波峰,目的cpu的负载曲线此时正好在波谷,此时就需要将波峰和波谷削平,让源cpu的负载下降a,而目的cpu的负载上升b,这样它们之间的负载差就会减少a+b,这个“阻力”足以阻止很多的进程迁移操作。

3).负载曲线平滑操作的基准

负载均衡平滑操作时需要两个值,即上述的a和b,这两个值决定了削平波峰/波谷的幅度,幅度越大,阻碍负载均衡的“力度”也就越大,反之“力度”也就越小。根据参与负载均衡的cpu的层次级别的不同,这种幅度应该不同,幸运的是,可以根据调整“负载均衡过程中对cpu总负载的计算公式”中的delta来影响幅度的大小, 这样,1)和2)就在这点上获得了统一。对于目的cpu,取计算得到的total_load和nowa_load之间的最大值,而对于源cpu,则取二者最小值。可以看出,在公式中,如果delta等于1,则不执行削波峰/波谷操作,这适用于smt的情况,delta越大,历史负载值的影响也就越大,削波峰/波谷后的源cpu负载曲线和目的cpu负载曲线的差值曲线越趋于平滑,这样就越能阻止负载均衡操作(差分算法....)。

4).自下而上的遍历方式

Linux在基于调度域进行负载均衡的时候采用的是自下而上的遍历方式,这样就优先在对cache影响最小的cpu之间进行负载均衡,同时这种均衡操作会增加本cpu的负载,反过来在比较高的调度域级别上有力的阻止了对cache影响很大的cpu之间的负载均衡。 我们知道,调度域是从对cache影响最小的最底层向高层构建的。

5).结论

随着cpu级别的提高,由于负载均衡对cache利用率的影响逐渐增大,“阻力”也应该逐渐加大,因此负载均衡对应调度域使用的delta也应该增加。 算法的根本要点是什么呢?画幅图就一目了然了,delta越大,负载值受历史值的影响越大,因此按照公式所示,只有持续单调递增 的cpu负载,在源cpu选择时才会被选中,偶然一次的高负载并不足以引起其上的进程迁移至别处,相应的,只有负载持续单调递减 ,才会引起其它cpu上的进程迁移至此,这正体现了负载以一个时间段而不是一个时间点为统计周期! 而级别越高的cpu间的进程迁移,需要的“阻力”越大,因此就越受历史值的影响,因为只要历史中有一次负载很小,就会很明显的反应在当前,同样的道理,历史中有一次的负载很大,也很容易反映在当前;反之,所需“阻力”越小,就越容易受当前负载值的影响,极端的情况下,超线程的不同逻辑cpu之间的负载计算公式中delta为1,因此它们的负载计算结果完全就是该cpu的当前负载!
结论有三:

5.1).通过“负载均衡过程中对cpu总负载的计算公式”平滑了单独cpu的负载曲线,使之不受突变的影响,平滑程度根据delta微调

5.2).通过“削掉波峰/波谷”平滑了源cpu和目的cpu负载曲线在负载均衡这个时间点的差值,尽可能阻止进程迁移,阻止程度根据delta微调

5.3).执行负载均衡的过程中,一轮负载均衡在每一层的效果需要随着级别的升高而降低,这通过自下而上的遍历方式来完成

6).引申

total_load的计算公式实际上使用了一个数列,该数列是一个“等比数列+微扰数列”的和数列, 等比的比值的分母决定着数列的平滑程度,而微扰数列则是cpu的当前真实负载,它根据delta的取值不同对整个cpu的负载影响不同,为了连续化数列,我们设这两个数列为函数f(x)和g(x),证明如下:

 

 

6.1).算法改进

不能从delta中得到随着d的增加,阻碍负载均衡的力度将加大这个事实虽然在技术上通过自下而上的遍历方式解决了,然而这使得算法依赖了一个操作方式,这在数学却不是很完美, 因此可以改进,引入一个参数k来微调g(x) ,而不是依赖d来微调,如果配置k和d相等,那么新算法将回退到老算法:

 

 

有了新的负载计算公式,我们可以控制一个变量k,然后得知,随着d的增加,负载均衡实际发生的可能性将降低。

五.Linux负载均衡的类型以及时机

1.周期性忙负载均衡:在时钟中断中针对当前cpu调用,负载计算时更多受到历史值的影响;

2.周期性空闲负载均衡:当前cpu上没有进程可运行时调用,适当减少历史值的影响,和忙负载均衡的周期相差一个busy_factor因子(该因子可配置)。

3.唤醒进程负载均衡:Linux内核倾向于本地唤醒进程,也就是说将进程唤醒在本cpu上。在网络应用中,这显得尤为重要,众所周知,网卡中断某一个cpu,该cpu处理软中断,软中断处理协议栈,在cpu的处理过程中网络数据相应进入cache,此时唤醒用户态进程继续处理应用数据,如果是本地唤醒的话,应用程序可以有效利用cpu中已经被内核载入的cache。

4.进程新创建的时候会进行负载均衡,因此多了一个进程可能会引起负载失衡。

5.进程调用exec的时候会进行负载均衡,和4一样,这两种负载均衡都是“自负载”均衡,也就是要为自己选择一个cpu来运行

6.当前cpu马上进入idle的时候,会进行负载均衡

7.push平衡,这是一种将本地进程“推”给其他cpu的负载均衡方式

 

第二部分:Linux负载均衡的实现(2.6.18内核)

一.数据结构

1.sched_group结构

2.sched_domain结构

二.代码

1.初始化

1.1.build_sched_domains函数

对于每一个最低级别的cpu(比如超线程cpu)依次执行:

其中三个函数返回了cpu在对应级别的编号,用于初始化调度组:

1.1.1.返回物理cpu的cpu号:

1.1.2.返回cpu核的cpu号:

1.1.3.返回逻辑cpu的cpu号:


初始化了每一个逻辑cpu的调度域之后,依次初始化每一个调度域的各个调度组

1.2.init_sched_build_groups函数

该函数初始化了同一调度域中的所有的调度组,逻辑很简单:

2.负载均衡运行

2.1.rebalance_tick函数

每次时钟中断时,要判断是否要做负载均衡操作了,具体来讲实现两个逻辑:

2.1.1.更新cpu_load,也就是实现了计算总负载的公式


可见,Linux将3个负载值保存成了数组,随着索引的增加,历史值影响逐渐加大,具体祥见第一部分的分析

2.1.2.从下往上依次判断是否进行负载均衡


j的设置是巧妙的,由于每次时钟中断都会导致jiffies递增,因此当某个时刻j-sd->last_balance正好等于interval的时候,比该cpu的cpu号大的cpu的结果将是j-sd->last_balance<interval,由此多个cpu同时操作同一个cpu的几率将减少(有效避免了该cpu将别的cpu上的进程拉了过来,然而别的cpu在调用同一函数的时候又将进程拉了回去这种互相扯皮的事情),鉴于随机数的产生会有很大的开销,因此采用了jiffies+cpu*HZ/NR_CPUS这种算法来混乱化执行的时间。 然而,由于对于每一个cpu,balance_interval参数是可以配置的,因此配置不同的balance_interval参数可能会抵消掉这种混乱化操作的结果。

2.2.load_balance函数

该函数比较复杂,它在同一个调度域的各个调度组之间进行负载均衡,总的来讲分为三块

2.2.1.找出最busy的组

2.2.2.在最busy的组中找出最busy的cpu

2.2.3.迁移最busy的cpu上的进程到本cpu,并返回实际迁移的进程的数目

 

2.3.find_busiest_group函数

该函数实现很复杂,然而逻辑很简单,基本策略祥见第一部分的“负载均衡算法分析”。对于代码,实际上就是一个两层的循环加上数据的更新

其中source_load取了cpu_load[delta]和nowa_load的最小值,削掉了波峰,而target_load则相反,削掉了波谷

可以看到,基于调度域的负载均衡是从下往上进行的,这样做的好处在于,每次优先从最底层级别附近pull进程过来,这样对cache的影响最小,比如两个逻辑cpu之间迁移进程对cache的影响就会小到可以忽略。随着调度域的级别的增加以及pull过来的进程增加,本cpu的负载会增加,一般而言,到达物理cpu级别这个调度域,本cpu已经就已经很忙了,因此也就很难再进行负载均衡了,实际上这也是一种阻碍进程迁移的方式。

2.4.find_busiest_queue函数

在最busy的组中寻找最busy的cpu,很简单,就是一次冒泡算法。

2.5.move_tasks函数

迁移进程

2.6.can_migrate_task函数

内核代码中的注释解决本函数:
We do not migrate tasks that are:
1) running (obviously), or
2) cannot be migrated to this CPU due to cpus_allowed, or
3) are cache-hot on their current CPU.
Aggressive migration if:
1) task is cache cold, or
2) too many balance attempts have failed.

需要注意的是,cache的热度是通过进程离开运行态到现在的时间差来决定的,而这个差的阀值到底是多少,则由调度域的一个cache_hot_time字段决定。

2.7.migration_thread内核线程

本文不谈这种push模式的进程迁移,同时也不深究所谓的主动均衡和被动均衡,这些在理解了核心算法后都会很简单的。故此处略过

 

第三部分:负载均衡的配置

一.概述

值得注意的是,Linux所实现的调度域和调度组仅仅描述了一个cpu的静态拓扑和一组默认的配置, 这组默认的配置生成的原则就是在第一部分中描述的各种情况的基础上在负载均衡和cache利用率之间产生最小的对抗 Linux并没有将这些配置定死,实际上Linux的负载均衡策略是可以动态配置的。
     由于负载均衡实现的时候,对调度域数据结构对象使用了浅拷贝 ,因此对于每一个最小级别的cpu,都有自己的可配置参数,而对于所有属于同一调度域的所有cpu而言,它们有拥有共享的调度组,这些共享的信息在调度域数据结构中用指针实现,因此对于调度域参数而言,每个cpu是可以单独配置的。每个cpu都可配置使得可以根据底层级别cpu上的负载情况(负载只能在最底层级别的cpu上)进行灵活的参数配置,还可以完美支持虚拟化和组调度。

二.配置方法

目录/proc/sys/kernel/sched_domain 下有所有的最底层级别的cpu目录,比如你的机器上有4个物理cpu,每个物理cpu有2个核心,每个核心都开启了超线程,则总共的cpu数量是4*2*2=16,因此
root@ZY:/proc/sys/kernel/sched_domain# ls
cpu0  cpu1  cpu2  cpu3  cpu4  cpu5  cpu6  cpu7 ...cpu15
root@ZY:/proc/sys/kernel/sched_domain# cd cpu0/
root@ZY:/proc/sys/kernel/sched_domain/cpu0# ls
domain0  domain1 domain2
root@ZY:/proc/sys/kernel/sched_domain/cpu0# cat domain0/name
SIBLING
root@ZY:/proc/sys/kernel/sched_domain/cpu0# cat domain1/name
MC
root@ZY:/proc/sys/kernel/sched_domain/cpu0# cat domain2/name
CPU
root@ZY:/proc/sys/kernel/sched_domain/cpu0# ls domain0/  #以下这些参数都是可配置的,使用sysctl即可,含义见sched_domain结构体
busy_factor       cache_nice_tries  forkexec_idx      imbalance_pct     min_interval      newidle_idx      
busy_idx          flags             idle_idx          max_interval      name              wake_idx

三.配置实例

列举一个性能调优的实例,当我们手工绑定了进程在各自cpu上运行,并且手工平衡了各个cpu的负载,每一个cpu都有特定的任务,比如cpu0处理网络中断和软中断,cpu1处理磁盘IO,cpu2运行web服务,...(暂不考虑dca等对cache的影响),那么也就不希望内核再做负载均衡了,因此需要针对每一个cpu的调度域进行配置,使之不再进行或者“很不频繁”进行负载均衡操作:
root@ZY:/proc/sys/kernel/sched_domain# echo 100000000000 > cpu0/domain1/min_interval
root@ZY:/proc/sys/kernel/sched_domain# echo 100000000000 > cpu0/domain2/min_interval

...//针对cpuX依照上述执行,另外还要设置max_interval,要大于100000000000 。对于domain0,由于它是SMT级别的,因此负载均衡并不会破坏cache,因此不设置。
...//其实还有很多的参数可以设置,比如flags,imbalance_pct,busy_factor等。
注解: 由于fork和exec的行为是不同的,fork后的新进程还是要访问老进程的数据(写时复制),而exec则彻底告别老进程(虽然还可能会访问同样载入老进程的共享库),因此调度域的flags中的SD_BALANCE_FORK和SD_BALANCE_EXEC最好应该区别开来,我们可以通过在SMT或者MC调度域中设置SD_BALANCE_FORK而SMP中不设置SD_BALANCE_FORK来优化fork后的进程的写时复制,至于SD_BALANCE_EXEC则全部支持,不过这样设置的前提是你对你的应用进程的脾气很了解,如果exec后的进程和之前的进程共享大量的在之前之后都大量被读写的共享库的话,说实话SD_BALANCE_EXEC标志也最好不要设置在SMP调度域中。

第四部分:又一个内核hack

完全可以使用sysctl配置系统的debug级别或者重新编译内核增加更多的打印信息,然而编写modules导出自己需要的信息一直都是最好的方式,因为它只输出你需要的信息,而内核的debug信息虽然很详细,但是你可能还真的需要花一番功夫才能明白其所以然。

     以下是一个内核模块的代码,它揪出了两个cpu的调度域和调度组信息,然后打印出来,这种编写模块的好处在于,你可以做且仅做你需要的,且一切按照你自己的风格来!



对于一个单物理cpu开启超线程的系统加载上述模块,dmesg得到以下结果:
[63962.546289] domain address: ffff88000180fa20
[63962.546294] domain name: SIBLING
[63962.546297] domain busy: 3
[63962.546300] domain busy: 180fa98
[63962.546303] group address:ffff88000180fae0  #cpu0-第一个逻辑cpu的smt调度域的第一个组,包括它自身(1)
[63962.546306] group address:ffff88000184fae0  #cpu0-第一个逻辑cpu的smt调度域的第二个组,包括它兄弟(2)
[63962.546308] next domain
[63962.546311] domain address: ffff88000180fb30
[63962.546314] domain name: MC
[63962.546316] domain busy: 30
[63962.546319] domain busy: 180fba8
[63962.546321] group address:ffff88000180fbf0
[63962.546324] next domain
[63962.546326] NEXT CPU 
[63962.546329] domain address: ffff88000184fa20
[63962.546332] domain name: SIBLING
[63962.546335] domain busy: 0
[63962.546337] domain busy: 184fa98
[63962.546340] group address:ffff88000184fae0  #cpu0-第一个逻辑cpu的smt调度域的第一个组,包括它自身,等于(2)
[63962.546343] group address:ffff88000180fae0  #cpu0-第一个逻辑cpu的smt调度域的第一个组,包括它兄弟,等于(1)
[63962.546345] next domain
[63962.546348] domain address: ffff88000184fb30
[63962.546351] domain name: MC
[63962.546354] domain busy: 2
[63962.546357] domain busy: 184fba8
[63962.546359] group address:ffff88000180fbf0
[63962.546362] next domain

 

第五部分:Linux内核《sched-domains.txt》翻译

要问关于Linux内核的那些资料最好,我觉得最好的有两个,一个是LKML(Linux kernel maillist)还有一个就是内核文档,内核文档中的信息相当丰富,涉及了几乎所有的核心功能,因此阅读它们是有帮助的,本文的最后,我尝试将其中《sched-domains.txt》翻译一下,“[]”中的是我的一切注释,注意,以下并不是原文直译,而是意译(信,达,雅三境界中,我可能连“信”都谈不上,因此找个理由,说是意译!)。译文如下:

每一个cpu都拥有一个“base”调度域(struct sched_domain)[注:基本的调度域,也就是最底层的调度域,以下的per-cpu指的就是最底层的cpu]。这些“base”调度域可以通过cpu_sched_domain(i)和this_sched_domain()这两个宏来访问。调度域层次结构从这些“base”调度域开始[注:向上]构建,通过调度域的parent指针可以访问到其上级调度域。parent指针必须是NULL结尾的[注:最高一级别的调度域的parent指针为NULL],调度域是per-cpu的,因为这样可以在更新其字段时,锁的开销更小[注:同时每个调度域也是单独可配置的]。
每一个调度域覆盖一定数量的cpu(存储于span字段)。一个调度域的span必须是其子调度域span的超集。cpui的“base”调度域的span中起码要包含cpui。最顶层的调度域需要覆盖系统所有的cpu,虽然严格来讲这并不是必须的,会导致一些cpu上从来都没有进程运行。调度域的span的含义是“在这些cpu之间进行负载均衡”
每一个调度域必须拥有起码一个cpu调度组(struct sched_groups),这些组组织成一个环形链表,该链表从调度域的groups字段开始。同一个调度域的这些组的cpumasks的并集表示的cpu必须和该调度域的span字段表示的cpu完全相等,同时,属于同一调度域的任意两个组的cpumasks字段的交集必须是空集。一个调度域的调度组覆盖的cpu必须是该调度域所覆盖的。这些调度组的只读数据在cpu之间是共享的。
一个调度域的负载均衡操作发生在其各个调度组之间。此时,每一个调度组被当成了一个整体对待。一个调度组的负载定义为该组中所有的cpu成员的负载之和,并且只有当组与组之间的负载失衡的时候,才会在组与组之间迁移进程。
从源文件kernel/sched.c中可以看出,每一个cpu会周期性的调用rebalance_tick函数。该函数将从该cpu的“base”调度域开始检查该调度域内的进程是否到达了其负载均衡的周期,如果是,则在该调度域调用load_balance,然后在“base”调度域的parent调度域中执行上述操作,这是一个遍历的过程,遍历过程以此类推。
*** 调度域的实现[和定制] ***
“base”调度域将构成调度域层级结构的第一级。举例来讲,在SMT的情况下,“base”调度域覆盖了一个物理cpu的所有逻辑cpu,每一个逻辑cpu构成了一个调度组。SMP的情况下,物理cpu调度域作为“base”调度域的parent,它将覆盖一个NUMA节点中的所有的cpu。其每一个调度组覆盖一个物理cpu。同样的道理,在NUMA情况下,节点调度域作为物理cpu调度域的parent,它将覆盖整台机器的所有cpu,其每一个调度组覆盖一个节点的所有cpu。
实现者需要阅读include/linux/sched.h文件里面关于sched_domain结构体的字段的注释以及SD_FLAG_*和SD_*_INIT来了解更多的细节以及了解如何来调节这些参数从而影响内核负载均衡的行为。
如果你想支持SMT,必须定义CONFIG_SCHED_SMT宏,并且提供一个cpumask_t类型的数组cpu_sibling_map[NR_CPUS],元素cpu_sibling_map[i]的含义是所有和cpui属于同一个物理cpu[或者物理cpu核]的逻辑cpu的掩码,这个掩码中当然也包括cpui本身。
[针对特定的体系结构可以对Linux内核默认的调度域/调度组的默认参数以及设置进行重载,此处不再翻译]

2019-06-05 10:41:06 Zhou_August 阅读数 516
  • Linux根目录和常见目录

    区块链游戏开发的入门到深入了解 学到linux命令行;EOS环境安装;C++;HTML;CSS;JavaScript;React;Redux等知识。

    0课时 0分钟 13人学习 张云波
    免费试看

使用 hbash shell 输错一直换行 退不出去怎么办

在这里插入图片描述
就是 >’ 大于号加英文状态下的小撇 然后输入正确的命令 就可以退出循环

2018-10-21 13:56:12 AthlenaA 阅读数 1666
  • Linux根目录和常见目录

    区块链游戏开发的入门到深入了解 学到linux命令行;EOS环境安装;C++;HTML;CSS;JavaScript;React;Redux等知识。

    0课时 0分钟 13人学习 张云波
    免费试看

4Linux常用文件操作命令

在这里插入图片描述

4.1.1 Linux常用命令-文件处理命令-命令格式与目录处理命令ls

在这里插入图片描述

在这里插入图片描述
选项是用来调整功能的,参数就是操作的对象:文件名称
简化选项用一个-,完整选项用两个–
在这里插入图片描述

以点开头的是隐藏文件,隐藏文件并不是为了让你找不到,是为了告诉用户是一个系统文件不要乱动它
linux中设置隐藏文件的方式就是改名加上.
在这里插入图片描述

l-long长格式,就是相关的属性
在这里插入图片描述
分成了七个组成部分,先说后六个部分,
1:引用计数,代表这个文件被引用过多少次
root:所有者
root:所属组
针对文件把用户分为三类root所有者只能有一个、group所属组一组相同类型的用户只能有一组、other其他人
1205:文件大小,默认单位是字节
-lh人性化显示
在这里插入图片描述
1.2k:文件大小
3月:最后一次修改时间
anaconda-ks.cfg:文件名称

接下来看第一部分是什么意思:文件类型和用户的权限
在这里插入图片描述
第一个字符:- d l
后九个字符:分为三组,分别代码所有者、所属组、其他人
每组有3个字符,读、写、执行
这样就可以判断每种类型的用户对这个文件可以执行什么操作

-d direct显示目录本身,而不显示目录下的东西
在这里插入图片描述

i结点:每个文件和目录都有一个id号,-i查结点号
在这里插入图片描述

总结:ls命令的五个
-a 查隐藏文件
-l 详细信息
-d 指定目录的详细信息
-h 人性化
-i i结点

4.1.2 Linux常用命令-文件处理命令-目录处理命令

1. 目录处理命令:mkdir 创建目录

在这里插入图片描述
不要把目录弄得乱七八糟,不要在根目录下创建目录
-p允许递归创建目录
在这里插入图片描述
允许同时创建多个目录
在这里插入图片描述

2.目录处理命令:cd +目录名称

在这里插入图片描述
创建完目录后,如何切换到这个目录
在这里插入图片描述

3.目录处理命令:pwd 显示绝对目录

注意前面只保存了当前的子目录的名称,如果我们想要让他显示完整的路径。查询到当前的详细目录
在这里插入图片描述
在这里插入图片描述

.表示当前目录,…表示上一级目录。注意这里cd空格…,要有空格。
在这里插入图片描述

4.目录处理命令:rmdir (remove empty directories)删除空目录

在这里插入图片描述
备注:只能删除空目录,非空不能删。如果你想要删除一个目录,先要把这个目录下的子目录和文件都给删除掉。要删除非空目录需要用rm
在这里插入图片描述

5.目录处理命令:cp (copy)

在这里插入图片描述

在这里插入图片描述
复制文件
在这里插入图片描述
复制目录
在这里插入图片描述
可以同时复制多个目录或文件
在这里插入图片描述

在这里插入图片描述
复制前后文件的时候不一样了,是以文件最后修改的时间为准。

但有的时候,想把日志备份,不希望文件最后更改的时间发生变化,就用-p,可以保存你想要复制的文件的属性
在这里插入图片描述

可以复制文件的同时改名
在这里插入图片描述

6.目录处理命令:mv(剪贴)

在这里插入图片描述
ctrl+l清屏或者敲clear命令
可以省略当前目录也可以不省略
在这里插入图片描述

剪切的时候改名
在这里插入图片描述

如果要在当前目录下面改名:在同一个目录下面移动就是改名
在这里插入图片描述

7.目录处理命令:rm (remove)

在这里插入图片描述
在这里插入图片描述
剪切和删除要谨慎,小心系统文件。
rm -rf [文件或目录]
-r 删除目录 (不加-r只能删除文件)
-f 强制执行(系统不会询问你是否确定删除,如果不加系统会一个个文件询问你是否确定删除)
在这里插入图片描述

一次删除多个文件,不要让他询问是否确认删除
在这里插入图片描述

删除目录
在这里插入图片描述
如果不写-f会一个一个的问你,这个目录下面有几百个文件
ctrl+c终止
在这里插入图片描述
这样就算这个目录下面有上百个文件也不会让你确认,直接就删除掉了

4.1.3 Linux常用命令-文件处理命令-文件处理命令

touch cat tac more less head tail

1.touch命令(创建文件)

格式: touch [文件名]
在这里插入图片描述
在这里插入图片描述
这表明要在当前目录tmp下创建文件,若要在指定目录下创建文件,需要在文件名前指定它的路径。
备注:空格的使用 如果想创建带有空格的文件
eg. touch program files 会创建两个文件 program 和 files 可以同时创建多个文件
touch “program files” 创建单个文件 program files
不建议这么用,因为空格多用于多个文件的隔开
在这里插入图片描述

2.cat命令(显示文件内容,通常应用于短文件)

格式:cat -n [文件名]
-n:显示行号
在这里插入图片描述
在这里插入图片描述

3.tac(倒着显示文件内容)格式与cat一样

不支持-n
在这里插入图片描述
这是看一个比较短的文件,如果要查长的文件,

4.more(分页显示文件内容,通常应用于长文件,但是不能向上翻页)

格式:more [文件名]
(空格) 或 f 翻页
(Enter) 换行
q或者Q 退出
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
按空格:一页一页的往后翻
按回车:一行一行的往后翻
往回返页?

5.less(与more 类似,只是多了向上翻页功能,同时多了搜索功能)

在less命令下输入 /xxx 即搜索包含xxx关键词的文件
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同样也可以空格翻页、回车换行
如果想要回返,按page up一页一页往回翻,用上箭头一行一行往回翻

在这里插入图片描述
可以进行搜索,写上斜杠/以及要搜索的关键词,按下回车
在这里插入图片描述
高亮显示的部分即为找到,当前页没有,按n往下找

6.head(显示文件前几行)

格式:head -n [文件名]
-n表示指定行数
在这里插入图片描述
在这里插入图片描述
如果没有指定行数,默认显示前10行
在这里插入图片描述

7.tail (显示末尾几行,与head基本相同)

格式:tail -n [文件名]
-f:动态显示文件末尾内容,更新文件时也会动态变化
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
这里并没有回到命令行
然后我们再打开一个命令框:去更改一些命令,在这个文件中就会动态显示。
最多用的就是监控一下日志。

4.1.4 Linux常用命令-文件处理命令-链接命令ln

1.ln(生成链接文件)

原意:link
格式: ln -s [原文件] [目标文件]
-s 创建软链接
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

2.软链接:类似于windows的快捷方式

在这里插入图片描述
特点:①文件属性 lrwxrwxrwx
②文件大小-只是符号链接
③/tmp/issue.soft -> /etc/issue 有箭头指向源文件
软链接文件:l表示是软链接文件,3种用户3种权限rwx都可以,所有人都有全部的操作权限
相当于windows的快捷方式,方便快捷的让你找到常用的软件,右边属性只是一个快捷文件而不是应用。
软链接指向它的源文件。因为这个权限不决定它的源文件的操作方式。
很小,不管源文件有多大,他都只有十几个字节

3.硬链接:相当于拷贝 cp -p,但是同步更新

备注:硬链接不能跨分区,不能对目录使用
在这里插入图片描述
在这里插入图片描述
之前:
在这里插入图片描述
在这里插入图片描述
之后:往其中的某一个文件中写入一行
在这里插入图片描述
源文件末尾增加了一行
在这里插入图片描述
硬链接文件末尾也增加了一行
那么软链接是什么样的?
在这里插入图片描述
软链接末尾也增加了一行,毕竟软链接是指向硬连接的

在这里插入图片描述
把这个文件拷贝到一个地方去,然后把这个文件给删掉
那么再查看软链接,就会找不到
看硬链接,还在
在这里插入图片描述
在这里插入图片描述

4.区别:

1如果把原文件删除,软链接丢失,硬链接仍能使用(相当于复制但是同步更新)
2软链接可以跨分区,硬链接不可以跨分区
在这里插入图片描述
3硬链接不可以针对目录,软链接可以对目录使用
这个目录不可以作为硬链接
在这里插入图片描述
4判断硬链接方法:通过i节点识别
ls -i 查看inode,硬链接与源文件inode相同
判断软链接很简单,直接查看这个软链接文件会有箭头指向
在这里插入图片描述
直接查看三个文件的inode。一个文件只有一个inode,但是一个inode可以对应多个文件。
在这里插入图片描述
5硬链接作用:相当于实时备份,但是用的不多

4.2.1 Linux常用命令-权限管理命令-权限管理命令chmod

在这里插入图片描述
前面讲了如果查询到文件的权限
默认有一个权限,但是不一定合理,我们要学习如何更改权限

1.chmod (改变文件或目录权限)

格式:chmod [{ugoa} {±=} {rwx}] [文件或目录] [mode=421] [文件或目录]
-R:递归修改(在该目录下修改其全部子文件子目录的权限)
解释: u表示所有者 ,g表示所属组 ,o表示其他人,a表示所有人
±=增加减少或者直接赋予某种权限
rwx读写执行
在这里插入图片描述
只有文件的所有者和root可以更改权限。

我们想给所有者添加上执行的权限
在这里插入图片描述

可以同时做多个授权
我们给所属组增加写的权限,给其他人减去读的权限
在这里插入图片描述

=号的意思是我不考虑以前你是什么样的权限,我现在就要把权限设置成这个样子
在这里插入图片描述

2.权限通常用数字来更改,非常简单

在这里插入图片描述
把9个位的权限改成用3个数字来表示需要掌握权限与数字的转换,给你一个权限,你要能写出来它对应的数字
给你一个数字,你能写出来权限的表示
eg. 更改abc文件权限为 rw-r-----
对应输入: chmod 640 abc
在这里插入图片描述
在这里插入图片描述

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

3.-R递归修改

在这里插入图片描述
a目录和b目录 权限相等
在这里插入图片描述
我们改了a的权限,b的权限不会被改变
如果我们想要在改变一个目录的权限的同时,把这个目录下面的所有文件的权限都给改掉,那么:
在这里插入图片描述

4.对于权限的理解

在这里插入图片描述
问:一个普通用户是否可以删除这个文件?
在这里插入图片描述
创建了一个普通用户,用普通用户再进一遍
#是root
$是普通用户
在这里插入图片描述
为什么普通用户可以删除掉root创建的这个文件?而且这个普通用户明明没有写的权限呀?
其实是因为我们对rwx的理解是有问题的,要深入理解
在这里插入图片描述
文件的rwx权限:
在这里插入图片描述
r可以查看文件的内容
w可以修改文件的内容,像word一样
x可以执行,脚本、命令可以执行
目录的rwx权限:
在这里插入图片描述
r你可以列出目录中的内容,可以查看目录中有哪些文件
w你可以在这个目录下面创建、删除文件。刚刚那个例子的答案:我用root创建了一个目录,在这个目录下又用root创建了一个文件,但是注意我们把目录的权限改成了777,也就是所有的用户对所有的操作都有全部的权限,虽然那个目录下的文件只有root有写权限,但是这个文件所在的目录是每个人都有写权限的,所以linzhiling登陆过来一看,我对整个目录有写权限呀,证明我可以在这个目录下创建、删除文件呀。
在这里插入图片描述

为什么可以删除temp目录下testfile这个文件,是因为temp这个目录的权限是77呀。不管是root创建的还是任何一个普通用户创建的,都可以被删除。
刚才你为什么认为这个文件删除不了,是因为你以为此用户对这个文件有写权限,其实这个写文件的权限只能修改这个文件的内容,而对这个目录有写权限,才能删除掉这个目录下的文件。
千万要记住这个事情!
x你可以进入这个目录
注:想查看目录有哪些文件要有r,想进去这个目录要有x权限。
目录有r权限一定有x权限:因为不可能你有r权限你却进不去。也不可能你有x权限却没有r权限,你能进去却查看不到里面有什么文件。

为什么这个linzhiling用户可以删除temp/testfile文件?
此用户对这个文件有写权限,其实这个写文件的权限只能修改这个文件的内容,而对这个目录有写权限,才能删除掉这个目录下的文件。

4.2.2 Linux常用命令-权限管理命令-其他权限管理命令

既然一个文件它的权限属性是可以更改的,那么它的所有者它的所属组也可以更改
chmod 改变权限 root和所有者
chown 改变所有者 root
chgrp 改变所属组 root
umask

1. chown(改变文件或者目录的所有者)

在这里插入图片描述
首先我们讲,谁可以改变一个文件的所有者?
上节课讲过,如果你想要改变一个用户的权限,只有两个用户可以,一个是这个文件的所有者,一个是管理员root。
这里我们用linzhiling用户创建一个文件abcd,创建后,有它的默认权限,如果我们想要修改这个权限。
这个用户改是可以的。
在这里插入图片描述
但是今天要讲的改变一个文件的所有者,是不是也像改变操作权限一样呢?试试
在这里插入图片描述
不能,即使你是这个文件的所有者,你也不能改变这个文件的所有者。

那谁可以做呢?管理员root,用管理员登陆
刚刚用户linzhiling创建的abcd文件,是在/home/linzhiling下面
在这里插入图片描述
所以,只有root可以改变一个文件的所有者。
什么时候需要改变?如果一个人的工作还没有写完就要出去,就改变这个文件的所有者 。

现在我们root创建了一个文件fengjie,又创建了一个用户shenchao,然后改变所有者
在这里插入图片描述
中文设置问题
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
前提是这个系统中存在你要改的这个用户。

2.chgrp(改变文件或者目录的所属组)

格式:chgrp [用户组] [文件或目录]
在这里插入图片描述
fenhjie这个文件默认所属组是root
在这里插入图片描述
在这里插入图片描述
把所属组改了以后,你也可以增加所属组的权限,这个组里面所有的成员都是

3.umask(显示设置文件的缺省权限)

格式:umask [-S]
-S:以rwx形式显示新建文件缺省权限
备注:在linux种默认新建的文件权限:644 不具备可执行权限,为了安全性
默认新建的目录权限:755
使用方法:可以更改默认文件权限的参考值
在这里插入图片描述
创建的文件和目录都有默认的权限
一个文件的所有者:谁创建的就是谁
在这里插入图片描述
一个文件的所属组:一个用户可以属于多个组,但是它总只有一个缺省组,就是最主要的
所以就是这个文件所有者的缺省组

谁定义了一个目录默认的命令
在这里插入图片描述
在这里插入图片描述
新建一个目录,则目录的权限与默认的权限一致
在这里插入图片描述
但是新建一个文件,权限就和默认权限不一样了
在这里插入图片描述
看看这个文件的权限相比于目录的权限少了什么东西
每个用户的权限位上都少了一个x
原因:权限管理有一个最基本的定义:缺省创建的文件是不能有可执行文件的
为什么要这样规定:我们看到的木马病毒都是不具有可执行权限的

在这里插入图片描述
0特殊权限
022正常的权限
0—所有者什么权限都没有
2-w-其他人和所属组有写权限,没有读和执行权限
这样是非常不合理的,但是为什么会是这样的呢?
这叫做权限掩码,即真正的权限是用777减去022得到的值
777-022=755 rwxr-xr-x 就是我们用 umask -R显示出的那个
在这里插入图片描述

假设我们觉得755 rwxr-xr-x这个权限不合理,我们希望权限能是rwxr-xr–,这时候怎么做呢
先把这个权限对应的数字写出来754,先要用777减去754=023,即你要把umask的值改为023
在这里插入图片描述
在这里插入图片描述
缺省创建文件权限的值是可以更改的,但是不建议这么做,联系完后改回来

4.3.1 Linux常用命令-文件搜索命令-文件搜索命令find

在这里插入图片描述
目录结构都规划好,减少搜索操作,会占用大量的服务器资源
windows中有一个软件叫做everthing,可以搜索
在这里插入图片描述
根据文件名搜索、根据文件大小搜索
find的选项有很多,这里介绍最常用的
.find(文件搜索)

格式:find [搜索范围] [条件匹配]

备注:-name为精准搜索,搜索init则只搜索文件名为init的文件,名字中包含init的文件不会被搜索到

                 如果要搜索包含init字母的文件名,则  -name *init*

                  或者  -name init???    其中问号匹配单个字符,会搜索到inittab

       -iname不区分大小写搜索

       -size 大小换算方法:  1数据块 = 512字节 = 0.5K

                                           100MB = 102400KB = 204800

1按文件名搜索

在这里插入图片描述
在这里插入图片描述
这里是精准搜索,只有文件名叫做init的会被搜索到,多一个字符都不行

也可以模糊搜索,用匹配符*
在这里插入图片描述
只要文件名中包含了init就会搜索出来

在这里插入图片描述
查找所有的以init开头的文件

在这里插入图片描述
init开头的后面有三个字母的文件
*可以匹配任意字符,?可以匹配单个字符

在这里插入图片描述
区分大小写

在这里插入图片描述
加个-i才能不区分大小写

2按照文件大小搜索

清理时先把大的文件删掉
三种执行的方法
在这里插入图片描述
单位是数据块,一个数据块是512字节,0.5KB。即1KB占两个数据块。
100MB=102400KB=204800数据块
在这里插入图片描述

查找大于100M小于200M的文件

3根据所有者查找-home

在这里插入图片描述

4根据时间属性来查找

在这里插入图片描述
在这里插入图片描述
查找/etc下30分钟之内被修改过内容的文件

5find连接的选项

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
查找出了在etc下面以init开头的文件!用到了-a,既满足什么又满足什么
在这里插入图片描述
查找出了在etc下面以init开头的目录!

6查找后并执行一些操作

在这里插入图片描述
花括号:find查找的结果
\:转义符
;:结束
在这里插入图片描述
找到文件并且列出详细信息
在这里插入图片描述
查找以init开头的为文件列出它们的详细信息

-ok与-exec有什么区别
最大的区别就是就一个询问确认环节
在这里插入图片描述
它一个个的问你要不要看
比如,你要执行查找后删除的命令,就需要这样一个个的确认
在这里插入图片描述

7-inum 针对i节点的

什么时候用:比如我创建了一个文件,删除是不好操作的
在这里插入图片描述
在这里插入图片描述
用引号引起来
那么我们就需要用inode
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.3.2 Linux常用命令-文件搜索命令-其他文件搜索命令

locate which whereis grep

1.locate(文件资料库种查找文件)

格式:locate 文件名
备注:locate存在的问题,可能因为系统没有及时更新文件,搜索不到,但是大量常用的系统文件还是能搜的
在这里插入图片描述
原理:并不像find一样遍历你所查找的范围,而是在文件资料库中找,所以很快
在这里插入图片描述

在这里插入图片描述
我们看看这个资料库在哪呢
在这里插入图片描述
有一个数据库文件资料库

有一个问题:新创建的文件还没有被收集到资料库的话,就找不到
在这里插入图片描述
所以就需要我们手动更新资料库updatedb
在这里插入图片描述

还会有一个问题,创建一个文件叫做zhangbozhi放在tmp目录下
在这里插入图片描述
手动更新资料库后,还是找不到zhangbozhi文件
因为它是存放在tmp目录下,这个目录并不在update所收录的范围之中

如果想要locate查找不区分大小写,就需要用-i
在这里插入图片描述

2.which(搜索命令所在目录及别名信息)

格式:which [命令名称]
在这里插入图片描述
你现在查找的文件就是一个可执行文件、命令,可以更精准的查找到所在目录和别名
在这里插入图片描述

在这里插入图片描述
为什么这个会有询问确认,是因为我们用的是别名rm -i:
在这里插入图片描述
如果我们使用/bin下面的rm命令,则不会有询问确认
在这里插入图片描述

所以你要怎么知道一个命令有没有别名,就是要用which查看

3.whereis(搜索命令所在目录及帮助文档)

格式:whereis [命令名称]
在这里插入图片描述
whereis与which相同点:就是他也能找到文件所在的目录
在这里插入图片描述
不仅如此,他还列出帮助文档所在的位置

4.grep(在文件中搜索字串匹配的并行输出)

格式: grep -iv [指定字串] [文件]
-i 不区分大小写
-v 排除指定字串
在这里插入图片描述
在文件内容中搜索
在这里插入图片描述
在这里插入图片描述
比如我们只想要查看multiuser相关的所在的行
在这里插入图片描述
要想不区分大小写要加-i
在这里插入图片描述
很常用,比如apche Apche

-v排除指定字符串
在这里插入图片描述
这些行里面都会有一个#,表示配置文件或者脚本的注释
要想把注释行去掉,去掉#写在开头的,加一个表示行首的^
在这里插入图片描述
在这里插入图片描述

4.4 Linux常用命令-文件搜索命令-帮助命令

man help
1.man(帮助命令,获得帮助信息)
格式:man [命令或配置文件]
eg. man ls 查看ls命令的帮助信息
man services 查看配置文件services的帮助信息
备注:例如password同时有命令和配置文件,linux优先查看命令
passwd命令在man1目录下,passwd配置文件在man5目录下
通常1表示命令的帮助,5表示配置文件的帮助
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
进入了帮助手册,空格翻页,回车翻行,/加上响应的内容进行定位
输入/-a或者/-l就会定位到相应的位置
在这里插入图片描述

man除了可以看命令的帮助,还可以看配置文件的帮助
在这里插入图片描述
在这里插入图片描述
千万不要写成这个样子man+绝对路径,因为这个时候你得到的并不是它的帮助信息,而是把这个文件都写出来了
只要用man+配置文件名称
在这里插入图片描述
在这里插入图片描述

命令:作用、每个小的选项
配置文件:作用、每个内容
一个配置文件怎么会有选项
在这里插入图片描述
如果一个东西既有命令又有配置文件,会优先查看命令的帮助信息
1命令的帮助
5配置文件的帮助

那么我们只需要加上数字
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
用whatis+命令,直接查看命令的简短信息
在这里插入图片描述
用apropos+配置文件,查看配置文件的简短信息

只是想知道命令的选项
在这里插入图片描述

2man的使用

我们想要更改时间,不需要记住,用的时候查就行了
在这里插入图片描述
在这里插入图片描述
也可以用info

3help(获得shell内置命令的帮助信息)

格式: help 命令
备注:系统有很多内置命令无法通过man来查找 比如cd umask等,这时候需要help
在这里插入图片描述
一些内置命令,找不到路径
在这里插入图片描述
在这里插入图片描述
不能使用man来查找命令
如果用了man
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
后面shell编程时也很有用
总结:
在这里插入图片描述

4.5 Linux常用命令-文件搜索命令-用户管理命令

1.useradd(添加新用户)

格式:useradd 用户名
在这里插入图片描述

2.passwd(设置用户密码)

格式:passwd 用户名
备注:普通用户设置密码不能太简单,而root可以
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
虽然会提示你无效,但你第二次输,还是可以的。
用户自己 登陆后改密码:
要是提示无效,那么就不行。
在这里插入图片描述

3.who(查看登陆用户信息)

格式:who
显示: 登陆用户名 登陆终端(tty表示本地终端 pts表示远程登陆) 登陆时间 登陆主机IP地址
在这里插入图片描述
在这里插入图片描述
登陆的用户名 登陆的终端(tty叫做本地登录,pts叫做远程终端) 登录时间 IP地址

4.w(查看登陆用户详细信息)

在这里插入图片描述
在这里插入图片描述
当前系统时间
up这个linux系统连续运行的时间
当前登陆的用户数目
负载情况(CPU和内存的负载)
IDLE:这个用户登陆过来已经空闲了多久了
JCPU:
PCPU:CPU时间
WHAT:做了什么操作

4.6 Linux常用命令-文件搜索命令-压缩解压命令

几种常见的压缩格式

1.gzip(压缩成.gz的压缩文件)gzip

原意:GNU zip
格式:gzip [文件]
压缩后文件格式: .gz
备注:gzip只能压缩文件,不能压缩目录,与WINDOWS不同
gzip压缩完之后是不保留原文件的
在这里插入图片描述
在这里插入图片描述
不能压缩目录
在这里插入图片描述
不保留源文件
在这里插入图片描述

2.gunzip(解压缩.gz的压缩文件)gunzip

格式: gunzip [压缩文件]
在这里插入图片描述
解压缩有两种方法:一种是gunzip 一种是gzip -d
在这里插入图片描述
压缩比还是挺惊人的,压缩前是641020,压缩后是127223,5倍的压缩比

3.tar(打包目录)

tar -cf tar -xf
tar -zcf tar -zxf
格式: tar 选项[-zcf] [压缩后文件名] [目录]
-c 打包
-v 显示详细信息
-f 指定文件名
-z 打包同时压缩
备注:LINUX压缩目录的步骤:1.先对目录打包 tar 2.再对打包好的tar文件压缩 3.最终格式abc.tar.gz

在这里插入图片描述
先打包
在这里插入图片描述
再压缩
在这里插入图片描述
互联网上常见的压缩包后缀.tar.gz
刚才我们那个有些麻烦,先打包再压缩。能不能打包的同时压缩。
打包的同时压缩
在这里插入图片描述
在这里插入图片描述

解压缩
在这里插入图片描述
打包-c,解包-x
另外三个选项不变
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
后面会大量使用 tar -zxvf abc.tar.gz 的命令来解压缩编译环境

4.zip(压缩文件或目录)zip -r

格式: zip 选项[-r] [压缩后文件名] [目录]
-r 压缩目录
压缩后文件格式:zip
备注:zip会保留原文件
linux压缩文件有gz和zip两种
在这里插入图片描述
在这里插入图片描述
.zip是windows和linux都支持的压缩格式
.gz .tar.gzwindows都支持
比.gz改变了两点:1能保留源文件 2-r可以压缩目录
在这里插入图片描述
压缩目录
在这里插入图片描述

5.unzip(解压缩zip文件)unzip

格式:unzip [压缩文件]
在这里插入图片描述
在这里插入图片描述

6.bzip2(压缩文件)bzip2

格式: bzip2 选项[-k] [文件]
-k:产生压缩文件后保留原文件
备注:特点是压缩比惊人,一般用于大文件
在这里插入图片描述
在这里插入图片描述
对.tar.bz2类型的压缩文件。用tar -cjf压缩 -xjf解压缩
在这里插入图片描述

7.bunzip2(解压缩)bunzip2

格式:bunzip2 选项[-k] [压缩文件]
-k:解压缩后保留原文件
在这里插入图片描述
在这里插入图片描述
总结:
压缩文件类型 压缩命令 选项
在这里插入图片描述

4.7 Linux常用命令-网络命令

1.write(给用户发信息,以ctrl+D保存结束)

格式: write <用户名>
在这里插入图片描述
输入信息,按回退键回不来了课,应该是按下ctrl+回退键
root给linzhiling写一封信:
在这里插入图片描述
linzhiling收到了:
在这里插入图片描述

你可以用who命令查看谁在线
在这里插入图片描述

2.wall(发广播信息)

格式: wall [message]
在这里插入图片描述
给所有用户发信息
在这里插入图片描述
在这里插入图片描述

3.ping(测试网络连通性)

格式: ping 选项 IP地址
-c:指定发送次数
在这里插入图片描述
在这里插入图片描述
除非按ctrl+C就不会停止
在这里插入图片描述

4. ifconfig(查看和设置网卡信息)

格式:ifconfig 网卡名称 IP地址
查看方法:ifconfig(本机网卡和回环网卡)
在这里插入图片描述
在这里插入图片描述
当前计算机的网卡信息
etho本机网卡
以太网 网卡的mac地址16进制表示的物理地址
lo本机回环网卡

5.mail(查看发送电子邮件)

格式:mail [用户名]
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
q退出

6.last(列出目前与过去登入系统的用户信息)

格式:last
在这里插入图片描述
在这里插入图片描述
统计所有用户的登陆信息

7.lastlog(检查某特定用户上次登录的时间)

在这里插入图片描述
在这里插入图片描述
把所有的用户列出来,并且列出最后的登录时间
如果你不想看这么多信息-u和uid
在这里插入图片描述

8.traceroute(显示数据包到主机间的路径)

在这里插入图片描述
在这里插入图片描述
在实际工作中特别有效
能看出来哪个节点断了

9.netstat(显示网络相关信息)

在这里插入图片描述
查询网络状态
在这里插入图片描述
TCP:三次握手 安全 传输控制协议
UDP:快 不可靠 QQ用户数据包协议
在这里插入图片描述
查询本机开了那些数据端口
tcp监听udp不监听
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
tlun只能查看监听
an能查看到正在连接的

查询路由列表
在这里插入图片描述
最后一行是网关

10.setup(配置网络)

在这里插入图片描述
去改变ip地址
在这里插入图片描述
在这里插入图片描述
去网络配置:
在这里插入图片描述
去设备配置:
在这里插入图片描述
只有一个网卡
回车
在这里插入图片描述
在这里插入图片描述
DHCP那里打了个*号,下面三行全都变白了,相当于选了自动获取
作用:自动获取的服务
但是我的电脑环境里不存在自动获取的服务,所以DHCP那里不选
这些要重启一下网络服务,才能生效
在这里插入图片描述
用setup配置的ip地址是永久生效的,和ifcofig不一样哦

11.mount(把我的设备连接到我所给它分配的挂载点 )

在这里插入图片描述
把我的设备连接到我所给它分配的挂载点
外接设备不能自动挂载
在这里插入图片描述
在虚拟机放进一个光盘
手动挂载
在这里插入图片描述
先创建一个目录
在这里插入图片描述
sr0是源文件
在这里插入图片描述

在这里插入图片描述
这就是挂载光盘
用完了光盘:
在这里插入图片描述
你不能在这个光盘里面去卸载
在这里插入图片描述

4.8 Linux常用命令-关机重启命令

1shutdown(关机)

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

2(其它关机命令)

在这里插入图片描述

3(其它重启 命令)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
不能改成0或者6
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5退出登陆

在这里插入图片描述

2019-06-07 02:11:37 dog250 阅读数 2528
  • Linux根目录和常见目录

    区块链游戏开发的入门到深入了解 学到linux命令行;EOS环境安装;C++;HTML;CSS;JavaScript;React;Redux等知识。

    0课时 0分钟 13人学习 张云波
    免费试看

大量线程争抢锁导致CPU自旋乃至内核hang住的例子层出不穷。

我曾经解过很多关于这方面的内核bug:

  • nat模块复制tso结构不完全导致SSL握手弹证书慢。
  • IP路由neighbour系统对pointopoint设备的处理不合理导致争锁。
  • IPv6路由缓存设计不合理导致争锁。
  • Overlayfs的mount设计不合理导致争锁。

凌晨将近两点半,本文再解一例。即描述 IPv6路由cache的设计缺陷 问题以及相应的解法。


IPv4的路由cache在3.6内核及以后被彻底干掉了。但是IPv6的路由cache并没有被干掉,相反,IPv6的路由cache存在非常大的性能问题,直到4.2-rc1版本才被解决。

这是一件非常有意思的事情,整个故事听起来非常好玩,非常值得一读。但是由于本文并不是技术文档,所以我不会去介绍什么是IPv6,也不会去分析IPv6路由查找逻辑的代码。显然,这些都是前置知识,这是读懂这个故事的前提。


被广泛部署的CentOS 7.2所使用的3.10内核,IPv6路由表的实现,存在性能缺陷!

我先给出3.10内核的IPv6路由查找逻辑的框图:
在这里插入图片描述

看上去超级复杂,看上去很有技术含量的一个逻辑。好吧,说说要点:

  • 3.10内核的IPv6路由Trie树的操作采用读写锁。
  • 3.10内核实现的IPv6路由表项和路由cache,被保存在同一棵Trie树里。
  • 3.10内核针对查找到的结果路由项执行cow生成路由cache项,插回Trie树。

这个结构以及这些操作适合读写锁的场景吗?换句话说,这是读多写少的场景吗?

完全不是!

如果IPv6源地址分布离散 ,按照源/目的地址二元组定义一个路由cache项的方法,意味着每一次读之后都会伴随着一次写:

  • 读:查找路由,企图命中路由cache,读锁。
  • 结果:由于源地址离散,未命中路由cache,而命中了FIB。
  • 写:生成路由cache项,插回Trie,写锁。

这便是soft lockup的根源!

注意,触发缺陷的条件是 源地址分布离散! 这非常容易满足,因为:

  • IPv6地址空间巨大。
  • IPv6原则上不会做NAT。

下面是一个复现脚本:

#!/usr/bin/python
from scapy.all import *
import socket

msg = "hello"

while True:
    s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
    # 75的意思是IPV6_TRANSPRANT,即可以bind任意源地址!
    s.setsockopt(socket.IPPROTO_IPV6, 75, 1)
    # 随机生成IPv6源地址
    addr = RandIP6("**")
    try:
        s.bind((str(addr), 1234))
    except Exception as e:
        pass
    address = ('2010:222:2002::xxx', 31500)
    try:
        # 这里将包发出去后,被压的机器将会目睹超级多的源/目标向其袭来!
        s.sendto(msg, address)
    except Exception as e:
        pass
    s.close()

问题分析清楚了,看看解法吧。下面给出我的解法中的框图:
在这里插入图片描述

完美解决问题,解法非常简单:

  • 干掉IPv6的路由cache!

再跑下脚本?


现在故事开始了,我来讲述一下这个性能缺陷的来龙去脉,让我们一同领略David Miller的大师风采吧!


程序员讨厌说历史,但历史地看问题是一种方法论,理清脉络方知问题出在哪儿,如何修正便是信手拈来了。

如果说IPv4是在3.5版本内核之后,3.6版本内核开始废掉了路由cache,那么对于IPv6而言,同样含义的版本号则是4.2-rc1.即,在4.2-rc1版本内核之后,IPv6的路由cache几乎不再起作用了。但是IPv6对路由cache的废弃并没有IPv4做的那样决绝,那般彻底。

让我们先从问题说起。

本文开头,我展示了3.10内核IPv6路由cache机制存在的缺陷,该缺陷会导致系统软锁的问题。在处理完这个问题后,我决定挖一下坟,以便为我的解决问题的patch寻找一些支撑或者说可行性的依据:
这个问题是固有的呢,还是中间被引入的呢?

如果问题是固有的,那么我相当于按照正确的思路完成了一个正确的事情,如果它是被引入的,那么我想看看引入这个问题前,事情是什么样子的。

于是,我review并check了从2.6.18开始的主要kernel changelog以及patchwork上的讨论,最终发现是David Miller在2.6.38-rc3中将这个不算Bug的Bug引入了内核,对,就是他,David S. Miller!一点都不平易近人,远不如Google大神Neal Cardwell那般nice的David Miller。

他的patch如下:
http://git.emacinc.com/Linux-Kernel/linux-emac/commit/d80bc0fd262ef840ed4e82593ad6416fa1ba3fc4
下面的链接展示了一个集合:
https://lwn.net/Articles/425770/

在这里插入图片描述
接着看:
在这里插入图片描述

来来来,看下究竟是个什么东西:

diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 373bd0416f69f9ad7e4645eebb574a3ec4eb4127..1534508f6c68a3c4f010657e94051e06a7d727c4 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -72,8 +72,6 @@
 #define RT6_TRACE(x...) do { ; } while (0)
 #endif
 
-#define CLONE_OFFLINK_ROUTE 0
-
 static struct rt6_info * ip6_rt_copy(struct rt6_info *ort);
 static struct dst_entry	*ip6_dst_check(struct dst_entry *dst, u32 cookie);
 static unsigned int	 ip6_default_advmss(const struct dst_entry *dst);
@@ -738,13 +736,8 @@ restart:
 
 	if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP))
 		nrt = rt6_alloc_cow(rt, &fl->fl6_dst, &fl->fl6_src);
-	else {
-#if CLONE_OFFLINK_ROUTE
+	else
 		nrt = rt6_alloc_clone(rt, &fl->fl6_dst);
-#else
-		goto out2;
-#endif
-	}
 
 	dst_release(&rt->dst);
 	rt = nrt ? : net->ipv6.ip6_null_entry;

正是这个 rt6_alloc_clone函数 的调用,让事情开始变的悲哀。

2.6.38-rc3以前的内核,如果不编译CLONE_OFFLINK_ROUTE这个宏,路由cache是不会被插入Trie树的,被David Miller这么一改,故事就开场了!这一去就是7年多啊!期间,我们可以Redhat官方网段看到一个相关的Bug报告:
https://access.redhat.com/solutions/1985663

都是无条件调用rt6_alloc_clone惹的祸!

换句话说,把这个patch回退,就是我在前文中所说的解决问题的我的patch,当然我还做了一些额外的其它处理。

现在的问题是,David Miller为什么要引入这么一个patch呢?显然,他肯定不是故意的。是他水平不够吗?显然他是高手。

肯定是发生了什么事情,为了解决某一个问题而引入的这个patch,然而解决一个问题却导致另一个问题的事情却经常发生,日光之下,并无新事!

下面是2.6.38-rc3这个patch的一点线索:
https://groups.google.com/forum/#!searchin/fa.linux.kernel/CLONE_OFFLINK_ROUTE/fa.linux.kernel/EBkPjNM6dp0/pzK57imIFGoJ
http://patchwork.ozlabs.org/patch/80293/

那么顺着这个2.6.38-rc3的线索开始,经由3.10等稳定版内核,这个IPv6路由cache的soft lockup问题一直存在着,虽然Redhat的3.10会回移上游的patch,然而却不包括修复这个问题的patch. 这非常令人遗憾。

好几年的时间,人们屡次碰到这个问题,却一直没有看到有解法…2018年2月份时,有人提出了一个patch:
https://patchwork.ozlabs.org/cover/877605/
这个比较有意思,可以看看。作者提到:

IPv6 uses the same data struct for both control plane (FIB entries) and
data path (dst entries). This struct has elements needed for both paths
adding memory overhead and complexity (taking a dst hold in most places
but an additional reference on rt6i_ref in a few). Furthermore, because
of the dst_alloc tie, all FIB entries are allocated with GFP_ATOMIC.

然而作者却没有说:

IPv6在运行时使用同一棵树保存路由项和路由cache。

此外,下面这个patch也是解决问题的关键:
https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git/commit/?id=45e4fd26683c9a5f88600d91b08a484f7f09226a
只要看题目就知道其意义了:

ipv6: Only create RTF_CACHE routes after encountering pmtu exception
This patch creates a RTF_CACHE routes only after encountering a pmtu
exception.

奋六世之余烈之后,在4.2-rc1中,事情突然(注意,这里不是悄悄地起了变化)起了变化,我们review这个版本的ip6_pol_route这个函数,会发现它和之前的实现有大不同,查找到的结果路由项不再往Trie树里插入了。Trie上的每一个路由项节点,都携带一个percpu的rt cache,可以直接按照percpu的方式获取之,附着在skb上,然后直接xmit,这看起来比2.6.38-rc3之前的做法更加高明,当然,也比我的实现更加高明,因为我的思路和2.6.38-rc3之前的处理逻辑几乎是一致的。

不过,我依然觉得这个变化来的太慢了。我始终想不通为什么这么一大群高手这么多年却不能秒解David Miller一行代码引入的如此明显的问题,而同样对待这件事,作为一个不会编程的半吊子内核爱好者的我,却可以定位到读写锁的问题,这有点不符合逻辑。

这个问题很明显,只需要perf top看下热点,然后看一遍代码,问题就能浮出水面,我觉得从2.6.38到4.2,整整24个大版本,没能把问题修复,这有点不可思议。

4.2-rc1的关于ipv6路由cache的更新如下:

在这里插入图片描述

在这里插入图片描述

https://gitlab.ic.unicamp.br/lkcamp/linux-staging/commit/c1a34035506d3a7ad62403125d59c86b763c477d
在这里插入图片描述
注意下面的红色框框里的描述:
在这里插入图片描述

当然,在4.2版本内核之后,关于IPv6的路由cache的实现是持续优化的,它再一次发生了变化,这就跟IPv4的处理逻辑非常类似了,不再cache路由结果,而是直接使用,用完后回收到percpu专门的数据结构,而不是释放到slab。

每一个路由项都会关联一个percpu的copy,这意味着路由项和percpu rt cache即实现了天生关联又分了层,这应该就是正确的做法吧。

那么剩下的问题是什么?解决了这个问题之后,还有别的问题吗?

我先重申一下,soft lockup的问题本质不在Trie树,而在锁,而锁的原因则是路由cache的滥用。那么当锁的问题解决了之后,接下来才会正面应对Trie树的查询效率问题。不过我认为,不会有太大问题,Trie已经在IPv4路由上被考验过了,而IPv4的路由表是炸裂式的,乱得很,IPv6在地址规划的时候就自然考虑了聚合,不会像IPv4早年那样乱分一通,所以IPv6的路由表会更加紧凑,这对于组织良好的Trie树结构的非常有帮助的。

所以,我对Trie的性能是满怀信心,我相信它不会轻易畸化,当然了,遇到有备而来的DDoS,另说咯,这就是另一个话题了。


解决了David Miller的问题之后,还有一个问题,那就是peer的问题。

IPv6的事情并没有由于貌似解决了soft lockup问题而结束,相反,一切才刚刚开始!

我们需要对比IPv6和IPv4在同等压力下的性能表现。

那好,我们看看同样脚本同样压力的IPv4路由处理时CPU利用率:IPv4狂暴IPv6的性能

为什么IPv6路由处理性能没有IPv4好?肯定还有哪里是热点!

于是就用perf top来看咯:

在这里插入图片描述
inet_getpeer又来捣乱!干掉它就是了!Linux内核代码里很多不合理的东西,干掉保持清洁。

inet peer和路由的关系又是什么呢?它怎么会影响移除rt cache后的处理流程呢?带着这样的问题,我重新check了ip6_pol_route这个查找路由的核心函数。原来,我在第一个版本中虽然干掉了rt cache,但是并没有做干净,事情是这样的。

IPv6为每一个接入的源IP/目标IP都保留了一个二元祖作为rt cache项插入到Trie树中,每一项看上去是下面的样子,用ip -6 r l c 可以得到:

在这里插入图片描述
这种项太多了,都会被插入到同一棵树中,这也就是前文提到的soft lockup问题的根源之所在,与此同时,每一个rt cache项还将绑定一个peer结构体,即以目标IP为健值的元素,该peer结构体里面保存一些和该目标IP相关的信息,比如到达它的MTU,到达它的RTT,到达它的CWND等等。

由于peer的很多信息是由端到端的四层协议来使用的,为了解除四层处理和路由处理之间的强耦合,故而peer是单独被管理的,也就是说,内核中除了有一棵IPv6的包含了fib,rt cache等节点的路由树之外,还有一棵peer树,用于管理所有的peer项。

很显然,rt cache和peer在路由处理时,难以避免地要发生关联,让rt cache关联一个peer就再好不过了,这样只需要找到rt cache项,从中取出peer即可,这样就避免了查完rt cache树,再去查peer树。

听起来不错的样子。我自己也设计过一个在nf conntrack项中保存所有需要查找才能得到的信息,比如路由dst entry,socket,arp项等等。

然而,在新项频繁产生的场景下,问题发生了!注意,我们的压测脚本模拟了大量的随机IPv6的客户端瞬间同时接入。我来展示一下发生了什么:

在这里插入图片描述
哈哈,dst use这个冰山下原来还有这么大一坨东西,由于急于消除rt cache插入Trie这件事,没有细看代码,导致了spin lock将CPU微微跑高一点点。

和上一篇文章里描述mount对象和mnt_namespace对象的关系一样,杂糅和不合理的耦合是各种问题的温床。

没关系,干掉它就是了。干掉它的依据有吗?当然有!

  • 形而上意义上的缘由
    IP协议本来就是一个无连接,无session,尽力而为的协议,你为每一个与之通信的IP保存一个peer,并且还cache到路由项里面,这本身就是污染IP协议的行为!peer在IP层的存在,相当于为IP协议增加了一个session!
  • 实际上的现实缘由
    实际上,在3.1内核之前,peer在IPv6路由处理中根本就没有起到过作用。搞笑了。

这上面第二点,里面其实是有一个故事的,先看一个patch再说:
https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git/commit/?id=21efcfa0ff27776902a8a15e810147be4d937d69
有点意思
在这里插入图片描述
这是又一起弄巧成拙的事故!

有意思的是,在3.1内核之前,每一个rt cache绑定一个与cache的源地址相应的peer是IPv6路由代码作者的本意,但是代码可能没有写好,正如上面这个patch说的那样,所有的rt cache项将绑定在同一个健值为0的peer项上,啊哈,这是多么大的一个失误啊!

赶紧改呗,就有了上面的patch。在代码意义上,这当然很完美了,一个rt cache绑定一个peer,一个peer中保存有该peer的metrics,TCP的socket上会绑定一个dst entry,然后顺着就能取到metrics,简直Perfect!

但是,代码不是小说用来给人看的,代码是要在CPU上跑的啊!这么一改不要紧,代码是美了,性能却跪了,原因就是上面我那种图里画的,非常明显的一个性能抖降,却没人管。可笑的是,3.1版本之前IPv6路由性能之所以比3.1之后还要好,靠的居然是一个代码上的错误,这太讽刺了!

值得注意的是,这个inet peer自旋锁问题和前文中rt cache读写锁是两个不同的问题:

  • IPv6 rt cache读写锁问题:2.6.38-rc3引入,David Miller提交
  • IPv6 peer自旋锁问题:3.1.10引入,David Miller提交

既然3.1.10之前IPv6的peer在路由逻辑中就没有起到过作用,那干掉它有问题吗?显然没有问题!又是一个回退式的修补方案。

其实,在我的移除rt cache的第一个修复版本里,路由Trie树里保存的仅仅是配置的路由,和配置的metrics,即便是某一个关联二元组的dst entry关联了特定的metrics,由于rt cache不再插入树,这个metrics也没有地方放,所以,我的方案就是,peer是peer,dst entry是dst entry,一码归一码,二者在路由层面不再纠缠。

peer在IP层真的没有必要,peer是端到端意义上的,你看它的名字和里面的信息就知道,所以说TCP才需要peer,而不是IP。

如果说TCP层需要peer,那么TCP自己维护peer树就好了嘛。

好了,回而退之,这时跑同样的压测,再看perf:
在这里插入图片描述
有点帅,但还不是特别帅,但还是红色,还是两位数,那就继续查!这直接撸代码就好了。原因已经找到:
在这里插入图片描述
根据规范,IPID只要保证四层协议维度的唯一性即可,干嘛搞这么复杂,就取一个协议维度递增的ID,竟然getpeer,竟然不成功还要create,而create就要lock!

我们看看4.20是怎么做的:

在这里插入图片描述
清爽吧。

好了,改之,为我自己绕开rt cache的路由全部加上NOPEER标志,测之:
在这里插入图片描述
这里面的关键在于,注意到3.1版本之前,事实上是不会频繁插入peer项的,因为在查找的时候,尚未初始化目标IP地址,所以永远都能找到那个以0为健的peer项目,但是peer的创建和插入是避免不了的,比如上面的函数,在为IPv6数据报文选取IPID的时候,此时目标地址已经就位,此时就会创建真正的peer项并且插入peer树了,插入就会lock。

加入DST_NOPEER标志之后,便不再执行这个逻辑,再一次解了锁,清爽多了。

锁是令人遗憾的,是锁而不是数据结构的问题限制了Linux内核在多核心系统高效并行,锁对事情严重性的影响远大于数据结构的影响。

数据结构是可以持续优化的,当一个线程在一个非常差劲低效率的数据结构里进行搜索时,CPU可以调度另一个承接关系的线程去干别的,然而对于锁而言,CPU只能stall,却显示很忙,事实上这段时间CPU是在瞎忙!

有没有过面试完了等结果的经历?什么都不想干,什么也干不了,就是忙等,不能安宁。CPU在抢锁时就是这种stall状态!当我们看到CPU利用率很高时,其实这里说的所谓 “利用率” 就有问题了,很多时候,这个高值代表的是 “CPU stall率” ,够讽刺吧!当我们看到有个spin lock在那spin时,显然,我们知道此时CPU高并没有在干正事儿,但是你知道吗,除了显式的spin lock,CPU访问内存也会让CPU短暂stall,积水成渊,CPU的 “利用率” 就高了起来!访存问题导致的CPU stall,需要高效的编程模式,提高cache命中率,但是lock导致的CPU stall,却更加复杂,这需要你对整个逻辑有非常清晰的理解。

举个栗子?,如果你不懂IPv6,但却是个编程狂人,编程神士,你也同样会遭遇路由处理的lock hell,代码写得好,不代表它高效。一般意义上,解锁都要从数据结构开始。

然而,悲哀的是,只要是容器类的数据结构,几乎都无法做到原子插入和删除操作,树这种复杂的数据结构自然不必说,就连最简单的单向链表,一个插入都需要两个步骤才能完成:

  1. 修改前序的next
  2. 将自己的next指向后继
    所以这种操作,锁是必须的。

假设我们已经接受了锁,另一种观点就是锁和数据结构效率之间的此消彼长了。

越简单的数据结构,对于单独的插入操作,指令数越少(比如说单链表,插入只需要两个步骤,也就是两次指针赋值而已),锁造成的CPU stall时间越短,但是,另一方面,越是简单的数据结构,判断一个元素需不需要插入的时间就越长,比如单链表必须每次遍历,而二叉树却可以二分查,这个方面,锁造成的CPU stall的时间越长,和单独的插入步骤简单数据结构带来的收益相比,人们普遍倾向于以复杂数据结构复杂的单独插入为代价(想象一下插入一个树节点需要的步骤),换取查找的收益。这无可厚非。

错误在于, 锁的滥用!不顾一切的滥用! 只要代码美,纠缠无所谓的滥用!不懂业务逻辑只求代码完美导致的锁的滥用!很多人倾向于把什么锁都改成RCU,然而很多人并不真的懂RCU。

在这两起由于不关注流量模式而导致的lock hell事故中,锁的滥用责任不可推卸!

从代码上看,David Miller带来的代码或者其审核通过的代码,绝对是佳作,我们谁不希望用统一的方式处理所有的情况,我们谁不希望可以把所有东西链接在一起,按图索骥获得所需,而不是每次都要查一遍,从代码上看,David Miller带来的东西似乎满足了人们的胃口。

但是,IPv6特殊的流量模式,导致了内核陷入了锁的地狱,IPv6的地址非常多,并且IPv6没有NAT,所以,地址分布会非常广,这足以让数以万计的锁幽灵带着脚镣跳舞了!

说实话,要不是IPv4的NAT为各个服务器汇聚了大量的源地址,这个问题早就在IPv4的场景中显露。

以IPv4和IPv6为维度,对于IPv4,在3.5版本开始了路由处理的净化,而对于IPv6,则在4.2版本开始了同样的过程,晚了一个世代。这其实是和IPv6的部署量以及部署进度紧密相关的。

Linux内核版本升级的过程,其实就是一个解锁的过程。我一般倾向于用自己的方法先搞一遍,然后去核对一下社区的方案,所以说在解了这个peer锁问题并且出了自己的patch之后,我跟踪了一下上游直到4.20版本(如今5.x都已经放出来了)的内核代码,已经超级干净了:

  • 去掉了rt cache(4.2开始就去掉了),并且为每一条配置的路由加入了percpu的copy
  • 解除了rt 和peer之间的关联(没有跟踪commit),直接浅引用路由查询结果的metrics

大致嘛,都是这个思路,路子走对了,就不怕fix自己写的bug。

不过对于我这种不会编程的人而言,一切宗旨就是少改代码,能一行搞定的就不两行,不然就是在写bug…


浙江温州皮鞋?湿,下雨☔️进水不会胖。

2017-04-15 15:46:27 JAZZSOLDIER 阅读数 13065
  • Linux根目录和常见目录

    区块链游戏开发的入门到深入了解 学到linux命令行;EOS环境安装;C++;HTML;CSS;JavaScript;React;Redux等知识。

    0课时 0分钟 13人学习 张云波
    免费试看

可以尝试使用如下三种组合指令:

ctrl-c
向当前进程发送 SIGINT 信号,用于终止一个进程
ctrl-z
向当前进程发送 SIGSTOP 信号,用于挂起一个进程
ctrl-d
不是发送信号,而是表示一个特殊的二进制值,表示 EOF,如果在输入无法结束,提示 ">" 符号(大于号)时,可以尝试按下该组合来结束输入


如果还是无法解决,描述下具体使用场景与环境,可以给我留言或者评论哦~:-D

Linux kernel 配置选项

阅读数 1222