Linux进程初识

news2024/11/17 7:19:03



本文已收录至《Linux知识与编程》专栏!
作者:ARMCSKGT
演示环境:CentOS 7

在这里插入图片描述


Linux进程初识目录

  • 前言
  • 正文
    • 冯诺依曼体系结构
    • 操作系统简介
      • 概念
      • 操作系统的管理
      • 系统调用
    • 进程初识
      • 进程理解
      • 进程的属性和数据
      • 进程控制块
      • 查询进程信息的相关指令
      • 进程PID
    • 父子进程
      • 父子进程及PPID
      • fork创建子进程
      • fork系统调用原理
  • 最后


前言

进程是计算机中的重要概念,一个程序被操作系统加载进入内存那么这个程序就成为进程,一个程序可以启动多次产生多个进程,操作系统也要管理这些进程,本节将介绍关于进程的一些基本知识!
Windows进程(任务)管理器


正文

冯诺依曼体系结构


我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系,冯诺依曼早在二十世纪四十年代就提出了这种结构。

冯诺依曼体系结构将计算机的组成分为五大部分:

  • 输入设备:键盘,摄像头,麦克风,硬盘,网卡等。
  • 存储器(内存):只读存储器(如BIOS系统),随机读写存储器(我们生活中使用的内存)。
  • 运算器:负责数据的处理计算,是CPU的一部分。
  • 控制器:协调整个电脑有序工作,是CPU的一部分。
  • 输出设备:显示器,扬声器,硬盘,网卡等。
    冯诺依曼体系结构

其中这五大部分可以总结为三个设备

  • 外部设备:输入输出设备
  • 存储器:内存
  • 中央处理器(CPU):运算器+存储器

    很早以前,计算机并没有存储器这个设备,CPU直接跟外设打交道,但由于外设比较慢CPU经常都处于空闲状态,这样使得CPU资源被极大的浪费,为了提高CPU资源的使用效率,冯诺依曼提出在CPU和外设之间增加一个存储器!

    存储器是断电易失设备,也就是说存储器上的数据断电后下次启动就没有了,这里要区别与硬盘。虽然存储器断电易失数据,但是其速度非常快(相对于外设来说);如每次我们启动程序时将程序先预加载到存储器上,CPU在需要某些程序资源时直接去存储器中取即可(避免频繁与外设I/O),这样就不需要与外设打交道,这样就极大的提高了CPU资源的使用效率!

    冯诺依曼体系结构中CPU只需要与存储器打交道,需要从外设读取资源时通过存储器向对应设备发送指令然后将数据加载到内存处理即可,而外设只需要和存储器打交道就行,这样通过添加了一个硬件层提高了计算机整体的运行效率!

    总的来说,冯诺依曼体系结构下,一般情况CPU只与存储器打交道,外设只与存储器打交道,所以程序必须被加载到内存上才能运行是由冯诺依曼体系结构决定的!


操作系统简介


概念

我们普通用户无法直接与计算机中的硬件打交道,也就是说在没有操作系统的情况下,我们几乎是无法使用计算机的,于是计算机大佬们创造了各种各样的操作系统!

目前常见的操作系统:

  • Windows操作系统
  • Linux操作系统
  • Mac操作系统(基于Unix)
  • Android操作系统(基于Linux)
  • …等

任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:

  • 内核(进程管理,内存管理,文件管理,驱动管理)
  • 其他程序(例如函数库,shell程序,应用软件等等)

在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件

OS的作用:

  • 与硬件交互,管理所有的软硬件资源
  • 为用户程序(应用程序)提供一个良好的执行环境


操作系统的管理

对于一个公司来说,可以分为以下层级
公司管理层级
公司中有很多部门,每个部门有很多员工,为了更好的管理所有的部门和员工,这些部门需要编号,每个部门的员工也需要编号,且部门与员工也需要有对应的独立信息,相当于我们的身份证一样!

这种记录信息的方式我们一般称为 描述 某信息的存储方式,在C/C++语言中一般用 结构体 去存储!

最后公司使用一个系统将信息存储起来,发生信息变动时对这个系统的信息进行增删查改即可!

这种存储所有信息的方式我们一般称为 组织 所有信息的存储,在所有语言中一般使用 各种高效的数据结构 进行存储!


通过上述的方式,我们对所有信息的管理就转变为了对数据结构的增删查改!
所有操作系统对信息的管理方式是:先描述,再组织

因此,操作系统管理进程也是使用此手段进行管理!
在Linux系统中,一个进程有自己的ID(一般称为pid),内存空间地址等,这些信息使用结构体进行描述,然后使用链表进行组织!



系统调用

在Linux系统中,如果我们要访问硬件,例如磁盘,是不可能通过软件跨过操作系统直接访问,而是借助操作系统的帮助去访问!

这种借助操作系统接口(函数)去实现某个功能的方法,称为系统调用
而软件调用系统调用的大致逻辑是:软件 → 操作系统 → 硬件驱动 → 硬件

系统层次结构:
系统层次结构
通过上图,我们位于开发操作层级,也是用户层级,来开发各种方便的软件提供给上一层的用户群体

所以操作系统对下通过管理好软硬件资源的手段,来达到对上给用户提供良好(安全,稳定,高效,功能丰富等)的执行环境的目的。

注意操作系统给我们提供非常良好的服务,并不代表操作系统相信我们,反而操作系统不相信任何人,就像我们去银行办业务一样,银行提供各种非常良好的服务,但是不会让你接触到例如金库和用户账户信息这样的核心资源!


系统调用和库函数的概念

  • 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
  • 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。

进程初识


通过上面操作系统的简介,我们可以发现操作系统内核有四个模块:

  • 内存管理
  • 进程管理
  • 文件管理
  • 驱动管理(设备管理)

这里我们对操作系统进程管理进行初步简介!


进程理解

进程 = 内核(关于进程相关的)数据结构 + 当前程序的代码和数据

任何启动并运行程序的行为都是由操作系统帮助我们将程序转化为进程完成特定的任务并将程序加载到内存上通过CPU计算运行得出结果的过程

可执行程序本质是一个普通的二进制文件,也就是说本质程序也是文件那么:文件 = 内容 + 属性。这里属性就是进程在操作系统中描述进程的信息(进程控制块PCB),内容则是程序所需要的代码和资源文件等!

通过上面操作系统的介绍,操作系统管理进程的方式是先描述再组织进行管理。每创建一个进程就会将程序的信息和属性读取到这个结构体,根据进程的属性设置结构体。而Linux操作系统中使用双向循环链表对所有的进程进行管理!



进程的属性和数据

进程的数据就是程序的代码以及需要使用的各种资源文件!对于我们来说,进程的数据大部分都是代码。

我们编写程序例如使用C/C++语言形成 .c/.cpp 文件,经过编译得到一个可执行,通过操作系统运行生成相应的进程属性(进程控制块PCB),并执行二进制指令得到程序的运行结果!
程序
程序运行结果



进程控制块

进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。一般我们将进程控制块称之为PCB(Process Control Block),Linux操作系统下的PCB是:task_struct。

PCB中的属性与程序文件属性几乎无关,PCB中存储的文件属性并不是磁盘存储时文件的属性,而是根据其内容动态创建的,所以文件属性与文件本身属性几乎无关!

task_struct是PCB的一种,其中:

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

task_ struct包含的内容:

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

我们启动一个程序是通过 路径+可执行程序名 启动的,这也是为什么我们在Linux下运行一个程序需要加 ./,因为Linux系统并不知道我们在当前哪一个文件夹,所以我们需要想系统交代清楚!

当我们启动一个程序时,操作系统将可执行程序加载到内存中,创建对应的PCB,然后将其组织起来!



查询进程信息的相关指令

ps指令

ps指令会简略的显示当前系统运行的部分进程信息
ps指令
但这并不是ps的常用方法;ps常用方法是搭配选项axj(或ajx)显示全部进程的详细信息然后通过其他指令筛选!

指令:

ps ajx | head -1 && ps ajx | grep 进程名 | grep -v grep

其中:

  • ps ajx | head -1 是取头标题
  • ps ajx | grep 进程名 | grep -v grep 是从所有进程中筛选出指定进程名的进程信息并去掉grep进程信息
    ps搭配管道和grep

top指令

top指令相当于Linux下的任务管理器,显示进程的详细信息,而且是动态更新的,输入 q 退出,在任务管理器下可以进行各种操作!
top指令


ls /proc 查看进程控制块目录

/proc目录是一个内存级目录,不会存储在磁盘上,在系统启动时生成,Linux中所有的task_struct都会存放在这个目录中(但不只存储task_struct),每一个task_struct以进程PID进行命名!
proc目录



进程PID

进程的PID相当于每个进程的身份证号,是独一无二的!对于后面的一些进程控制需要使用PID控制子进程!

#include <sys/types.h> //系统调用所需头文件
#include <unistd.h>

pid_t getpid (void) //返回当前进程的PID,不需要任何参数
#include <iostream>
using namespace std;
#include <sys/types.h>
#include <unistd.h>

int main()
{
   while(true)
   {
       cout << "这是一个PID为" << getpid() <<"进程..." << endl;
       sleep(1); //睡眠一秒再执行 - 每秒打印一次
   }
   return 0;
}

getpid
注意程序是唯一的,而进程可以有多个,一个程序可以启动多次生成多个进程,每个进程都是独立的,所以PID也不同,即我们每次启动同一个程序时进程的PID是不同的!


父子进程


父子进程及PPID

进程间存在父子关系,一个进程可以创建子进程执行其他任务

在Linux中我们启动的大部分程序都是通过Bash帮我们去执行的,也就是说我们启动的几乎所有程序都是Bash创建的子进程去运行的,这样的设计也是为了防止恶意程序破坏内核的运行!

这里需要介绍一个新系统调用,getppid用于获取当前进程的父进程PID,在查询进程信息中PPID就是父进程的PID。

#include<unistd.h> //系统调用所需头文件
#include<sys/types.h>

pid_t getppid(void);	//用法与getpid保持一致

我们修改上面的代码进行运行

int main()
{
   while(true)
   {
       cout << "这是一个 PID为" << getpid() << " PPID为" << getppid() <<" 的进程..." << endl;
       sleep(1); //睡眠一秒再执行 - 每秒打印一次
   }
   return 0;
}

运行结果



fork创建子进程

fork函数是一个系统调用,用于创建子进程

#include <unistd.h> //系统调用头文件

pid_t fork(void); 

对于fork函数有以下特性:

  • fork函数形象上有两个返回值,对于父进程fork会返回其子进程的PID对于子进程会返回0如果子进程创建失败会向父进程返回 -1
  • fork函数创建子进程可以嵌套,也就是说可以在子进程中继续创建子进程。
  • 父进程可以一次性使用fork创建多个子进程。
int main()
{
   pid_t id = fork();
   if(id == -1) //创建失败 直接退出
   {
       cout << "进程创建失败!" <<endl;
       exit(1);
   }

   if(id == 0) //如果id为0则是子进程 开始执行子进程代码块
   {
   	   cout<<"子进程所接收的id值为:"<<id<<endl;
       while(true)
       {
           cout<<"我是子进程,我的PID:"<<getpid()<<"我的PPID:"<<getppid()<<endl;
           sleep(1);
       }
   }

   //运行到这个地方的必定是父进程
   cout<<"父进程所接收的id值为:"<<id<<endl;
   while(true)
   {
       cout<<"我是父进程,我的PID:"<<getpid()<<"我的PPID:"<<getppid()<<endl;
       sleep(1);
   }

   return 0;
}

fork演示代码运行



fork系统调用原理

父进程在创建子进程时会先创建一个pcb以父进程为模板拷贝大部分属性但不是全部,这样就在内核中创建另一份pcb并共享父进程的代码和数据。

进程具有独立性,相互之间互不干扰,包括父子进程,所以如果父进程创建子进程后先被关闭,则子进程也不会受到很大影响,只不过子进程由于失去了父进程会由Bash直接接管成为孤儿进程。

由于进程具有独立性,当两个进程在函数域中共用一个变量时,一开始两个进程同时使用这个变量,如果只是读取则不会发生什么变化,如果一方修改,则修改的一方操作系统会为其新开辟一块该变量的空间并将变量内容复制过去,然后修改方进程自由进行修改,这样两个进程的数据不会受影响,这个过程称为写时拷贝

所以父子进程代码共享(因为代码数据是只读),但数据是写时拷贝各自一份


孤儿进程验证
验证孤儿进程
向进程发送9号信号关闭该进程,指令:

kill -9 进程PID

后台进程无法被使用CTRL+C关闭,所以需要使用kill -9强制关闭后台进程。


写时拷贝验证

int main()
{
   pid_t id = fork();
   int num = 0;
   if(id == -1) //创建失败 直接退出
   {
       cout << "进程创建失败!" <<endl;
       exit(1);
   }

   if(id == 0) //如果id为0则是子进程 开始执行子进程代码块
   {
       for(num = 1;num<=5;++num) //子进程运行10次结束
       {
           cout<<"子进程num:"<<num<<endl;
           sleep(1);
       }
       cout<<"子进程退出!"<<endl;
       exit(1); //子进程运行结束直接退出不需要执行父进程的代码
   }

   //运行到这个地方的必定是父进程
   while(true)
   {
       cout<<"父进程num:"<<num<<endl;
       sleep(1);
   }
   return 0;
}

写时拷贝
这里可以发现子进程修改了num但并未影响父进程!
关于写时拷贝的细节涉及虚拟地址空间,我们后期会详细介绍!


关于父子进程小结

  • bash 命令行解释器本质上也是一个进程,可以被销毁。
  • 命令行启动的所有程序都是bash的子进程,其父进程都是 bash,因为Linux系统通过这种方式避免内核被破坏。
  • 父进程被销毁后,子进程会变成孤儿进程。
  • 进程间具有独立性,包括父子进程,当双方共用数据时一旦有一方修改就会触发写时拷贝。

最后

进程初识的介绍到这里就差不多结束了,本节我们简单的介绍了进程的相关知识,介绍了冯诺依曼体系结构,操作系统对于数据的管理方式,进程的理解以及信息的查看,最后说明了关于父子进程的相关知识,说明了进程间具有独立性的问题,关于进程的知识这只是冰山一角,后面我们会展开进行详细介绍!

本次 <Linux进程初识> 就先介绍到这里啦,希望能够尽可能帮助到大家。

如果文章中有瑕疵,还请各位大佬细心点评和留言,我将立即修补错误,谢谢!
结尾

🌟其他文章阅读推荐🌟
C++ <STL之string的使用> -CSDN博客
C++ <模板> -CSDN博客
C++ <内存管理> -CSDN博客
C++ <类和对象 - 下> -CSDN博客
C++ <类和对象 - 中> -CSDN博客
C++ <类和对象 - 上> -CSDN博客
🌹欢迎读者多多浏览多多支持!🌹

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

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

相关文章

机器学习笔记 Segment Anything用于图像分割的通用大模型

一、简述 人工智能中的基础模型正变得越来越重要。它们被定义为在大量数据上训练的大型人工智能模型,可以适应广泛的任务。 基础模型的早期例子是大型语言模型(LLM),如GPT和BERT。随后,该行业也看到了同样的想法被应用于多模态基础模型,如DALLE、CLIP等。基础模型这个术语…

基于深度神经网络的图像分类与训练系统(MATLAB GUI版,代码+图文详解)

摘要&#xff1a;本博客详细介绍了基于深度神经网络的图像分类与训练系统的MATLAB实现代码&#xff0c;包括GUI界面和数据集&#xff0c;可选择模型进行图片分类&#xff0c;支持一键训练神经网络。首先介绍了基于GoogleNet、ResNet进行图像分类的背景、意义&#xff0c;系统研…

S3C6410 中的 cascaded irqdomain 之 gpio

文章目录 VIC 中断 与 gpio 中断 的硬件拓扑图描述linux cascaded irq domainirq domain 初始化时获取 IRQ number(软件中断号) 时中断发生时如何调试linux irq domain 实例 VIC domain 与 gpio domain 的硬件拓扑语言描述VIC 与 INT_EINTx 的关系INT_EINTx 与 GPIO的关系INT_E…

python+excel的接口自动化测试框架实战教程(视频讲解+源码)

目录 设计流程图 Excel和结果预览 框架结构 Excel相关 日志封装 正则操作 核心操作 测试操作 测试报告发送邮件类 运行 设计流程图 这张图是我的excel接口测试框架的一些设计思路。 首先读取excel文件&#xff0c;得到测试信息&#xff0c;然后通过封装的requests方…

PyTorch中的优化器探秘:加速模型训练的关键武器

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

shell的基础学习三

文章目录 一、Shell 流程控制二、Shell 函数三、Shell 输入/输出重定向四、Shell 文件包含总结 一、Shell 流程控制 for 循环 与其他编程语言类似&#xff0c;Shell支持for循环。 for循环一般格式为&#xff1a; while 语句 while 循环用于不断执行一系列命令&#xff0c;也…

数字取证在打击和预防网络犯罪中的作用

数字取证在调查网络犯罪、防止数据泄露、在法律案件中提供证据、保护知识产权和恢复丢失的数据方面发挥着关键作用。 详细了解数字取证的重要性、如何进行网络安全调查以及数字取证专家面临的挑战。 数字取证的 4 种类型 数字取证涉及使用专门的技术和工具来检查数字设备、网…

【Python零基础学习入门篇④】——第四节:Python的列表、元组、集合和字典

⬇️⬇️⬇️⬇️⬇️⬇️ ⭐⭐⭐Hello&#xff0c;大家好呀我是陈童学哦&#xff0c;一个普通大一在校生&#xff0c;请大家多多关照呀嘿嘿&#x1f601;&#x1f60a;&#x1f618; &#x1f31f;&#x1f31f;&#x1f31f;技术这条路固然很艰辛&#xff0c;但既已选择&…

SPSS如何进行均值比较和T检验之案例实训?

文章目录 0.引言1.均值过程2.单样本T检验3.独立样本T检验4.成对样本T检验 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对SPSS进行了学习&#xff0c;本文通过《SPSS统计分析从入门到精通》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对均值比较…

Day5_创建mapper文件/编写查询语句sql

上一节主要介绍了springboot集成mybatis进行&#xff0c;以及后端开发思想。这一节主要编写sql映射文件&#xff0c;即真正的sql语句。实现增删改查用户数据&#xff0c;以及配置application.yml或者configuration文件实现控制台打印SQL语句。 接着上一节编写续写~~~~~~ 目录…

目标检测模型量化---用POT工具实现YOLOv5模型INT8量化

POT工具是什么 POT工具&#xff0c;全称&#xff1a;Post-training Optimization Tool&#xff0c;即训练后优化工具&#xff0c;主要功能是将YOLOv5 OpenVINO™ FP32 模型进行 INT8 量化&#xff0c;实现模型文件压缩&#xff0c;从而进一步提高模型推理性能。 不同于 Quantiz…

vim操作笔记

1. Vim普通模式指令 指令描述yy复制当前行y{n}y复制当前行起的后面 n 行p在当前行粘贴{n}p在当前行重复粘贴 n 次dd删除当前行d{n}d删除当前行起的后面 n 行x剪切当前光标的字符X剪切当前光标的前一个字符r{char}替换一个字符R不定长替换yw复制一个词dw删除一个词&#xff08;…

【GAMES101】03 Transformation

2D线性变换 ——写成矩阵形式 1、Scale&#xff08;缩放&#xff09; 2、Reflection Matrix&#xff08;反射矩阵&#xff09; 3、Shear Matrix&#xff08;剪切矩阵&#xff09; 4、Rotation Matrix&#xff08;旋转矩阵&#xff09; 推导过程&#xff1a; 5、Translation Ma…

第十四届蓝桥杯大赛软件赛省赛(Java 大学B组)

目录 试题 A. 阶乘求和1.题目描述2.解题思路3.模板代码 试题 B.幸运数字1.题目描述2.解题思路3.模板代码 试题 C.数组分割1.题目描述2.解题思路3.模板代码 试题 D.矩形总面积1.问题描述2.解题思路3.模板代码 试题 E.蜗牛1.问题描述2.解题思路3.模板代码 试题 F.合并区域1.题目描…

Vue2加载倾斜摄影

vue3项目加载倾斜摄影 vue3项目加载倾斜摄影的教程可见此人的教程&#xff0c;亲测可用 https://blog.csdn.net/qq_37750030/article/details/124680036 vue2项目加载倾斜摄影 可是为什么到了vue2的老项目里面用不了呢&#xff1f; 原因在于这几个库&#xff0c;全是ts的&…

只出现一次(N次)的数字 / 出现次数最多的数字 / 数组中数字出现的次数

一.题目类型简介 数组中数字出现的次数是一类经典的问题&#xff0c;通常让我们求数组中数字出现的次数及其衍生的问题&#xff0c;比如&#xff0c;只出现一次的数字&#xff0c;只出现两次的数字&#xff0c;在一个数组中只有一个数字出现一次&#xff0c;其他出现两次或者三…

基于FPGA+JESD204B 时钟双通道 6.4GSPS 高速数据采集模块设计(二)研究 JESD204B 链路建立与同步的过程

基于 JESD204B 的采集与数据接收电路设计 本章将围绕基于 JESD204B 高速数据传输接口的双通道高速数据采集实现展 开。首先&#xff0c;简介 JESD204B 协议、接口结构。然后&#xff0c;研究 JESD204B 链路建立与同 步的过程。其次&#xff0c;研究基于 JESD204B …

linux驱动开发 - 10_阻塞和非阻塞 IO

文章目录 1 阻塞和非阻塞 IO1.1 阻塞和非阻塞简介1.2 等待队列1、等待队列头2、等待队列项3、将队列项添加/移除等待队列头4、等待唤醒5、等待事件 1.3 Linux驱动下的poll操作函数 2 阻塞 IO 实验1、驱动程序编写2、编写测试 APP3、编译驱动程序和测试 APP4、运行测试 3 阻塞 I…

elform 动态 rules

一.使用v-for渲染时 前端由于某些需求场景需要&#xff0c;部分表单的渲染是通过 v-for循环渲染显示&#xff0c;此时如何实现表单验证呢&#xff1f;如下&#xff0c;点击第一行的号可以动态的增加更多行表单&#xff0c;不同于单一固定的表单行[参见下文一般情况下]&#xf…

book-riscv-rev1.pdf 翻译(自用)第一章 操作系统接口

Job of operating system: 操作系统使得多个程序分享一台计算机&#xff0c;提供一系列仅靠硬件无法支持的服务。 管理与抽象低级别硬件&#xff08;如&#xff1a;文件处理程序不需要关注使用哪种硬盘&#xff09;使得多个程序分享硬件&#xff08;programs that can run at…