程序运行(1)----OS与进程

  导入

(冯诺依曼结构)

冯诺依曼结构是我们大部分计算机都遵守的结构体系,我们所认识的计算机都有一个个硬件组成:

. 输入单元:键盘,鼠标,扫描仪等

. 中央处理器(CPU):含有运算器和控制器

. 输出单元:显示器,打印机等

注:这里的存储器指的是内存

不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)

外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。

一句话,所有设备都只能直接和内存打交道。

 基于冯诺依曼这一基本结构,让我们详细了解计算机在处理数据和返回数据的具体过程吧!

1.操作系统(0S)

总:OS本质上是一种进行软硬件资源管理的计算机软件

宏观来说OS包括两大部分:

(1)内核:用于进程/线程管理,文件系统,内存管理,驱动管理;

(2)其他程序:shell,glibc,原生函数库等

下图是程序在用户与底层间运行的模块图,该图具象化了用户与底层间的联系:

目的:

从该图我们可以看出OS处于一个承上启下的位置,所以设计OS的目的是:

(1)OS对下进行资源管理,使其稳定,高效,安全的工作;

(2)OS对上给用户提供稳定,高效的运行环境

系统调用和库函数概念

在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分 由操作系统提供的接口,叫做系统调用。

系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统 调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。

注:向上提供的接口只能是C语言的接口,所有软件的底层都必须和C直接或间接有关

下来我们针对OS的进程管理做展开:

计算机是如何进行进程管理的呢?

1. 描述起来,用struct结构体

2. 组织起来,用链表或其他高效的数据结构

让我们通过下面讲述再次理解“先描述在组织”这一管理的本质与方法

2.进程

总:进程 = 内核数据结构(结构体task_struct) + 程序代码数据

内核:(担当分配系统资源(CPU时间,内存)的实体)
概念:一般而言正在执行的程序我们称为进程
图示文件的运行:

解释:

描述进程:

(1)进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。

(2)课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct

 task_struct-PCB的一种

(1)在Linux中描述进程的结构体叫做task_struct。

(2)task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息

task_struct内容分类:

(a)标示符: 描述本进程的唯一标示符,用来区别其他进程。

(b)状态: 任务状态,退出代码,退出信号等。

(c)优先级: 相对于其他进程的优先级。

(d)程序计数器: 程序中即将被执行的下一条指令的地址。

(e)内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

(f)上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。

(g)I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。

(h)记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

(i)其他信息

(1)标识符

查看进程:

详细:

简略:

利用ps -ajx查看:

以test为例:

最上面一行的前两个即为进程的标识符:

在了解他们之前我们要知道每一个进程都是由一个父进程创建而来(后面有解释具体原因),可以方便管理

PID:进程本身编号

PPID:父进程编号

对于这两个标识符我们可以利用系统调用来查看:

geipid--PID:

getppid--PPID:

以test为例:

(2)状态

储备:
认识系统调用fork

子进程的程序代码是从父进程中继承的,一般而言,代码可以分享,但数据是各自私有的!

return:fork如果成功会给子进程返回0,父进程大于1

以test为例:

为什么ret有两个返回值:

程序在fork后return之前就已经产生了父子两个进程,他们会分别return一个ret!

并行与并发:

并行:任何时刻多进程同时执行;

并发:CPU执行进程代码,并不是执行完一个才开始执行下一个,而是给每一个进程分配一个时间片,基于时间片进行调度轮转(在一段时间内执行一个程序)(针对于单CPU)

时间片

使进程分时进行的一个结构体属性

等待

等待本质:连入目标外部设备后,CPU不调度

等待分为两种情况:

内存:

在内存上等待称为运行(只要进程在运行队列里)

操作系统:

在操作系统上的等待称为阻塞

图示:

挂起

本质:用时间换空间

在内存资源严重不足时,操作系统会让阻塞的数据换出到磁盘(swap分区),等外设输入后再换入内存并改变状态

介绍:(具体介绍以Linux为例)

下面的状态在kernel源代码里定义:

/*
 * The task state array is a strange "bitmap" of
 * reasons to sleep. Thus "running" is zero, and
 * you can test for combinations of others with
 * simple bit tests.
 */
 static const char * const task_state_array[] = {
 "R (running)", /* 0 */
 "S (sleeping)", /* 1 */
 "D (disk sleep)", /* 2 */
 "T (stopped)", /* 4 */
 "t (tracing stop)", /* 8 */
 "X (dead)", /* 16 */
 "Z (zombie)", /* 32 */
 };

在Linux中进程状态具体有:

R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列 里。

S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))(进程可被kill -编号杀掉)

D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。(进程不可被强制杀掉)

T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。

X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态

t状态 (tracing stop):当进程被追踪,遇到断点停止

Z状态(僵尸状态):僵死状态(Zombies)是一个比较特殊的状态。

当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码

所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

以test为例:

命令行监控脚本:

while :; do ps ajx | head -1 && ps ajx | grep test | grep -v grep;sleep 1; echo "###############"; done

僵尸进程的危害:

进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎 么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!

维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话 说,Z状态一直不退出,PCB一直都要维护?是的!

那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构 对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空 间!

内存泄漏?是的!

最后补充一个状态:孤儿进程:(子进程被祖父进程接收)

父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?

父进程先退出,子进程就称之为“孤儿进程”

孤儿进程被1号init进程领养,当然要有init进程回收喽。

(3)优先级

利用ps -l我们可以看到:

其中PRI代表进程可被执行的优先级,越小越早执行,NI代表进程nice值,两者合起来构成有优先级计算公式:Linux进程最终优先级 = PRI(缺省值80) + NI(修行数据)

其中NI取值为[-20,19),则优先级范围为[60,99)

补:可以利用top -r +编号修改优先级

(4)进程切换

储备:

a.进程执行时会有许多的临时数据,在CPU寄存器中

(如:pc指针(eip)储存当前执行命令的下一条命令地址;

            ir:指令寄存器(保存正在执行的命令)

b.CPU内部的寄存器数据,是进程执行时的瞬时状态信息数据

c.寄存器 != 寄存器的上下文数据(数据保存在各自的PCB中)

挂起swap交换本质:在进程换出时将相关寄存器内容保存起来,换回时将历史保存数据恢复到寄存器

寄存器是CPU中在CPU与内存间的特殊高速处理器!

图示:

单个PBC:

进程切换图示:

源码:

(5)Linux真实调度算法O(1)

a.利用哈希桶体现优先级

根据哈希桶的140个数据从而确定了Linux共有四十个优先级,相同优先级会链在同一位置,相当于一个优先级会有一个进程链。

b.调度队列

注意:

(1)CPU调度只会从active队列中进行调度

调度情况有:

运行退出

不退出但时间片到了

有新进程产生

后两种情况均放入expired队列等待,最终造成active队列数据减少,expired队列数据增多,当active队列数据没了,两队列交换继续按优先级进行进程调度

(2)为减少检测哈希桶数据的误差,降低交换的风险,利用位图bitmap[5],32*5=160覆盖140个元素的哈希桶

调度进程时以32位一次进行检测

(3)Linux链式结构均为双链表,进程中存在一个属性struct_link结构体用于进程间的链接

(4)进程可以处在许多队列中,因为其PCB中含有许多结果体属性用于链接各队列

error:

(5)若已知一进程某一属性我们可以利用偏移量访问其他属性

图示:

总结:

程序在运行时:

(1)会将磁盘文件加载到内存中,在加载之前,会根据默认的结构体事先建立PCB(task_struct),然后根据加载的文件补充其他属性,并加上时间片等信息(先描述);

(2)将多个task_struct链在一起(再组织);(创建)

(3)链好后根据结构体中struct-link链到struct*head加载到运行队列中,并根据优先级加载到哈希桶中并以active队列包装,并与expried队列配合合作;(等待,挂起)

(4)CPU从active队列取数据,加载到寄存器中处理并返回处理结果,或者返回未完成的数据,并保留临时数据,方便下次调用(运行)

这便是程序运行的流程,理解后便可以在脑海里形成动态图!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值