进程概念、PCB及进程查看

news/2025/2/25 0:29:59

文章目录

  • 一.进程的概念
    • 进程控制块(PCB)
  • 二.进程查看
    • 通过指令查看进程
    • 通过proc目录查看
    • 进程的`cwd`和`exe`
    • 获取进程pid和ppid
    • 通过fork()创建子进程

一.进程的概念

进程是一个运行起来的程序,而程序是存放在磁盘的,cpu要想执行程序的指令,需要先将程序加载到内存中。

课本概念:进程是被加载到内存运行的程序
内核观点:担当分配系统资源(CPU时间,内存)的实体。

操作系统中有着大量的进程,操作系统作为管理者,管理的其实是大量进程相关的数据,那么如何管理这些数据呢?

先描述,再组织

当二进制代码直接加载到内存时,操作系统为了更好地管理加载的程序,创建了描述该进程的数据结构。这样,操作系统只用看这个数据结构,不用管各种复杂多样的二进制代码,并且将它们组织起来进行管理

进程控制块(PCB)

这个数据结构叫PCB(process control block),进程信息被放在其中,可以理解为进程属性的集合,在linux的PCB是task_struct

struct task_struct {
 volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
 struct thread_info *thread_info;
 atomic_t usage;
 unsigned long flags; /* per process flags, defined below */
 unsigned long ptrace;

 int lock_depth;  /* Lock depth */

 int prio, static_prio;
 struct list_head run_list;
 prio_array_t *array;
 //.....
}

当有一个程序被加载到内存时,操作系统会为该进程在内存中创建一个task_struct类型的对象,并将该进程放入双链表等其他结构中。这样,操作系统对进程的管理就变为操作系统对PCB的管理,再变为操作系统对双链表等结构的增删查改等操作

由此可以总结:进程 = 内核数据结构(PCB等)+ 可执行程序(代码+数据)

二.进程查看

通过指令查看进程

为了让进程能够一直运行方便观察,写一个死循环程序,让其每隔1秒钟打印一句话。

#include <stdio.h>
#include <unistd.h>
 
int main()
{
    while(1){
        printf("It's a process.\n");
        sleep(1);
    }
    return 0;
}

随后运行它,此时该程序变成了一个进程:
在这里插入图片描述

接着就可以用ps指令查看进程信息,同时配合grep进行抓取

ps ajx | grep myprocess

得到以下结果:
在这里插入图片描述

可以看到系统中关于myprocess的进程一共有两个,第一行是我们写的运行的程序,第二行是grep命令进行抓取的进程。展示了各种信息:PPID、PID、PGID等等,这些就是PCB的一部分。
注意:task_struct是内核数据结构,查看进程信息读取该数据,必须要通过系统调用。

通过proc目录查看

proc是一个目录,里面存放当前系统实时的 进程信息
ls /proc
在这里插入图片描述

这里的数字就是进程的PID,由于此时已经将myprocess进程停止,此目录并没有找到名为167647的目录。
但是,仔细看,却有165058,这是刚才myprocess的父进程ID即PPID,通过指令可以知道,该进程其实就是bash
在这里插入图片描述

再次运行myprocess,并且通过指令得到其PID,进入该文件夹,可以发现进程的数据显式存在文件中。

在这里插入图片描述

进程的cwdexe

查看该目录详细信息,有两个文件很瞩目
在这里插入图片描述

cwd: Current Work Directory 指出该进程当前工作路径
exe: 指出该进程可执行程序的磁盘文件

修改程序,添加一个fopen函数

#include <stdio.h>
#include <unistd.h>
 
int main() 
{
    FILE* fp = fopen("1.txt", "w");  // 若不存在就创建
    while (1) 
        {
        printf("It's a process.\n");
        sleep(1);
    }
}

在这里插入图片描述

这恰好就是cwd链接的目录,说明fopen使用了查看cwd的系统调用。


再看exe,此时进程运行中,直接删除其链接在磁盘中的文件,发现进程没有终止,停止进程再运行显然就会失败了。
在这里插入图片描述

运行程序,本质就是将其从磁盘拷贝至内存中,进程与其磁盘上对应程序没有直接关系。

获取进程pid和ppid

可以直接通过系统调用getpid()getppid()得到当前进程的pid和ppid(父进程的pid),返回值为pid_t类型,底层就是整数。

运行以下代码

#include <stdio.h>
#include <unistd.h>

int main()
{
    while (1)
    {
        printf("It's a process.\t");
        printf("pid:%d, ppid:%d\n",getpid(), getppid());
        sleep(1);
    }
    return 0;
}

可以看到打印出当前进程的pidppid
在这里插入图片描述

通过ps axj | head -1; ps axj | grep 184670进行验证,当前进程是./myprocess且其父进程是bash

在这里插入图片描述

通过fork()创建子进程

通过man指令查看fork()函数细节
在这里插入图片描述

fork()函数可以创建子进程,创建成功后父子进程代码共享。
若成功创建,子进程的pid返回给父进程,0返回给子进程;
若失败,-1返回给父进程,没有子进程。

代码共享可以通过以下代码得到验证

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
 
int main() 
{
    printf("before\n");
    fork();
    printf("Hello, pid:%d\n", getpid());
}

fork()之前的代码只执行了一次,之后的代码执行了两次,这两次分别是两个进程执行的。
在这里插入图片描述


创建父子进程是为了做不同的事情,一般是通过if/else来进行分流达到的,这恰恰用到了fork()有两个返回值的特点,下面的代码若是初见一定会迷惑。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
 
int main() 
{
   pid_t id = fork();

   // id: 0-子进程 >0-父进程
   if (id == 0)
   {
        while(1)
        {
            printf("child process, pid: %d, ppid: %d", getpid(), getppid());
            sleep(1);
        }
   }
   else
   {
    while(1)
        {
            printf("father process, pid: %d, ppid: %d", getpid(), getppid());
            sleep(1);
        }
   }
}

利用父子进程fork()返回值不同,达到两个死循环都在不断执行的效果:
在这里插入图片描述

通过指令查看,确实两个进程是父子进程关系:

在这里插入图片描述


下面来简要分析上面的情况,具体细节会在之后进程地址空间部分详谈。

  1. 为什么两个死循环会同时执行❓

上节讲过,进程 = 内核数据结构(PCB等)+ 可执行程序(代码+数据)。通过fork()创建子进程,肯定也要给子进程创建一个独立的task_struct,而其代码和数据指向了父进程接下来的代码和数据。子进程的大部分属性值也是由父进程拷贝而来,修改前地址不会改变。
在CPU角度,它不会管谁是父进程,谁是子进程,会在操作系统的管理下并发执行。在我们的视角下,两个死循环同时执行了。

  1. 为什么fork()返回值如此设计❓

父与子的关系是一对一或者一对多的。这样的关系导致父找子并不容易,所以创建子进程成功后需要把子进程的pid返回给父进程,方便父进程控制子进程。
而子找父是很容易的,通过系统调用getppid()即可。

  1. 为什么fork()会返回两次值❓

fork()之前只有父进程,即只有父进程才能调用fork()fork()内部在return之前肯定已经将子进程创建成功,又子进程和父进程在创建成功后代码共享,那么子进程和父进程都会执行return这条语句,这也就是为什么fork()会返回两次值。

  1. 同一个变量id怎么会既大于0,又等于0❓

进程之间具有独立性,一个进程崩溃了,不会影响另一个进程。这里的id是父子进程的共享数据,若父子进程对共享数据有写操作,这时操作系统会将该数据拷贝两份,这就是写时拷贝。那么此时,虽然这是同一个变量名,但实际上表示的是不同的值,那么id出现两种情况也就不足为奇了,实际在底层的空间根本就不是一个。


http://www.niftyadmin.cn/n/5864882.html

相关文章

#渗透测试#批量漏洞挖掘#Progress Software Flowmon命令执行漏洞(CVE-2024-2389)

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

二:前端发送POST请求,后端获取数据

接着一&#xff1a;可以通过端口访问公网IP之后 二需要实现&#xff1a;点击飞书多维表格中的按钮&#xff0c;向服务器发送HTTP请求&#xff0c;并执行脚本程序 向服务器发送HTTP请求&#xff1a; 发送请求需要明确一下几个点 请求方法&#xff1a; 由于是向服务器端发送值…

即插即用Transformer、扩散模型、机器人规划、长文本检索增强生成 | Big Model Weekly 第57期...

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 01 ProTransformer: Robustify Transformers via Plug-and-Play Paradigm 近年来&#xff0c;基于Transformer的架构在机器学习的各个领域占据了主导地位。本文介绍了一种新颖的鲁棒性注意力机制&#xff0c;旨…

【论文阅读】SAM-CP:将SAM与组合提示结合起来的多功能分割

导言 近年来&#xff0c;视觉基础模型的快速发展推动了多模态理解的进步&#xff0c;尤其是在图像分割任务中。例如&#xff0c;Segment Anything模型&#xff08;SAM&#xff09;在图像Mask分割上表现出色&#xff0c;但在语义及实例分割方面仍存在局限。本文提出的SAM-CP&am…

Spring5框架八:整合Mybatis

精心整理了最新的面试资料&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 1、导入相关的jar包 <dependencies><!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --><dependency><groupId>…

java后端开发day19--学生管理系统升级

&#xff08;以下内容全部来自上述课程&#xff09; 1.要求及思路 1.总体框架 2.注册 3.登录 4.忘记密码 2.代码 1.javabean public class User1 {private String username;private String password;private String personID;private String phoneNumber;public User1() {}…

【多语言生态篇六】【DeepSeek×Node.js:WebSocket实时服务从入门到“超神“指南(史诗级万字拆解)】

各位WebSocket老司机们系好安全带!今天我们要把《DeepSeekNode.js:WebSocket实时服务》这本"武功秘籍"揉碎了掰开了讲。从单机玩具到百万级并发,从握手原理到熔断限流,咱们边撸代码边开车,全程高能无尿点! 一、开篇暴击:WebSocket的"真香"现场 先来…

深度学习入门--神经网络

初学&#xff0c;若有错误&#xff0c;恳请指正。 目录 初学&#xff0c;若有错误&#xff0c;恳请指正。 3.1 从感知机到神经网络 3.1.1 神经网络的例子 3.1.2 复习感知机 3.1.3 激活函数登场 3.2 激活函数 3.2.1 sigmoid 函数 3.2.2 阶跃函数的实现 3.2.3 阶跃函数…