【Linux】进程的基本概念

Linux进程概念

文章目录

  • Linux进程概念
    • 基本概念
      • PCB
      • task_struct中的描述信息
      • 组织进程
    • Linux进程状态*
      • R 运行状态
      • S 可中断休眠态
      • D 不可中断休眠状态
      • T 停止状态
      • x 死亡状态
      • Z 僵尸状态
    • 僵尸进程
    • 孤儿进程
      • 守护进程
    • 环境变量
      • 环境变量的特性和作用
      • 常见环境变量
      • 和环境变量相关的命令
      • 通过代码获取环境变量
      • 通过系统调用获取或设置环境变量
    • 进程地址空间
      • 虚拟地址空间
      • 什么是虚拟地址空间
      • 为什么要有虚拟地址空间
      • 地址空间是怎么工作的

基本概念

从用户角度看:进程就是当前正在运行中的程序

从操作系统角度:是程序运行的动态描述-pcb,结构体中包含程序运行的各种信息,实现操作系统对于运行中程序的管理。

PCB

PCB(process control block)也叫进程控制块,PCB其实是一个结构体对象,用来描述运行中的程序

的各种信息,Linux操作系统下的PCB是task_struct。

task_struct中的描述信息

  • 标识符(PID):进程的唯一标识符,每个进程独一份,用来区别其他进程
  • 内存指针:包括程序代码和进程相关的数据指针,还有和其他进程共享的内存块指针
  • 程序计数器:程序中即将被执行的下一条指令
  • 上下文数据:进程执行时处理器的寄存器中的数据
  • 进程状态:任务状态,退出代码,退出信息等。
  • 优先级:相对于其他进程的重要程度,决定CPU的调度顺序
  • I/O信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
  • 记账信息:可能包括处理器时间综合,使用的时钟总和,时间限制,记帐号等。

组织进程

操作系统对进程的管理,总体来说六个字先描述,后组织

当进程创建时,也就是我们的代码程序运行在操作系统上时,操作系统首先会用PCB对进程进行描述,之后对进程的管理其实就是对PCB的管理,也就是对PCB对象的管理。

操作系统对每一个进程都进行了管理,形成了一个个进程控制块(PCB对象),并将这些对象以链表的形式组织起来,这样一来,对进程的管理就成了对链表的管理。

Linux进程状态*

在Linux内核源码中,有这样的定义

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
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 运行状态

一个进程处于运行状态(running),并不一定程序就正在运行,有可能是正在running的进程,也有可能是可以执行,但是尚未被CPU调度的ready状态。这些进程的PCB被组织在可执行队列中,一旦拿到时间片就能立即运行

S 可中断休眠态

可以中断的阻塞状态,这些进程因为等待某些事件的发生而被挂起,等到资源到位或者收到信号,可以立即被唤醒进入运行状态的状态。

D 不可中断休眠状态

这个状态的进程处于阻塞状态不能被唤醒,一般进程在进行磁盘IO时处于这个状态,这时的进程是不能被唤醒运行的,要等到IO结束之后才会被唤醒。

T 停止状态

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

x 死亡状态

这个状态只是一个返回状态,你不会在任务列表里看到这个状态

Z 僵尸状态

当一个进程的子进程先于父进程退出,并且父进程没有调用wait或waitpid回收子进程。此时子进程即处于僵尸状态

僵尸进程

如果一个进程处于僵尸状态,也就是这个进程先于父进程退出,而且这个进程的父进程没有对这个进程进行处理,那么这个进程就是僵尸进程

为什么会有僵尸状态?

进程在退出的时候会有一个退出码和一些统计信息,这些信息会被保存在PCB里面,而父进程在子进程退出的时候要获取这些信息,并对子进程进行处理,所以在子进程退出时,父进程不处理,那这个进程就会变为僵尸进程。

僵尸进程的危害

如果产生了僵尸进程,并且不处理,就会产生内存泄漏。因为僵尸状态的进程会保存退出码和一些信息,PCB不会完全释放,这些PCB还是要被操作系统管理,会占用内存资源和进程数量。

僵尸进程的避免和处理

  • 进程等待
  • 退出父进程

孤儿进程

如果一个进程的父进程先于子进程退出,那么这个子进程就成为孤儿进程

特性:

父进程退出后,子进程会被1号进程领养,那么这个进程的父进程就会变为1号进程。孤儿进程是运行在后台的。

守护进程

特殊的孤儿进程,也叫精灵进程,在孤儿进程的基础上脱离与终端之间的关系。

如果我们想将一个程序稳定运行在后台,不受任何影响,这个时候将这个进程转换为守护进程。

环境变量

环境变量是指操作系统用来指定操作系统运行环境的一些参数

环境变量的特性和作用

**特性:**环境变量具有进程之间的传递性

**作用:**用于进程之间的一些数据传递

常见环境变量

  • PATH : 指定命令的搜索路径
  • HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
  • SHELL : 当前Shell,它的值通常是/bin/bash

和环境变量相关的命令

  • echo:显示某个环境变量值
  • export :设置一个新的环境变量
  • env :显示所有环境变量
  • set :显示本第定义的shell变量和环境变量
  • unset :清除环境变量

通过代码获取环境变量

  • 通过命令行的第三个参数
#include
using namespace std;
//第一个参数是命令数量,第二个是命令表,第三个是环境表
int main(int argc, char* argv[], char* env[]) {
    for (int i = 0; env[i]; ++i) {
        cout << env[i] << endl;
    }
    return 0;
}

【Linux】进程的基本概念_第1张图片

【Linux】进程的基本概念_第2张图片

  • 通过第三方变量environ获取
#include 
int main(int argc, char* argv[])
{
    extern char** environ;
    int i = 0;
    for (; environ[i]; i++) {
        printf("%s\n", environ[i]);
    }
    return 0;
}

【Linux】进程的基本概念_第3张图片

【Linux】进程的基本概念_第4张图片

libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。

通过系统调用获取或设置环境变量

#include 
#include 
int main()
{
    printf("%s\n", getenv("PATH"));
    return 0;
}

【Linux】进程的基本概念_第5张图片

进程地址空间

我们以前知道,程序的不同变量和资源存放在不同的内存区域,每个内存区域都有自己的专有的存储对象,如下图所示就是程序的地址空间分布

【Linux】进程的基本概念_第6张图片

地址空间的作用:仅仅限定了一块内存空间,属于当前进程的一段活动范围,并不代表这个进程把所有资源都占有了

虚拟地址空间

看一段代码

#include
#include
int val = 888;
int main() {
	pid_t ret = fork();
	if (ret > 0) {
		//parent
		while (1) {
			sleep(1);
			printf("%d %p\n", val, &val);
		}
	}
	else if (ret == 0) {
		//child
		while (1) {
			sleep(1);
			val = 999;
			printf("%d %p\n", val, &val);
		}
	}
	else {
		perror("fork error!!!");
		return 1;
	}
	return 0;
}

我们给出一个全局变量,赋值为888,并且在子进程中修改变量的值,然后让父进程和子进程循环打印,并且打印出变量的地址

【Linux】进程的基本概念_第7张图片

我们发现,子进程和父进程打印出的变量,值不一样!但是变量的地址却是一样的。

  • 所以说子进程和父进程两个val的内存绝对不是一个内存地址。
  • 我们程序所打印出来的地址,不是真实的物理地址,而是操作系统虚拟出来的。

当数据没有改变时,子进程继承了父进程的大部分信息,父子进程共用同一块地址空间,但是当数据进行修改时,由于进程之间的数据独立,子进程就会重新开一块空间构建页表,映射属于自己的虚拟地址空间。

【Linux】进程的基本概念_第8张图片

在Linux系统中,虚拟地址空间实际上是一个结构体mm_struct ,task_struct中有这个结构体的指针,每个进程的地址空间都是通过这个结构体进行管理

申请空间的本质是:向内存索要空间,得到物理地址。然后在特定区域申请没有被使用的虚拟地址,建立映射关系,返回虚拟地址即可。

什么是虚拟地址空间

地址空间的本质实际上是描述进程所占用物理内存的一个数据结构mm_struct,存在操作系统的内核中,task_struct的结构体内有指针指向mm_struct用来管理地址空间。

每个进程都有一个进程地址空间,所以系统内有多个地址空间,要管理这些地址空间,就得先把这些地址空间描述起来。

  • 先描述

地址空间本质就是一个数据结构,在Linux系统中,叫做mm_struct,它包含了一些区域信息,能够实现区域划分。

在内核源码(/usr/src/kernels/3.10.0-1160.31.1.el7.x86_64/include/linux/mm_types.h)中,有这样一些信息,实现区域的划分

struct mm_struct {
         ......
         ......
         ......
         unsigned long start_code, end_code, start_data, end_data;
         unsigned long start_brk, brk, start_stack;
         unsigned long arg_start, arg_end, env_start, env_end;
         ......
		......
}

请添加图片描述

  • 再组织

进程的PCB即task_struct里面存着指向mm_struct的指针,mm_struct的对象每个进程独有一份,PCB通过mm_struct管理空间,把内存组织起来。

为什么要有虚拟地址空间

  • 为了保护物理内存:一个进程有自己的虚拟地址空间,而且这个空间是由操作系统统一分配管理的,在分配时就确定了映射关系,进程对自己的虚拟地址空间不管做什么操作,都不会影响到物理内存中其他进程分配的内存。如果出现越界访问,在页表中查不到对应的映射关系,保证了内存的安全性。
  • 通过页表限制访问权限:页表中不同的区域有不同的读写权限,如果越界访问,没有对应的权限也无法操作数据,也保护了数据。
  • 使空间的使用是连续的地址:由于空间的使用和释放,不能保证物理内存中的空间足够开辟一段连续的空间让进程访问,空间会出现碎片化现象,这样访问起来不方便,而且越界风险大大增加,所以操作系统通过页表的映射,把物理内存中的不连续的内存片段映射到虚拟地址空间,进程在访问自己的地址空间时,访问的就是连续的空间地址,至于页表的映射,是由操作系统完成的,开发者就不用担心内存的连续性,访问也更加安全。

地址空间是怎么工作的

地址空间上所呈现暴露给上层的所有的地址都叫做虚拟地址,而实际进程访问时,要通过页表映射转换到物理内存,然后拿到对应的代码和数据

你可能感兴趣的