Linux 磁盘IO

Linux 磁盘IO,第1张

磁盘结构与数据存储方式, 数据是如何存储的,又通过怎样的方式被访问?

机械硬盘主要由磁盘盘片、磁头、主轴与传动轴等组成;数据就存放在磁盘盘片中

现代硬盘寻道都是采用CHS( Cylinder Head Sector )的方式,硬盘读取数据时,读写磁头沿径向移动,移到要读取的扇区所在磁道的上方,这段时间称为 寻道时间(seek time) 因读写磁头的起始位置与目标位置之间的距离不同,寻道时间也不同 。磁头到达指定磁道后,然后通过盘片的旋转,使得要读取的扇区转到读写磁头的下方,这段时间称为 旋转延迟时间(rotational latencytime) 。然后再读写数据,读写数据也需要时间,这段时间称为 传输时间(transfer time)

固态硬盘主要由主控芯片、闪存颗粒与缓存组成;数据就存放在闪存芯片中

通过主控芯片进行寻址, 因为是电信号方式, 没有任何物理结构, 所以寻址速度非常快且与数据存储位置无关

如何查看系统IO状态

查看磁盘空间

调用 open , fwrite 时到底发生了什么?

在一个IO过程中,以下5个API/系统调用是必不可少的

Create 函数用来打开一个文件,如果该文件不存在,那么需要在磁盘上创建该文件

Open 函数用于打开一个指定的文件。如果在 Open 函数中指定 O_CREATE 标记,那么 Open 函数同样可以实现 Create 函数的功能

Clos e函数用于释放文件句柄

Write 和 Read 函数用于实现文件的读写过程

O_SYNC (先写缓存, 但是需要实际落盘之后才返回, 如果接下来有读请求, 可以从内存读 ), write-through

O_DSYNC (D=data, 类似O_SYNC, 但是只同步数据, 不同步元数据)

O_DIRECT (直接写盘, 不经过缓存)

O_ASYNC (异步IO, 使用信号机制实现, 不推荐, 直接用aio_xxx)

O_NOATIME (读取的时候不更新文件 atime(access time))

sync() 全局缓存写回磁盘

fsync() 特定fd的sync()

fdatasync() 只刷数据, 不同步元数据

mount noatime(全局不记录atime), re方式(只读), sync(同步方式)

一个IO的传奇一生 这里有一篇非常好的资料,讲述了整个IO过程;

下面简单记录下自己的理解的一次常见的Linux IO过程, 想了解更详细及相关源码,非常推荐阅读上面的原文

Linux IO体系结构

[站外图片上传中...(image-38a7b-1644137945193)]

Superblock 超级描述了整个文件系统的信息。为了保证可靠性,可以在每个块组中对superblock进行备份。为了避免superblock冗余过多,可以采用稀疏存储的方式,即在若干个块组中对superblock进行保存,而不需要在所有的块组中都进行备份

GDT 组描述符表 组描述符表对整个组内的数据布局进行了描述。例如,数据块位图的起始地址是多少?inode位图的起始地址是多少?inode表的起始地址是多少?块组中还有多少空闲块资源等。组描述符表在superblock的后面

数据块位图 数据块位图描述了块组内数据块的使用情况。如果该数据块已经被某个文件使用,那么位图中的对应位会被置1,否则该位为0

Inode位图 Inode位图描述了块组内inode资源使用情况。如果一个inode资源已经使用,那么对应位会被置1

Inode表 (即inode资源)和数据块。这两块占据了块组内的绝大部分空间,特别是数据块资源

一个文件是由inode进行描述的。一个文件占用的数据块block是通过inode管理起来的 。在inode结构中保存了直接块指针、一级间接块指针、二级间接块指针和三级间接块指针。对于一个小文件,直接可以采用直接块指针实现对文件块的访问;对于一个大文件,需要采用间接块指针实现对文件块的访问

最简单的调度器。它本质上就是一个链表实现的 fifo 队列,并对请求进行简单的 合并 处理。

调度器本身并没有提供任何可以配置的参数

读写请求被分成了两个队列, 一个用访问地址作为索引,一个用进入时间作为索引,并且采用两种方式将这些request管理起来;

在请求处理的过程中,deadline算法会优先处理那些访问地址临近的请求,这样可以最大程度的减少磁盘抖动的可能性。

只有在有些request即将被饿死的时候,或者没有办法进行磁盘顺序化 *** 作的时候,deadline才会放弃地址优先策略,转而处理那些即将被饿死的request

deadline算法可调整参数

read_expire : 读请求的超时时间设置(ms)。当一个读请求入队deadline的时候,其过期时间将被设置为当前时间+read_expire,并放倒fifo_list中进行排序

write_expire :写请求的超时时间设置(ms)

fifo_batch :在顺序(sort_list)请求进行处理的时候,deadline将以batch为单位进行处理。每一个batch处理的请求个数为这个参数所限制的个数。在一个batch处理的过程中,不会产生是否超时的检查,也就不会产生额外的磁盘寻道时间。这个参数可以用来平衡顺序处理和饥饿时间的矛盾,当饥饿时间需要尽可能的符合预期的时候,我们可以调小这个值,以便尽可能多的检查是否有饥饿产生并及时处理。增大这个值当然也会增大吞吐量,但是会导致处理饥饿请求的延时变长

writes_starved :这个值是在上述deadline出队处理第一步时做检查用的。用来判断当读队列不为空时,写队列的饥饿程度是否足够高,以时deadline放弃读请求的处理而处理写请求。当检查存在有写请求的时候,deadline并不会立即对写请求进行处理,而是给相关数据结构中的starved进行累计,如果这是第一次检查到有写请求进行处理,那么这个计数就为1。如果此时writes_starved值为2,则我们认为此时饥饿程度还不足够高,所以继续处理读请求。只有当starved >= writes_starved的时候,deadline才回去处理写请求。可以认为这个值是用来平衡deadline对读写请求处理优先级状态的,这个值越大,则写请求越被滞后处理,越小,写请求就越可以获得趋近于读请求的优先级

front_merges :当一个新请求进入队列的时候,如果其请求的扇区距离当前扇区很近,那么它就是可以被合并处理的。而这个合并可能有两种情况,一个是向当前位置后合并,另一种是向前合并。在某些场景下,向前合并是不必要的,那么我们就可以通过这个参数关闭向前合并。默认deadline支持向前合并,设置为0关闭

在调度一个request时,首先需要选择一个一个合适的cfq_group。Cfq调度器会为每个cfq_group分配一个时间片,当这个时间片耗尽之后,会选择下一个cfq_group。每个cfq_group都会分配一个vdisktime,并且通过该值采用红黑树对cfq_group进行排序。在调度的过程中,每次都会选择一个vdisktime最小的cfq_group进行处理。

一个cfq_group管理了7棵service tree,每棵service tree管理了需要调度处理的对象cfq_queue。因此,一旦cfq_group被选定之后,需要选择一棵service tree进行处理。这7棵service tree被分成了三大类,分别为RT、BE和IDLE。这三大类service tree的调度是按照优先级展开的

通过优先级可以很容易的选定一类Service tree。当一类service tree被选定之后,采用service time的方式选定一个合适的cfq_queue。每个Service tree是一棵红黑树,这些红黑树是按照service time进行检索的,每个cfq_queue都会维护自己的service time。分析到这里,我们知道,cfq算法通过每个cfq_group的vdisktime值来选定一个cfq_group进行服务,在处理cfq_group的过程通过优先级选择一个最需要服务的service tree。通过该Service tree得到最需要服务的cfq_queue。该过程在 cfq_select_queue 函数中实现

一个cfq_queue被选定之后,后面的过程和deadline算法有点类似。在选择request的时候需要考虑每个request的延迟等待时间,选择那种等待时间最长的request进行处理。但是,考虑到磁盘抖动的问题,cfq在处理的时候也会进行顺序批量处理,即将那些在磁盘上连续的request批量处理掉

cfq调度算法的参数

back_seek_max :磁头可以向后寻址的最大范围,默认值为16M

back_seek_penalty :向后寻址的惩罚系数。这个值是跟向前寻址进行比较的

fifo_expire_async :设置异步请求的超时时间。同步请求和异步请求是区分不同队列处理的,cfq在调度的时候一般情况都会优先处理同步请求,之后再处理异步请求,除非异步请求符合上述合并处理的条件限制范围内。当本进程的队列被调度时,cfq会优先检查是否有异步请求超时,就是超过fifo_expire_async参数的限制。如果有,则优先发送一个超时的请求,其余请求仍然按照优先级以及扇区编号大小来处理

fifo_expire_sync :这个参数跟上面的类似,区别是用来设置同步请求的超时时间

slice_idle :参数设置了一个等待时间。这让cfq在切换cfq_queue或service tree的时候等待一段时间,目的是提高机械硬盘的吞吐量。一般情况下,来自同一个cfq_queue或者service tree的IO请求的寻址局部性更好,所以这样可以减少磁盘的寻址次数。这个值在机械硬盘上默认为非零。当然在固态硬盘或者硬RAID设备上设置这个值为非零会降低存储的效率,因为固态硬盘没有磁头寻址这个概念,所以在这样的设备上应该设置为0,关闭此功能

group_idle :这个参数也跟上一个参数类似,区别是当cfq要切换cfq_group的时候会等待一段时间。在cgroup的场景下,如果我们沿用slice_idle的方式,那么空转等待可能会在cgroup组内每个进程的cfq_queue切换时发生。这样会如果这个进程一直有请求要处理的话,那么直到这个cgroup的配额被耗尽,同组中的其它进程也可能无法被调度到。这样会导致同组中的其它进程饿死而产生IO性能瓶颈。在这种情况下,我们可以将slice_idle = 0而group_idle = 8。这样空转等待就是以cgroup为单位进行的,而不是以cfq_queue的进程为单位进行,以防止上述问题产生

low_latency :这个是用来开启或关闭cfq的低延时(low latency)模式的开关。当这个开关打开时,cfq将会根据target_latency的参数设置来对每一个进程的分片时间(slice time)进行重新计算。这将有利于对吞吐量的公平(默认是对时间片分配的公平)。关闭这个参数(设置为0)将忽略target_latency的值。这将使系统中的进程完全按照时间片方式进行IO资源分配。这个开关默认是打开的

target_latency :当low_latency的值为开启状态时,cfq将根据这个值重新计算每个进程分配的IO时间片长度

quantum :这个参数用来设置每次从cfq_queue中处理多少个IO请求。在一个队列处理事件周期中,超过这个数字的IO请求将不会被处理。这个参数只对同步的请求有效

slice_sync :当一个cfq_queue队列被调度处理时,它可以被分配的处理总时间是通过这个值来作为一个计算参数指定的。公式为: time_slice = slice_sync + (slice_sync/5 * (4 - prio)) 这个参数对同步请求有效

slice_async :这个值跟上一个类似,区别是对异步请求有效

slice_async_rq :这个参数用来限制在一个slice的时间范围内,一个队列最多可以处理的异步请求个数。请求被处理的最大个数还跟相关进程被设置的io优先级有关

通常在Linux上使用的IO接口是同步方式的,进程调用 write / read 之后会阻塞陷入到内核态,直到本次IO过程完成之后,才能继续执行,下面介绍的异步IO则没有这种限制,但是当前Linux异步IO尚未成熟

目前Linux aio还处于较不成熟的阶段,只能在 O_DIRECT 方式下才能使用(glibc_aio),也就是无法使用默认的Page Cache机制

正常情况下,使用aio族接口的简要方式如下:

io_uring 是 2019 年 5 月发布的 Linux 5.1 加入的一个重大特性 —— Linux 下的全新的异步 I/O 支持,希望能彻底解决长期以来 Linux AIO 的各种不足

io_uring 实现异步 I/O 的方式其实是一个生产者-消费者模型:

逻辑卷管理

RAID0

RAID1

RAID5(纠错)

条带化

Linux系统性能调整:IO过程

Linux的IO调度

一个IO的传奇一生

理解inode

Linux 文件系统是怎么工作的?

Linux中Buffer cache性能问题一探究竟

Asynchronous I/O and event notification on linux

AIO 的新归宿:io_uring

Linux 文件 I/O 进化史(四):io_uring —— 全新的异步 I/O

上文学到 不管什么文件系统类型,都通过VFS(虚拟文件系统层)读和写等 *** 作文件,写文件的元数据和文件的实际数据到磁盘 。但数据是怎么落地磁盘中的呢?落到磁盘中的都经过什么组件?

以一个写数据到磁盘为例,给出Linux I/O子系统的体系结构。

当磁盘执行写入 *** 作时发生的 基本 *** 作 (假设磁盘上扇区中的文件数据已经被读取到分页缓存)。

1) 一个进程通过write()系统调用 VFS虚拟文件系统 请求写一个文件。

2) 内核更新已映射文件的分页缓存。

3) 内核线程 pdflush/Per-BDI flush将分页缓存刷新到磁盘。

4) 同时 VFS虚拟文件系统层 在一个bio(block input output)结构中放置每个块缓冲,并向块设备层提交写请求。

5) 块设备层 从上层得到请求,并执行一个 I/O电梯 *** 作,将请求放置到I/O 请求队列。

6) 设备驱动器 (比如SCSI 或 其他设备特定的驱动器)将执行写 *** 作。

7) 磁盘设备 固件执行硬件 *** 作,如在盘片扇区上定位磁头,旋转,数据传输。

过去的20年中,处理器性能的改进要超过计算机系统中的其他组件,如处理器缓存、物理内存及磁盘等等。 访问内存和磁盘的速度较慢会限制整个系统的性能 ,怎么解决这个问题呢?引入 磁盘缓存机制 ,在较快的存储器中缓存频繁使用的数据,减少了访问较慢的存储器的次数。

磁盘缓存机制有以下3个地方解决:

引入存储层次结构 ,在CPU和磁盘之间放置L1缓存、L2缓存、物理内存和一些其他缓存减少这种不匹配,从而让进程减少访问较慢的内存和磁盘的次数,避免CPU花费更多的时间等待来自较慢磁盘驱动器的数据。

另外一种解决思路: 在更快的存储器上实现更高的缓存命中率,就可能更快地访问数据 。怎么提高缓存命中率呢?引入 参考局部性(locality of reference) 的技术。这项技术基于以下2个原则:

1) 大多数最近使用过的数据,在不久的将来有较高的几率被再次使用(时间局部性)。

2) 驻留在数据附近的数据有较高的几率被再次使用(空间局部性)。

Linux在许多组件中使用这些原则,比如分页缓存、文件对象缓存(索引节点缓存、目录条目缓存等等)、预读缓冲等。

以进程从磁盘读取数据并将数据复制到内存的过程为例。进程可以从缓存在内存中的数据副本中检索相同的数据,用于读和写。

1) 进程写入新数据

当一个进程试图改变数据时,进程首先在内存中改变数据。此时磁盘上的数据和内存中的数据是不相同的,并且内存中的数据被称为 脏页(dirty page) 。脏页中的数据应该尽快被同步到磁盘上,因为如果系统突然发生崩溃(电源故障)则内存中的数据会丢失。

2) 将内存中的数据刷新到磁盘

同步脏数据缓冲的过程被称为 刷新 。在Linux 2.6.32内核之前(Red Hat Enterprise  Linux 5),通过内核线程pdflush将脏页数据刷新到磁盘。在Linux 2.6.32内核中(Red Hat Enterprise Linux 6.x)pdflush被Per-BDI flush线程(BDI=Backing Device Interface)取代,Per-BDI flush线程以flush-MAJOR:MINOR的形式出现在进程列表中。当内存中脏页比例超过阀值时,就会发生刷新(flush)。

块层处理所有与块设备 *** 作相关的活动。块层中的关键数据结构是bio(block input output)结构,bio结构是在虚拟文件系统层和块层之间的一个接口。

当执行写的时候,虚拟文件系统层试图写入由块缓冲区构成的页缓存,将连续的块放置在一起构成bio结构,然后将其发送到块层。

块层处理bio请求,并链接这些请求进入一个被称为I/O请求的队列。这个链接的 *** 作被称为 I/O电梯调度(I/O elevator)。问个问题:为啥叫电梯调度呢?

Linux 2.4内核使用的是一种单一的通用I/O电梯调度方法,2.6内核提供4种电梯调度算法供用户自己选择。因为Linux *** 作系统适用的场合很广泛,所以I/O设备和工作负载特性都会有明显的变化。

1)CFQ(Complete Fair Queuing,完全公平队列)

CFQ电梯调度为每个进程维护一个I/O队列,从而 对进程实现一个QoS(服务质量)策略 。CFQ电梯调度能够很好地适应存在很多竞争进程的大型多用户系统。它积极地避免进程饿死并具有低延迟特征。从2.6.18内核发行版开始,CFQ电梯调度成为默认I/O调度器。

CFQ为每个进程/线程单独创建一个队列来管理产生的请求,各队列之间用时间片来调度,以保证每个进程都能分配到合适的I/O带宽。I/O调度器每次执行一个进程的4个请求。

2)Deadline

Deadline是一种循环的电梯调度(round  robin)方法,Deadline 算法实现了一个近似于实时的I/O子系统。在保持良好的磁盘吞吐量的同时,Deadline电梯调度既提供了出色的块设备扇区的顺序访问,又确保一个进程不会在队列中等待太久导致饿死。

Deadline调度器为了兼顾这两个方面,引入了4个队列,这4个队列可分为两类,每一类都由读和写两种队列组成。一类队列用来对 请求 按 起始扇区序号 进行排序(通过红黑树来组织),称为sort_list;另一类对 请求 按 生成时间进行排序 (由链表来组织),称为fifo_list。每当确定了一个传输方向(读或写),系统都将会从相应的sort_list中将一批连续请求调度到请求队列里,具体的数目由fifo_batch来确定。 只有遇到三种情况才会导致一次批量传输的结束 :1.对应的sort_list中已经没有请求了;2.下一个请求的扇区不满足递增的要求;3.上一个请求已经是批量传输的最后一个请求了。

所有的请求在生成时都会被赋上一个期限值,并且按期限值将它们排序在fifo_list中, 读请求的期限时长默认为500ms,写请求的期限时长默认为5s。 在Deadline调度器定义了一个writes_starved默认值为2,写请求的饥饿线。 内核总是优先处理读请求,当饿死进程的次数超过了writes_starved后,才会去考虑写请求 。 为什么内核会偏袒读请求呢? 这是从整体性能上进行考虑的。读请求和应用程序的关系是同步的,因为应用程序要等待读取完毕,方能进行下一步工作所以读请求会阻塞进程,而写请求则不一样。应用程序发出写请求后,内存的内容何时被写入块设备对程序的影响并不大,所以调度器会优先处理读请求。

3) NOOP

一个简单的FIFO 队列,不执行任何数据排序。NOOP 算法简单地合并相邻的数据请求,所以增加了少量的到磁盘I/O的处理器开销。NOOP电梯调度假设一个块设备拥有它自己的电梯算法。当后台存储设备能重新排序和合并请求,并能更好地了解真实的磁盘布局时,通常选择NOOP调度,

4)Anticipatory

Anticipatory本质上与Deadline一样,但Anticipatory电梯调度在处理最后一个请求之后会等待一段很短的时间,约6ms(可调整antic_expire改变该值),如果在此期间产生了新的I/O请求,它会在每个6ms中插入新的I/O *** 作,这样可以将一些小的I/O请求合并成一个大的I/O请求,从而用I/O延时换取最大的I/O吞吐量。

Linux内核使用设备驱动程序得到设备的控制权。 设备驱动程序 通常是一个独立的内核模块,通常针对每个设备(或是设备组)而提供,以便这些设备在Linux *** 作系统上可用。一旦加载了设备驱动程序,将被当作Linux内核的一部分运行,并能控制设备的运行。

SCSI (Small Computer System Interface,小型计算机系统接口)是最常使用的I/O设备技术,尤其在企业级服务器环境中。SCSI在 Linux 内核中实现,可通过设备驱动模块来控制SCSI设备。 SCSI包括以下模块类型 :

1) Upper IeveI drivers(上层驱动程序)。 sd_mod、sr_mod(SCSI-CDROM)、st(SCSI Tape)和sq(SCSI通用设备)等。

2) MiddIe IeveI driver(中层驱动程序) 。如scsi_mod实现了 SCSI 协议和通用SCSI功能。

3) Low IeveI drivers(底层驱动程序) 。提供对每个设备的较低级别访问。底层驱动程序基本上是特定于某一个硬件设备的,可提供给某个设备。

4) Pseudo drive(伪驱动程序) 。如ide-scsi,用于 IDE-SCSI仿真。

通常一个较大的性能影响是文件系统元数据怎样在磁盘上存放 。引入 磁盘条带阵列 (RAID 0、RAID 5和RAID 6)解决这个问题。在一个条带阵列上,磁头在移动到阵列中下一个磁盘之前,单个磁盘上写入的数据称为 CHUNKSIZE ,所有磁盘使用一次它后返回到第一个磁盘。 如果文件系统的布局没有匹配RAID的设计,则有可能会发生一个文件系统元数据块被分散到2个磁盘上,导致对2个磁盘发起请求 。或者 将所有的元数据在一个单独的磁盘上存储,如果该磁盘发生故障则可能导致该磁盘变成热点 。

设计RAID阵列需要考虑以下内容:

1) 文件系统使用的块大小。

2) RAID 阵列使用的CHUNK大小。

3) RAID 阵列中同等磁盘的数量。

块大小 指可以读取/写入到驱动器的最小数据量,对服务器的性能有直接的影响。块的大小由文件系统决定,在联机状态下不能更改,只有重新格式化才能修改。可以使用的块大小有1024B、2048B、4096B,默认为 4096 B。

stride条带 是在一个chunk中文件系统块的数量。如果文件系统块大小为4KB,则chunk大小为64KB,那么stride是64KB/4KB=16块。

stripe-width 是RAID阵列上一个条带中文件系统块的数量。比如 一个3块磁盘的RAID5阵列 。按照定义,在RAID5阵列每个条带中有1个磁盘包含奇偶校验内容。想要得到stripe-width,首先需要知道每个条带中有多少磁盘实际携带了数据块,即3磁盘-1校验磁盘=2数据磁盘。2个磁盘中的stride是chunk中文件系统块的数量。因此能计算 2(磁盘)*16(stride)=32(stripe)。

创建文件系统时可以使用mkfs给定数量:mk2fs -t ext4 -b 4096 -E stripe=16,stripe_width=64 /dev/vda

1,最多支持4个主分区

2,系统只能安装在主分区

3,扩展分区要占一个主分区

4,MBR最大只支持2TB,但拥有最好的兼容性

1,支持无限多个主分区(但 *** 作系统可能限制,比如windows下最多128个分区)

2,最大支持18EB的大容量(1EB=1024PB,1PB=1024TB)

3,windows7 64位以后支持gtp

1,linux来说无论有几个分区,分给哪一目录使用,它归根结底就只有一个根目录,一个独立且唯一的文件结构,linux中每个分区都是用来组成整个文件系统的一部分。

2,linux采用了一种叫”载入“的处理方法,它的整个文件系统中包含了一整套的文件和目录,且将一个分区和一个目录联系起来。这时要载入的一个分区将使它的存储空间在一个目录下获得。

1,Linux硬盘分IDE硬盘和SCSI硬盘,目前基本上是SCSI硬盘

2,对于IDE硬盘,驱动器标识符为"hdx",其中"hd"表明分区所在设备的类型,这里是指IDE硬盘了。”x“为盘号(a为基本盘,b为基本从属盘,c为辅助主盘,d为辅助从属盘),”~“代表分区,前四个分区用数字1到4表示,它们是主分区或扩展分区,从5开始就是逻辑分区。例,hda3表示为第一个IDE硬盘上的第三个主分区或扩展分区,hdb2表示为第二个IDE硬盘上的第二个主分区或扩展分区。

3,对于SCSI硬盘则标识为"sdx",SCSI硬盘是用”sd“来表示分区所在设备的类型的,其余则和IDE硬盘的表示方法一样。

查看系统的分区和挂载的情况

需求:给linux系统增加一个新的硬盘,并且挂载到/home/newdisk

1,虚拟机添加硬盘

2,分区(fdisk /dev/sdb)

3,格式化(mkfs -t ext4 /dev/sdb1)

4,挂载(mount /dev/sdb1 /home/newdisk)(注:此方式是临时挂载,重启电脑后会没掉)

5,设置可以自动挂载(永久挂载):修改vim /etc/fstab 文件。

查询系统整体磁盘使用情况

查询指定目录的磁盘占用情况

-s 指定目录占用大小汇总

-h 带计量单位

-a 含文件

--max-depth=1 子目录深度

-c 列出明细的同时,增加汇总值

1,统计/home文件夹下的文件的个数

ls -l /home | grep "^-" | wc -l

2,统计/home文件夹下目录的个数

ls -l /home | grep "^d" | wc -l

3,统计/home文件夹下文件的个数,包括子文件夹里的

ls -lR /home | grep "^-" | wc -l

4,统计文件夹下目录的个数,包括文件夹里的

ls -lR /home | grep "^d" | wc -l

5,以树状显示目录结构

yum install tree

tree


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/8473629.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-16
下一篇 2023-04-16

发表评论

登录后才能评论

评论列表(0条)

保存