多任务和 CPU 时间片

前面说了 Liunx 是多用户多任务的,所谓的多任务就是多个进程“同时”执行。比如,同时开多个软件(进程),对于用户来说好像每个软件(进程)都在工作,但是实际上,单核 CPU 做不到多个进程同时执行,只有多个 CPU 才能真正实现多任务执行。之所以会产生多个任务同时执行的错觉是因为 CPU 的运行速度太快了,远远超过了肉眼能够感知的速度。
CPU 处理进程是通过系统的调度类程序实现的,调度类根据进程的优先级调度进程,给进程分配时间片,优先级越高的进程越容易被调度,同时分配的时间片也越多。时间片的示意图如下:
 
 
如图所示,每个进程都会被调度到,同时,时间片分配越多的进程, CPU 会处理更长时间。当当前处理的进程时间片结束的时候,内核会将进程临时停止时的运行环境,即寄存器中的内容和页表保存到内存中(保护现场)。同时,收回 CPU 的使用权,将 CPU 交给调度类选中的进程继续处理下个进程,在下个进程运行时,将下个进程原来的运行时环境加载到 CPU (恢复现场),这样 CPU 就可以在当初处理该进程的运行时环境继续工作了。
基于上面描述可以知道,如果调度类调度进程太过频繁的话会使得 CPU 陷入保护现场和恢复现场的时间过长,而这部分时间 CPU 是没有工作的,这样不好。当然,调度类调度的太慢的话,会使得其它进程一直处于等待状态,这样也不好。
 
 

进程状态

 
 
事实上进程不是只有运行和等待两种状态,它有多种状态,如上图所示,分别是:
运行态:进程正在运行,CPU 正在处理它;
就绪态:进程可以被运行,已经在等待队列中,调度类可能会调度到它;
阻塞(睡眠)态:进程睡眠了,不可被运行,通常是等待某事件如 I/O 请求等;
 
各个进程的状态可以相互转换,转换形式有:
  • 新状态->就绪态:当等待队列允许接纳新进程时,内核便把新进程加入等待队列;
  • 就绪态->运行态:调度类选中等待队列中的进程,该进程进入运行态;
  • 运行态->阻塞态:正在运行的进程因需要等待某事件(如 I/O 请求、信号等待等)的出现而无法执行,进入阻塞态;
  • 阻塞态->就绪态:进程所等待的事件发生,从阻塞态进入等待队列,等待下次被调度执行;
  • 运行态->就绪态:正在执行的进程因时间片用完而被暂停执行;或者在抢占式调度方式中,高优先级进程强制抢占了正在执行的低优先级进程;
  • 运行态->终止态:一个进程已完成或发生某种特殊事件,进程将变为终止状态。对于命令来说,一般都会返回退出状态码。
 
上图中少了终止态和僵尸态,终止态即是进程完成或由于特殊事件使得进程终止的状态,称为终止态。僵尸态是进程结束了进入终止状态,但是内核没有发现该进程已经终止从而没把它从进程表中删除,处于僵尸态的进程称为僵尸进程,它会一直占用进程号和少量资源。<僵尸进程的详细信息可看这里>。
其中,阻塞(睡眠)态又分为可中断睡眠和不可中断睡眠。可中断睡眠是允许接收外界信号和内核信号而被唤醒的睡眠,绝大多数睡眠都是可中断睡眠,能被 ps 或 top 捕捉到的睡眠几乎都是可中断睡眠。不可中断睡眠只能由内核发起信号唤醒,外界无法通过信号来唤醒,主要表现在和硬件交互的时候。例如, cat 一个文件时,从硬盘上加载数据到内存,在和硬件交互的那一小段时间一定是不可中断的,否则如果进程被唤醒,加载数据中断,cat 出来的文件会只显示一部分,严重者,甚至会造成硬件的奔溃。
 

父子进程和 BASH

上一节工作管理说到,工作可以被放到后台执行,相应的,进程也可分为前台进程和后台进程:
前台进程:一般命令(非内置命令)在执行时都会 fork(复制) 子进程来执行命令,在子进程执行过程中,父进程会进入睡眠,这类进程是前台进程。
后台进程:在命令的结尾加上 & 号,会将命令放入后台,并返回该后台进程的的 jobid 和 pid,同时父进程进入运行态,当后台进程终止时,父进程会收到信号。所以,通过在命令后加上 "&" 号可以实现 “伪并行”的工作方式。
 
前台进程执行时,如果输入 bash 内置命令,父进程将不会创建子进程来执行这些命令,而是直接在当前 bash 进程中执行。但如果将内置命令放在管道后,则此内置命令将和管道左边的进程同属于一个进程组,所以仍然会创建子进程。
shell 中可通过 enable 和 type 命令查看命令是否是内置命令:
[test@lianhua ~]$ type echo
echo is a shell builtin
[test@lianhua ~]$ enable | grep echo
enable echo
[test@lianhua ~]$ enable cp
-bash: enable: cp: not a shell builtin
 
 
Liunx 中创建子进程的方式有三种,分别是:
1. fork 复制进程:fork 会复制当前进程的副本,产生一个新的子进程,父子进程是完全独立的两个进程,他们掌握的资源(环境变量和普通变量)是一样的。
2. exec:exec 方式不会产生子进程,它会加载新的程序从而取代当前进程,当前进程的变量是被初始化了。exec 加载的程序执行完毕后会退出当前 exec 所在的 shell。
3. clone:用来实现 Liunx 中的线程。
 
可以看出,一般稳妥的执行程序的方式是先 fork 出一个子进程,然后在此子进程上执行 exec 命令,exec 执行完之后退出当前 shell 环境,返回状态码给父进程,同时切换到父进程。这样的方式可以保证父进程以及父进程下其它子进程的安全。
 
 
这里面有个特殊的进程就是 bash, bash 是通过开启 shell 启起来的。那么,在创建子 bash 进程时,何时启动子 shell,子 shell 的环境变量和普通变量是怎么来的,这些问题就变得很需要讨论了。
命令 echo $BASHPID 可打印当前 bash 的 PID,通过它我们可以查看当前 bash 进程是不是在子 shell 下,比如:
[test@lianhua ~]$ echo $BASHPID
557442
[test@lianhua ~]$ bash
[test@lianhua ~]$ echo $BASHPID
782463
可以看出执行 bash 命令进入了子 shell ,在子 shell 中的 bash PID 是 782463。
 
常用的在 shell 中是否进入子 shell 可分为以下几种:
1. 执行 bash 内置命令:前面略有提及,执行 bash 内置命令是在当前 bash 进程中执行命令,并不会产生子 shell:
[test@lianhua ~]$ type let
let is a shell builtin
[test@lianhua ~]$ echo $BASHPID
557442
[test@lianhua ~]$ let a=$BASHPID
[test@lianhua ~]$ echo $a
557442
 
但是如果内置命令后加管道时,管道会把内置命令加入到同一进程组中,从而创建子 shell:
[test@lianhua ~]$ type cd
cd is a shell builtin
[test@lianhua ~]$ echo $BASHPID
557442
[test@lianhua ~]$ cd | expr $BASHPID
872810
 
2. 执行 bash 非内置命令:执行 bash 非内置命令会先 fork 出子 bash 进程,子 bash 进程是父 bash 进程的副本,具有父 bash 进程一样的变量,然后通过 exec 加载非内置命令,此时子 bash 的环境被初始化了,这个环境也称为单独的环境,执行完之后 exec 退出子 shell:
[test@lianhua ~]$ echo $BASHPID
557442
[test@lianhua hxia]$ type cat
cat is /usr/bin/cat
[test@lianhua hxia]$ cat / &
[1] 907235
 
3.执行 bash 命令:执行 bash 会进入子 shell,产生子 bash 进程,子 bash 进程会重新加载环境配置项(fork bash 时完全继承父 shell 的环境信息),覆盖从父 shell 中继承来的变量(普通变量):
[test@lianhua ~]$ env | grep HOME
HOME=/home/test
[test@lianhua ~]$ a=lianhua
[test@lianhua ~]$ env | grep lianhua
[test@lianhua ~]$ echo $a
lianhua
[test@lianhua ~]$ echo $BASHPID
992329
[test@lianhua ~]$ bash
[test@lianhua ~]$ env | grep HOME
HOME=/home/test
[test@lianhua ~]$ echo $a [test@lianhua ~]$ echo $BASHPID
1004303
 
4. 执行 shell 脚本:shell 脚本中第一行总是 "#!/bin/bash" 或者执行 shell 脚本时使用 bash ***.sh 执行,这和 bash 进入子 shell 时一样的,都是通过 bash 命令进入子 shell,不过和直接执行 shell 脚本不一样的是,执行 shell 脚本产生的子 shell 只会继承父 shell 的父进程存储命令路径这一项属性。
 
 
进程状态举例
结合前面的知识,这里以在 shell 下执行 cp 为例进一步解释进程间状态转换流程:
当执行 cp 命令时,首先 fork 出一个 bash 子进程,然后在子 bash 上 exec 加载 cp 程序,cp 子进程进入等待队列,由于是在命令行下敲的命令,所以优先级较高,调度类很快选中它。在 cp 这个子进程执行过程中,父进程 bash 进入睡眠状态(不仅是因为cpu只有一颗的情况下一次只能执行一个进程,还因为进程等待),并等待被唤醒,此刻 bash 无法和人类交互。当 cp 命令执行完毕,它将自己的退出状态码告知父进程此次复制是成功还是失败,然后 cp 进程自己消逝掉,父进程 bash 被唤醒再次进入等待队列,并且此时 bash 已经获得了 cp 退出状态码。根据状态码这个"信号",父进程 bash 知道了子进程已经终止,然后通知给内核,内核收到通知后将进程列表中的 cp 进程项删除。至此,整个 cp 进程正常完成。
假如 cp 这个子进程复制的是一个大文件,一个 cpu 时间片无法完成复制,那么在一个 cpu 时间片消耗尽的时候它将进入等待队列。
假如 cp 这个子进程复制文件时,目标位置已经有了同名文件,那么默认会询问是否覆盖,发出询问时它等待 yes 或 no 的信号,所以它进入了睡眠状态(可中断睡眠),当在键盘上敲入 yes 或 no 信号给 cp 的时候,cp 收到信号,从睡眠态转入就绪态,等待调度类选中它完成 cp 进程。
在 cp 复制时,它需要和磁盘交互,在和硬件交互的短暂过程中,cp 将处于不可中断睡眠。
假如 cp 进程结束了,但是结束的过程中出现了某种意外导致 bash 这个父进程不知道它已经结束了(此例中是不可能出现这种情况的),那么 bash 就不会通知内核回收进程列表中的 cp 表项,cp 就成了僵尸进程。
 
 
 
[声明]
文章中大部分内容来源于骏马金龙老师的博文,详细了解可看他的博客: 
 
 
 
(完)

每天学五分钟 Liunx 0100 | 服务篇:进程状态的更多相关文章

  1. 五分钟学Java:如何才能学好Java Web里这么多的技术

    原创声明 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 系列文章介绍 本文是<五分钟学Java>系列文章的一篇 本系列文章主要围绕Java程序员必须掌握的核心技能,结合我个人三年 ...

  2. 零元学Expression Blend 4 - Chapter 42 五分钟快速完成扇形变圆形动画

    原文:零元学Expression Blend 4 - Chapter 42 五分钟快速完成扇形变圆形动画 零元学Expression Blend 4 - Chapter 42 五分钟快速完成扇形变圆形 ...

  3. 五分钟给你的 gRPC服务 加上 HTTP 接口

    gRPC 服务要加 HTTP 接口? go-zero 给大家带来极简的 RESTful 和 gRPC 服务开发体验的同时,社区又给我们提出了新的期望: 我想只写一次代码 既要 gRPC 接口 也要 H ...

  4. 如何从40亿整数中找到不存在的一个 webservice Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库 WPF实战案例-打印 RabbitMQ与.net core(五) topic类型 与 headers类型 的Exchange

    如何从40亿整数中找到不存在的一个 前言 给定一个最多包含40亿个随机排列的32位的顺序整数的顺序文件,找出一个不在文件中的32位整数.(在文件中至少确实一个这样的数-为什么?).在具有足够内存的情况 ...

  5. [分享] 史上最简单的封装教程,五分钟学会封装系统(以封装Windows 7为例)

    [分享] 史上最简单的封装教程,五分钟学会封装系统(以封装Windows 7为例) 踏雁寻花 发表于 2015-8-23 23:31:28 https://www.itsk.com/thread-35 ...

  6. 《sed的流艺术之一》-linux命令五分钟系列之二十一

    本原创文章属于<Linux大棚>博客,博客地址为http://roclinux.cn.文章作者为rocrocket. 为了防止某些网站的恶性转载,特在每篇文章前加入此信息,还望读者体谅. ...

  7. GC算法精解(五分钟让你彻底明白标记/清除算法)

    GC算法精解(五分钟让你彻底明白标记/清除算法) 相信不少猿友看到标题就认为LZ是标题党了,不过既然您已经被LZ忽悠进来了,那就好好的享受一顿算法大餐吧.不过LZ丑话说前面哦,这篇文章应该能让各位彻底 ...

  8. zookeeper-架构设计与角色分工-《每日五分钟搞定大数据》

    本篇文章阅读时间5分钟左右 点击看<每日五分钟搞定大数据>完整思维导图   zookeeper作为一个分布式协调系统,很多组件都会依赖它,那么此时它的可用性就非常重要了,那么保证可用性的同 ...

  9. zookeeper核心-zab协议-《每日五分钟搞定大数据》

    上篇文章<paxos与一致性>说到zab是在paxos的基础上做了重要的改造,解决了一系列的问题,这一篇我们就来说下这个zab. zab协议的全称是ZooKeeper Atomic Bro ...

  10. 「每日五分钟,玩转JVM」:线程共享区

    前言 上一篇中,我们了解了JVM中的线程独占区,这节课我们就来了解一下JVM中的线程共享区,JVM中的线程共享区是跟随JVM启动时一起创建的,包括堆(Heap)和方法区()两部分,而线程独占区的程序计 ...

随机推荐

  1. 复习:Java基础-泛型方法

    泛型 大家都很熟悉了 泛型方法呢 可能很多小伙伴都有混淆,今天来稍微复习一下 泛型方法(普通方法) public class Test<T> { public T f(T c) { //注 ...

  2. 一键式调试工具—Reqable 使用指南

    简介 Reqable是一款跨平台的专业HTTP开发和调试工具,在全平台支持HTTP1.HTTP2和HTTP3(QUIC)协议,简单易用.功能强大.性能高效,助力程序开发和测试人员提高生产力!本产品需要 ...

  3. Windows手工入侵排查思路

    文章来源公众号:Bypass Windows系统被入侵后,通常会导致系统资源占用过高.异常端口和进程.可疑的账号或文件等,给业务系统带来不稳定等诸多问题.一些病毒木马会随着计算机启动而启动并获取一定的 ...

  4. 你真的会用 npx 吗❓❓❓

    Hello,大家好! 日常开发中大家应该经常使用 npm install xxx 来安装包依赖,那是否注意到npm升级到 npm@5.2.0 之后,在 npm 二进制命令旁边安装了一个 npx 二进制 ...

  5. 在winform blazor hybrid中绘图

    前几天跟大家介绍了在winform中使用blazor hybrid,而且还说配上blazor的ui可以让我们的winform程序设计的更加好看,接下来我想以一个在winform blazor hybr ...

  6. CodeForces 1141F2 贪心 离散化

    CodeForces 1141F2 贪心 离散化 题意 给定一个序列,要求我们找出最多数量的不相交区间,每个区间和都相等. 思路 一开始没有头绪,不过看到 \(n \le 1500\) 后想到可以把所 ...

  7. 实践解读丨Python 面向对象三大特征之多态

    摘要:多态从字面意思上看就是多种形态,在我们python的面向对象里就是不同的对象在接收相同方法或者函数时会产生不同的行为,也就是说,每个对象可以用自己的方式去响应共同的函数,不同的方式实现不同的结果 ...

  8. Serverless冷启动:如何让函数计算更快更强?

    摘要:借助Serverless计算,开发者仅需上传业务代码并进行简单的资源配置便可实现服务的快速构建部署,云服务商则按照函数服务调用量和实际资源使用收费,从而帮助用户实现业务的快速交付和低成本运行. ...

  9. 云图说|华为HiLens云上管理平台 花样管理多种端侧设备

    摘要:华为HiLens作为端云协同多模态AI开发应用平台,支持对接和管理多种端侧计算设备,帮助用户开发多模态AI应用并下发到端侧设备,实现多场景的智能化解决方案. 本文分享自华为云社区<[云图说 ...

  10. 云小课|三大灵魂拷问GaussDB(DWS)数据落盘安全问题

    阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 摘要:GaussDB(D ...