本站提倡有节制游戏,合理安排游戏时间,注意劳逸结合。

【js语音插件源码】【步数修改源码】【小程序代理源码】cgroup 源码

2024-11-18 22:49:22 来源:热点 分类:热点

1.Linux系统的源码OOM Killer处理机制
2.如何计算容器的CPU使用值
3.华为openEuler21.03版本怎么样
4.sysctl 参数防篡改 - 基于 ebpf 的实现 [一]
5.Linux内核源码解析---cgroup实现之整体架构与初始化
6.Linux 中断( IRQ / softirq )基础:原理及内核实现

cgroup 源码

Linux系统的OOM Killer处理机制

       最近有位 VPS 客户抱怨 MySQL 无缘无故挂掉,还有位客户抱怨 VPS 经常死机,源码登陆到终端看了一下,源码都是源码常见的 Out of memory 问题。这通常是源码因为某时刻应用程序大量请求内存导致系统内存不足造成的,这通常会触发 Linux 内核里的源码js语音插件源码 Out of Memory (OOM) killer,OOM killer 会杀掉某个进程以腾出内存留给系统用,源码不致于让系统立刻崩溃。源码如果检查相关的源码日志文件(/var/log/messages)就会看到下面类似的 Out of memory: Kill process 信息:

       ...

       Out of memory: Kill process (mysqld) score 9 or sacrifice child

       Killed process , UID , (mysqld) total-vm:kB, anon-rss:kB, file-rss:kB

       m: mit memory)的办法来间接利用这部分 “空闲” 的内存,提高整体内存的源码使用效率。一般来说这样做没有问题,源码但当大多数应用程序都消耗完自己的源码内存的时候麻烦就来了,因为这些应用程序的源码内存需求加起来超出了物理内存(包括 swap)的容量,内核(OOM killer)必须杀掉一些进程才能腾出空间保障系统正常运行。源码用银行的源码例子来讲可能更容易懂一些,部分人取钱的时候银行不怕,银行有足够的存款应付,当全国人民(或者绝大多数)都取钱而且每个人都想把自己钱取完的时候银行的麻烦就来了,银行实际上是没有这么多钱给大家取的。

       内核检测到系统内存不足、挑选并杀掉某个进程的过程可以参考内核源代码 linux/mm/oom_kill.c,当系统内存不足的时候,out_of_memory() 被触发,然后调用 select_bad_process() 选择一个 “bad” 进程杀掉,如何判断和选择一个 “bad” 进程呢,总不能随机选吧?挑选的过程由 oom_badness() 决定,挑选的算法和想法都很简单很朴实:最 bad 的那个进程就是那个最占用内存的进程。

       /

**

       * oom_badness - heuristic function to determine which candidate task to kill

       * @p: task struct of which task we should calculate

       * @totalpages: total present RAM allowed for page allocation

       

*

       * The heuristic for determining which task to kill is made to be as simple and

       * predictable as possible. The goal is to return the highest value for the

       * task consuming the most memory to avoid subsequent oom failures.

       */

       unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,

       const nodemask_t *nodemask, unsigned long totalpages)

       {

       long points;

       long adj;

       if (oom_unkillable_task(p, memcg, nodemask))

       return 0;

       p = find_lock_task_mm(p);

       if (!p)

       return 0;

       adj = (long)p-signal-oom_score_adj;

       if (adj == OOM_SCORE_ADJ_MIN) {

       task_unlock(p);

       return 0;

       }

       /

*

       * The baseline for the badness score is the proportion of RAM that each

       * task's rss, pagetable and swap space use.

       */

       points = get_mm_rss(p-mm) + p-mm-nr_ptes +

       get_mm_counter(p-mm, MM_SWAPENTS);

       task_unlock(p);

       /

*

       * Root processes get 3% bonus, just like the __vm_enough_memory()

       * implementation used by LSMs.

       */

       if (has_capability_noaudit(p, CAP_SYS_ADMIN))

       adj -= ;

       /* Normalize to oom_score_adj units */

       adj *= totalpages / ;

       points += adj;

       /

*

       * Never return 0 for an eligible task regardless of the root bonus and

       * oom_score_adj (oom_score_adj can't be OOM_SCORE_ADJ_MIN here).

       */

       return points 0 ? points : 1;

       }

       上面代码里的注释写的很明白,理解了这个算法我们就理解了为啥 MySQL 躺着也能中枪了,因为它的体积总是最大(一般来说它在系统上占用内存最多),所以如果 Out of Memeory (OOM) 的话总是不幸第一个被 kill 掉。解决这个问题最简单的办法就是增加内存,或者想办法优化 MySQL 使其占用更少的内存,除了优化 MySQL 外还可以优化系统(优化 Debian 5,优化 CentOS 5.x),让系统尽可能使用少的内存以便应用程序(如 MySQL) 能使用更多的内存,还有一个临时的办法就是调整内核参数,让 MySQL 进程不容易被 OOM killer 发现。

       我们可以通过一些内核参数来调整 OOM killer 的行为,避免系统在那里不停的杀进程。比如我们可以在触发 OOM 后立刻触发 kernel panic,kernel panic 秒后自动重启系统。

       # sysctl -w vm.panic_on_oom=1

       vm.panic_on_oom = 1

       # sysctl -w kernel.panic=

       kernel.panic =

       # echo "vm.panic_on_oom=1" /etc/sysctl.conf

       # echo "kernel.panic=" /etc/sysctl.conf

       从上面的 oom_kill.c 代码里可以看到 oom_badness() 给每个进程打分,根据 points 的高低来决定杀哪个进程,这个 points 可以根据 adj 调节,root 权限的进程通常被认为很重要,不应该被轻易杀掉,所以打分的时候可以得到 3% 的优惠(adj -= ; 分数越低越不容易被杀掉)。我们可以在用户空间通过操作每个进程的 oom_adj 内核参数来决定哪些进程不这么容易被 OOM killer 选中杀掉。比如,如果不想 MySQL 进程被轻易杀掉的话可以找到 MySQL 运行的进程号后,调整 oom_score_adj 为 -(注意 points 越小越不容易被杀):

       # ps aux | grep mysqld

       mysql 1.6 2.1 ? Ssl : 0: /usr/sbin/mysqld

       # cat /proc//oom_score_adj

       0

       # echo - /proc//oom_score_adj

       当然,如果需要的话可以完全关闭 OOM killer(不推荐用在生产环境):

       # sysctl -w vm.overcommit_memory=2

       # echo "vm.overcommit_memory=2" /etc/sysctl.conf

       我们知道了在用户空间可以通过操作每个进程的 oom_adj 内核参数来调整进程的分数,这个分数也可以通过 oom_score 这个内核参数看到,比如查看进程号为的步数修改源码 omm_score,这个分数被上面提到的 omm_score_adj 参数调整后(-),就变成了3:

       # cat /proc//oom_score

       

       # echo - /proc//oom_score_adj

       # cat /proc//oom_score

       3

       下面这个 bash 脚本可用来打印当前系统上 oom_score 分数最高(最容易被 OOM Killer 杀掉)的进程:

       # vi oomscore.sh

       #!/bin/bash

       for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do

       printf "%2d %5d %sn"

       "$(cat $proc/oom_score)"

       "$(basename $proc)"

       "$(cat $proc/cmdline | tr '' ' ' | head -c )"

       done 2/dev/null | sort -nr | head -n

       # chmod +x oomscore.sh

       # ./oomscore.sh

        /usr/sbin/mysqld

       4 -bash

       4 -bash

       1 sshd: root@pts/6

       1 sshd: vpsee [priv]

       1 -bash

       1 sudo -i

       1 sshd: root@pts/3

       1 sshd: vpsee [priv]

       1 /usr/sbin/sshd -D

如何计算容器的CPU使用值

       å› ä¸ºç›‘控系统调整需要,需要从宿主机获取容器的 CPU 使用率。

        以前在给容器分配 CPU 资源的时候,是绑定指定 CPU 的方式,那宿主只要计算不同容器绑定的 CPU 使用率即可。但是最近对 CPU 资源的分配方式进行了调整,通过 CPU使用时间 的方式对 CPU 使用率进行限制。(通过 CPU使用时间 限制有不少优势,另外写文章介绍。)

        原来的方法不再适用。既然 Cgroup 可以通过 CPU 时间对 CPU 资源进行限制,那必然在某个地方会统计 CPU 的使用时间。于是我在网络上搜索了一番,大部分的结果都是告诉我可以通过以下命令获取容器的 CPU 使用率。

        显然,现在一旦说到容器,基本上都会认为说的是 docker,其实我用的是LXC。不过不管怎样,计算方法应该是一致的。

        摸索一番,发现在以下路径就能找到一个容器(这里是LXC)的 CPU 使用时间,时间单位是纳秒:

        利用这个时间,再计算实际经过的时间,就能得出在一段时间内,CPU的使用率。

        PS. 通过这个方法,不仅能计算整个 CPU 使用率,还可以计算出用户态和内核态分别使用的情况,在特定情况会更有助于了解应用程序的使用情况。(见 cpuacct.usage_sys 和 cpuacct.usage_user )

        CPU使用时间就是上一节文中提到的cgroup文件下的 cpuacct.usage 文件里的时间。

        当前时间,以纳秒计算,可以通过以下函数获取:

        只要两个时间点的当前时间相减,就可以得到总共经过的时间了。

        这个程序的源码也可以贴出来,有需要的朋友也可以去Github上可克隆:

       /aaron/lxc-cpu-usage

华为openEuler.版本怎么样

       华为 openEuler .是华为发布的全新创新桌面选择。该版本属于全新的创新版本而不是 LTS (Long Term Support) 版本。全新的创新桌面为用户们带来的是更强的虚拟化功能和更多桌面选择支持。

       华为 openEuler .介绍

全新的5.内核

       1、深度优化调度、IO、内存管理,提供Arm、x、RISC-V等更多计算能力支持。

       2、对调度程序进行了优化,以优化CFS任务的公平性,并添加了感知numa的异步调用机制,这在NVDIMM初始化方面有了显著的改进;优化了SCHED_IDLE的调度策略,显著提高了高优先级任务的调度延迟,降低了其他任务干扰的成本。

       3、numa平衡机制的优化带来了更好的亲和性、更高的利用率和更少的无效迁移。

       4、增强CPU隔离机制,支持中断隔离,支持非绑定线程隔离,增强CPU内核隔离,更好地避免业务间相互干扰。

       5、优化了cgroup单线程迁移的性能,消除了对线程组读写信号量的依赖;时间名称空间的引入使得容器迁移更加方便。

       6、系统容器支持限制容器中使用的文件句柄的数量。文件句柄包括普通的文件句柄和网络套接字。在启动容器时,可以指定——files-limit参数来限制容器中打开句柄的最大数量。

       7、PSI能力支持。PSI (Pressure Stall Information)提供了一种评估系统资源(如CPU、内存和IO)压力的方法。准确的检测方法可以帮助资源用户确定合适的工作量,也可以帮助系统制定高效的资源调度策略,最大限度地利用系统资源,最大限度地提高用户体验。

       8、进程间通信优化,pipe/epoll_wait唤醒机制优化,解决唤醒多个等待线程的性能问题。

       9、增强内存管理、细化内存控制、统计、异构内存、热插拔、内存初始化等功能得到了改进,小程序代理源码并提供了更有效的用户控制界面;热点锁和信号量优化,激进的内存调节和碎片整理,优化、vmap/vmalloc机制,显著提高内存应用效率;KASAN, kmemleak, slub_debug, oom和其他内存维护功能都得到了增强,以提高内存问题的定位和解决效率。

       、提前发包时间模式开关解决了原有TCP框架在报文发送过程中的局限性。根据调度策略,为数据包设置EDT时间戳,避免了队列缓冲区过大造成的延迟,从而大大提高了TCP的性能。

       、支持多路径TCP,可以提高移动和数据场景下的性能和可靠性,支持负载均衡场景下多个子流并行传输。

       、引入了log fast commit方法,而EXT4引入了一种新的、更轻量级的日志记录方法——fast commit,它可以极大地加快耗时的操作,比如fsync,并带来更好的性能。

       、支持dm写cache特性,提高SSD大容量顺序写性能,提高DDR持久化内存性能。

       、io_uring是一个新的异步IO框架和实现,支持轮询模式。在轮询模式下,性能得到了与spdk类似的显著提高,队列深度越高,性能越好。

       、支持ILP,在鲲鹏 Arm环境下支持位应用程序。

       、IMA商业增强,基于开源IMA解决方案,增强安全性,提高性能,提高易用性,并帮助商业实现。

       、支持单任务栈巡检,增强对ROP攻击的防范能力。

       、MPAM资源管理和控制,支持Arm架构的缓存QoS和内存带宽控制技术。

       、支持基于sedim的NMI机制和基于pmu的NMI机制,实现硬锁检测;启用perf nmi可以实现更准确的性能分析。

       、web项目案例源码Arm平台支持虚拟机CPU热插拔,提高了资源配置的灵活性。

       、Arm kdump得到了增强,支持4G以上地址的内存预留,这样kdump可以预留更多的内存空间,并支持具有更大内存的机器。

       、支持树莓派系列板。Raspberry Pi的支持已经集成到本地的openEuler .内核中,可以直接使用openEuler .内核源代码进行调试。

       、RISC-V平台支持KVM虚拟化。

       、支持智能网卡。

热内核升

       1、热内核升级是修复和升级操作系统漏洞的解决方案。实现了无服务意识的内核快速热替换。

       2、Cpu Park和Quick Kexec的功能加速系统启动和停止,减少停机时间,提高系统可用性。

       3、Pin存储器和Pmem功能可确保快速准确地恢复业务流程,并提高业务灵活性。

       4、内核热升级控制器提供gRPC通讯接口,使用方便。

内存分层

       1、支持多内存和存储介质的统一管理,支持系统容量的平滑扩展。

       2、冷热页识别,通过内核态内存页的空闲和空闲统计机制,准确识别进程内存页访问的冷热分布。

       3、可以配置淘汰策略,提供配置界面,自定义内存页面冷热分类策略。

       4、平滑扩展,冷页自动切换到扩展内存,其上部署的软件兼容运行,无需改变或调整编程模式。

       5、支持多媒体扩展,支持单片机、XL Flash、NVMe SSD等介质作为扩展内存,并根据介质本身的访问速度指定冷热内存分层方案,以达到扩展内存、降低性能损失的目的。

       6、增强的python sorted. 源码虚拟化能力和可维护的测量能力。

       7、增加了热迁移Pro的能力扩展,提高了可维护性和可测性。

       8、热迁移专业版功能,增强热迁移多功能支持TLS,确保迁移过程中的数据安全;支持热迁移数据的并行压缩,提高迁移性能;增加数据页面访问频率的统计,支持热迁移数据的早期预测。

       9、vmtop性能调试工具可以实时动态查看虚拟机的资源使用情况,包括CPU占用率、内存占用率等信息。增加了支持x_架构的扩展。

       、支持IO挂起。默认情况下,当发生io错误时,IO会自动重试,超时后会报告警报。

       、RISC-V架构支持虚拟化热迁移。

轻量级虚拟运行

       1、添加灵活的内存,大页面功能,系统调用过滤功能,增强IO子系统,提高性能和稳定性。

       2、灵活的内存支持,根据工作负载的内存需求实现内存分配和恢复,virtio-balloon的内存恢复速度可达3GB/s。

       3、大页面支持。在轻量级框架中提供大页面支持,可以为轻量级虚拟机提供连续的物理内存页面,提高虚拟机的内存访问效率。

       4、系统调用过滤简化了设备模型,增加了对系统调用过滤的支持,只需要最简单配置的个系统调用,有效减少了系统攻击面。

       5、输入输出子系统得到增强,以支持多通道并发输入输出能力并提高性能。支持IO- qos能力,增强虚拟机IO流量管理的灵活性和稳定性。open stack 维多利亚集成。

       6、简单、可扩展、丰富、统一的云管理操作系统。更多功能见OpenStack Victoria官方发行说明。

       7、通过集成openStack Vicoria版本实现IaaS(基础设施即服务)解决方案。

       8、增强数据块存储的服务能力,并添加容量扩展、快照和虚拟机映像克隆等高级功能。

       9、增强集装箱化部署和网络能力,以便更好地与集装箱集成。

       、添加扩展服务支持,并支持扩展服务,如控制面板管理、裸机部署和云资源跟踪。

Kubernetes 1.集成

       1、有关云本机操作系统自动部署、扩展和管理容器化应用程序的更多功能,请参考Kubernetes 1.的官方发行说明。

       2、自动联机和回滚。Kubernetes会自动将应用程序或其配置更改的实例联机,并监控应用程序的运行状态。如果失败,将回滚以前的更改。

       3、服务发现和负载均衡,支持基于容器IP和DNS名称的服务发现和负载均衡。

       4、存储业务流程支持自动挂载多个存储后端,如本地存储、NFS、iSCSI、Gluster、Ceph等网络存储系统。

       5、水平扩展,支持命令行扩展,用户界面手动扩展,根据CPU占用率自动扩展。

sysctl 参数防篡改 - 基于 ebpf 的实现 [一]

       系统调用参数防篡改 - 基于ebpf的实现

       本文基于内核代码版本5..0进行讨论。

       ebpf能够修改某些函数的返回值,但仅限于允许错误注入的函数,这限制了其应用范围。系统tap能够作用于几乎任何函数,但由于内核API的不稳定,它在不同内核上可能无法运行。

       ebpf的优势在于其与内核交互的API保持稳定,特别是用于“系统调用参数防篡改”的一组helper函数。在阅读代码实现时,发现中段插入了一个“BPF_CGROUP_RUN_PROG_SYSCTL”。

       这一功能源于v5.2-rc1的commit,旨在限制容器对sysctl的错误写入,要求内核版本不低于5.2,且配置项包含“CONFIG_CGROUP_BPF”。

       配套的4个helper函数记录在内核文档“Documentation/bpf/prog_cgroup_sysctl.rst”中,用于读取sysctl参数名称和值、在参数修改时获取写入的值以及覆盖准备写入的值。这些helper与内核原生路径中的过滤函数交互。

       使用示例

       通过Linux内核源码中的“tools/testing/selftests/bpf”目录下的测试用例可以学习ebpf的使用。在源码根目录下执行make命令编译。

       针对sysctl部分,测试用例主体为“test_sysctl.c”,用于将ebpf程序加载至内核,并在对应的点位上附加。ebpf程序可以是直接以ebpf汇编语法写的,也可以是C文件编译成.o二进制文件的形式。

       当判断为write操作时,返回0,内核源码中决定sysctl参数读写结果的点位返回“-EPERM”,使得修改不成功。

       ebpf prog源文件中的“SEC”宏定义用于指示编译器将函数/变量放在特定的section中,便于用户态loader查找和解析。

       为了实现加载和附加程序,使用了“sysctl_write_deny_prog.o”作为附加程序,类型为“BPF_CGROUP_SYSCTL”,方式为“BPF_F_ALLOW_OVERRIDE”。借助“fd”这样的整形数字,用户态程序可以深入内核态获取对应的结构体实例。

       最终通过libbpf封装系统调用接口,用户态程序可以通过“bpf”系统调用入口与内核交互。使用strace工具可以追踪这一过程。

       了解ebpf helper函数的使用,可以借助现成的工具进行学习,更多详情请参考后续文章。

Linux内核源码解析---cgroup实现之整体架构与初始化

       cgroup在年由Google工程师开发,于年被融入Linux 2.6.内核。它旨在管理不同进程组,监控一组进程的行为和资源分配,是Docker和Kubernetes的基石,同时也被高版本内核中的LXC技术所使用。本文基于最早融入内核中的代码进行深入分析。

       理解cgroup的核心,首先需要掌握其内部的常用术语,如子系统、层级、cgroupfs_root、cgroup、css_set、cgroup_subsys_state、cg_cgroup_link等。子系统负责控制不同进程的行为,例如CPU子系统可以控制一组进程在CPU上执行的时间占比。层级在内核中表示为cgroupfs_root,一个层级控制一批进程,层级内部绑定一个或多个子系统,每个进程只能在一个层级中存在,但一个进程可以被多个层级管理。cgroup以树形结构组织,每一棵树对应一个层级,层级内部可以关联一个或多个子系统。

       每个层级内部包含的节点代表一个cgroup,进程结构体内部包含一个css_set,用于找到控制该进程的所有cgroup,多个进程可以共用一个css_set。cgroup_subsys_state用于保存一系列子系统,数组中的每一个元素都是cgroup_subsys_state。cg_cgroup_link收集不同层级的cgroup和css_set,通过该结构可以找到与之关联的进程。

       了解了这些概念后,可以进一步探索cgroup内部用于结构转换的函数,如task_subsys_state、find_existing_css_set等,这些函数帮助理解cgroup的内部运作。此外,cgroup_init_early和cgroup_init函数是初始化cgroup的关键步骤,它们负责初始化rootnode和子系统的数组,为cgroup的使用做准备。

       最后,需要明确Linux内一切皆文件,cgroup基于VFS实现。内核启动时进行初始化,以确保系统能够正确管理进程资源。cgroup的初始化过程分为早期初始化和常规初始化,其中早期初始化用于准备cpuset和CPU子系统,确保它们在系统运行时能够正常工作。通过这些步骤,我们可以深入理解cgroup如何在Linux内核中实现资源管理和进程控制。

Linux 中断( IRQ / softirq )基础:原理及内核实现

       中断(IRQ),尤其是软中断(softirq)的广泛用途之一是网络数据包的接收与发送,但其应用场景并非单一。本文将全面整理中断(IRQ)与软中断(softirq)的基础知识,这些内容与网络数据包处理虽无直接联系,但整理本文旨在更深入地理解网络数据包处理机制。

       什么是中断?

       CPU 通过时分复用处理多任务,其中包括硬件任务,如磁盘读写、键盘输入,以及软件任务,如网络数据包处理。CPU 在任何时刻只能执行一个任务。当某个硬件或软件任务当前未被执行,但希望CPU立即处理时,会向CPU发送中断请求——希望CPU暂停手头工作,优先服务“我”。中断以事件形式通知CPU,因此常看到“在XX条件下会触发XX中断事件”的表述。

       中断分为两类:

       管理中断的设备:Advanced Programmable Interrupt Controller(APIC)。

       硬中断的中断处理流程

       中断随时发生,处理流程如下:

       Maskable and non-maskable

       Maskable interrupts 在x_上可以通过sti/cli指令来屏蔽(关闭)和恢复:

       在屏蔽期间,这种类型的中断不会触发新的中断事件。大部分IRQ都属于这种类型。例如,网卡的收发包硬件中断。

       Non-maskable interrupts 不可屏蔽,因此属于更高优先级的类型。

       问题:执行速度与逻辑复杂性之间的矛盾

       IRQ处理器的两个特点如下:

       存在内在矛盾。

       解决方式:中断的推迟处理(deferred interrupt handling)

       传统解决方式是将中断处理分为两部分:

       这种方式称为中断的推迟处理或延后处理。现在已是一个通用术语,涵盖各种推迟执行中断处理的方式。中断分为两部分处理:

       在Linux中,有三种推迟中断(deferred interrupts):

       具体细节将在后续介绍。

       软中断与软中断子系统

       软中断是内核子系统的一部分:

       每个CPU上会初始化一个ksoftirqd内核线程,负责处理各种类型的softirq中断事件;

       使用cgroup ls或ps -ef都能看到:

       软中断事件的handler提前注册到softirq子系统,注册方式为open_softirq(softirq_id, handler)

       例如,注册网卡收发包(RX/TX)软中断处理函数:

       软中断占用了CPU的总开销:可以使用top查看,第三行倒数第二个指标是系统的软中断开销(si字段):

       Linux内核源码分析学习地址:ke.qq.com/course/...

       文章福利小编推荐自己的Linux内核源码分析交流群:点击加入整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!

       主处理

       smpboot.c类似于事件驱动的循环,会调度ksoftirqd线程执行pending的软中断。ksoftirqd内部会进一步调用到__do_softirq,

       避免软中断占用过多CPU

       软中断的潜在影响:推迟执行的部分(如softirq)可能会占用较长时间,在这段时间内,用户空间线程只能等待。反映在top中,si占比。

       不过softirq调度循环对此有所改进,通过budget机制来避免softirq占用过多CPU时间。

       硬中断-软中断调用栈

       softirq是一种推迟中断处理机制,将IRQ的大部分处理逻辑推迟在这里执行。有两条路径都会执行到softirq主处理逻辑__do_softirq():

       1、CPU调度到ksoftirqd线程时,会执行到__do_softirq();

       2、每次IRQ handler退出时:do_IRQ()->...

       do_IRQ是内核中主要的IRQ处理方式。它执行结束时,会调用exiting_irq(),这会展开成irq_exit()。后者会检查是否pending有softirq,如果有,则唤醒:

       进而会使CPU执行到__do_softirq。

       软中断触发执行的步骤

       总结,每个软中断会经过以下阶段:

       以收包软中断为例,IRQ handler并不执行NAPI,只是触发它,在内部会执行到raiseNET_RX_SOFTIRQ;真正的执行在softirq,会调用网卡的poll()方法收包。IRQ handler中会调用napi_schedule(),然后启动NAPI poll()。

       需要注意的是,虽然IRQ handler所做的工作很少,但处理这个包的softirq和IRQ在同一CPU上运行。这意味着,如果大量的包都放在同一个RX队列,虽然IRQ开销可能不多,但该CPU仍然会非常繁忙,都花费在softirq上。解决方式:RPS。它不会降低延迟,只是将包重新分配:RXQ->CPU。

       三种推迟执行方式(softirq/tasklet/workqueue)

       提到,Linux中的三种推迟中断执行方式:

       其中:

       前面已经看到,Linux在每个CPU上创建了一个ksoftirqd内核线程。

       softirqs是在Linux内核编译时确定的,例如网络收包对应的NET_RX_SOFTIRQ软中断。因此是一种静态机制。如果想添加一种新softirq类型,需要修改并重新编译内核。

       内部组织

       内部由一个数组(或称为向量)管理,每个软中断号对应一个softirq handler。数组与注册:

       在5.中所有类型的softirq:

       也就是在cat /proc/softirqs看到的哪些。

       触发(唤醒)softirq

       以收包软中断为例,IRQ handler并不执行NAPI,只是触发它,在内部会执行到raiseNET_RX_SOFTIRQ;真正的执行在softirq,会调用网卡的poll()方法收包。IRQ handler中会调用napi_schedule(),然后启动NAPI poll()。

       如果对内核源码有一定了解,会发现softirq使用非常有限,原因之一是它是静态编译的,依赖内置的ksoftirqd线程来调度内置的9种softirq。如果想添加一种新功能,就得修改并重新编译内核,开发成本很高。

       实际上,实现推迟执行的更常用方式是tasklet。它构建在softirq机制之上,具体来说就是使用了两种softirq:

       换句话说,tasklet是在运行时(runtime)创建和初始化的softirq,

       内核软中断子系统初始化了两个per-cpu变量:

       tasklet再执行针对list的循环:

       tasklet在内核中的使用非常广泛。不过,后面又出现了第三种方式:workqueue。

       这也是一种推迟执行机制,与tasklet有些相似,但有显著不同。

       使用场景

       简而言之,workqueue子系统提供了一个接口,通过该接口可以创建内核线程来处理从其他地方enqueue过来的任务。这些内核线程称为worker threads,内置的per-cpu worker threads:

       结构体

       kworker线程调度workqueues,原理与ksoftirqd线程调度softirqs类似。然而,我们可以为workqueue创建新的线程,而softirq则不行。

       参考资料引用链接

       [1]

       中断与中断处理:0xax.gitbooks.io/linux-...

       作者:赵亚楠 原文:arthurchiao.art/blog/li...来源:云原生实验室

相关推荐
一周热点