文章目录

  • 进程的创建
    • 哪些事件导致进程的创建
    • fork 和 exec 命令创建和控制进程
      • fork() 命令
      • execve() 命令
  • 进程的状态
  • 中断
    • 中断的种类
  • 线程
    • 线程共享内容
    • 线程独有内容
  • 进程和线程的比较
    • 线程的好处
  • 线程的类别及区别
    • user-level thread(用户级别的线程)
      • user-level thread 的优点
      • user-level thread 的缺点
    • kernel-level thread(内核级别的线程)
      • kernel-level thread 的优点
      • kernel-level thread 的缺点
  • 竞态条件
  • Critical region
    • 解决 Critical region 的各种问题可以使用的策略
    • 范例研究
      • 通过 lock variable 来解决 critical region 的 race condition 问题
      • 通过 busy waiting 来解决 critical region 的 race condition 问题
        • busy waiting 的特点和缺点
  • 死锁 deadlock
    • 何时发生
    • 解决死锁
  • CPU 密集型 / IO 密集型进程
  • 进程调度 Process scheduler
    • 几种重要的时间
    • 调度的目标

进程的创建

哪些事件导致进程的创建

  • system initialization:当系统启动的时候会按照设定创建和启动一些固定的进程
  • Execution of a process creation system call by a running process:一个进程在执行的时候可能通过 “进程创建系统” 来创建一个新的进程
  • A user request to create a new process:用户创建新的进程(双击打开某个软件)
  • Initiation of a batch job

fork 和 exec 命令创建和控制进程

fork() 命令

  • fork() 命令会创建一个新的进程,自己作为 parent 进程; fork 命令会在 parent 执行 fork 命令的时候返回 child 的进程号,
  • 然后把子进程执行过程中路过 fork 命令的时候将进程号置为 0
  • 当子进程创建的时候,他会和父进程共享程序计数器(program counter),cpu寄存器,和打开的文件(openfile)
#include<stdio.h>
#include<unistd.h>int main(){int x;int count = 0;x = fork();printf("\nrun ...");printf("%d",x);}
run ...94779
run ...0
  • 首先当这个进程从 main 中的第一行开始执行,通过 fork 创建了子进程并返回了子进程的 id=94779

  • 然后这个时候子进程被创建了,依然是从头开始执行这个进程,但是在经过 fork() 的那一行,x 被设置成了 0

  • 你可以这么理解,就是系统能够识别到底是父进程发出的 fork 命令,还是子进程中的 fork 命令,当子进程经过 fork 命令的时候,返回 0

  • 如果程序如下,会产生什么样的结果呢:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){int pid;int count = 0;printf("fork1:%d:%d\n",getpid(),fork());printf("fork2:%d:%d\n",getpid(),fork());}
fork1:95301:95302
fork2:95301:95303
fork2:95303:0
fork1:95302:0
fork2:95302:95304
fork2:95304:0
  • 这个过程可能不好理解,但是你可以试着去想一下:
  1. 父进程 95301 经过第一个 fork 创建了 child1 95302
  2. 父进程 95301 经过第二个 fork 创建了 child2 95303
  3. child2 经过第一个 fork 的时候,啥反应也没有,所以什么也没打印
  4. child2 经过第二个 fork 的时候,返回了 0 所以打印了 fork2:95303:0
  5. child1 经过第一个 fork 的时候,返回了 0 所以打印了 fork1:95302:0
  6. child1 经过第二个 fork 的时候,又创建了 childchild 并返回了它的进程编号, fork2:95302:95304
  7. childchild 经过第一个 fork 的时候,啥反应也没有,所以什么也没打印
  8. childchild 经过第二个 fork 的时候,返回了 0,所以打印了 fork2:95304:0
  • 所以经过 上述的代码命令,最终 parent 是产生了 3 个 孩子进程( childchild 进程也是它的孩子进程)

execve() 命令

  • 如果只通过 fork 产生一个进程,那么这个进程的子进程的所有操作都和父进程一模一样;那么这个分身就没有什么意义了,而通过 execve 命令,可以让这个复制出来的进程采用不同的操作。

进程的状态

  • 就绪态

  • 运行态

  • 阻塞态

  • 运行态就是这个程序正在占用 CPU 资源进行操作,

  • 就绪态就是随时可以执行,但没有得到 CPU 的运行资源,等 CPU 资源轮询到他,立马就可以处理任务

  • 阻塞态就是,等待某个事件的发生,比如等待用户的输入,即使这个时候有空闲的 CPU 也不能马上处理,因为他要等待用户输入完成才能解除阻塞状态,进入就绪状态,然后才能进行任务处理。

中断

中断的种类

  • 真正的中断产生于硬件;伪中断产生于 CPU
  • 用户程序可能不经意间产生伪中断(pseudo-interrupts),例如除以0操作,这种事件经常被称作 “exceptions”这些异常会导致一些程序终止
  • 用户可以自发地通过命令产生伪中断(ctrl + c)

线程

线程共享内容

  • 地址空间和内存
  • 代码和数据部分
  • 存储空间内容(全局变量和堆空间(heap))
  • 打开的文件
  • 子进程
  • 信号和信号处理器

线程独有内容

  • 程序计数器
  • 寄存器
  • 栈空间(维护局部变量和函数调用的数据空间)
  • 状态(就绪/等待…)

堆空间 heap:可以暂时认为堆区是一块没有经过分配的内存地址,是和栈区等内存空间分开的,当我们调用方法的和使用局部变量的时候,其实都是在 stack 区进行操作,只有当我们使用 malloc 在 C 语言中申请一块空间的时候才会在堆区操作

进程和线程的比较

  • 可以说线程是轻量化的进程,而且线程可以被调度器直接管理,提高了程序的并发性
  • 进程被创建之后,其实是有一个主线程 main thread 和系统分配的各种资源(代码、数据、文件、寄存器、栈);这个时候如果不创建其他线程,那么进程就只有一个主线程。
  • 而当进程创建多个线程的时候,随着线程的创建,他们会同时创建自己的栈区、寄存器

线程的好处

  • 创建方便,快速
  • 终止也方便、快速
  • 在不同的线程之间切换很简单
  • 因为一个进程的不同线程之间共享内存,所以线程之间的通信很方便

线程的类别及区别

参考文章:https://www.tutorialspoint.com/user-level-threads-and-kernel-level-threads

user-level thread(用户级别的线程)

  • 用户级线程是由用户实现的,内核不知道这些线程的存在。它像处理单线程进程(single-threaded process)一样处理它们。用户级线程比内核级线程小且快得多。它们由程序计数器(PC)、堆栈、寄存器和一个小的过程控制块(small process control block)组成。另外,在用户级线程的同步中与内核(kernel)无关。

user-level thread 的优点

  • 创建迅速,管理容易(可以通过调度器直接管理)
  • 可以在任何操作系统上运行
  • 在用户级线程中切换线程不需要内核模式特权。

user-level thread 的缺点

  • 用户级线程中的多线程应用程序不能充分利用 multiprocessing
  • 如果一个用户级线程执行阻塞操作,整个进程将被阻塞

kernel-level thread(内核级别的线程)

kernel-level thread 的优点

  • 在内核级线程中,同一个进程的多个线程可以调度在不同的处理器上(multiprocessing)
  • 内核例程也可以是多线程的
  • 当一个线程阻塞了,不影响其他线程的运行

kernel-level thread 的缺点

  • 两个 kernel-level thread 在切换的时候,要转移控制权 transfer control
  • kernel-level thread 的创建和管理都比 user-level 的要慢(几乎接近于创建和管理一个进程的时间)

  • 最大的好处是,创建和管理方便; 最大的缺点是一个阻塞全都阻塞

  • 因为涉及到阻塞行为和 system call,因此要选用 kernel-level thread

竞态条件

https://www.techtarget.com/searchstorage/definition/race-condition#:~:text=A%20race%20condition%20is%20an,sequence%20to%20be%20done%20correctly.

  • 从上面的文字中我们可以得到:

    • race condition 可以发生在多个 program(程序) process(进程)或者多个(thread)线程之间,只要他们要在同一时间访问公共的资源就会发生。
  • 在 race condition 的情况下:

    • 所有资源的争夺方都可以对资源进行读和写
    • 当输出依赖于操作的顺序时,就会出现 race condition
    • 非常难 debug,因为很难重复这些资源争夺方的执行顺序

  • 假设现在有两个进程 A,B 来同时进行这个函数的调用,用 A1 表示 A 进程执行到第一行代码,B2 代表 B 进程执行到第二行代码

  • 假设 stack 里面现在还有一个值

  • case1:A1 -> A2 -> B1 这段程序就会执行完毕,并且 B 的返回值是 0

  • case2:A1 -> B1 -> A2 -> B2 这段程序就会报错,因为当 B 去 pop 的时候已经没有值 pop 了

Critical region

https://www.educative.io/edpresso/what-is-the-critical-section-problem-in-operating-systems

  • 指进程访问共享资源(如公共变量、文件等)并对其进行"写"操作的代码段
  • 因为读操作哪怕同时进行也不会导致程序出现问题,但是写操作是绝对不能同时进行的。因此要对写操作的代码段进行并发的控制来避免 race condition 的情况

解决 Critical region 的各种问题可以使用的策略

  • mutual exclusion:当一个进程在处理它的 critical region 的时候,其他的进程不允许访问 critical region
  • progress:当 critical region 里没有进程在执行,并且有一个进程希望进入临界区时,它不应该无限期地等待进入critical region。
  • bounded waiting:在另一个进程 A 请求进入 critical region 时,在 A 请求被接受之前,一个进程允许在临界区时执行的次数必须有一个上限。这是为了防止某个进程不断地、密集地获得进入 critical region 的权力,导致其他进程不能得到资源

范例研究

  • 通过下面几个例子分析通过这几种方式是否能够达到解决 critical region 问题的目的。

通过 lock variable 来解决 critical region 的 race condition 问题

  • 假设现在有两个进程 A,B, 用 A1, B1 分别表示两个进程执行到代码段的第 1 行

  • 当 A1->A2->B1->… 的情况,即当 A1,A2 发生在 B1 之前,那么这种情况可以保证 A B 不会发生 critical region 的 race condition 问题,但是如果发生顺序是:

    • A1->B1->… 这种情况,则无法避免 A,B 之间的 critical region 中的 race condition 问题

通过 busy waiting 来解决 critical region 的 race condition 问题

  • 这种情况与上一种相比,不再存在同时越过 while 条件的情况,也就是说,一定会有一个线程卡在第二行的 while 处无法访问 critical region;但是这种写法依然存在问题,设想下面的情况:

    • A1->B1->A2->B2->A3->B2->A4->B3->A5->B4 -> B5 ->B5 -> B5
    • 当 thread A 执行完毕,但是 thread B 依然卡在 B5 很长时间,这个时候,因为 turn = 0,所以 A 会再开始一轮,直到 A 再次执行完 A4, B 还是没有执行完 B5,那么由于 A 在这个循环中的 A4 中将 turn设置 = 1,所以这个时候如果 B 不执行完 B5,A 也将无法再开始下一次循环。
    • 所以这种情况下,A,B 都依赖于对方执行的结束,否则自己也无法继续往下执行,因此也没有真正解决 race condition 的问题

busy waiting 的特点和缺点

  • 当一个进程想要进入 critical region 的时候

    • 需要 check 是否可以进入
    • 如果不能进入,那就不断地执行循环操作(就是上例中的 while 循环),直到能够访问 critical region 为止
  • 缺点:

    • 大量消耗 CPU, 其实完全可以用其他方法来代替,比如让 CPU 每隔一段时间来检查一遍
    • 优先级倒置问题

https://www.sciencedirect.com/topics/computer-science/priority-inversion#:~:text=Priority%20inversion%20is%20a%20situation,thus%20the%20name%20priority%20inversion).

死锁 deadlock

https://www.geeksforgeeks.org/introduction-of-deadlock-in-operating-system/

何时发生

  • 发生死锁的四个必要条件:

    • 两个或多个非共享的资源(非共享的意思就是每种资源一次只能被一个进程访问),
    • 一个进程占有了最少一种(非共享的)资源并且在等待另外一个资源(非共享的)
    • 某种资源一旦被某个进程占用,就不可被抢占(必须等到这个进程执行完毕才能释放当前资源)
    • 多个进程等待其他进程的资源,这个过程形成了环结构

解决死锁

  • 忽略这个问题
  • 通过图算法来检测死锁的发生并进行修复
  • 通过精细的资源分配避免死锁

CPU 密集型 / IO 密集型进程

  • CPU 密集型的进程就是运行过程中特别消耗CPU资源的进程
  • IO 密集型的进程就是进程中很长的时间都在阻塞状态,使用 CPU 的机会并不多

进程调度 Process scheduler

https://www.geeksforgeeks.org/cpu-scheduling-in-operating-systems/?ref=lbp

几种重要的时间

  • 到达时间:一个进程到达 ready queue (就绪队列)的时间点;代表这个进程只要获得 CPU 时间就马上可以运行
  • 完成时间:进程完成任务的时间点
  • 触发时间:进程真正使用 cpu 的时间段
  • 周转时间:完成时间点 - 到达时间点 (即完成整个进程使用的时间周期,这个过程可能包括了进程等待 CPU 的时间 + CPU 处理这个进程的时间,这两部分共同构成了周转时间)
  • 等待时间:周转时间 - 触发时间

参考:
https://www.geeksforgeeks.org/difference-between-arrival-time-and-burst-time-in-cpu-scheduling/#:~:text=Burst%20Time%20(BT)%20%3A,running%20time%20of%20the%20process.

调度的目标

  • 最大化利用 CPU 资源(让 CPU 尽可能忙)
  • 最大化 CPU 的吞吐量(让 CPU 尽可能在一个时间单位完成更多进程的任务)
  • 最小化周转时间(让一个进程完成从进入就绪队列到自己的任务用的时间尽可能短)
  • 最小化等待时间(让进程等待的时间尽可能短)
  • 最小化响应时间(一个进程产生响应的最小时间)

等待时间和响应时间的区别:

  • 响应时间:就绪状态到获得 CPU 的时间段
  • 等待时间:在整个等待队列中待的时间

计算机系统学习之(1):基础知识概要——进程、中断、线程、竞态条件、关键区域、死锁、进程调度相关推荐

  1. JAVA基础知识系列---进程、线程安全

    1.1 临界区 保证在某一时刻只有一个线程能访问数据的简便方法,在任意时刻只允许一个线程对资源进行访问.如果有多个线程试图同时访问临界区,那么在有一个线程进入后,其他所有试图访问临界区的线程将被挂起, ...

  2. Nginx学习(一)——Nginx基础知识

    目录 1.Nginx学习(一)--Nginx基础知识 2.Nginx学习(二)--配置文件.反向代理与负载均衡 3.Nginx搭建HTTPS服务器 一.初试Nginx 一.下载安装 1.安装必要的一些 ...

  3. JNI学习开始篇 基础知识 数据映射及学习资料收集

    JNI学习开始篇 基础知识 数据映射及学习资料收集 JNI介绍 JNI(Java Native Interface) ,Java本地接口. 用Java去调用其他语言编写的程序,比如C或C++. JNI ...

  4. OpenCV与图像处理学习一——图像基础知识、读入、显示、保存图像、灰度转化、通道分离与合并

    OpenCV与图像处理学习一--图像基础知识.读入.显示.保存图像.灰度转化.通道分离与合并 一.图像基础知识 1.1 数字图像的概念 1.2 数字图像的应用 1.3 OpenCV介绍 二.图像属性 ...

  5. php基础教学笔记,php学习笔记:基础知识

    php学习笔记:基础知识 2.每行结尾不允许有多余的空格 3.确保文件的命名和调用大小写一致,是由于类Unix系统上面,对大小写是敏感的 4.方法名只允许由字母组成,下划线是不允许的,首字母要小写,其 ...

  6. 计算机网络基础心得体会结尾,学习《计算机网络基础知识》心得体会

    学习<计算机网络基础知识>心得体会 ... 如今已经是信息时代,作为主流信息工具的网络越来越重 要,网络是信息的载体,是人们传递感情的工具.随着信息社会 的不断发展,网络的应用将会更加广泛 ...

  7. 计算机学生要学的基础知识,中小学生应注重学习计算机的基础知识

    "知识爆炸"和"知识老化"这两大问题,不断困扰着现代教育,人们解决这一问题的良方之一,就是加强学生对基础知识的学习.近年来在中国兴起的中小学生学习计算机热,也同 ...

  8. 【学习笔记--FMCW基础知识】

    学习笔记--FMCW基础知识 前言 mmWave测距原理 mmWave区分多个物体 mmWave的距离分辨率(Range Solution) mmWave的最大测量距离 前言 由于工作原因需要了解TI ...

  9. 使用Vue3学习Vue的基础知识

    创建 Vue 应用 vue的安装有多种方式,本文只讨论基础知识,其他安装方式请自行查阅官网 https://v3.cn.vuejs.org/guide/installation.html 本文使用CD ...

最新文章

  1. linux查看特定文件的位置
  2. IOS后台运行机制详解(二)
  3. HDOJ(HDU) 2502 月之数(进制)
  4. cookies与session
  5. toj 4596 一行盒子
  6. vim的一些基本应用
  7. Tomcat是如何将请求一步步传递到我们编写的HttpServlet类中的
  8. 特斯拉电池检测_特斯拉风格的割草机,也是采用电池供电
  9. Mahout的taste里的几种相似度计算方法
  10. 用CSS Houdini画一片星空
  11. TCP/IP四层模型
  12. 新浪微博批量删除微博的方法
  13. oracle box怎么全屏,Oracle VM VirtualBox 虚拟机设置全屏与共享
  14. ElasticSearch-索引生命周期(ILM)-日期分割索引
  15. 不限速,无需登录就能下载的网盘工具,非常适合您!
  16. Mac快捷键大全及cheatsheet插件
  17. 【数值分析】Doolittle分解和Cholesky分解的Python实现
  18. 拾忆Elasticsearch02:Elasticsearch的基本命令回顾
  19. 圆的周长面积(YZOJ-1020)
  20. IDEA终于支持云端了,可同步所有配置和插件,一招搞定,重装不愁~

热门文章

  1. Office2013安装完毕后开启提示VEB6EXT.OLB无法载入
  2. python编程学习——第六周
  3. 网页无法复制的修复方法---Chrome上,最简单的解除“右键限制”的方法 - 书签法
  4. 考研高等数学上下册归纳总结思维脑图(超详细)
  5. 爬取金山词霸并制作成exe小程序
  6. 这是什么意思?求解释
  7. pfSense多WAN设置指南
  8. 电感啸叫原因与应对措施
  9. 怎么快速实现改变一张CAD图纸的背景色为白色?
  10. Online Office实现在线编辑