1.å¦ä½è®¡ç®å®¹å¨çCPU使ç¨å¼
2.Linux系统的源码OOM Killer处理机制
3.Linux 中断( IRQ / softirq )基础:原理及内核实现
4.Linux内核源码解析---cgroup实现之整体架构与初始化
å¦ä½è®¡ç®å®¹å¨ç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
Linux系统的OOM Killer处理机制
最近有位 VPS 客户抱怨 MySQL 无缘无故挂掉,还有位客户抱怨 VPS 经常死机,源码登陆到终端看了一下,源码都是源码常见的 Out of memory 问题。这通常是源码因为某时刻应用程序大量请求内存导致系统内存不足造成的,这通常会触发 Linux 内核里的源码桃娃源码搭建 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 -= ; 分数越低越不容易被杀掉)。我们可以在用户空间通过操作每个进程的gdal源码解读 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
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内核源码分析交流群:点击加入整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的035源码笔记可以自行添加哦!
主处理
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...来源:云原生实验室
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内核中实现资源管理和进程控制。