一.查看进程
进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
我们先来看。
windows环境中
Linux环境中:
1. 用ps ajx |grep “可执行文件名字”
第二种方案 ls / proc
所有的目录结构都是在内存中的,提供proc 目录让我们以文件的形式查看。
.exe对应进程是谁。
二.进程VS程序
加载到内存中的程序就叫做进程。那进程和程序有什么区别吗?
- 例如磁盘当中的x文件 XXX.exe可执行程序程序本质上就是文件,可执行程序文件也必须加载到内存当中。
- OS也要为你这个进程创建一个PCB 或者 task_struct,这个结构结构体当中包含了这个文件的相关属性(暂时不展开讲)两者相结合统称为进程。
例如:并不是你进学校你就是这个学校的学生,这个学校里面得有你的信息。 - 所以进程= 程序文件内容+ 相关数据结构(与进程相关的)task_struct{}(由操作系统自动创建)
List当中的Node节点类型就相当于task_struct,包含了所有的属性信息也就是数据。
三. 进程PCB
1. 进程PCB 对于程序的意义
- CPU中对于进程的调用只需要对PCB相应的进程属性的访问就可以。
操作系统将进程给CPU,只需要你将test_struct 结构体变量链接到CPU之中。控制块去排队,通过各种属性,就可以使得CPU找到进程的代码进行跑。
*有了进程控制块,所有的进程管理任务和进程对应的程序毫无相关,与进程对应的内核创建的该进程的PCB强相关。
进程控制块就是常说的pcb,是操作系统调度控制程序运行的唯一实体。 - 程序是静态的概念;进程是程序在处理机上一次执行的过程,是动态的概念一个程序可以作为多个进程的运行程序。
一个进程是一个程序对于多个数据集的执行过程,是分配资源的基本单位。
作业需要计算机完成的某项任务,是要求计算机完成工作的集合,一个作业可以是多个进程组合,但至少是一个进程。
所以,这里可以再一次强调:
进程=程序 + 操作系统维护进程的相关数据结构。
2. PCB的内部构成
PCB(test_struct) 内容分类:
1. 标识符:pid
用于描述进程的唯一标识符,用来区别于其他进程->pid.
查看进程pid的系统调用接口:pid_t getpid();
getpid(); //获取id标识符
getppid(); //获取父进程的id标识符
在命令行上运行的命令,基本上父进程都是bash
注:kill -9 +pid 命令将进程杀掉
2. 退出码
进程属性有:任务状态,退出代码,退出信号
我们书写main函数时结尾的return 0;就是进程退出时退出码,让他的父进程拿到。
指令echo $? :查看执行最近一次进程的退出码
3. 优先级 VS 权限
他俩之间的区别就是 先后的问题 和 能不能的问题
CPU 比较少,但是进程还是非常多的。就涉及到进程彼此之间优先级的问题。(在后面讲到,现在只说明PCB中有这个属性。)
4. 程序计数器:
指向当前程序正在执行语句的下一条语句的地址
我们逻辑中的顺序结构以及CPU 是怎么区分先后顺序的,依靠的就是PCB中的程序计数器。
5. 内存指针:
通过PCB task_struct的内存指针找到数据和代码。
通过这个就实现了只通过调用PCB就可以实现调用进程的结果。
6. I/O状态信息:
进程执行时处理器中寄存器中的数据。文件操作就是进程操作。
7. 记账信息:
OS调度模块,较为均衡的调度每个进程获取CPU资源,然后进程被执行。调度器依靠记账信息就可以更好的调度进程。类似你的年龄,不同的年龄去被调度做不一样的事情。
8. 上下文数据
进程被创造就是要被CPU执行其代码的,但是进程很多,所以每个进程的PCB都需要在运行队列中排队。
但是有的时候,进程的代码可能不是很短时间就完成的,所以,
规定每个进程单次运行的时间片:假如每个进程只给10ms时间,运行不完成就继续去排队。
在单CPU情况下,用户感到多个进程同时运行,本质上是通过CPU在时间片的作用下快速切换进程完成的。
所以:
进程在运行期间是有切换的,进程可能存在大量的临时数据 ->
暂时在CPU的寄存器中保存,保存进程的核心临时数据。
可是CPU只有一套寄存器啊!所以在多进程切换进程的时候可能会存在数据的替换。
故事:大学入伍当兵但是没有给学校报备,因为学校没有你的信息了,所以认为你在学校的进程就结束了。等你大学回来再想继续你大学的进程时,没有你的信息你就无法再继续了。
那么切换回来的时候怎么找到原来的数据呢?
-> 保护上下文数据,恢复上下文数据。
目的是:
为了让你做其他事情但是不耽误当前,当你想回来的时候可以接着进行。
所以,
虽然寄存器硬件只有一份,但是寄存器里面数据是你这个进程的!!切换的时候,将寄存器中的数据先保存一下,结构体里面再套一个结构体
再运行排到时,将原来的上下文信息再重新交给CPU接着执行。
通过上下文,我么可以感受到进程是相互切换的。
这个上下文信息就是进程放在CPU中运行产生的数据。
四. fork()创建子进程的本质
1. 如何用代码创建进程 fork();
2. 怎么验证有两个进程?
用fork() 创建子进程,就会有两个执行流。现象表示:
(1)既可以执行if也可以执行else
(2)while执行了两个进程
(3)printf();打印了两次
图示如下:
3. 那我们如何理解fork创建子进程的本质?
- ./cmd ,run command, fork() 在操作系统角度都是创建进程的方式
- fork本质是创建进程,系统里面多了一个进程与进程相关的内核数据结构+进程的代码的数据
我们只是fork了,创建了子进程,但是对应的代码和数据呢?
fork只是创建了子进程,没有代码和数据。
通过系统调用创建进程-fork初始化,
默认情况下,会继承父进程的代码和数据。
内核数据结构task_struct 也会以父进程的为模板初始化子进程的task_struct
*特点:
3. fork之后,子进程和父进程的代码是共享的,fork 之前的代码也是共享的,只是不执行罢了。顺序往后执行而已。
4. 代码是不可以被修改的,父子代码只有一份,因为代码是具有只读性的。例如家业只有一份,父子是共享的。
*代码跟父进程一样,那数据呢?
5. 数据默认情况下也是共享的,不过要考虑被修改的情况,数据因人不同就会不一样可修改。
通过数据的**“写时拷贝”***来完成进程数据的独立性,
*写时拷贝:就是一个进程的数据修改了,写实拷贝拷贝一份一样的给你修改,别干扰别人。
所以:
如果父子进程都不进行数据的写入,那么数据还是共享的。
当有一个人呢尝试修改时,OS 会干涉,先进行写实拷贝,然后再进行修改。
但是并不是每一个数据都需要写实拷贝,所以只有在需要用的时候才会践行写实拷贝,这样会更加省空间,不会造成不必要的数据拷贝而浪费空间。
数据和代码这样处理都是为了维护进程的独立性。
fork的返回值 pid_t 衍生出一个问题:
4. 我们创建的子进程就是为了和父进程干一样子的事情吗?
不是。一般还是根据fork的返回值来完成分开去做不一样的事情。
fork有的返回值:
要么失败要么成功,
成功的话:给父进程返回子进程的pid,给子进程返回0.
图示:
5.但是现在的函数fork为什么有两个返回值呢?
因为现在是多执行流
*1. 如何理解有两个返回值?
开始return语句 说明代码的核心数剧已经执行完毕了,也就是在返回之前已经完成了两个进程的创建。后续的代码也就是return是共享的,会被两个进程执行两次,返回的时候直接返回两个。
*返回值是数据吗?return的时候会写入吗?
返回通过寄存器返回,函数有变量来接收,接收的变量就是储存数据的空间。父子进程两个执行流会有两次返回和接收,所以会有有两个返回值。
return的时候的先后问题就会有写入,进行写实拷贝的时候造成返回值的不同的问题
*2. 如何理解两个返回值的设置?
父进程:子进程=1:n的关系,子进程的返回值便于父进程根据返回值判断并控制子进程。
写代码多进程。
3.父子进程谁先开始跑的呢?不确定,调度器CPU会调整。
五.进程状态的解释和图示
状态信息在PCB中。
进程状态的意义*:方便OS判断进程,完成特定的功能,比如调度,本质是对进程进行分类.
具体状态:
- R 运行态一定是在占有CPU吗?一定是在CPU中正在运行吗?
不一定,已经在运行队列(run_queue)当中,CPU调用的都是对于task_struct 的增删查改
*R状态的意义是什么?
随时准备着被调度到CPU当中运行,是在等待CPU。
查看R状态:
- S :睡眠状态(浅度睡眠)可终止*
当我们想要完成某种任务的时候,任务条件不具备时,需要进程进行某种(输入者输入)等待,
排列在wait_queue等待队列当中的进程(是进程的PCB在等待),等待资源就绪.
等到磁盘可读等资源就绪就会将进程状态改为R状态,放到run_queue中,再开始访问外设。
千万不要认为进程只会等待 CPU 资源,等CPU时,是运行队列。 等待磁盘网卡等外设时是等待队列。
所谓的进程在运行的时候,有可能因为运行需要,可能在不同的队列。
在不同的队列,进程的状态是不同的。(状态额本质就是对进城进行分类,这个队列就是实现了分类)
我们把从运行状态的task_struct(位于run_queue)放到等待队列中就叫做 挂起等待(阻塞)
从等待对列放到运行队列,被CPU调度就叫做唤醒进程。
可中断睡眠S
两个队列之间的切换就完成了。
S状态的查看:
等待配置就绪而一直卡着的状态就是S状态,可以被中止。
我们日常的软件卡死现象就是运行在等待。
网卡磁盘显示器键盘,进程都可能等待这些外设就绪。所以会存在少量的运行队列,大量的等待状态。
实测:
(1)死循环向显示器这些外设设备上打印(IO状态)的时候,速度在CPU看来是很慢的,所以检测处于S状态。
(2) 一直切换RS速度很快。往外设上打印时需要时间的,此时在CPU看来,你的速度很慢,我就得处于S状态等会你。
所以, IO和CPU相比,太慢了。
-
D状态(深度睡眠状态):不可中断状态,操作系统也杀不掉。
例如在进程等待外设完成某些任务时,此时的进程是不能被OS干掉的,如果进程此时被干掉了,外设完成任务之后返回数据时就会造成数据丢失就完了。
如果进程处于这个状态,就只能等这个任务完成,或者重启。 -
T状态:暂停状态
实测图示和注释如下:
kill -19 pid :将进程中止,此时进程处于T状态
kill -18 pid :进程继续运行,此时进程处于S状态,但是已经没有右上角的+号了。Ctrl C也不好使了,因为已经处于后台状态了
只能用 kill -9 pid :将现在这个后台进行的进行中止
-
t状态,追踪状态,调试的时候。
-
x状态:死亡状态,需要回收进程资源,内核数据结构+你的代码和数据。
-
z状态: 僵尸状态。
为什么要有僵尸状态?
法医鉴定是否死亡的期间,人就处于僵尸状态,你还不确定他死,并且未查出死亡原因。
为了辨别退出死亡原因。进程退出的信息也是数据放在task_struct 中,这时候的PCB就被更改为z状态。
如果没有人检测或者回收进程(父进程),该进程进入z状态。
确认死亡之后才是X状态。如何检测和回收暂时不说。
怎么看到?
僵尸进程
子进程死了,父进程还没回收的间隔,子进程状态就处于僵尸进程。
演示如下:
僵尸进程是指先于父进程退出的子进程程序已经不再运行,但是因为保存退出原因,
因此资源没有完全释放的进程,因此不会自动退出释放资源,也不会被kill命令再次杀死
僵尸进程因为资源不会完全释放,因此有可能会造成资源泄漏,但是孤儿进程不会。
孤儿进程
父进程先死,先退出了。
孤儿进程:父进程先死了,子进程还在跑,就将子进程交给1号进程(OS)领养。
精灵进程与守护进程指的是同一种进程,守护进程:运行在后台的一种特殊进程,独立于控制终端并周期性地执行某些任务。
孤儿进程:子进程先于父进程退出,运行在后台,父进程成为1号进程,退出后由1号进程回收资源
六. 进程优先级
为什么会有优先级?
本质上就是资源太少。本质是分配资源的一种方式
1. 查看进程优先级:pri
ni
ps -al
UID:用户ID 身份证号码
Linux 当中的优先级数据值越小,优先级越高。
ni
:优先级的修正数据。进程优先级的调整幅度。
调整进程优先级就是调整nice 值。调整的时候PRI的值是80开始
ni的取值范围是-20~19,一共40个级别。优先级最高就是60
new_pri=old_pri+ni;
调整优先级的过程。
每一次指令进行修改的时候,old_pri都是80开始的。
调整进程优先级的系统接口:不推荐自己改。
2.修改进程优先级
top命令进行优先级的修改。但是修改之后的优势还是感觉不到的。
nice值为什么只能是相对比较小的范围呢?
优先级再怎么设置,也只能是一种相对的优先级不能出现绝对的优先级,插队差不多就行了别插太多。
避免出现很严重的“饥饿问题”。别让一个进程长时间得不到CPU资源。 而调度器的核心功能:较为均衡的让每个进程享受到CPU资源。
七.进程的其他概念
知道就行,自行理解,不多BB了!!
进程的竞争性。
独立性。
并行性。多个CPU中同时运行多个进程。
并发性:多个进程在一个CPU下采用进程切换的方式,在一段时间之内让多个进程得以推进。