操作系统之线程和进程

简介: 操作系统之线程和进程

一. 什么是操作系统

操作系统本质上是一个软件, 发挥的是管理作用, 可以管理软件和硬件, 让其有条不紊的运行和使用.


对于操作系统下层, 操作系统需要管理好硬件; 对于操作系统上层要给软件提供稳定的运行环境, 所以操作系统是软件, 硬件, 用户之间交互的媒介.

73d8c9be8b2a4960a39693770de0ac9a.png

硬件设备: 实体设备, 比如电脑后盖打开看到的都是硬件设备.


驱动程序: 硬件厂商在开发硬件的同时会提供驱动程序, 电脑安装了对应的驱动程序, 才能让系统正确的识别硬件设备.


操作系统内核: 是操作系统的核心功能, 承上启下.


系统调用: 操作系统给应用程序提供的一些API接口, 比如有个程序想操作一下硬件设备, 就需要先通过系统调用, 把操作命令告诉给系统内核, 内核调用驱动程序, 进一步的操作硬件设备.


应用程序:QQ , 所写的Java代码等都是应用程序.


常见的操作系统:


Windows:windows98, 2000, xp, vista,win7, win10, win11(最熟悉的操作系统).


Linux: 特别适合进行开发和部署(程序员必须掌握的系统).


Mac: 苹果电脑用的系统, 和Linux有一些相似之处.


还有一些手机上运行系统;Android ,IOS等.

二. 进程和线程

1. 进程

简单来说进程就是正在运行的程序, 通过双击一个可执行文件(.exe), 操作系统就会把这个可执行文件加载到内存上, 并在CPU上执行可执行文件中的指令, 此时这个exe就跑起来了, 就成为一个正在运行的程序, 也称作一个进程(process), 也叫做一个任务.


现在的很多电脑都是多进程模式的, 宏观上, 可以有很多个进程同时执行.

73d8c9be8b2a4960a39693770de0ac9a.png

上面一部分进程是我们自己去执行应用的可执行文件, 而另一部分是操作系统自动启动的进程.


2. 线程

线程是进程内部的一部分, 将进程类比作一个工厂, 那么线程就是工厂中的生产线, 所以一个进程可以包括很多个线程, 一个进程至少拥有一个线程.


我们日常所写的java代码, 其实最终都是通过java进程(jvm)跑起来的.


3. 进程的管理

进程是一个重要的"软件资源", 是由操作系统内核负责管理的, 进程中含有一个或者多个线程, 在进程中开启第一个线程时, 需要进行资源的分配, 之后进程中开启的其他线程复用的都是同一一份资源.


线程是通过结构体(C语言)来描述的(操作系统基本上都是通过C/C++来实现的), 这个结构体也被称为进程控制块(PCB), 这若干个结构体就描述了进程, 对进程进行描述后, 然后就是组织线程, 线程的本质上也是数据, 那么对数据的组织需要通过数据结构来进行组织, 在Linux系统中是通过双链表来组织线程的, 也就是通过一个双向链表将多个PBC给串到一起.


创建一个线程, 本质上就是创建一个PCB这样的结构体对象, 把它插入到链表中.


销毁一个线程, 本质上就是把链表上的 PCB节点删除掉.


任务管理器查看到进程列表, 本质上就是遍历这个PCB链表.


所以对线程的增删查改本质上就是对双链表进行增删查改, 当然这个双链表是复杂的双链表, 但是对线程的操作的基本思路和操作双链表是一样的.]


3.1 PBC中的一些属性

PCB里包含的属性是非常多的, 下面介绍的是一些核心属性.


进程id(pid): 进程的一个身份标识符, 是一个唯一的数字.

73d8c9be8b2a4960a39693770de0ac9a.png

内存指针: 指明了自己分配到的内存.


文件描述符表: 硬盘上的文件等其他资源.


一个进程一运行, 操作系统就会自动打开至少3个文件: 标准输入(System.in), 标准输出(System.out), 标准错误(System.err).


想要一个进程正常工作就需要为这个进程分配资源, 包括但不限于内存, 硬盘, CPU.

3.2 并发和并行

分配资源时, 对于其他资源都比较好分配, 但CPU资源是不太好分配的: 电脑中进程可能有几十上百个, 电脑的CPU(现在电脑大多是多核CPU)资源是很有限的, 远不够给这些进程同时分配.


但我们又希望进程都是同时运行的, 上面说过, 一个进程中可以有多个线程, 我们可以让多个线程去并发执行(分时复用), 那么什么是并发呢?

并行性和并发性是既相似又有区别的两个概念, 并行性是指两个或多个事件在同一时刻发生, 而并发性是指两个或多个事件在同一时间间隔内发生.


从宏观上来讲, 并行与并发没有区别, 或者说是我们肉体凡胎感知不到.

从微观上来讲, 并行表示两个CPU核心同时运行两个任务的代码, 并发表示一个CPU核心先运行任务A的代码,再运行任务B的代码, 也就是多个任务能够在CPU核心上快速的切换运行; 只要运行得足够快, 就可以认为多个任务在同时运行.


3.3 进程的调度

线程是系统调度执行的最基本单位, 为了方便描述进程的调度, 这里假定此处的进程只有一个线程, 这样就可以将线程的调度视为进程的调度, 这里我们谈到的进程调度是基于多任务操作系统, 就是从宏观上看, 同一时间能够同时运行多个进程.


进程的状态:


就绪状态: 准备就绪, 随时等待CPU调度执行.

运行状态: 线程正在CPU上运行的状态.

阻塞状态: 进程由于一系列因素无法及时响应调度, 短时间内无法在CPU上执行.

优先级:


多个进程, 操作系统进行调度的时候, 并不是完全平等, 而是有一定的优先级的.


上下文:


表示上一次进程被调出CPU的时候, 程序的运行状态(将进程的 “中间状态” 保存下来), 这样下一次这个进程调度到CPU时就能接着上次的状态继续运行.


进程的上下文, 就是CPU中的各个寄存器的值, 所以进程调出CPU之前, 需要先把CPU上寄存器上的数据保存到内存中,相当于存档; 下次该进程再被调进CPU时, 就可以根据上次储存在内存中的数据恢复到寄存器中, 相当于读档.


记账信息:


操作系统统计每个进程在CPU上的占用的时间以及指令数目, 根据这个来决定下一阶段如何调度.


举一个形象的场景来进行理解:


比如有三个进程A, B, C, 不妨将操作系统比作一个小姐姐, 将线程A, B, C比作三位小哥哥, A特别帅, B特别有钱, C特别会舔.


这位小姐姐同时与A, B, C 一起谈恋爱, 那么这里小姐姐姐如何同时谈三个男朋友呢, 其实也很简单, 小姐姐只需要做好对ABC的时间管理即可, 让ABC都不知道对方的存在.


所以小姐姐需要妥善地安排与三位小哥哥谈恋爱的时间, 确保时间不能重合, 比如:

因为小姐姐特别喜欢帅的, 其次是有钱的, 最后是会舔的, 所以小姐姐对A, B, C的安排有了优先级, 给A安排的时间多一点, B次之, C最后。

此时小姐姐做了一周时间的规划表, 属实是时间管理大师了.

时间 计划
星期一 与C谈恋爱
星期二到星期三 与B谈恋爱
星期四到星期六 与A谈恋爱
星期日 休息

此时小姐姐这个时间规划, 就是可以理解为进程的调度, 谈恋爱期间, 如果B需要出差一个月, 那么在小姐姐眼中, B这种状态就称为阻塞或睡眠状态, 如果正常, 男朋友可以随叫随到, 则在小姐姐眼中就称这种状态为就绪状态, 小姐姐正在与A谈恋爱的状态称为运行状态.


小姐姐与三位小哥哥同时谈恋爱, 总会有时候"串戏", 比如A需要带小姐姐回家见家长, 让小姐姐准备准备一份礼物, 而B想要带小姐姐去三亚旅游, 要小姐姐做好准备,但是有一天A问小姐姐准备好没?小姐姐说我买了两件泳衣, 这样就 “串戏” 了, 小姐姐最后总算将A忽悠过去了, 小姐姐吸取了教训, 之后每次都会将上次和某人进行到啥程度了, 是否有啥未完成的任务之类的记录下来, 下次约会的时候, 能够恢复出之前的状态, 这样小姐姐在之后就不会串戏了, 这个场景就可以看作是操作系统中的上下文了.


小姐姐和ABC都已经相处了一段时间了, 就需要去想一想, 也就是每隔一段时间,做个总结, 统计一下在每个人身上大概花费了多少精力, 然后根据这个总结做出下一个阶段的时间安排, 要保证和每个人要始终保持好一个合适的尺度, 不能太远也不能太近, 这便可以看作是进程调度中的记账信息了.


3.4 内存管理

我们通过代码程序所获取的内存地址, 其实不是硬件上真真实实的地址, 而是经过一些计算整合出来的虚拟地址.


内存(物理上是一个内存条), 其中可以存很多数据, 内存就可以想象成是一个大走廊, 走廊非常长, 有很多房间, 每个房间大小是1Byte, 每个房间还有个编号, 从0开始依次累加, 这个内存编号就是"地址", 这个地址也就"物理地址".


下面张图就是真实的内存条.


内存有个了不起的特性就是随机访问, 访问内存上的任意地址的数据, 速度都极快, 时间上都差不多, 正是这个特点造就了数组取下标操作的时间复杂度是 O(1).

73d8c9be8b2a4960a39693770de0ac9a.png

我们的程序如果直接访问的就是物理地址, 如果程序正常运行还好, 但如果代码出bug了, 就可能出现越界访问的情况, 就可能会对其他进程的数据做出一些操作, 比如进程1的指针变量因为bug变成了0x8000, 也就是说这里明明是进程1的bug却把进程给搞坏了.

73d8c9be8b2a4960a39693770de0ac9a.png

为了避免这种问题的出现, 针对进程使用的内存空间进行了"隔离", 引入了虚拟地址空间.


代码里不再直接使用真实的物理地址了, 而是使用虚拟的地址; 由操作系统和专门的硬件设备负责进行虚拟地址到物理地址的转换.

73d8c9be8b2a4960a39693770de0ac9a.png

这里引入的虚拟空间地址, 对物理地址进行隐藏和隔离, 避免进程之间的相互影响, MMU硬件设备(很多时候是集成在CPU里的), 可以让虚拟地址和物理地址的转换速度更快.


当我们操作系统内核发现转换后的地址超过了该进程物理地址的访问范围, 就会直接向进程反馈一个错误, 进而引发该进程崩溃, 不会影响到其他进程.

3.5 进程间的通信

上面引入的虚拟空间地址使进程之间相互隔离, 这就导致每个进程只能访问自己的那一块地址空间的数据, 无法访问其他进程的地址空间的数据, 这样当一个进程崩溃时, 另外的进程不会受到影响.


但是, 在实际开发工作中, 有些时候进程间需要进行数据的交互的, 为了实现进程间的通信, 操作系统提供了一块多个进程都能访问到的 “公共空间” , 进程A可以先把数据存入 “公共空间” , 然后进程B可以到 “公共空间” 取出数据, 这样就实现了进程间的通信.


操作系统提供的 “公共空间/通信方式” 有很多种具体的体现形式, 现在最常见的进程间通信机制有:


基于文件操作.

基于网络操作(socket).

3.6 并发编程

CPU进入了多核心的时代, 要想进一步提高程序的执行速度, 就需要充分的利用CPU的多核资源, 这个时候并发编程就应用而生了; 引入进程的概念, 最主要的目的是为了解决"并发编程" 这样的问题, 让代码程序能够把这些CPU核心充分利用.


其实多进程编程已经可以将cpu的多核资源利用起来了, 可以解决并发编程的问题了, 但还是存在一些问题, 进程的创建需要分配资源, 进程的销毁需要回收资源, 而分配和回收资源的效率相对来说是比较低的, 也就是说进程的创建,销毁,调度的开销都是比较大的, 成本是比较高的.


问题解决:


方案1:

使用进程池, 与字符串常量池类似, 将已经创建的进程存入常量池, 后面需要使用时直接加载资源即可, 但是也存在问题, 那就是闲置的进程也会占用资源, 相当于空间换时间, 消耗空间来提升效率.


方案2:

使用多线程来实现并发编程, 因为进程包含线程, 一个进程中包含多个线程,所以线程比进程更轻量,因此也将线程称为 “轻量级进程”, 线程把申请资源/释放资源的操作给省下了, 这就致使线程的创建,销毁,调度的成本很低, 速度更快, 所以使用多线程实现并发编程比多进程更加合适.


类比以下场景进行理解多线程:


我们把进程看做一个工厂, 线程看作工厂中的生产线, 比如有一个任务需要生产一些硬件设备, 想要生产速度更快, 有两个方案:


建造两个工厂进行生产.

只建造一个工厂, 多增加一批生产线.

上面两种方案都以相同的效率生产设备, 但是方案1需要多建造一个工厂; 方案2只需要增加一批生产线, 复用同一个工厂的资源即可(原料, 运输资源等), 而方案1较之方案2的付出的代价更大, 类比一下方案1表示就是多进程实现并发编程, 方案2表示的是多线程实现并发编程.


4. 进程与线程的区别

进程包含线程, 一个进程包含一个或者多个线程.

进程间是独立的, 每个进程都有独立的地址空间, 一个进程崩溃不会影响其余的进程, 但是在同一个进程中, 多个线程是共用的是一块资源, 一个线程崩溃, 这个进程中的所有线程都会崩溃.

进程是操作系统分配资源的基本单位, 线程是操作系统调度执行的基本单位.

多进程相比于多线程不会存在线程安全的问题, 多线程编程可能存在线程安全问题.


目录
相关文章
|
9天前
|
调度 开发者
深入理解:进程与线程的本质差异
在操作系统和计算机编程领域,进程和线程是两个核心概念。它们在程序执行和资源管理中扮演着至关重要的角色。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
31 5
|
7天前
|
算法 调度 开发者
深入理解操作系统:进程与线程的管理
在数字世界的复杂编织中,操作系统如同一位精明的指挥家,协调着每一个音符的奏响。本篇文章将带领读者穿越操作系统的幕后,探索进程与线程管理的奥秘。从进程的诞生到线程的舞蹈,我们将一起见证这场微观世界的华丽变奏。通过深入浅出的解释和生动的比喻,本文旨在揭示操作系统如何高效地处理多任务,确保系统的稳定性和效率。让我们一起跟随代码的步伐,走进操作系统的内心世界。
|
25天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
24天前
|
消息中间件 安全 算法
深入理解操作系统:进程管理的艺术
【10月更文挑战第38天】在数字世界的心脏,操作系统扮演着至关重要的角色。它不仅是硬件与软件的桥梁,更是维持计算机运行秩序的守夜人。本文将带你走进操作系统的核心——进程管理,探索它是如何协调和优化资源的使用,确保系统的稳定与高效。我们将从进程的基本概念出发,逐步深入到进程调度、同步与通信,最后探讨进程安全的重要性。通过这篇文章,你将获得对操作系统进程管理的全新认识,为你的计算机科学之旅增添一份深刻的理解。
|
28天前
|
算法 调度 UED
深入理解操作系统:进程管理与调度策略
【10月更文挑战第34天】本文旨在探讨操作系统中至关重要的一环——进程管理及其调度策略。我们将从基础概念入手,逐步揭示进程的生命周期、状态转换以及调度算法的核心原理。文章将通过浅显易懂的语言和具体实例,引导读者理解操作系统如何高效地管理和调度进程,保证系统资源的合理分配和利用。无论你是初学者还是有一定经验的开发者,这篇文章都能为你提供新的视角和深入的理解。
42 3
|
1月前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
27天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
26 1
|
29天前
|
消息中间件 算法 调度
深入理解操作系统:进程管理的艺术
【10月更文挑战第33天】本文旨在揭示操作系统中进程管理的神秘面纱,带领读者从理论到实践,探索进程调度、同步以及通信的精妙之处。通过深入浅出的解释和直观的代码示例,我们将一起踏上这场技术之旅,解锁进程管理的秘密。
27 0
|
1月前
|
算法 Linux 调度
深入理解操作系统之进程调度
【10月更文挑战第31天】在操作系统的心脏跳动中,进程调度扮演着关键角色。本文将深入浅出地探讨进程调度的机制和策略,通过比喻和实例让读者轻松理解这一复杂主题。我们将一起探索不同类型的调度算法,并了解它们如何影响系统性能和用户体验。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇理解操作系统深层工作机制的大门。
34 0
|
5月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能

热门文章

最新文章