【Linux】进程初阶(1)——基本进程理解

目录

前言

1.1进程基本理解

1.2进程描述

1.3查看进程

1.4通过系统调用的基本进程操作

1.4.1通过系统调用获取pid

1.4.2通过系统调用创建进程


前言

操作系统对计算机软件与硬件进行管理的方式是先描述再组织,而PCB就是那个"描述",那到底什么是PCB?PCB又在描述什么呢?更多Linux学习内容看准Linux专栏


1.1进程基本理解

在操作系统中,我们运行的一个个软件本质上都是程序。例如我们在windows上打开浏览器时,本质是运行一个.exe的可执行文件。当程序在计算机上动态运行时,操作系统为了方便管理程序的运行,会用一个叫进程控制块(Process Control Block)的数据结构存储管理程序运行时所需要的属性信息,再用链表将PCB存储起来,进而对进程进行管理,以此做到"先描述,再组织"。因此可以认为进程就是PCB数据结构+程序本身的代码和数据。

这里需要注意的是单一的程序不能被称为进程,当程序未被执行时本质还是普通文件中的一个数据。只有当程序被执行,加载到计算机内存中,操作系统能通过PCB对执行程序进行控制时,才能被称为进程。

1.2进程描述

对进程进行描述也就是记录进程信息是进程控制块(PCB),PCB数据结构中存储的信息就是进程的属性信息。在不同操作系统中对应的PCB数据结构会有不同,而在Linux中PCB数据结构对应的是task_struct结构体。

task_struct结构体本质是PCB的一种,是Linux内核中的一种数据结构,它会被加载到内存中并且包含Linux操作系统中进程的信息。

task_struct基本内容简介

• 标示符(Process Identifier):描述本进程的唯⼀标示符,⽤来区别其他进程。

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

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

• 程序计数器: 程序中即将被执⾏的下⼀条指令的地址。

• 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

• 上下⽂数据: 进程执⾏时处理器的寄存器中的数据。

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

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

• 其他信息

• 具体详细信息后续会介绍

进程标示符与进程状态在后文会详细展开,下面简单介绍后面内容。

优先级:进程在运行时需要操作系统对其分配硬件资源(内存,CPU等),进程优先级就代表了分配资源的优先级。优先级主要涉及后续进程调度部分。

程序计数器:程序计数器本质是一个寄存器,用来指示程序下一步应该执行哪里。简单举例来说,若一个程序有500行,程序计数器记录程序已经执行到哪一行,进而计算机通过程序计数器来判断下一步程序应该执行哪一行。后面主要与进程切换有关。

内存指针:储存程序运行时的地址,方便操作系统对程序进行管理。

记账信息:类似于表示进程已经调度的时间。

后续更深入的内容会在各部分单独讲解。

1.3查看进程

在查看进程之前,我们需要获取到我们想要查看的那个进程的标识符(pid),pid作为描述进程的唯一标识符,用来区分其他进程。在获取到pid之后,我们可以查看在/proc文件夹下对应pid进程的进程信息。例如我们要查看pid为1的进程信息:

bash 复制代码
ls /porc/1

当然我们也可以查看/proc文件夹下所有的进程信息(所有正在运行的程序)

bash 复制代码
ls /proc/

1.4通过系统调用的基本进程操作

1.4.1通过系统调用获取pid

系统调用简而言之就是操作系统内置的库函数,最基本的,我们可以通过系统调用来获取进程的pid。Linux获取pid的库函数是getpid,可以通过man手册来查看其api。

bash 复制代码
man getpid

可以看到getpid的返回类型为pid_t,不需要参数。这时通过自己编写程序来验证getpid是否真的能获取到pid:

cpp 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
    while(1)
    {
        pid_t pid = getpid();
        printf("this is a process and pid is : %d\n", pid);
        pid_t ppid = getppid();
        printf("this is a process and ppid is : %d\n", ppid);
        sleep(1);
        //fflush(stdout);
    }

    return 0;
}

这里通过死循环让程序一直保持运行状态,即进程运行状态,随后打印该进程的pid。来看看运行结果

可以看到该程序进程的pid是21718,随后再看看程序运行过程中用proc了查看该进程信息

可以看到这时是可以查看到的,那如果停止程序运行呢?

可以看到程序停止运行之后该pid进程就没有了,说明getpid获取的就是当前程序的pid。

而当我们用man手册了查询pid时,可以看到还有一个叫getppid的函数。ppid同样也是task_strcut结构体中储存的信息之一,ppid代表父进程的pid,也就是当前程序父进程的标识符。

pid与ppid的用法类似:

cpp 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
    while(1)
    {
        pid_t pid = getpid();
        printf("this is a process and pid is : %d\n", pid);
        pid_t ppid = getppid();
        printf("this is a process and ppid is : %d\n", ppid);
        sleep(1);
        //fflush(stdout);
    }

    return 0;
}

1.4.2通过系统调用创建进程

此时可以看到该进程的父进程pid就是26918,那到底什么是父进程呢?

我们先来看看pid为26918的进程是什么:利用ps -p <PID> -o comm命令可以查看该pid进程在系统中的命名

bash 复制代码
ps -p 26918 -o comm

可以看到pid为26918的进程在系统中叫作bash,bash在Linux系统中就是我们看见的命令行提示器,就是Linux系统的shell外壳。也就是说我们自己程序创建的进程的父进程就是bash,当我们在bash中运行程序编译好的可执行文件时,同时也创建了新的进程,我们自己程序的进程就成为了bash的子进程。

所以当一个进程在运行过程中创建了一个新的进程时,这个新进程就是原进程的子进程,原进程就是新进程的父进程。那在Linux中如何创建进程呢?

创建进程属于系统层面的操作,因此需要系统调用。在Linux中创建子进程的系统调用函数为fork:

先不管其他的,我们先来创建一个子进程看一看:

cpp 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
    pid_t pid1 = getpid();
    printf("原pid为%d\n", pid1);

    fork();

    pid_t pid2 = getpid();
    pid_t ppid = getppid();

    while(1)
    {
        printf("这个进程的pid为:%d,ppid为:%d\n", pid2, ppid);
        sleep(2);
    }

    return 0;
}

运行结果:

可以看到这里就是创建子进程成功了,再进一步了解fork:

这里for返回的是pid_t类型,但如果创建子进程成功父进程就是返回子进程的pid,而子进程则返回0;若失败则返回-1。先来验证下正确性:

cpp 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
    pid_t pid1 = getpid();
    printf("原pid为%d\n", pid1);

    pid_t id = fork();

    while(1)
    {
        if(id == -1)
        {
            perror("fork faild!");
            break;
        }
        else
        {
            if(id == 0)
            {
                printf("这是子进程,pid:%d\n", getpid());
            }
            else
            {
                printf("这是父进程,pid:%d\n", getpid());
            }
        }

        sleep(1);
    }

    return 0;
}

运行结果:

可以看到确实是这样的,那么这样fork就衍生出三个问题:

fork三问题

1.为什么给子进程返回的是0,给父进程返回的是子进程的id?

2.为什么fork作为一个函数能返回两个值?

3.为什么id仅仅一个变量,能接收两个值?

对于问题1:在更为复杂的系统工程项目中,有时需要创建多个子进程,为了方便对个子进程进行管理,因此父进程需要得到创建的子进程的id;而每个子进程只会有一个父亲,因此只需要一个0。

对于问题2:函数在进行返回时,基本功能已经完成。对应fork函数,在return之前子进程已经创建,因此return实际返回的不是两个值,而是在两个进程中个返回的一个值。

对于问题3:主要涉及虚拟内存部分的写时拷贝,更深入内容在后续讲解。

相关推荐
我想吃余3 小时前
Linux进程间通信:管道与System V IPC的全解析
linux·服务器·c++
紫荆鱼3 小时前
设计模式-备忘录模式(Memento)
c++·后端·设计模式·备忘录模式
egoist20233 小时前
[linux仓库]打开线程的“房产证”:地址空间规划与分页式管理详解[线程·壹]
linux·页表·地址空间·分页式存储管理·缺页异常
Wind哥4 小时前
VS Code搭建C/C++开发调试环境-Windows
c语言·开发语言·c++·visual studio code
黄毛火烧雪下4 小时前
彻底掌握 CSS 定位:深入理解 relative、absolute、fixed 与 sticky 的原理与实战
1024程序员节
江拥羡橙4 小时前
css实现拼图样式,响应不同屏幕宽度
vue·less·css3·html5·1024程序员节·calc
周杰伦_Jay4 小时前
【Mac下通过Brew安装Ollama 】部署 DeepSeek 轻量模型(实测版)
人工智能·macos·数据挖掘·database·1024程序员节
啥都不懂的小小白4 小时前
PLSQL 连接本地 Oracle 数据库报 ORA-12514
oracle·plsql·1024程序员节
热心网友俣先生4 小时前
2025年第六届MathorCup大数据竞赛赛题浅析-助攻快速选题
1024程序员节