questions

基础概念

  • 线程安全(Thread Safety):

当多个线程访问某个类或者对象时,如果不需要额外的同步机制或者用户干预,这个类或者对象依然能够表现出正确的行为,那么它就被认为是线程安全的。
简而言之,线程安全意味着在多线程环境下,对象的状态不会发生不一致或者不正确的情况。

  • 线程不安全(Thread Unsafe):

相反,当多个线程访问某个类或者对象时,如果没有适当的同步措施,就可能导致对象的状态变得不可预测或者不正确,那么这个类或者对象就是线程不安全的。
在线程不安全的情况下,多个线程可能会在同一时间访问、修改相同的数据,这可能导致数据损坏或者不一致。

用户态和内核态

用户态(User Mode) : 用户态运行的进程可以直接读取用户程序的数据
内核态(Kernel Mode):内核态运行的进程几乎可以访问计算机的任何资源包括系统的内存空间、设备、驱动程序等

如何切换用户态和内核态

  • 系统调用:比如open close write read,还有一堆IO函数
  • 中断:当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号
  • 异常:缺页异常等
    1
    2
    3
    4
    1. 系统调用:是一种软中断处理程序,用于让程序从用户态陷入内核态,以执行相应的操作
    流程:中断,保存现场,陷入内核态,执行完恢复现场(上下文),从保存的地址开始继续执行(产生中断的那一句)

    2. 中断分软和硬中断,由中断向量表(中断号-中断程序入口地址)和中断处理程序完成。

进程上下文包括哪些

  • 进程控制块 PCB
  • CPU寄存器状态
  • 程序计数器

进程和线程的区别

  • 进程是系统资源分配的基本单位,一个程序对应一个进程。每个进程都有自己独立的内存空间,相互之间不会共享。它包括了程序代码、内存空间、资源和文件等。

  • 线程是进程中的执行单元,一个进程可以包含多个线程。线程共享了进程的内存空间和资源,可以同时执行不同的任务。

共享资源指的是哪些资源

内存、全局变量、对象、文件等

协程和线程的区别

协程(goroutine)可以理解为轻量化的用户态线程的实现,区别是
协程由goruntime进行调度,线程由操作系统内核调度

进程切换:

  • 保存当前进程上下文:当操作系统决定要切换到另一个进程时,首先会保存当前进程的上下文信息,包括寄存器的值、程序计数器(PC)等。
  • 加载目标进程的上下文:接下来,操作系统会从进程调度队列中选择一个新的进程,将其保存的上下文信息加载到 CPU 寄存器中,以便开始执行该进程。
  • 切换页表:在多进程的环境下,每个进程都有自己独立的地址空间(虚拟内存),因此在切换进程时,需要将当前进程的页表(用于地址映射)切换为目标进程的页表。
  • 恢复执行:一旦目标进程的上下文信息被加载,CPU 会从目标进程的上次停止的位置继续执行。

与线程切换的区别:

  • 线程切换发生在用户态,进程切换发生在内核态。
  • 线程切换相对于进程切换来说更加轻量级,因为线程共享了同一地址空间和其他资源,上下文切换的开销较小。

进程间的通信方式

  • 共享内存
  • 管道 半双工
  • 信号 signal ,用于处理异步事件,如外部中断、错误、异常等情况
  • 信号量 semaphore(mutex)实现进程间的互斥与同步,int计数器
  • 消息队列 保存在内核中的消息链表
  • socket

    线程同步的方式有哪些

  • 互斥锁
  • 读写锁
  • 自旋锁 适用于锁被持有时间较短的情况:线程尝试获得锁时,如果锁已被占用,线程会一直忙等待直到锁被释放
  • 信号量
  • 条件变量
  • 原子操作

    fork子进程与父进程的区别

    在调用 fork() 函数时,操作系统会创建一个新的进程,这个新的进程称为子进程。子进程是父进程的一个复制,它将继承父进程的内存空间、文件描述符等资源。
    fork之后,子进程和父进程之间是完全独立进行,互不干扰。

死锁定义& 产生条件

定义:
死锁(Deadlock)是指在多个进程或线程之间,每个进程或线程都在等待一个事件,而这个事件只能由其他等待的进程或线程触发,从而导致所有进程或线程都无法继续执行的一种状态。

产生条件

  • 互斥(Mutual Exclusion):资源只能被一个进程或线程占用,如果资源被占用,其他进程或线程必须等待。
  • 持有和等待(Hold and Wait):一个进程或线程可以在持有某个资源的同时等待其他资源,这时如果其他资源被占用,就会导致死锁。
  • 不可剥夺(No Preemption):资源不能被强制从一个进程或线程中抢占,只能由占用它的进程或线程显式释放。
  • 循环等待(Circular Wait):多个进程或线程之间形成一个环路。

进程调度策略

  • 先来先服务
  • 短进程优先
  • 时间片轮转
  • 优先级队列
  • 多级反馈队列

    页面置换算法

  • 先进先出
  • 最近最久未使用(手撕)

磁盘调度

  • 先来先服务
  • 最短寻道优先
  • 扫描算法

    分页是什么,内存页面

    分页是操作系统中一种内存管理技术。

优势

  • 虚拟内存的实现:允许程序使用比实际物理内存更大的地址空间。
  • 分页策略:可以实现页面置换算法,将不常用的页置换到磁盘上。
  • 内存保护:可以将页设置为只读或只执行,从而保护程序的关键部分不被修改。
  • 内存共享:多个进程可以共享相同的物理页,减少了内存的占用。

分段是什么,存在碎片问题

与分页不同,分段不将内存划分为固定大小的块,而是将其划分为逻辑上相关联的段,每个段的长度可以不同。

优势

  • 更灵活的内存管理:可以根据程序的需要分配不同大小的段,更好地利用内存。
  • 更好的地址空间划分:可以将程序的不同部分(如代码、数据、堆栈等)放置在不同的段中,提高了程序的可读性和可维护性。
  • 内存保护:可以通过设置段的权限(如只读、读写等)来保护程序的关键部分,防止非法访问。
  • 共享和动态加载:不同程序可以共享相同的段,也可以在运行时动态加载和卸载段。

使用场景

  • 动态链接库(DLL):在操作系统中,动态链接库通常会以独立的段来存放,这样可以在运行时被加载和卸载。
  • 程序的逻辑结构:将程序的不同部分(如代码段、数据段、堆栈段等)放置在不同的段中,可以使程序的逻辑结构更清晰,提高可读性和可维护性。
  • 内存保护:可以通过设置段的权限来保护程序的关键部分,防止非法访问。例如,将代码段设置为只读,防止在运行时修改代码

虚拟内存

通过将程序的逻辑地址空间映射到物理内存或磁盘上的存储空间来实现。
实现技术:分页和分段技术

  • 隔离进程:物理内存通过虚拟地址空间访问,虚拟地址空间与进程一一对应。每个进程都认为自己拥有了整个物理内存,进程之间彼此隔离,一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。
  • 提升物理内存利用率:有了虚拟地址空间后,操作系统只需要将进程当前正在使用的部分数据或指令加载入物理内存。
  • 简化内存管理:进程都有一个一致且私有的虚拟地址空间,程序员不用和真正的物理内存打交道,而是借助虚拟地址空间访问物理内存,从而简化了内存管理。
  • 多个进程共享物理内存:进程在运行过程中,会加载许多操作系统的动态库。这些库对于每个进程而言都是公用的,它们在内存中实际只会加载一份,这部分称为共享内存。
  • 提高内存使用安全性:控制进程对物理内存的访问,隔离不同进程的访问权限,提高系统的安全性。
  • 提供更大的可使用内存空间:可以让程序拥有超过系统物理内存大小的可用内存空间。这是因为当物理内存不够用时,可以利用磁盘充当,将物理内存页(通常大小为 4 KB)保存到磁盘文件(会影响读写速度),数据或代码页会根据需要在物理内存与磁盘之间移动。

如果没有虚拟内存的话

  1. 容易爆内存,内存不够用
  2. 浪费内存,内存崩溃等,根据局部性原理

局部性原理

时间局部性:如果一个数据项被访问,那么在不久的将来它很可能再次被访问。这意味着程序往往会重复使用相同的数据。
空间局部性:如果一个数据项被访问,那么在它附近的数据也很可能会被访问。这意味着程序往往会以块或连续区域的方式访问数据。

TLB是什么快表,或者叫cache

缓存了虚拟页号到物理页号的映射关系,你可以将其简单看作是存储着键(虚拟页号)值(物理页号)对的哈希表

1
2
3
4
5
6
使用 TLB 之后的地址翻译流程是这样的:

1. 用虚拟地址中的虚拟页号作为 key 去 TLB 中查询;
2. 如果能查到对应的物理页的话,就不用再查询页表了,这种情况称为 TLB 命中(TLB hit)。
3. 如果不能查到对应的物理页的话,还是需要去查询主存中的页表,同时将页表中的该映射表项添加到 TLB 中,这种情况称为 TLB 未命中(TLB miss)。
4. 当 TLB 填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一

交换空间(Swap分区)

当物理内存不足时,操作系统会将一部分不常用的数据(可能是程序的代码、数据等)从物理内存移动到磁盘上的交换文件(Swap File)或者交换分区(Swap Partition)中。这样,物理内存就会腾出空间来运行当前活动的程序。当需要访问被移到交换空间的数据时,操作系统会将其再次加载到物理内存中。

缓冲区泄漏

当程序向缓冲区写入超过其容量的数据时,会覆盖相邻内存区域的数据或者控制程序的执行流程,从而可能导致程序崩溃或者执行意外的行为。

  • 程序崩溃:如果溢出的数据覆盖了程序关键的数据或者控制信息,可能会导致程序崩溃或异常终止。
  • 提权漏洞:如果一个程序以特权用户或系统管理员的身份运行,并且存在缓冲区溢出漏洞,攻击者可以利用这个漏洞来提升自己的权限,获取对系统的控制权。
  • 执行恶意代码:攻击者可以通过精心构造的输入数据来覆盖程序的返回地址或者函数指针,从而强行将程序执行流程转移到恶意代码所在的地址,实现远程代码执行攻击。
  • 信息泄露:攻击者可以利用缓冲区溢出漏洞来读取程序内部的数据,可能包括敏感信息、密码等。

僵尸进程和孤儿进程

僵尸进程:子进程结束,父进程不知道,导致子进程的资源没被释放

定义:僵尸进程是已经结束执行的子进程,但其父进程尚未调用wait()或waitpid()系统调用来获取子进程的退出状态,因此子进程的资源(如进程表项、文件描述符等)尚未被完全释放。

状态:僵尸进程处于”Z”状态(在ps命令中以”Z”标识),找到Z状态kill掉。

1
2
3
ps -aux | grep Z
kill -s SIGCHLD <PID>
#SIGCHLD信号是一个用于通知父进程子进程状态变化的信号。通过向僵尸进程发送这个信号

危害:僵尸进程占用了系统资源(如进程表项),如果大量的僵尸进程积累,可能会导致系统资源不足。

孤儿进程:父进程结束,子进程还在执行,会被init进程接管

定义:孤儿进程是指一个子进程的父进程提前结束了,而子进程还在继续运行。此时,子进程会被init进程(PID为1)接管,成为init的子进程。
状态:孤儿进程的状态正常,不会变成僵尸进程,因为init进程会负责回收孤儿进程的资源。
危害:一般情况下,孤儿进程并不会造成严重问题。但如果大量的孤儿进程在系统中运行,可能会占用系统资源,因此最好的做法是在父进程退出前,确保它的子进程已经正确地结束。

重点:IO多路复用

IO Multiplexing是指通过一种机制同时监听多个文件描述符(sockets、文件、设备等),当其中任意一个文件描述符就绪(可读、可写或异常)时,就可以对其进行相应的处理,从而提高了系统的性能和响应速度。

  • select 轮训,在一个数组中注册多个文件描述符,数据量少时效果好
  • 数据结构:数组
  • poll 轮训,数据量多时效果好
  • 数据结构:链表
  • epoll 使用了事件驱动的方式来管理文件描述符,只有当文件描述符真正就绪时才会通知应用程序
  • 数据结构:红黑树

优点:相对于select和poll,epoll的性能更高,因为它采用了红黑树的数据结构来管理文件描述符,可以处理大量的文件描述符,并且在文件描述符就绪时会立即得到通知。

硬连接 软连接

硬连接

  • 硬链接与原文件共享同一个索引节点,具有相同的 Inode 号。
  • 不能连接目录,防止形成环
    软连接
  • 软链接是一个单独的文件,其中包含了指向目标文件的路径信息。
  • 能连接目录
  • 删除源文件,软连接失效,硬连接还嫩用,因为原来的Inode还在用

中断的处理过程

  1. 中断触发,事件可能包括定时器结束,系统调用,异常等
  2. 中断请求,向CPU发送通知,我要处理一个终端
  3. 中断控制器(Interrupt Controller)将中断请求映射到一个中断向量,对应了中断处理程序的入口地址,是映射关系,类似于函数指针。
  4. 根据中断向量表将中断向量映射到实际的中断处理程序的入口地址。
  5. 保存上下文
  6. 执行中断服务程序
  7. 关中断
  8. 恢复现场后,重新执行产生中断的那一句指令

中断和轮训

  1. 触发条件不同,中断是被动条件触发(定时器结束、产生异常),轮训是主动查询资源状体啊
  2. 响应能力不同,中断实时发生,论文有延迟
  3. 轮训同步,中断异步

零拷贝

零拷贝是一种优化数据传输的技术,它通过减少或消除数据在内存之间的复制过程来提高传输效率,从而在高性能的数据传输场景中发挥重要作用。

  1. 传递文件描述符
  2. 允许内核缓冲区直接访问
  3. DMA
  4. sendfile() 将一个文件描述符的内容发给另一个
  5. mmap()文件映射到内存

Redis:
Redis 通过使用 sendfile 系统调用,实现了零拷贝技术,可以在文件传输时避免数据在用户空间和内核空间之间的多次拷贝。

Kafka:
Kafka 在数据的生产者和消费者之间使用了零拷贝技术。生产者将数据直接写入内核缓冲区,消费者从内核缓冲区直接读取数据,避免了中间的数据拷贝过程。

堆和栈的区别

分配管理

  • 栈由编译器管理,自动分配和释放内存,函数调用时分配,并在函数返回时自动释放。
  • 堆通常用于存储动态分配的数据,由程序员手动管理内存的分配和释放。malloc()、calloc()、realloc()

存储内容
栈用于存储函数的局部变量和控制信息
堆用于存储动态分配的数据,如动态数组、对象等。
大小和位置

  • 栈的大小是固定有限的,由系统在程序启动时分配,每个函数调用时,都会创建一个称为帧(Frame)的区域来存储这些信息
  • 堆大小不固定,位置不固定

CPU占用率 使用率 负载

CPU占用率、使用率和负载是监控和评估计算机系统性能的三个不同方面。

CPU占用率(CPU Usage):
CPU占用率是指计算机CPU处理任务的效率和利用率。它通常以百分比的形式表示,表示CPU正在执行任务的时间占总时间的比例。例如,一个CPU占用率为50%的系统表示CPU一半的时间用于处理任务,一半的时间处于空闲状态。

CPU使用率(CPU Utilization):
CPU使用率是指实际用于处理计算任务的CPU时间与总时间的比例。它反映了CPU的工作效率,是一个反映计算机性能的重要指标。CPU使用率可以分为用户态使用率、内核态使用率等不同类型。

负载(Load Average):
负载是一个相对于一段时间内系统CPU运算负荷的指标。通常以三个数值表示,分别表示系统在过去1分钟、5分钟和15分钟内的负载情况。例如,一个负载为1.0的系统表示在平均1分钟内,有一个任务在运行。

区别总结:

CPU占用率关注的是CPU执行任务的效率,以百分比表示。
CPU使用率关注的是实际用于处理任务的CPU时间占总时间的比例,也以百分比表示,但更偏向于反映CPU的工作效率。
负载关注的是系统的整体负担情况,包括CPU、内存、磁盘等资源的占用情况,以及等待队列中的任务数。

Linux进程间通信:管道(匿名管道和有名管道)、信号、消息队列、共享内存、信号量、套接字(socket)

Linus线程间通信:互斥量、信号量、条件变量

linux启动

主板加电BIOS,加电自检
引导系统的Boot Loader
grub配置文件读取,加载内核
用户层init进程执行rc.syninit
初始化内核模块
初始化用户空间的init

执行/etc/rc.d/rc.local(本地运行服务)
执行/bin/login,就可以登录了。

linux删除命令rm,发生了什么

linux文件系统里,用inode保存文件的数据结构,rm本质上是删除了这个inode和文件名的关联,然后文件的引用计数减少,当一个文件引用计数减少到0时,系统释放空间