跳至主要內容

操作系统(core)

holic-x...大约 80 分钟操作系统操作系统

操作系统(core)

学习核心

  • 系统结构
  • 进程管理(*)
  • 内存管理(*)
  • 文件系统
  • 网络I/O(*)
  • Linux命令(*)

(PS:❎ 了解 ⚡️重要)

学习资料

系统结构

1.存储结构

为什么计算机要给储存结构分级?

核心:分级存储结构设计用于适配不同场景的数据存储需求,可以按照不同的访问速度、容量、成本对数据进行管理

不同级别的储存结构具有不同的访问速度,通过分级储存结构,可以将数据按照不同的访问速度、容量和成本要求进行管理

​ 例如:CPU Cache用于存储近期频繁访问的数据,内存用于存储当前执行的程序和数据,而硬盘则用于存储大量数据和长期存储

​ CPU 访问 CPU Cache 的延时只需要几纳秒,访问物理内存的延时则是 100 纳秒,速度相差 100倍,访问磁盘延时就更高了,已经到毫秒级别了,但是访问速度越快的存储器造价成本会高很多,容量也比较小

image-20240827161404436

CPU cache L1L2L3 读取数据的时间量级差距有多大?

​ 三者的差距不大,都是纳秒级别的访问速度

​ CPU访问 CPU CacheL1 的延时大概是 1-2 纳秒,访问 CPU CacheL2 的延时大概是 5-10 纳秒,访问 CPU cacheL3 的延时大概是 10-30 纳秒。

CPU 缓存(CPU cache)对程序性能的影响都体现在哪些方面?

​ 程序具有局部性原理主要体现在两方面:时间局部性、空间局部性

  • 时间局部性:指在相邻的时间里可能会访问同一个数据项(如果一个数据项正在被访问,那么在近期它很可能会被再次访问)
  • 空间局部性:指相邻的空间地址可能会被连续访问(如果一个数据项正在被访问,那么与其地址相邻的数据也很可能会被访问到)

基于程序的局部性原理,它为CPU缓存的设计和优化提供了可能,CPU cache在访问数据的时候不会只从内存读取指定的一个数据,而是会从内存连续加载数据到CPU cache中,则相邻的数据会被缓存在CPU Cache中,程序在访问相邻数据的时候就可以在CPU cache中直接命中而无需访问内存,进而提高访问效率(因为CPU cache的访问速度要比内存快很多,这点设计实际是通过提高CPU cache的缓存命中率来优化访问效率)

2.内核态(重要)

什么是内核?

​ 内核是操作系统的核心组件(可以理解为应用连接硬件设备的”中间人“,应用程序只需要跟内核打交道,而不需要关注硬件的细节),它负责管理系统资源、提供硬件抽象层、调度任务和实现进程间通信等功能。内核是系统中第一个加载的程序,并拥有最高的特权级别,控制整个系统的运行和资源分配。它与用户空间程序进行交互,提供系统调用接口,实现了操作系统的基本功能。

image-20240827163628198

内核态和用户态区别?为什么要区分内核态和用户态?

核心:基于操作指令的安全性考虑,CPU指令分为特权指令和非特权指令。操作系统根据CPU的特权分级机制将进程的运行空间划分为内核态和用户态,内核态拥有最高权限,而用户态只能执行非特权指令(如果需要访问硬件资源等只能通过系统调用的方式,让进程进入到内核才才能访问)。通过划分内核态和用户态可以限制高危指令的操作,进而避免一些程序恶意操作导致系统崩溃的问题

​ 在CPU的所有操作指令中,有些指令是非常危险的,如果错用将会导致整个系统崩溃(例如清空内存、修改时钟等)。因此CPU将指令分为特权指令非特权指令,对于较为危险的指令只允许操作系统本身及相关模块进行调用,而对于用户侧的应用程序而言只能使用那些不会造成危险的指令。操作系统根据CPU的特权分级机制,将进程的运行空间划分为内核空间和用户空间

内核态具有最高权限,可以执行特权指令,直接访问所有系统和硬件资源

用户态不能执行特权指令,只能执行非特权指令,所以只能访问受限的资源,比如不可以直接访问内存、硬盘、网卡等硬件设备,如果要访问这些硬件资源,需要通过系统调用的方式,让进程陷入到内核态才能访问这些特权资源。

为什么要区分内核态和用户态? =》目的就是为了保证系统的稳定性和安全性,通过限制应用程序对特权指令的访问,可以防止恶意程序直接操作危险的指令导致系统崩溃的问题

什么时候会由用户态陷入内核态?

核心:有3种方式程序会从用户态陷入内核态:系统调用、异常、外围设备的中断

  • 系统调用:用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作(这是用户态进程主动陷入到内核态的一种方式)
    • 应用程序执行系统调用时,会触发一个软件中断,进入内核态执行相应的内核代码,完成后再返回用户态
  • 异常:当应用程序发生异常情况(例如访问非法内存、除零错误等),CPU会触发一个异常,将控制权转移到内核态的异常处理程序中
  • 外围设备的中断:当系统接收到外部设备的中断信号(例如硬件设备的输入输出请求、定时器中断等),处理器会中断当前的执行,进入内核态执行相应的中断处理程序

系统调用的过程?

​ 当应用程序发生系统调用时会涉及到用户态和内核态的切换,过程分析如下:

  • 从用户态到内核态:当应用程序使用系统调用时,先将系统调用名称转换为系统调用号,接着将「系统调用号」和「请求参数」放到寄存器里,然后执行软件中断指令(int $0x80 指令),产生一个中断,CPU 陷入到内核态
  • 执行内核态逻辑:CPU 跳转到中断处理程序,先将当前用户态的寄存器(用户态的代码段、数据段、保存参数的寄存器)压入到内存栈中,接着将系统调用号从寄存器里面取出来,最后根据系统调用号,在「系统调用表」中找到相应的系统调用函数进行调用,并将寄存器中保存的参数取出来,作为函数参数
  • 从内核态到用户态:执行完系统调用后,执行中断返回指令(iret 指令),内核栈会弹出之前保存的寄存器将原来用户态保存的现场恢复回来,包含代码段、指令指针寄存器等。这时候 CPU 恢复到用户态,用户态进程恢复执行

​ 一次系统调用过程中会发生两次【CPU上下文切换】(所谓CPU上下文指的是CPU寄存器和程序计数器)

  • 第一次CPU上下文切换:从用户态切换到内核态,CPU寄存器需保存原来用户态的指令位置,然后更新为内核态指令的新位置,跳转到内核态执行内核任务
  • 第二次CPU上下文切换:从内核态切换到用户态,CPU寄存器需恢复原来用户态的指令位置,切换到用户空间,继续运行进程

用户态和内核态是如何切换的?

​ Linux 系统中每个进程都有两个栈,分别是用户栈和内核栈,当应用程序运行在用户态的时候,就会使用用户栈,当应用程序运行在内核态的时候,就会使用内核栈。内核态与用户态的相互切换,其中最重要的一个步骤就是用户栈和内核栈的切换

(1)用户栈到内核栈

​ 执行中断指令(int $0x80 指令),中断发生时,CPU 去一个特定的结构(比如 TSS)中,获取该进程的内核栈的地址信息,也就是内核栈的段选择子和栈顶指针(这两个东西是描述内核栈在内存的哪个地址空间),并分别送入 ss 寄存器和 rsp 寄存器,这时候 CPU 就指向了该进程的内核栈的栈顶位置了,这就完成了用户态到内核态的一次栈的切换

​ 然后,IP 寄存器(指令指针寄存器)跳入中断服务程序开始执行,中断服务程序会把用户态的所有寄存器压入到内核栈中,如下图,CPU 自动地将用户态栈的段选择子 ss3、和栈顶指针 rsp3 都放到内核态栈里了。这里的数字3代表了 CPU 特权级,内核态是 0,用户态是 3。

(2)内核栈到用户栈

​ 当中断结束时,中断服务程序会从内核栈里将 CPU 寄存器的值全部恢复,最后再执行"iret"指令

​ 将 ss3/rsp3 都弹出栈,并且将这个值分别送到 ss 和 rsp 寄存器中,这时候 CPU 就指向了该进程的用户栈的栈顶位置了,这样就完成了从内核栈到用户栈的一次切换

​ 内核栈的 ss0 和 rsp0 也会被保存到前面所说的 CPU 的一个特定的结构(比如 TSS)中,以供下次切换时使用

为什么用户态和内核态的相互切换过程开销比较大

核心:用户态和内核态的相互切换涉及上下文切换、权限切换、地址空间切换、TLB刷新,这些过程会增加额外的性能开销

(1)上下文切换:在用户态和内核态之间切换时,需要保存当前进程或线程的上下文信息,包括程序计数器、寄存器状态、栈指针等。这些上下文信息保存在内存中或者内核数据结构中,在切换完成后需要再次加载回来。这个过程会消耗一定的时间和资源。

(2)权限切换:用户态和内核态具有不同的权限级别,内核态拥有更高的特权级别,可以执行一些用户态不可访问的指令和操作。因此,从用户态切换到内核态需要进行特权级别的变更,这个过程也会增加开销。

(3)地址空间切换:用户态和内核态通常有不同的地址空间,为了保护操作系统内核不受用户程序的直接影响,当切换到内核态时需要进行地址空间的切换和映射,这也会增加开销。

(4)TLB刷新:TLB是CPU中的一种缓存,用于存储虚拟地址到物理地址的映射关系。当发生用户态和内核态的切换时,可能导致TLB中的缓存无效,需要进行TLB刷新,这会引起一定的性能损失。

3.中断

什么是中断?为什么要有中断?

​ 中断机制的好处是化主动为被动,避免 CPU 轮询等待某条件成立。如果没有中断机制,那么“某个条件成立”就需要 CPU 轮询判断,这样就会增加系统的开销。而使用中断机制,就可以在条件成立之后,向 CPU 发送中断事件,强制中断 CPU 执行程序,转而去执行中断处理程序。

什么是硬件中断和软件中断?(❎)

什么是硬中断和软中断?(❎)

为什么要有软中断?(❎)

进程管理(*)

1.进程与线程(*)

进程和线程有什么区别?

核心:从定义、共享资源、上下文切换、安全性这几个方面进行阐述

  • 定义
    • 进程:是运行起来的可执行程序,当运行一个可执行程序时就会创建一个或者多个进程(例如Windows任务管理器中查看程序进程),创建进程需要分配空间(因此也说进程是资源分配的最小单位)
    • 线程:是程序执行的基本单位,每个进程都有唯一一个主线程,主线程和进程是相互依存的关系,主线程结束进程也会结束
  • 共享资源
    • 进程:每个进程有自己的独立地址空间,不和其他进程分享
    • 线程:一个进程可以有多个线程,彼此共享同一个地址空间
  • 上下文切换:进程和线程在进行上下文切换的时候都会发生内核态和用户态的切换
    • 进程:进程间的切换除了需要切换CPU上下文,还涉及页表的切换(影响TLB的命中率),整体表现程序运行变慢
    • 线程:线程间的切换只需要切换CPU上下文,切换成本较低
  • 安全性:并发应用开发可以用多进程或者多线程的方式
    • 进程:多进程不共享资源,开发比较麻烦,但安全性较好。在多进程场景下,一个进程出问题时,其他进程一般不受影响。
    • 线程:多线程由于可以共享资源,效率较高,但存在安全性问题。在多线程场景下,一个线程执行异常中断可能会导致整个进程退出

为什么需要线程?

核心:线程可以理解为轻量级的进程,可以从并发能力、切换开销、通信方便三个方面切入

  • 并发能力:通过引入多线程编程,实现程序的并发执行,提升程序的性能(提高系统的响应速度和资源利用率)
  • 切换开销:线程的切换开销相对于进程而言较小(线程切换只涉及CPU上下文切换,不需要切换页表,也不会影响TLB命中率)
  • 通信方便:同一个进程内多个线程是共享进程内存资源的,线程间的通信比进程间的通信更简单高效

多线程是不是越多越好,太多会有什么问题?

核心:”切换开销“、”死锁问题“=》线程数量越多,切换的开销就越高,发生死锁的概率就越大

​ 多线程不一定越多越好,过多的线程可能会导致一些问题

  • 切换开销:线程的创建和切换会消耗系统资源,包括内存和CPU。如果创建太多线程,会占用大量的系统资源,导致系统负载过高,某个线程崩溃后,可能会导致进程崩溃

  • 死锁的问题:过多的线程可能会导致竞争条件和死锁

    • 竞争条件指的是多个线程同时访问和修改共享资源,如果没有合适的同步机制,可能会导致数据不一致或错误的结果
    • 死锁则是指多个线程相互等待对方释放资源,导致程序无法继续执行

什么时候用单线程,什么时候用多线程呢?

​ 一般单个逻辑比较简单,执行速度相对非常快的场景下可以使用单线程(例如Redis处理命令的实现就是用了单线程,从内存中快速取值,避免锁的开销)

​ 对于一些逻辑比较复杂、等待时间相对较长、需要大量计算的场景,可以通过引入多线程来提高系统的整体性能(例如NIO时期的文件读写操作、图像处理、大数据分析等)

线程共享了进程的哪些资源?

核心:线程共享了进程的虚拟内存空间,文件描述符、还有信号处理器

  • 虚拟内存空间:意味着线程可以访问进程的代码区、堆区、数据区的资源等
  • 文件描述符:意味着线程可以通过文件描述符访问进程打开的文件、套接字等资源
  • 信号处理器:意味着线程可以接收和处理进程收到的信号。

为什么创建进程比创建线程慢?

核心:Linux创建进程需要相应为其分配资源(代码区、堆区、栈区、数据区等),而创建线程则相对简单,只需要确定PC指针和寄存器的值,给线程分配一个栈用于执行程序。Linux中创建一个进程会相应对应创建一个主线程,同一个进程内的线程共享进程的虚拟内存空间,减少了创建虚拟内存的开销,因此创建进程比创建线程慢,且进程的内存开销更大

​ Linux 中创建一个进程自然会创建一个线程,也就是主线程。创建进程需要为进程划分出一块完整的内存空间包含代码区、堆区、栈区、数据区等内存资源

​ 创建线程则简单得多,只需要确定 PC 指针和寄存器的值,并且给线程分配一个栈用于执行程序,同一个进程的多个线程间可以复用进程的虚拟内存空间,减少了创建虚拟内存的开销。因此,创建进程比创建线程慢,而且进程的内存开销更大。

为什么进程的切换比线程开销大?

核心:进程和线程的切换都涉及内核栈和CPU的上下文切换,除此之外进程的切换还涉及页表的切换,因此会影响TLB的命中率,整体表现会比较慢

  • 进程的切换:进程间的切换除了需要切换CPU上下文,还涉及页表的切换以使用新的地址空间(会影响TLB的命中率),虚拟地址转化为物理地址就会变慢,整体表现程序运行变慢
  • 线程的切换:线程间的切换只需要切换CPU上下文,切换成本较低(不会改变虚拟地址空间,不会影响TLB命中率)

补充信息

​ 每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,页表查找是一个很慢的过程,因此通常使用Cache(TLB,translation LookasideBuffer)来缓存常用的地址映射以加速页表查找

线程的上下文切换是怎么个过程?

核心:线程发生上下文切换的时候,会保存当前运行线程的状态,然后恢复另一个线程的上下文

​ 线程发生上下文切换的时候,正在运行的线程会将寄存器的状态保存到内核中的TCB(线程控制块)里,然后恢复另一个线程的上下文。和进程的区别是,线程只需要切换CPU的上下文,不会改变地址空间.

进程有哪些状态?

image-20240828075157684

​ 操作系统理论中的进程状态划分为5种:创建状态、就绪状态、运行状态、阻塞状态、结束状态

  • 创建状态(new):进程new状态,创建之后就会进入ready状态
  • 就绪状态(Ready):当ready状态的线程被调度器选择(被调度)时进入running状态,而被中断(时间片被用完)时又会重新进入ready状态
  • 运行状态(Running):处于ready状态的进程被调度器调度时会进入running状态执行,此时进程持有cpu执行权
  • 阻塞状态(Blocked):当runing状态的进程I/O或事件等待时,会进入waiting状态,当I/O或事件完成时会进入ready状态等待调度器调度
  • 结束状态(Exit):当处于running状态的进程运行退出时会进入终止状态

僵尸进程,孤儿进程,守护进程的区别?

僵尸进程(儿子死了,爸爸没有收尸,儿子就成为僵尸)

​ 僵尸进程是指终止但还未被回收的进程。子进程已终止,但是其父进程尚未调用wait()或waitpid()函数来获取子进程的终止状态,导致子进程的进程描述符仍然保留在系统进程表中,成为僵尸进程(僵尸进程不占用系统资源,但会占用一个进程号)

孤儿进程(爸爸没了,就是孤儿)

​ 孤儿进程是指父进程先于子进程退出或者异常终止,导致子进程成为孤儿进程。孤儿进程会被init进程(进程ID为1)进行接管,init进程会成为孤儿进程的新的父进程

守护进程(daemon)

​ 守护进程是在后台运行的一种特殊进程(独立于控制终端并周期性地执行某种任务或等待处理某些发生的事件),不与任何终端关联,关闭终端并不会影响守护进程的生命周期,守护进程的生命周期通常是伴随系统的启动和关闭,会一直在后台运行

怎么杀死僵尸进程?

危害占用进程号,系统所能使用的进程号是有限的,可能导致不能产生新的进程;占用一定的内存。

如何避免产生僵尸进程:

  • 父进程调用 wait 或者 waitpid 等待子进程结束

  • 子进程结束时,内核会发生 SIGCHLD 信号给父进程。父进程可以注册一个信号处理函数,在该函数中调用 waitpid(),等待所有结束的子进程;也可以用 signal(SIGCLD,SIG_IGN)忽略 SIGCHLD 信号,那么子进程结束后,内核会进行回收

  • 杀死父进程,僵尸进程就会变成孤儿进程,由 init 进程接管并处理( init 进程会成为其新的父进程)

如何查找僵尸进程?

​ 通过指令ps aux | grep Z(状态位带有Z关键字的进程代表僵尸进程),也可使用top命令查看系统中是否有僵尸进程存在(以MAC为例)

image-20240828091528739

​ 正常情况下可以用SIGKILL信号来杀死进程,但是僵尸进程已经死了(不能杀死已经死掉的东西),因此只能通过杀死僵尸进程的父进程来达到杀死僵尸进程的效果,因为当父进程终止的时候,操作系统会将其所有子进程的父进程设置为init进程(即交由init进程接管僵尸进程的回收工作)

​ 因此杀死一个僵尸进程,需要找到对应父进程的PID并kill(SIGKILLkill -9 PPID

# 查找指定进程的父进程
ps -p PID -o ppid

# 杀死父进程
kill -9 ppid

-  ps -p 3003 -o ppid
- output:
	PPID
  2794
- kill -9 2794

并行和并发的区别

  • 并行:同时执行多个任务或操作,利用多个处理单元(如多核CPU)同时进行计算,以提高整体的计算能力和效率
    • 特点:并行计算中,多个任务可以同时进行,彼此之间相互独立
    • 场景:例如将一个大任务拆分为多个子任务,分配给多个处理单元并行执行
  • 并发:多个任务或操作在时间上有重叠,可以交替执行,共享计算资源
    • 特点:并发执行中,多个任务可能会交替执行,由于计算资源有限,任务之间可能需要进行切换和调度
    • 场景:例如在单核CPU上运行多个线程,每个线程在一段时间内执行,然后切换到另一个线程

​ 如果从计算机的角度来说:

  • 并行:多个线程在多核CPU上同时运行,同一时间可以有多个线程同时在执行
  • 并发:多个线程在单核CPU上交替运行,同一时间只能有一个线程在执行
image-20240828094005536

多进程和多线程的区别?

核心:本质是并发场景下多进程和多线程的对比(进程 VS 线程),可以从数据共享、切换开销、可靠性、分布式这几方面说明

​ 每个进程都独立有自己的内存空间,创建和切换进程的开销会比线程大,对于开发而言效率相对较低,但进程间不会相互影响,因此其可靠性也相对较强

​ 同一个进程的线程可复用进程的内存空间,创建和切换线程的开销很小,开发相对简单。但多线程间会相互影响,需相应考虑并发问题

一个进程fork出一个子进程,那么他们占用的内存是之前的2倍吗?

核心:”写时复制“(COW,Copy-On-Write)概念 =》只有在发生写操作的时候,操作系统才会去复制物理内存,否则父子进程的物理内存还是共享状态,并不会增加内存占用

​ 当执行fork操作的时候,创建的子进程复制的是父进程的虚拟内存(而非物理内存),此时父子的虚拟内存指向的是同一个物理内存,并不会增加额外的物理内存(基于此达到节约物理内存资源的目的),但该物理内存的权限会被限定为”只读“

​ 当父进程或者子进程向这个内存发起写操作时,CPU会触发写保护中断(写保护中断是由于违反权限导致的),随后操作系统会在【写保护中断处理函数】中进行物理内存的复制,并重新设置内存映射关系、将父子进程的内存读写权限设置为”可读写“,最后才会对内存进行写操作,这个过程成为写时复制,发生了这个过程,内存占用才会增多

image-20240828100731040

2.协程

什么是协程?

核心:协程是一个轻量级线程,是用户态的线程,协程切换的时候不需要发生用户态和内核态的切换

​ 协程是一个用户态的线程,用户在堆上模拟出协程的栈空间。当需要进行协程上下文切换的时候,主线程只需要交换栈空间和恢复协程的一些相关的寄存器的状态,就可以实现上下文切换。相比于线程上下文切换,没有了从用户态转换到内核态的切换成本。

协程和线程有什么区别?(⚡️)

从调度方式、切换开销、栈大小这三个方面来展开说明区别

调度方式:协程的调度是协作式调度(主动放弃执行,并不会被其他协程打断),当一个协程处理完自己的任务后,可以主动将执行权限让给其他协程。这意味着协程能更好地在规定时间内完成自己的工作,而不会被任意抢占。当一个协作式运行的时间过长,Go 语言调度器才会强制抢占其执行。线程是抢占式调度,当发生中断或者io的时候,cpu就会自动切换别的线程,也就是线程的切换的主动权不再线程自己身上。

切换开销:协程的切换要快于线程,因为协程的切换不用经过操作系统用户态与内核态的切换,并且 Go 语言中协程的切换只需要保留极少的状态和寄存器变量值(SP/BP/PC),而线程的切换会保留额外的寄存器变量值(例如浮点寄存器)。在 Go 语言中,上下文切换速度的一个可参考量化指标是,线程大约为 1~2 微秒,协程大约是 0.2 微秒,比线程快数倍。

栈大小:线程的栈比协程的栈大很多,线程的栈大小一般是在创建时指定,为了避免出现栈溢出(Stack Overflow)的错误,默认的栈大小会相对较大(例如 8M),这意味着创建 1000 个线程就需要消耗 2G 的虚拟内存,这大大限制了线程创建的数量(虽然今天 64 位的虚拟内存地址空间已经让这种限制变得不太严重)。而 Go 语言中的协程栈默认为 2KB,在实践中,我们经常会看成千上万的协程存在。另外,线程的栈在运行时栈不能够更改,而协程栈在 Go 运行时的帮助下可以动态检测栈的大小,进行扩容和收缩。由于协程栈很小,在实践中,协程常被看作是一种轻量级的资源。

协程切换的本质是什么?

核心:协程可以理解为将函数实现成可以任意中断的函数,例如函数1执行到一半,可以直接切换执行别的函数,从整体表现上看就好像多个函数在并发,因此协程的切换类似于函数的调用栈切换

​ 协程的切换本质就是切换CPU寄存器,把当前协程的 CPU 寄存器状态保存起来,比如IP、SP寄存器,然后将需要切换进来的协程的 CPU 寄存器状态加载到 CPU 寄存器上就完成了切换的过程,整个切换过程发生在用户态,不会陷入内核态。

为什么协程切换的开销比线程切换小?进程间通信(⚡️)

核心:从用户态切换、协作式调度方式去对比

用户态切换:协程的切换是在用户态进行的,不需要操作系统的介入。相比之下,线程的切换需要操作系统进行调度和上下文切换,需要从用户态切换到内核态,这涉及到CPU寄存器的保存和恢复等操作,开销较大

协作式调度:协程的调度是协作式的,由协程自身主动让出执行权,而不是被操作系统强制切换。这种调度方式避免了不必要的上下文切换,减少了切换开销。

进程间有哪些通信方式?

核心:管道(有名管道、匿名管道)、消息队列 、共享内存、信号量、信号、socket通信

(1)管道(pipe):单向的以无格式字节流方式传输

  • 有名管道(namedpipe):有文件实体,适用于任何进程的通信,数据可以持久化保存。即使创建它的进程退出,其他进程仍然可以正常使用该管道
  • 匿名管道(anonymous pipe):无文件实体,只能由于父子进程通信。它的生命周期以进程为参考(随着进程的创建而创建,随着进程的消失而消失)

(2)消息队列(message queue):消息队列在内核中通过链表组织消息,克服了管道通信数据是无格式的问题

(3)共享内存(shared memory):共享内存解决了管道和消息队列通信过程中涉及的用户态与内核态的数据拷贝问题,优化通信效率

​ 多个进程可以将各自的虚拟内存空间映射到共享内存,进而实现共享数据的读写,这个过程不会涉及用户态和内核态的数据拷贝,因此共享内存方式也是进程通信效率最高的。但需注意多进程场景下,可能由于多个进程共享内存(竞争同一个资源)而引发数据错乱问题,需要引入同步机制确保多进程的数据读写安全

(4)信号量(semophore):可实现进程间的同步和互斥访问共享资源

​ 信号量本质是一个计数器(表示资源个数):

  • 当一个进程想要访问资源时,会尝试递减信号量(P操作),如果信号量的值大于0则操作会成功,否则进程会阻塞,直到信号量变为正数
  • 当进程完成对资源的访问后,会递增信号量(V操作),允许其他阻塞的进程访问资源

(5)信号(sinal):一种异步的通知机制,用于通知某个事件已经发生

​ 当一个信号发送给一个进程时,操作系统会中断进程的正常流程来优先处理信号,只适用于简单的通知和事件处理

(6)socket通信(套接字):跨主机的进程间通信,可以实现基于TCP或UDP协议的通信方式

​ 前面的通信方式都是只能在本机上进行进程间通信,如果要实现跨主机的进程间通信,则需要借助socket通信方式

哪个进程间通信效率最高的?

核心:共享内存的方式通信效率最高,因为共享内存不涉及"内核态和用户态之间的数据拷贝"

​ 共享内存的本质是拿出一块共享空间,将多个进程的虚拟空间映射到这块共享空间,数据就不用拷贝来拷贝去了,因此大大提高了进程间的通信速度

有名管道和匿名管道的区别?

核心:有无文件实体、适用何种进程、数据持久化、生命周期

  • 有名管道(namedpipe):有文件实体,适用于任何进程的通信,数据可以持久化保存。即使创建它的进程退出,其他进程仍然可以正常使用该管道
  • 匿名管道(anonymous pipe):无文件实体,只能由于父子进程通信。它的生命周期以进程为参考(随着进程的创建而创建,随着进程的消失而消失)

信号和信号量的区别

核心:信号和信号量是两种不同的通信方式,信号量用于解决多进程同步和互斥问题,而信号则是一种异步的通知机制

  • 信号量:本质是一个计数器(表示资源个数),用于解决多继承同步和互斥问题,确保同步进程以安全的方式访问共享资源(保护共享资源,防止多进程同时访问)

    • 当一个进程想要访问资源时,会尝试递减信号量(P操作),如果信号量的值大于0则操作会成功,否则进程会阻塞,直到信号量变为正数

    • 当进程完成对资源的访问后,会递增信号量(V操作),允许其他阻塞的进程访问资源

  • 信号:一种异步的通知机制,用于异步处理异常或者特殊事件

    • 当一个信号发送给一个进程时,操作系统会中断进程的正常流程来优先处理信号,只适用于简单的通知和时间处理

3.调度

进程的调度算法有哪些?

核心:先来先服务、最短作业优先、优先级调度、时间片轮转、多级反馈队列调度

  • 先来先服务:按照进程到达的顺序进行调度,先到达的进程先执行

    • 特点:优势是简单、公平,但可能导致长作业等待时间过长,无法适应实时性要求高的场景
  • 最短作业优先:选择估计运行时间最短的进程优先执行

    • 特点:优势是能够最大程度地减少平均等待时间,但需要准确预测进程的运行时间,实现起来会比较困难
  • 优先级调度:为每个进程分配一个优先级,优先级高的进程先执行。可根据不同的调度策略确定优先级,如静态优先级、动态优先级等

    • 特点:优势是能够根据进程的重要性和紧急程度进行调度,但可能导致优先级低的进程长时间等待
  • 时间片轮转调度:按照时间片的方式轮流调度进程执行,每个进程分配一个固定的时间片,时间片用完后切换到下一个进程

    • 特点:优势是公平,但对于长作业和实时性要求高的场景可能不够高效
  • 多级反馈队列调度:将进程划分为多个队列,每个队列有不同的优先级和时间片大小,每个队列优先级从高到低,同时优先级越高时间片越短,新的进程会被放入到第一级队列的未尾,按先来先服务的原则排队等待被调度,如果在第一级队列规定的时间片没运行完成,则将其转入到第二级队列的未尾,以此类推,直至完成。如果进程运行时,有新进程进入较高优先级的队列,则停止当前运行的进程并将其移入到原队列未尾,接着让较高优先级的进程运行

    • 特点:多级反馈队列调度存在"处理器被优先级较高的队列长时间占用"的问题,导致优先级较低的进程无法得到执行,从而产生饥饿现象

4.锁(*)

线程之间的通信方式?

​ 通信是指线程之间应该如何交换信息,主要有两种机制:共享内存和消息传递

  • 共享内存通信
    • 指线程A和B有共享的公共数据区,线程A写数据,线程B读数据,这样就完成了一次隐式通信

(PS:==如何理解此处的"隐式"概念?==显式就是一发一接,发送方可以知道接收方是什么时候接收到数据(同步概念);隐式更像是一种异步概念,例如快递员将快递放在菜鸟驿站,用户什么时候取快递对快递员来说不知道也不需要关心)

  • 消息传递通信
    • 指线程之间没有公共数据,需要线程间显示的直接发送消息来进行通信

​ **Java主要采用的是第一种共享内存的方式,**所以线程之间的通信对于开发人员来说都是隐式的,如果不理解这套工作机制,可能会碰到各种奇奇怪怪的内存可见性问题。针对消息传递通信方式,Java中的BlockingQueue、SynchronousQueue、Exchanger等可以看作是消息传递通信方式

线程间同步方式有哪些?

​ 同步是指一种用来控制不同的线程之间操作发生相对顺序的机制,同步需要程序员显式的定义,主要是指定一个方法或者一段代码需要在线程之间互斥执行。(Java提供了很多用来做同步的工具,比如Synchronized、Lock等)

Linux提供的线程同步的方式:互斥锁、读写锁、条件变量、自旋锁、信号量

  • 互斥锁:用于保护共享资源,确保同一时间只有一个线程可以访问该资源。只有获得互斥锁的线程才能进入临界区,其他线程需要等待锁的释放
  • 读写锁:也称为共享-独占锁,允许多个线程同时读取共享资源,但在写操作时需要独占访问。读写锁在读多写少的场景中可以提供更好的并发性能
  • 信号量:用于控制对一组资源的访问。信号量可以允许多个线程同时访问资源,但是需要在访问前进行P操作(申请资源)和在访问结束后进行V操作(释放资源),以确保资源的正确使用
  • 自旋锁:是一种忙等待锁,在获取锁之前,线程会一直尝试获取锁,而不会进入睡眠状态。自旋锁适用于保护临界区较小、锁占用时间短暂的情况
  • 条件变量:用于在线程之间进行条件同步。一个线程可以等待某个条件满足,而另一个线程在满足条件时可以通知等待的线程继续执行

信号量和互斥锁应用场景有什么区别?

核心:信号量和互斥锁是线程同步的方式

信号量:用于控制对一组资源的访问。信号量可以允许多个线程同时访问资源,但是需要在访问前进行P操作(申请资源)和在访问结束后进行V操作(释放资源),以确保资源的正确使用

互斥锁:用于保护共享资源,确保同一时间只有一个线程可以访问该资源。只有获得互斥锁的线程才能进入临界区,其他线程需要等待锁的释放

区别:

  • 控制方式:信号量一般以同步方式对共享资源进行控制;互斥锁则是以互斥方式对共享资源进行控制
  • 资源个数:信号量可以控制有限资源的方案;互斥锁只能控制一个资源的访问
  • 加解锁:信号量的可以由一个线程释放,另一个线程得到;互斥锁只能由同一线程进行加解锁处理

自旋锁和互斥锁有什么区别?分别适合哪些应用场景?

核心:从锁类型(定义)、特点(优缺点)、适用场景进行分析

  • 锁类型(定义)
    • 互斥锁:阻塞锁,线程获取锁时如果发现锁已被其他线程持有,则其会被阻塞、释放CPU资源、等待锁的释放
    • 自旋锁:忙等待锁,线程获取锁时如果发现锁已被其他线程持有,它会一直忙等待,不会释放CPU资源
  • 特点(优缺点)
    • 互斥锁:可以避免线程忙等待,减少CPU资源的浪费,但互斥锁的阻塞和唤醒操作带来额外的线程切换和上下文切换的开销
    • 自旋锁:可以减少线程切换和CPU上下文切换的开销,但对于并发竞争时间长或竞争激烈的场景,自旋锁可能会浪费大量的CPU资源
  • 适用场景
    • 互斥锁:适用于并发竞争时间长或者资源争用较激烈的情况
    • 自旋锁:适用于并发竞争时间短暂的情况

悲观锁和乐观锁有什么区别?

核心:从锁类型(定义)、特点(优缺点)、适用场景进行分析

  • 锁定义:

    • 悲观锁:悲观心态(未雨绸缪),认为自己操作资源的时候如果不锁住这个资源则一定会受到其他线程的干扰(认为并发冲突发生的概率很高),进而造成数据错误。因此其在访问资源(获取并修改)的时候就会直接锁住资源,进而确保操作结果的正确性
    • 乐观锁:乐观心态(随遇而安),认为自己操作资源的时候不会受到其他线程的干扰(认为并发冲突发生的概率很低),因此并不会直接锁住被操作对象,并不会不让其他线程去接近临界资源
      • 即在更新之前,会先确认在修改数据期间数据是否已被其他线程修改过,如果没有则说明只有自己在修改(允许正常修改操作),如果发现数据已经被其他线程修改,则放弃这次修改进而选择报错、重试等策略
  • 如何更新数据:

    • 悲观锁:先锁住数据,然后再更新数据,确保数据的正确操作
    • 乐观锁:不锁住数据,而是先更新数据,然后再去验证修改数据这段时间有没有发生冲突,如果没有则操作完成,如果发生冲突则放弃本次操作,并选择报错、重试等策略
  • 适用场景

    • 悲观锁:适用于并发写入多、临界区代码复杂、竞争激烈等场景,可以避免大量的无用的反复尝试等消耗
    • 乐观锁:适用于读多写少的场景,也适用于虽然读写很多但并发竞争不激烈的场景,基于这些场景,乐观锁的优势得以体现

乐观锁怎么实现?

​ 乐观锁的实现一般是基于CAS算法实现(CAS,Compare And Sap 比较并替换)

  • CAS机制核心
    • CAS 基本操作数:VEN =》V(内存地址)、E(期望值)、N(新值)
    • 原理概要:当要更新一个变量时,只有V和E相同时,才会将V更新为N。如果不相等,则会重试

​ CAS操作会比较当前内存值与预期的值,如果相同将V值更新为新值N。如果不同,则说明在修改操作执行期间,有其他线程对内存值V做了修改操作,则不会修改数据,会根据业务逻辑选择报错或重试

操作系统死锁怎么产生的?

​ PS:此处概念中的"多个线程"也可以理解为"多个进程"

死锁场景:多线程并发执行过程中,每个线程都在等待其他线程释放资源,导致所有线程无法继续执行的一种状态

image-20240828132947763

  • 死锁的四个必要条件:(简记:1 V 1、不放、不抢、死循环
    • 互斥条件:一个资源只能被一个进程/线程使用
    • 持有并等待条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
    • 不剥夺条件:进程获得的资源,在未完全使用完之前,不能强行剥夺
    • 环路等待条件:若干进程之间形成一种头尾相接的唤醒等待资源关系

如何避免死锁?

核心:说明死锁如何发生,死锁发生的四个必要条件,破坏其中之一即可避免死锁

​ 当两个或两个以上的线程并发执行因争夺资源而造成互相等待的现象,则称为发生了"死锁"

​ 死锁产生有四个必要条件:互斥、持有并等待、不剥夺、环路等待四个条件,只要破坏其中之一的条件,即可避免死锁

【避免死锁】核心:加"锁顺序"、加"锁时限"、死锁检测

加"锁顺序"分析业务逻辑,设定锁顺序,通过"有序分配法"来破坏环路等待条件

​ 破坏死锁四个必要条件中任意一个就能避免死锁,最常用的方法就是使用资源有序分配法来破坏环路等待条件,比如线程A先对A资源加锁,再对B资源加锁,线程B也使用相同的顺序

image-20240828134445276

加"锁时限"设定锁的超时时限,如果超时则放弃现有资源、择机重试

​ 在尝试获取锁的时候加一个超时时间,如果超时时间到了,该线程则放弃对该锁请求。如果一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行

死锁检测:当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。例如,线程A请求锁B,但是锁B这个时候被线程B持有,这时线程A就可以检查一下线程B是否已经请求了线程A当前所持有的锁。如果线程B确实有这样的请求,那么就是发生了死锁,检测出结果后,就释放锁资源,类似 mysql 的死锁检测机制

发生死锁时,怎么排查?

核心:Java 中通过jstack工具排查死锁,或者借助其他工具分析死锁状态

​ 定位死锁最常见的方式就是利用 jstack 等工具获取线程栈,然后定位互相之间的依赖关系,进而找到死锁。如果是比较明显的死锁,往往 jstack 等就能直接定位,类似 JConsole 甚至可以在图形界面进行有限的死锁检测。

​ 如果是开发自己的管理工具,需要用更加程序化的方式扫描服务进程、定位死锁,可以考虑使用 Java 提供的标准管理 API,ThreadMXBean,其直接就提供了 findDeadlockedThreads() 方法用于定位

内存管理(*)

为什么操作系统会有虚拟内存?(⚡️)

核心:对比多进程运行场景中没有虚拟内存和有虚拟内存的情况进行分析

虚拟内存 VS 物理内存

  • 程序所使用的内存地址称为【虚拟内存地址】
  • 实际在硬件中的内存地址称为【物理内存地址】

​ 操作系统引入虚拟内存,将其与不同的物理内存进行映射,进程可以通过相应的映射关系获取到实际的物理地址,然后访问内存

image-20240828141049208

虚拟内存引入前后

  • 没有虚拟内存时

    • 程序直接操作物理内存的话,多个进程会相互干扰彼此的内存数据,一个进程可能会访问或修改另一个进程的内存数据,从而破坏系统的稳定性和安全性
  • 引入虚拟内存后

    • 进程只能操作虚拟内存,不能操作物理内存,每个进程的虚拟地址空间是相互独立的,进程不能访问其他进程的虚拟地址,以此解决了多进程之间地址冲突的问题

​ 此外,操作系统允许进程使用的虚拟内存的大小可以超过实际的物理内存大小,对于超出的范围(或者一些没有经常被使用到的内存空间)可以把它换出到物理内存之外(比如硬盘上的 swap 区域),需要时再将数据加载到内存中

​ 场景案例分析:多进程场景下,如果希望同时运行QQ音乐和微信

  • 没有引入虚拟内存时:无法同时运行,例如QQ音乐运行时给某个内存地址1xxx赋值后,微信也给1xxx这个内存地址赋值的话就会冲掉原有的配置,进而导致QQ音乐进程崩溃
  • 引入虚拟内存后:每个进程独享自己的一套内存空间,相互之间不受影响,操作系统会提供一种机制将虚拟内存和不同的物理空间进行映射,基于此不同进程写入的时候就会写入不同的物理地址,不会造成冲突

虚拟内存有什么作用?(⚡️)

核心:可以让程序运行内存(虚拟内存)超出物理内存大小、可避免多进程之间地址冲突、可进行权限控制(引出"写时复制"概念)

  • 可以让程序运行内存(虚拟内存)超出物理内存大小:操作系统允许进程使用的虚拟内存超出物理内存的大小,基于“局部性原理”,一些数据如果被访问,则与其相邻的数据在近期也很可能被访问到,因此操作系统会将那些不常用的内存先换到物理内存之外(例如硬盘的swap区域),等到需要使用的时候再加载回来
  • 可避免多进程之间地址冲突:每个进程有自己的页表,独享自己的一套内存空间(每个进程的虚拟内存空间是相互独立的),进程无法访问其他进程的页表(私有化),进而解决了多进程之间的地址冲突问题
  • 可进行权限控制:页表中的页表项中除了物理地址之外,还有一些权限控制的属性(例如控制一个页的读写权限、标记该页是否存在等),基于此设计进一步引入“写时复制”的设计
    • "写时复制":当fork一个子进程时,不直接复制父进程的物理内存,而是复制"虚拟内存"(即复制页表,将其指向和父进程相同的物理内存,并将页表属性设置为"只读"),只有当父子进程需要进行"写"操作的时候,CPU发现页表处于“只读”状态,就会触发【写保护中断】,操作系统才会开辟一块新的物理内存空间,更新进程的地址映射(更新进程的页表和对应的物理内存的关联关系),并将页表属性设置为“可读写”,然后才执行写操作

什么是内存分段?

核心:内存分段是为了适配过程中的一些逻辑需求(例如数据共享、 数据保护、动态链接等)

​ 分段内存管理中,地址是二维的(一维是段号,二维是段内地址),每个段的长度不同(每个段的内部都是从0开始编址)

​ 段表机制概念:分段管理中,每个段内部是连续内存分配,但段与段之间是离散分配的,因此存在一个逻辑地址到物理地址的映射关系,相应就是段表机制概念

段表机制:内存分段管理中,内存在段内是连续分配的,在段与段之间是离散分配的,因此构建一个映射关系用于关联逻辑地址和实际的物理地址

image-20240828150337916

什么是内存分页?

​ 把内存空间划分为大小相等且固定的块,作为主存的基本单位。因为程序数据存储在不同的页面中,而页面又离散的分布在内存中,因此需要一个页表来记录映射关系,以实现从页号到物理块号的映射

​ 访问分页系统中内存数据需要两次的内存访问:

  • 第1次:从内存中访问页表,从中找到指定的物理块号,加上页内偏移得到实际物理地址;
  • 第2次就是根据第一次得到的物理地址访问内存取出数据;

image-20240828151215482

段式管理和页式管理会出现内存碎片吗?

  • 段式管理:无内部碎片,但是有外部碎片
    • 段式管理根据实际需求分配空间,在段内部连续分配内存,不会产生空间浪费,但由于段大小是不固定的,因此段与段之间会产生碎片(外部碎片)
  • 页式管理:有内部碎片,但是无外部碎片
    • 页是固定大小的(通常是4K),即使某段机器码不足4K也要为其分配4K(最小分配单位是一页),因此页内会出现内存浪费(内存碎片),由于页是固定大小,页与页之间是紧密排列的,不会有外部碎片

页面置换有哪些算法?

核心:页面置换算法(确定要被置换的页面,为新页面腾挪空间) =》OPT、FIFO、LRU、Clock、LFU

​ 页面置换算法是用于确定哪些页面应该从物理内存中置换出去,以便为新的页面腾出空间

  • 最佳置换算法(OPT):置换掉未来永远不会被访问的页面
    • 特点:仅限于理论,理论上这种算法效果最好,但是在实际中无法得知未来进程的行为,所以无法实现
  • 先进先出算法(FIFO):置换掉最早进入内存的页面
    • 特点:简单易实现,但可能导致"先进入"的页面是常用页面而被频繁置换,造成性能下降
  • 最近最久未使用算法(LRU):置换掉最近一段时间内最少被访问的页面
    • 特点:给每个页面设置一个时间戳,记录最近一次访问的时间,如果发生缺页错误,则从所有页面中淘汰时间戳最久远的一个
  • 时钟算法(Clock)
    • 每页设置一个访问位,再将内存中的所有页面都通过链接指针链接成一个循环队列。某个页面被访问时其访问位置1。淘汰时,检查其访问位,如果是0就换出、若为1则重新将它置0,然后检查下一个页面,如果到队列中的最后一个页面时,若其访问位仍为1,则再返回到队首再去检查第一个页面
  • 最少使用算法(LFU):置换掉访问频率最少的内存页面
    • 特点:实现方式是对每个页面设置一个访问计数器,每当一个页面被访问时,该页面的访问计数器就累加 1,如果发生缺页错误,淘汰计数器值最小的那个页面

数组的物理空间连续吗?

​ 数组的虚拟地址是连续的,但是物理地址不一定连续(因为虚拟地址映射到物理地址是由操作系统负责的,操作系统并不会保证映射的物理地址也是连续的)

进程的虚拟内存的布局是怎么样的?(⚡️)

​ 操作系统将每个进程的虚拟地址空间划分成两个部分:内核空间和用户空间。内核空间存放的是内核代码和数据,用户空间存放的是用户程序的代码和数据

  • 进程的虚拟内存空间,从高地址到低地址分别是:
    • 内核虚拟内存:所有进程共享内核的代码和数据,独享与进程相关的数据结构栈段:包括局部变量和函数调用的上下文等,栈的大小是固定的,一般是8 MB
    • 文件映射段:包括动态库、共享内存等
    • 堆段:包括动态分配的内存
    • BSS 段:包括未初始化的静态变量和全局变量
    • 数据段:包括已初始化的静态常量和全局变量
    • 代码段:包括二进制可执行代码

栈的增长趋势是什么?

核心:栈是从内存高地址往低地址增长

堆区和栈区有什么区别?

增长方向:栈向低地址方向增长,堆向高地址方向增长

申请回收:栈自动分配和回收,堆需要手动申请和释放

生命周期:栈的数据仅存在于函数运行过程中,堆的数据只要不释放就一直存在

连续分配:栈是连续分配的,堆是不连续的,容易产生内存碎片

空间大小:栈的大小是有限的(如默认8M,Linux上通过 ulimit -s查看),而堆的空间较大,受限于系统中有效的虚拟内存

在栈上的数据操作比堆上快很多的原因?

核心:从内存访问方式、空间局部性两方面进行分析

  • 内存访问方式
    • 访问栈(直接寻址):访问栈上的数据是可以直接访问,因为 CPU 有栈相关的寄存器直接对栈进行访问
    • 访问堆(间接寻址):堆上的数据需要通过指针进行访问,是一个间接寻址的过程,访问速度相对较慢
  • 空间局部性:如果一个数据被访问,则与其空间相邻的数据在未来也有很大可能被访问。因此连续存储的空间可以更好地利用CPU Cache
    • 栈上的数据是连续存在,可以更好的利用 CPU 的 CPU Cache,访问栈上的数据缓存命中率比较高
    • 堆上的数据分布是散乱的,无法充分利用 CPU 缓存,导致缓存命中率较低,访问速度相对较慢

32 位操作系统,4G物理内存,程序可以申请 8GB内存吗?

​ 程序申请的内存实际上是虚拟内存,32位操作系统中用户空间的虚拟内存大小是3GB,进程最多只能申请 3GB 大小的虚拟内存空间,所以进程申请 8GB 内存的话,在申请虚拟内存阶段就会失败

64 位操作系统,4G物理内存,程序可以申请 8GB内存吗?

​ 操作系统允许使用虚拟内存大小超过物理内存大小,对于一些不经常使用的内存空间,可以将其置换到swap分区,提高内存利用率

​ 在 64位 位操作系统,因为进程理论上最大能申请 128 TB大小的虚拟内存,即使物理内存只有 4GB,申请 8G内存也是没问题,因为申请的内存是虚拟内存。如果这块虚拟内存被访问了,要看系统有没有 Swap 分区:

  • 如果没有 Swap 分区,因为物理空间不够,进程会被操作系统杀掉,原因是 0OM(内存溢出)
  • 如果有 Swap 分区,即使物理内存只有 4GB,程序也能正常使用 8GB 的内存,进程可以正常运行

fork 的写时复制是如何实现的?

​ 在fork一个子进程的时候,是先复制一个虚拟内存(而非复制物理内存),调整映射关系将虚拟内存指向父进程的物理内存,并将相应页表状态设置为“只读”。当父子进程发生写操作的时候,CPU会触发“写保护中断”,操作系统会复制一个物理内存(分配新的物理内存并将旧物理页的数据进行拷贝),并更新虚拟内存和物理内存的映射关系,将页表状态设置为“可读写”,最后再进行写操作

malloc会陷入内核态吗?malloc的底层原理是什么?

​ maloc 不是系统调用函数,不会陷入到内核态。

​ malloc 申请内存的时候,会通过 brk 或者 mmap 这两个系统调用来申请内存。为了避免频繁向操作系统申请内存,malloc 还实现了内存池化技术,先申请一大块内存,然后将内存分成不同大小的内存块,然后用户申请内存时,直接从内存池中选择一块相近的内存块即可,这样就减少系统调用的开销

文件系统

1.文件系统相关

读取一个文件的时候,操作系统会发生什么?

核心:读取磁盘文件时,CPU不会负责从磁盘读取数据,而是将这一工作交由DMA控制器;从磁盘读取的数据会缓存在内核中Page Cache(内核缓冲区)

​ 读取磁盘文件的时候,CPU 不会负责从磁盘读取数据的过程,而是将磁盘数据搬运到内存的工作全部交给DMA 控制器,CPU不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的工作,而不会阻塞住。具体过程分析如下:

  • 用户进程调用 read 方法,向操作系统发出 IO 请求,然后进程进入阻塞状态
  • 操作系统收到请求后,进一步将 I/O 请求发送 DMA,然后 CPU 会执行其他任务
  • DMA 进一步将 I/O 请求发送给磁盘,磁盘收到 DMA 的 I/O 请求,把数据从磁盘读取到磁盘控制器的缓冲区中,当磁盘控制器的缓冲区被读满后,向 DMA 发起中断信号,告知自己的缓冲区已满
  • DMA 收到磁盘的信号,将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中,数据准备好后,然后就会发送中断信号给 CPU,CPU 收到 DMA 的信号,于是将数据从内核拷贝到用户空间,read 系统调用返回

​ DMA(Direct Memory Access,直接内存操作)技术:指外部设备不通过CPU而直接与系统内存交换数据的接口技术,数据拷贝过程中不需要CPU干预

读取磁盘文件时,用户进程触发read()系统调用(从用户态切为内核态),CPU不会负责从磁盘读取数据,而是将【从磁盘读取数据到内存】的工作交由DMA处理(这个期间CPU可以去做其他事情),DMA接收到信号则会向磁盘发出I/O请求,磁盘接收请求并将数据从磁盘读取到磁盘控制器的缓冲区,当该缓冲区读满则通知DMA。DMA接收信号,随后将磁盘缓冲区的数据存储到内核中的Page Cache(内核缓冲区),待数据准备好之后,DMA会通知CPU将数据从内核拷贝到用户空间(内核缓冲区拷贝到用户缓冲区),随后read()系统调用返回(从内核态切回用户态)

  • 用户进程触发read()系统调用(从用户态切换到内核态)
  • CPU通知DMA进行数据读取工作(此时CPU可以去做其他事情)
  • DMA进一步将I/O请求发送给磁盘,磁盘接收到信号后将数据从磁盘读取到磁盘控制器的缓冲区,当该缓冲区读满之后,磁盘会告知DMA
  • DMA收到磁盘信号后,会将数据从磁盘控制器的缓冲区拷贝到内核缓冲区(内核中Page Cache),待数据准备好就会通知CPU
  • CPU收到DMA信号后,会将数据从内核拷贝到用户空间(从内核缓冲区 =》用户缓冲区),随后read()系统调用返回(从内核态切换到用户态)

image-20240828160733719

操作系统复制一个文件的流程是怎么样的?

核心:打开源文件、读取数据、打开目标文件、写入数据

​ 打开源文件,调用 read 系统调用读取源文件的数据,磁盘会将数据读取到磁盘控制器的缓冲区,随后 DMA 将磁盘数据拷贝到内核缓冲区, CPU 再将内核缓冲区的数据拷贝到用户缓冲区,最后 read 系统调用返回

​ 打开目标文件,调用 write 系统调用将从源文件读到的数据,写入到目标文件,这时候 CPU 会将用户缓冲区的数据拷贝到内核缓冲区, 然后write函数返回,后续操作系统会将内核缓冲区的数据刷入磁盘

软链接和硬链接有什么区别?

软链接和硬链接都是给文件取一个别名,区别主要有:

  • 软链接文件有独立的 inode 节点,文件的内容是目标文件的路径,不存储实际的文件数据,支持跨文件系统建立,而且目标文件删除了,软链接还是存在的,只不过指向的文件找不到了
  • 硬链接文件和目标文件都共用一个 inode 节点,说明他们是用一个文件,不支持跨文件系统建立,删除文件的时候,要除文件的所有硬链接以及源文件时,系统才会彻底删除该文件。

​ PS:如果是简单理解,“Linux的软链接”接近“Windows下的快捷方式”、“Linux的硬链接”接近“Windows下的拷贝粘贴”概念

网络I/O(*)

1.网络I/O相关

说一下Linux五种IO模型(⚡️)

核心:阻塞IO、非阻塞IO、IO多路复用、信号驱动式IO、异步IO

​ 根据定义,IO模型中:阻塞式IO、非阻塞式IO、IO复用、信号驱动式IO这4种IO模型都是同步IO模型,其中真正的IO操作都会阻塞进程。只有异步IO模型与POSIX定义的异步IO相匹配

IO模型的不同在于分析"数据还没准备好"的情况下会采取什么措施进行处理

  • 阻塞IO:当应用程序执行IO操作时,如果数据还没有准备好,则应用程序就会被阻塞(挂起),直到数据准备好为止,这期间应用程序不能做其他事情

  • 非阻塞IO:当应用程序执行IO操作时,如果数据还没有准备好,操作会返回一个错误/提示,而不是阻塞应用程序。应用程序可以继续执行其他操作,也可以反复尝试该IO操作

  • IO多路复用(事件驱动I/O):应用程序可以同时监控多个IO描述符,当任何一个IO描述符准备好数据时,应用程序就可以对其进行处理。可以在一个单独的进程或线程中同时处理多个IO操作,而不需要阻塞或轮询(select、poll、epoll都是这种模型的实现)

  • 信号驱动式IO:应用程序向操作系统注册一个信号处理函数,当数据准备好时,操作系统会发送一个信号,应用程序成功接收到信号时执行操作(读取数据)。这种模式避免了阻塞和轮询,但是编程的复杂性较高

  • 异步IO:当应用程序执行IO操作时,会发起IO请求,然后就可以去做其他事情,当数据准备好时,操作系统会将数据复制到应用程序的缓冲区,并通知应用程序。该模式的优点在于应用程序不需要等待IO操作的完成,缺点在于编程复杂性高

场景案例对比:装水排队买票

  • 阻塞IO:A用杯子装水,打开水龙头装满水离开的过程。如果说这一过程中水没装满,则A必须等水装满才能离开去做别的事情

  • 非阻塞IO:B用杯子装水,打开水龙头开始装水,然后每隔一段时间就来看看水装好没,不会一直等待。在离开的期间,B可以去做其他事情

  • IO多路复用:C用杯子装水,现有一排水龙头但是还没有水,这个时候C可以离开去做其他事情,等待有人通知水龙头来水(但不确定哪个水龙头有水,需要自己一个个试一下)

    • select模型:C等人通知来水(select),当被告知来水后需要自己一个个试一下,然后用杯子装水(recv)
    • epoll模型:C会被告知哪些水龙头是有水的,不需要自己一个个确认
  • 信号驱动式IO:D特意让其他人有水的时候通知他(注册信号函数),没多久D收到信号就装水

  • 异步IO:E让其他人把水装满后通知他,整个过程中E都可以去做其他事情(没有recv)

如何理解此处的同步、异步概念:

​ 可以将场景中的IO交互过程理解为2个步骤:“排队等待”(等待数据准备就绪)、“装水”(复制数据)

​ 上述场景中有些虽然看起来很像异步,但实际上还是同步IO,因为并没有省去装水的时间,更倾向于在“排队”等待的时候可以不被阻塞(可以去做其他事情),等到可以装水的时候还是要自己动手的。

​ 而真正的异步是将“装水”这个操作全权交给其他人处理(只关心结果不关心过程),发送完指令之后就可以去做其他事情,可能隔段时间询问下操作结果或者操作完成自行通知

区分对比

  • 阻塞同步IO模型和非阻塞同步IO模型的区别在于:进程发起系统调用后,是会被挂起直到收到数据后在返回还是立即返回成功或错误
  • 同步IO和异步IO的区别在于:将数据从内核复制到用户空间时,用户进程是否会阻塞,如果用户进程会阻塞,则是同步IO,如果不会阻塞就是异步IO

阻塞IO和非阻塞IO的应用场景问题,有一个计算密集型的场景,和一个给用户传视频的场景,分别应该用什么IO?

核心:关注场景的瓶颈,根据瓶颈进行择选:计算机密集型关注CPU资源、视频传送关注效率

  • 计算密集型的场景:需要消耗的是 CPU 资源,用阻塞IO会比较好,如果用非阻塞IO,在非阻塞IO模型中用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU

  • 用户传视频的场景:瓶颈不是 CPU 资源,用非阻塞IO比较好,使用非阻塞IO可以避免阻塞在传输函数上提高程序的并发性和响应时间

谈谈对IO多路复用的理解(⚡️)

核心:针对服务端如果要支持并发处理多个客户端IO的场景,IO多路复用引用前存在什么问题?引入后解决什么问题?

​ IO多路复用是Linux的一种同步IO模型,它用于优化服务端支持并发处理多个客户端IO的场景。

​ 在没有引入IO多路复用前(服务端进程:客户端IO连接 = 1 : 1),服务端要并发处理多个客户端IO的场景时,需要通过创建多个子进程(此处概念于子线程同样适用)的方式实现,即针对每一个连接的IO实现需要有一个子进程进行对接。但随着客户端越来越多,意味着服务端需要创建更多的进程,于系统而言开销太大了

​ 在引入IO多路复用后,可以实现多个客户端IO复用一个进程(可以理解为1个进程维护多个socket),即一个进程可以并发处理多个客户端IO事件,进程可以通过select、poll、epoll这类IO多路复用系统调用接口从内核中获取有事件发生的socket集合,然后应用程序可以遍历这个集合,对每个socket事件进行处理

在Redis中单线程也能做到高性能的原因,也是得益于IO多路复用的设计

select、poll、epoll 有什么区别?(⚡️)

核心:理解工作核心流程,说明select/poll 现存缺陷、epoll 引入优势

​ select 和 poll 内部都是使用「线性结构」来存储进程关注的 Socket 集合,在使用的时候,首先需要把关注的Socket 集合通过 select/poll 系统调用从用户态拷贝到内核态,然后由内核检测事件,当有网络事件产生时,内核需要遍历进程关注 Socket 集合,找到对应的 Socket,并设置其状态为可读/可写,然后把整个 Socket 集合从内核态拷贝到用户态,用户态还要继续遍历整个 Socket 集合找到可读/可写的 Socket,然后对其处理

​ select 和 poll 的缺陷在于当客户端越多(即关心的 Socket 集合越大),Socket 集合的遍历和拷贝会带来很大的开销,epoll 则是通过两个方面(在Socket存储和事件扫描)解决了 select/poll 的问题:

  • 内核中引入红黑树存储进程关注的Socket集合:epoll 在内核里使用「红黑树」来关注进程所有待检测的 Socket,减少了内核和用户空间大量的数据拷贝和内存分配
    • 红黑树是个高效的数据结构,增删改一般时间复杂度是 O(logn),通过对这棵黑红树的管理,不需要像 select/poll 在每次操作时都传入整个 Socket集合
  • 引入链表记录就绪事件:epoll 使用事件驱动的机制,内核里维护了一个「链表」来记录就绪事件,只将有事件发生的 Socket 集合传递给应用程序,不需要像 select/poll 那样轮询扫描整个集合(包含有和无事件的 Socket),大大提高了检测的效率

select、poll、epoll 适合哪些应用场景?

核心:可以从select、poll方案的瓶颈进行切入,对比有缺

​ 在连接数较少并且都十分活跃的情况下,选择 select 或者 poll 会比 epoll 性能好,因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll ctl() 进行系统调用,频繁系统调用降低效率

​ 在连接数较多并且有很多的不活跃连接时,epoll 会比 select 和 pol 性能好,因为 epoll 在内核用红黑树来关注所有待检测的 socket,不需要像 select/poll 在每次操作时都传入整个 Socket 集合,减少了内核和用户空间大量的数据拷贝和内存分配,再加上 epoll有就绪队列,也不需要像 select/poll 那样轮询扫描整个集合(包含有和无事件的 Socket),大大提高了检测的效率。

epoll ET 模式和 LT 模式有什么区别?哪一个更高效?

ET 模式和 LT 模式 区别如下:

  • 边缘触发(ET,Edge Trigger):当描述符从未就绪变为就绪时,只会通知一次,之后不会再通知,因此程序要保证一次性将事件处理完
  • 水平触发(LT,Level Trigger):当文件描述符就绪时,会触发通知,如果用户程序没有一次性把数据读/写完,下次还会发出可读/可写信号进行通知

边缘触发的效率比水平触发的效率要高,因为边缘触发可以减少 epoll wait 的系统调用次数

零拷贝技术了解过吗?说一下原理(⚡️)

核心:分析传统文件传输方案的核心和优化方向,引入零拷贝技术解决了什么问题

​ 零拷贝(Zero-Copy)是一种优化数据传输的技术,它减少了在 用户空间和内核空间之间、内核缓冲区和硬件设备之间 的数据拷贝次数。这种技术可以显著提高应用程序的性能,尤其在网络编程和数据存储领域

​ 如果应用程序需要从磁盘读取数据发送到网络,则可以从系统调用(涉及用户态和内核态的上下文切换)和数据拷贝两个角度进行分析

image-20240829103348079image-20240829103734251

  • 传统文件传输的方式:

    • 2次系统调用(write 和 read 函数):期间会发生4次用户态与内核态的上下文切换
    • 4次数据拷贝(2次DMA、2次CPU)
  • 零拷贝技术的文件传输方式:

    • 1次系统调用(sendfile 函数):2次用户态与内核态上下文切换
    • 2次数据拷贝:如果网卡支持 SG-DMA 技术的话,数据拷贝次数会被优化为2次(2次的数据拷贝过程都不需要通过CPU,而是交由DMA 来搬运)

​ Kafka 高性能的优势之一(消息队列IO高吞吐量)也在于其底层使用了的零拷贝技术,优化了数据传输,大大提高性能(提升可数据传输的效率)

高性能网络模式(❎ java可忽略)

高性能网络模式:Reactor 和 Proactoropen in new window

(1)reactor 模式有哪些方案?
(2)proactor 和 reactor 模式有什么区别?

Linux命令(*)

1.系统相关命令

Linux怎么看进程占用的 CPU?

核心:ps、top命令可查看进程占用的CPU

ps命令:ps aux(显示进程的详细信息)、ps aux | grep xxxps -ef | grep xxx

image-20240829105601603

top命令:可以实时跟踪各个进程的相关信息(进程ID、进程名、CPU使用率等),默认情况下按照CPU使用率进行降序排列

image-20240829105621265

Linux怎么查看一个进程的进程号?

核心:ps、top命令可查看进程的进程号(PID)

Linux怎么看进程中的线程?

核心:ps、top命令可查看进程中的线程。先通过命令找到进程的pid,然后根据pid查看对应进程创建的线程信息

  • ps -T -p <pid>:查看进程号为<pid>的进程创建的所有线程

    image-20240829110859723

  • top:可实时查看各个线程的情况,通过top -H -p <pid>查看指定进程内运行的线程状况

    image-20240829135240334

Linux怎么看端口被哪个进程占用了?

核心:netstat、lsof命令(lsof:list open file)

  • netstat -napt | grep <port>:查看指定端口占用情况

    image-20240829135331985
  • lsof -i :<port>

怎么查看一个进程占用的端口号?

核心:netstat、lsof命令

  • netstat -tulnp | grep <pid>:查看指定进程占用端口
  • lsof -i -P -n | grep <pid>:查看指定进程占用端口

​ 先通过netstat/lsof命令输出端口占用信息,然后通过grep进行过滤(筛选出指定进程)

Linux怎么看tcp状态?

核心:netstat、ss命令

  • netstat -nat:显示所有的TCP连接和监听状态

    image-20240829140454187
  • ss -t:显示当前TCP连接状态

    image-20240829140740068

如何判断远端端口是否开启?

核心:nc、telnet 命令探测远端端口

  • nc -zvm3 192.168.1.1 22:探测指定IP的端口是否开启
  • telnet [hostname or ip] [port]telnet 192.168.1.1 22

Linux查看TCP连接数

核心:netstat

  • netstat -ant | grep ESTABLISHED | wc -l:先通过netstat -ant获取所有TCP连接,然后通过grep过滤, 最后通过sc -l进行统计

Linux top 命令有哪些信息?

image-20240829141246348

  • 系统任务统计信息
    • 系统运行时间:显示系统的运行时间
    • 用户信息:显示当前登录系统的用户数量和用户的相关信息
    • Load Average(负载均衡):显示系统在最近1、5、15分钟内的平均负载情况
  • 进程统计信息
    • 运行进程数量:显示当前运行的进程数量和总的进程数量
  • CPU统计信息
    • CPU使用情况:显示总体的CPU使用率以及每个CPU核心的使用率
  • 内存统计信息
    • 内存使用情况:显示物理内存的总量、使用量、空闲量等信息
  • Swap交换分区统计信息
    • Swap使用情况:显示交换空间(swap)的总量、使用量、空闲量信息
  • 进程信息区
    • 进程列表:显示当前运行的进程的详细信息,包括进程ID(PID)、CPU使用率、内存使用率、进程优先级等

​ 主要有系统的负载均衡情况、CPU使用情况、内存使用情况、运行进程数量,还有进程列表,进程列表主要会显示当前运行的进程的详细信息,包括进程ID(PID)、CPU使用率、内存使用率、进程优先级等。

CPU使用率达到100%呢?怎么排查?(⚡️)

核心思路:top定位CPU占用最高的进程 =》top -Hp 定位该进程中CPU占用最高的线程 =》根据线程ID通过jstack打印线程栈信息,跟踪定位排查

​ 首先先通过 top 命令找到占用CPU最高的进程,然后通过 top -Hp 命令找到进程中占用CPU最高的线程,记录这个线程的 id 号,接着通过 jstack 打印这个线程的堆栈信息,通过这些信息定位到具体的代码位置去排查问颖

  • (1)top:查看进程列表信息,定位CPU占用最高的进程
  • (2)top -Hp <pid>:查看指定进程关联的所有线程信息,定位进程中CPU占用最高的线程
  • (3)printf "0x%x\n" <pid>:获取到线程ID号,将其转为16进制(例如printf "0x%x\n" 958=>0x3be
  • (4)jstack:通过jstack指令打印堆栈信息,定位有问题的代码(jstack 163 | grep '0x3be' -C5 --color或者jstack 163|vim +/0x3be -

怎么用top 命令查看是多少个 CPU 核心?

核心:执行完top命令之后,按键盘数字1,可显示服务器有多少个CPU核心

​ 以下述图示为例,查看的CPU核数为为2核

image-20240829142245090

Linux top结果CPU占用会超过100%吗?

核心:top命令显示的是所有 cpu 占用的总数,如果 cpu是多核心的,那么是会观察到 cpu 显示超过 100%的,可以通过按键盘数字 1,来显示每个 cpu 的 cpu 占用率

​ top 显示的 CPU 占用率是所有 CPU 核心占用CPU的数值累加,比如说 CPU1的 CPU 占用率是 80%,CPU2的CPU 占用率是 80%,那么 top 显示的 CPU 占用率就是 160%

​ 即如果是2核CPU则通过TOP指令查看显示CPU占用率最高可达200%,同理4核就是可高达400%,因此Linux中top结果显示的CPU占用率是可以超过100%的,可以通过数字键盘1来显/隐CPU信息

Linux如何查看内存使用情况?

核心:free命令

image-20240829143718307

​ 使用free命令查看操作系统的内存情况

totalusedfreesharedbuff/cacheavailable
Mem总内存已使用内存空闲内存被共享使用的内存被缓冲和缓冲的内存当前可用内存
Swap
  • total :表示系统总共的内存大小
  • used:表示已使用的内存大小
  • free:表示空闲的内存大小
  • shared:表示被共享使用的内存大小
  • buff/cache:表示被缓存和缓冲的内存大小
  • available:表示系统当前可用的内存大小

Linux怎么查看磁盘剩余多少

核心:df命令

​ 通过df -h显示磁盘空间的使用情况(包括已使用、可用、总共的磁盘空间)

image-20240829144621999

Linux 服务器当中如何查看负载情况?通过什么指标进行查看?

核心:top、uptime 命令

​ 可以通过 top 命令或者 uptime 命令,查看当前系统的负载情况

​ 数据显示会有一个平均负载的情况,主要有三个数字(依次则是过去1分钟、5分钟、15分钟的平均负载),可以通过观察这三个数字的大小,可以简单判断系统的负载是下降的趋势还是上升的趋势

指令操作分析

image-20240829145200415

load average的3个数字分别表示过去1、5、15分钟的平均负载,通过观察这些数据的大小,可以简单判断系统负载的升降趋势

​ 平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数。所以,它不仅包括了正在使用 CPU 的进程,还包括等待 CPU 和等待 IO 的进程。

​ 而 CPU 使用率,是单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应。例如:

  • CPU 密集型进程,使用大量 CPU 会导致平均负载升高,此时这两者是一致的;
  • IO密集型进程,等待IO也会导致平均负载升高,但 CPU 使用率不一定很高;
  • 大量等待 CPU 的进程调度也会导致平均负载升高,此时的 CPU 使用率也会比较高;

2.文件相关命令

Linux查看文件的命令有哪些?

核心:cat、more、less、head、tail

  • cat 命令:显示整个文件内容
  • more命令:逐页显示文件内容(空格键向下翻页)
  • less命令:和more功能类似,但提供更多功能(例如向前、向后翻页、搜索等)
  • head命令:显示文件的开头部分(默认显示头部10行)
  • tail命令:显示文件的末尾部分(默认显示末尾10行)

Linux查看文件大小命令

核心:ls -l、du -h、stat

  • ls -l:显示文件的详细信息(包括文件大小,以字节为单位)
  • du -h:显示目录或文件的大小(以人类可读的方式,例如KB、MB单位)
  • stat:显示文件的详细信息(包括文件大小和其他属性)

Linux查询当前所在目录的语句

核心:pwd

  • pwd:显示当前工作目录的绝对路径

Linux创建文件夹和文件的语句是什么?

核心:mkdir 创建文件夹、touch 创建文件

Linux如何删除一个文件?

核心:rm 命令

Linux如何删除一个目录(文件夹)?

核心:rm -r

  • rm -r <directory_name>:递归删除目录

Linux怎么创建、复制、移动一个文件?

  • touch:创建一个文件
  • cp:复制一个文件(cp a.txt a_01.txt
  • mv:移动一个文件(mv /home/test1/a.txt /home/test/mv <源文件> <目标路径>

Linux cp 命令怎么复制整个文件夹?

核心:cp -r

  • cp -r:递归复制整个文件夹

Linux如何文件重命名

核心:mv

  • mv:用mv移动命令来重命名文件

Linux 文件夹中如何查看最近被修改的文件?

核心:ls -lt

  • ls -ltls列出文件和目录,-l以列表方式显示文件和目录的详细信息,-t按照文件修改时间排序(最近修改的文件会显示在最上面)

Linux怎么修改文件的权限?

核心:chmod

  • chomd:用于更改文件或目录的访问权限,chmod [选项] 权限模式 文件名
    • 选项
      • -c:显示修改的详细信息;
      • -R:递归地修改目录及其子目录下的文件权限
    • 权限模式 (可以使用数字或符号)
      • 数字方式:
        • 设置对象:使用u(所有者)、g(所属组)、o(其他人)、a(所有用户)表示权限的对象
        • 每个权限用一个数字表示,分别对应权限为:读(r)=>数字4、写(w)=>2、执行(x)=>1
          • 将这三个数字相加,即可得到对应的权限模式 =》O(rwx)G(rwx)U(rwx)
          • 如果不设定某个权限,则对应位置设为-
        • 例如:权限模式为rwxr-xr--可以用数字表示为754,即u(rwx)g(-xr)o(r--)
      • 符号方式
        • 设置对象:使用u(所有者)、g(所属组)、o(其他人)、a(所有用户)表示权限的对象
        • 执行操作:+、-、=表示添加、删除或设置权限
        • 例如:将文件的所有者权限设置为读写,可以使用命令 chmod u+rw <filename> ,如果不指定权限对象则默认所有对象都加

chmod+x是给哪个属性赋予权限

​ 给所有用户赋予执行权限

Linux中如何查找一个文件

核心:find

  • find <搜索目录路径> -name <filename>:使用find命令查找指定目录的文件

Linux 怎么查看实时滚动日志?

核心:tail

cat是读取文件所有数据,如果日志文件很大,执行该指令可能会影响系统性能,因此可以选用tail读取文件尾部的内容,只显示部分数据

  • tail -n 5 <filename>:查看末尾5行的日志信息
  • tail -100f <filename>:可以实时查看日志信息,当查看日志的时候是阻塞状态,当有新日志输出的时候就会实时滚动

查找一个字符串是否在文件中

核心:grep

​ 通过grep命令查找一个字符串是否出现在文件中

  • grep <searchTarget> <filename>:在单个文件中搜索字符串
  • grep -n <searchTarget> <file1> <file2> <file3>:在多个文件中搜索字符串,并显示匹配的行号
  • grep -rl <searchTarget> <目录名>:递归地在指定目录及其子目录的文件中搜索字符串,并仅显示包含匹配字符串的文件名

查看文件行数命令

核心:wc

​ 通过wc -l命令,查看文件行数

统计一个文件中某个字段的次数

核心:grep 搭配 wc命令

  • grep -o <searchTarget> <filename> | wc -l:先grep检索文件中指定的字符串,然后通过wc进行数据统计

替换一个文件中的字符串

核心:sed

  • sed -i 's/旧字符串/新字符串/g' 文件名:例如sed -i 's/Hello/Hi/g' example.txt表示将文件example.txt文件中的Hello字符串替换为Hi字符串
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3