您的当前位置:首页正文

【Linux】Linux进程揭秘:从理论到实践的深度探索之旅

2024-11-01 来源:个人技术集锦

前言:操作系统简介

概念

设计目的

理解

进程:程序的执行之魂

进程和程序的联系与区别

 描述进程-PCB

进程的标识符 

进程状态

 状态转换

僵尸进程

孤儿进程


前言:操作系统简介

概念

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

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

狭义上来说,操作系统只是操作系统的内核。

设计目的

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

具体来说,有以下几个目的:

  • 资源管理:确保CPU、内存、外设等资源被有效分配和调度,同时通过抽象层简化硬件复杂性。
  • 用户界面:提供GUI或CLI,实现用户与系统直观交互,支持多用户环境。
  • 程序执行:管理进程生命周期,实现内存虚拟化,支持程序并发运行。
  • 数据管理:通过文件系统组织数据,实施访问控制保障数据安全。
  • 设备支持:集成设备驱动,优化输入输出,确保硬件兼容性。
  • 系统安全:实施安全措施,保护系统免受攻击,维护数据安全。
  • 可移植与兼容:支持跨平台运行,确保软件兼容性,促进应用生态发展。

理解

在整个计算机软硬件架构中,操作系统的定位是:一款专门用于“管理”的软件 。

怎么理解这个“管理”?

  • 管理的例子
  • 描述被管理对象
  • 组织被管理对象

总的来说就是:

那么操作系统对进程的管理就是先把进程描述起来,再把进程组织起来!

进程:程序的执行之魂

进程,简而言之,是程序在计算机中的一次执行实例,是系统资源(如CPU时间、内存)的分配实体。Linux中,进程以task_struct(进程控制块,PCB)的化身形式存在内存中,存储着进程的全息:标识符、状态、优先级、程序计数器、内存指针、I/O状态、记账信息等。每一个进程,皆是task_struct链表的一员,是Linux内核管理进程的基石。

基本概念

  • 课本概念(狭义):程序的一个执行实例,正在执行的程序等
  • 内核观点(广义):担当分配系统资源(CPU时间,内存)的实体。

进程和程序的联系与区别

  • 程序(Program)程序是指由一组有序的计算机指令组成的一段可以在计算机上运行的代码。它是静态的,即指令的集合本身不随时间变化,也不具有执行的能力,直到被加载到内存中并由操作系统调用执行。程序是计算机编程的基本单位,用于实现特定的功能或解决特定的问题。
  • 进程(Process)进程是程序在一个数据集合上的运行过程,是系统进行资源分配和调度的一个独立单元。进程是动态的,它包含了程序计数器、寄存器的当前值以及内存的状态(如程序代码和数据的存储空间、程序控制块等)。进程是程序执行的具体实例,它反映了程序在执行过程中的状态变化。

主要区别:

  1. 静态与动态
    • 程序是静态的,它是一组指令的集合,本身不具有执行的能力。
    • 进程是动态的,它是程序在执行过程中的一个实例,包含了程序运行的当前状态。
  2. 生命期
    • 程序的存在是永久的,只要不被删除或修改,它将一直存在于存储介质中。
    • 进程则是有生命的,它因创建而产生,因调度而执行,因得不到资源而暂停,因撤销而消亡。
  3. 组成
    • 程序仅包含指令的集合,不包含执行时的数据状态。
    • 进程则是由程序、数据和进程控制块(PCB)三部分组成。PCB是进程存在的唯一标识,包含了进程的状态信息、控制信息以及资源分配情况等。
  4. 对应关系
    • 同一程序可以对应多个进程。当程序被多次执行时,每次执行都会创建一个新的进程。
    • 但一个进程只能对应一个程序(虽然一个进程可以执行多个程序段,但通常指其主程序)。
  5. 独立性
    • 程序作为指令的集合,其本身是独立的,不依赖于特定的执行环境。
    • 进程则是一个独立的执行实体,具有独立的内存空间和系统资源,可以与其他进程并发执行。

联系:进程是程序的一次执行过程,是程序动态特性的体现。没有程序就没有进程可言;而进程则是程序在特定数据集合上的具体执行实例,是程序功能得以实现的载体。

 描述进程-PCB

  • 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合
  • 课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct。
  • task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。

task_struct的内容分类

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

在Linux中,进程 = 内核task_struct结构体 + 程序的代码和数据。

进程的标识符 

概念:

  • 进程的标识符(PID,Process Identifier)是系统给每个进程定义的一个唯一标识该进程的非负整数。这个标识符类似于人的身份证号码,用于唯一地识别和定位一个进程。
  • 其类型为 pid_t(整型),其本质上是一个无符号整型的类型别名(typedef),范围:0~32767。

特性:

  • 唯一性:每个进程在系统中都有一个唯一的进程标识符,不同进程的标识符不会重复。
  • 动态性:当一个进程结束时,其标识符可以被系统回收并重新分配给新的进程。

注:在某些系统中,特定的进程标识符被保留给特定的系统进程。例如,进程标识符0通常被保留给系统调度进程(如Linux中的swapper进程),而进程标识符1则通常被分配给系统初始化进程(如Linux中的init进程)。

进程id (pid): 标识进程的一个非负整型数。
父进程id (ppid) : 任何进程( 除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程id称为父进程id(PPID)。如,A 进程创建了 B 进程,A 的进程号就是 B 进程的父进程id。

获取进程id和查看进程

getpid函数(获取进程id)

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);

 getppid函数(获取父进程id)

#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);

 查看进程 : ps ajx

进程状态

我们可以先看看Linux内核源代码是怎么描述进程(任务)的状态的:

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
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))。
  • D 磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束
  • T 停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
  • X 死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

五态模型:

  1. 新建态(创建状态)
    • 进程被创建时的初始状态。在这一状态下,系统为进程分配必要的资源,并进行初始化操作。
    • 当操作系统完成了进程创建的必要操作,并且当前系统的性能和内存的容量均允许时,进程将进入就绪态。
  2. 就绪态
    • 进程已处于准备好执行的状态,即进程已分配到除CPU以外的所有必要资源后,只要再获得CPU,便可立即执行。
    • 在这一状态下,进程已经具备了执行条件,但尚未被调度到CPU上执行。
  3. 运行态(执行状态)
    • 进程已获得处理机(CPU),其程序正在处理机上执行。
    • 在这一状态下,进程占用CPU资源,执行程序代码,实现进程的功能。
  4. 阻塞态(等待状态)
    • 正在执行的进程由于发生某事件(如I/O请求、申请缓冲区失败等)而暂时无法执行,即指进程的执行受到了阻塞。
    • 在这一状态下,进程无法继续执行,需要等待外部事件或资源的满足。
  5. 终止态(结束状态)
    • 进程执行完毕或被系统终止时的状态。在这一状态下,系统需要进行善后处理,如释放进程占用的资源等。

 状态转换

  • 新建态 → 就绪态:当操作系统完成了进程创建的必要操作,并且当前系统的性能和内存的容量均允许时,进程将从新建态转换为就绪态。
  • 就绪态 → 运行态:当CPU空闲时,操作系统的进程调度器会选中一个就绪进程,将其状态转换为运行态,并分配CPU资源给该进程执行。
  • 运行态 → 就绪态:当进程的运行时间片用完或发生更高优先级的进程需要执行时,操作系统会将当前进程的状态转换为就绪态,以便后续调度执行。
  • 运行态 → 阻塞态:当进程请求某个事件且必须等待时(如I/O操作),操作系统会将进程的状态转换为阻塞态,直到外部事件或资源满足为止。
  • 阻塞态 → 就绪态:当进程等待的事件完成时(如I/O操作完成),操作系统会将进程的状态从阻塞态转换为就绪态,以便后续调度执行。
  • 运行态 → 终止态:当进程执行完毕或由于某种原因被系统终止时,进程将进入终止态。

僵尸进程

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

 僵尸进程的危害

僵尸进程本身不占用系统资源(除了进程表中的一个槽位),但它们会积累并占用越来越多的进程表条目,特别是如果父进程频繁地创建子进程而不回收它们时。在进程数量有限制的系统中(比如某些UNIX系统),这可能会导致无法再创建新的进程。

简单来说,就是会造成内存泄漏。

孤儿进程

  • 父进程先退出,,而子进程还在运行,这时,子进程就称之为“孤儿进程”
  • 孤儿进程被1号init进程(init进程是一个特殊的系统进程,它是所有用户级进程的祖先,负责在系统启动时启动其他系统进程,并在系统关闭时终止它们)领养,最后由init进程回收。

____________________

⭐感谢你的阅读,希望本文能够对你有所帮助。如果你喜欢我的内容,记得点赞关注收藏我的博客,我会继续分享更多的内容。⭐

Top