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

news2024/11/16 17:50:24

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

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

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

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

我们知道,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.coloradmin.cn/o/1975200.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#对Sqllite操作

前言 数据库的操作也是程序设计中的家常便饭了&#xff0c;关系型数据库中Sqlite3是非常轻量级别的&#xff0c;所以这个数据在小型应用程序的设计中占用很高的比重。当然我这里描述的是1.0版本&#xff0c;也是最原始的方案&#xff0c;大型应用开发中一般选择EF进行桥接&…

信息学奥赛初赛天天练-54-CSP-J2019阅读程序3-二叉树、满二叉树、单侧二叉树、二分查找、递归、等差数列求和

PDF文档公众号回复关键字:20240803 2019 CSP-J 阅读程序3 1阅读程序(程序输入不超过数组或字符串定义的范围&#xff1b;判断题正确填 √&#xff0c;错误填 。除特殊说明外&#xff0c;判断题 1.5 分&#xff0c;选择题 3 分&#xff0c;共计 40 分) 01 #include <iostre…

idea项目创建提交到gitee gitee创建仓库 gitee删除仓库(全网最新最详细)

一、gitee创建仓库 1.如下图 2.创建好后如下图 3.打开idea创建好项目 3.1点击终端 3.2 从gitee页面复制命令进行运行 具体步骤如下图&#xff1a; 在步骤5时可能会提醒你远程仓库没有main分支&#xff0c;这个时候需要执行下图中的命令4创建一个远程main分支 结果运行如下图…

GATK ReferenceDataSource接口介绍

在 GATK(Genome Analysis Toolkit)库中,ReferenceDataSource 接口是一个重要的接口,用于表示与参考基因组相关的数据源。它提供了一种标准化的方式来访问和操作参考基因组的不同来源的数据。ReferenceMemorySource 类和ReferenceFileSource 类是ReferenceDataSource接口的实…

给本地设备搭建一个云端语音助手

概述 本语音助手实现了从关键词唤醒 (KWS) 到语音识别 (ASR) 再到自然语言理解 (NLU) 的完整流程。该系统可以通过监听用户的音频输入,检测指定的关键词,并将用户的语音转换为文本,最后与预设的命令进行匹配,执行相应的操作(具体实现请参考main.py),为你的设备配置远程…

ASPCMS

1.后台修改配置文件拿Shell 步骤一&#xff1a;访问以下地址为ASPCMS...并登陆到后台&#xff08;这里注意在搭建站点的时候注意权限问题&#xff09; #网站后台 http://192.168.4.139/admin_aspcms/login.asp //全功能版本 #账户密码 username:admin password:123456 步骤二…

API网关理解

项目背景介绍&#xff1a; 首先介绍一下项目背景&#xff0c;这个项目是API开发平台&#xff0c;需要完成的接口的功能是&#xff1a;统计谁调用了这个接口&#xff0c;并且将这个接口的调用次数1&#xff0c;剩余次数-1。 首先看到这个需求第一反应&#xff1a; 得先建个表…

第三期书生大模型实战营之浦语提示词工程实践

一. 基础任务 背景问题&#xff1a;近期相关研究发现&#xff0c;LLM在对比浮点数字时表现不佳&#xff0c;经验证&#xff0c;internlm2-chat-1.8b (internlm2-chat-7b)也存在这一问题&#xff0c;例如认为13.8<13.11。 任务要求&#xff1a;利用LangGPT优化提示词&#…

林轩田机器学习基石——笔记1.2 Learn to Answer Yes/No(如何进行学习)

When can Mechine learn&#xff1f; 2.Learn to Answer Yes/No&#xff08;如何进行学习&#xff09; 2.1perceptron hypothesis set 2.2Perceptron Learning Algorithm 2.3Guarantee of PLA 2.4Non-Separate Data Why can Mechine learn&#xff1f; How can Mechine …

通向 AGI 之路:大型语言模型(LLM)技术精要

ChatGPT出现后惊喜或惊醒了很多人。惊喜是因为没想到大型语言模型&#xff08;LLM,Large Language Model&#xff09;效果能好成这样&#xff1b;惊醒是顿悟到我们对LLM的认知及发展理念&#xff0c;距离世界最先进的想法&#xff0c;差得有点远。我属于既惊喜又惊醒的那一批&a…

Android 12系统源码_Settings(一)认识Preference

前言 想刀一家公司的心事藏不住的&#xff0c;原本只了解一下Android系统应用Settings的配置开关列表中某个开关开启或关闭的时候&#xff0c;系统做了哪些响应操作&#xff0c;结果搞了半天发现完全看不懂。写界面就写界面吧&#xff0c;但是Settings模块完全没有使用Android…

STM32Cubemx在FreeRTOS中使用面向对象的方式使用串口

文章目录 前言一、创建FreeRTOS工程二、创建文件对串口进行封装三、代码编写总结 前言 本篇文章将带大家来学习使用面向对象的方式在FreeRTOS中使用串口&#xff0c;使用面向对象的方法非常适合编写可移植性强的代码&#xff0c;那么这篇文章就带大家来看一下这个代码要怎么写…

Evaluating the Generation Capabilities of Large Chinese Language Models

文章目录 题目摘要相关工作CG-Eval实验 题目 评估大型中文语言模型的生成能力 论文地址&#xff1a;https://arxiv.org/abs/2308.04823 项目地址&#xff1a;http://cgeval.besteasy.com/ 摘要 本文介绍了 CG-Eval&#xff0c;这是有史以来第一个全面的自动化评估框架&#xf…

《Milvus Cloud向量数据库指南》——什么是二进制嵌入?

引言 向量嵌入在现代机器学习和数据科学中已成为不可或缺的工具,它们能够将复杂数据以算法可以理解的数值格式表示。尽管密集嵌入因其能够以最小的信息损失保留语义含义而普遍存在,但随着数据量的增加,它们的计算需求和内存需求也在增加。这种增加促使开发者寻求更高效的数…

Python RPA流程自动化机器人简单案例

RPA&#xff08;Robotic Process Automation&#xff0c;机器人流程自动化&#xff09;是一种通过软件机器人模拟和执行人类用户在计算机上的操作的技术。 下面是pyautogui 键盘操作的常见参数说明&#xff1a; https://blog.csdn.net/wydyzq12/article/details/122008396 以…

TDEngine(taos) 涛思数据库-java写入数据

一、java写入taos简单案例&#xff1a; pom.xml 中加入以下依赖。 <dependency><groupId>com.taosdata.jdbc</groupId><artifactId>taos-jdbcdriver</artifactId><version>3.3.0</version> </dependency> java代码 import…

2024网络安全学习路线,最全保姆级教程,学完直接拿捏!

关键词&#xff1a; 网络安全入门、渗透测试学习、零基础学安全、网络安全学习路线 首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题 前排提示&#xff1a;文末有CSDN独家网络安全资料包&#xff01; 1、打基础时间太长 学基础花费很长时间&#xff0c;光语言都有…

医疗器械网络安全 | 第三方组件安全检测怎么做?

医疗器械软件安全中的第三方组件安全检测是确保医疗器械软件整体安全性的重要环节。以下是如何进行第三方组件安全检测的详细步骤&#xff1a; 一、明确检测目标 首先&#xff0c;需要明确检测的目标和范围&#xff0c;即确定哪些第三方组件需要进行安全检测。这通常包括操作系…

C++初学(10)

10.1、共用体 共用体是一种数据格式&#xff0c;它能够存储不同的数据类型&#xff0c;但只能同时存储其中的一种类型。比如说&#xff1a;结构可以同时存储int、long、和double&#xff0c;而共用体只能存储int、long、或double。共用体的句式与结构相似&#xff0c;但含义不…

计算复杂度论文解读系列(1)《通用顺序搜索问题》---L. A. Levin

《通用顺序搜索问题》 L. A. Levin 摘要 本文研究了几种著名的“顺序搜索类型”问题&#xff0c;并证明这些问题只能在解决任何同类型问题所需的时间内得到解决。 1 简介 在阐明算法的概念之后&#xff0c;证明了许多经典问题的算法不可解性&#xff08;例如&#xff0c;群元…