关于 Linux 系统进程的几个重要的概念

本文主要介绍几个系统中比较重要的概念。

我们倡导面对任何问题的时候,先要弄明白系统的设计目标。

 

1、进程的调度  --  吞吐 和 响应

我们在思考一个系统的调度器时,要理解任何操作系统的调度器设计只追求两个目标:吞吐率大低延时

吞吐率大:势必要把更多的时间花费在 真实的有用功 上,而不是把时间浪费在频繁的 进程上下文切换 上。

低延时:要求把优先级高的进程可以随时抢占进来,打断别人,强行插队。但是抢占会引起上下文切换,

              上下文切换的时间,本身对吞吐率来说,是一个消耗,这个消耗可以降低到 2us 或者更低(这看起来没什么?),

              但是上下文切换更大的消耗不是切换本身,而是切换会引起大量的cache miss。你明明weibo跑的很爽,现在切过去微信,那么CPU的cache是不太容易命中微信的。

不抢肯定响应差,抢了吞吐会下降。Linux不是一个完全照顾吞吐的系统,也不是一个完全照顾响应的系统,它作为一个软实时的操作系统,

实际上是想达到某种平衡,同时也提供给用户一定的配置能力,在内核编译的时候,Kernel Features  --->  Preemption Model

选项实际上可以让我们编译内核的时候,是倾向于支持吞吐,还是支持响应:

越往上面选,吞吐越好,越好下面选,响应越好。服务器你一个月也难得用一次鼠标,而桌面则显然要求一定的响应,这样可以保证UI行为的表现较好。但是Linux即便选择的是最后一个选项“Preemptible Kernel (Low-Latency Desktop)”,它仍然不是硬实时的。因为,在Linux有三类区间是不可以抢占调度的,这三类区间是:

  • 中断

  • 软中断

  • 持有类似spin_lock这样的锁而锁住该CPU核调度的情况 (该核的调度会被关掉)

如下图,一个绿色的普通进程在T1时刻持有spin_lock进入一个critical section(该核调度被关),绿色进程T2时刻被中断打断,而后T3时刻IRQ1里面唤醒了红色的RT进程(如果是硬实时RTOS,这个时候RT进程应该能抢入),之后IRQ1后又执行了IRQ2,到T4时刻IRQ1和IRQ2都结束了,红色RT进程仍然不能执行(因为绿色进程还在spin_lock里面),直到T5时刻,普通进程释放spin_lock后,红色RT进程才抢入。从T3到T5要多久,鬼都不知道,这样就无法满足硬实时系统的“可预期”的确定性的延迟性,因此Linux不是硬实时操作系统。

 

Linux的preempt-rt补丁试图把中断、软中断线程化,变成可以被抢占的区间,而把会关本核调度器的spin_lock替换为可以调度的mutex,它实现了在T3时刻唤醒RT进程的时刻,RT进程可以立即抢占调度进入的目标,避免了T3-T5之间延迟的非确定性。

 

2、进程的类型 --  CPU 消耗性  和 IO 消耗型

IO 消耗型进程 :(狂睡,等I/O,CPU利用率低),等待I/O请求,提交I/O请求。比如系统读写文件(磁盘I/O),鼠标,键盘都属于。I/O消耗型任务对延迟比较敏感,应该被优先调度。

CPU消耗型进程:(狂算,CPU利用率高),优先级比IO进程低。

比如,你正在疯狂编译安卓,而等鼠标行为的用户界面老不工作(正在狂睡),但是鼠标一点,我们应该优先打断正在编译的进程,而去响应鼠标这个I/O,这样电脑的用户体验才符合人性。

 

3、进程的内存  -- 分配和占据

Linux 作为一个把应用程序员当傻逼的操作系统,它必须允许应用程序犯错。

所以这类问题就不要问了:

进程malloc了内存,还没有free就挂了,那么我前面分配的内存没有释放,是不是就泄露掉了?

       明确的说,这是不可能的,Linux内核如果这么傻,它是无法应付乱七八糟各种开源有漏洞的软件的,所以进程死的时候,肯定是资源被内核释放掉了,这类傻问题,你明白linux的出发点,就不会再去问了。

同样的,你在应用程序里面malloc成功的那一刻,你真的拿到内存了吗?

       malloc成功,不要以为你真的拿到了内存,这个时候你的 vss ( 虚拟地址空间 ,virtual set size ) 会增大,但是你的 rss ( 驻留在内存条上的内存,占用的物理内存,resident set size ) 会随着写到每一页而缓慢增大,所以分配成功的那一刻,你顶多是被忽悠了,和你实际占用还是不占用,暂时没有半毛钱的关系。

举例来说明,如下图,最初的堆是8KB, 这8KB 也写过了,所以堆的 vss 和 rss 都是 8KB 。此后,我们调用 brk() 把堆变大到 16KB, 但是实际上它占据的内存 rss 还是 8KB ,因为第3页还没有写,根本没有真正的从内存条上拿到内存。直到写到第3页,堆的 rss 才变成 12KB, 这就是 linux 针对 app 的 lazy 分配机制,它的出发点,当然也是防止应用程序傻逼了。

 

代码段的内存、堆的内存、栈的内存都是这样懒惰地拿到,demanding page。

我们有一台1GB内存的32位Linux系统,我们关闭swap,同时透过修改overcommit_memory为1来允许申请不超过进程虚拟地址空间的内存:

$ sudo swapoff -a

$ sudo sh -c 'echo 1 >/proc/sys/vm/overcommit_memory'

此后,我们的应用可以申请一个超级大的内存(比实际内存还大):

上述程序在1GB的电脑上面运行,申请2GB内存可以申请成功,但是在写到一定程度后,系统出现out-of-memory,上述程序对应的进程作为oom_score最大(最该死的)的进程被系统杀死。

 

4、进程的资源 -- 隔离和共享

Linux 的 某一个进程究竟耗费了多少内存?
 

这个问题很复杂,除了上面的 vss  ,rss 外,还有 pss 和 uss ,这些都是 Linux  不同于 RTOS 的显著特点之一。Linux 各个进程既要做到隔离,但是隔离中又要实现共享,比如1000个进程都要用到 libc ,libc的代码段显然在内存中只有1份。

下面的一幅图上有3个进程,pid为1044的 bash、pid为1045的 bash和pid为1054的 cat。每个进程透过自己的页表,把虚拟地址空间指向内存条上面的物理地址,每次切换一个进程,即切换一份独特的页表。

 

仅从此图而言,进程1044的 vss 和 rss 分别是:

vss= 1+2+3 

rss= 4+5+6

但是是不是“4+5+6”就是1044这个进程耗费的内存呢?这显然也是不准确的,因为4明显被3个进程指向,5明显被2个进程指向,坏事是大家一起干的,不能1044一个人背黑锅。

这个时候,就衍生出了一个pss(按比例计算的驻留内存, Proportional Set Size )的概念,仅从这一幅图而言,进程1044的pss为:

pss= 4/3 +5/2 +6

最后,还有进程1044独占且驻留的内存 uss(Unique Set Size ),仅从此图而言,

uss = 6

所以,分析Linux,我们不能模棱两可地停留于表面,或者想当然地说:“Linux的进程耗费了多少内存?”因为这个问题,又是一个要靠装逼来回答的问题,“dependon…”。

 

本文参考:

https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652662197&idx=1&sn=8c75937824893e26ae920ed515a15d4c&chksm=810f2f28b678a63eab7c2b889c2a25967ce0f06719692ff6a29bfa0e843c7d49b59e52fefb96&scene=21#wechat_redirect

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值