【操作系统】基础知识总结、深入理解

1、计算机基本结构为5个部分,分别为运算器、控制器、存储器、输入设备、输出设备(冯诺依曼模型)。运算器、控制器在中央处理器CPU里,此外包含了寄存器存储器就是我们常见的内存,与中央处理器打交道离不开总线

2、常见寄存器种类

通用寄存器(R0-R12):存放需要进行运算的数据。

程序计数器(PC):存储CPU要执行下一条指令所在的内存地址。

指令寄存器(IR):存放当前正在执行的指令,指令本身。

3、a=1+2执行过程

a、数据:

数据1存放至0x200位置,数据2存放至0x204。

b、指令:编译器将a=1+2翻译为4条指令:

0x100是load指令将0x200地址中的数据1装入寄存器R0。

0x104是load指令将0x204地址中数据2装入寄存器R1。

0x108是add指令将R0和R1结果相加,并将结果放入寄存器R2。

0x10c是store指令将寄存器R2的数据存回数据段中的0x208地址。(a的地址)

指令和数据存放在不同的区域。

4、32位的计算机对应的地址总线只有32根,所以计算机一次最多寻址范围2^32,即4GB。理论上也可以进行高32位、低32位共64位的寻址,但考虑到逻辑上设计复杂,和效率较低等因素,该分段方法不被采用。

5、L1、L2缓存每个CPU核心都单独有,L3缓存是多个CPU核心共有

6、CPU 并不会直接和每一种存储器设备直接打交道,而是每一种存储器设备只和它相邻一层的存储器设备打交道。

7、多核处理器中,只有数据先从内存中读取到高速缓存中才能修改。为了(1)保存数据一致性,其他核才知道指向相同的变量被修改,才能更新状态。(2)提高访问的性能,若后面用到这个变量,每次访问数据都要从内存中读取。

8、Cache Line(缓存行):是Cache高速缓存从内存中读取数据的基本单位。通常是一组连续的缓存行的形式,大小为64字节。

9、CPU数据访问

(1)CPU在访问数据时,无论数据是否存放到了Cache中,CPU都是先访问Cache,只有当Cache访问不到数据时,才会去访问内存,并将内存中的数据加入到Cache中,CPU再从Cache中读取数据。(即CPU读取数据都是从Cache中)

(2)CPU访问内存数据时,是一小块一小块的读取的,其大小取决于coherency_line_size的值,一般64字节。内存中,该块数据成为内存块,读取时要拿到数据所在的内存地址。

(3)内存直接映射:将内存地址使用映射在一个缓存块的地址,采用取模运算。例如下面的32个内存块,15号内存中的数据对应在CPU Cache的15%8,7号CPU Cache Line。此外7号、23号、31号内存块都是映射到7号CPU Cache Line中。CPU Cache Line存放一个组标记,区分不同内存块。

10、CPU Cache Line的组成,包括组标记、从内存加载来的数据、有效位。

11、内存访问地址的组成,包括组标记、CPU Cache Line索引、偏移量这三种信息,CPU就能通过这些信息,在CPU Cache中找到缓存的数据。

12、CPU访问一个内存地址的步骤(直接映射),若内存数据已在CPU Cache中

(1)根据内存地址中的索引信息,计算CPU Cache的索引,找出对应的CPU Cache Line的地址。

(2)找到后,判断Cache Line的有效位,若无效,CPU则会直接访问内存,重新加载数据,若有效,则继续向下执行

(3)对比内存地址中组标记和CPU Cache中的组标记,确认Cache Line中数据是我们要访问的,若不是,CPU则会直接访问内存,重新加载数据。若是,则继续向下执行。

(4)根据内存地址中偏移量信息,从CPU Cache Line中,读取对应的字

13、将Cache中的数据写回到内存的方式:

(1)写直达:把数据同时写入内存和Cache中。若数据已在Cache里,将数据更新至Cache,再写入内存。若数据不在Cache,则直接更新数据到内存。但每次都写回内存,对性能有很大影响。

(2)写回:当发生写操作时,新数据仅仅被写入Cache Block里,只有当修改过的Cache Block[被替换]时才需要写到内存中。减少了数据写回内存的频率。

(3)写回具体过程:

       a、发生写操作时,数据[在CPU Cache]中,则将数据更新至Cache里,同时标记该Cache内的这个Cache Block为脏(Dirty),脏则表示该Cache中的Cache Block的数据和内存是不一致的,不用将数据写到内存。

       b、发生写操作时,数据对应的Cache Block中存放的是[别的内存地址的数据],则检查这个Cache Block里的数据有没有标记为脏。

              若标记为脏,则将Cache Block数据写回到内存,再把要写入的数据从内存读入到Cache Block里,然后再把当前要写入的数据写入到Cache Block,最后标记它为脏。

              若不为脏,则把当前写入的数据写入先从内存写入Cache Block,接着将数据写入Cache Block,再把这个Cache Block标记为脏。

14、缓存一致性,要满足两点:

(1)写传播:当某个CPU核心发生写入操作时,需要将该事件广播通知给其他核心。

(2)事务串行化:某个CPU核心里对数据的操作顺序,必须在其他核心看起来顺序是一样的。

(3)写传播的实现:总线嗅探,采用MESI协议,通过记录已修改、独占、共享、已失效四种状态来标记Cache Line四个不同的状态。

(4)事务串行化的实现:a、对Cache中数据的操作,需要同步给其他CPU核心。b、引入[锁],若两个CPU核心里有相同数据的Cache,对该Cache数据的更新,只有拿到了[锁],才能进行对应的数据更新。

15、在Linux内核中,进程和线程都是用task_struct结构体表示的,线程的task_struct中部分资源是共享的进程中已创建的资源,例如内存地址空间、代码段、文件描述符等。Linux中的线程又被称为轻量级进程(线程的task_struct比进程的task_struct承载资源较少)。

16、Linux 内核里的调度器,调度的对象就是 task_struct,也称为任务。

17、Linux中任务优先级数值越小,级别越高:

实时任务:对响应时间要求高,优先级在0~99范围内。

普通任务:响应时间没有很高要求,优先级在100~139内。

18、nice值映射到100~139,是给普通任务用的,用以调节普通任务优先级。

19、Linux中每个任务都有自己的任务调度策略,任务调度策略是针对每个任务,而不是整个系统。(改变任务调度策略,可能会影响任务优先级)

20、

       SCHED_OTHER(默认的时间片轮转调度策略)

       SCHED_FIFO(先进先出调度策略)

       SCHED_RR(轮转调度策略)

21、调度类:为保证高优先级任务尽早执行,分为以下几种调度类:

Deadline和Realtime这两个调度类,都是应用于实时任务的

而Fair调度类是应用于普通任务,都是由CFS调度器管理的

22、调度类的执行优先级:Deadline>Realtime>Fair。即Linux选择任务执行时,会先从dl_rq内选择任务,再到rt_rq,再到cfs_rq。实时任务总是比普通任务优先被执行

23、多核CPU:具有并行处理能力,也就是可以同时执行多个线程或任务,每个核心可以独立的执行指令,多个任务同一时间同时进行。

单核CPU:多任务时,具有并发处理能力。每个任务执行一小段时间,就切换到另一个任务,宏观看,一段时间内执行了多个任务。

24、对内核的理解

a、内核是作为应用连接硬件设备的桥梁,应用只需关心与内核交互,不用关心硬件的细节。

b、内核提供4个基本能力:

进程调度能力:管理线程、进程,决定哪个进程、线程使用CPU

内存管理能力:管理内存,决定内存的分配和回收

硬件通信能力:管理硬件设备,为进程与硬件设备之间提供通信能力

提供系统调用:应用程序若要运行更高权限的服务,就需要有系统调用,是用户程序和系统之间的接口。

c、内核具有很高的权限(指令集权限ring0),可以控制cpu、内存、硬盘等硬件,而应用程序的具有权限很小(ring3),因此大多数操作系统将内存分为了两个区域:

内核空间,该内存空间只有内核程序可以访问

用户空间,该内存空间专给应用程序使用

用户空间代码只能访问一个局部的内存空间,而内核空间代码可以访问所有内存空间。

25、ELF是可执行文件链接格式,它是 Linux 操作系统中可执行文件的存储格式。

执行ELF文件(可执行文件)的时候,会通过「装载器」把ELF文件装载到内存里,CPU 读取内存中的指令和数据,程序就被执行起来了。、

26、宏内核:系统内核的所有模块,如进程调度、内存管理、文件系统、设备驱动等,都运行在内核态。

微内核(华为鸿蒙):只保留了最基本的能力,如进程调度、虚拟机内存、终断等。其他一些放到了用户空间,如驱动程序、文件系统等。

区别:微内核功能少,可移植性高,但相比宏内核,由于驱动程序不在内核中,而驱动程序会频繁调用底层能力,驱动和硬件设备就需要频繁切换到内核态,会带来性能损耗。

原因在于:驱动程序需要执行特级指令时(如访问硬件寄存器、I/O操作等)时,需要切换为内核态。执行完特权操作后,又需要切换回用户态,继续执行其他非特权操作。

混合内核:内核中有一个微内核,其他模块在此基础上搭建,实现时和宏内核类似,大部分服务都在内核中,就像是宏内核的方式包裹着一个微内核。

27、用户态和内核态间的隔离关系

(1)对用户态操作硬件的限制:通过CPU指令集权限,硬件限制。用户态权限为ring3权限最低,只能使用常规cpu指令集,不能操作硬件资源。内核态权限为ring0,具有对所有硬件的操作权限。

(2)用户态不能访问内核态空间:用户态只能操作0-3G范围内的低位虚拟空间地址。内核态则0-4G的虚拟空间地址都可以操作,尤其3-4G只能由内核态操作,并且该部分指向的物理内存是所有进程共享的。

(3)对用户态空间的限制方式:(1)页表中的权限位(2)cpu指令集权限

用户空间若想访问内核中资源,需要通过系统调用下跳到内核态来实现。(权限位中标明了哪些页表只能被内核态访问(U/K权限))

(4)用户态与内核态的切换

保护用户态线程(上下文、寄存器、用户栈等)

复制用户态参数,用户栈切到内核栈,进入内核态

额外检查(因为内核代码对用户不信任)

执行内核态代码

复制内核态代码执行结果,回到用户态

恢复用户态现场(上下文、寄存器、用户栈等)

可参考:「操作系统」什么是用户态和内核态?为什么要区分-CSDN博客

28、用户态和内核态的组成

用户态:应用程序、shell(特殊的应用程序)、函数库(如glibc标准C库、线程库等)

内核态:系统调用、进程管理、内存管理、文件系统、网络管理、驱动

内存管理

1、(1)操作系统使得多个程序可以同时运行(同时有多个进程),每个进程都有自己的虚拟空间映射到物理空间中。

(2)虚拟地址空间内部又分为内核空间用户空间两部分。

(3)虽然每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。

2、虚拟内存的作用

a、将进程所使用的地址[隔离]开来,给每个进程分配一套独立的[虚拟地址],让每个进程都认为自己拥有了所有的物理内存,扩大了内存容量

b、防止了进程间的访问冲突,程序在访问虚拟地址时,会由操作系统(MMU)转换为不同的物理地址,不同进程运行时,就不会有变量的访问冲突。

c、提供了更好的内存管理机制,每个进程都有自己的页表,页表中包含一系列标志位,可以标识该页是否存在等,有的虚拟内存可能还尚未分配物理内存,或被换入了磁盘中。

3、Linux中每一页大小为4KB,虚拟地址与物理地址间通过页表来映射。每个页表项大小为4字节

4、虚拟内存地址:

(1)虚拟地址转换为物理地址:a、虚拟地址由页号页内偏移量组成b、根据页号,从页表内查询对应的物理页号c、用物理页号,加上偏移量,可得到物理内存地址。

(2)32位环境下,虚拟空间4GB,一个页4KB,则需要100万个页,每个页表项4字节,则共需要4MB存储页表,若100个进程则需要400MB来存储。

(3)为解决上面的问题,可以采用多级页表。一级页表只占4KB,看似总占用量增加了(4KB+4MB),但由局部性原理,若是一级页表没有被用到,则不需要创建这个页表项对应的二级页表。

(4)32位虚拟地址由页目录项10位、页表项10位、页内偏移12位组成。

64位虚拟地址由全局页目录项、上层页目录项、中间页目录项9位、页表项9位、页内偏移12位组成

5、虚拟内存空间布局

内核虚拟空间:0XC0000000-0XFFFFFFFF

用户虚拟空间分布: 0X0-0XC0000000

栈段,局部变量和函数调用的上下文等。栈大小固定,一般为8MB,也可在系统中指定。

文件映射段,动态库、共享内存等,从低地址开始向上增长。(mmap映射的虚拟内存在该区域,进程运行所依赖的动态链接库中的代码段、数据段、BSS段也加载在这)

堆段,动态分配的内存,从低地址开始向上增长。

BSS段,未初始化的静态变量和全局变量。

数据段,已初始化的静态变量和全局变量。

代码段,二进制可执行代码。

从低到高6种内存段

6、通过malloc()函数申请内存时,实际上申请的是虚拟内存,并不会分配物理内存。只有当应用程序读写了该块虚拟内存,CPU会去访问这个虚拟内存,发现未映射到物理内存上,CPU就会产生缺页中断,进程从用户态切换到内核态,将缺页中断交给内核的Page Fault Handle(缺页中断函数)处理。

7、缺页中断处理函数会看是否有空闲的物理内存:

(1)如果有,就直接分配物理内存,并建立虚拟内存与物理内存之间的映射关系。

(2)如果没有空闲的物理内存,那么内核就会开始进行回收内存 (opens new window)的工作,如果回收内存工作结束后,空闲的物理内存仍然无法满足此次物理内存的申请,那么内核就会触发 OOM (Out of Memory)机制(最后的大招)。

8、内存回收的方式,主要是两种:

(1)后台内存回收:物理内存紧张时,唤醒kswapd内核线程回收内存,异步,不会阻塞进程执行。

(2)直接内存回收:若后台回收跟不上进程内存申请速度,开始直接回收,同步,阻塞进程执行。

9、可以被回收的内存,两类:

(1)文件页:内核缓存的磁盘数据(Buffer)和内核缓存的文件数据(Cache)都为文件页。回收干净页的方式是直接释放内存,回收脏页的方式是先写回磁盘后再释放内存。

(2)匿名页:像进程的堆、栈等没有实际载体(物理内存中)的数据,可能会被再次访问,不能直接释放,用一个保存匿名页的磁盘载体,即swap分区保存。用Swap机制回收。

10、swap机制:内存存在压力时,开始触发内存回收行为,将不常访问的内存写入磁盘,然后释放这些内存,当需要访问时,再从磁盘读出。相当于将一块磁盘空间当作内存来用。

11、swap触发情况:(1)内存不足:直接内存回收(同步)(2)内存限制,kSwapd,空闲内存低于一定水平时触发,后台内存回收(异步)

12、文件页和匿名页的回收都是基于LRU算法,优先回收不常访问的内存。(链表尾部通常为最不常访问的)

13、4GB 物理内存的机器上,申请 8G 内存时,32位操作系统(理论上)最多申请3G(用户空间)虚拟内存,而64位可以使用128T大小的虚拟内存,因为如果不读写虚拟内存,操作系统就不会分配物理内存,所以申请8G内存没问题。

14、预读失效:提前加载进来的页,没有被访问。

避免预读失效:实现了两个链表:活跃链表(active_list)和非活跃LRU链表(inactive_list)。预读页就加入inactive_list链表。

15、缓存污染:批量读取数据时,很多数据就读取了一次,而之前活跃在LRU链表中的热点数据就会被淘汰,当这些热点数据再次被访问时,由于缓存未命中,会产生大量的磁盘I/O,导致系统性能下降。

避免缓存污染:Linux中只有在内存也被访问第二次时,才将页从inactive list升到active list里。

16、CPU访问进程的虚拟地址时,经地址翻译硬件(MMU)将虚拟地址转换为不同的物理地址,这样不同进程运行时,虽然操作的是同一虚拟地址,但背后写入的是不同的物理地址,这样子就不会发生冲突了。

17、进程进入内核态之后使用到仍然是虚拟内存地址,只不过内核中使用的虚拟内核地址被限制在了内核态内存空间范围中(对物理地址的访问,还要ioremap,需要将虚拟地址跟对应的物理地址映射起来,这样子在访问虚拟地址时,才能通过MMU找到对应的物理地址访问,相当于要建立一个页表的映射。)

18、内核中使用start_brk标识堆的起始位置brk标识堆的结束位置。当堆申请新内存空间时,只需要将brk指针增加对应的大小,回收时减少对应大小即可。如用malloc向内核申请很小的一块内存是(128K内),就是通过改变brk位置实现的。(多次调用malloc时,不是每次调用malloc,都会调用到brk。)

19、文件页第一次读取会被放置在inactive链表头部,若被继续访问,则会提升至active链表尾部。匿名页第一次读取会被放置在active链表尾部,因为匿名页换出swap out成本更大,当匿名页再次被访问时会被提升到active链表的头部。

20、若是匿名页和文件页放置同个链表,若有大量匿名页,内核会不断跳过匿名页寻找文件页,性能会低效。因此内核将active链表和inactive链表按匿名页和文件页进行了分类。(匿名页的active链表,inactive链表和文件页的active链表,inactive链表)。

进程管理

  1. 子进程共享了父进程的虚拟内存空间,就会变为线程,是否共享地址空间几乎是进程和线程之间的本质区别。但Linux内核并不区别对待它们,线程对其来说就是一个共享特定资源的线程而已。
  2. CPU上下文切换即将前一个任务上下文(CPU寄存器和程序计数器)保存,再加载新任务的上下文,最后跳转至程序计数器所指的新位置,运行新任务。

3、CPU只会访问虚拟内存地址。

4、并发和并行:

进程和线程间的关系:

5、所谓操作系统的任务调度,实际上的调度对象是线程,而进程只是给线程提供了虚拟内存、全局变量等资源。

6、线程和进程:(1) 当进程只有一个线程时,可以认为进程就等于线程(主线程)。(2)当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源,这些资源在上下文切换时是不需要修改的;

7、线程上下文切换要看是不是同一个进程的,同一个进程切换的数据少很多。

8、线程通信方式:同个进程下的线程之间都是共享进程的资源,只要是共享变量(多个线程可以同时访问和修改的变量)都可以做到线程间通信。

线程间关注的不是通信方式,而是多线程竞争共享资源的问题,信号量同样可在线程间实现同步与互斥。

9、线程是调度的基本单位,进程则是资源分配的基本单位。(当调度只有主线程的进程时,可理解为此时进程调度即线程调度)

进程控制块

10、process control block PCB(进程控制块)中包含的信息:(1)进程标识符、用户标识符(2)进程当前状态如new,ready等、进程优先级(3)打开的文件列表和使用的I/O设备信息。(4)CPU中寄存器的值,进程切换时,状态信息保存在相应的PCB中。

11、PCB通过链表的方式进行组织,把具有相同状态的进程链在一起,组成各种队列。如就绪队列、阻塞队列。

进程上下文切换和线程上下文切换:

12、进程是由内核管理和调度的,所以进程的切换只能发生在内核态。

进程上下文切换:不仅包含了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的资源。

通常,会把交换的信息保存在进程的 PCB,当要运行另外一个进程的时候,我们需要从这个进程的 PCB 取出上下文,然后恢复到 CPU 中,这使得这个进程可以继续执行。

13、线程是调度的基本单位,进程是资源拥有的基本单位.

14、线程上下文切换

当两个线程不是属于同一个进程,则切换的过程就跟进程上下文切换一样;

当两个线程是属于同一个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动(不需要切页表),此外共享全局变量、文件描述符等,只需要切换线程的私有数据、寄存器等不共享的数据;

15、线程控制块(Thread Control Block, TCB)

操作系统不参与用户线程的调度,如果一个线程发起了系统调用而阻塞,那进程所包含的用户线程都不能执行了。

内核线程是由操作系统管理的,线程对应的 TCB 自然是放在操作系统里的,这样线程的创建、终止和管理都是由操作系统负责。

16、从就绪态 -> 运行态:当进程被创建时,会进入到就绪队列,操作系统会从就绪队列选择一个进程运行;

从运行态 -> 阻塞态:当进程发生 I/O 事件而阻塞时,操作系统必须选择另外一个进程运行;

从运行态 -> 结束态:当进程退出结束后,操作系统得从就绪队列选择另外一个进程运行;

17、调度算法:先来先服务、最短作业优先、高响应比优先、时间片轮转、最高优先级、多级反馈队列调度算法

18、信号量会导致睡眠。睡眠后会有一个睡眠等待队列,存放等待信号量的线程或进程。

19、匿名管道,通信范围是存在父子关系的进程。命名管道,可以在不相关的进程间通信。

20、进程写入管道的数据都是缓存在内核中,遵循先进先出原则。管道间的通信效率低,不适合进程间频繁的交换数据

21、消息队列:(1)消息队列是保存在内核中的消息链表,有通信不及时和数据传输大小限制两个不足,不适合较大数据的传输。(2)通信过程中,也存在用户态和内核态间的数据拷贝开销(进程写入数据到内核中消息队列)

22、每个进程的用户空间都是独立的,不能相互访问,这时则需要借助内核空间来实现进程间通信,因为每个进程都是共享一个内核空间。

23、共享内存是最快的进程间的通信方式。

24、零拷贝:全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的。没有在内存层面去拷贝数据。

25、Page Cache(磁盘高速缓存):是操作系统中用于存储磁盘数据的一种缓存机制,用于加速磁盘 I/O 操作。当程序需要读取磁盘上的数据时,操作系统会将数据读取到内存中的一个页面(Page)中,并在需要时将其写回磁盘。由操作系统管理。

Cache Line(缓存行):是计算机体系结构中用于存储 CPU 缓存中数据的最小单位。由 CPU 控制。

文件系统:

1、Linux 最经典的一句话是:「一切皆文件」,不仅普通的文件和目录,就连字符设备、块设备、管道、socket 等,也都是统一交给文件系统管理的。

2、Linux 文件系统会为每个文件分配两个数据结构:索引节点(index node)和目录项(directory entry),它们主要用来记录文件的元信息和目录层次结构。

(1)索引节点,也就是 inode,用来记录文件的元信息,比如 inode 编号、文件大小、访问权限、创建时间、修改时间、数据在磁盘的位置等等。索引节点存储在硬盘中,同样占用磁盘空间。

(2)目录项,也就是 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的层级关联关系。与索引节点不同的是,目录项是由内核维护的一个数据结构,不存放于磁盘,而是缓存在内存。

索引节点唯一标识一个文件,而目录项记录着文件的名字,所以目录项和索引节点的关系是多对一,也就是,一个文件可以有多个别名。(硬链接的实现)

(3)目录也是文件,也是用索引节点唯一标识,和普通文件不同的是,普通文件在磁盘里面保存的是文件数据,而目录文件在磁盘里面保存子目录或文件

在磁盘中的存储情况:

超级块,用来存储文件系统的详细信息,比如块个数、块大小、空闲块等等。

索引节点区,用来存储索引节点;

数据块区,用来存储文件或目录数据;

3、文件系统,根据存储位置的不同,把文件系统分为三类:

磁盘的文件系统,直接把数据存储在磁盘中,比如 Ext 2/3/4、XFS 等都是这类文件系统。

内存的文件系统,这类文件系统的数据不是存储在硬盘的,而是占用内存空间,我们经常用到的 /proc 和 /sys 文件系统都属于这一类,读写这类文件,实际上是读写内核中相关的数据。

网络的文件系统,用来访问其他计算机主机数据的文件系统,比如 NFS、SMB 等等。

4、打开文件表:我们打开了一个文件后,操作系统会跟踪进程打开的所有文件。操作系统为每个进程维护一个打开文件表,文件表里的每一项代表「文件描述符」

打开文件的状态和信息:

文件指针、文件打开计数器、文件磁盘位置、访问权限

5、文件存储方式:连续空间存放、非连续空间存放(链表和索引)

6、目录的存储:和普通文件不同的是,普通文件的块里面保存的是文件数据,而目录文件的块里面保存的是目录里面一项一项的文件信息。(如文件名、文件 inode、文件类型等)

7、有时候我们希望给某个文件取个别名,那么在 Linux 中可以通过硬链接(Hard Link) 和软链接(Symbolic Link) 的方式来实现。

8、硬链接是多个目录项中的「索引节点」指向一个文件,也就是指向同一个 inode。不可用于跨文件系统的。

软链接相当于重新创建一个文件,这个文件有独立的 inode,但是这个文件的内容是另外一个文件的路径,所以访问软链接的时候,实际上相当于访问到了另外一个文件。

9、文件I/O分为:(1)缓冲与非缓冲 I/O(2)直接与非直接 I/O(3)阻塞与非阻塞 I/O VS 同步与异步 I/O

10、非阻塞 I/O,非阻塞的 read 请求在数据未准备好的情况下立即返回,可以继续往下执行,此时应用程序不断轮询内核,直到数据准备好.

11、为了解决这种傻乎乎轮询方式,于是 I/O 多路复用技术就出来了,如 select、poll,它是通过 I/O 事件分发,当内核数据准备好时,再以事件通知应用程序进行操作。

整个流程要比阻塞 IO 要复杂,似乎也更浪费性能。但 I/O 多路复用接口最大的优势在于,用户可以在一个线程内同时处理多个 socket 的 IO 请求

12、实际上,无论是阻塞 I/O、非阻塞 I/O,还是基于非阻塞 I/O 的多路复用都是同步调用。因为它们在 read 调用时,内核将数据从内核空间拷贝到应用程序空间,过程都是需要等待的。

13、而真正的异步 I/O 是「内核数据准备好」和「数据从内核态拷贝到用户态」这两个过程都不用等待。

当我们发起 aio_read 之后,就立即返回,内核自动将数据从内核空间拷贝到应用程序空间,这个拷贝过程同样是异步的,内核自动完成的。应用程序并不需要主动发起拷贝动作。

14、I/O 是分为两个过程:(1)数据准备的过程(2)数据从内核空间拷贝到用户进程缓冲区的过程

15、阻塞 I/O 会阻塞在「过程 1 」和「过程 2」,而非阻塞 I/O 和基于非阻塞 I/O 的多路复用只会阻塞在「过程 2」,所以这三个都可以认为是同步 I/O。

16、异步 I/O 则不同,「过程 1 」和「过程 2 」都不会阻塞。

17、软链接,又称符号链接即将一个路径名链接到一个文件。软链接文件内存储的是源文件的路径。可以在不同目录下创建,源文件位置变动或删除时,会失效。ln -s file link

硬链接,是对原文件起了一个别名,源文件删除后不影响硬链接(inode还在),源文件其实也是个硬链接,每少一个硬链接,链接数减1。ln file link

TCP:

1、三次握手的报文:第一个报文SYN报文、第二个报文SYN+ACK报文、第三个报文ACK报文。

2、三次状态的切换:

  1. 第一次,客户端发送SYN报文后,处于SYN_SENT状态。
  2. 第二次,服务端接收到客户端的SYN后,发送SYN+ACK报文,之后变为SYN_RCVD状态
  3. 第三次,客户端收到SYN报文后,发送一个ACK报文,状态变为ESTABLISHEN状态。服务器接收到ACK报文后,也处于ESTABLISHEN状态
  1. 报文组成部分:序列号、确认应答号、标志位SYN、ACK。

4、进行三次握手而非两次握手的原因:

(1)防止【历史连接】初始化了连接。在两次握手的情况下,服务器没有中间状态给客户端来组织历史连接,导致服务端可能建立一个历史连接,造成资源浪费。(可能多次分配资源,建立多个无效的socket连接)

(2)同步双方初始序列号

(3)避免资源浪费:若是没有第三次握手,服务端不清楚客户端是否收到了自己回复的ACK报文,所以服务端每收到一个SYN就只能先主动建立一个连接。而若是SYN报文再网络中阻塞,重复发送多次,服务器收到请求后则会建立多个冗余的无效链接,造成资源浪费。

5、三次握手,每次的作用:

第一次握手:服务端可知:客户端的发送能力、服务端接收能力是正常的。

第二次握手:客户端可知:服务端的发送、接收能力,客户端发送、接收能力是正常的。

第三次握手:服务端可知:客户端接收能力、服务端发送能力是正常的。

拓展部分:
1、socket是怎么实现两台linux间的通信的 ping又是怎么实现的(怎么检测ping通的)。

2、任何想要进入临界区的线程,必须执行加锁操作。若加锁操作顺利通过,则线程可进入临界区;在完成对临界资源的访问后,再进行解锁操作,以释放该临界资源。

3、最常用的破坏死锁的方法 就是破坏环路等待条件。

4、对于互斥锁加锁失败而阻塞的现象,是由操作系统内核实现的。当加锁失败时,内核会将线程置为【睡眠状态,等待锁被释放后,内核会在合适的时机唤醒线程。            互斥锁加锁失败时,会从用户态陷入到内核态,让内核帮忙切换线程,虽然简化了锁的使用难度,但存在一定性能开销成本。

5、自旋锁是通过CPU提供的CAS函数,在用户态完成加锁和解锁操作,不会主动产生线程上下文切换,相比互斥锁,会快些,开销少些。

6、读写锁由【读锁】和【写锁】两部分构成,只读取共享资源用【读锁】加锁,要修改共享资源则用【写锁】加锁。

写锁没有被线程持有时,多个线程能够并发的持有读锁,读取共享资源。

写锁是独占锁,任何时候只能有一个线程持有写锁。读锁是共享锁,可以被多个线程同时持有。

7、根据实现的不同,读写锁可以分为读优先锁和写优先锁。

PCB通过链表方式管理,将相同状态进程链在一起,组成队列。有就绪队列、阻塞队列、运行队列。

8、一个进程最多可以创建多少个线程取决于:1、进程虚拟内存空间上限,创建一个线程,操作系统会为其创建一个栈空间 2、系统参数限制

9、ulimit -a可查看默认分配给线程栈空间大小,默认是8M。ulimit -s 512 可调整为512k

10、线程共享进程的代码段、数据段、地址空间、打开的文件描述符。单独创建不共享的有寄存器、栈。

11、让进程崩溃的方法:发送信号kill

12、服务端首先调用 socket()函数,创建网络协议为 IPv4,以及传输协议为 TCP 的 Socket ,接着调用bind() 函数,给这个 Socket 绑定一个 IP 地址和端口,绑定这两个的目的是什么?

绑定端口的目的:当内核收到 TCP 报文,通过 TCP 头里面的端口号,来找到我们的应用程序,然后把数据传递给我们。

绑定 IP 地址的目的:一台机器是可以有多个网卡的,每个网卡都有对应的 IP 地址,当绑定一个网卡时,内核在收到该网卡上的包,才会发给我们。

13、有时候我们希望给文件取个别名,就可以采用硬链接和软链接的方式。

本文笔记总结,参考阅读自小林coding和其他一些资料。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值