************************************************************************************************
* 事件标志管理 (EVENT FLAGS MANAGEMENT)
*
* OSFlagAccept() 检查事件标志组函数(标志组的指针、事件标志位、等待事件标志位的方式、错误码指针)
* OSFlagCreate() 建立一个事件标志组(初值、错误码)
* OSFlagDel() 删除一个事件标志组(指针、条件值、错误值)
* OSFlagPend() 等待事件标志组的事件标志位(事件组指针、需要检查的标志位、等待事件标志位的方式、
* 允许等待的时钟节拍、出错代码的时钟节拍)
* OSFlagPost() 置位或清0事件标志组中的标志位(指针、标志位、条件值、错误码)
* OSFlagQuery() 查询事件标志组的当前事件标志状态(事件标志组的指针、错误代码的指针)
************************************************************************************************
************************************************************************************************
* 消息邮箱管理 (MESSAGE MAILBOX MANAGEMENT)
*
* OSMboxAccept () 查看消息邮箱(消息邮箱指针)
* OSMboxCreate () 建立并初始化一个消息邮箱(msg 参数不为空含内容)
* OSMboxDel () 删除消息邮箱(消息邮箱指针、删除条件、出错代码指针)
* OSMboxPend () 等待一个消息邮箱函数(消息邮箱指针、允许等待的时钟节拍、代码错误指针)
* OSMboxPost () 发送消息函数(消息邮箱指针、即将实际发送给任务的消息)
* OSMboxPostOpt () 向邮箱发送一则消息(邮箱指针、消息、条件)
* OSMboxQuery ()查询一个邮箱的当前状态(信号量指针、状态数据结构指针)
************************************************************************************************
************************************************************************************************
* 内存管理项 (MEMORY MANAGEMENT)
* OSMemCreate () 建立并初始化一块内存区(起始地址、需要的内存块数目、内存块大小、返回错误的指针)
* OSMemGet () 从内存区分配一个内存块
* OSMemPut () 释放一个内存块,内存块必须释放回原先申请的内存区
* OSMemQuery () 得到内存区的信息
************************************************************************************************
************************************************************************************************
* 互斥型信号量项管理 (MUTUAL EXCLUSION SEMAPHORE MANAGEMENT)
*
* OSMutexAccept () 无等待地获取互斥型信号量[任务不挂起](信号量指针、错误代码)
* OSMutexCreate () 建立并初始化一个互斥型信号量(优先级继承优先级(PIP)、出错代码指针)
* OSMutexDel () 删除互斥型信号量(信号指针、删除条件、错误指针)
* OSMutexPend ()等待一个互斥型信号量(指针、等待超时时限、出错代码指针)
* OSMutexPost ()释放一个互斥型信号量(互斥型信号量指针)
* OSMutexQuery () 查询一个互斥型信号量的当前状态(互斥型信号量指针、状态数据结构指针)
************************************************************************************************
************************************************************************************************
* 消息队列管理 (MESSAGE QUEUE MANAGEMENT)
*
* OSQAccept () 检查消息队列中是否已经有需要的消息(消息队列的指针)
* OSQCreate () 建立一个消息队列(消息内存区的基地址(指针数组)、消息内存区的大小)
* OSQDel () 删除一个消息队列(消息队列指针、删除条件、错误指针)
* OSQFlush () 清空消息队列(指向得到消息队列的指针)
* OSQPend ()任务等待消息队列中的消息(消息队列指针、允许等待的时钟节拍、代码错误指针)
* OSQPost ()向消息队列发送一则消息FIFO(消息队列指针、发送的消息)
* OSQPostFront () 向消息队列发送一则消息LIFO(消息队列指针、发送的消息)
* OSQPostOpt () 向消息队列发送一则消息LIFO(消息队列指针、发送的消息、发送条件)
* OSQQuery () 查询一个消息队列的当前状态(信号量指针、状态数据结构指针)
************************************************************************************************
/***********************************************************************************************
* 消息队列数据 (MESSAGE QUEUE DATA)
************************************************************************************************
*/
队列控制块是一个用于维护消息队列信息的数据结构,它包含了以下的一些域。这里,仍然在各个变量前加入
一个[.]来表示它们是数据结构中的一个域。
* 1).OSQPtr: 在空闲队列控制块中链接所有的队列控制块。一旦建立了消息队列,该域就不再有用了。
* 2).OSQStart: 是指向消息队列的指针数组的起始地址的指针。用户应用程序在使用消息队列之前必须先定义该数组
* 3).OSQEnd: 是指向消息队列结束单元的下一个地址的指针。该指针使得消息队列构成一个循环的缓冲区。
* 4).OSQIn: 是指向消息队列中插入下一条消息的位置的指针。当.OSQIn和.OSQEnd相等时,.OSQIn被调整指向
消息队列的起始单元。
* 5).OSQOut: 是指向消息队列中下一个取出消息的位置的指针。当.OSQOut和.OSQEnd相等时,.OSQOut被调整指向消息队列的起始单元。
* 6).OSQSize: 是消息队列中总的单元数。该值是在建立消息队列时由用户应用程序决定的。在uC/OS-II中,该值最大可以是65,535。
* 7).OSQEntries: 是消息队列中当前的消息数量。当消息队列是空的时,该值为0。当消息队列满了以后,该值和 .OSQSize值一样。 在消息队列刚刚建立时,该值为0。
***********************************************************************************************
*/
/***********************************************************************************************
* 信号量管理 (SEMAPHORE MANAGEMENT)
*
* OSSemAccept() 无条件地等待请求一个信号量函数
* OSSemCreate() 建立并初始化一个信号量(输入一个信号量值)
* OSSemDel() 删除一个信号量(信号指针、删除条件、错误指针)
* OSSemPend ()等待一个信号量函数(信号量指针、允许等待的时钟节拍、代码错误指针)
* OSSemPost () 发出一个信号量函数(信号量指针)
* OSSemQuery () 查询一个信号量的当前状态(信号量指针、状态数据结构指针)
*/
/*
************************************************************************************************
* 任务管理 (TASK MANAGEMENT)
*
* OSTaskChangePrio () 改变一个任务的优先级(任务旧的优先级、任务新的优先级)
* OSTaskCreate () 建立任务(任务代码指针、传递参数指针、分配任务堆栈栈顶指针、任务优先级)
* OSTaskCreateExt () 建立扩展任务(任务代码指针/传递参数指针/分配任务堆栈栈顶指针/分配任务优先级
* //(未来的)优先级标识(与优先级相同)/分配任务堆栈栈底指针/指定堆栈的容量(检验用)
* //指向用户附加的数据域的指针/建立任务设定选项)
* OSTaskDel () 删除任务(任务的优先级)
* OSTaskDelReq () 请求一个任务删除其它任务或自身?(任务的优先级)
* OSTaskResume () 唤醒一个用OSTaskSuspend()函数挂起的任务(任务的优先级)
* OSTaskStkChk () 检查任务堆栈状态(任务优先级、检验堆栈数据结构)
* OSTaskSuspend () 无条件挂起一个任务(任务优先级)
* OSTaskQuery ()获取任务信息(任务指针、保存数据结构指针)
************************************************************************************************
*/
/*
************************************************************************************************
* 时钟管理项 (TIME MANAGEMENT)
*
* OSTimeDly ()任务延时函数(时钟节拍数)
* OSTimeDlyHMSM ()将一个任务延时若干时间(设定时、分、秒、毫秒)
* OSTimeDlyResume () 唤醒一个用OSTimeDly()或OSTimeDlyHMSM()函数的任务(优先级)
* OSTimeGet ()获取当前系统时钟数值
* OSTimeSet ()设置当前系统时钟数值
************************************************************************************************
*/
/***********************************************************************************************
* 混杂函数定义
*
* OSInit()初始化UCOS-II函数
* OSIntEnter()中断函数正在执行
* OSIntExit() 中断函数已经完成(脱离中断)
* OSSchedLock() 给调度器上锁
* OSSchedUnlock() 给调度器解锁
* OSStart() 启动多个任务
* OSStatInit()统计任务初始化
* OSVersion() 获得版本号
************************************************************************************************/
/***********************************************************************************************
* 内部函数原型 INTERNAL FUNCTION PROTOTYPES
* 你在应用程序中不能使用它们 (Your application MUST NOT call these functions)
*
* OS_Dummy() 建立一个虚拟函数
* OS_EventTaskRdy() 使一个任务进入就绪态(OS_EVENT *pevent, void *msg, INT8U msk)
* OS_EventTaskWait() 使一个任务进入等待某事件发生状态(ECB指针)
* OS_EventTO()由于超时而将任务置为就绪态(ECB指针)
* OS_EventWaitListInit() 事件控制块列表初始化(事件控制块指针)
* OS_FlagInit() 初始化事件标志结构
* OS_FlagUnlink() 把这个OS_FLAG_NODE从事件标志组的等待任务链表中删除(OS_FLAG_NODE *pnode)
* OS_MemInit()初始化内存分区
* OS_QInit() 初始化事件队列结构
* OS_Sched() 任务调度函数
* OS_TaskIdle() 空闲任务函数(指向一个数据结构)
* OS_TaskStat() 统计任务(指向一个数据结构)
* OS_TCBInit()初始化任务控制块TCB(优先级指针、栈顶指针、栈底指针、任务标志符、
* 堆栈容量、扩展指针、选择项)
************************************************************************************************
*/
Linux内核设计与实现 十、内核同步方法
手把手教Linux驱动5-自旋锁、信号量、互斥体概述
== 基础概念: ==
并发 :多个执行单元同时进行或多个执行单元微观串行执行,宏观并行执行
竞态 :并发的执行单元对共享资源(硬件资源和软件上的全局变量)的访问而导致的竟态状态。
临界资源 :多个进程访问的资源
临界区 :多个进程访问的代码段
== 并发场合: ==
1、单CPU之间进程间的并发 :时间片轮转,调度进程。 A进程访问打印机,时间片用完,OS调度B进程访问打印机。
2、单cpu上进程和中断之间并发 :CPU必须停止当前进程的执行中断
3、多cpu之间
4、单CPU上中断之间的并发
== 使用偏向: ==
==信号量用于进程之间的同步,进程在信号量保护的临界区代码里面是可以睡眠的(需要进行进程调度),这是与自旋锁最大的区别。==
信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。它负责协调各个进程,以保证他们能够正确、合理的使用公共资源。它和spin lock最大的不同之处就是:无法获取信号量的进程可以睡眠,因此会导致系统调度。
1、==用于进程与进程之间的同步==
2、==允许多个进程进入临界区代码执行,临界区代码允许睡眠;==
3、信号量本质是==基于调度器的==,在UP和SMP下没有区别;进程获取不到信号量将陷入休眠,并让出CPU;
4、不支持进程和中断之间的同步
5、==进程调度也是会消耗系统资源的,如果一个int型共享变量就需要使用信号量,将极大的浪费系统资源==
6、信号量可以用于多个线程,用于资源的计数(有多种状态)
==信号量加锁以及解锁过程:==
sema_init(&sp->dead_sem, 0)/ 初始化 /
down(&sema)
临界区代码
up(&sema)
==信号量定义:==
==信号量初始化:==
==dowm函数实现:==
==up函数实现:==
信号量一般可以用来标记可用资源的个数。
举2个生活中的例子:
==dowm函数实现原理解析:==
(1)down
判断sem->count是否 >0,大于0则说明系统资源够用,分配一个给该进程,否则进入__down(sem)
(2)__down
调用__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT)其中TASK_UNINTERRUPTIBLE=2代表进入睡眠,且不可以打断;MAX_SCHEDULE_TIMEOUT休眠最长LONG_MAX时间;
(3)list_add_tail(&waiter.list, &sem->wait_list)
把当前进程加入到sem->wait_list中;
(3)先解锁后加锁
进入__down_common前已经加锁了,先把解锁,调用schedule_timeout(timeout),当waiter.up=1后跳出for循环;退出函数之前再加锁;
Linux内核ARM构架中原子变量的底层实现研究
rk3288 原子操作和原子位操作
原子变量适用于只共享一个int型变量;
1、原子操作是指不被打断的操作,即它是最小的执行单位。
2、最简单的原子操作就是一条条的汇编指令(不包括一些伪指令,伪指令会被汇编器解释成多条汇编指令)
==常见函数:==
==以atomic_inc为例介绍实现过程==
在Linux内核文件archarmincludeasmatomic.h中。 执行atomic_read、atomic_set这些操作都只需要一条汇编指令,所以它们本身就是不可打断的。 需要特别研究的是atomic_inc、atomic_dec这类读出、修改、写回的函数。
所以atomic_add的原型是下面这个宏:
atomic_add等效于:
result(%0) tmp(%1) (v->counter)(%2) (&v->counter)(%3) i(%4)
注意:根据内联汇编的语法,result、tmp、&v->counter对应的数据都放在了寄存器中操作。如果出现上下文切换,切换机制会做寄存器上下文保护。
(1)ldrex %0, [%3]
意思是将&v->counter指向的数据放入result中,并且(分别在Local monitor和Global monitor中)设置独占标志。
(2)add %0, %0, %4
result = result + i
(3)strex %1, %0, [%3]
意思是将result保存到&v->counter指向的内存中, 此时 Exclusive monitors会发挥作用,将保存是否成功的标志放入tmp中。
(4) teq %1, #0
测试strex是否成功(tmp == 0 ??)
(5)bne 1b
如果发现strex失败,从(1)再次执行。
Spinlock 是内核中提供的一种比较常见的锁机制,==自旋锁是“原地等待”的方式解决资源冲突的==,即,一个线程获取了一个自旋锁后,另外一个线程期望获取该自旋锁,获取不到,只能够原地“打转”(忙等待)。由于自旋锁的这个忙等待的特性,注定了它使用场景上的限制 —— 自旋锁不应该被长时间的持有(消耗 CPU 资源),一般应用在==中断上下文==。
1、spinlock是一种死等机制
2、信号量可以允许多个执行单元进入,spinlock不行,一次只能允许一个执行单元获取锁,并且进入临界区,其他执行单元都是在门口不断的死等
3、由于不休眠,因此spinlock可以应用在中断上下文中;
4、由于spinlock死等的特性,因此临界区执行代码尽可能的短;
==spinlock加锁以及解锁过程:==
spin_lock(&devices_lock)
临界区代码
spin_unlock(&devices_lock)
==spinlock初始化==
==进程和进程之间同步==
==本地软中断之间同步==
==本地硬中断之间同步==
==本地硬中断之间同步并且保存本地中断状态==
==尝试获取锁==
== arch_spinlock_t结构体定义如下: ==
== arch_spin_lock的实现如下: ==
lockval(%0) newval(%1) tmp(%2) &lock->slock(%3) 1 <<TICKET_SHIFT(%4)
(1)ldrex %0, [%3]
把lock->slock的值赋值给lockval;并且(分别在Local monitor和Global monitor中)设置独占标志。
(2)add %1, %0, %4
newval =lockval +(1<<16)相当于next+1;
(3)strex %2, %1, [%3]
newval =lockval +(1<<16)相当于next+1;
意思是将newval保存到 &lock->slock指向的内存中, 此时 Exclusive monitors会发挥作用,将保存是否成功的标志放入tmp中。
(4) teq %2, #0
测试strex是否成功
(5)bne 1b
如果发现strex失败,从(1)再次执行。
通过上面的分析,可知关键在于strex的操作是否成功的判断上。而这个就归功于ARM的Exclusive monitors和ldrex/strex指令的机制。
(6)while (lockval.tickets.next != lockval.tickets.owner)
如何lockval.tickets的next和owner是否相等。相同则跳出while循环,否则在循环内等待判断;
* (7)wfe()和smp_mb() 最终调用#define barrier() asm volatile ("": : :"memory") *
阻止编译器重排,保证编译程序时在优化屏障之前的指令不会在优化屏障之后执行。
== arch_spin_unlock的实现如下: ==
退出锁时:tickets.owner++
== 出现死锁的情况: ==
1、拥有自旋锁的进程A在内核态阻塞了,内核调度B进程,碰巧B进程也要获得自旋锁,此时B只能自旋转。 而此时抢占已经关闭,(单核)不会调度A进程了,B永远自旋,产生死锁。
2、进程A拥有自旋锁,中断到来,CPU执行中断函数,中断处理函数,中断处理函数需要获得自旋锁,访问共享资源,此时无法获得锁,只能自旋,产生死锁。
== 如何避免死锁: ==
1、如果中断处理函数中也要获得自旋锁,那么驱动程序需要在拥有自旋锁时禁止中断;
2、自旋锁必须在可能的最短时间内拥有
3、避免某个获得锁的函数调用其他同样试图获取这个锁的函数,否则代码就会死锁;不论是信号量还是自旋锁,都不允许锁拥有者第二次获得这个锁,如果试图这么做,系统将挂起;
4、锁的顺序规则(a) 按同样的顺序获得锁;b) 如果必须获得一个局部锁和一个属于内核更中心位置的锁,则应该首先获取自己的局部锁 c) 如果我们拥有信号量和自旋锁的组合,则必须首先获得信号量;在拥有自旋锁时调用down(可导致休眠)是个严重的错误的;)
== rw(read/write)spinlock: ==
加锁逻辑:
1、假设临界区内没有任何的thread,这个时候任何的读线程和写线程都可以键入
2、假设临界区内有一个读线程,这时候信赖的read线程可以任意进入,但是写线程不能进入;
3、假设临界区有一个写线程,这时候任何的读、写线程都不可以进入;
4、假设临界区内有一个或者多个读线程,写线程不可以进入临界区,但是写线程也无法阻止后续的读线程继续进去,要等到临界区所有的读线程都结束了,才可以进入,可见:==rw(read/write)spinlock更加有利于读线程;==
== seqlock(顺序锁): ==
加锁逻辑:
1、假设临界区内没有任何的thread,这个时候任何的读线程和写线程都可以键入
2、假设临界区内没有写线程的情况下,read线程可以任意进入;
3、假设临界区有一个写线程,这时候任何的读、写线程都不可以进入;
4、假设临界区内只有read线程的情况下,写线程可以理解执行,不会等待,可见:==seqlock(顺序锁)更加有利于写线程;==
读写速度 : CPU >一级缓存 >二级缓存 >内存 ,因此某一个CPU0的lock修改了,其他的CPU的lock就会失效;那么其他CPU就会依次去L1 L2和主存中读取lock值,一旦其他CPU去读取了主存,就存在系统性能降低的风险;
mutex用于互斥操作。
互斥体只能用于一个线程,资源只有两种状态(占用或者空闲)
1、mutex的语义相对于信号量要简单轻便一些,在锁争用激烈的测试场景下,mutex比信号量执行速度更快,可扩展
性更好,
2、另外mutex数据结构的定义比信号量小、
3、同一时刻只有一个线程可以持有mutex
4、不允许递归地加锁和解锁
5、当进程持有mutex时,进程不可以退出。
• mutex必须使用官方API来初始化。
• mutex可以睡眠,所以不允许在中断处理程序或者中断下半部中使用,例如tasklet、定时器等
==常见操作:==
struct mutex mutex_1
mutex_init(&mutex_1)
mutex_lock(&mutex_1)
临界区代码;
mutex_unlock(&mutex_1)
==常见函数:==
=
.plg:编译器编译结果.hex和.bin:可执行文件
.map和.lst:链接文件
.o:目标文件
.crf、.lnp、.d和.axf:调试文件
.opt:保存工程配置信息
.bak:工程备份文件
欢迎分享,转载请注明来源:夏雨云
评论列表(0条)