目前更新到5.3节,请在http://dl.dbank.com/c02ackpwp6下载5.3节的全部文档
本节源代码请在http://dl.dbank.com/c0fp2g5z9s下载
释放信号量的函数MDS_SemGive的代码如下:
00143
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211
00212
00213
00214
00143行,函数返回值RTN_SUCD代表释放信号量成功,RTN_FAIL代表释放信号量失败。入口参数pstrSem为释放的信号量的指针。
00152~00155行,对入口参数pstrSem进行检查,若为空则返回失败。
00157行,锁中断,防止多个任务同时操作信号量。
00160行,若信号量处于空状态走此分支。
00163行,从信号量pstrSem的调度表里面被阻塞的任务中查找需要被释放的任务,返回该任务的TCB。
00166行,若该任务的TCB不为空,说明有被阻塞的任务,走此分支。
00169行,将这个任务从信号量调度表中删除。
00172行,如果这个任务处于delay表中,走此分支。
00174行,获取这个任务TCB中挂接到delay表的节点。
00175行,将该任务从delay表中删除。
00178行,将该任务的标志修改为不在delay表中。
00182行,清除该任务的pend状态。
00185行,将该任务的返回值保存在TCB的strTaskOpt.uiDelayTick中,当该任务重新运行时就会得到RTN_SUCD这个返回值。注意,该任务是当前运行任务使用信号量释放出来的任务,而不是当前正在运行的任务。
00188~00196行,将该任务添加到ready表中,并将任务的状态修改为ready状态。
00198行,对信号量操作完毕,解锁中断。
00201行,已经有任务新加入到ready表中,使用软中断函数调度任务。
00205行,信号量中没有被阻塞的任务,走此分支。
00207行,走到此分支,说明信号量是在空的状态下被释放,并且没有被阻塞的任务需要获取信号量,因此直接将信号量置为满状态。
00211行,对信号量操作完毕,解锁中断。
00213行,对信号量操作完毕,返回释放信号量成功。
如果有任务被信号量阻塞,在中断中使用MDS_SemGive函数释放信号量时并不会立刻发生任务调度,因为MDS_SemGive函数所使用的软中断调度函数MDS_TaskSwiSched无法在中断中执行,任务调度需要等到下个tick中断到来时才能执行,因此在中断中释放信号量激活的最高优先级任务可能会有一个小的延迟后才能运行。在后面章节我们会将Mindows移植到cortex内核的芯片上,cortex内核拥有pendsv软中断,使用pendsv软中断可以解决这个问题,不会有延迟产生。
MDS_SemGetActiveTask函数的功能是从信号量的调度表中获取需要被最先释放的任务,如果信号量采用的是优先级调度方式,则使用MDS_TaskHighestPrioGet函数从信号量调度表中找出最高优先级任务的TCB,如果信号量采用的是先进先出的调度方式,则从信号量调度表中取出最先入链表的头节点。函数比较简单,不再详细介绍,代码如下:
00424
00425
00426
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454
00455
MDS_TaskDelFromSemTab函数的功能是将任务从信号量调度表中删除,如果信号量采用的是优先级调度方式,则从任务TCB中找出任务的相关属性,使用MDS_TaskDelFromSchedTab函数将任务从信号量调度表中删除,这个过程与从ready表中删除任务的过程是一样的。如果信号量采用的是先进先出的调度方式,则从信号量调度表中删除最先入链表的头节点。函数比较简单,不再详细介绍,代码如下:
00388
00389
00390
00391
00392
00393
00394
00395
00396
00397
00398
00399
00400
00401
00402
00403
00404
00405
00406
00407
00408
00409
00410
00411
00412
00413
00414
00415
00416
00417
MDS_SemGive函数一次只能释放一个被阻塞的任务,MDS_SemFlush可以一次性释放被信号量阻塞的所有任务。MDS_SemFlush函数的原理与MDS_SemGive函数是一样的,MDS_SemFlush函数会循环查找信号量调度表中的所有任务,将他们全部释放掉,不再做详细介绍,代码如下,请读者自行分析:
00301
00302
00303
00304
00305
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235
00236
00237
00238
00239
00240
00241
00242
00243
00244
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255
00256
00257
00258
00259
00260
00261
00262
00263
00264
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
当信号量不再需要使用时,可以使用MDS_SemDelete函数删除信号量,释放信号量所占用的资源。删除信号量时,被阻塞在该信号量上的所有任务全部会被激活,重新挂入ready表中参与任务调度,这个过程使用MDS_SemFlushValue函数就可以实现。MDS_SemDelete函数比较简单,代码如下,不再详细介绍。
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324
00325
00326
00327
00328
本节新增加了一个pend任务状态,那么在任务调度的时候也需要对这个状态进行处理,需要在MDS_TaskDelayTabSched函数里增加对pend状态的任务的处理,该函数改动较小,只是增加了一个对pend状态处理的分支。
00544
00545
……
00588
00589
00590
00591
00592
00593
00594
00595
00596
00597
00598
00599
00600
00601
……
00631
00590行,走到此处说明delay表中有时间耗尽的任务,如果这个任务是pend状态,那么走此分支。
00593行,走到此处说明该任务是pend状态,并且时间耗尽,需要从信号量调度表删除。
00596行,清除任务的pend状态。
00599行,将返回值RTN_SMTKTO存入超时任务的TCB中的strTaskOpt.uiDelayTick变量中,当这个超时任务返回时,返回值RTN_SMTKTO可以说明这个任务是由于时间耗尽超时返回的。
任务增加了pend状态,还影响到了MDS_TaskDelete函数,在删除处于pend状态的任务时需要将这个任务从相关的信号量调度表中拆除,MDS_TaskDelete函数新增的代码如下:
00121
00122
……
00167
00168
00169
00170
00171
00172
00173
00174
……
00191
当我们需要串行访问一个需要多步骤操作的资源时,就可以使用二进制信号量对其进行保护,例如对串口、FLASH等外设操作工程的保护。在使用信号量保护资源时要避免出现信号量死锁的情况,所谓信号量死锁就是指每个任务占有一个信号量,它们都因无法获取到对方已占有的信号量而无法运行,因此本身占有的信号量也无法被释放,这样,多个任务之间形成互相等待信号量的情况,所有任务都处于一种互相等待的永久等待状态,形成死锁。例如任务A已经获取到了信号量a,任务B已经获取到了信号量b,但任务A需要获取到信号量b才能继续运行,而任务B则需要获取到信号量a才能继续运行,这样2个任务就会形成死锁,这两个任务也就永远无法运行了。
void { } | void { } |
前面我们主要介绍了二进制信号量互斥的作用,二进制信号量互斥的这个特点也可以用来触发任务,当同步事件使用。例如,当我们需要由任务B来触发任务A的运行时,先将信号量a初始化为空状态,由任务A获取信号量a,由任务B释放a。当任务B没有释放信号量a时,任务A就会因为获取不到信号量a而被阻塞,一旦任务B释放了信号量a,任务B就会被激活,开始运行。
void { (void)MDS_SemTake(&a, } | void { } |
到目前为止操作系统已经有了ready表、delay表和多个信号量表,系统运行时需要不断的更新任务TCB中的结构与各种调度表之间的关系。strTcbQue结构中的strQueHead节点可以挂接到ready表或者信号量调度表,strDelayQue结构中的strQueHead节点可以挂接到delay表,pstrSem指针需要指向阻塞任务的信号量。