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的地方退出
异常退出:程序运行到中途崩溃了
不管子进程正常退出或者异常退出,只要是退出了,没有被父进程等待处理,就都会成为僵尸进程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!