操作系统与进程

计算机的工作过程

我们的计算机是由一个个硬件组成的,这些硬件大体上可以分为四类:
1.输入设备(如键盘,磁盘,网卡,显卡,话筒,摄像头等)
2.输出设备(如显示器,磁盘,网卡,显卡,音响等)
输入设备和输出设备统称为外设
3.存储器(就是指内存
4.运算器&&控制器(并称为中央处理器,也就是CPU

我们所使用的计算机,基本上都遵循冯诺依曼体系,其工作过程如下图
在这里插入图片描述
可以发现,当我们让CPU处理数据时,并不是直接把数据传给CPU,而是要把数据先给内存,即任何外设在数据层面都是先和内存交互。并且CPU处理完数据后也不会直接把数据给输出设备,也是先给内存,即CPU在数据层面也直接和内存交互

这样设计的根本原因就是不同的设备的读写效率是不一样的。下面是一张存储器速度金字塔示意图
在这里插入图片描述
越靠近CPU的设备速度就越快,而我们一般的外设他们的读写速度可能是以毫秒,秒为单位的,而CPU的速度是以纳秒为单位的,如果我们让他们直接进行交互,那就会降低计算机的整体效率,并且速度越快的设备价格也越高。所以我们在中间就加入了存储器这个设备,让数据都在内存中进行交互,而内存的速度虽然不如CPU那么快,但是好在它相对比较便宜,与磁盘这样的外设相比虽然比较贵,但是速度快,这样夹在CPU与外设之间,就可以提高计算机的整体效率,所以:内存是体系结构的核心设备

操作系统的概念

操作系统(Operator System,简称:OS)是任何计算机系统都包含的一个基本的程序集合,

操作系统包括:
内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(例如库函数,shell程序等)

操作系统的本质就是一款专门针对计算机软硬件资源进行管理工作的软件

如何理解操作系统的管理?
在这里插入图片描述
当用户想要对硬件进行操作时,是如何进行的呢?操作系统又承担了什么角色?
在这里插入图片描述
用户对硬件进行操作必须要使用系统调用接口,什么是系统调用接口?

系统调用接口(system call):操作系统提供的对硬件进行操作的接口
操作系统不信任任何用户,所以当用户想要对硬件进行操作时,就必须使用操作系统提供给我们的接口进行操作,而直接使用系统调用接口比较复杂,成本较高,所以有人对系统调用接口进行了封装,以第三方库或者语言的形势呈现,比如C语言中的printf(),它的作用是向屏幕中打印,对屏幕进行了操作,那么printf的底层就一定使用了系统调用接口,同理,所有涉及到对硬件的操作,一定在语言的内部调用了系统调用接口

那么OS如何进行管理?可以分为两步:
先描述:可以用一个结构体包含被管理对象的各种信息,即用一个结构体描述被管理对象。
再组织:将被管理对象的各种信息使用特定的数据结构组织起来。

什么是进程

操作系统的任务是对软硬件资源进行管理,当我们的程序允许时,会被加载到内存中,形成一个进程。在某一时候系统中可能有大量的进程,那么操作系统就要对这些软件资源进行管理,其管理的方式就是:先描述,再组织

如何描述:任何进程在形成时,操作系统会为该进程创建PCB—进程控制块

这里的进程控制块,在语言角度上来说就是一个包含了该进程各种信息的一个结构体。

Linux系统中的PCB叫做 struct task_struct{//包含了进程的所有属性信息}。

查看进程的命令

ps axj | grep “进程名”
//在屏幕上只打印出指定的进程信息
ps axj | head -1 && ps axj | grep “进程名”
//打印进程信息的同时打印出对应的表头
ls /proc
进程是在内存中的,所以Linux提供了一个/proc目录让我们以文件的方式查看进程
while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep ; sleep 1 ; echo “##################################################################################”; done

> ps axj | grep "进程名"     
> 在屏幕上只打印出指定的进程信息                            
> ps axj | head -1 && ps axj | grep "进程名" 
> 打印进程信息的同时打印出对应的表头 
> ps -l
> 查看与bash相关进程,有进程的优先级等信息
> ps -al
> 与-l类似,可以看到所有的进程
> ps aux
> 列出所有正在内存中的进程
> ls /proc
> 进程是在内存中的,所以Linux提供了一个/proc目录让我们以文件的方式查看进程
> while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep ; sleep 1 ; echo "##################################################################################"; done
> 一个监控进程的脚本,每隔1秒打印一次myproc的进程信息

进程与程序

进程与程序之间是什么关系呢?
在这里插入图片描述
程序也可以看作是文件,程序启动时,一定要把可执行程序文件加载到内存中,而操作系统为了对文件进行管理,就会自动创建一个与进程相关的,包含了进程全部属性的各种信息构成的一个结构体去描述这个文件,也就是PCB,Linux中就是task_struct,他们加起来就叫做进程。
所以,进程=程序文件内容(进程的代码和数据)+为维护该进程的相关数据结构(PCB)

操作系统如何进行进程管理

在这里插入图片描述
当程序被加载到内存,OS就会自动创建对应的PCB,而OS对进程的管理也就变成了对进程控制块的管理,当要让CPU执行某些进程,OS可以通过进程控制块找到进程对应的代码和数据,然后加载到CPU中运行,以此完成对进程的管理。

有了进程控制块,所有的进程管理任务都与进程对应的程序毫无关系,而是与进程对应的内核(OS)创建的该进程的PCB强相关。即操作系统把对进程的管理转化成了对进程数据的管理。

PCB的内部构成

标识符
描述本进程的唯一标识符,用于区别其他进程:PID

可以通过一个系统调用接口查看当前进程的标识符

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);//查看当前进程的PID
pid_t getppid(void);//查看父进程的PID->PPID

在命令行上运行的命令,基本上父进程都是bash

进程状态
包括:任务状态,退出代码,退出信号等。
我们一般main函数中的return 0中的0就是退出码,这个退出码最终会由操作系统给父进程。

命令:
echo $? (输出最近执行命令的退出码)
ps aux | ps axj (可查看进程状态)

优先级
相对于其他进程的优先级。(进程运行的先后问题)

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

相当于在代码中的一个指针,指向即将指向的部分,普通的代码就是顺序结构,从上到下按照顺序执行,而代码中如果有跳转,循环,分支就是不连续的修改pc指针的内容。

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

OS就是通过这个内存指针才能通过PCB找到进程对应的代码和数据

上下文数据
进程执行时处理器寄存器中的数据。

CPU中有很多的寄存器(临时存储单元),在VS2022中可以在调试程序时依次点击:调试->窗口->寄存器 以查看寄存器。

EIP:pc指针
ESP:栈顶
EBP:栈底
EAX,EBX,ECX,EDX:四个通用寄存器
ESI:状态寄存器

什么是上下文?
OS通过把多个进程的PCB放到一个运行队列中来管理CPU一个运行哪一个进程,但是进程的代码可能不是很短的时间就可以执行完的,如果我们的管理方法是执行完一个进程再切换其他的进程,那么假如说我一个进程执行了很久,其他的进行在很长的时间内就没有任何进展,这种情况显然是不合理的,所以OS规定了每个进程单次运行最长时间(时间片),如果这个时间片非常短,在单CPU的情况下,OS系统快速的把进程加载到CPU,运行,切换。用户感受到的就是多个进程是在同时运行的,其本质是CPU的快速切换完成的。即进程在运行期间是有切换的,而进程在运行过程中又可能会产生大量的临时数据,这些数据就会暂存在CPU的寄存器中,当线程时间片到了而被切换时,其暂存在寄存器中的临时数据也会被进程带走(这些临时数据是属于进程的),当进程被加载到CPU时,又会把它的这些临时数据存储到寄存器,然后运行。

上面的操作是通过OS调度模块实现的,OS调度模块可以理解为一种算法,其功能是较为均匀的调度每个进程,让每个进程可以较为均衡的获得CPU资源(即进程被执行)。

以下是上下文的概念:
上下文:就是进程执行时所形成的处理器的寄存器中与进程强相关的临时数据(属于进程)
保护上下文:时间片到了以后,把运行产生的一堆临时数据保存
恢复上下文:再次运行(调度)这个进程时,把曾经保存的数据恢复到CPU上

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

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

其他信息

fork创建子进程

创建进程的方法很多,比如在命令行中执行的命令就属于一个进程,这里创建子进程的方法主要使用系统调用fork。

使用方法可以用man fork查看man手册
<unistd.h>
pid_t fork(void);//

可以使用如下代码创建一个子进程观察一下现象

#include <iostream>
#include <unistd.h>
int main()
{
	fork();
	std::cout<<"PID:"<<getpid()<<" PPID:"<<getppid()<<std::endl;
	return 0;
}

在这里插入图片描述
屏幕上打印了两次,这两次就分别是我们刚刚的父进程和子进程打印的,第二行的PPID刚好等于第一行的PID,也就是说打印的二行的进程是第一行的子进程,其PID也正好比它的父进程的PID大1,这个父进程的PPID是什么?
可以输入查看进程的命令查看一下bash的PID
在这里插入图片描述
也就是说我们创建的进程的父进程就是bash。

fork的本质是创建一个进程,当我们创建了一个进程,那么就表示在系统中又多了一份与进程相关的内核数据结构(PCB)和进程的代码和数据。PCB可以由OS创建,那么fork创建的子进程的代码和数据是什么?默认情况下,fork创建的子进程会“继承”父进程的代码和数据,内核的数据结构task_struct也会以父进程的为模板来初始化子进程的task_struct。对于两个进程来说,fork之前的代码是一样的,只是同时执行到fork的位置,然后各自执行后面的内容。
也就是说,fork之后子进程和父进程的代码是共享的默认情况下数据也是共享的,进程运行时代码是不可以被修改的,所以共享没问题,但是数据在运行时是可能被修改的,如果我的父进程可以修改子进程的数据或者子进程可以修改父进程的数据,这显然是不合理的,进程之间就失去了独立性,所以我们还需要保证进程数据的独立性,用到的方法就是写时拷贝,即当数据没有被修改时,子进程会和父进程共用代码和数据,当进程要对数据做修改时,子进程会重新拷贝一份自己的数据,以此实现数据的独立性。

独立性就是多进程运行,需要独享各种资源,多进程运行期间互不干扰。

fork之后父子进程谁先运行是不确定的,取决于调度器。

一般情况下我们不会让子进程和父进程做一模一样的事,让父子进程分别做不一样的事就是通过fork的返回值实现的。

其返回值有以下几种情况:
fork创建子进程失败返回值:<0
fork创建子进程成功:(1)给父进程返回子进程的PID.(2)给子进程返回0

当子进程创建成功时,会有两个返回值分别返回给父进程和子进程,这样设计的原因是一个父进程可能会有很多的子进程,而子进程只能有一个父进程,所以给父进程返回子进程的PID可以达到控制子进程的目的,而子进程也可以通过getppid得到父进程的PID。

以下是通过if else分流,让父子进程做不一样的事情的代码

pid_t id=fork();
if(id==0)
{
	//子进程	
}
else if(id>0)
{
	//父进程
}
else
{
	//创建子进程失败
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

c铁柱同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值