OS

软中断 中断请求的处理程序应该要短且快,因为中断处理程序要求关中断(不接受新的中断请求),如果中断处理程序执行时间过长,可能在还未执行完中断处理程序前,会丢失当前其他设备的中断请求。 为了解决由于中断处理时间过长,导致新来的中断丢失,将中断分成两个部分: 硬中断:先关中断,处理跟硬件紧密相关或者时间敏感的事情 软中断:由内核触发,完成该中断剩余的耗时工作 其中硬中断直接抢占cpu,而软中断有专门的内核线程ksoftirqd处理,由操作系统调度执行。 软中断不只是设备中断的下半部分,一些内核自定义事件也属于软中断,比如内核调度等、RCU 锁。 linux上/proc/softirqs记录当前各类型软中断个数,如果NET_RX个数变化过快,说明很多网络包打进来,可以用tcpdump抓包分析,如果发现是异常流量,可以加防火墙,如果是正常流量,需要考虑升级硬件。 进程的状态 挂起态:处于阻塞状态的进程,进程可能会占用着物理内存空间,那么,就需要一个新的状态,来描述进程没有占用实际的物理内存空间的情况,这个状态就是挂起状态。(sleep或者ctrl+z) 进程和线程比较 创建: 进程是资源(内存、文件等)分配的基本单位,线程是调度的基本单位 进程创建涉及内存管理、文件管理等操作。而线程独享的资源只有寄存器和栈,对于内存和文件只需要共享即可 线程销毁更快,因为相对要释放的资源更少 切换调度 同一进程的线程切换更快,因为线程共享进程的地址空间,内存管理单元不涉及切换过程,不需要切换页表,不用冲刷TLB,要切换的上下文信息也少了很多 数据传递 同一进程的线程传递数据更快,由于共享内存、文件等资源,那么线程传递数据不需要经过内核,经过内核意味着需要切换到系统栈上执行,相当于切换上下文,并且系统调用由于不相信外界用户代码,会有额外的检查工作 死锁 条件: 互斥 资源不可剥夺 循环等待 占有并等待 切换内核态开销大的原因 每个进程都会有两个栈,一个内核态栈和一个用户态栈。当中断执行时就会由用户态栈转向内核态栈,系统调用时需要进行栈的切换,而且内核代码对用户不信任,需要进行额外的检查。系统调用的返回过程有很多额外工作,比如检查是否需要调度等。 虚拟内存 进程访问一个虚拟地址时,CPU芯片中的内存管理单元MMU会将其映射为物理地址,最终拿着这个物理地址去访问内存。而虚拟地址物理地址的映射方式主要有分段和分页两种。 分段:由程序员将虚拟内存划分为一个个段,虚拟地址划分为段号和段内偏移量,段号用于在段表中查询段的物理基地址、段大小、特权等级等。段基地址+段内偏移量就构成了物理地址。 分段存在的问题: 内存碎片:会有外部碎片。解决外部碎片是通过内存交换,也就是通过swap的方式实现了碎片整理,具体是将在用的内存先交换到硬盘上,然后再装载到内存当中,但是不是装载回原来的位置,而是紧邻上一片正在使用的内存。 内存交换率低:由于涉及到读写硬盘,因此swap的效率低是必然的。 综上,分段由于经常出现外部内存碎片,经常触发swap整理碎片,导致整个机器的卡顿。 分页:由操作系统将虚拟内存划分成一个个大小相同的页,虚拟地址划分为页号和页内偏移量,页号用于在页表中查询页的物理页号,拼接上页内偏移量就构成了物理地址。 分页的特点:消除了外部碎片,并且通过限制页的大小,限制了每次swap也只有一个或几个页,提高swap的效率。但是由于操作系统给进程分配物理内存是以页为单位的,当一个页没有全部使用到的话,会出现内部碎片,但页大小比较小,因此内部碎片的大小也会控制在一定范围内。 多级页表:由于一个虚拟地址必须通过查询页表来找到对应的物理页,而32位机器+4KB页大小的环境下,一个进程的页表占据4MB大小,这样纯属是一种浪费。因此引入二级页表,让一级页表长居内存,而二级页表在需要的时候再加载到内存,由于程序的空间局部性,最好的情况下内存只需要为进程保留一级页表和一页的二级页表。 linux内存布局 每个进程的虚拟内存空间划分为用户空间和内核空间,所有进程的内核空间实际上映射的是同一块物理内存: 下面是用户空间的具体布局: 其中文件映射段的内存和堆内存一样都是动态分配的,比如使用 C 标准库的 malloc() 或者 mmap() ,就可以分别在堆和文件映射段动态分配内存。 malloc分配内存的原理 malloc可能会使用brk或者mmap这两个系统调用来向操作系统申请内存:当分配内存小于128KB则用brk,大于128KB则用mmap。brk的内存在free之后会归还内存池,不会归还给操作系统,而mmap的内存free之后直接归还给操作系统。因此,如果全部使用mmap来分配内存,那么每次分配内存都触发系统调用,并且每次都触发缺页中断,效率非常低。 OOM原理 触发异步回收,不阻塞进程执行 触发同步回收,阻塞进程执行 OOM 可以被回收的内存分为文件页和匿名页,对于脏页,会触发IO写回硬盘,否则可以直接释放。对于匿名页(堆、栈等用户程序使用到的内存)则通过swap机制换出到硬盘。因为触发磁盘IO,降低系统运行效率,看起来就是发生卡顿。 如果没有空闲文件页和匿名页,并且也没有未分配的物理内存,就会触发OOM,根据算法选择占用物理内存较高的进程,然后将其杀死,以便释放内存资源,直到有足够的内存进行分配。 如何保护一个进程不被OOM杀掉:OOM killer选择被杀程序的时候通过计算一个分数来决定,分数越高越优先被杀 // points 代表打分的结果 // process_pages 代表进程已经使用的物理内存页面数 // oom_score_adj 代表 OOM 校准值 // totalpages 代表系统总的可用页面数 points = process_pages + oom_score_adj*totalpages/1000 oom_score_adj可设置的范围为[-1000, 1000],默认为0,只与进程占用的物理内存页数相关。可以通过设置oom_score_adj为 -1000,降低该进程被 OOM 杀死的概率。一般是特别重要的系统服务才会做这样的配置。 ...

九月 2, 2024 · by NOSAE

Redis

字符串 实现 使用SDS(简单动态字符串),SDS不仅可以保存字符串还可以保存二进制数据,获取长度的时间复杂度是O(1),SDS的API是安全的,比如拼接字符串不会造成缓冲区溢出,总的来说就是对数组封装,提供一系列方便操作的API。 字符串对象有三种编码:int、raw、embstr 整数:ptr从void*转换为long 短字符串(至于多短,每个redis版本不一样):分配一块连续空间保存redisObject和SDS 长字符串:分别为redisObject和SDS分配两个内存,redisObject.ptr指向SDS embstr如果要修改大小的话,只能重新分配空间。因此embstr实际上是只读的,当要修改embstr的长度,redis会先将其转换为raw再进行修改。 使用场景 缓存对象、常规计数(INCR)、分布式锁(SET-NX)、共享Session List 列表 List在使用上就是一个Deque双端队列,存字符串 实现 redis3.2之前:小列表使用压缩列表实现,大列表使用双向链表实现 redis3.2之后:使用quicklist实现 使用场景 消息队列: 保序:LPUSH+RPOP,但是RPOP需要轮询,浪费CPU性能,因此还提供了BRPOP阻塞式读取 处理重复:每条消息设置一个全局唯一ID,利用ID判断是否已经消费,List不会为消息生成ID,需要用户自己添加 保证可靠:BRPOPLPUSH,读取的同时将消息插入另一个List作为留存,如果用户处理消息时失败,下次从留存List重新读取 作为消息队列的缺点: 不支持消费者组 Hash 哈希 适合存储对象 实现 redis7.0之前:小Hash使用压缩列表,大Hash使用哈希表 redis7.0之后:小Hash使用listpack,大Hash使用哈希表 使用场景 缓存对象:一般可以用String+序列化存储对象,并将变化频繁的字段抽出来用Hash存储 Set 集合 实现 元素都是整数的小Set:整数集合 否则:哈希表 使用场景 点赞:保证每个用户只能点一次赞 共同关注(SINTER) 推荐关注(SDIFF) 抽奖(允许重复中奖SRANDMEMBER,不允许SPOP) 潜在风险:「并、交、差」的计算复杂度高,数据量大的情况下会阻塞redis。可以用从库进行计算,或交给客户端来自行处理,从而不阻塞主库 ZSet 有序集合 比Set多了一个score,按照score排序。ZSet不支持「差」运行 实现 redis7.0之前:小zset使用压缩列表,大zset使用跳表 redis7.0之后:小zset使用listpack,大zset使用跳表 使用场景 排行榜:score最大的前几个(ZREVRANGE),范围score内最小的前几个(ZRANGEBYSCORE) 电话排序:获取132、133开头的号码( ZRANGEBYLEX phone [132 (134 ),不要在分数不一致的 SortSet 集合中去使用 ZRANGEBYLEX和 ZREVRANGEBYLEX 指令,因为获取的结果会不准确 BitMap 实现 String 使用场景 签到统计:比如一年的签到只需要365个bit 判断登陆态:用户ID作为offset,如果ID是连续的,5000 万用户只需要 6 MB 的空间(5000万位) ...

九月 2, 2024 · by NOSAE

网络

tcp报文 tcp三次握手 客户端发送:SYN、随机序列号x 服务端发送:SYN、ACK、随机序列号y、确认应答号x+1 客户端发送:ACK,可以携带数据 tcp为什么不是两次握手 防止旧的SYN建立连接:如果只有两次握手,那么服务端收到SYN后直接进入established状态(此时可以发送数据),然后返回ack给客户端,如果这个SYN是旧的,那么最终客户端发现不是想要的ack,就会发送rst断开连接,那么服务端又要去断开已经建立好的连接,浪费资源。 如果是三次握手,那么服务端不会直接进入established。 同步序列号:初始化序列号是最重要的,所以客户端发送初始序列号x(第一次握手),客户端需要得知服务端已经收到并且服务端发送初始序列号y(第二次握手),服务端需要得知客户端已经收到(第三次握手) tcp keepalive keepalive是TCP保鲜定时器,链接空闲时的心跳机制。 当超过一段时间之后,TCP自动发送一个数据为空的报文给对方,如果对方回应了这个报文,说明对方还在线,链接可以继续保持,如果对方没有报文返回,并且重试了多次之后则认为链接丢失,没有必要保持链接。 tcp四次挥手 客户端发送:FIN 服务端发送:ACK 服务端发送:FIN 客户端发送:ACK 客户端进入TIME_WAIT,等待2MSL(报文最大生存时间) 其中,客户端一直收不到第三次握手FIN的话,客户端有两种情况: 对于客户端调用shutdown()的情况,只关闭发送数据不关闭接收数据,因此客户端死等 对于客户端调用close()的情况,同时关闭发送和接收数据,长时间收不到FIN就会主动close 服务端一直收不到第四次握手ACK的话(在这之前处于CLOSED_WAIT,并且服务端调用close(),发送了FIN),就会主动close。 tcp四次挥手客户端为什么要TIME_WAIT 原因: 等待历史连接的数据都已经在网络中自然消亡:如果没有TIME_WAIT,假设此时客户端建立新的连接,并收到了上个连接中延迟到达的报文,并且序列号恰好在客户端的滑动窗口内,那么则接收到了错误的数据。 保证服务端能正确关闭:等待足够的时间让ACK发到对面,如果由于网络原因服务端收不到的话就会重发FIN,客户端收到后重置计时器为2MSL,重传ACK。如果没用TIME_WAIT,客户端收到重传FIN的时候就会回一个RST,虽然服务端也能关闭,但是是将其解释为错误,可能会使用户迷惑。 滑动窗口 滑动窗口用于提高发送数据的速率以及流量控制,每个窗口的单位为1个MSS大小的数据(一个 TCP 报文的最大长度,为了避免超过MTU造成分片,因为丢失一个分片就得重传整个tcp报文) 滑动窗口就是一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。这样一来,就不用发一个数据就等一个ack,可以把窗口的数据连续发送了。 累计确认:ack=n表示序号为n之前的报文都收到了,就算之前的ack都丢失也没关系。 窗口大小:窗口的大小由接收方的窗口大小来决定,由接收方告诉自己还有多少缓冲区可以接收数据,即发送端窗口不能大于接收端窗口 tcp Nagle发送算法 解决发送数据量太小,头部占比很大,性价比很低(即糊涂窗口综合症)。伪代码如下: if 有数据要发送 { if 可用窗口大小 >= MSS and 可发送的数据 >= MSS { 立刻发送MSS大小的数据 } else { if 有未确认的数据 { 将数据放入缓存等待接收ACK } else { 立刻发送数据 } } } 根据代码,为了避免糊涂窗口综合症,需要:接收方「小窗口直接告诉发送方窗口为0」+ 发送方开启 Nagle 算法 拥塞控制 为了有了流量控制后,还需要拥塞控制?只需要考虑最极端的情况,如果发送方和接收方的传输和接收能力都是无限的,那么瓶颈就出现在网络中,如果无限制地发送,网络只会越来越拥塞,因此需要拥塞控制。 拥塞控制也基于滑动窗口,并加入「拥塞窗口」的概念,因此发送方窗口=min(接收方窗口,拥塞窗口)。 拥塞窗口如何增长: 慢启动:每收到一个ACK,拥塞窗口+1。拥塞窗口初始为1,第一次收到ACK,1+1=2,发送两个包,收到两个ACK,2+2=4, 8, 16…慢启动每轮发送是指数增长的。 拥塞避免:当慢启动超过阈值,每收到一个ACK,拥塞窗口+1/cwnd,总的来看就是每轮发送才+1,而不是每收到一个ACK+1.拥塞避免每轮发送是线性增长的。 拥塞窗口如何收缩(发生拥塞后): 拥塞发生:发生超时重传的时候,慢启动阈值设为cwnd/2,cwnd设为1 快速回复:发生快速重传的时候,慢启动阈值设为cwnd/2,cwnd设为慢启动阈值 tcp粘包解决 特殊字符作为消息结束符 自定义消息结构,比如在头部定义一个消息长度 tcp已经处于established服务端收到新的SYN 服务端会回复属于它的连接的ack,这样客户端发现不是自己想要的ack,就会回一个rst,然后服务端就会释放这个连接 ...

九月 2, 2024 · by NOSAE

在MacOS中运行k3s

k3s https://github.com/caicloud/kube-ladder 安装ubuntu家的multipass虚拟机 brew install multipass 创建一个虚拟机并启动 multipass launch -n k3s01 -c 2 -m 2G -d 16G 查看虚拟机 multipass list multipass info k3s01 进入虚拟机终端 multipass shell k3s01 设置root密码,更新apt sudo passwd apt-get update 数据卷,数据传输 multipass mount host-path vm-name:vm-path multipass umount vm-name multipass transfer host-file vm-name:vm-path 虚拟机操作 multipass start/stop/delete/purge vm-name 指定虚拟机初始化时的配置、执行的命令等 multipass launch -n vm-name --cloud-init config.yaml # config.yaml runcmd: - apt-get update # 更新包 - curl -sfL https://get.k3s.io | sh - # 安装k3s - echo "alias k=kubectl" >> ~/.bash_aliases 指定k3s启动时config的访问权限,使得在非root用户下也能kubectl管理k3s ...

九月 1, 2024 · by NOSAE

jekyll chrispy主题的语法

Headings H1 — heading {: .mt-4 .mb-0 } H2 — heading {: data-toc-skip=’’ .mt-4 .mb-0 } H3 — heading {: data-toc-skip=’’ .mt-4 .mb-0 } H4 — heading {: data-toc-skip=’’ .mt-4 } Paragraph Quisque egestas convallis ipsum, ut sollicitudin risus tincidunt a. Maecenas interdum malesuada egestas. Duis consectetur porta risus, sit amet vulputate urna facilisis ac. Phasellus semper dui non purus ultrices sodales. Aliquam ante lorem, ornare a feugiat ac, finibus nec mauris. Vivamus ut tristique nisi. Sed vel leo vulputate, efficitur risus non, posuere mi. Nullam tincidunt bibendum rutrum. Proin commodo ornare sapien. Vivamus interdum diam sed sapien blandit, sit amet aliquam risus mattis. Nullam arcu turpis, mollis quis laoreet at, placerat id nibh. Suspendisse venenatis eros eros. ...

八月 31, 2024 · by NOSAE