目录
前言:
本篇主要是带大家了解进程,在了解进程之前我们先来简单的认识一下操作系统
下图这是我们计算机的大体构成,我们很容易看到,操作系统起到的是一个承上启下的作用
那么操作系统是什么呢?
其实操作系统本质上就是一款软件,用于管理软硬件资源的软件
操作系统其向下管理好软硬件资源,向上提供了良好的执行环境(为用户提供了一个稳定、安全、简单的操作环境)
操作系统是如何管理软硬件资源的呢?
操作系统的管理其实主要概述成以下内容:
1、管理是对被管理对象的数据的管理(对数据的管理),拿到了被管理者的数据,即可通过数据进行对应的处理(决策)
这里操作系统通过驱动程序管理硬件(同时操作系统也在管理驱动程序)
2、由于操作系统需要管理大量的数据(硬件设备)等,所以我们需要 先描述,再组织
描述:使用struct结构体
组织:使用各种数据结构
即先将被管理者(硬件)抽象成一个对象模型(建模),然后再将所有对象组织起来(形成某种数据结构)进行管理
OS对软硬件的管理最后都会转变成对某种数据结构的管理
tips:操作系统的对外提供服务是通过为用户提供系统调用接口完成服务
(我们看到的往上的对调用接口的各种封装都是为了让用户更好操作硬件)
这里我们可以简单参考一下这句话:在Linux下一切皆文件。
在Linux下,无论是软件还是硬件,都可以看作一个文件,将硬件形象化为了一个文件
操作系统存在的意义?
用户直接访问硬件是十分高难度的一件事,亦或者用户直接访问操作系统也是比较不容易的事,所以操作系统将一切复杂的体系给封装起来,只向外提供了系统调用的接口,再对这些系统接口进行封装,使其更易使用(如鼠标点击等行为),此时作为普通用户也可以比较容易去使用计算机
简单了解完操作系统之后,我们正式来认识认识本篇的主题:进程
什么是进程?
我们在了解冯诺依曼体系结构时知道了:所有程序在运行时,都要被加载到内存
并且我们要知道,所有程序本质上即是代码+数据的组成,其实其本质也是个文件,该文件加载到内存后并不能直接被操作系统进行调度管理,所以操作系统需要管理该程序的话则需要进行我们所说的:先描述,再组织。
所谓描述即是为这个可执行程序加载到内存的代码+数据,生成一个PCB(Process control block)结构体,之后操作系统再通过这个PCB结构体去控制这个程序。操作系统内的进程都是如此进行描述,产生的许多PCB结构体再通过某种数据结构给组织到一块儿。
操作系统对PCB的调度控制即被称为进调度。
我们的所说的进程即:程序的代码及数据+PCB结构体
tips:在Linux下,进程的PCB结构体为“task_struct”,不同的操作系统的PCB不尽相同
知道了什么是进程之后,我们来看看在Linux下
怎样去查看具体的进程
我们首先来写一个死循环的程序,便于我们查看进程
平常我们写的程序运行时都是属于进程,不过就是一运行完便退出了
#include<iostream> #include<unistd.h> #include<sys/types.h> using namespace std; int main() { while (1) { pid_t id = getpid(); pid_t pid = getppid(); cout << "hello world 当前进程id:" << id << " 当前父进程id:" << pid << endl; sleep(1); } return 0; }
getpid()与getppid()是属于前面所说的操作系统提供的系统调用接口,作用即是查看进程的pid以及ppid
我们先让程序运行起来,再打开一个新的终端查看我们的进程
通过 ps -ajx | head -1 && ps -ajx | grep mytext 指令可以查看到我们对应进程的属性信息,其中便包括了pid以及ppid,以及进程的状态等
我们也可以通过下述方式进行进程查看
在Linux根目录下,有一个proc的目录,里面即是系统的一些正在运行的进程
我们可以通过ps命令获取到进程具体的pid后再通过/proc目录查看到进程的更具体的信息
这里我们主要关注两个属性:pwd和exe
cwd:指向当前进程工作路径
exe:指向当前二进制文件(可执行程序)所在路径
我们知道怎么查看进程后,那在Linux下
如何创建进程?
系统为我们提供了一个系统调用:fork()
fork可将进程一分为二,给子进程返回0,给父进程返回子进程的pid
- pid_t fork():创建子进程
- 关于fork()的返回值
- 创建子进程失败时返回-1
- 创建子进程成功时给父进程返回子进程的pid,给子进程返回0
- fork()之后则分为了父子进程且父子进程的代码是共享的
但fork返回给父进程和子进程的值不同,则可以通过fork的返回值区分父子进程,让父子进程进行不同的工作
- 创建子进程时,OS需要做什么?
(本质就是系统多了一个进程)
- 新建一个task_struct(PCB)
内部属性以父进程为模板创建
- 返回子进程pid给父进程
因为父进程需要通过pid区分子进程,而子进程只有一个父进程
- 父子进程创建出来后,谁先运行具有不定性
具体由操作系统的调度器决定我们来具体实践一下如何创建子进程,并且利用子进程执行与父进程不同的代码
#include<iostream> #include<sys/types.h> #include<unistd.h> #include<stdlib.h> using namespace std; int main() { while (1) { pid_t id = fork(); if (id < 0) { cerr << "fork"; } else if (id == 0) { while (1) { cout << "fork return : " << id << endl; cout << "this is child process,pid:" << getpid() << endl; sleep(2); } } else { while (1) { cout << "fork return : " << id << endl; cout << "this is parent process, pid:" << getpid() << endl; sleep(2); } } return 0; } }
通过程序执行结果我们能很好的验证fork的返回值以及如何利用fork创建子进程执行与父进程不同的代码,但是fork为什么会有两个返回值呢?
其实当pit_t fork()函数执行完创建子进程的代码逻辑后,子进程便已经被成功创建,此时再之后的代码便都是父子进程共享的了,包括fork()函数的return语句。由于return语句被父进程和子进程都执行了一遍,因此便有了两个返回值
咱晓得了怎么创建进程,再来看看进程都有一些什么样的概念吧
- 进程
- 竞争性: 系统进程数目众多,而CPU资源相对来说只有少量,所以进程之间是具有竞争属性的,为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
一般关闭一个进程对其它进程不会造成影响- 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
并行的多个CPU也是通过并发进行运算- 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
- 时间片:即进程在CPU中的运行时间,时间片可以被出让
时间片需要通过调度器进行抢占
接下来我们来看看之前我们提到的一个PCB结构体,这个结构体可以说是进程控制的主要部分
这个PCB结构体里面都有什么呢?它具体有什么用呢?
PCB结构体里都有什么?
task_struct会被装载到RAM里并且包含着进程的信息(属性信息):
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。 (进程PID)
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息(我们主要关注上面几种即可)
前台进程:运行时占据了bash窗口,无法进行其它操作,可以直接Ctrl+C终止
后台进程:在后台运行,不占据bash窗口,但无法使用Ctrl+C终止(运行时加上 &即说明在后台运行)ps:可以使用kill -9 进程ID进行终止
关于进程状态:
- 进程状态
- R运行状态(running):
并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。- S睡眠状态(sleeping):阻塞状态
意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep)- D磁盘休眠状态(Disk sleep)
有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束- T停止状态(stopped):调试状态
可以通过发送 SIGSTOP 信号(19号信号)给进程来停止(T)进程,这个被暂停的进程可以通过发送 SIGCONT 信号(18号信号)让进程继续运行- Z(zombie)-僵尸进程:
是一个进程已经退出但还不允许被OS释放,处于一个被检测的状态,该状态则被称为僵尸状态
- 维持僵尸进程是为了让父进程和OS进行回收
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
- X死亡状态(dead):
这个状态只是一个返回状态,你不会在任务列表里看到这个状态(一瞬间的状态)
- 孤儿进程
- 什么是孤儿进程?
父进程先退出,子进程还在,该子进程便被称为孤儿进程,孤儿进程会被1号进程领养(系统本身)
- 孤儿进程为什么要被领养?
因为子进程本来需要由父进程回收,但父进程退出了,子进程则没人回收,只能由系统回收ps:我们可以使用一段命令行脚本来持续查看进程状态,方便查看进程状态的变化
while :; do ps axj | head -1 && ps ajx | grep myproc | grep -v grep; sleep 1; done
关于上下文数据:
tips:CPU的寄存器只有一份,但是上下文数据可以有多份,分别对应不同的线程
- 上下文数据
- 上下文数据主要即程序在执行时产生的临时数据,包括运行状态、执行代码信息以及程序的返回值等等
- 上下文数据在程序执行时绝对不能被抛弃,抛弃了即等于丢失了代码运行信息
- 由于CPU是并行执行运算的,所以当进程进行切换时,上下文数据需要被进程自身保留(保存至PCB当中),当下次进程切换回运行时则能从上次的运行状态继续执行
关于进程优先级:
- 进程优先级
确认哪些进程该优先获取某种资源
- 查看进程优先级:ps -al | grep 程序名称
- 为什么要有优先级?
因为CPU的资源有限,但进程太多,则进程间需要通过某种方式竞争资源
- 进程优先级于PCB中是由整型表示
整型越小,优先级越高
- 优先级是为了让调度器评判,决定该线程是否要优先执行
优先级可以手动进行调整,但一般不建议也不需要进行调整
- Linux下优先级 = 老优先级 + nice值
nice取值范围:-20 ~ 19一共40个级别
- 优先级修改:top命令后,按r进入优先级修改命令行后输入进程名 nice值即可
关于task_struct的内容我们就暂时先了解这么多,其它的博主会在后续内容里嵌入
有关进程的基础了解我们就先到这里,随着之后学习的深入,进程这块儿还会有一些补充知识,随后再进行相对应的删改。
如果本篇对你有帮助的话,可以给博主点个小小的关注⑧