进程切换和线程调度
文章目录
- 进程切换和线程调度
-
- 一、前言
- 二、进程切换
-
- [2.1 进程的终止](#2.1 进程的终止)
- [2.2 进程的状态](#2.2 进程的状态)
- [2.3 进程的层次结构(管理结构)](#2.3 进程的层次结构(管理结构))
- [2.4 进程状态模型](#2.4 进程状态模型)
-
- [2.4.1 三态模型](#2.4.1 三态模型)
- [2.4.2 五态模型](#2.4.2 五态模型)
- [2.4.3 七态模型](#2.4.3 七态模型)
- 三、线程调度
-
- [3.1 线程概念](#3.1 线程概念)
- [3.2 线程的使用](#3.2 线程的使用)
- [3.3 多线程解决方案](#3.3 多线程解决方案)
- [3.4 单线程](#3.4 单线程)
- 四、小结
一、前言
先前了解了什么是进程,那么进程是怎样切换的呢?什么又是线程呢?它和进程有什么区别?
二、进程切换
2.1 进程的终止
-
正常退出(自愿的)
main函数运行到return,return后面的值作为退出的状态退出的状态有用吗?
进程执行完,需要告诉调度的程序一个状态。在Linux中有一个变量
?,这个变量名是获取上一个进程退出的状态
0-代表成功

2-退出为2号状态,这也属于自愿退出,因为进程发现了这个错误,并把状态返回给调用者
每个非0数字代表一个错误号

最后返回的0是对于上一个进程
echo $?的返回 -
错误退出(自愿的)
正常是0,错误是非0,但是是主动发出错误的,并将错误信息告诉进程,再返回给父进程,父进程将错误信息放到一个变量里暂存一下

-
严重错误(非自愿的)
不可控的因素导致必须退出。比如:程序闪退
cppchar *s = "123"; s[0] = 'a';程序没有错,但是是异常访问。
这种错误不再关注返回的状态数字号,因为这种状态值一般和系统的上下文有关,可能是随机的
-
被其他进程杀死(非自愿的)
比如:遇到程序死循环了,就可以利用另外一个终端
kill -9 进程号来杀死进程,类似"强制退出"退出
-
普通退出
普通退出有可能会被系统忽略
-
强制退出
强制退出是不会被系统忽略的(系统会屏蔽某些信号,但是有些信号是不会被屏蔽-强制信号)
-
2.2 进程的状态
shell
ps aux

shell
man ps

重点记忆:D、R、S
sleep:阻塞 = 睡眠,放弃CPU了,可唤醒的睡眠状态
Linux睡眠分两类
-
可中断的
-
不可中断的
IO,和磁盘打交道,把命令发给磁盘IO了,这是不可中断的
快速设备:CPU等,慢速设备:磁盘IO等
进程的业务分为两种:
-
IO密集型
cwhile(1) { printf("====\n"); }CPU很闲
-
CPU密集型
cint i = 0; while(1) { i++; }CPU很累
2.3 进程的层次结构(管理结构)
UNIX:树状结构,父和子的概念->父进程和子进程
父进程比子进程先退出,子进程还在不在?
cpp
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
void childProcess()
{
sleep(1);
printf("========%d : %d=======\n", getpid(), getppid());
}
int main()
{
printf("fork test...\n");
pid_t pid;
pid = fork(); // 返回值会返回1次,由于复制,会父进程返回一次,子进程返回一次
if(pid < 0)
{
perror("fork");
exit(2); // 释放PCB
}
else if(pid == 0)
{
childProcess();
}
else
{
exit(0);
}
return 0;
}
运行结果:

解释:父进程死掉了,但是子进程还在,但是其父进程是1(子进程是3095,按理来说,其父进程应该是3094),这种情况下的子进程被称为"孤儿进程 ",在OS中,其资源最后会被1号进程回收。(这就是Linux的层次结构)
但是Windows没有,是平级的状态(只要产生进程,彼此之间就是平级的),彼此是独立的。
由此,OS之间是不同的,我们学习OS,学习的更多是普适的观念
2.4 进程状态模型
关于进程状态,这里解释的都是理论模型,在工程中,完全可以自己去设计,合理即可。
注意:Linux没有使用下面任何一种模型
2.4.1 三态模型
-
就绪态(Ready State)
所谓的就绪态就是一个就绪队列(由OS内核维护),和CPU交互就靠就绪队列,即:CPU和就绪队列之间实现了通信(OS中的代码可以实现:CPU从就绪队列中取东西,也可以把CPU中正在运行的东西放到就绪队列中)
-
运行态(Running State)
在CPU中的东西就是运行态,运行态和就绪态是双向的。

a:从就绪态中取出一个东西加载到CPU,开始运行,这个加载过程就是调度行为。
b:运行态也有可能把进程送到就绪态,会有几种可能触发的情况:
- 时间片到了,OS争夺到执行权,OS根据就绪队列的情况,把正在运行的东西放到就绪队列中,再通过调度算法获取一个进程,重新加载到CPU(运行态)【基于分时】
- 基于抢占式(有高优先级的进程)的,当任务创建时,根据进程的优先级决定:把正在运行的程序打断(OS将其送到就绪态),把高优先级的进程马上送到CPU运行
-
阻塞态(Blocked State)
c:访问IO设备(耗时)、P操作(后续会讲)、等待事件发生的情况
如何让阻塞态的进程有机会被CPU光顾?
不能由阻塞态直接指向运行态。这会造成运行态紊乱(两个指向运行态)。
可以将其放到就绪态,这就是唤醒------d。谁唤醒呢?

比如键盘,
scanf(这个进程就不能跟CPU打交道了),调用OS的键盘驱动,等待中断发生(键盘按下),键盘的驱动缓存中没有数据,睡眠(当前调用这个驱动的进程在队列里睡觉)键盘一旦按下,就触发了中断,被CPU捕获,进入到键盘的处理函数,就会读取具体按键的数据放入到缓存区,开始判断(调用wake_up唤醒队列),唤醒队列后底层驱动就发现有数据了,将驱动缓存的数据解析到用户给的空间里,之后
scanf继续执行阻塞态的队列有多个,每个事件都有一个队列
2.4.2 五态模型
增加了创建和终止

2.4.3 七态模型
- 挂起:将数据结构从内存搬移到磁盘
- 阻塞、睡眠:等待事件发生,任务还在内存上
- 运行:CPU,单核CPU的运行态只有一个
- 就绪:可以有多个就绪态
阻塞队列多个,就绪队列一个,有的OS就绪队列是多个(有优先级)
三、线程调度
3.1 线程概念
进程 & 线程
进程:任务调度的单位(task_struct,有一个结构体指针mm_struct*指向一个内存映射表,完成CPU的任务调度),资源的维护单位(mm_struct资源表空间0-3G)
多进程:多任务,任务一切换,(指针、空间也要跟着换)开销比较大。

任务一切换,缓存区的内容就变了
进程 :多个任务被CPU调度,这些任务独享某些内存资源(既是任务的调度体,也是资源的维护单位)
线程 :多个任务被CPU调度,这些任务共享某些内存资源(仍然是调度体,但是共享某个进程的任务资源)


进程释放时,依附于进程的所有线程也都释放
调度的角度
-
纯用户空间的角度(很麻烦)
-
纯内核空间的角度(轻量级)
内核空间给OS和CPU使用
-
混合角度(更灵活)

3.2 线程的使用
-
资源共享
-
轻量级与高效性
创建线程时,仅是分配一个任务而已,仅是存一些状态,没有必要把整个资源重映射
-
性能提升
web服务器需要大量的响应,进程调度少点性能更高
3.3 多线程解决方案

Web服务进程采用多线程:
- 调度线程:分发任务,分发出各个工作线程,保证各个任务都能响应。
- 工作线程:共享Web页面的高速缓存,不用直接去访问内核
优点:共享了资源
3.4 单线程
有个矛盾:多个程序怎么保证先处理哪个程序
2套解决方案:
-
改为I/O复用模型
epoll,后续详解
-
改为非阻塞状态机模型
阻塞就是一直等到响应才走,非阻塞就是不响应就立即走(轮询)。
将每一个要发生的事件设置成为一个状态,根据状态的切换关系设计主程序。
非常麻烦,一般不用
四、小结
进程的切换不是那么容易的:进程的终止分4种,进程的状态重点把握D、R、S,进程的层次结构:Linux的树形结构->父子进程和Windows的平级结构,进程的状态模型:重点把握三态模型。
线程和进程看似相同,其实不同,进程是独占资源,线程是共享资源,各具不同的应用。