既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
经典同步问题:生产者和消费者
2.进程控制
在进程生存的全部期间,对其全部行为的控制,分为四个典型的控制行为
- 创建进程
- 阻塞进程
- 撤销进程
- 唤醒进程
2.1 进程创建:
- 创建一个空白PCB
- 获得并赋予进程标识符ID
- 为进程分配空间
- 初始化PCB
- 插入相应的进程队列(新进程一般插入就绪队列)
2.1.1 Linux系统创建进程–fork()
pid_t pid = fork()
// fork()函数返回值:
// 在子进程中:pid = 0
// 在父进程中:pid > 0 (子进程ID)
// 出错: pid = -1
- 新进程是当前进程的子进程
- 父进程是fork() 的调用者,新建的进程的子进程
- 子进程是父进程的复制(相同的代码、数据、堆栈。不同的ID、时间参数)
- 父进程和子进程并发运行
fork() 执行流程:
// 父进程
int main(void){
pid_t pid;
pid = fork();
if(pid == 0)
printf("hello word\n");
else if(pid > 0)
printf("how are you\n")
}
// 子进程:和父进程有相同的代码,从fork()之后开始运行
int main(void){
pid_t pid;
pid = fork();
// 从这里开始运行
if(pid == 0)
printf("hello word\n");
else if(pid > 0)
printf("how are you\n")
}
在linux中,除了 init 进程不是通过 fork() 创建,其他所有进程都是通过 fork() 函数创建。
那么子进程如何执行和父进程不同的功能呢?
exec函数簇:
- 功能
- 装入一个指定的可执行程序运行
- 使子进程具有和父进程完全不同的功能
- 步骤
- 根据文件名找到响应的可执行程序
- 将可执行程序的内容填入子进程的地址空间
- 进入新进程执行且不再返回
2.2 进程的阻塞与唤醒
正在执行的进程由于等待的事件未发生,由系统执行阻塞原语,由运行态变为阻塞态。
阻塞过程:
- 找到将要被阻塞进程的 PCB。
- 如果进程为运行态,保护现场并转为阻塞态,停止运行。
- 把 PCB 插入相应事件的等待队列,当被阻塞进程期待的事件发生时,由相关进程调用唤醒原语,将进程唤醒。
唤醒过程:
- 在该事件的等待队列中找到进程对应的 PCB。
- 将其从等待队列中移除,设置状态为就绪态。
- 将 PCB 插入就绪队列,等待调度程序调度。
2.3 进程切换
进程切换是指处理机从一个进程的运行转到另一个进程上运行。
进程切换过程:
- 保存处理机上下文,包括程序计数器和其他寄存器。
- 更新 PCB 信息,并把 PCB 移入相应的阻塞队列。
- 选择另一个进程执行并更新其 PCB。
- 更新内存管理的数据结构,恢复处理机上下文。
2.4 进程终止
调用exit( )函数终结进程。exit(int status)
进程终结时,要释放资源并报告父进程
- 利用status传递进程结束时的状态
- 变为僵尸状态,保留部分PCB信息供wait( )收集,包括:
- 正常结束还是异常结束
- 占用系统cpu时间
- 缺页中断次数
- 调用 schedule( ) 函数,让操作系统选择新进程运行
3.进程通信
管道、消息队列、共享内存,信号量,socket,信号,文件锁
3.1 管道
管道是进程间的一种通信机制,一个进程(A)可以通过管道把数据传输给另外一个进程(B)。
特点:
- 面向字节流
- 生命周期随内核
- 自带同步互斥机制
- 半双工,单向通信,两个管道实现双向通信。
3.2 信号
- 信号是Linux进程间一种重要的通信机制
- 信号是向进程发送的一个通知,通知某个事件已发生
- 收到信号的进程可以立即执行指定的操作
- 信号的发出者可以是进程,也可以是系统(含硬件)
3.3 信号量
线程:
信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数器,每当调用acquire()时-1,调用release()时+1。
计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)
资源:
信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有
信号量的值为正的时候,说明它空闲。
所测试的线程可以锁定而使用它。若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。
对信号量的操作
- P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
- V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
PV操作用于同一进程,实现互斥。
PV操作用于不同进程,实现同步。
3.4 消息队列
消息队列是保存在内核中的消息链表,消息的发送方和接收方要约定好消息体的数据类型,每个消息体都是固定大小的存储块,不像管道是无格式的字节流。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。消息队列的通信效率高于管道,进程发送消息时,把数据放在消息队列后就可以正常返回。
消息队列不适合较大数据的传输,因为内核中每个消息体都有最大长度限制。此外,消息队列通信存在数据拷贝开销,进程写数据到消息队列时,会发生从用户态拷贝数据到内核态的过程,读取数据时,会发生从内核态拷贝数据到用户态的过程。
3.5 共享内存
共享内存解决了消息队列中用户态与内核态间的数据拷贝问题,将虚拟地址空间映射到相同的物理内存,当某个进程写数据时,另一个进程马上就能看到,不需要拷贝,提高通信效率。
特点:
- 不用从用户态到内核态的频繁切换和拷贝数据,直接从内存中读取就可以。
- 共享内存是临界资源,所以需要操作时必须要保证原子性。使用信号量或者互斥锁都可以。
- 生命周期随内核。
4.进程调度
在合适的时间以一定策略选择一个就绪进程运行。
1.调度算法
- 先来先服务:先进入系统的作业优先被运行
- 短作业优先调度:优先运行时间短的作业
- 响应比高者优先调度:响应比(作业的响应(等待+运行)时间和运行时间的比值)
- 优先数调度
- 循环轮转调度:把所有进程按先进先出原则排成队列
2.Linux进程调度
普通进程:
- 采用动态优先级调度
- 调度程序周期性的修改优先级(避免饥饿)
实时进程:
- 采用静态优先级调度
- 由用户预先指定,以后不会改变
3.Linux进程的优先级
动态优先级:
- 在进程运行期间可以按调度策略改变
- 非实时进程采用动态优先级,由调度程序计算
- 只要进程占用CPU,优先级就随时间流失而不断减小
- task_struct的counter表示动态优先级
静态优先级:
- 进程创建时指定,或由用户修改
4.进程切换
- 内核挂起当前CPU上的进程并回复之前挂起的某个进程
- 任务切换,上下文切换
进程切换与中断上下文切换有差别:中断前后在同一进程上下文中,只是用户态向内核态执行
进程上下文包含了进程执行需要的所有信息:
- 用户地址空间:包括程序代码,数据,用户堆栈等
- 控制信息:进程描述符,内核堆栈等
- 硬件上下文(注意中断也要保存硬件上下文,只是保存方法不同)
两个进程A,B切换的基本过程:
- 正在运行用户态进程A
- 发生中断(例如时钟中断)
- 保存当前进程的cs:eip/esp/eflags到内核堆栈
- 从内核堆栈装入ISR中断服务例程的cs:eip和ss:esp
- SAVE_ALL //保存现场,已进入内核中断处理过程
- 中断处理过程中或者中断返回前调用schedule()
- 其中的switch_to做了进程的上下文切换
- 运行用户态进程B,(B曾经通过以上步骤被切换出去过)
- RESTORE_ALL // 恢复现场
- iret // 中断返回
- 继续运行用户态B
二.线程
线程是进程中的一个实体,是操作系统独立调度和分配的基本单位,由线程 ID、程序计数器、寄存器集合和堆栈组成。引入线程是为了减少程序并发执行的开销,进一步提高操作系统的并发性能。
2.1 线程和进程的区别
调度: 进程是分配资源的基本单位,而线程是独立调度的基本单位。
资源: 进程拥有系统资源,而线程只有一点运行必需的资源。如果线程也是分配资源的单位,切换就需要较大开销,引入没有意义。
开销: 进程切换涉及当前 CPU 环境的保存和设置,但线程切换只需要保存和设置少量的寄存器容量。
地址空间: 进程的地址空间互相独立,同一进程的线程共享进程资源,进程内的线程对其他进程不可见。
通信: 进程通信需要同步和互斥手段的辅助,保证数据一致性。线程可以直接读写进程数据段(全局变量)来进行通信。
2.2 线程实现
内核级线程 1:1 实现
内核通过操纵调度器对线程进行调度,并将线程的任务映射到处理器上。程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口,轻量级进程,即通常意义上的线程。
优点:当一个线程被阻塞时,允许其他线程继续执行。
缺点:代价相对较高,需要在用户态和内核态来回切换。
用户级线程 1:N 实现
从广义上讲,一个线程只要不是内核线程,就可以认为是用户线程。狭义上的用户线程指的完全建立在用户空间的线程库上,系统内核不能感知到用户线程的存在及其是如何实现的。
优点:由于线程管理在用户空间进行,不需要切换到内核态,开销小,支持大规模并发。
缺点:一个线程在使用内核服务时被阻塞,整个进程都会被阻塞。
混合方式 N:M 实现
混合模式下既存在用户线程,也存在轻量级进程。用户线程完全建立在用户空间中,因此开销依然很小,可以支持大规模并发。轻量级进程作为用户线程和内核线程之间的桥梁,使用内核提供的线程调度功能及处理器映射,降低整个进程阻塞的风险。
2.2.1 linux创建线程 CreateThread( )
// 用线程实现并发画圆和画方
void DrawCircleAndRect(){
CreateThread(0, 0, DrawCircle, 0);
CreateThread(0, 0, DrawRect, 0);
}
// 创建线程的关键:把 DrawCircle() 和 DrawRect() 作为参数传给 CreateThread( )
2.3 临界资源 和 临界区
临界资源(Critical Resource):一次只允许一个进程独占访问(使用)的资源
比如:图中的 共享变量i
临界区(Critical Section):进程中访问临界资源的程序段
比如:图中标红区域
临界区和临界资源的访问特点:
- 排他性
- 并发进程不能同时进入临界区
临界区访问机制的四个原则:
- 忙则等待:当临界区忙时,其他进程必须在临界区外等待
- 空闲让进:当没有进程处于临界区时,任何有权进程可进入临界区
- 有限等待:进程进入临界区的请求应在有限时间内得到满足
- 让权等待:进入等待状态的进程放弃CPU,让其他进程有机会得到CPU
三.死锁
1.死锁的定义
两个或多个进程无限期的等待永远不会发生的条件的一种状态。(结果:相关的每个进程永远阻塞)
2.产生原因
- 互斥
- 不可剥夺
- 请求并保持
- 循环等待
3.预防死锁
- 破坏互斥条件
允许系统资源共享,但有的资源不可能同时访问,如打印机等临界资源。
- 破坏不可剥夺条件
允许剥夺其他进程已占有的资源,但释放已获得的资源可能会造成前一段工作的失效。
- 破坏部分分配条件
采用预先资源分配法,在进程运行前一次性分配它需要的所有资源,缺点是有些资源可能仅在运行初期或快结束时才使用。
- 破坏循环等待条件
采用顺序资源分配法, 给系统资源编号,规定每个进程必须按编号递增的顺序请求资源。
避免死锁:不破坏产生死锁的条件,而是在资源分配过程中,用某种方法去评估若分配资源是否会让系统进入死锁状态,若是,则拒绝此次分配资源。从而避免死锁产生。
**检测和恢复死锁:**允许死锁发生,但可以通过检测机制及时检测出死锁状态,并精确确定与死锁有关的进程和资源,然后采取措施将死锁清除,将进程从死锁状态解脱出来。
4. 解除死锁
- 资源剥夺法
挂起某些死锁进程,抢占其资源,分配给其它死锁进程。
- 撤销进程法
强制撤销部分甚至全部死锁进程,可以按进程优先级和撤销代价进行。
- 进程回退法
让一个或多个进程回退到足以避免死锁的地步,要求系统保持进程的历史信息,设置还原点。
5. Windows和Linux采用了哪种死锁解决方案?
鸵鸟策略:忽略死锁,当死锁发生时假装没有看见。
四.内存
实际存储体系:
CPU + Cache、内存、辅存。
存储管理的功能:
- 地址映射
- 虚拟存储
- 内存分配
- 存储保护
地址映射
- 把程序中的地址(虚拟地址/逻辑地址) 转换成内存的真实地址(物理地址)的过程
- 地址重定位,地址重映射
虚拟内存
- 虚拟内存是面向用户的虚拟封闭存储空间
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
时假装没有看见。
四.内存
实际存储体系:
CPU + Cache、内存、辅存。
存储管理的功能:
- 地址映射
- 虚拟存储
- 内存分配
- 存储保护
地址映射
- 把程序中的地址(虚拟地址/逻辑地址) 转换成内存的真实地址(物理地址)的过程
- 地址重定位,地址重映射
虚拟内存
- 虚拟内存是面向用户的虚拟封闭存储空间
[外链图片转存中…(img-UuaxsPDt-1715510833286)]
[外链图片转存中…(img-QX5XrJVd-1715510833286)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!