Linux进程学习【基本认知】
🌼🌼前言:前言理解冯诺依曼体系结构与操作系统原理
在计算机科学的基础理论中,冯诺依曼体系结构和操作系统是两个关键概念,它们共同构成了现代计算机的运行基础。本文将从这两个方面入手,简要讲解它们的基本原理和相互关系。
一、冯诺依曼体系结构:计算机的基础框架
1. 冯诺依曼体系结构概述
冯诺依曼体系结构,又称普林斯顿结构,是一种经典的计算机结构设计模型。其核心思想是将程序指令存储器和数据存储器合并为一个统一的存储空间,并且通过共享的内存接口进行数据交换。在这种结构中,程序指令和数据位于内存的不同物理位置,但它们的存储形式和存取方式是一样的。
冯诺依曼体系结构被广泛应用于各类计算机系统中,包括从最早的EDVAC电子计算机到现代的高性能处理器。这个结构的设计思想至今仍是计算机系统的基石。
2. 冯诺依曼体系的组成
冯诺依曼体系结构的核心由五个基本组成部分构成:
-
运算器(ALU):负责执行算术和逻辑运算。
-
控制器(CU):管理计算机的指令流,控制数据流动和硬件操作。
-
存储器(Memory):存储数据和程序指令,CPU通过内存与其他硬件设备进行交互。
-
输入设备(Input):如键盘、鼠标、摄像头等,负责将外部信息输入计算机。
-
输出设备(Output):如显示器、打印机等,负责展示计算机处理结果。
在冯诺依曼结构中,所有的数据和指令都必须通过内存,CPU只能与内存进行交互,而不能直接操作外设(输入输出设备)。所有的输入输出操作实际上是通过内存中介完成的,这样可以避免CPU和外设之间的速度差异对计算机整体性能的影响。
3. 内存和数据流动
冯诺依曼体系架构中的内存扮演了至关重要的角色。所有的外设数据都首先传输到内存,再由内存传递到CPU,经过处理后再返回内存,并最终传输到输出设备。内存作为数据的缓存和中转站,大大提升了数据处理的效率。
二、操作系统:硬件和用户之间的桥梁
1. 操作系统的定义
操作系统(OS)是管理计算机硬件与软件资源的系统软件,它提供了一个稳定、有效的执行环境,允许用户和程序通过高效的方式使用计算机资源。简而言之,操作系统是计算机与用户之间的桥梁,帮助用户操作硬件、运行应用程序,并提供必要的服务和接口。
操作系统的功能远超简单的资源调度,它还涉及到进程管理、内存管理、文件系统管理和设备驱动管理等多个方面。
2. 为什么需要操作系统?
操作系统的出现是因为计算机硬件的复杂性远超单一用户的需求。假设每个用户都可以直接与硬件交互,效率将大打折扣,而且系统的安全性和稳定性也无法保障。
操作系统通过以下方式来简化与硬件的交互:
-
资源管理:操作系统管理计算机的所有硬件资源,包括处理器、内存、存储设备和输入输出设备,确保资源的合理分配和高效利用。
-
进程与线程管理:操作系统负责调度进程的执行,确保各个进程之间不会相互干扰,提供多任务处理能力。
-
内存管理:操作系统通过虚拟内存管理技术,为每个程序分配独立的内存空间,有效避免了程序之间的数据干扰。
-
文件管理:操作系统提供文件系统,使得用户能够方便地存储和管理数据文件。
3. 驱动程序与硬件抽象
操作系统和硬件之间并不是直接交互的,操作系统通常依赖于驱动程序来管理硬件设备。每种硬件设备都需要一个对应的驱动程序,这些程序为操作系统提供硬件的抽象层,操作系统通过驱动程序与硬件进行交互。
例如,计算机的硬盘更换时,用户只需要更新硬盘的驱动程序,而不需要更改操作系统本身的代码。驱动程序为硬件和操作系统之间提供了一个可扩展的接口,使得硬件的更新和维护变得更加容易。
4. 系统调用与库函数
操作系统通常会暴露一些系统调用接口供开发者和程序使用。系统调用是操作系统向外提供的基本服务接口,程序可以通过这些接口来执行文件操作、内存分配、进程管理等基础功能。
对于开发者来说,直接使用系统调用较为复杂,因此操作系统还会提供一些封装好的库函数。这些库函数基于系统调用,将常见的操作封装成简单的函数,供程序开发者调用。
三,🚀 进程管理
进程 是计算机中的重要概念,每个运行中的程序都有属于自己的 进程 信息,操作系统可以根据这些信息来进行任务管理,比如在我们Windows中的任务管理器中,可以看到各种运行中的任务信息,这些任务就可以称之为 进程,简单的 进程 二字后面包含着许多知识,比如为什么OS需要对任务进行管理、任务信息是如何组成的、如何创建新任务等,下面我将带大家从 冯诺依曼 结构体系开始,理解学习 进程 相关知识。
在操作系统的核心职能中,进程管理是至关重要的组成部分,它直接关系到计算机如何有效地运行多个任务。理解进程的概念和管理方式,是学习操作系统的基础之一。
操作系统的基本职能
操作系统的功能可以分为四大部分:
-
内存管理
-
进程管理
-
文件管理
-
设备管理
进程:
我们以前的任何启动并运行程序的行为,都是由 操作系统 帮助我们将程序转换为 进程,然后完成特定任务。
正确定义:进程 由两边组成,分别是 相关代码和数据 和 内核关于进程的相关数据结构
也就是说,一个 进程 应该有两部分,数据 与 信息,此处的 信息(进程控制块) 是由 操作系统 对代码和数据进行描述后生成的 信息块 ,原因很简单,方便进行管理,而这就是管理本质的体现: 先描述,再组织
我们对 进程 的相关学习是建立在 进程控制块 上的,上面包含了其对应 进程 的各种信息,下面就来学习一下 数据 与 信息 这两部分知识吧
📃代码与数据
数据生万物,任何一个进程都有自己的代码和数据,比如我们常见的 C语言 源文件,经过编译后生成的可执行程序中,就包含着二进制代码和其创建修改的时间、所处位置信息
当可执行程序 myprocess
运行时,各种数据就会被描述,生成相应的进程控制块
📃进程控制块
进程控制块即PCB(process control block)
,Linux
中的 PCB
是 task_struct
,程序会被描述生成相应的task_struct
装载至 内存
中。
进程在CPU上面运行实际上是将
进程控制块包含内容:
标示符: 描述本进程的唯一标示符,用来区别其他进程
状态: 任务状态,退出代码,退出信号等
优先级: 相对于其他进程的优先级
程序计数器: 程序中即将被执行的下一条指令的地址
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
其他信息
注: ./可执行程序 其实就是将可执行程序加载至内存中,再执行描述+组织
四, 进程PID的介绍
1.PID进程标识符
PID(Process ID)即进程标识符,是操作系统为每个进程分配的一个唯一的数字编号:
查看进程PID的几种方法:
1,指令查找
操作系统提供了多种方式来查看进程的状态和信息。在Linux中,常见的查看工具包括:
ps
命令
ps
(Process Status)命令用于查看当前系统中运行的进程。结合管道和grep
命令,可以精确查找目标进程。
-
示例命令
-
ps -ajx|head -1;ps -ajx|grep filename|grep -v grep
命令列出系统指定文件进程的信息,并且可以通过管道将输出结果过滤,以便查找特定的进程。
top
命令
top
命令类似于Windows的任务管理器,它提供了一个动态实时的进程视图,允许用户监控系统的资源使用情况。
-
示例命令:
-
top
top
命令显示的内容包括:进程的PID、用户、CPU占用率、内存占用率等信息。
/proc
目录
Linux系统将所有进程的信息存储在 /proc
目录下。每个正在运行的进程都有一个对应的目录,目录名称即为进程的PID。
-
查看某个进程的信息:
-
cd /proc/PID/ //先进后看
ls /proc/PID/
通过访问这些文件,我们可以了解进程的详细信息,包括内存使用情况、CPU时间、打开的文件描述符等。
2.通过调用系统函数
操作系统对进程进行管理,但是用户不能直接访问操作系统,因此需要通过系统提供的系统调用函数来管理进程。
查看pid的函数为getpid();
可以通过man手册进行查询,输入命令: man getpid
通过创建一个C语言代码来查看PID:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{while(1){printf("I am a process,pid: %d\n",getpid());//查看pidsleep(1);//打印一次之后休眠一秒}return 0;
}
2.PPID父进程的PID
进程其实还有父亲,儿子这种说法。还可以用指令,函数等等,查看父进程的PID,在子进程中父进程的PID 名字叫做PPID。
该程序的父进程是bash(命令行解释器) 。
注:
- 进程可以创建也可以销毁,通过指令
kill -9 PID
可以销毁指定进程,包括bash
,当然这个指令需要在新的窗口中执行 - 也可以通过热键
ctrl+c
强制终止当前进程的运行
五,创建父子进程
创建跟上面查看进程一样,需要调用系统提供的函数。创建进程的函数为fork();fork之后,父子代码共享。
/*
* 创建子进程
* 这个函数有两个返回值
* 进程创建成功时,给父进程返回子进程的PID,给子进程返回0
* 创建失败时,返回 -1
*/
int fork(void)
fork
函数是一个非常重要的函数,它能在当前进程下主动创建 子进程
,用于程序中
编写代码如下:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>/** 测试fork创建子进程* 理解fork函数的返回值* 通过if语句进行分流* 总结:fork创建子进程成功时,给父进程返回子进程PID,给子进程返回0,如果失败返回-1;通过两次fork可以发现当父进程执行后,才会去执行子进程,父子进程间存在独立性,即父进程被kill后,子进程任然可以运行,父子进程间存在写时拷贝机制,当子进程的值发生改变时,只会作用于子进程中*/int main()
{pid_t ret = fork(); //获取返回值int val = 1; //比较值if(ret == 0){//在子进程内再创建(孙)子进程pid_t rett = fork();if(rett > 0){while(1){val = 2; //写时拷贝printf("二代进程正在执行 PID:%d PPID:%d 比较值为:%d 地址:%p\n\n", getpid(), getppid(), val, &val);sleep(1);}}else if(rett == 0){while(1){val = 3; //写时拷贝printf("三代进程正在执行 PID:%d PPID:%d 比较值为:%d 地址:%p\n\n", getpid(), getppid(), val, &val);sleep(1);}}elseprintf("进程创建失败\n");}else if(ret > 0){while(1){val = 1; //写时拷贝printf("一代进程正在执行 PID:%d PPID:%d 比较值为:%d 地址:%p\n\n", getpid(), getppid(), val, &val);sleep(1);}}elseprintf("进程创建失败\n");return 0;
}
不难发现,子进程
是否出现取决于在当前进程中是否调用 fork
函数
fork函数工作原理:
- fork 创建子进程时,会新建一个属于 子进程 的 PCB ,然后把 父进程 PCB 的大部分数据拷贝过来使用,两者共享一份代码和数据
各进程间是相互独立的,包括父子进程
这句话当我们销毁 父进程 后,它所创建的 子进程 并不会跟着被销毁,而是被 init 1号进程接管,成为一个 孤儿进程
fork
创建子进程时还存在 写时拷贝
这种现象,即存在一个变量,当父进程的改变值时,不会影响子进程的值,同理子进程也不会影响父进程,再次印证 相互独立
这个现象
父子进程相互独立
的原因:
- 代码是只读的,两者互不影响
- 数据:当其中一个执行流尝试修改数据时,OS 会给当前进程触发
写时拷贝
机制
以上只是对 fork
函数的一个简单介绍,关于这个函数底层是如何实现的,是一件较复杂的事,限于篇幅原因,我会在以后对此函数进行补充。
以上就是本文关于 进程 相关知识的讲解了,我们从 冯诺依曼 体系切入,理解了为什么需要 操作系统 ,以及 操作系统 是如何对计算机进行合理管理的:先描述,再组织;之后引入 进程 概念,清楚 进程 的构成及如何通过多种方式查看 进程 信息,最后学习了 fork 创建子进程,见识了 进程间具有独立性 这个重要概念。进程