PCB linux中进程管理

PCB进程

PCB(Process Control Block)进程控制块是进程在操作系统中的数据结构,用于存储进程状态、程序计数器、寄存器值等信息。每个进程都有一个与之对应的PCB,操作系统利用PCB来管理和调度进程。PCB由操作系统形成,代码数据由用户来提供。

管理进程的本质就是管理PCB结构体

在PCB中存储的信息包括(进程属性的集合):

  1. 进程ID:唯一标识一个进程的ID,用于区分不同的进程。

  2. 进程状态:表示进程当前所处的状态,如运行、等待、阻塞等。

  3. 程序计数器:存储CPU下一条将要执行的指令的地址,用于实现进程的切换和恢复执行。

  4. 寄存器值(Registers):存储进程在切换时所用的寄存器值,用于在恢复执行时还原进程的状态。

  5. 进程优先级:表示进程在调度时的优先级,用于确定进程在CPU上的执行顺序。

  6. 进程映像(Process Image):包含了进程的代码、数据和栈等信息。

  7. PCB本身的信息:包括PC在内存中的地址、PCB的大小等。

  8. 下一个PCB的指针:操作系统管理进程就是对单链表的增删查改。注意可能是多个指针,PCB在系统中以多叉树的形式链接,内部会形成各种链表,队列,栈等等链式数据结构。

所以要创建进程时要做两件事(简化):创建PCB,把进程的代码和数据加载到内存中

PCB的访问

PCB是一个大型的结构体,

PCB的访问实现原理可以分为以下几个步骤:

  1. 获取PCB指针:当一个进程需要访问自己的PCB时,可以使用操作系统提供的系统调用来获取当前进程的PCB指针。例如,在Linux中,可以使用getpid()系统调用获取当前进程的进程ID(PID),然后使用/proc文件系统中的特定文件来访问该进程的PCB信息。

  2. 访问PCB:当获取到PCB指针后,进程可以访问PCB中的信息。例如,可以使用指针操作来读取或修改PCB中的CPU寄存器值、内存映射信息等。

  3. 同步和互斥:在多进程环境下,需要使用同步和互斥机制来保证进程之间的协作关系。例如,可以使用互斥锁来防止多个进程同时访问共享资源,使用信号量来控制进程的执行顺序等。

  4. 访问权限控制:直接访问进程的PCB通常是操作系统内核的特权操作,只有内核或具有特权的进程(如超级用户)才能够进行。这是为了确保系统安全和进程隔离性。普通进程只能通过操作系统提供的接口或系统调用来访问和操作自己的PCB信息,而不能直接访问其他进程的PCB。

偏移量

在一些操作系统中,为了访问PCB中的不同字段,可能需要获得PCB指针到头部的偏移量。这是因为PCB结构中的各个字段并不一定是连续存储的,可能存在填充字节或对齐需求等。

通过获得PCB指针到头部的偏移量,可以根据字段的偏移量直接计算字段的地址。这样,就可以访问PCB中的各个字段,而无需知道具体的内存地址。

对于某些操作系统,提供了一些宏和函数来简化获取PCB指针到头部的偏移量的操作。例如,在Linux的内核开发中,可以使用offsetof()宏来获取某个字段的偏移量。同时,也可以使用container_of()宏根据字段的指针和偏移量计算PCB的地址。

offsetof()宏的实现原理:

#define offsetof(type, member) ((size_t) &((type *)0)->member)

type是结构体类型,member是结构体中的字段名。

怎么访问PCB成员
  1. 通过指针访问:如果我们有一个指向结构体的指针,我们可以使用指针算术来访问成员。例如,如果我们有一个struct PCB类型的指针pcb,那么我们可以通过以下方式访问member成员:

struct PCB *pcb = /* 获取PCB的指针 */;
int value = (*pcb).member; // 或者 pcb->member;
  1. 通过指针加偏移量访问:我们也可以直接通过指加上偏移量的方式来访问成员。例如:

struct PCB *pcb = /* 获取PCB的指针 */;
int value = (*(char **)pcb + offsetof(struct PCB, member))[0];
int value = (pcb *)(*(char **)pcb + offsetof(struct PCB, member))->member;

在这个例子中,我们首先将pcb指针强制转换为char *类型,然后加上offsetof(struct PCB, member)偏移量,最后通过[0]来获取member成员的值。这种方法在某些情况下可能更高效,因为它避免了额外的 dereferencing(指针操作)。

  1. 通过联合体访问:如果结构体中的成员是通过联合体实现的,那么我们可以使用联合体的特性来访问成员。例如:

struct Union {
    int i;
    char s;
};
​
struct Union *u = /* 获取Union的指针 */;
int value = u->i; // 访问整数成员

在这种情况下,我们通过->操作符直接访问i成员,因为它是联合体Union中第一个定义的,所以它的偏移量为0。

linux中进程的管理

linux 中的PCB 叫做 task_struct,是PCB的一种

task_struct是内核的一部分,它包含了进程控制块(Process Control Block,PCB)和系统调用堆栈信息。

task_struct中的各个字段和成员变量用于表示进程的不同方面,如进程id(pid,ppid)、进程状态(状态码)、优先级、程序计数器(pc)、内存管理信息(内存指针)、上下文数据、I/O状态信息、记账信息(使用时钟数总和、时间限制等)、资源使用情况、文件描述符、网络连接等。

通过task_struct,内核可以跟踪进程的状态和执行路径,并根据需要执行各种操作,如创建、调度、终止和回收进程。

task_struct提供了与进程相关的系统调用接口,允许用户空间程序与内核进行交互和通信。

进程管理

在linux内核中,task_struct采用双向链表的方式组织,同时各个节点由队列,进程树等数据结构来组织,以实现等待队列等等

进程的属性

可以在proc目录中查看每一个进程的信息

一些常见的属性:ps ps aux ps ajx查看

  1. 进程ID(PID):每个进程都有一个唯一的标识符,用于标识进程。

  2. 父进程ID(PPID):父进程ID是该进程的父进程的进程ID。一般是bash

  3. 进程组ID(PGID):进程组ID是一个整数,它标识了该进程所属的进程组。

  4. 进程优先级Priority(PRI):进程优先级是一个数值,它决定了进程在系统中的优先级。优先级数值越低,进程获得CPU的时间片越多。

  5. 进程的修正优先级Nice value(NI):NI值表示进程的"好人度"或"友好度",用于影响进程在系统中占用CPU时间的优先级。通过nicerenice来设置和调整,也可以通过编程接口如setpriority()系统调用在程序中设置。

  6. 进程状态(STAT):进程状态表示进程当前所处的状态,如运行、等待、阻塞等。

  7. 进程资源使用情况:进程资源使用情况包括进程占用的内存(MEM)、CPU时间(CPU)、磁盘IO等资源的使用情况。PRI

  8. 进程打开的文件描述符(File Descriptors):进程打开的文件描述符是一个整数,用于标识进程打开的文件或套接字。

  9. 进程创建时间(Creation Time):进程创建时间表示进程被创建的时间。

  10. 进程执行时间(Exit Time):进程执行时间表示进程执行完毕的时间。

  11. 进程命令行(Command Line):进程命令行表示启动该进程时传入的命令行参数。

进程状态(STAT)

  1. 新建(New):当一个进程被创建但还未被系统调度执行时,它处于新建状态。在这个状态下,操作系统正在为该进程分配所需的资源。

  2. 就绪(Ready):一旦新建进程获得了所有必需的资源,它就会进入就绪状态。在就绪状态下,进程已准备好被调度执行,但还未被分配R到CPU运行。

  3. 运行(Running):当一个就绪进程获得了CPU资源并被调度执行时,它进入运行状态。在运行状态下,进程正在执行其指令和代码。(R)

  4. 等待(Waiting)/阻塞(Blocked):一个正在运行的进程可能会因某些原因而无法继续执行,例如等待某个事件的发生、等待输入/输出操作的完成等。在这种情况下,进程会进入等待状态,直到满足特定的条件或事件发生。(S+,S)

  5. 挂起:进程因为某些原因暂时不被执行,数据被放回外存,但它的状态(也就是PCB)被保存在操作系统中,可以在将来某个时刻被唤醒并继续执行。

  6. 结束(Terminated):一个进程完成其任务或被提前终止后,进程进入终止状态。在终止状态下,进程的执行已经完全结束,但系统仍会保留其进程控制块等相关信息。

Z:僵尸状态:如果父子进程中一个程序提前结束 会进入僵尸状态 导致占时的内存泄漏 在后边解决

D:disk sleep 深度睡眠(不可被系统杀死的状态) 例如磁盘写入的进程 S 是随时可以唤醒的普通睡眠

X : dead

T: stopped

t: tracing stop

计算机进行进程的步骤

  1. 进程创建:操作系统根据用户或系统的请求,使用fork()或其他相关的系统调用创建新的进程。新进程会从父进程继承一些属性,如文件描述符、环境变量等。

  2. 进程调度:操作系统根据一定的调度算法(如轮转调度、优先级调度等)从就绪状态的进程队列中选择一个进程执行。被选中的进程会被调度器分配给可用的处理器或CPU核心。

  3. 进程执行:被调度的进程开始执行其指令和代码。进程的代码会从磁盘加载到内存,CPU会按照指令的顺序执行代码,并操作内存和其他资源。

  4. 进程等待:在父子进程关系中,父进程通常会等待子进程完成,以便获取子进程的退出状态或处理子进程的结果。这个等待的过程可以使用系统调用(如waitwaitpid等)来实现。在等待期间,父进程会阻塞,直到子进程结束或满足特定的条件。当子进程结束后,父进程会从等待状态恢复并继续执行。

  5. 进程替换:在进程替换中,新的进程取代当前进程的执行,原进程的代码和数据被新进程的代码和数据所替代。这个过程通常是通过调用exec系列函数来实现的,例如execvpexeclp等。进程替换可以用于加载并执行新的程序,替换当前进程的执行环境、代码和数据。

  6. 进程阻塞:在执行过程中,进程可能需要等待某个事件的发生,如等待用户输入、等待磁盘读取等。在这种情况下,被调度的进程暂时挂起,进入阻塞状态,直到等待的事件发生。

  7. 进程唤醒:当一个进程等待的事件发生时,操作系统会将该进程从阻塞状态唤醒,并重新放入就绪状态的队列中,以便可以被再次调度执行。

  8. 进程终止:当一个进程完成其任务或不再需要时,它会被终止。操作系统会回收该进程使用的资源,并释放其占用的内存空间。

这些步骤是基本的进程执行过程,当多个进程同时存在时,操作系统会根据调度策略和资源管理来决定进程的执行顺序和时间片分配。同时,进程间可能会进行通信和同步操作,以实现协作和共享数据等功能。操作系统提供了在进程间传递消息、共享内存、管道、套接字等机制,用于实现进程间的交互和数据共享。

运行队列->进程调度与执行

运行队列是操作系统中管理进程调度和资源分配的一种数据结构。它存储了当前可运行的进程或任务,并根据调度算法从中选择一个进程分配给CPU执行。

  1. 队列形式:运行队列通常是一个队列数据结构,按照进入队列的顺序排列。当进程处于就绪状态时,会被添加到队列的末尾。

  2. 调度策略:运行队列的调度策略决定了从队列中选择进程的顺序。常见的调度算法包括先来先服务(FCFS)、轮转调度(Round-Robin)、最短作业优先(SJF)、最高优先级优先(HPF)等。

  3. 优先级:进程或任务可以具有不同的优先级,优先级高的进程在选择时会先于优先级低的进程执行。调度器根据进程的优先级从运行队列中选择进程。

  4. 时间片:一些调度算法,如轮转调度,将一个时间片分配给每个进程,一个时间片表示进程能够连续运行的时间。当时间片用完后,进程会被放回运行队列,等待下一次调度。

  5. 调度器:调度器是操作系统的一部分,负责管理进程的调度和资源分配。调度器会选择运行队列中适当的进程,并将其分配给CPU执行。

等待对列->进程阻塞与唤醒

等待队列是在操作系统中用于存储等待某个事件发生的进程或线程的队列。当一个进程或线程需要等待某个条件满足时(如等待输入、等待资源可用等),它会被放入等待队列中,直到条件满足时被唤醒并重新加入可运行队列,以便继续执行。链表链接在要操作的事件(如键盘)后。

  1. 条件:等待队列的每个元素都与一个特定的条件相关联。这个条件通常是指特定的事件或资源状态,当这个条件满足时,等待队列中的元素可以被唤醒。

  2. 队列:等待队列是一个数据结构,用于存储等待某个条件满足的进程或线程。队列可以是先进先出(FIFO)的结构,也可以是优先级队列。

  3. 阻塞和唤醒机制:当一个进程或线程处于等待状态时,它会被阻塞,暂停执行。当条件满足时,操作系统会将其从等待队列中唤醒,并放入可运行队列,使之能够继续执行。

等待队列的使用可以有效地实现进程或线程间的同步与通信,避免了资源竞争和浪费。常见的应用场景包括等待用户输入、等待网络连接、等待I/O完成等。

等待对列->进程的挂起 换入换出

挂起状态是进程生命周期中的一个正常状态,它表示进程因为某些原因暂时不被执行(把PCB加载到内存的数据和代码放回外存),但它的状态(PCB)被保存在操作系统中,可以在将来某个时刻被唤醒并继续执行。

进程挂起的原因可能包括:

  1. 等待资源:进程需要等待某些资源(如I/O设备、共享内存等)变得可用。

  2. 等待事件:进程需要等待某个特定事件的发生,例如输入、信号量变化等。

  3. 系统调用:进程可能因为执行了一个系统调用(如信号处理)而被挂起。

  4. 调度决策:操作系统可能会根据调度策略起某些进程,以便为其他进程提供CPU时间。

  5. 错误或异常:进程可能因为执行了非法操作或发生了异常而被操作系统挂起。

挂起状态不同于终止状态。挂起状态的进程可能会在将来被唤醒,而终止状态的进程则会从系统中移除,其资源将被回收。操作系统通常提供机制来管理挂起进程,例如使用信号量、互斥锁或专门的进程控制块(PCB)来跟踪挂起进程的状态和条件。

挂起的标志就是换入换出

进程的换入换出(也称为进程的调度和交换)是操作系统中的一种机制,用于有效地管理内存中活动进程的数量。

当可用内存不足时,操作系统需要将一些进程从内存中换出(置换出)到外存或磁盘上,以便给新的进程腾出内存空间。当需要执行一个先前从内存中换出的进程时,该进程需要再次换入(置换入)到内存中才可以继续执行。

换入换出的过程包括以下几个步骤:

  1. 选择换出的进程:操作系统根据一定的策略和算法,选择一个需要换出的进程。常见的选择策略包括最近最久未使用(LRU)、先进先出(FIFO)等。

  2. 保存进程的状态:被选择换出的进程的当前状态,如寄存器内容、页表等,需要被保存到外存或磁盘上。这样,在将来需要重新换入时,进程可以从之前保存的状态恢复。

  3. 释放内存空间:被换出的进程占用的内存空间被释放,以便为其他进程腾出空间。

  4. 选择换入的进程:根据前面换出的进程空出的内存空间,操作系统选择一个需要执行的进程,并将其换入内存。

  5. 恢复进程状态:被换入的进程的之前保存的状态被加载到内存中,以便该进程可以继续执行。

父子进程中...
  1. 继承性:在子进程先终止时,它的资源会被父进程继承。如果父进程不主动回收,子进程会一直处于Z状态,相关资源如打开的文件描述符、内存空间不会被释放。发生内存泄漏.

  2. 孤儿进程:在父进程先终止时,子进程不会随之终止。子进程的父进程会变为 init 进程(通常是进程ID为1的进程)。 init 进程是所有其他进程的祖先进程,它会接管孤儿进程(即父进程终止时没有被其他进程接管的子进程)。子进程会继续执行,直到它自己终止或被其他进程终止。init 进程会负责回收孤儿进程的资源。

  3. 消息传递:在某些情况下,父子进程之间可能存在消息传递机制。例如,使用管道、共享内存或套接字等机制,子进程可以向父进程发送消息,反之亦然。

你可能感兴趣的:(重要知识点块,linux,运维,服务器)