多任务和 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. 8、switch语句

    1.switch语句:"开关" switch是一个条件语句,它计算表达式并将其与可能匹配的列表进行比较,并根据匹配执行代码块.它可以被认为是一种惯用的方式来写多个if else子句 ...

  2. 浅谈数字孪生和GIS融合的必要性

    随着科技的不断发展和应用的不断深入,数字孪生和GIS在各自领域中展现出巨大的潜力.然而,更引人注目的是,数字孪生和GIS的融合将为许多行业带来全新的机遇和变革.在本文中,我们将探讨数字孪生和GIS融合 ...

  3. 在C#中,如何以编程的方式设置 Excel 单元格样式

    前言 在C#开发中,处理Excel文件是一项常见的任务.在处理Excel文件时,经常需要对单元格进行样式设置,以满足特定的需求和美化要求,通过使用Java中的相关库和API,我们可以轻松地操作Exce ...

  4. DVWA File Inclusion(文件包含)全等级

    File Inclusion(文件包含) 目录: File Inclusion(文件包含) 前言 PHP伪协议 1.Low get webshell 本地文件包含 远程文件包含 2.Medium 3. ...

  5. 9 个让你的 Python 代码更快的小技巧

    哈喽大家好,我是咸鱼 我们经常听到 "Python 太慢了","Python 性能不行"这样的观点.但是,只要掌握一些编程技巧,就能大幅提升 Python 的运 ...

  6. Tpon 1.0 一键查询网站存在过的路径

    Tpon 1.0 寻找网站存在过的路径 该工具能够让你发现意料之外的路径 工具描述 编写该工具旨在寻找网站存在过的网站路径,这个地址可能是机器爬下来的也可能是某些人访问过的,在表面你可能看不到它的入口 ...

  7. Flutter Getx 中的Dialog 以及改变主题

    设置应用程序入口 当我们导入依赖后,在应用程序顶层把 GetMaterialApp 作为顶层,如下所示 import 'package:flutter/material.dart'; import ' ...

  8. Nacos 本地单机版部署步骤和使用

    本系列是 Spring Cloud 微服务实战系列教程.之前在 <Spring Cloud Eureka 入门 (一)服务注册中心详解> 聊过 Spring Cloud Eureka.那今 ...

  9. 万万没想到,我在夜市地摊解决了MySQL临时表空间难题~~

    都说"大隐隐于市,高手在深宫".突如其来的"摆地摊"风潮,让原本冷清的街道热闹非凡,也让众人发现了那些神龙见首不见尾的大神们. 这不,小毛在下班的途中就遇到了大 ...

  10. 微服务下,使用 ELK 进行日志采集以及统一处理

    摘要:微服务各个组件的相关实践会涉及到工具,本文将会介绍微服务日常开发的一些利器,这些工具帮助我们构建更加健壮的微服务系统,并帮助排查解决微服务系统中的问题与性能瓶颈等. 微服务各个组件的相关实践会涉 ...