除了原子操作,中断屏蔽,自旋锁以及自旋锁的衍生锁之外,在Linux内核中还存在着一些其他同步互斥的手段。
综述 在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争。 首先什么是并发与竟态呢?并发(concurrency)指的是多个执行单元同时、并行被执行。而并发的执行单元对共享资源(硬件资源和软件上的全局、静态变量)的访问则容易导致竞态(race conditions)。可能导致并发和竟态的情况有: SMP(Symmetric Multi-Processing),对称多处理结构。SMP是一种紧耦合、共享存储的系统模型,它的特点是多个CPU使用共同的系统总线
Linux里的信号量是一种睡眠锁,调用者试图获得一个已被占用的信号量时,信号量会将其推入一个等待队列,让其睡眠。当该信号量被释放后,等待队列中的任务会被唤醒,获得该信号量。
在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。
对于信号量我们并不陌生。信号量在计算机科学中是一个很容易理解的概念。本质上,信号量就是一个简单的整数,对其进行的操作称为PV操作。进入某段临界代码段就会调用相关信号量的P操作;如果信号量的值大于0,该值会减1,进程继续执行。相反,如果信号量的值等于0,该进程就会等待,直到有其它程序释放该信号量。释放信号量的过程就称为V操作,通过增加信号量的值,唤醒正在等待的进程。
在上一篇文章中,我们探讨了进程间通信的三种常见机制:管道、消息队列和共享内存。我们了解到,这些机制各有其特点和适用场景,可以根据实际需求选择合适的机制进行进程间通信。然而,进程间通信并不仅限于这三种方式。
并发相关的缺陷是最容易制造的,也是最难找到的,为了响应现代硬件和应用程序的需求,Linux 内核已经发展到同时处理更多事情的时代。这种变革使得内核性能及伸缩性得到了相当大的提高,然而也极大提高了内核编程的复杂性。
1、信号量的定义: struct semaphore { spinlock_t lock; unsigned int count; struct list_head wait_list; }; 在linux中,信号量用上述结构体表示,我们可以通过该结构体定义一个信号量。 2、信号量的初始化: 可用void sema_init(struct semaphore *sem, int val);直接创建,其中val为信号量初值。也可以用两个宏来定义和初始化信号量的值为1或0: DECLAR
Linux互斥与同步 零、前言 一、Linux线程互斥 1、基本概念及引入 2、互斥量mutex介绍 3、互斥量的使用 4、互斥量原理 二、可重入/线程安全 1、基本概念 2、线程安全 3、重入函数 4、联系与区别 三、常见锁概念 四、Linux线程同步 1、基本概念 2、条件变量的使用 3、条件变量等待 4、条件变量使用规范 五、POSIX信号量 1、信号量概念及介绍 2、信号量的使用 零、前言 本章主要讲解学习Linux中对多线程的执行中的同步与互斥 一、Linux线程互斥 1、基本概念及引入 互
函数原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
信号量(semaphore)本质上是一个计数器,用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。 在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)。
Linux 内核中的同步机制:原子操作、信号量、读写信号量、自旋锁的API、大内核锁、读写锁、大读者锁、RCU和顺序锁。 1、介绍 在现代操作系统里,同一时间可能有多个内核执行流在执行,即使单CPU内核也需要一些同步机制来同步不同执行单元对共享的数据的访问。 主流的Linux内核中的同步机制包括: 原子操作 信号量(semaphore) 读写信号量(rw_semaphore) 自旋锁spinlock 大内核锁BKL(Big Kernel Lock) 读写锁rwlock、 brlock(只包含在2.4内核中
在早期的 Linux内核中,并发的来源相对较少。早期内核不支持对称多处理( symmetric multi processing,SMP),因此,导致并发执行的唯一原因是对硬件中断的服务。这种情况处理起来较为简单,但并不适用于为获得更好的性能而使用更多处理器且强调快速响应事件的系统。
我们使用过windows的都知道,当一个程序被卡死的时候不管怎样都没反应,这样我们就可以打开任务管理器直接强制性的结束这个进程,这个方法的实现就是和Linux上通过生成信号和捕获信号来实现相似的,运行过程中进程捕获到这些信号做出相应的操作使最终被终止。
每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的, 所以进程之间要通信必须通过内核。
在多年前,linux还没有支持对称多处理器SMP的时候,避免并发数据访问相对简单。
文章主要介绍了在Linux系统中,如何利用自旋锁来实现线程之间的同步和互斥。主要包括了自旋锁的定义、工作原理、使用方式和注意事项,并通过实例介绍了如何在C语言中实现自旋锁。
---- Hello、Hello大家好,我是木荣,今天我们继续来聊一聊Linux中多线程编程中的重要知识点,详细谈谈多线程中同步和互斥机制。 同步和互斥 互斥:多线程中互斥是指多个线程访问同一资源时同时只允许一个线程对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的; 同步:多线程同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
前言:非常早之前就接触过同步这个概念了,可是一直都非常模糊。没有深入地学习了解过,最近有时间了,就花时间研习了一下《linux内核标准教程》和《深入linux设备驱动程序内核机制》这两本书的相关章节。趁刚看完,就把相关的内容总结一下。
信号量强调的是线程(或进程)间的同步:“信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在sem_wait的时候,就阻塞在那里)。当信号量为单值信号量时,也可以完成一个资源的互斥访问。信号量测重于访问者对资源的有序访问,在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
总结 Postgresql使用匿名信号量完成进程间的一些同步操作。 匿名信号量由父进程创建在mmap的共享内存内,通过血缘关系继承给子进程,子进程从共享内存中获取信号量数据结构直接使用即可。 Postgresql的信号量分配比较简单,每一个进程拥有一个自己的信号量。初始化后值为1,表示未锁定状态。 加锁后信号量=0。 解锁后信号量=1。 Postgresql的信号量初始化使用的是POSIX接口(SYSTEM V)中的匿名信号量(命名信号量)。 struct PGPROC { ... PGSem
线程 为什么使用线程? 使用fork创建进程以执行新的任务,该方式的代价很高——子进程将父进程的所有资源都复制一遍。 多个进程之间不会直接共享内存。 进程是系统分配资源的基本单位,线程是进程的基本执行
Windows开发环境:Windows 7 64bit、Windows 10 64bit
因为现代操作系统是多处理器计算的架构,必然更容易遇到多个进程,多个线程访问共享数据的情况,如下图所示:
想必各位读者在看了昨天的文章分享之后,大概对线程有了一个比较清楚的认识了,但是昨天讲的东西过于纯理论化,所以在昨天的基础上,今天我们就来进行实战演练,做到活学活用,废话不多说,直接开干吧。
下面是一系列使用信号量实现生产者/消费者场景的类。 “主”进程初始化信号量并等待用户指示活动已全部完成。生产者在循环中随机增加一个信号量值,更新之间的延迟可变。消费者尝试在随机时间从信号量中删除随机数量,也是在循环中。该示例由 5 个类组成:
前言:在大学的时候,我们班级上面都有很多人觉得学习UCOSII(包括UCOSIII)是没什么厉害的,因为很多人都喜欢去学习Linux操作系统,但是,但是,真实的对整个UCOSII操作系统进行学习,我可以保证,如果你是基于源码级别的阅读的话,绝对是不简单的。仅仅是调用几个API的话,是永远用不好UCOSII的操作系统的。还有你真正学通了UCOSII操作系统的话,那么你对Linux操作系统的内核也不会有太大的难度。
而且在中断中发送信号量或队列的时候,务必把使能中断的语句放在初始化freertos之后,尤其是cubemx生成的代码,默认是把中断初始化放在freertos初始化之前,如果这个时候,外部有中断触发,就立刻发送信号量,但是此时却未初始化信号量,导致系统死机在configASSERT( xQueue );
sem: 0 -> 1 -> 0 若临界资源只有1个,则sem设为1,当要使用临界资源时,sem由1变为0,其他人在想申请,则申请不到挂起排队,等待释放临界资源时 sem由0变为1 ,才可以再申请临界资源 这种信号量称为 二元信号量 ,等同于互斥锁
c#信号量Semaphore只允许有限数量的线程进入临界区。信号量主要用于资源有限,我们必须限制使用线程的数量的场景。
相同: 都在 缓存内核 中 读写 , 先进先出 ,不支持 lseek 之类文件定位操作
项目中遇到一个bug,因为接入了几家越狱平台:91、同步推、PP助手,在设备上安装了三个应用,启用其中任意一个,另外二个启动后无法创建发送socket消息,从而导致游戏直接死在登录那里,再次点击登录时线程才会被唤醒(无法发送的原因定位到,是因为在调用sem_post方法后无法将线程唤醒)。之后我尝试将信号量改为条件变量,就再也没有遇到那个问题了。具体改写的几个方法:
同步问题是保证数据安全的情况下,让线程访问资源具有一定的顺序性,从而有效避免饥饿问题,叫做同步。
我正在学习 Zephyr,一个很可能会用到很多物联网设备上的操作系统,如果你也感兴趣,可点此查看帖子zephyr学习笔记汇总。
在Linux下, 线程的互斥量数据类型是pthread_mutex_t 在使用前, 要对它进行初始化:
生产者消费者模型(CP模型)是一种非常经典的设计,常常出现在各种 「操作系统」 书籍中,深受教师们的喜爱;这种模型在实际开发中还被广泛使用,因为它在多线程场景中是十分高效的!
结果面试过程只花了 5 分钟就结束了,面完的时候,天还是依然是亮的,还得在烈日下奔波 1 小时回去。
Semaphore 使用纯粹的内核时间(kernel-time)方式(等待时间很短),并且支持在不同的进程间同步线程(像Mutex)。
实际上就是解释ucore的哲学家就餐怎么实现的,内核级别的信号量怎么实现的,之后给出自己关于用户级别的信号量的设计方案,比较两者异同。
Semaphore是什么? Semaphore是一个计数信号量,底层依赖AQS和CAS来实现,可用来做限流。 初始化Semaphore时设置信号量数量,同一时间只有固定数量的线程可以持有信号量,没有获取信号量的线程进入等待队列排队等待,等持有信号量的线程释放了信号量,会唤醒等待队列中的线程持有信号量并执行。
一 线程间同步 同步:相互之间配合完成一件事情 互斥:保证访问共享资源的完整性(有你没我) POSIX 线程中同步:使用信号量实现 信号量 : 表示一类资源,它的值表示资源的个数 对资源访问: p操作(申请资源) [将资源的值 - 1] .... V操作(释放资源) [将资源的值 + 1] 1.定义信号量 sem_t sem ; 2.初始化信号量 int sem_init(sem_t *sem, int pshared, unsigned int value); 参数: @sem 信号量 @pshared 0:线程间使用 @value 初始化的信号量的值 返回值: 成功返回0,失败返回-1 3.P操作 int sem_wait(sem_t *sem); 4.V操作 int sem_post(sem_t *sem); 二 进程间通信(进程间数据交互) (1)传统进程间通信方式 [1]无名管道 [2]有名管道 [3]信号 (2)System 5 IPC对象进程间通信方式 [1]消息队列 [2]共享内存 [3]信号灯集 (3)socket通信 (4)Android系统中增加Binder进程间通信方式 Linux 支持以上所有进程间通信方式 三 管道进程间通信 (1)无名管道 特点: 只能用于具有亲缘关系进程间通信(具有亲缘关系的进程具有数据拷贝动作(复制父进程创建子进程)) int pipe(int pipefd[2]); 功能:创建一个无名管道 参数: @pipefd 获取操作无名管道的文件描述符 pipefd[0]:读无名管道 pipefd[1]:写无名管道 返回值: 成功返回0,失败返回-1 (2)管道读写规则 读端存在 ,写管道 ---->只要管道没有满,都可以写入数据到管道 读端不存在,写管道 ---->此时写管道没有意义,操作系统会发送SIGPIPE杀死写管道的进程 写端存在, 读管道 ---->此时管道中读取数据,管道中没有数据,读阻塞 写端不存在,读管道 ---->此时管道中读取数据,管道中没有数据,此时不阻塞,立即返回,返回值0 (3)有名管道 特点:可以用于任意进程间通信,它是一种特殊的文件,在文件系统存在名字, 而文件中存放的数据是在内核空间,而不是在磁盘上 1.创建一个有名管道文件 int mkfifo(const char *pathname, mode_t mode); @pathname 有名管道存在的路径 @mode 有名管道的权限 返回值: 成功返回0,失败返回-1 2.打开有名管道文件 open 如果有名管道的一端以只读的方式打开,会阻塞,直到另一端以写(只写或读写)的方式打开 如果有名管道的一端以只写的方式打开,会阻塞,直到另一端以读(只读或读写)的方式打开 3.读写操作 read /write 4.关闭管道文件 close(fd); 四 信号 信号是异步进程间通信方式 进程对信号的响应方式: <1>忽略 SIGKILL 和 SIGSTOP 不能忽略 <2>捕捉 当进程收到信号,此时执行的信号处理函数 <3>默认 大部分信号对进程的默认操作方式都是杀死进程 子进程状态发生改变的时候,操作系统向父进程发送SIGCHLD,默认对它处理方式是忽略 typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 功能:设置进程对信号处理方式 参数: @signum 信号的编号 @handler SIG_IGN : 忽略信号 SIG_DFL : 使用默认处理方式 函数名 : 捕捉方式处理 返回值: 成功返回handler,失败返回SIG_ERR 练习: 如何进行不阻塞,不轮训方式回收僵尸态子进程 2.在进程中设置一个定时器 unsigned int alarm(unsigned int seconds); 参数: @seconds 定时的时间,以秒为单位 注意: 一旦定时时间完成,操作系统就会向进程发送SIGALRM信号 A进程: 读文件,写管道 A进程结束条件:文件没有数据可读 B进程: 读管道,写文件 B进程结束条件:在
其实.NET中的信号量(Semaphore)是操作系统维持的一个整数。当整数位0时。其他线程无法进入。当整数大于0时,线程可以进入。每当一个线程进入,整数-1,线程退出后整数+1。整数不能超过信号量的最大请求数。信号量在初始化的时候可以指定这个整数的初始值。
这样看来,这段程序保护得挺好,在串口在执行接收的过程中不会被中断打断。但如果不注意的话,会被坑,我们来看下面这种情况。
工作中的难点问题正是我们知识技术栈全谱查漏补缺的最佳机遇,有问题不可怕,all in、死磕就完事了,哈哈哈~
并发 是指在某一时间段内能够处理多个任务的能力,而 并行 是指同一时间能够处理多个任务的能力。并发和并行看起来很像,但实际上是有区别的,如下图(图片来源于网络):
答案是用信号量,当然是在线程不多的情况,每个线程分配一个信号量,然后在前面的线程先获取自己的信号量,结束后释放下一个线程的信号量,从而达到有序
领取专属 10元无门槛券
手把手带您无忧上云