Linux系统编程之进程概念

注:本文中的部分图片来自互联网。如果有侵权行为,请通知我们删除。

[En]

Note: some of the pictures in this article come from the Internet. If there is any infringement, please inform us to delete it.

1. 什么是进程?

在我们理解过程的概念之前,我们需要了解程序的概念。

[En]

Before we can understand the concept of process, we need to know the concept of program.

程序指的是位于磁盘上且不占用系统资源的已编译二进制文件。

[En]

A program refers to compiled binary files that are on disk and do not take up system resources.

进程,指的是一个程序的执行实例,是操作系统分配系统资源的单位,这里的系统资源有CPU时间,内存等。当程序运行起来,产生一个进程。

换句话说,与程序相比,过程是一个动态的概念。

[En]

In other words, compared to programs, process is a dynamic concept.

2. 用什么来描述进程?

进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。教材中称为PCB(process control block),不同的操作系统下有不同的PCB,Linux 下的进程控制块是 task_struct。

task_struct是Linux内核的一种数据结构,当一个进程创建时,系统会先将程序加载到内存,同时会将task_struct装载到内存中,在task_struct中包含着进程的信息。

task_struct的内容主要分为以下几类:

  • 标示符(PID) : 描述本进程的唯一标示符,用来区别其他进程,本质上是一个非负整数。
  • 进程状态: 任务状态,退出代码,退出信号等。
  • 上下文数据: 进程执行时处理器的寄存器中的数据。
  • 程序计数器: 程序中即将被执行的下一条指令的地址。
  • 文件描述符表,包含很多指向 file 结构体的指针。
  • 优先级: 相对于其他进程的优先级。
  • 其他信息。

3. PID、PPID

为了便于管理,在操作系统中引入了父子进程的概念。子流程继承父流程的属性和权限,父流程也可以对子流程进行系统管理。

[En]

In order to facilitate management, there is the concept of parent-child process in the operating system. The child process inherits the properties and permissions of the parent process, and the parent process can also systematically manage the child process.

进程的标志符是PID,是进程的唯一标识,而父进程的标志符是PPID。

要查看进程的父子关系,可以用命令 ps axj

我们在后台运行一个./test可执行文件,用如下命令查看该进程的父子信息

Linux系统编程之进程概念

可以看到,该进程的进程PID为7711,其父进程PPID为29455

要获取进程id和父进程id,可以使用getpid()和getppid()函数:

获取当前进程 ID pid_t getpid(void);

获取当前进程的父进程 ID pid_t getppid(void);

如运行如下代码后,可以输出该进程的id和父进程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;
}
</unistd.h></sys></stdio.h>

输出结果:

Linux系统编程之进程概念

4. fork函数

运行 man 2 fork后,可以看到 pid_t fork(void);

fork函数是用于创建子进程的一个函数,当父进程调用fork函数后,会创建一个子进程,父子进程代码共享,数据各自开辟空间。

一般情况下,fork之后通常要进行分流,如代码1

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

int g_val = 0;

int main()
{
    pid_t id = fork();
    if(id < 0){
        perror("fork fail");
        return 1;
    }
    else if(id == 0) {
        //child
        printf("g_val = %d,child_pid = %d , &g_val = %p\n",g_val,getpid(),&g_val);
    }
    else {
        //parent
        printf("g_val = %d,parent_pid = %d , &g_val = %p\n",g_val,getpid(),&g_val);
    }
    return 0;
}
</unistd.h></sys></stdio.h>

执行结果如下

Linux系统编程之进程概念

可以看出,分流之后,父进程执行的是id>0的代码,而子进程执行的是id == 0 的代码,也就是说,fork是有两个返回值的,如果子进程创建成功,fork给父进程返回的是子进程的PID,给子进程返回0。

需要注意的是,子进程执行的是fork之后的代码。这是为什么?

在父进程创建好子进程后,父子进程代码共有,父进程会将自己的数据拷贝给子进程,其中就包括了父进程程序计数器的值。程序计数器内存放的是程序中即将被执行的下一条指令的地址,由于父进程已经执行了fork前面的代码,因此子进程会和父进程一样,都执行fork之后的代码。

5. 进程的状态

当一个进程实体从磁盘加载到内存时,会创建对应的task_stuct,进程有不同的状态。在Linux中,所有运行在系统里的进程都以task_struct链表的形式存在内核里,根据状态的不同,可以将

task_struct中有关于进程状态的描述:

static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};

R状态:可执行状态,只有该状态的进程才可以上处理机运行。同一时刻可以有多个进程同时处于R状态,除了上处理机的进程外,其余R状态的进程以链表的形式组成队列,等待上处理机。在操作系统教材中的运行态和就绪态,在Linux中统一为R状态。

S状态:可中断睡眠状态,进程因为等待某些资源,而没有上处理机运行,该状态即S状态。当得到等待的资源,或者接收到某些异步信号时,进程将会被唤醒。一般情况下用ps命令查看进程状态,大多数进程都是S状态。

D状态:深度睡眠状态,该状态下不接受一些异步信号。该状态存在的原因是操作系统的某一些操作要求是原子操作,中间不可以接受其他异步信号的干扰,只要对应资源不得到满足,就一直处于D状态。例如, kill -9 也杀不死D状态的进程。而实际中,我们用ps命令几乎是无法捕捉到D状态的进程,因为原子操作往往比较短暂。

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

X状态:死亡状态,该状态是返回状态,在任务列表中看不到。

Z状态:僵尸状态,该状态是一个特殊的状态。当进程退出时,如果父进程没有读取到子进程退出的返回代码,就会产生僵尸进程。僵尸进程会一直以Z状态留在进程表中,等待父进程读取其退出状态。即便是退出状态的进程,本身也需要用PCB进行维护,也就是说,如果父进程不读取子进程的退出信息,子进程的PCB会一直在内存中,从而造成了内存泄漏。

除了僵尸进程,系统中还可能存在另外一种进程——孤儿进程。当父进程先退出时,子进程就成了孤儿进程,此时孤儿进程会被1号init进程领养,其PPID变为1。

6. 进程地址空间

我们将第4节讲解fork函数时的代码稍作修改

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

int g_val = 0;

int main()
{
    pid_t id = fork();
    if(id < 0){
        perror("fork fail");
        return 1;
    }
    else if(id == 0) {
        //child
        g_val = 10000;
        printf("g_val = %d,child_pid = %d , &g_val = %p\n",g_val,getpid(),&g_val);
    }
    else {
        //parent
        sleep(3);//&#x8FD9;&#x6BB5;&#x4EE3;&#x7801;&#x8BA9;&#x7236;&#x8FDB;&#x7A0B;&#x4F11;&#x7720;3s&#xFF0C;&#x4FDD;&#x8BC1;&#x5B50;&#x8FDB;&#x7A0B;&#x7684;&#x4EE3;&#x7801;&#x5148;&#x6267;&#x884C;&#xFF0C;&#x8BA9;&#x5B50;&#x8FDB;&#x7A0B;&#x4FEE;&#x6539;g_val
        printf("g_val = %d,parent_pid = %d , &g_val = %p\n",g_val,getpid(),&g_val);
    }
    return 0;
}
</unistd.h></sys></stdio.h>

执行结果如下

Linux系统编程之进程概念

我们惊奇地发现,父进程和子进程的&g_val是一样的,但是g_val居然不一样!

我们知道在同一个物理存储单元中存储两个不同的数字是不可能的,也就是说,这里的地址不是实际的物理地址,而是虚拟地址。那么,操作系统如何管理进程的地址空间呢?

[En]

We know that it is impossible to store two different numbers in the same physical memory unit, that is, the address here is not the actual physical address, but the virtual address. So how does the operating system manage the address space of processes?

6.1 mm_struct

对于操作系统而言,管理的方式是先用数据结构进行描述,再将数据结构进行组织。我们知道当一个进程创建时,会创建对应的PCB,在Linux中,task_struct中有一个结构体——struct mm_struct,这个结构体就是用来描述该进程虚拟地址的结构体。

mm_struct源码如下

struct mm_struct {

    //&#x6307;&#x5411;&#x7EBF;&#x6027;&#x533A;&#x5BF9;&#x8C61;&#x7684;&#x94FE;&#x8868;&#x5934;
    struct vm_area_struct * mmap;       /* list of VMAs */
    //&#x6307;&#x5411;&#x7EBF;&#x6027;&#x533A;&#x5BF9;&#x8C61;&#x7684;&#x7EA2;&#x9ED1;&#x6811;
    struct rb_root mm_rb;
    //&#x6307;&#x5411;&#x6700;&#x8FD1;&#x627E;&#x5230;&#x7684;&#x865A;&#x62DF;&#x533A;&#x95F4;
    struct vm_area_struct * mmap_cache; /* last find_vma result */

    //&#x7528;&#x6765;&#x5728;&#x8FDB;&#x7A0B;&#x5730;&#x5740;&#x7A7A;&#x95F4;&#x4E2D;&#x641C;&#x7D22;&#x6709;&#x6548;&#x7684;&#x8FDB;&#x7A0B;&#x5730;&#x5740;&#x7A7A;&#x95F4;&#x7684;&#x51FD;&#x6570;
    unsigned long (*get_unmapped_area) (struct file *filp,
                unsigned long addr, unsigned long len,
                unsigned long pgoff, unsigned long flags);

       unsigned long (*get_unmapped_exec_area) (struct file *filp,
                unsigned long addr, unsigned long len,
                unsigned long pgoff, unsigned long flags);

    //&#x91CA;&#x653E;&#x7EBF;&#x6027;&#x533A;&#x65F6;&#x8C03;&#x7528;&#x7684;&#x65B9;&#x6CD5;&#xFF0C;
    void (*unmap_area) (struct mm_struct *mm, unsigned long addr);

    //&#x6807;&#x8BC6;&#x7B2C;&#x4E00;&#x4E2A;&#x5206;&#x914D;&#x6587;&#x4EF6;&#x5185;&#x5B58;&#x6620;&#x5C04;&#x7684;&#x7EBF;&#x6027;&#x5730;&#x5740;
    unsigned long mmap_base;        /* base of mmap area */

    unsigned long task_size;        /* size of task vm space */
    /*
     * RHEL6 special for bug 790921: this same variable can mean
     * two different things. If sysctl_unmap_area_factor is zero,
     * this means the largest hole below free_area_cache. If the
     * sysctl is set to a positive value, this variable is used
     * to count how much memory has been munmapped from this process
     * since the last time free_area_cache was reset back to mmap_base.

     * This is ugly, but necessary to preserve kABI.

     */
    unsigned long cached_hole_size;

    //&#x5185;&#x6838;&#x8FDB;&#x7A0B;&#x641C;&#x7D22;&#x8FDB;&#x7A0B;&#x5730;&#x5740;&#x7A7A;&#x95F4;&#x4E2D;&#x7EBF;&#x6027;&#x5730;&#x5740;&#x7684;&#x7A7A;&#x95F4;&#x7A7A;&#x95F4;
    unsigned long free_area_cache;      /* first hole of size cached_hole_size or larger */

    //&#x6307;&#x5411;&#x9875;&#x8868;&#x7684;&#x76EE;&#x5F55;
    pgd_t * pgd;

    //&#x5171;&#x4EAB;&#x8FDB;&#x7A0B;&#x65F6;&#x7684;&#x4E2A;&#x6570;
    atomic_t mm_users;          /* How many users with user space? */

    //&#x5185;&#x5B58;&#x63CF;&#x8FF0;&#x7B26;&#x7684;&#x4E3B;&#x4F7F;&#x7528;&#x8BA1;&#x6570;&#x5668;&#xFF0C;&#x91C7;&#x7528;&#x5F15;&#x7528;&#x8BA1;&#x6570;&#x7684;&#x539F;&#x7406;&#xFF0C;&#x5F53;&#x4E3A;0&#x65F6;&#x4EE3;&#x8868;&#x65E0;&#x7528;&#x6237;&#x518D;&#x6B21;&#x4F7F;&#x7528;
    atomic_t mm_count;          /* How many references to "struct mm_struct" (users count as 1) */

    //&#x7EBF;&#x6027;&#x533A;&#x7684;&#x4E2A;&#x6570;
    int map_count;              /* number of VMAs */

    struct rw_semaphore mmap_sem;

    //&#x4FDD;&#x62A4;&#x4EFB;&#x52A1;&#x9875;&#x8868;&#x548C;&#x5F15;&#x7528;&#x8BA1;&#x6570;&#x7684;&#x9501;
    spinlock_t page_table_lock;     /* Protects page tables and some counters */

    //mm_struct&#x7ED3;&#x6784;&#xFF0C;&#x7B2C;&#x4E00;&#x4E2A;&#x6210;&#x5458;&#x5C31;&#x662F;&#x521D;&#x59CB;&#x5316;&#x7684;mm_struct&#x7ED3;&#x6784;&#xFF0C;
    struct list_head mmlist;        /* List of maybe swapped mm's.  These are globally strung
                         * together off init_mm.mmlist, and are protected
                         * by mmlist_lock
                         */

    /* Special counters, in some configurations protected by the
     * page_table_lock, in other configurations by being atomic.

     */

    mm_counter_t _file_rss;
    mm_counter_t _anon_rss;
    mm_counter_t _swap_usage;

    //&#x8FDB;&#x7A0B;&#x62E5;&#x6709;&#x7684;&#x6700;&#x5927;&#x9875;&#x8868;&#x6570;&#x76EE;
    unsigned long hiwater_rss;  /* High-watermark of RSS usage */&#x3001;
    //&#x8FDB;&#x7A0B;&#x7EBF;&#x6027;&#x533A;&#x7684;&#x6700;&#x5927;&#x9875;&#x8868;&#x6570;&#x76EE;
    unsigned long hiwater_vm;   /* High-water virtual memory usage */

    //&#x8FDB;&#x7A0B;&#x5730;&#x5740;&#x7A7A;&#x95F4;&#x7684;&#x5927;&#x5C0F;&#xFF0C;&#x9501;&#x4F4F;&#x65E0;&#x6CD5;&#x6362;&#x9875;&#x7684;&#x4E2A;&#x6570;&#xFF0C;&#x5171;&#x4EAB;&#x6587;&#x4EF6;&#x5185;&#x5B58;&#x6620;&#x5C04;&#x7684;&#x9875;&#x6570;&#xFF0C;&#x53EF;&#x6267;&#x884C;&#x5185;&#x5B58;&#x6620;&#x5C04;&#x4E2D;&#x7684;&#x9875;&#x6570;
    unsigned long total_vm, locked_vm, shared_vm, exec_vm;
    //&#x7528;&#x6237;&#x6001;&#x5806;&#x6808;&#x7684;&#x9875;&#x6570;&#xFF0C;
    unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
    //&#x7EF4;&#x62A4;&#x4EE3;&#x7801;&#x6BB5;&#x548C;&#x6570;&#x636E;&#x6BB5;
    unsigned long start_code, end_code, start_data, end_data;
    //&#x7EF4;&#x62A4;&#x5806;&#x548C;&#x6808;
    unsigned long start_brk, brk, start_stack;
    //&#x7EF4;&#x62A4;&#x547D;&#x4EE4;&#x884C;&#x53C2;&#x6570;&#xFF0C;&#x547D;&#x4EE4;&#x884C;&#x53C2;&#x6570;&#x7684;&#x8D77;&#x59CB;&#x5730;&#x5740;&#x548C;&#x6700;&#x540E;&#x5730;&#x5740;&#xFF0C;&#x4EE5;&#x53CA;&#x73AF;&#x5883;&#x53D8;&#x91CF;&#x7684;&#x8D77;&#x59CB;&#x5730;&#x5740;&#x548C;&#x6700;&#x540E;&#x5730;&#x5740;
    unsigned long arg_start, arg_end, env_start, env_end;

    unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */

    struct linux_binfmt *binfmt;

    cpumask_t cpu_vm_mask;

    /* Architecture-specific MM context */
    mm_context_t context;

    /* Swap token stuff */
    /*
     * Last value of global fault stamp as seen by this process.

     * In other words, this value gives an indication of how long
     * it has been since this task got the token.

     * Look at mm/thrash.c
     */
    unsigned int faultstamp;
    unsigned int token_priority;
    unsigned int last_interval;

    //&#x7EBF;&#x6027;&#x533A;&#x7684;&#x9ED8;&#x8BA4;&#x8BBF;&#x95EE;&#x6807;&#x5FD7;
    unsigned long flags; /* Must use atomic bitops to access the bits */

    struct core_state *core_state; /* coredumping support */
#ifdef CONFIG_AIO
    spinlock_t      ioctx_lock;
    struct hlist_head   ioctx_list;
#endif
#ifdef CONFIG_MM_OWNER
    /*
     * "owner" points to a task that is regarded as the canonical
     * user/owner of this mm. All of the following must be true in
     * order for it to be changed:
     *
     * current == mm->owner
     * current->mm != mm
     * new_owner->mm == mm
     * new_owner->alloc_lock is held
     */
    struct task_struct *owner;
#endif

#ifdef CONFIG_PROC_FS
    /* store ref to file /proc/<pid>/exe symlink points to */
    struct file *exe_file;
    unsigned long num_exe_file_vmas;
#endif
#ifdef CONFIG_MMU_NOTIFIER
    struct mmu_notifier_mm *mmu_notifier_mm;
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
    pgtable_t pmd_huge_pte; /* protected by page_table_lock */
#endif
    /* reserved for Red Hat */
#ifdef __GENKSYMS__
    unsigned long rh_reserved[2];
#else
    /* How many tasks sharing this mm are OOM_DISABLE */
    union {
        unsigned long rh_reserved_aux;
        atomic_t oom_disable_count;
    };

    /* base of lib map area (ASCII armour) */
    unsigned long shlib_base;
#endif
};

</pid>

因此,进程地址空间实际上就是结构体mm_struct所描述的虚拟空间,每个进程都有自己的虚拟地址空间。每个进程的虚拟地址如下图所示。

Linux系统编程之进程概念

在Linux中,采用分页存储的方式对内存进行管理。既然我们平时所看到的地址不是实际的物理地址,那就需要操作系统将虚拟地址映射为物理地址。操作系统是借助页表来实现虚拟地址和物理地址的映射的,页表的本质也是一个数据结构,最主要的两项就是进程的虚拟地址和实际物理地址的映射关系。

6.2 写时拷贝

在我们的代码中,当fork创建子进程时,会将父进程的mm_struct也拷贝给子进程,一开始,内存中只有一份g_val,当子进程修改g_val时,由于父子进程的数据是各自私有的,进程之间的执行应该具有独立性,因此子进程修改g_val不应该影响到父进程。此时就会发生 写时拷贝,即子进程在内存中开辟一块新的空间,将修改后的值填入该空间,并且修改子进程页表中虚拟地址映射的实际物理地址。

结果,我们看到了存储在同一虚拟地址中的值不同的情况。

[En]

As a result, we see a scenario in which the values stored in the same virtual address are different.

6.3 为什么要有进程地址空间?

这是因为引入了进程地址空间后,可以保证每个进程所用的空间独立而连续3。一个进程的越界操作并不会影响另一个进程,这样就实现了内存的保护。同时,每个进程地址空间是远大于实际内存空间的,这样也可以通过虚拟的方式实现内存的扩充。当一个进程退出后,我们只需要清除掉该进程的mm_struct和页表就可,有利于内存的分配回收。

Original: https://www.cnblogs.com/Grong/p/15516013.html
Author: 乌有先生ii
Title: Linux系统编程之进程概念

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/524395/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Docker安装使用–Centos

    前言 Docker安装使用 安装步骤 卸载旧版本 安装utils 配置utils的存储库 安装Docker 安装指定版本Docker 验证安装 官网安装说明 阿里云镜像加速 Doc…

    Linux 2023年6月7日
    091
  • NTP和chrony时间同步

    古代计时方式 ●在远古时期,人类用来确定时间的方式是一些自然界”相对”亘古不变的周期。如地球的公转是为一年,月球的公转是为一月,地球的自转是为一天等,最早的…

    Linux 2023年6月7日
    0112
  • Zabbix-(1)安装

    环境: VMware Workstation Pro 16.0 &#x7248;&#x672C; &#x7CFB;&#x7EDF; Centos7 …

    Linux 2023年6月13日
    078
  • 正则表达式

    正则表达式:REGEXP,REGular EXPression。正则表达式分为两类: Basic REGEXP(基本正则表达式 Extended REGEXP(扩展正则表达式) 元…

    Linux 2023年6月7日
    0115
  • C++ 之处理模板化基类的成员名称

    问题描述 假设有下面这么一段简单的代码,其中定义了两个类模板,一个基类 Animal,一个派生类 Dog: #include #include using namespace st…

    Linux 2023年6月7日
    099
  • LeetCode 416.分割等和子集 | 类0-1背包问题 | 解题思路及代码

    Given a nonempty array nums, which only contains positive number. Find if the array can be…

    Linux 2023年6月13日
    084
  • Markdown基本使用

    元素 Markdown 语法 [标题(Heading)] [粗体(Bold)] [斜体(Italic)] [引用块(Blockquote)] [有序列表(Ordered List)…

    Linux 2023年6月13日
    089
  • LeetCode-678. 有效的括号字符串

    题目来源 题目详情 给定一个只包含三种字符的字符串: &#xFF08;&#xA0;, &#xFF09; 和 *,写一个函数来检验这个字符串是否为有效字符串。…

    Linux 2023年6月7日
    0101
  • 灵感来袭,基于Redis的分布式延迟队列

    延迟队列 延迟队列,也就是一定时间之后将消息体放入队列,然后消费者才能正常消费。比如1分钟之后发送短信,发送邮件,检测数据状态等。 Redisson Delayed Queue 如…

    Linux 2023年5月28日
    089
  • JAVA中如何将以Date型的数据保存到数据库以Datetime型的字段中

    用Timestamp就行了 recordOuttime是Date类型 import java.sql.Timestamp; Record record = recordMapper…

    Linux 2023年6月8日
    080
  • phpcms v9全站点击量排行代码

    前台: {pc:content action=”sitehits” siteid=”1″ num=”10″ …

    Linux 2023年6月13日
    079
  • [转]万智牌规则和异能详解

    下面这些都是之前的旧文档了,直到我发现了一个神奇的网站。建议大家有任何疑问,都可以到这里查看规则文档 点击网站的右上方可以搜索 最近游戏过程中发现规则和异能详解的文档很少,找起来非…

    Linux 2023年6月13日
    097
  • Linux目录结构信息

    一、系统目录结构介绍 1.1 常见系统目录结构 Windows下: C:\windows D:\Program Files Linux下: /etc/sysconfig /usr/…

    Linux 2023年5月27日
    078
  • 职场最讨厌的人,没有之一

    人物背景: 姓名:春绿,性别:未知,年龄:不详,工龄:菜鸟,人物特点:爱管闲事,管不住自己的嘴,情商约等于0.000001 人物故事: 1、领导给小明安排了一个工作,被春绿听到了,…

    Linux 2023年6月13日
    0103
  • 最小生成树-Kruskal算法

    与 Prim算法贪心选择不同,Kruskal算法采取 每次选择权值最小的边的方法,这样,在 不构成环且最后能够连接完所有边它们的权重和一定是最小的。 和之前Prim算法的图一样,便…

    Linux 2023年6月7日
    0117
  • 三款优秀的替代Xshell的SSH软件

    在之前的文章介绍个, 由于公司禁止使用xshell, 让我很是难受了一阵, 因为一直无法找到好的工具来替代xshell, 前面文章中提到的那些对我来时功能还是太单一了, 界面也不够…

    Linux 2023年5月28日
    0855
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球