注: 此系列内容来自网络,未能查到原作者。感觉不错,在此分享。不排除有错误,可留言指正。
CPU:
孤儿进程:一个父进程退出,它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。他们将被init进程(进程号为1)所接管,并对它们完成状态收集工作。
僵尸进程:一般工作中叫Z进程,即一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,称之为僵尸进程,如何处理Z进程 ,杀死父进程,都转成孤儿进程让init进程处理。cpu负载过高说明任务过多,或是僵尸进程过多
CPU的寄存器:https://blog.csdn.net/qq_34246965/article/details/115865306
PC/IP(程序计数器)、IR(指令寄存器)、PSW(程序状态字)、BP/EBP(指向当前函数栈底的地址)、SP/ESP(当前函数栈顶)、DX(数据寄存器)
SP/ESP(栈指针寄存器) BP/EBP(基址指针寄存器)也叫堆栈指针 PC/IP/EIP(程序计数器寄存器)存储着CPU下次要执行的指令地址
根据pc/ip/eip寄存器存储的地址,cpu就知道函数结束,下次要执行的函数指令地址。ebp寄存器存储着函数栈底地址,这个地址是esp在函数调用前传给ebp的。当调用结束,ebp会把该地址再传回给esp。esp又指向了函数结束后,上一次函数栈顶的地址。
CPU的利用率:
CPU处于用户态执行的时间(users),系统内核执行的时间(sys),和空闲系统进程执行的时间(idle)。平时说的CPU利用率是指:1-CPU空闲运行时间/总运行时间. CPU利用率显示的是程序在运行期间实时占用的CPU百分比 https://blog.csdn.net/longerzone/article/details/8631183
CPU负载计算:系统的load是指running和runable进程的总数,
取决于CPU任务队列长度,使用CPU队列长度则可以很直接反应CPU的负载量。loadAverage是对CPU负载的评估,其值越高,说明其任务队列越长,处于等待执行的任务越多。即一段时间内共有多少任务在使用或等待使用CPU。CPU负载是一段时间内正在使用和等待使用CPU的平均任务数。CPU利用率高,并不意味着负载就一定大。
CPU负载分担到每个CPU上的负载是多少呢?那就要看我这台服务器有一共有多少个内核了。如:4个内核15分钟的负载最理想的状态是4*0.7。https://blog.51cto.com/zhanglm/1952818、https://blog.csdn.net/zouli415/article/details/79794355
CPU时间 = user(用户态)+sys(内核态)+nice()+idle(IO等待以外其它等待时间)+iowait(硬盘IO等待时间)+irq(硬中断时间)+softirq(软中断时间)
操作系统:
PCB(task_struct)结构:
代码段:该部分空间只能读,不可写、数据段:保存全局变量、静态变量的空间、堆:平时说的动态内存, malloc/new大部分都来源于此。其中堆顶的位置可通过函数 brk 和 sbrk进行动态调整、文件映射区域:动态库、共享内存等映射物理空间的内存,一般是mmap分配的虚拟地址空间、栈:用于维护函数调用的上下文空间、内核虚拟空间:用户代码不可见内存区域,由内核管理(页表就存放在内核虚拟空间)。
内核态:R0,cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。中断用户态会陷入内核态。
用户态:R3,有限的访问内存,不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。中断时用户态cpu上下文是保存在内核栈的pt_regs里。
用户态和内核态:现代操作系统只使用R0和R3两种模式,对应于内核模式和用户模式
CPU所有指令中,有些指令是非常危险的,如果错用将导致系统崩溃,如清内存、设置时钟等。防止他们获取别的程序的内存数据、获取外围设备的数据, 并发送到网络。若进程都能使用这些指令,系统死机的概率将大大增加。所以要设置级别权限。(系统调用、缺页异常、外设中断会用户态陷入内核态) 使用线程内核栈执行内核空间的系统函数,这被称为从`用户态`切换到`内核态` 。INT指令(软中断指令) 0x80触发系统调用软中断,执行中断服务程序(ISR),陷入内核(PSW cpu模式位设为内核态)。
用户态到内核态时,进程内核栈总是空的。进程在用户态时,用的是用户栈,当陷入到内核态时,内核栈保存进程在用户态运行的信息,但进程返回到用户态后,内核栈中保存的信息无效,会全部恢复,因此每次从用户态陷入内核的时得到的内核栈都是空的。所以在进程陷入内核时,直接把内核栈的栈底地址给堆栈指针寄存器就可以了。
(公众号作者注: 此图应该来自幼麟实验室B站账号)
线程栈与堆:
线程都拥有独立的用户栈、内核栈、独立的程序计数器(PC)。栈空间不是无限有上限,超出上限会栈溢出stack overflow
栈和PC用来保存线程的执行历史和线程的执行状态,是线程私有的资源。堆、共享空间、全局变量 是由同一个进程内的多个线程共享。
进程间通信(IPC):消息队列、共享内存空间、管道(pipe文件)、套接字/远程过程调用、信号。
无名管道:优点:简单方便;缺点:局限于单向通信、只能创建在它的进程及其有亲缘关系的进程之间、缓冲区有限;
有名管道:优点:可以实现任意关系的进程间通信;缺点:长期存于系统中,使用不当容易出错、缓冲区有限
信号:一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;缺点:传递信息少。
消息队列:内核中由消息队列标识符标识,克服了信号传递信息少,管道只能承载无格式字节流及缓冲区大小受限等。允许不同进程将格式化的数据流以消息队列形式发送给任意进程。优点:任意进程间通信,系统调用实现消息发送和接收间的同步,无需考虑同步问题;缺点:信息复制要消耗额外CPU时间,不适用于信息量大或操作频繁的场合。
共享内存:映射一段能被其他进程访问的内存,多个进程都能访问.最快的IPC方式,专门设计的针对其它效率低的IPC方式。与信号量配合使用,来实现进程间的同步与通信;优点:无须复制,快捷,信息量大;缺点:通信是通过共享空间缓冲区直接附加到进程的虚拟地址空间中实现,有进程间操作的同步问题、用内存缓冲区直接交换信息,只能用于同机器内进程,不方便网络通信
套接字(RPC):用于不同及其间的进程通信。优点:传输数据为字节级,传输数据可自定义,数据量小效率高、传输数据时间短,性能高、适合于远程间信息实时交互、可以加密,数据安全性强;缺点:需对传输的数据进行解析,转化成应用级的数据。
中断: 软中断(异常指令,系统调用也是异常的一种)、硬中断(外设信号)。看信号来源是来自cpu内部还是外部 http://www.noobyard.com/article/p-fpatcjwd-uc.html
中断控制器:CPU的控制部件,中断控制逻辑线路和中断寄存器。硬件向其发出中断请求(IRQ)信号,在中断寄存器中设置已发生的中断。指令处理结束前,会检查中断寄存器,若有不被屏蔽的中断产生,改变cpu的操作顺序,pc指向操作系统的中断处理程序入口地址。中断寄存器:保存中断源的中断请求信息的专用寄存器。
中断向量表:中断码=>中断处理程序入口地址
软中断:CPU在执行指令时,执行到异常指令而产生,是程序内部产生。软中断的中断号由软中断指令直接发出(INT 0x80),无需使用中断控制器。
硬中断:执行每个指令后,CPU都要检查中断寄存器是否有中断信号,有则中断当前程序的执行,陷入内核态去执行中断处理程序。硬中断是可屏蔽的,软中断不可屏蔽。
中断响应处理过程:当执行异常指令/有外设中断信号时,会陷入内核态并保存用户态的PSW PC SP到内核栈,执行操作系统的中断处理程序。PC指向内核函数,esp ebp指向内核栈。执行完 CPU检测到中断返回指令,从内核栈pt_regs里拿回用户态上下文恢复到用户态环境。
用户态到内核态转换是由中断机制实现的(并将PSW cpu模式位设为内核态)且是唯一途径。 内核态到用户态是执行一个特权指令,将PSW cpu模式位设为用户态。
信号(signal):https://www.shangmayuan.com/a/ca9bfb268fd544228a3f9000.html
信号本质上是软件层次上对中断机制的一种模拟,同中断类似,内核为每个进程准备了一个信号向量表,记录着每个信号对应的处理机制,默认是调用默认处理机制。当进程为某个信号注册了信号处理程序,发生该信号时,内核会调用注册的函数。
用户进程提供的信号处理函数是在用户态里的。我们发现信号时,找到信号处理函数的时刻处于内核态中,所以我们需要从内核态跑到用户态去执行信号处理程序,执行完毕后还要返回内核态。
进程系统调用或中断进入内核,完成任务返回用户态前夕,检查信号队列,若有,则根据信号向量表找到信号处理函数,设置好“栈帧”后,跳到用户态执行信号处理函数。信号处理函数执行完毕后,返回内核态,设置“栈帧”,再返回到用户态继续执行程序。
信号处理程序执行完毕之后,进程调用sigreturn()系统调用再回到内核,看有没有其他信号需要处理。若没有,内核就会做一些善后工作,然后返回用户空间,程序就能够继续执行。至此,内核遍完成了一次(或几次)信号处理工作。
线程的调度与抢占:内核调度第一定律,必须是运行中的进程主动调用__schedule https://blog.csdn.net/gatieme/article/details/51702662
完全公平调度算法:Linux里实现了个基于CFS的调度算法。每个CPU有自己的rq结构,用于描述在此上运行的所有task_struct,包括实时队列rt_rq和CFS运行队列cfs_rq
主动调度:如IO读写。进程主动调内核的__schedule让出CPU。__schedule做两件事情,1:选下一个进程; 2:上下文切换,上下文切换又分用户态空间切换和内核态切换。
抢占调度:进程要被抢占了,不能直接踢它下来,而是先标记为被抢占,打上TIF_NEED_RESCHED标签。进程调度第一定律:必须是运行中的进程调用__schedule才行。
用户态的抢占时机: 当进程进行系统调用/中断从内核态返回到用户态时,若该进程有TIF_NEED_RESCHED标签,进行抢占,进程调用schedule进行调度。
内核态的抢占时机:CPU在内核态时,被抢占是发生在调用preempt_enable()时。CPU内核态时,有的操作是不能中断的,进行这些操作之前,先调用preempt_disable()关闭抢占,再调用preempt_enable()时,就是内核态代码被抢占的时机;内核态也会遇到硬中断,硬中断返回时仍是内核态。这是抢占的时机;判断有TIF_NEED_RESCHED标签,会调用schedule进行调度。
调度算法:FCFS、时间片轮转法、短进程优先、最短剩余时间优先(抢占式短进程优先)、最高响应比优先、多级反馈队列算法(结合时间片、FCFS、短进程优先)。
调度时Cpu的硬件上下文(pc,sp,psw)会存到进/线程的thread_struct里,通用寄存器保存在内核栈,thread_info用于系统快速找到task_struct
虚拟文件系统与具体文件系统:
VFS是一个内核抽象层,隐藏具体文件系统的实现细节,给用户态进程提供一套统一的 API 接口。VFS 使用了一种通用文件系统的设计,具体的文件系统只要实现了VFS的接口设计,就能够注册到 VFS 中,从而使内核可以读写这种文件系统。 像面向对象设计中的接口类与实现类,VFS就是用c语言写的面向对象的接口。
VFS通用文件模型四种元数据结构:
超级块对象:存放已注册文件系统的信息。如ext3等这些基础的磁盘文件系统、用于读写socket的socket文件系统、用于读写cgroups配置信息的cgroups文件系统等。
索引节点对象:存放具体文件的信息。对于磁盘文件系统,inode中一般存放文件在硬盘中存储块等信息;对于socket文件系统,inode会存放socket相关属性;对于cgroups文件系统,inode会存放cgroup相关属性信息。比较重要的一个是inode_operations结构体,定义了在具体文件系统中创建文件,删除文件等的具体实现。
文件对象:一个文件对象表示进程内打开的一个文件,文件对象是存放在进程的文件描述符表里面的。这个文件中比较重要的部分是一个叫 file_operations 的结构体,这个结构体描述了具体的文件系统的读写实现。当进程在某一个文件描述符上调用读写操作时,实际调用的是 file_operations 中定义的方法。 对于普通的磁盘文件系统,file_operations 中定义的就是普通的块设备读写操作;对于socket文件系统,file_operations中定义的就是 socket 对应的 send/recv 等操作;而对于cgroups这样的特殊文件系统,file_operations 中定义的就是操作cgroup结构体等具体的实现。
目录项对象:在每个文件系统中,内核在查找某一个路径中的文件时,会为内核路径上的每一个分量都生成一个目录项对象,通过目录项对象能够找到对应的 inode 对象,目录项对象一般会被缓存,从而提高内核查找速度。
文件系统存储在磁盘中:大部分磁盘能够划分出一到多个分区,叫磁盘分区或者是磁盘分片。每个分区都有独立的磁盘文件系统,每块分区的磁盘文件系统可以不同。同一个操作系统里可以同时存在多个磁盘文件系统,可以各自设置他们的块大小,各个分区的磁盘文件系统的块大小是可以不同的。
索引节点(index-node):索引节点会存放到磁盘上,要用时才会调用内存;文件的物理结构:逻辑块与物理块,就像内存的虚拟内存和物理内存间映射,文件读写、查找时都是操作的是逻辑块;unix里的文件分配方式都是混合索引方式,适用于小文件与大文件
存储空间管理:unix里采用`成组链接法`对磁盘空闲块进行管理,空闲表法、空闲链表法不适用于大型文件系统。
文件操作:
1、在写文件的时候同时删除文件,文件依旧可以读写,多进程打开文件时打开计数会增加,在删除文件时打开计数为0时才真正删除(类似引用计数,标记有多少个进程打开) i_nlink是硬连接计算 https://www.cnblogs.com/yangyuliufeng/p/9339088.html
2、同一个文件是可以open多次的。每次open都会建立一个新的file句柄/指针与file结构体,指向同一个文件的inode节点。生成子进程时会共享file结构体。
数据缓存与设备I/O缓冲区:pageCache是对文件数据的缓存,内存不够时会用LRU算法刷盘释放内存空间、bufferCache是对设备读写数据的缓冲暂存(批处理),当IO操作完成后,bufferCache会归还操作系统,如socket读写缓冲区。两者都是用radix树进行管理。
信号量:
整型信号量:整型信号量存在的问题,不满足让权等待,会忙等待,吃光cpu
记录型信号量:锁的实现都是基于记录型信号量的,会有队列挂起线程进行`让权等待`
互斥与同步:互斥前P后V,互斥的PV操作都是在同一进程里的、同步前V后P,同步的PV操作是在不同的进程里
多进程/多线程互斥:并发三大特性:原子性、可见性、有序性。注意:互斥与同步是两个问题
线程互斥和进程互斥的本质区别在于锁的sema放在哪,放在私有的进程空间还是放在多进程共享的空间,并且看sema是否具备进程共享的属性
进程共享锁实现:https://blog.csdn.net/weixin_44344462/article/details/97180648
go共享内存变量: https://studygolang.com/articles/10203
线程互斥:互斥锁、自旋锁、读写锁、条件变量。在linux里线程和进程是同一结构体task_struct,多线程的本质仍是进程。
进程互斥:设置共享内存信号量(shmget)+对记录型信号量做PV原语操作,消息队列、管程、文件大锁。
锁的实现方式:先原子操作自旋几次,若还没抢到锁则触发 futex系统调用 在内核里的futex队列挂起进/线程
futex系统调用(基于记录型信号量):https://hardcore.feishu.cn/docs/doccn9Ld4O9tGh7DenRv3GOj7Uh
futex是一种用户态和内核态混用的同步机制,由一个内核层的队列和一个用户空间层的atomic integer构成。当获得锁时,尝试cas更改integer,如果integer原始值是0,修改成功,该线程获得锁,否则就将当前进/线程放入到 futex_hash_bucket结构体里的等待队列中(内核里的等待队列)。
(公众号作者注: 如水印,此图应该来自微信号 syd3600520)
futex同步机制即可用于线程之间同步,也可用于进程之间同步。
用于线程比较简单,因为线程共享虚拟内存空间,futex变量由唯一的虚拟地址表示,线程即可用虚拟地址访问futex变量
用于进程稍微复杂一些,因为进程间是分配的独立的虚拟内存地址空间。需要通过mmap让进程共享一段地址空间来使用futex变量。故每个进程访问futex的虚拟地址不一样,但是操作系统知道所有这些虚拟地址映射到同一个表示futex变量的物理地址。sem_wait、 pthread_mutex_lock底层是用futex机制实现的。
死锁 :
产生死锁条件:
死锁避免:
死锁检测:
虚拟内存:页面到页框的映射是一一对应的,都是4KB。https://juejin.cn/post/6844904132378116104
分段与分页的区别:分段是对用户可见的,用户编程时需要显式地给出段名,目的是为了更好满足用户需求,段的长度是不固定的,取决于用户编写的程序、分页是系统行为,对用户不可见的,目的是为了实现内存离散分配,提高内存利用率,页的大小是固定且由操作系统决定(4KB)。
分段与分页的结合(段页式管理):先将进程按逻辑模块分段,再将各段分页(每个页面4KB)。
Linux采用段页式管理机制,有两个部件用于地址转换:分段部件和分页部件。https://www.shuzhiduo.com/A/qVdeXlp15P/
分段部件:将逻辑地址转换为线性地址。分段提供了隔绝各个代码、数据和堆栈区域的机制,因此多个程序(任务)可以运行在同一个处理器上而不会互相干扰。
分页部件:将线性地址转换为物理地址(页表和页目录),若没有启用分页机制,那么线性地址直接就是物理地址。
多级页表:基于局部性原理,主要目的是为了节省页面的内存分配。一级页面存放二级页表的1024个页面
操作系统中有个页表寄存器(PTR),存放页表在内存的起始地址和长度(长度指有多少个页表项),页表项长度都是相同的,可以很快计算出页号位置。进程未上cpu时,页表的物理始址和长度会存储在task_struct结构体里mm_struct字段里的pdg指针。
变量操作->访问快表(TLB 联想寄存器 硬件实现)->访问虚拟内存地址->访问页目录(是否有对应物理内存)->产生缺页异常中断->判断有没有足够的物理内存(没有就脏页刷盘)->访问磁盘swap空间->加载到内存->设置页表做好映射。物理内存页框由伙伴关系系统在维护。
动态内存分配的原理: https://blog.csdn.net/gfgdsg/article/details/42709943 https://zhuanlan.zhihu.com/p/367386292
系统调用malloc分配堆内存空间,实际是完成了次用户态的内存映射。<=128KB调用brk,其他调用mmap。brk将数据段(.data)最高地址指针_edata往高地址推、mmap是在文件映射区找一块空闲的虚拟内存。brk分配的内存要等高地址内存释放后才能回收,mmap分配的内存可单独释放。内存碎片就是brk的非高地址内存释放时产生。
堆是连续空间,高地址释放后才能真正释放前面释放的内存。非高地址不是真正的释放内存,堆碎片没有归还OS ,可重用碎片,再访问该内存很可能不需任何系统调用和缺页中断,将降低CPU消耗。 默认下:最高地址空间的空闲内存超过128K时,执行内存紧缩操作(trim)。
malloc流程:调用时搜索空闲内存块,若找到大小合适的则分配出去。若找不到大小合适的空闲内存,则调用brk/mmap系统调用扩大堆区而获得更多的空闲内存。malloc系统调用后开始转入内核态,此时操作系统中的虚拟内存系统开始工作,扩大进程的堆区,扩大的只是虚拟内存,并没有分配真正的物理内存。brk执行结束后返回到malloc,内核态切换到用户态,malloc找到一块合适的空闲内存后返回。程序拿到新申请的内存,程序继续。第一次访问时出现缺页中断,由用户态切换到内核态,此时才真正的分配物理内存,建立虚拟和物理内存间的映射,之后再次由内核态切换回用户态,程序继续。
虚拟内存寻址空间:32 位系统:3G 用户态, 1G 内核态,寻址到2^32、 64 位系统: 用户态、内核态都128T,中间有一大块是未利用的,寻址到2^64。
内存栏栅:
栈溢出有两种,一种是stackoverflow,另一种是outofmemory,前者一般是因为方法递归没终止条件,后者一般是方法中线程启动过多。堆栈空间:栈空间,由编译器自动分配释放。堆空间,要手动或GC处理释放。
(公众号作者注: 此图应该来自幼麟实验室B站账号)
fork系统调用:https://www.cxyzjd.com/article/qq_22613757/88770579 https://www.coonote.com/os/child-process-inherit-from-parent-process.html
fork:调用一次返回两次。子进程返回0,父进程返回子进程的ID。子进程获得父进程数据空间、堆和栈的虚拟地址复制,即变量的虚拟地址也一样。内核会先调度fork出来的子进程。子进程完全复制父进程的虚拟空间,复制了页表,没有复制物理页面,这时虚拟地址相同,物理地址也相同。
子进程继承:父进程的部分用户号UIDs和用户组号GIDs、环境、堆栈、共享内存、打开文件的描述符、执行时关闭标志、信号控制设定、进程组号、当前工作目录、根目录、文件方式创建屏蔽字、资源限制、控制终端。
子进程独有:进程号PID、父进程号、自己的文件描述符和目录流的拷贝、子进程不继承父进程的进程正文,数据和其他锁定内存、不继承异步输入和输出。
写时复制(COW):fork的子进程,虚拟空间独立(但地址相同),子进程的代码段、数据段、堆栈都指向父进程的物理空间与父进程共享物理内存。这样创建速度就很快了。父子进程中有更改相应段行为发生时,再为子进程分配相应段物理空间。
mmap内存映射与shmget共享内存:
1、mmap可以虚拟内存映射至物理内存,也可以映射文件到虚拟内存。映射文件时,实际是映射虚拟内存到物理内存再到文件。mmap可用于在malloc申请动态内存
2、mmap内存映射是通过操作内存来实现对文件的操作,可加快执行速度,不是专门用来进行数据通信的。mmap是在磁盘上建立一个文件,每个进程地址空间中开辟出一块空间进行映射。(但它也可以用于进程间的通信)
3、对shm而言,shm每个进程最终会映射到同一块物理内存。shm保存在物理内存,这样读写的速度要比磁盘要快,但是存储量不是特别大。相对于shm来说,mmap更加简单,调用更加方便,所以这也是大家都喜欢用的原因。
4、另外mmap有一个好处是当机器重启,因为mmap把文件保存在磁盘上,这个文件还保存了操作系统同步的映像,所以mmap不会丢失,但是shmget就会丢失。
零拷贝:directIO mmap(connfd->filefd 4 次上下文切换,1 次 CPU 拷贝和 2 次 DMA 拷贝) sendfile(filefd->connfd 2次上下文切换,1 次 CPU 拷贝)
mmap的优缺点:
优点: 省了传统读写的用户态和内核态的cpu上下文切换,提高了大文件读写效率。
缺点:mmap是按页做映射的,文件若小于4KB也是占用4KB内存,若连续mmap小文件会浪费很多内存空间,映射的文件大小最好是4KB的整数倍;用mmap写文件时,文件是无法拓展。mmap映射到内存时,能够操作的范围就确定了。要写入文件的大小是不能超过mmap映射文件的大小,它不能动态扩展文件;若更新文件的操作很多,会触发大量的脏页回写及由此引发的随机IO上。随机写很多的话,mmap方式在效率上不一定会比带缓冲区的一般写快;
进程切换:
1.切换页目录物理地址(来实现切换用户态地址空间)以使用新的地址空间并要刷掉快表。
2.切换内核栈,切换是要陷入内核态的,切换时用户态上下文是保存在内核栈pt_regs里,恢复时从内核栈里弹出。
3.切换硬件上下文(pc程序计数器, psw程序状态寄存器, sp堆栈指针)。Linux里, 硬件上下文大部分是在在thread_struct中的, 通用寄存器是保存在内核栈.
线程切换:taskStruct时间片用完时,会触发时间中断INT 0x08,让taskStruct进行切换 https://www.bilibili.com/video/BV1GA411T7qU
1.只切换内核栈(线程拥有独立的用户栈和内核栈),切换是要陷入内核态的,切换时用户态上下文是保存在内核栈pt_regs里,恢复时从内核栈里弹出。切换时cpu要陷入内核态,有用户态和内核态切换,是系统调用过程。且cpu切换耗时成本比协程高
2.硬件上下文(pc程序计数器,psw程序状态寄存器,sp堆栈指针)。linux里 大部分是被保存到thread_struct里的,通用寄存器是保存在内核栈。
协程切换:用户态切换,只切换上下文,无内核栈切换,无需陷入内核
线程切换要借助内核完成,即用户态到内核态切换,保存用户态上下文开销、切换内核栈,再内核态用户态的切换。协程切换只在用户态就可以完成,无需借助内核,不需要进入内核态。用户态和内核态的互相切换及系统调用才是最主要的开销,线程切换频率也比协程切换的频率高很多。
协程(2kb)切换没有内核开销,协程上下文切换只涉及到cpu三个寄存器(PC程序计数器/SP BP堆栈指针/DX数据寄存器)的值修改, 协程上下文切换最多就是几十ns (协程切换只涉及基本的CPU上下文切换,而且是由P执行处理无内核帮忙,切换只发生在用户态);而对比线程的上下文切换则需要涉及模式切换(线程切换,cpu要用户态陷入内核态,要操作系统去完成线程调度,还有线程私有栈(内核栈)的切换,要切换的上下文比协程多,再返回用户态)
协程切换的时机:(仅针对Go语言)
1、select阻塞时,2、io(文件,网络)读写阻塞时,3、channel阻塞时,4、等待锁,5、sleep时,6 、GC时:GC的gcBgMarkWoker会抢占P,7 、系统调用和抢占时 sysmon线程会监控并把他们调度走,8、runtime.Gosched()
这些阻塞时会调用gopark把协程切换出去到对应的结构体里挂起,当就绪时goready会把他们扔回P的本地队列等待调度。系统调用时间过长时会切出去独立线程处理。
https://blog.csdn.net/m0_37145844/article/details/106412949
磁盘块与IO:
磁盘的最小存储单位就是扇区了,磁盘没有block的概念。若文件系统按扇区为单位来读/写数据,太慢。所以有了block的概念,是按块大小的读取的,块才是文件读写的单位,一次性读2^n个扇区。磁盘的单位是扇区,文件系统的单位是块。
磁盘块是文件系统的一个虚拟概念(软件概念),不是真实的。块大小可以由操作系统决定配置,块大小=扇区大小*2^n,n是可以修改的。它读取磁盘数据就是扇区的大小,一个扇区是512字节,这是物理结构,定下来是没法修改的。一般是4KB/块,B+树是16KB/块,文件系统GFS是64MB/块。
文件读/写是以块为单位,一块4KB(一般情况下)。前一次的读写地址与后一次的起始地址相差不大,可以在一个块内解决,有效利用缓冲区,不用再去读取其他块。
连续IO,因为本次初始扇区和上次结束扇区相隔很近,磁头几乎不用换道或换道时间极短;若相差太大,则磁头需要很长的换道时间。如果随机IO很多,导致磁头不停换道,效率大大降底。连续IO:指两次不同的读/写IO,前一次的结束地址与后一次的起始地址相差不大;随机IO: 则前一次的结束地址与后一次的起始地址相差很大;
顺序写(WAL):writeAheadLog需要的操作仅仅只是在末尾append的一个操作,比write磁盘快。
判定I/O模型是同步还是异步,看数据在用户态和内核态间复制的时候是否阻塞当前进程,若会,同步I/O;否则,异步I/O。非阻塞I/O的特点是用户进程需要不断的主动询问 kernel 数据好了没有
IO设备与进程间的关系:当进行IO操作时,会把线程放到IO等待队列。这里的IO等待队列,就是设备对应的设备控制表的设备队列字段,会把线程控制块信息放入此队列,等待DMA完成IO操作发送中断信号。
Linux资源管理:https://tech.meituan.com/2015/03/31/cgroups.html https://www.uetty.com/article/2019-11-25-linux-cgroup-cpu https://www.cnblogs.com/menkeyi/p/10941843.html
CGroups文件系统:docker资源控制的基础。资源限制: 限制资源的使用量,例如我们可以通过限制某个业务的内存上限,从而保护主机其他业务的安全运行。优先级控制:不同的组可以有不同的资源(CPU 、磁盘 IO 等)使用优先级。审计:计算控制组的资源使用情况。控制:控制进程的挂起或恢复。
cpu子系统,限制进程的cpu使用率、cpuacct子系统,统计cgroups中的进程的cpu使用报告、cpuset子系统,为cgroups中的进程设置CPU的亲和性,限制cgroup中的进程只能在指定的CPU上运行,或者不能在指定的CPU上运行。
blkio子系统,限制进程的块设备io、devices子系统,控制进程能访问某些设备。
net_cls子系统,标记 cgroups中进程的网络数据包,然后使用traffic control模块对数据包进行控制
freezer子系统,挂起或者恢复cgroups中的进程、ns子系统,使不同cgroups下面的进程使用不同的namespace、memory子系统,限制进程的memory使用量