Unix/Linux系统编程(自学笔记3)

一.进程同步

  (前言)关于进程

  操作系统是一个多任务处理系统。在操作系统中,任务也称作进程。在实际的应用中,任务和进程这两个术语可以互换使用。这里有一个关于执行映像的概念,我们把执行映像定义为包含执行代码、数据和堆栈的存储区(类似一个存放我们可被执行内容的仓库,只不过这个仓库采用的是堆栈方式,先进后出)。

  而进程,正式定义为对映像的执行(即一个是仓库,一个是对仓库内容物的处理)

  进程通常被定义为一个正在运行的程序的实例。简单来说,磁盘上的可执行文件被载入内存执行之后,就变成“进程”了。

 

  (一)进程的创建(参考链接:https://blog.csdn.net/lena7/article/details/119845079

  1.父子进程


    在OS中,允许一个进程创建另一个进程,创建者称为父进程,被创建者称为子进程,子进程也能创建更多孙进程。结构像树一样。如在UNIX中,由父子进程组成一个进程组。
    子进程可以继承父进程所拥有的的资源。当子进程被撤销时,应将从父进程那里获得的资源归还给父进程。此外,在撤销父进程的时候,也必须同时撤销其所有子进程。
   

  2.进程创建过程

  OS调用进程创建原语Creat,该原语按照以下步骤创建一个新进程:

    1)申请空白PCB,为新进程从申请获得唯一的数字标识符。
    2)为新进程分配其运行所需资源,包括各种物理资源和逻辑资源,如CPU时间、所需内存大小、I/O设备等。
    3)初始化PCB:①初始化标识信息(数字标识符和父进程标识符)

         ②初始化处理机状态(程序计数器指向程序入口地址,栈指针指向栈顶)

         ③初始化处理机控制信息(设置进程状态、优先级等)
    如果进程就绪队列能够接纳新进程,便将新进程插入就绪队列。


  (二)进程同步(参考链接:https://blog.csdn.net/m0_56561130/article/details/119678738)

 

  这里有一个关于进程同步的类比,有助于更好的了解进程同步的概念:

 

 

   在这个例子中,司机和售票员的工作就类似两个进程(任务),司机的任务在于“启动车辆”“驾驶车辆”“停靠车辆”,而售票员的任务则是“打开车门”“售票”“关闭车门”。

  但这两个进程之间却不能独立存在进行,比如司机不能在售票员还未关闭车门的时刻就启动车辆,他需要确定售票员已经完成了售票并关好了车门;售票员打开车门之前需要等待司机停靠好了车辆。那么需要同步的就是司机启动车辆之前及售票员打开车门之前。

 

 

 

  那么,在特定的时候需要让进程“睡眠”,去等待另一个进程的执行;而在另一个进程执行到相应的位置后,需要将“睡眠”的进程“唤醒”。

  引入“睡眠”和“唤醒”后 ,司机和售票员的进程如上图所示。

  所以接下来的问题是,我们在进程中如何知道何时该“睡眠”,何时将被“唤醒”。

  睡眠模式/唤醒模式(参考链接:https://blog.csdn.net/Blackoutdragon/article/details/104802787)

   很多时候,进程同步问题被比作“生产者—消费者问题”。生产者—消费者问题是相互合作关系的进程关系的一种抽象,是一个广义的概念,可以代表一类具有相同属性的进程。
  生产者进程和消费者进程共用一个数据缓存区,生产者向缓存区写入数据,而消费者则从缓存区取出数据;当缓存区满的时候,生产者就必须睡眠,等待生产者取出数据后将其唤醒,而缓存区为空时,消费者需要等待生产者向缓冲区写入数据。

 

 

 

  进程工作时

  • 生产者进入缓冲区,申请空的存储单元,没有则阻塞
  • 消费者进入缓冲区,申请满的存储单元,没有则阻塞

   生产者图例

 

  消费者图例:

  我们定义empty和full两个信号:empty表示“空闲缓存区个数”,消费者进程用掉一个内容单元时,即产生一个空闲缓存区,就会向生产者进程发送一个empty信号;而生产者在向一个空闲单元填入内容后,也会向消费者发送一个full信号。

  如上上图所示,当counter == BUFFER_SIZE时,也就是说缓存区已经满了,生产者就会进入睡眠状态,等待信号empty;直到消费者使用了一个内存单元,发现counter == BUFFER_SIZE,有空闲的内存单元了,就会向生产者进程发送empty信号,将其唤醒。

  但是如果存在两个消费者进程P1和P2的情况下,又会出现什么情况了?

  当缓冲区满了,那么P1和P2进程都分别进入了阻塞状态,等待empty信号;而当消费者进程使用了一个内存单元后,判断counter == BUFFER_SIZE-1,就会向P1发送empty信号,P1就会被唤醒;而当消费者进程再循环一次,counter == BUFFER_SIZE-2,但此时并不会触发wake_up,P2无法被唤醒。

  而为了解决这一问题,必须将信号转变为信号量。

  (1)信号量就是一个整型变量,用来记录和进程同步有关的重要信息;

  (2)能让进程阻塞睡眠在这个信号量上

  (3)需要同步的进程对信号量进行操作(加一减一)实现进程的阻塞和唤醒

 

  (三)进程的终止(参考链接:https://blog.csdn.net/m0_46657980/article/details/109060421)

   执行程序映像的进程可能以两种方式中止:正常终止、异常终止

  1.进程常见退出方法

  (1)main函数中的return

  普通函数中的return只能退出函数,不能退出进程

  (2)void  exit (int status);

  在任意位置调用程序的任意位置退出一个进程

  (3)void  _exit (int status);

  在任意位置调用程序的任意位置退出一个进程

  2.进程退出场景 

  正常退出:进程运行到return或exit的地方退出

  异常退出:程序运行到中途崩溃了

  不管子进程正常退出或者异常退出,只要是退出了,没有被父进程等待处理,就都会成为僵尸进程

 

posted @   遥鱼  阅读(99)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示