【Linux】初识进程

本文介绍了进程控制块PCB的概念,它是操作系统描述和管理进程的重要结构。在Linux中,PCB是通过task_struct结构体实现的,包含了进程的标识符、状态、优先级、程序计数器、内存指针等信息。文章还演示了如何使用ps指令查看进程,kill指令结束进程,以及如何通过系统调用获取进程ID。此外,讨论了通过fork函数创建子进程的过程。
摘要由CSDN通过智能技术生成

进程控制块的引入

首先,听到进程这个词肯定不模糊,

因为我们常用的Windows系统就有进程管理器,

可以用进程管理器随时挂掉一个进程。

但是,进程和我们常说的程序有什么区别呢?

很多地方都写到,进程狭义的定义就是加载到内存中运行起来的程序

image-20230105213046114

但真的这么简单吗?

程序中的数据会一五一十地照搬到内存中吗?

照搬到内存之后操作系统又是怎么对零散的数据和代码进行处理呢?

我同时运行十几个程序,

这么多零散的代码和数据操作系统又是怎么能精准管理的呢?

这就引入了一个非常重要的概念 —— 进程控制块(PCB - Process Control Block)


初识进程控制块(PCB - Process Control Block)

什么是PCB

为什么要有进程控制块,

很简单,因为操作系统要管理进程,

要管理就要先描述再组织,

进程控制块就是为了描述进程用的

就好比我们用C 语言写一个简单的学生管理系统,

是不是也要声明一个结构体来描述一个学生呢?

Linux是用C语言写的,

所以Linux下的进程控制块就是用C语言声明的结构体,

它就是task_struct

struct task_struct
{
    ///与进程相关的所有属性
}

这样操作系统就给每一份进程分配了一个PCB,

只需要将各个进程的PCB组织在一起,

对进程的管理就变成了对组织PCB的数据结构的管理。

首先先明确一下,一个进程最基本的要有哪些属性呢?

程序的代码和数据要加载到内存中,

要记录这些代码和数据的地址吧;

每个进程要区分一下吧,

要给每个进程编号并记录一下吧;

进程是在等待,还是在运行,还是在挂起,

要记录进程的状态吧;

这个进程是先执行还是后执行,

进程的优先级要记录吧;

进程执行到了哪里,下一条代码该执行什么,

这个也需要记录吧…

各种相关的属性集合在一起,加上程序本身加载到内存中的代码和数据,

就构成了一个完整的进程。

注意,这些属性在程序的代码和数据中包含吗?

对于大部分程序来说,答案肯定是否定的,

一个硬盘中的死程序怎么知道它的状态呢。

所以PCB的创建和组织是由操作系统来完成的

同样地,PCB实际上是操作系统内核提供的结构

所以一个完整的进程是什么呢,

内核数据结构 + 代码和数据


Linux下的PCB

上面提到,Linux下的PCB叫task_struct的结构体

那这个结构体存放了进程的什么信息呢?

标示符: 描述本进程的唯一标示符,用来区别其他进程。这个好理解,就好比学生管理系统中的每个学生都有一个学号来进行唯一标识,这里的标识符具体就是PID。

状态: 任务状态,退出代码,退出信号等。

优先级: 相对于其他进程的优先级。

程序计数器: 程序中即将被执行的下一条指令的地址。

内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。所以就可以通过内存指针找到进程对应的数据和代码。

上下文数据: 进程执行时处理器的寄存器中的数据。一个进程在执行的过程中难免会产生不少临时数据,而进程不是执行完这个就去执行下一个的,是并发运行的,因为对于优先级相同的进程要有相同的调度,不然相同优先级又何来相统一说呢?就比如A进程执行了10ms,执行完之后它再去后面排队,执行它后面的B进程,执行完10ms之后B进程跑到后面排队,依次往下来......既然进程不是一次就执行完的,它产生的临时数据也是要记录的,所以上下文数据其实就是指向的这一部分。

I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。

记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

其他信息

需要注意,task_struct中的内容远不止于此,

这里只是挑重点的浅浅认识了一下,

如果有兴趣的话可以参考一下大佬的博客:Linux进程管理之task_struct结构体 - zxiaocheng - 博客园 (cnblogs.com)


初见进程

既然对进程有一个大概的轮廓了,

怎么查看一个进程呢?

ps指令查看进程

Linux下可以通过一个简单的指令来查看:

ps axj ,用于查看所有进程的信息:

image-20230106165747987

当然也可以通过grep命令加管道来查看一些具体的信息,

比如我写了一个C语言代码proc.c,

将其编译形成一个可执行程序proc,

程序运行起来成了进程,

就可以用下面的这条命令来查看:

ps axj | grep "proc"

image-20230106171803655

当然如果想显示各个属性的名称还可以再加一句:

ps axj | head -1 & ps axj | grep "grep"

其中head -1是提取文本的第一行,

也就是各个属性的名称:

image-20230106172248489

其中PPID是父进程的ID

PID是当前进程的ID

TTY是进程运行的终端

STAT是当前进程的状态

UID是当前用户的ID

进程在运行的过程中就有了好多属性,很显然这些属性不是一成不变的,

所以进程在调度运行的时候,就具有了动态属性


kill指令挂掉进程

我写的这个代码是一个死循环。

如果是前台运行,可以直接给它用Ctrl C挂掉,

当然,拿到它的PID之后,可以直接用kill命令挂掉:

kill -9 PID

image-20230106173102592

kill命令的好多选项如下,这里不做解释:

image-20230106174603979


通过系统调用接口得到进程的ID(进程标识符)

我们也可以通过系统调用接口在程序内部查看进程的PID(getpid)或PPID(getppid):

image-20230106173802265

image-20230106174006801

image-20230106174131710

相应的还有PPID

PPID就是父进程的PID,没什么好说的,

不过需要注意,从命令行打开的进程,

它的父进程就是命令行bash

image-20230106222149853


从根目录下的proc文件查看进程

执行指令ls /proc

image-20230106175845190

看到了好多数字命名的目录,

而这些目录其实就是进程的PID,

也就是说,一个进程的属性也是数据,

这个数据存在了内存中:

image-20230106180000123

我们可以看到有一个exe文件,

它就指向了进程对应的硬盘上的可执行程序,

当进程挂掉之后,

对应的目录也就被清理掉了:

image-20230106180240627

这里做个小实验;

当一个进程在运行的时候,我们把磁盘上对应的可执行程序删掉,这个进程还能正常运行吗?

初始进程1

可以看到,硬盘中的可执行程序删掉之后进程还在运行,

只是进程对应的目录下的exe指向变成了红色并标注了deleted

所以对于部分进程,

并不依赖硬盘中对应的可执行程序,

当然这个结论并不严谨,

具体问题还需具体分析。


通过fork函数创建子进程

首先认识一下fork函数:

NAME
       fork - create a child process

SYNOPSIS
       #include <unistd.h>
       pid_t fork(void);

RETURN VALUE
       On success, the PID of the child process is returned in the parent, and 0 is returned in the child.  On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately.

这里只是浅浅地见识一下,关于具体细节不做讨论。

fork 如果成功创建子进程的话,

会给子进程返回0,给父进程返回子进程的PID。

如果创建失败的话就给父进程返回-1。

那么看下面的代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        printf("我是子进程, pid:%d, ppid:%d\n", getpid(), getppid());
        sleep(1);
    }
    else if (id < 0)
    {
        perror("fork");
        sleep(1);
    }
    else
    {
        printf("我是父进程, pid:%d, ppid:%d\n", getpid(), getppid());
    }
    return 0;
}

运行结果如下;

image-20230107151940133

简单理解一下,子进程创建成功后,

会和父进程一样执行后续的代码,

数据在不修改的情况下会共用。

所以 fork 之后就有了两个进程,

两个进程的 id 不同,

但都会执行下面的 if-else 语句,

所以就有了不同的结果。

至于为什么会这样,不是这里的重点,

这里只是浅浅地认识一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LeePlace

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值