首页 > 系统相关 >进程的初步认识

进程的初步认识

时间:2024-07-06 23:26:11浏览次数:17  
标签:状态 优先级 操作系统 认识 初步 进程 运行 struct

目录

一、硬件方面介绍

1.冯诺依曼体系结构

2.存储分级

二、软件 方面

1.操作系统是一款进行管理的软件,它可以管理硬件也可以管理软件

2.操作系统如何管理?

三、进程 

1.概念

总结

四、linux中对进程的管理 

1.task_ struct内容分类

2.查看进程

 3.通过系统调用获取进程标示符

4.知识补充

5.一些信号

6.通过系统调用创建进程-fork

1.对于fork返回值有两个的解释

2.如何做到一个函数返回两次? 

3.一个变量如何储存两个值?

4.父子进程谁先运行呢?

 bash

7.进程状态 

1.运行

2.阻塞

3.挂起

8.linux下状态 

 R运行状态(running):

S睡眠状态(sleeping):

知识补充 

D磁盘休眠状态(Disk sleep)

T停止状态(stopped):

X死亡状态(dead): 

Z僵死状态(Zombies)

孤儿进程

9.进程内的访问

10.进程优先级

1.优先级是什么?

2.为什么要有优先级

3.linux中的进程优先级

4.用top命令更改已存在进程的nice:

5.优先级的工作原理

如何判断当前运行的那个队列是否为空 


一、硬件方面介绍

1.冯诺依曼体系结构

abcde都是独立的个体,所以各个单元必须要用“线”连接起来,称为总线,为图中红色

1.系统总线   连接运算器和控制器

2.io总线   连接存储器和输入输出设备

一个程序要运行必须先加载到内存,是因为冯诺依曼体系就是这样子规定的

2.存储分级

二、软件 方面

1.操作系统是一款进行管理的软件,它可以管理硬件也可以管理软件

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

                                                                总体纵览图

操作系统存在的意义是通过管理好底层的软硬件资源,为用户提供一个良好的执行环境 

操作系统里面会有各种数据,但是操作系统不相信任何用户

因此为了保护自身数据的安全,也为了能够给用户提供服务,操作系统以接口的方式给用户提供调用的入口,来获取操作系统内部的数据。

这些接口是操作系统提供的,用c语言实现的,自己内部的函数调用---------系统调用(即上图的系统调用接口)

库函数(lib)vs系统调用

库函数和系统调用是上下层,调用与被调用的关系

所有访问操作系统的行为都只能通过系统调用完成,任何库函数只要试图访问操作系统或者硬件(软硬件资源),都需要经过系统调用

2.操作系统如何管理?

先描述后组织

管理者决策者    操作系统     类比校长

执行者               驱动程序             辅导员

被管理者            软硬件资源         学生

我们要管理学生,先得对学生的信息进行描述,例如学院  姓名  班级  专业等等

每个学生都可以转换成一个结构体

我们再将这些结构体进行组织,例如使用双向链表将每一个结构体进行连接

那么我们对学生的管理就转换成对链表的增删查改了

在操作系统中任何管理对象,最终都可以转化成为对某种数据结构的增删查改

三、进程 

1.概念

一个已经加载到内存中的程序,被称为进程,也被称为任务

可以理解成,正在运行的程序,叫做进程

而一个操作系统不仅仅只能运行一个进程,可以同时运行多个进程,因此我们也必须将进程管理起来,如何管理呢?先描述再组织。

进程 = 内核PCB数据结构对象+ 我们自己的代码和数据

                     这个数据结构对象

                     描述这个进程的所有属性值

任何一个程序在加载到内存的时候,形成真正的进程时,操作系统要先创建描述进程的结构体对象----PCB  (process ctrl block 进程控制块),PCB就是一个进程属性的集合

这个集合就是一个struct结构体,里面包含例如:进程编号(PID)  进程的状态 优先级等等,根据进程的PCB类型,为该进程创建对应的PCB对象

我们只需要对PCB进行管理就可以管理这个进程,PCB中含有进程的各个属性,因此也顺理成章地会去记录代码和数据的位置,记录下指针。

(操作系统也是软件,所以开机时也会加载到内存中) 

在操作系统之中,对进程进行管理,就变成了对单链表进行增删查改

总结

计算机管理硬件
1. 描述起来,用struct结构体
2. 组织起来,用链表或其他高效的数据结构 

四、linux中对进程的管理 

Linux操作系统下的PCB是: task_struct

所以

在Linux中描述进程的结构体叫做task_struct。
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息

1.task_ struct内容分类


1.标示符: 描述本进程的唯一标示符,用来区别其他进程。
2.状态: 任务状态,退出代码,退出信号等。
3.优先级: 相对于其他进程的优先级。
4.程序计数器: 程序中即将被执行的下一条指令的地址。
5.内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
6.上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
7.I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
8.记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等

9.其它信息

2.查看进程

1.ps ajx(我们通常会搭配grep来使用)

COMMAND系统运行这个进程时是调用什么指令

2. ls  /proc

这个文件夹内的信息实际上是操作系统将内存中的进程进行可视化(因此这个文件夹里面的信息是动态变化的)

这些蓝色的数字就是相应进程的PID 

(同一个程序进行多次运行时,所产生的PID也会不同)

进入目录,里面存储的是该进程的一些属性,我们这里简单看一下cwd和exe

cwd (current work dir)后面的内容指的是:当前的工作目录

例如我们touch  文件名  来创建一个文件,当touch载入内存变成进程的时候会记录它启动时的工作目录,所以创建的文件在没有指定路径的时候,默认就会在当前路径下创建

exe  后的内容指的是:这个进程对应的文件是这个路径下的这个文件,即指针信息可视化

 3.通过系统调用获取进程标示符


进程id(PID)   使用getpid()
父进程id(PPID)使用getppid()

进程的pid也是它的属性,所以pid也在task_struct中存放

ppid在同一个终端下启动一般不会改变,所有子进程的父进程都是bash

4.知识补充

1.在底行模式下,使用!man也可以直接查询手册

2.多条不同指令可以在同一行进行输入,可以使用&&进行分隔,从左到右一次执行

5.一些信号

kill -9  PID 杀死对应pid的进程

6.通过系统调用创建进程-fork

fork可以创建子进程,即系统里多了一个进程,那么这个进程也需要有自己的task_struct和数据以及代码

因为子进程没有自己的代码所以一般而言,fork之后的代码,父子共享

1.对于fork返回值有两个的解释

  我们为什么要创建子进程呢?是因为我们想让父子进程做不同的事情,所以需要让父和子执行不同的代码块,为了实现这个功能,fork就具有了不同的返回值,对父进程返回子进程的pid(因为字进程查询父进程pid成本低,但是父进程无法准确查询子进程),对子进程返回0,如果失败就返回-1,所以我们可以使用if else来对代码进行分流

2.如何做到一个函数返回两次? 

我们可以猜测到fork函数内的大概功能

pid_t fork(void)

{

1.创建子进程pcb

2.填充子进程pcb相应内容

3.让父子进程共享同样的代码

......

此时由于父子进程都有独立的pcb了,他们都可以被cpu调度运行了

return ;

}

我们可以看到,return位于函数的最后,而此时函数的功能已经被实现,因此return的代码也被共享了

3.一个变量如何储存两个值?

首先我们需要知道进程之间是相互独立的

因为数据可能被修改所以我们不能让父子进程共享同一份数据(可以共享代码是因为运行时代码已经不会被修改了,不会影响进程间的独立性)

所以为了解决这个问题,字进程会进行写时拷贝,当子进程修改父进程的数据时,会重新开辟一块空间,因此当return 返回的值被写入父进程的数据时候,父进程的数据直接被修改,而系统会给子进程开辟一块空间用于储存另一个返回值

4.父子进程谁先运行呢?

由调度器决定,是不确定的

调度器会相对公平地调度

 bash

bash,即命令行解释器,它通过fork创建子进程,执行相应的指令,而它本身继续接收我们的指令,这就是fork创建的父子进程的实际应用

7.进程状态 

操作系统学科上的进程状态一般分为,运行,阻塞,挂起 

1.运行

操作系统会将已经准备好运行的进程放入运行队列中,操作系统根据顺序去调度这些进程,处于这个状态的进程就为运行状态:R 

一个进程并不是放上去一直到执行结束的,每一个进程都有一个叫时间片的概念,在一个时间段内所有的进程代码都会被执行,即并发执行

大量地把进程放到cpu上和拿下来的动作被称为进程切换

2.阻塞

操作系统会像管理进程一样管理硬件,每一个硬件属性的结构体中会存在一个等待队列, 如果进程想要读取某个硬件的数据,那么它就会被排在该硬件的等待队列中,这时候进程就处于阻塞状态,一直等待到硬件已经准备好了,然后这个进程就会被排到运行队列中

等待特定设备的进程,我们称该进程处于阻塞态

3.挂起

当操作系统内部的内存不够了,那么在阻塞状态的进程,它的代码和数据就会被换出到磁盘中(swap盘),等到程序准备好运行了,它的代码就又会被换入到内存中,代码和数据被换出状态下的程序就处于挂起状态

8.linux下状态 

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 */
};

 R运行状态(running):

并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。


S睡眠状态(sleeping):

意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
(interruptible sleep))

由于cpu的速度很快,所以一个进程有很大一部分时间是在等待i/o设备就绪,我们查询的时候基本上是处于S状态,但是当我们执行一个空语句,即例如

while(1)

此时程序不进行输入输出,它就一直处于运行状态了

所以S状态可以对应阻塞状态

知识补充 

如果程序处于  例如S+ R+的状态那么说明该程序在前台运行,这时候我们就不能继续在命令行解释器输入指令了,我们可以

./test.exe  &

这样程序就会转为到后台运行,状态后的加号也会消失,这种进程只能使用kill来杀死

D磁盘休眠状态(Disk sleep)

有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。

在等待期间这个进程不能被杀死,对应的也是阻塞状态

T停止状态(stopped):

可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可

以通过发送 SIGCONT 信号让进程继续运行。

kill -19 暂停进程,暂停之后进程状态就会变成T

kill -18继续进程  进程继续后会转为后台运行

T状态有自己的应用场景,可能是要等待资源也可能是单纯被其它进程控制了

例如我们使用gdb调试进程时,运行某个程序并且在某个位置打上断点,进程在该代码处停止的状态就处于t状态,(t和T区别不是很大,暂时可以理解为同一种状态)

X死亡状态(dead): 

这个状态只是一个返回状态,你不会在任务列表里看到这个状态

Z僵死状态(Zombies)

是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)
没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态 

如果处于Z状态的程序没有被读取,僵尸进程会一直占用内存,但不会继续运行,会造成内存泄露

孤儿进程

当一个子进程的父进程被终止(在代码中使用exit(0)),但是子进程继续运行,那么子进程的父进程会被更改为1进程,即操作系统 ,因为如果子进程不被托管,在子进程运行结束后就不会被回收了

(如果将父进程直接ctrl+c终止,那么在父进程被回收的一瞬间,子进程在将父进程更改为操作系统后,子进程也会被回收)

9.进程内的访问

某个进程,它的pcb信息可以储存多种用于访问不同数据结构的指针,也就是说一个进程可以被存放在链表中也可以同时被存放在多叉树等等中。

下面我们简单讲一下进程如何相互访问,下是三点前提信息

1.这是一个双链表的结构体,我们现在有一个struct node* 类型的start变量,储存了第一个进程的一个地址

2.这个进程指向的是link

3.link节点内是指向 前后进程的指针

首先我们假设地址0是一个task_struct*类型的一个指针

(task_struct*)0

我们让它指向它的结构体内的link

(task_struct*)0->link

我们再将其取地址,得到的就是link与结构体的地址(即结构体最开始的那个变量的地址)之间的地址差,因为结构体地址为0。

&(task_struct*)0->link

因为我们现在已经有了一个结构体的link的地址,因此我们只需要将它的地址减去上面所算出的地址差,就找到了结构体的地址。(要使用int强转,转换成一个单纯数值的运算,而不是地址间的运算)

(int)start-(int)&(task_struct*)0->link

我们就得到了结构体的地址的数值,将其强转成task_struct*类型,即可访问该进程的其它内容了

(task_struct*)(int)start-(int)&(task_struct*)0->link

10.进程优先级

1.优先级是什么?

优先级是决定某个进程被访问顺序的一项属性。是一个[60,99]之间的一个数值

2.为什么要有优先级

因为资源是有限的,进程是多个的,因此进程之间具有竞争性,操作系统必须保证大家良性竞争,确认优先级

如果进程长时间得不到cpu资源,该代码得不到推进,进程就会面临饥饿问题。

3.linux中的进程优先级

我们使用ps-l指令可以看到

其中

PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值

PRI即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小,进程的优先级别越高
NI就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值,进程nice值会影响到进
程的优先级变化

程序一般的PRI值为80
当我们对程序进行修正,加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
nice其取值范围是-20至19,一共40个级别

我们每次修改进程的优先级,PRI(old)都为80,当nice的值太大或者太小时,nice值会被限制在-20和19。

4.用top命令更改已存在进程的nice:

top
进入top后按“r”–>输入进程PID–>输入nice值 

(只有root才能更改优先级)

nice和renice也可以更改

5.优先级的工作原理

 

在运行队列中会存在两个指针数组,他们指向对应的对应的结构体,其中下标[100,139]对应[60.99] 的PRI的值,根据PRI值,我们将相应的task_struct链接上去,相同PRI值遵循先来后到的原则,类似于哈希表的处理办法

运行队列中有两个相同的数组,这里为了区分我们将其命名为,waiting和running,即当前run和当前wait,当running中的进程依次开始运行时,新进入运行队列的进程会被插入到waiting中,当running中的进程都运行完毕,他们两个的职能就互换,这样能够避免先进入但是PRI较大的进程一直处在队列的末尾得不到运行。

为了实现上述功能,两个数组的指针会不断被run和wait交换,这两个二级指针用于寻找当前run和当前wait的数组

如何判断当前运行的那个队列是否为空 

我们可以创建数组

char bits[5],里面有40个比特位,对应四十级优先级,每个比特位上的1与0表示该优先级所对应的进程队列里面是否有进程,当bits为0时,说明当前run的数组里面指向的进程都已经被运行完毕。

标签:状态,优先级,操作系统,认识,初步,进程,运行,struct
From: https://blog.csdn.net/myhhhhhhhh/article/details/140182383

相关文章

  • 认识程序
    【教程】在macOS上用VSCode写C++代码3认识程序文件系统最基本的两种文件:文本文件:存字符ASCII码程序/二进制可执行文件程序/二进制可执行文件程序有输入,有输出,就算程序从计算机底层的角度来说,程序就是一堆机器码(0、1),就是二进制可执行文件,计算机底层硬件(处理器)可以读......
  • Windows如何查看端口是否占用,并结束端口进程
    需求与问题:前后端配置了跨域操作,但是仍然报错,可以考虑端口被两个程序占用,找不到正确端口或者后端接口书写是否规范,特别是利用PythonFlask书写时要保证缩进是否正确!Windows操作系统中,查看端口是否占用并结束占用端口的程序是一个常见的操作,特别是在进行网络配置或软件安装时。......
  • Linux开发:进程间通过Unix Domain Socket传递数据
    进程间传递数据的方式有很多种,Linux还提供一种特殊的Socket用于在多进程间传递数据,就是UnixDomainSocket(UDS)。虽然通过普通的Socket也能做到在多进程间传递数据,不过这样需要通过协议栈层的打包与拆包,未免有些浪费效率,通过UDS,数据仅仅通过一个特殊的sock文件就可以进行传递。......
  • 进程间通信方式-共享内存
    目录1.特点2.使用步骤3.函数接口3.1创建key值3.2创建或打开共享内存3.3映射共享内存3.4取消映射3.5删除共享内存4.命令5.基本操作1.特点(1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。(2)为了在多个进程间交换信息,内核......
  • 第1章 认识 Vite
    明白了,这里是第1章内容的详细展开版本:第1章认识Vite1.什么是ViteVite是一个由尤雨溪(Vue.js的创始人)开发的前端构建工具,旨在提供极快的开发体验。Vite的名字来源于法语,意为“快速”,这正是它的核心理念。1传统构建工具的挑战传统的构建工具(如Webpack、Parcel......
  • 【C++】认识使用string类
    【C++】STL中的string类C语言中的字符串标准库中的string类string类成员变量string类的常用接口说明成员函数string(constructor构造函数)~string(destructor析构函数)默认赋值运算符重载函数遍历string下标+[]迭代器范围for反向迭代器capacitysizelengthmax_sizeresi......
  • 进程、程序、应用程序之间的关系
    文章目录进程和程序进程和应用程序总结参考资料进程和程序程序:程序是存放在硬盘中的可执行文件,主要包括代码指令和数据。程序本身是一个静态的文件,只有在被操作系统加载到内存中并执行时才会变成进程。进程:进程是程序在操作系统中的执行实例。一个进程是一个程序......
  • 线程和进程
    1.什么是线程?什么是进程?正在运行的程序称之为进程。进程它是系统分配资源的基本单位。线程,又称轻量级进程(LightWeightProcess)。线程是进程中的一条执行路径,也是CPU的基本调度单位。若一个程序可同一时间执行多个线程,就是支持多线程的.一个进程由一个或多个线程组成,彼此......
  • GPT-4o不仅能写代码,还能自查Bug,程序员替代进程再进一步!
    目录1 CriticGPT01综合性(Comprehensiveness):02幻觉问题(Hallucinatesaproblem):2其他CriticGPT案例随着人工智能(AI)技术不断进步,AI在编程领域的应用取得了显著的成果。通过使用自然语言处理(NLP)和机器学习(ML)技术,AI可以自动生成代码、检测错误并优化性能。一个例......
  • python多线程与多进程开发实践及填坑记(1)
    1.需求分析1.1.概述基于Flask、Pika、Multiprocessing、Thread搭建一个架构,完成多线程、多进程工作。具体需求如下:并行计算任务:使用multiprocessing模块实现并行计算任务,提高计算效率、计算能力。消息侦听任务:使用threading模块完成RabbitMQ消息队列的侦听任务,将接收到......