Linux进程概念(PCB和fork)

基本概念

课本概念:程序的一个执行实例,正在执行的程序等

课本概念:担当分配系统资源(CPU时间,内存)的实体。

写完代码,把代码进程编译链接形成的可执行程序,这个程序本质上是一个文件,放在磁盘上的。当我们双击这个可执行程序将它运行起来时,本质是将它加载到内存里,只有加载到内存里,CPU才能对它进行逐行语句执行。一个程序加载到内存里,我们不应该将这个程序叫做程序,严格意义上应该叫做进程。

描述进程-PCB

 

进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
 
当程序加载到内存变成进程时,操作系统要对这个进程进行管理,操作系统如何对进程进行管理呢?
六字真言: 先描述,在组织。当一个进程出现时,操作系统会对进程进行描述,之后对进程的管理实质上是对描述信息的管理。进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合,课本上成为PCB(process control block)。
 
操作系统将每一个进程都进行描述,组织形成了一个个的进程控制块(PCB),并将这些PCB以双链表的形式组织起来。如果愿意,你可以放在任意一个数据结构(列如:vector,stack等)。
 
5f761c04528e47f1acca627335b82616.png
 
这样一来,我们对进程的操作就只是对这个双链表的操作。
例如:创建一个进程,进程加载到内存上,对其描述,组织成一个PCB,并把这个PCB插入到该双链表当中,退出程序就是把PCB从双链表中删除,然后操作系统再将进程占的资源进行释放。
总的来说,操作系统对进程的管理实际上就变成了对该双链表的增、删、查、改。 
 

task_struct-PCB的一种

进程控制块(PCB)是描述进程的,在C++当中我们称之为面向对象,而在C语言当中我们称之为结构体,Linux操作系统是用C语言进行编写的,那么Linux当中的进程控制必定是用结构体来实现的。

  • 在Linux中描述进程的结构体叫做task_struct。
  • task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息

task_ struct内容分类

task_strcut当中主要包含以下信息:

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

查看进程

通过系统目录查看

进程的信息可以通过 /proc 系统文件夹查看
7a510fd0f3ff4b1fae874eeea4c41bef.png
 

通过ps命令查看

ps aux

e38a32be1ac5467e8d74ee6c0aa7cfc5.png
ps命令与grep命令搭配使用,即可只显示某一个进程信息。
ps aux | grep test | grep -v grep

da0a09f982ca49e49fe1e7427301c9af.png

通过系统调用获取进程标示符

可以通过系统函数,getpid()和getppid()分别获取进程PID和PPID。

3a92f92f30d6448592d6def2a8ea70d3.png

getpid()函数用于获取当前进程的进程 ID(Process ID)。每个正在运行的进程都有一个唯一的进程 ID 来标识它。

getppid()函数用于获取当前进程的父进程 ID(Parent Process ID)。一个进程是由另一个进程创建的,创建它的进程就是父进程。

我们可以通过一段代码来进行测试。

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

运行当前代码就可以打印该进程的PID和PPID

c98e95d4665a41e592cd6bb940bae9c6.png

我们可以通过ps命令查看当前进度的信息,可以得到当前进程的PID和PPID,发现和系统调用函数getpid()和getppid()所获得的值相同。

6134e39772ce4f8bb46f690428cbec67.png

通过系统调用创建进程-fork初识

fork函数创建子进程

fork是一个系统级调用函数,其作用就是在当前进程创建子进程

通过 man 2 fork 查看函数文本

e48be113addf4f849751399f5c04c8e4.png

当调用 fork 系统调用时,会返回两个值:

  1. 在父进程中,fork 返回新创建的子进程的进程 ID(PID)。通过这个返回值,父进程可以识别和管理子进程。例如,父进程可以根据子进程的 PID 等待子进程结束、检查子进程的状态等。
  2. 在子进程中,fork 返回 0。子进程可以通过这个返回值判断自己是子进程,从而执行与父进程不同的代码逻辑。例如,子进程可以根据这个返回值执行特定的任务,而父进程可以执行其他任务

代码演示

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
    int ret = fork();
    printf("hello proc : %d!, ret: %d\n", getpid(), ret);
    sleep(1);
    return 0;
}

254058b51b274418afe6074f6780a626.png

运行结果可以看出,printf被执行了两次,第一个是父进程执行,第二个是子进程执行,由此可以看出,fork会返回两个值,一个是子进程的PID一个是0。

fork 之后通常要用 if 进行分流

用if分流,让父子进程做不同的任务。如果让父子进程做同一个任务,创建多进程的意义就不大了。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
    int ret = fork();
    if (ret < 0)
    {
        perror("fork");
        return 1;
    }
    else if (ret == 0)
    { // child
        printf("I am child : %d!, ret: %d\n", getpid(), ret);
    }
    else
    { // father
        printf("I am father : %d!, ret: %d\n", getpid(), ret);
    }
    sleep(1);
    return 0;
}

37f2350073804480be1c7dc2e1beff11.png

当调用fork时,操作系统首先为新的子进程分配一块新的内存空间。这个空间的大小通常与父进程的地址空间大小相同。

然后,操作系统会逐页地复制父进程的地址空间内容到子进程的地址空间中。这个过程并不是立即复制所有的页面,而是采用一种称为 “写时拷贝”的技术。这意味着在子进程实际修改某一页之前,父子进程共享相同的物理页面。只有当子进程试图修改一个页面时,操作系统才会为子进程创建一个新的物理页面,并将父进程的页面内容复制到新页面中,然后子进程可以对新页面进行修改。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值