前段实践在B站进行模拟面试时发现,
模拟面试第四期-已经拿到大厂OFFER的研究生大佬-LINUX卷到飞起
自己对Linux中的同步互斥方法,以及IPC方法,没有很好的理解和总结过。因此,本笔记将总结这部分内容。
这个部分在Linux内核学习笔记中,已经做过关于内部实现的笔记。
Linux驱动-同步互斥与原子变量 - 认真学习的小诚同学
Linux驱动-内核锁的介绍与使用 - 认真学习的小诚同学
Linux驱动-内核自旋锁spinlock的实现 - 认真学习的小诚同学
Linux驱动-内核信号量semaphore的实现 - 认真学习的小诚同学
Linux驱动-内核互斥量mutex的实现 - 认真学习的小诚同学
这里做个总结:
内核锁分为
- 自旋锁(无法获得锁时,当前线程原地等待):原始自旋锁(raw_spinlock_t)、位自旋锁(bit spinlocks)
- 睡眠锁(无法获得锁时,当前线程就会休眠):互斥量、实时互斥锁、信号量、读写信号量等。
SMP(多核):使用原子操作ldrex和strex完成互斥信号量通过自旋锁lock和计数量count,以及一个等待队列(阻塞的线程放在其中)互斥量同样通过自旋锁lock和二值计数量count,以及一个等待队列(阻塞的线程放在其中)
实现了一个快速路径fastpath-在大部分情况下都可以直接在此获得或释放锁,所以互斥量比信号量的效率高
线程同步(synchronization)是指在一定的时间内只允许某一个线程访问某个共享资源。而在此时间内,不允许其它的线程访问该资源。
2.1 互斥量
从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。
2.1.1 API
int pthread_mutex_trylock(pthread_mutex_t *mutex);返回:若成功返回0,否则返回错误编号;
调用pthread_mutex_lock,如果互斥量已经上锁, 调用线程阻塞直到互斥量被解锁。 如果不希望被阻塞,可以使用pthread_mutex_trylock 尝试加锁,无论成功都会返回。解锁int pthread_mutex_unlock(pthread_mutex_t *mutex);
2.1.2 死锁问题
死锁详解和解决办法_避免死锁的三种方法-CSDN博客
【并发 bug 和应对 (死锁/数据竞争/原子性违反;防御性编程和动态分析) 南京大学2022操作系统-P8】 【精准空降到 24:22】
死锁只有同时满足以下四个条件才会发生:
- 互斥:一个资源每次只能被一个进程使用;
- 持有并等待:一个进程请求资源阻塞时,不释放已获得的资源;
- 不可剥夺:进程已获得的资源不能被强行剥夺;
- 环路等待:若干进程之间形成头尾相接的循环等待资源关系。
解决方法就是经典的银行家算法。
2.1.3 示例
2.2 读写锁
与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。
适用场景:读写锁非常适合于对数据结构读的次数远大于写的情况。
2.2.1 API
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);返回:若成功返回0,否则返回错误编号;解锁int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
2.2.2 示例
2.3 信号量
可以理解为一个计数器,表示当前可用共享资源的数量。
- 信号量基于操作系统的 PV 操作,对信号量的操作都是原子操作;
- 信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数;
- 支持信号量组;
- 有两套信号量:System V 和 Semaphore
2.3.1 API
- System V 信号量
- 特性:
- 属于System V IPC(进程间通信)。
- 提供了一组复杂的功能,适合复杂的进程间同步控制。
- 典型操作包括(获取/创建信号量)、(操作信号量)和(控制信号量集)。
- 支持信号量集的概念,可以一次性操作多个信号量,适用于高级同步需求。
- // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
// 控制信号量的相关信息
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
- 使用场景:
- 适用于需要细粒度、复杂同步的多进程应用程序。
- 在需要跨越不同应用程序的持久性IPC时(System V信号量可以在系统重启后仍保持存在,直到显式删除)。
- 复杂性:
- 相对复杂,使用起来需要理解其数据结构和操作指标。
- 相关数据结构和操作通常需要更复杂的设置和管理。需要使用进行一些配置,这是标准的一个例外之处。
- POSIX 信号量
- 特性:
- 属于POSIX标准。
- 提供了更简单易用的信号量API。典型操作包括(初始化信号量)、(等待信号量)、(释放信号量)和(销毁信号量)。
- 可以是命名信号量或无名信号量。命名信号量可以用于进程间同步,而无名信号量通常用于线程间同步。
- 使用场景:
- 适用于进程间或线程间的简单同步。
- 易于使用,集成到标准C库中,大多数现代系统都支持。
- 复杂性:
- 易于使用和管理,适合需要简单同步机制的应用。
- 不支持信号量集,但更适合于一般的多线程和多进程同步场景。
2.3.2 示例
2.4 条件变量
条件变量是一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足。
主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使“条件成立”.
为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
2.4.1 API
2.4.2 示例
参考:一文搞懂六大进程通信机制原理(全网最详细)
3.1 管道
通常指无名管道。
- 数据只能在一个方向上流动, 具有固定的读端和写端
- 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
3.1.1 API
返回值:若成功则返回0,若出错则返回-1; 说明:由参数filedes返回两个文件描述符:filedes[0]为读而打开;filedes[1]为写而打开。
3.1.2 示例
3.2 FIFO
命名管道,它是一种文件类型。
- FIFO可以在无关的进程之间交换数据,与无名管道不同。
- FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中
返回值:成功返回0,出错返回-1;
其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
示例:
3.3 消息队列
消息队列就是消息的链表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
- 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级;
- 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除;
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
3.3.1 API
在以下两种情况下,msgget将创建一个新的消息队列:
- 如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREATE
- key参数为IPC_PRIVATE
函数msgrcv在读取消息队列时,type参数有下面几种情况:
- type == 0,返回队列中的第一个消息;
- type > 0,返回队列中消息类型为 type 的第一个消息;
- type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
服务端程序一直在等待特定类型的消息,当收到该类型的消息以后,发送另一种特定类型的消息作为反馈,客户端读取该反馈并打印出来
3.3.2 示例
服务端程序一直在等待特定类型的消息,当收到该类型的消息以后,发送另一种特定类型的消息作为反馈,客户端读取该反馈并打印出来。
3.4 信号
信号和信号量是完全不同的两个概念
3.4.1 有哪些信号
Linux 系统中有许多信号,其中前面 31 个信号都有一个特殊的名字,对应一个特殊的事件,这些信号都是从 Unix 系统继承下来的,他们还有个名称叫“不可靠信号”,他们有如下的特点:
- 非实时信号不排队,信号的响应会相互嵌套。
- 如果目标进程没有及时响应非实时信号,那么随后到达的该信号将会被丢弃。
- 每一个非实时信号都对应一个系统事件,当这个事件发生时,将产生这个信号。
- 如果进程的挂起信号中含有实时和非实时信号,那么进程优先响应实时信号并且会从大到小依此响应,而非实时信号没有固定的次序。
后面的 31 个信号(从 到 )是 Linux 系统新增的实时信号,也被称为“可靠信号”,这些信号的特征是:
- 实时信号的响应次序按接收顺序排队,不嵌套。
- 即使相同的实时信号被同时发送多次,也不会被丢弃,而会依次挨个响应。
- 实时信号没有特殊的系统事件与之对应
对以上信号,需要着重注意的是:
1,上表中罗列出来的信号的“值”,在 平台下是有效的,但是别的平台的信号值也许跟这个表的不一致。
2,“备注”中注明的事件发生时会产生相应的信号,但并不是说该信号的产生就一定发生了这个事件。事实上,任何进程都可以使用函数 来产生任何信号。
3,信号 和 是两个特殊的信号,他们不能被忽略、阻塞或捕捉,只能按缺省动作来响应。
换句话说,除了这两个信号之外的其他信号,接收信号的目标进程按照如下顺序来做出反应:
A) 如果该信号被阻塞,那么将该信号挂起,不对其做任何处理,等到解除对其阻塞为止。否则进入B。
B) 如果该信号被捕捉,那么进一步判断捕捉的类型:
B1) 如果设置了响应函数,那么执行该响应函数。
B2) 如果设置为忽略,那么直接丢弃该信号。
否则进入C。
C) 执行该信号的缺省动作
3.4.2 API
- 函数:
- 用于设置一个简单的信号处理器。
- 原型:
- 使用时,指定要捕获的信号以及处理信号的函数。
- 函数:
- 提供了更强大和灵活的信号处理设置。
- 原型:
- 可以将的设置为来忽略信号或恢复默认处理。
- 提供了一种可靠的方法来设置信号处理程序,并支持更多功能,如信号阻塞集。
- 函数:
- 发送信号给调用进程本身。
- 原型:
- 实际上相当于调用 。
- 函数:
- 发送信号给指定进程。
- 原型:
- 可以用来向一个或多个进程发送信号。
……
3.5 共享内存
3.5 共享内存
指两个或多个进程共享一个给定的存储区.
- 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取
- 因为多个进程可以同时操作,所以需要进行同步
- 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
3.5.1 API
当用shmget函数创建一段共享内存时,必须指定其size;而如果引用一个已存在的共享内存,将size 指定为0。
当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,随后可以访问。
shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。
shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。
3.5.2 示例
【共享内存+信号量+消息队列】:
- 消息队列将用于初始化通信。在这种情况下,客户端可以请求服务,同时也可以用来通知服务器有新的请求需要处理。
- 共享内存将作为数据交换的主要手段,因为它允许两个进程共享和访问同一内存段。
- 信号量用于同步,以便控制对共享内存的访问,防止竞态条件。
3.6 套接字
Socket支持不同主机上的两个进程IPC。这里不详细介绍了,可以参考网络编程相关知识。
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/cjjbc/45360.html