[Linux]:进程(上)

2024-09-07 13:28
文章标签 linux 进程

本文主要是介绍[Linux]:进程(上),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

img

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:Linux学习
贝蒂的主页:Betty’s blog

1. 初识进程

1.1 进程的概念

在计算机世界中,进程是一个关键概念。它是程序的执行实例,一般而言,当可执行程序被加载到内存后就形成了进程。进程担当着分配系统资源(如CPU时间和内存)的重要角色,是操作系统进行资源分配和调度的基本单位,使得程序能够在系统中实际运行并完成特定任务。

1.2 进程的理解

进程信息被放在一个叫做进程控制块(PCB)的数据结构中,可以理解为进程属性的集合。PCB是进程存在的唯一标识。在Linux环境下,PCB就是task_struct,一个包含进程属性信息的结构体。

然后我们就可以将进程理解为:被进程控制块PCB所管理的可执行程序。一旦可执行程序被执行加载到内存,操作系统就会创建对应的PCB将其管理,最后就形成了进程。

画板

最后操作系统通过双向链表的形式将各个进程控制块PCB联系起来,方便管理。如果新创建一个进程就将其对应的PCB链接入双向链表中,退出一个进程就是将对于的PCB删除即可。

画板

1.3 task_struct的内容

task_structLinux当中的进程控制块,主要包含以下信息:

  1. 标示符(PID): 描述本进程的唯一标示符,用来区别其他进程。
  2. 状态: 任务状态,退出代码,退出信号等。
  3. 优先级: 相对于其他进程的优先级。
  4. 程序计数器(pc指针): 程序中即将被执行的下一条指令的地址。
  5. 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
  6. 上下文数据: 进程执行时处理器的寄存器中的数据。
  • 进程的代码是不可能在很短时间运行完的,规定每个进程的时间片(单次运行的最长时间),用户感受到的多个进程同时运行,本质上是CPU的快速切换。CPU只有一套寄存器,为了保护上下文,进程的这些临时数据被写入在PCB中,再来执行时,恢复上下文。

画板

  1. I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  2. 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 进程创建出来,CPU要执行它对应的代码,然而CPU很少,进程很多。因此OS内有一个调度模块,负责较为均衡的调度每一个进程,较为公平的获得CPU资源。让每个进程都能获得CPU资源,让每个进程都能被执行。
  1. 其他信息。

1.4 查看进程

在根目录下存在一个proc文件夹,里面包含了大量关于进程的信息。

其中有很多关于数字的子目录,这些数字其实是某一进程的PID,记录着对应进程的各种信息。

我们可以通过指令ps aux查看所有进程信息。

我们也可以将ps指令与grep指令搭配使用,显示某一进程的信息。

2. 创建进程

2.1 进程标识符

通过系统调用函数getpidgetppid可以分别获取进程的PID(进程ID)和PPID(父进程ID)。


下面我们可以通过一个测试程序观察:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{while(1){printf("proc PID :%d,parent PID:%d\n",getpid(),getppid());sleep(1); }return 0;
}

我们可以通过ctrl+c或者kill -9 进程的PID杀死相应的进程。

2.2 fork函数

我们可以使用fork函数创建一个子进程。

如果上述代码加上fork之后,又会打印什么呢?

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{forkwhile(1){printf("proc PID :%d,parent PID:%d\n",getpid(),getppid());sleep(1); }return 0;
}

第一行数据是该进程的PIDPPID,第二行数据是代码中通过调用fork函数创建的子进程的PIDPPID。其中该进程的PID就是子进程的父进程PID,所以我们可以说这两个进程是父子关系。而该进程的父进程就是bash,一般而言,在命令行上运行的指令,父进程基本都是bash

并且值得注意的是:在子进程被创建之前的代码被父进程执行,而子进程被创建之后的代码,则默认情况下父子进程都可以执行。父子进程虽然代码共享,但是父子进程的数据各自开辟空间(采用写时拷贝)。

注意:使用fork函数创建子进程后就有了两个进程,这两个进程被操作系统调度的顺序是不确定的,这取决于操作系统调度算法的具体实现。

2.3 fork的返回值

因为父子进程代码共享,而且fork函数在执行return语句时已经创建好了子进程,所以return语句会被父子进程执行两次,所以fork函数肯定有两个返回值。

  1. 如果子进程创建成功,在父进程中返回子进程的PID,而在子进程中返回0。
  2. 如果子进程创建失败,则在父进程中返回 -1。

所以我们可以通过fork的返回值,让父子进程分别去执行不同的过程。代码示例如下:

#include<stdio.h> 
#include<unistd.h>      
int main()    
{    pid_t id = fork();    if(id == 0)    {    //child    while(1){printf("I am child PID:%d PPID:%d\n",getpid(),getppid());sleep(1);}}    else if (id > 0)    {    //parentwhile(1){printf("I am parent PID:%d PPID:%d\n",getpid(),getppid());sleep(1);}}    else    {     //fork error}    return 0;    
} 

并且进程之间具有独立性,即使一个进程中途异常退出,也不会影响其他进程。

3. 进程状态

Linux中,存在大量进程,有些进程可能在运行,有些进程可能在休眠。为了方便操作系统管理,我们需要对进程的状态进行表示。

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 *task_state_array[] = {"R (running)",       /*  0*/"S (sleeping)",      /*  1*/"D (disk sleep)",    /*  2*/"T (stopped)",       /*  4*/"T (tracing stop)",  /*  8*/"Z (zombie)",        /* 16*/"X (dead)"           /* 32*/
};

我们可以使用指令ps aux 或者 ps axj 查看对应的进程状态。

3.1 运行状态-R

**R运行状态(running) **: 运行状态不一定占用CPU,并不意味着进程一定在运行中,一个进程处于R状态,它只是表明进程要么是在运行中要么在运行队列里,随时可以被CPU调度 也就是说,可以同时存在多个处于R状态的进程。

比如如下所有处于运行队列的进程都处于运行状态。

画板

3.2 浅度睡眠状态-S

S浅睡眠状态(sleeping): 当需要完成某种任务,而条件不具备时,需要进程进行某种等待,此时的状态就是浅睡眠状态。

比如一下程序:

#include<stdio.h>
#include<unistd.h>
int main()
{printf("I am running\n");sleep(100);return 0;
}

浅度睡眠状态可以用kill指令杀死对应进程。

3.3 深度睡眠状态-D

D深度休眠状态(Disk sleep):有时候也叫不可中断睡眠(深度睡眠)状态,在这个状态的进程通常会等待IO的结束。

例如,某一进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。

3.4 停止状态-T

T停止状态(stopped):可以通过发送SIGSTOP信号来停止进程,这个被暂停的进程可以通过发送SIGCONT 信号让进程继续运行。

3.5 僵尸状态-Z

Z僵尸状态(Zombies):当一个进程退出时,该进程曾经申请的资源并不是立即被释放,而是要暂时存储一段时间,以供操作系统或是其父进程进行读取,如果退出信息一直未被读取,则相关数据是不会被释放掉的。一旦进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态。

3.6 死亡状态-X

X死亡状态(dead):当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了。同样因为不存在了,我们也无法观察到死亡状态。

4. 僵尸进程与孤儿进程

4.1 僵尸进程

处于僵尸状态的进程,我们就称之为僵尸进程。

例如,以下这段代码,fork函数创建的子进程在打印3次信息后会退出,而父进程会一直打印信息。也就是说,子进程退出了,父进程还在运行,此时父进程没有读取子进程的退出信息,那么此时子进程就进入了僵尸状态。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {pid_t id = fork();if (id == 0) {// childint count = 3;while (count > 0) {printf("I am child,PID:%d PPID: %d\n", getpid(), getppid());sleep(2);count--;}printf("I am child, I quit\n");} else if (id > 0) {// parentwhile (1) {printf("I am parent,PID:%d PPID:%d\n", getpid(), getppid());sleep(2);}} else {// fork errorprintf("fork error\n");}return 0;
}

运行开始我们可以通过该脚本指令检测:while :; do ps axj | head -1 && ps axj | grep pro.out | grep -v grep;echo "######################";sleep 1;done

如果僵尸进程一直不回收,子进程就会一直处于僵尸状态,而维护子进程系统会创建对应PCB,进而造成系统资源的浪费。并且随着僵尸进程的增多,实际使用的资源就会越少,会造成严重的内存泄漏问题。

4.2 孤儿进程

若子进程先退出而父进程没有对子进程的退出信息进行读取,那么我们称该进程为僵尸进程。但若是父进程先退出,那么此时子进程没有父进程对其进行处理,此时该子进程就称之为孤儿进程。若是一直不处理孤儿进程,那么孤儿进程就会一直占用资源,造成内存泄漏。因此,当出现孤儿进程的时候,孤儿进程一般会被1号init进程领养。

比如说下面这段代码,fork函数创建的子进程会一直打印信息,而父进程在打印3次信息后会退出,此时该子进程就变成了孤儿进程。

5. 进程的优先级

5.1 优先级的概念

优先级实际上就是获取某种资源的先后顺序,而进程优先级实际上就是进程获取CPU资源分配的先后顺序,就是指进程的优先权(priority),优先权高的进程有优先执行的权力。

优先级存在的主要原因就是资源是有限的,一个CPU一次只能跑一个进程,而进程是可以有多个的,所以需要存在进程优先级,来确定进程获取CPU资源的先后顺序。

5.2 查看优先级

我们可以通过指令ps -l查看进程的优先级。

  • PRI:代表这个进程可被执行的优先级,其值越小越早被执行。在Linux操作系统当中,PRI(old)默认为80,即PRI = 80 + NI
  • NI:代表这个进程的nice值。NI的取值范围是-20至19,一共40个级别。

5.3 修改优先级

因为PRI = 80 + NI,所以我们只需要修改NI值就能达到修改优先级的目的。

修改NI值一共有两种方法:

第一种就是输入top指令,然后按下R键输入要修改进程的PID,最高输入要修改的NI值。比如下列我们将进程a.outNi修改为10,其优先级PRI变为90。

第二种就是使用renice指令,语法为renice NI PID。比如说我们下面将a.outNI修改为15,其优先级PRI变为95。

其中无论哪种方法,如果想将NI值调为负值,也就是将进程的优先级调高,都需要使用sudo提升权限。

6. 进程调度队列

我们以Linux 2.6版本为例,详细谈一谈进程调度队列。

画板

  • active指针:永远指向活动队列。
  • expired指针:永远指向过期队列。
  • nr_active:代表总共有多少个运行状态的进程。
  • queue[140]:前面说到nice值的取值范围是-20~19,共40个级别,依次对应queue当中普通优先级的下标100~139,相同优先级的进程按照FIFO规则进行排队调度。而下标0~99对应的实时进程,实时进程是指先将一个进程执行完毕再执行下一个进程,现在基本不存在这种机器了,所以对于queue当中下标为0~99的元素我们不关心。
  • bitmap[5]:这是一个位图,queue数组当中一共有140个元素,即140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5 × 32个比特位表示每个队列是否为空。

所以调度过程如下:

  1. 首先从0下标开始遍历活动队列queue[140]
  2. 选中队列的第一个非空进程即优先级最高的基础,开始运行,调度完成后放入过期队列。
  3. 继续选中队列的第二个非空进程进行调度,直到所有活动队列都被调度,即bitmap[5]等于0。
  4. 如果活动队列全部被调度完毕,交换将active指针和expired指针的内容,让过期队列变成活动队列,活动队列变成过期队列,这样就又有了一批新的活动进程,如此循环进行即可。
  • 如果是新创建的进程则放入过期队列。

这篇关于[Linux]:进程(上)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1145180

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级

【Linux】应用层http协议

一、HTTP协议 1.1 简要介绍一下HTTP        我们在网络的应用层中可以自己定义协议,但是,已经有大佬定义了一些现成的,非常好用的应用层协议,供我们直接使用,HTTP(超文本传输协议)就是其中之一。        在互联网世界中,HTTP(超文本传输协议)是一个至关重要的协议,他定义了客户端(如浏览器)与服务器之间如何进行通信,以交换或者传输超文本(比如HTML文档)。

如何编写Linux PCIe设备驱动器 之二

如何编写Linux PCIe设备驱动器 之二 功能(capability)集功能(capability)APIs通过pci_bus_read_config完成功能存取功能APIs参数pos常量值PCI功能结构 PCI功能IDMSI功能电源功率管理功能 功能(capability)集 功能(capability)APIs int pcie_capability_read_wo