进程和线程

一、进程

  • 我们的操作系统中运行着各种各样的程序,为了管理这些程序的运行,操作系统提出了进程。
  • 为了使程序看起来是同一时间运行的,操作系统进一步提出了上下文切换的概念。

1.1、进程的状态

  • 进程存在五个状态。
  • 分别为:新生状态,预备状态,运行状态,阻塞状态,终止状态。
  • 新生状态是指:一个进程刚被创建出来,还没有完成初始化,这里初始化的意思应该是给进程创建相应的数据结构等一系列操作。新生状态经过初始化之后,就进入了预备状态。
  • 预备状态:表示该程序可以被调度执行,但是还没有被调度器选择。一旦被调度器选择之后,就进入了运行状态。
  • 运行状态:该状态表示进程正在cpu上运行,当一个进程执行一段时间之后,调度器可以选择中断它的执行,并将其重新放回调度队列中,它就会重新变成预备状态。如果运行结束,它会迁移至终止状态。如果这个进程需要某些外部事件,它可以放弃cpu,并迁移至阻塞状态。
    在这里插入图片描述
  • 阻塞状态:该状态表示,进程需要某些外部事件的触发,比如按下空格键,外部事件触发之后,会迁移至预备状态。
  • 终止状态:进程运行结束。
    在这里插入图片描述

进程的内存空间布局

在这里插入图片描述

  • 红色的是内核区域
  • 蓝色的是非内核区域。
  • 用户栈:存储进程需要使用的各种临时变量
  • 代码库:进程执行时需要依赖的代码库,比如libc
  • 用户堆:进程动态分配的内存,一般由程序员创建,最后也由程序员释放,如果程序员不释放,程序结束的时候可能由OS回收。(堆区域管理一般采用链表进行管理,程序员申请内存的时候,操作系统会遍历这个空闲内存地址的链表,找到一个合适地址,就把这个节点删除,并将其指向的内存地址分配给程序)
//main.cpp
int a = 0; // 全局初始化区
char *p1; // 全局未初始化区
main {
    int b; // 栈
    char s[] = "abc"; // 栈
    char *p2; // 栈
    char *p3 = "123456"; // 123456\0在常量区,p3在栈上
    static int c =0// 全局静态初始化区
    p1 = (char *)malloc(10);
    p2 = (char *)malloc(20); // 分配得来的10 和20 字节的区域就在堆区
    strcpy(p1, "123456"); // 123456\0在常量区,这个函数的作用是将"123456" 这串字符串复制一份放在p1申请的10个字节的堆区域中。
    // p3指向的"123456"与这里的"123456"可能会被编译器优化成一个地址。
}
  • 数据段:保存全局变量的值
  • 代码段:进程执行需要的代码
  • 内核部分:当进程由于中断和系统调用进入内核之后,会使用内核的栈。

进程的控制块和上下文切换

  • 在内核中,每个进程都有一个进程控制块来保存其相应的状态,,比如进程标识符(PID),进程状态,虚拟内存状态,打开的文件等,这个数据结构为进程控制块
  • 进程上下文包括进程运行时寄存器的状态,用于保存和恢复进程在处理器上运行的状态
  • 上下文切换过程:

父子进程的拷贝

fork

  • Linux系统中,进程一般是通过调用fork() 接口,从已经有的进程中分裂出去的。
  • fork()接口没有参数,只是返回当前值的PID。
  • fork()完成时,两个进程的内存,寄存器,程序计数器等状态完全一样,但是他们是两个独立的进程,拥有不同的PID和虚拟内存空间,并且他们互不干扰。
  • 父子进程的执行顺序是不确定的,完全取决于调度器的决策。
  • 每个进程在运行过程中都会维护一张已经打开的文件描述符(操作系统对引用某一文件的抽像)
  • 文件描述符使用偏移量记录当前进程读取到某一文件的某个位置。
  • fork()的过程中,由于文件描述符时PCB的一部分,所以会指向相同的文件抽象,与父进程用同一个偏移量。并且Linux在实现read操作时,会对文件加锁,所以父子进程读不到一样的字符串。
  • 操作系统中有0号进程。(由操作系统创建的)。

写时拷贝

  • fork()之后出现的时两个一毛一样的进程,但是往往我们需要其执行不同的任务,所以就有了exec()这个接口。
  • fork()之后,exec()之前,两个进程的内存空间是同一个,但是虚拟空间(在磁盘上是两块区域),当父子进程中有发生改变的时候,在为子进程分配物理内存空间。
  • 如果没有exec(),内核会给子进程的数据段、堆栈段分配相应的物理空间,代码和代码库都是用父进程的。
  • 如果调用的exec(),内核会给子进程的代码段分配独立的内存空间(功能不一样了,代码肯定不一样了)。
  • fork()之后
    在这里插入图片描述
  • 父子进程中有一个要改变
    在这里插入图片描述
  • 由于exec()导致的父子进程的不同,就是执行不同的功能,所以代码段就不能共享了
  • 就会如下图所示
    在这里插入图片描述

进程之间的监控(wait())

  • wait()不仅起监控作用,还起到回收进程的作用
  • 僵尸进程:如果父进程没有进行wait()操作,,就算子进程已经终止了,它占用的资源也不会释放,,这种进程为僵尸进程。(本来该死,结果自己又跳出来了,确实像僵尸)
  • 如果一个进程创建了大量的子进程确从不调用wait(),僵尸进程就会占用可以使用的PID,导致资源不足。
  • 如果父进程退出了,所有僵尸进程将会由0号进程通过wait的方式进行回收。
  • 孤儿进程:父进程结束了,但是子进程没有结束。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值