Linux基于centOS7【内存与OS的随谈】,进程初学【PCB】【fork】【进程排队】

冯诺依曼体系结构——存储器

存储器主要指的是内存,它有个特点就是掉电易失

磁盘等其它输入和输出设备

为什么要在计算机体系结构中要存在内存

我们知道,CPU的处理速度很快很快,但输入设备,以及输出设备,是相对很慢的两个结构,CPU处理完某件事之后,等待着输入设备再次将程序送进来进行计算,但是输入设备又不能很快的送达,因此,在等待的这段时间内,CPU是处于一个闲置的的状态,完全浪费了CPU高速处理事件的能力。这就需要一个物件积存一定量的任务,等到满足某个条件的时候,再将它送给CPU

OS——管理者

为什么要有操作系统:

操作系统是在计算机出来之后慢慢才有的,没有操作系统,就要自己控制硬件,人工管理硬件,效率低,它是管理者

就好比,你在渲染视频,你需要左手一边渲染,右手人工转动风扇,防止机体温度过高,导致线路烧坏

也好比,你在打王者荣耀,你需要一只手一直操控界面,另一只手要扯弄着网卡

有了OS的管理,我们才会方便很多

所以从为什么需要OS可以转换为为什么要有OS的管理

为什么要OS的管理

OS通过对下管理好软硬件资源,从而对上提供一个良好的运行环境(稳定,高效,安全)

前者是手段,后者是目的,所以我们才需要操作系统,因为人工管实在是太慢了

操作系统管理软硬件方式

先描述,在组织

即先描述好软硬件的基本属性,自身状态等内容,将这些属性抽象成结构体或者类类型,从而得以组织起来

再用适合的数据结构,如:链表,队列等,将其串联起来

这样只要OS需要某个硬件干什么,就先告诉驱动去办,搞完后再回头通过修改存储有各种硬件的数据结构里的数值,从而达到更新效果

如果保证OS的安全 

系统调用接口

为了保证操作系统的安全,即防止某些用户会对操作系统里的数据进行非法行为,操作系统就在用户和操作系统之间再设计一层系统调用接口,这里的系统调用接口,就是操作系统向上提供的调用接口,这些接口的存在不允许用户直接访问操作系统,只能通过它们才能访问。

这些接口也相当于用C语言设计的函数,由操作系统提供

就好比银行自动柜台,它不会直接将钱币暴露给你,而是通过柜台,一步步验证你的身份信息,密码等相关内容,将你所需要的一定数目的金额发放出来。所有的柜台都是银行提供的,所有的动作银行都能监控,这些柜台就相当于系统调用接口

经过OS的函数,因为OS不信任任何东西,这个函数的底层一定会封装系统调用,比如printf(), 只要是会影响到底层硬件的函数,一定会包含系统调用接口

系统调用接口的实例

库函数由用户层提供,并不一定所有的库函数都会调用系统调用,即用户可以直接跨过用户操作接口使用系统调用接口。

而用户操作接口就相当于系统调用接口的封装,以便用户可以直接调用等等,然后很多接口又封装成lib,比如printf 和 scanf 被封装在C++/C标准库里

真正实践了OS通过对下管理好软硬件资源,从而对上提供一个良好的服务(稳定,高效,安全)

越级访问

只要库函数调用了系统调用,它两就是上下层关系,库函数在上,系统调用在下

所有用户都不能直接越过OS就访问软硬件,驱动程序,即不能够越级访问,如果越级访问了,那么我还要OS的管理干嘛呢,如果用户直接越过OS就访问软硬件,那么用户会不会对我的软硬件干坏事呢?所以不允许用户直接越级访问

进程

我们编译后的二进制文件是放在外设磁盘中,要运行的时候加载到内存里,然后被CPU运行计算

我们可以启动多个程序,意味着将多个exe加载到内存

OS如何管理加载到内存的多个程序 ?

PCB(task_struct)

先描述,在组织

加载到内存的exe,OS刚开始的时候并不认识

OS为了更好的管理每一个加载进内存的exe, OS必须为每一个进程创建一个描述该进程的结构体变量或者对象 ,然后在将属性记录下来,这些结构体变量或者对象就被称为PCB

struct PCB{//状态//标识符//优先级//内存指针字段// ...//几乎所有的属性字段//struct PCB* next;
}

PCB:进程控制块

所以,某个程序运行时需要的字节大小,其实在内存时都会给这个程序多开,多出来的一般就会存放这个进程的PCB

进程的管理跟变成二进制的代码没有任何关系,跟与之形成的PCB有关系,对进程的管理转换为对PCB的管理

为什么程序加载到内存之后,变成进程之后,我们要给每一个进程形成一个PCB的对象呢?

因为OS要进行管理

所以进程 = 内核PCB对象 + 可执行程序 

内核数据结构 + 可执行程序 也可称为进程

进程排队

我们一般所说的让进程排队之列的,本质上是指PCB在排队,并不是可执行程序在排队

一般用队列实现,所以进程能被动态的调度,本质上就是把进程PCB放入到运行队列里,让CPU调度

进程排队的管理

所有对进程的控制和操作都只和进程的PCB有关,跟进程的可执行程序没有关系!!!

PCB不仅可以放到链接里,还可以放到其它容器里

PCB在Linux叫做 task_struct,属于OS的数据

task_struct内容分类

  • 标识符:标示符: 描述本进程的唯一标示符,用来区别其他进程,PID.
  • 状态: 任务状态(是运行的,还是阻塞的等等),退出代码,退出信号等。
  • 优先级:相对于其他进程的优先级,以此来确认谁先得到CPU运算。
  • 程序计数器: 程序中即将被执行的下一条指令的地址,程序寄存器。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  • 上下文数据: 进程执行时处理器的寄存器中的数据 。
  • I/O状态信息: 包括显示的I/O请求,分配给进程的 I/O 设备和被进程使用的文件列表
  • 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 其他信息

程序计数器:

CPU干的活就是取指令->分析指令->执行指令

CPU内部存在一种寄存器,里面存着eip/PC指针

PC指针指向的是当前正在执行指令的下一条指令的地址

判断,循环,跳转,本质就是修改PC指针

同时pc指向哪一个进程的代码,就表示哪一个进程被调度执行,pc指针就能成为程序计数器

进程实例

ps axj 查看进程指令

命令本身指令,几乎所有的独立运行的指令,都是程序,运行起来也要变成进程

PCB是在OS内进行维护的,它属于内核数据结构,所以要获取PCB的数据,就必须经过OS的调用

PID就是PCB(task_struct) 的标识符,getpid(),用于获取子进程编号

一般在Linux中,每个子进程都有父进程,父进程就是PPID,getppid(),获得的就是父进程id

每一次启动进程的pid几乎都会变化,因为已经变成新进程了

但是父进程却一直都没变,所有的进程都是bash的子进程

进程信息也可以通过文件目录查询到

ll /proc

这些蓝色字体就是进程的PID,LINUX会将进程相关的信息,以PID为命名的目录形式,把进程的属性放到该目录下

磁盘文件与内存文件区别

一个进程启动了,将它的可执行文件删掉之后,我们能发现这个进程还在跑 

因为在运行一个程序时,本质是将磁盘的内容拷贝到内存,删除的是磁盘里的内容,但我内存里拷贝的那份还在跑,当我退出的时候,这个进程才彻底消失并且再也找不到了

cwd:当前工作目录

在我们创建在编译器用fopen等函数,创建文件的时候,编译器就会根据cwd为其保证是在当前目录底下的

那么如何改变当前目录呢?

更改当前目录

用change dir

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main(){printf("这个进程的pid:%d\n",getpid());printf("现在更改它的当前工作目录\n");sleep(10);chdir("我想要的目录");ptintf("更改当前工作目录之后!\n");FILE* fp = fopen("test.txt","w");if(fp == NULL)return 1;fclose(fp);
}

所以当前目录是什么咋咋咋之类的,一定是因为当前程序运行起来变成进城之后,PCB里有cwd,从而能够认识到当前目录是什么

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

返回值pid_t ,它是一个整数,fork函数就是创建子进程的

  1 #include<stdio.h>2 #include<unistd.h>3 #include<sys/types.h>4 5 int main(){6   printf("在使用fork之前,我是一个进程了,我现在的pid是:%d,ppid是:%d\n",getpid(),getppid());7   fork();8   printf("在使用fork之后,我是一个进程了,我现在的pid是:%d,ppid是:%d\n",getpid(),getppid());9   sleep(3); //防止乱序,让它休眠3秒                                                                                                                                                                          10   return 0;                                                                                                                                                                        11 }    

我们发现在使用fork之前这条语句,每次运行只打印一遍,而在使用fork之后这条语句打印了两遍

也就是在fork之后,就分成了两个分支,两个分支都会走  

printf("在使用fork之后,我是一个进程了,我现在的pid是:%d,ppid是:%d\n",getpid(),getppid());

但是经历了fork之后,第一个进程跟原本的进程是一致的,第二个进程是第一个进程的子进程

fork之后,父和子进程都会进行

那我原本的进程1546是谁,我们应该如何查看

使用命令:

ps ajx | head -1 && ps ajx | grep 1546

很明显,它是我们的bash进程

fork返回值

pid_t id=fork();
printf("在使用fork之后,我是一个进程了,我现在的pid是:%d,ppid是:%d,retuen id是:%d\n",getpid(),getppid(),id);  

返回值说明

在成功的时候,fork之后的,对父进程返回PID,对子进程返回0

如果失败了,就返回-1给父进程,不创建子进程 

不同进程可以同时跑动

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main() {printf("在使用fork之前,我是一个进程了,
我现在的pid是: %d,ppid是: %d\n", getpid(), getppid());sleep(3);pid_t id = fork();if (id == -1)  return 1;else if (id == 0) {while (1) {printf("在使用fork之后,我是一个进程了,
我现在的pid是: %d,ppid是: %d, retuen id是: %d\n", getpid(), getppid(), id);sleep(1);}}else {while (1) {printf("在使用fork之后,我是一个进程了,
我现在的pid是: % d,ppid是: % d, retuen id是: % d\n", getpid(), getppid(), id);sleep(1);}}sleep(3); //防止乱序,让它休眠3秒return 0;
}

可以看到系统在不断的编译着两个死循环,这两进程又是父子进程

即父进程再跑,子进程也在跑

为什么fork的时候,两类代码都能跑

我们的每一类进程 = 内核数据结构 + 可执行程序的代码和数据

fork函数是父进程自己执行的,创建一个进程的时候,系统就会多一个进程

创建出了子进程,意味着创建出了子进程的task_struct,但是这个时候,我们又没有相应的可执行程序的代码和数据给子进程,所以它会指向父进程的代码和数据指向 ,但是父进程可以进行代码分流

父进程会将自己PCB里面的很多属性拷贝给子进程,从而才能使子进程跟父进程看到同样的代码,父进程并不是100%给子进程

用相同的代码,执行出不同的结果的原因是,父进程的代码进行了分流

Q:给父进程返回PID,给子进程返回0,为什么会这样

A:因为一个子进程只有一个父进程,子进程可以很容易的找到父进程,但是一个父进程可以有多个子进程,为了方便找到那个子进程,就需要返回那个子进程的编号,即PID

Q :fork为什么会返回两次

A :在fork函数里面,当已经运行到了最后开始执行return的时候,这个函数核心逻辑就已经做完了,也就是说子进程的创建,子进程PCB的拷贝复制,已经指向同一块代码的时候就已经完成了,此刻的他们已经运行他们各自的return了,也就是说代码在返回之前就裂开了

Q :id怎么可能同一个变量,既等于0,又大于0

A :进程是具有独立性的,互相不能互相影响,父子进程也是如此

OS能够保证进程之间的独立性,当某方想要修改其中代码的属性的时候,比如子进程想将代码里的a变量改为自己想要的值,OS为了父进程不受影响,OS会拷贝一份数据,交给子进程,让子进程拿着这份数据去玩,不要打扰父进程,同样,反过来也是如此,这种就叫做写时拷贝

写时拷贝带来的结果便是,子进程和父进程会使用两个不同的空间

返回的本质,就是写入

在Linux中,相同变量名可以表示不同的内存

一次创建多个进程

#include<stdio.h>    
#include<unistd.h>    
#include<sys/types.h>    
#include<stdlib.h>    const int num = 10;    void Worker(){    int cnt = 12;    while(cnt)    {    printf("child %d is running ,cnt: %d\n",getgid(),cnt);    cnt --;    sleep(1);    }    
}    int main(){    for(int i =0;i<num;i++){    pid_t id = fork();    if(id<0) break;    if(id == 0)    {    Worker();    exit(0);    //结束一个进程,这里就是结束子进程,从而不妨碍下次的子进程}    printf("father create child ,child's PID is %d\n",id);    sleep(1);    }    //只有父进程才能走到这里sleep(10);                                                                                        return 0;    
}    

 进程的状态

1,进程排队

进程不是一直运行的

它可能在等待某些资源,比如scanf( ) ,在输入数值之前,系统就相当于在等待数值资源,进程因此也没在运行 

进程放在了CPU上,也不是一直会运行的, 

写个死循环加载到CPU上,CPU不会放任这个死循环就一直让它搞事情,CPU有个叫时间片的东西,只要你的进程超过一定的阈值,它就会将这个进程拿下来

进程排队,一定是等待某种资源,这里的排队是指 task_struct(PCB) 在排队

一个task_struct 可以被链入多种数据结构中

这里那链表为例子

struct listnode
{struct listnode* next;struct listnode* prev;
}

每个PCB之间排队的时候又不是直接头对尾这样排,而是中间嵌入一个结构体,存放前驱和后驱指针,用它来进行排队,这样排队的占比大小又会减少

通过链表链接,我们是能够知道listnode的地址值的,但是我们却不能直接的知道我们每个PCB的开头值,那么我们可以

令 &n 为中间结构体的地址

  1. &n:表示获取变量n的内存地址。

  2. ((task_struct*)0):这是一个类型转换操作。0被强制转换为task_struct类型的指针,变成0x0000 0000 0000 0000。

  3. ->:这是C语言中指针的成员访问操作符。它允许你通过指针访问结构体的成员。

  4. n:这是结构体task_struct中的一个成员变量。

整个表达式&n - &((task_struct*)0->n)的意思是,它计算了两个地址之间的差值。第一个地址是变量n的地址,第二个地址是通过将0转换为task_struct类型的指针并访问它的成员n得到的地址。这个差值用来表示成员n在结构体task_struct中的偏移量。

也就是说把0号地址当成了开始处,到对象0的n处的话,就是偏移

哪个进程需要进行的话,就将它到相应的队列里,然后更改对应的链表指针指向即可

以上便是本次博文的学习内容,如有错误,还望大佬指定,谢谢阅读


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/394029.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sql注入靶场搭建

1.安装小皮面板&#xff08;PhpStudy&#xff09; 1.从官网下载&#xff1a;http://www.xp.cn 2、Sqli-labs环境安装 准备好sqli-labs-php7-master文件 3.安装之前确保本地没有下载mysql服务器 如果电脑下载了MySQL可以把MySQL的服务停掉 此电脑>右键>管理>服务…

QModbus例程分析

由于有一个Modebus上位机的需要&#xff0c;分析一下QModbus Slave的源代码&#xff0c;方便后面的开发。 什么是Modbus Modbus是一种常用的串行通信协议&#xff0c;被广泛应用于工业自动化领域。它最初由Modicon&#xff08;目前属于施耐德电气公司&#xff09;于1979年开发…

C++:vector容器

概览 std::vector是C标准模板库(STL)中的一种动态数组容器。它提供了一种类似于数组的数据结构&#xff0c;但是具有动态大小和更安全的内存管理。 定义和基本特性 std::vector是C标准库中的一 个序列容器&#xff0c;它代表了能够动态改变大小的数组。与普通数组一样&#x…

模拟面试题1

目录 一、JVM的内存结构&#xff1f; 二、类加载器分为哪几类&#xff1f; 三、讲一下双亲委派机制 为什么要有双亲委派机制&#xff1f; 那你知道有违反双亲委派的例子吗&#xff1f; 四、IO 有哪些类型&#xff1f; 五、Spring Boot启动机制 六、Spring Boot的可执行…

关于儿童编程语言

青少年通常会通过Scratch或Python开始学习编程。在这两种语言中&#xff0c;代码的编写&#xff08;或者在Scratch中是构建&#xff09;方式类似于英语&#xff0c;这使得初学者更容易学习。Scratch的一个重要卖点是对视觉和运动感知学习者非常友好。这些代码块按颜色编码&…

最短路问题中的bellman-ford算法

最短路问题中的bellman-ford算法 题目 如果要处理单源最短路问题当中存在负权边的&#xff0c;那么就需要用到 bellman-ford算法和SPFA算法&#xff0c;一般情况下都是用 SPFA算法&#xff0c;除了有边数限制的情况只能用bellman-ford算法&#xff0c;比如下面这种 题目 给定…

混合密度网络Mixture Density Networks(MDN)

目录 简介1 介绍2 实现3 几个MDN的应用&#xff1a;参考 简介 平方和或交叉熵误差函数的最小化导致网络输出近似目标数据的条件平均值&#xff0c;以输入向量为条件。对于分类问题&#xff0c;只要选择合适的目标编码方案&#xff0c;这些平均值表示类隶属度的后验概率&#x…

《程序猿入职必会(10) · SpringBoot3 整合 MyBatis-Plus》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

Linux中DHCP服务器配置和管理

文章目录 一、DHCP服务1.1、DHCP的工作流程1.2、DHCP的工作模式1.3、dhcp的主要配置文件 二、安装DHCP服务2.1、更新yum源2.2、安装DHCP服务软件包2.3、配置DHCP服务2.4、启用DHCP服务&#xff08;解决报错&#xff09;2.4.1、查看dhcpd服务的状态和最近的日志条目2.4.2、查看与…

【网络安全】探索AI 聊天机器人工作流程实现RCE

未经许可,不得转载。 文章目录 前言正文前言 我发现了一个广泛使用的AI聊天机器人平台中的远程代码执行漏洞。该漏洞存在于聊天机器人的自定义工作流响应代码中,这些工作流允许开发人员通过创建定制的流程来扩展机器人的功能。 正文 在浏览自动化聊天机器人的多个特定功能…

AI界的“小钢炮“:MiniCPM-V 2.6 版本震撼发布!

MiniCPM-V 2.6 面壁智能推出了一款颠覆性的端侧AI多模态模型——MiniCPM-V 2.6。这个被亲切地称为"小钢炮"的模型&#xff0c;以其惊人的性能和极致的效率&#xff0c;向业界巨头发起了挑战。 MiniCPM-V 2.6 MiniCPM-V 2.6 是 MiniCPM-V 系列中最新、性能最佳的模型。…

算法板子:匈牙利算法——二分图的最大匹配

目录 1. 基础概念 &#xff08;1&#xff09;二分图的概念 &#xff08;2&#xff09; 匈牙利算法的作用 2. 代码 1. 基础概念 &#xff08;1&#xff09;二分图的概念 顶点集 V 分为两个集合&#xff0c;且图中每条边依附的两个顶点都分属于这两个子集&#xff0c;也就是第…

《UniverSeg: Universal Medical Image Segmentation》ICCV2023

摘要 这篇论文提出了一种名为 UniverSeg 的方法&#xff0c;它能够解决未见过的医学图像分割任务&#xff0c;而无需额外的训练。现有的深度学习模型通常无法泛化到新的解剖结构、图像模式或标签上。UniverSeg 利用一种新的 CrossBlock 机制&#xff0c;通过查询图像和定义新分…

倍福PLC数据 转 CCLink IE Field Basic项目案例

目录 1 案例说明 1 2 VFBOX网关工作原理 1 3 准备工作 2 4 设置倍福PLC 2 5 配置网关参数采集倍福PLC数据 4 6 使用CCLINK协议转发数据 7 7 三菱PLC连接网关的CCLINK的设置 8 8 案例总结 12 1 案例说明 设置倍福PLC&#xff0c;开通ADS通信设置网关采集倍福PLC数据把采集的数…

小巧免费的笔记本电池检测工具

BatteryInfoView是一款免费的笔记本电池检测软件&#xff0c;适用于笔记本电脑和上网本。该软件能够提供电池的详细信息&#xff0c;包括电池名称、制造商名称、序列号、制造日期、电源状态&#xff08;充电/放电&#xff09;、当前电池容量、完全充电容量、设计容量、充电放电…

RAG私域问答场景超级详细方案(第一期方案)[1]:工业级别构建私域问答(知识处理、知识召回排序、搜索问答模块)

RAG私域问答场景整体夏详细方案(第一期方案):工业级别构建私域问答(知识处理、知识召回排序、搜索问答模块) 大模型性能的跳阶式增长给文本摘要、信息检索、信息抽取、语义问答等自然语言处理任务带来了卓越的性能提升。同时,LangChain 作为一种基于 LLM 的框架,能够快速…

基础岛 - 8G显存验证书生·浦语大模型的Demo

因为以前用过LMDeploy&#xff0c;所以本章的内容相对熟悉。 另外&#xff0c;因为教程写的很详细保姆级&#xff0c;所以大多数情况直接复制执行命令即可。开发机的创建略过。 总体验证结论&#xff1a; LMDeploy的模型加载有点慢&#xff0c;但推理速度快&#xff0c;符合预…

第三方软件检测机构服务类型

在信息技术飞速发展的今天&#xff0c;软件产品的质量已成为企业竞争力的重要组成部分。卓码软件测评这家第三方软件检测机构致力于提供一流的软件测试服务&#xff0c;帮助企业确保其软件产品的可靠性和安全性。 一、项目验收测试&#xff1a;确保交付质量   项目验收测试是…

【Nuxt】配置

runtimeConfig 运行时配置 // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({compatibilityDate: 2024-04-03,devtools: {enabled: true},runtimeConfig: {appKey: process.env.APP_KEY,public: {baseUrl: process.env.BASE_URL,}} …

suse SLE 12 SP3 安装 GeForce GT 730 显卡驱动

文章目录 [toc]获取驱动安装显卡驱动验证显卡驱动 获取驱动 浏览器打开 NVIDIA Official Drivers &#xff0c;找到自己对应的驱动&#xff0c;然后查询和下载 安装显卡驱动 下载完成后&#xff0c;有一个 .run 文件&#xff0c;可以直接 bash 执行&#xff0c; bash NVIDIA-Li…