操作系统之进程概念

进程概念

进程是什么:

  表面上来说进程是程序的一个执行实例,或者是一个正在执行的程序等,从操作系统的角度来说,程序运行需要将代码数据加载到内存中,由于在操作系统中运行了很多的程序,操作系统就必须去管理这些运行的程序,先描述再组织,所以在操作系统的层面的进程就是操作系统对一个运行的程序的描述。这个描述信息就是PCB(进程控制块),在Linux中这个PCB有一个专属的名称task_struct结构体。task_struct是Linux内核的一种数据结构,他会被装载到RAM里,并且包含着进程的信息。
pcb中的描述信息

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

程序计数器:程序中即将被执行的下一条指针的地址。

上下文数据:进程执行时处理器的寄存器中的数据。

标识符PID:描述本进程的唯一标识符,用来区别其他的进程。

进程状态:任务状态,退出代码、退出信号等。

进程优先级:决定了cpu给进程分布分配资源的优先级

记账信息:处理器时间总和,使用的时钟数总和,时间限制、记账号等。

IO信息:包括显示的I/O请求,分配给进程I/O设备和被进程使用的文件列表。

CPU分时机制

  对程序运行处理进行切换调度处理(时间片:cpu在每个程序上所运行的这段时间 )

查看进程

s -aux | grep 进程名
cd / -> cd /proc ->cd 进程ID

创建进程

创建子进程的意义:

分摊任务处理压力,让子进程完成其他任务(提高稳定性)

  操作系统通过写时拷贝技术来创建子进程,也就是说通过复制父进程创建子进程,子进程初始时与父进程指向同一块物理内存区域,当内存数据发生改变时,为了保证进程之间的独立性,操作系统会为子进程重新开辟一段物理内存空间,并且更新子进程的页表。

  复制:操作系统通过复制父进程创建子进程,因此父子进程数据独有,但是代码共享

  返回值:对于父进程来说,fork返回的是子进程的pid;创建子进程失败返回-1,对于子进程来说,fork的返回值是0

用户通过fork的返回值不同对父子进程进程运行流程进行分流

#include<iostream>
#include<string>
#include<pthread.h>
#include <unistd.h>
#include<vector>
using namespace std;

int main()
{

    //pid_t fork(void)
    //创建一个子进程,父进程返回子进程的pid,子进程返回0
    pid_t pid = fork();

    if(pid < 0)
    {
        cout << "fork error" << endl;
        return -1;
    }
    else if(pid == 0)
    {
        cout << "<----I am child----> " << getpid() << endl;
    }
    else
    {
        cout << "<----I am parent----> " << getpid() << endl; 
    }

    return 0;
}

进程状态

运行、就绪、阻塞

R:运行状态:但是并不意味着进程一定在运行中,它表明进程要么是在运行要么是在运行队列里

S:睡眠状态;意味着进程在等待事件完成(可中断休眠:能够被唤醒的状态)

D:磁盘休眠状态;不可中断休眠:要通过特定的条件才能被唤醒

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

X:死亡状态:这个状态只是一个返回状态,不会再任务列表中看到这个状态。

僵尸进程

处于僵尸状态的进程会造成内存泄漏

僵尸进程产生的原因

  子进程先于父进程退出,因为要保留推出原因,所以操作系统不能直接释放所有资源,通知父进程获取退出原因,允许操作系统释放资源,但是父进程没有关注这个通知导致子进程退出后无法释放所有资源,处于僵死状态,称为僵尸进程。

如何避免:进程等待

处理方式:退出父进程

孤儿进程

父进程先于子进程退出,子进程成为孤儿进程,运行在后台,父进程成为1号进程
守护进程/精灵进程:特殊的孤儿进程,一个进程的父进程先于他推出后成为孤儿进程,他会脱离各种会话和终端影响,将之前的所有联系取消,

环境变量

存储系统运行环境参数的变量

查看环境变量
env set echo

设置环境变量
export

删除环境变量
unset

常见环境变量
HOME PWD SHELL PATH

环境变量特性
全局特性(继承)
当前shell终端下所运行的进程能够获取到当前终端所有的环境变量,但是获取不到普通变量

环境变量在代码中的获取

通过函数: char* getenv(char* name)
main的第三个参数:  int main(int argc, char* argv[], char* env[])

环境变量的使用场景
  通常是父进程通过给子进程设置环境变量达到向子进程传递数据的功能。

程序地址空间

在这里插入图片描述
  内存地址就是内存区域的一个编号,变量就是一块内存地址的别名。

虚拟地址

  虚拟地址空间,实际上是一个结构体, mm_struct,这个结构体给进程描述了一块完整连续的虚拟地址空间,但是在物理内存中有可能是不连续的。

struct mm_struct
{
	ulong men_size;//内存大小
    ulong code_start;//代码段的起始位置
    ulong code_end;//代码段的结束位置
};

  每个进程都会有一份自己的程序地址空间。并且每个程序都需要一块连续的地址空间。操作系统对每个运行起来的程序都会建立一块虚拟的地址空间,给进程描述一块虚假的内存空间,每一个变量都有一个虚拟的地址,但是这个虚拟的地址无法存储数据,还是要存储到物理内存中,这时就有一个页表,它主要时做映射,将变量通过映射,映射到物理内存中,这块物理内存存放的就是变量的值,子进程复制了父进程而创建,也就是说子进程和父进程有着相同的虚拟地址空间和页表,在他的初始化全局这段空间中也有着和父进程相同的变量,也通过页表映射一块物理内存空间,这块物理内存空在开始的时候是相同的,但是当子进程要修改这块内存中的数据的时候,为了保证进程之间的独立性,操作系统会通过写时拷贝技术为子进程重新开辟一段块物理内存空间,所以说地址相同说的是他们的虚拟地址相同。

  我们所看到的程序地址空间实际上是一个虚拟地址空间,实际上是通过操作系统通过mm_struct这个结构体为进程描述的一个空间,因此有时候也称作内存描述符。

为什么要有虚拟地址空间?

  进程通过访问虚拟地址进而获取变量数据,最终还是要去访问物理内存,因为数据是存储在物理内存中的,在虚拟地址和物理地址之间通过页表进行地址映射,将虚拟地址转换得到物理地址,进而访问物理内存区域。通过映射之后物理地址可就不一定是连续的了,通过这种映射转换的方式实现数据的离散存储提高了内存的利用率。页表中不但记录了虚拟地址和物理地址的映射关系,还记录了这块地址的属性实现内存的访问控制。

为什么进程要独立

  进程应该具有独立性,因为独立才更加稳定

虚拟地址空间和页表有什么用?

​ 提高内存利用率,增加内存访问控制,保持进程独立性

页表是如何将虚拟地址转换到物理地址

一共有三种常见的内存管理方式:分段式、分页式和段页式

分段式内存管理

​ 内存地址构成:段号+段内偏移
​ 段表:有很多的段项

​ (物理段的起始地址)


  分段式物理地址获取方法:通过地址中的段号去段表中找到段表项,通过段表项中的物理起始地址加上地址中的段内偏移获取到物理地址

​ 分页式内存管理:

​ 内存地址的构成:页号 + 页内偏移

​ 页表中有很多页表项

在这里插入图片描述
  分页式物理地址获取方法:通过地址中的页号去页表中找到页表项,通过页表项中的物理页号加上页内偏移获取到物理地址

段页式内存管理

  内存地址:段号+段内页号+页内偏移

  段表向中包含段内页表起始地址

  段内页表中包含物理页号

段页式物理地址获取方法:通过段号在段表中找到段表项,通过段表项中的段内页表地址找到段内页表,通过地址中的段内页号在段内也表中找到页表项,通过页表项中的物理页号与页内偏移组成物理地址。

内存置换算法:

​ 内存中只有4G,但是想处理5G的数据怎么办?

​ swap分区也叫交换内存:内存不够时,将内存中的数据置换到交换分区中,腾出内存处理数据

置换算法

FIFO:先进先出,核心原则就是:如果一个数据最先进入缓存区,则因该最早淘汰掉
LFU:最近最少频率未使用。基于访问次数。
LRU:最近最久未处理,基于访问时间。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章