2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: ntoskrnl/ke/wait.c
5 * PURPOSE: Manages waiting for Dispatcher Objects
6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
10 /* INCLUDES ******************************************************************/
14 #include <internal/debug.h>
16 /* PRIVATE FUNCTIONS *********************************************************/
21 KiWaitSatisfyAll(PKWAIT_BLOCK FirstBlock
)
23 PKWAIT_BLOCK WaitBlock
= FirstBlock
;
24 PKTHREAD WaitThread
= WaitBlock
->Thread
;
26 /* Loop through all the Wait Blocks, and wake each Object */
29 /* Make sure it hasn't timed out */
30 if (WaitBlock
->WaitKey
!= STATUS_TIMEOUT
)
33 KiSatisfyObjectWait((PKMUTANT
)WaitBlock
->Object
, WaitThread
);
36 /* Move to the next block */
37 WaitBlock
= WaitBlock
->NextWaitBlock
;
38 } while (WaitBlock
!= FirstBlock
);
44 KiWaitTest(IN PVOID ObjectPointer
,
45 IN KPRIORITY Increment
)
47 PLIST_ENTRY WaitEntry
, WaitList
;
48 PKWAIT_BLOCK WaitBlock
;
50 PKMUTANT FirstObject
= ObjectPointer
;
53 /* Loop the Wait Entries */
54 WaitList
= &FirstObject
->Header
.WaitListHead
;
55 WaitEntry
= WaitList
->Flink
;
56 while ((FirstObject
->Header
.SignalState
> 0) && (WaitEntry
!= WaitList
))
58 /* Get the current wait block */
59 WaitBlock
= CONTAINING_RECORD(WaitEntry
, KWAIT_BLOCK
, WaitListEntry
);
60 WaitThread
= WaitBlock
->Thread
;
61 WaitStatus
= STATUS_KERNEL_APC
;
63 /* Check the current Wait Mode */
64 if (WaitBlock
->WaitType
== WaitAny
)
66 /* Easy case, satisfy only this wait */
67 WaitStatus
= (NTSTATUS
)WaitBlock
->WaitKey
;
68 KiSatisfyObjectWait(FirstObject
, WaitThread
);
71 /* Now do the rest of the unwait */
72 KiUnwaitThread(WaitThread
, WaitStatus
, Increment
);
73 WaitEntry
= WaitList
->Flink
;
79 KiUnlinkThread(IN PKTHREAD Thread
,
80 IN NTSTATUS WaitStatus
)
82 PKWAIT_BLOCK WaitBlock
;
85 /* Update wait status */
86 Thread
->WaitStatus
|= WaitStatus
;
88 /* Remove the Wait Blocks from the list */
89 WaitBlock
= Thread
->WaitBlockList
;
93 RemoveEntryList(&WaitBlock
->WaitListEntry
);
95 /* Go to the next one */
96 WaitBlock
= WaitBlock
->NextWaitBlock
;
97 } while (WaitBlock
!= Thread
->WaitBlockList
);
99 /* Remove the thread from the wait list! */
100 if (Thread
->WaitListEntry
.Flink
) RemoveEntryList(&Thread
->WaitListEntry
);
102 /* Check if there's a Thread Timer */
103 Timer
= &Thread
->Timer
;
104 if (Timer
->Header
.Inserted
)
106 /* Remove the timer */
107 Timer
->Header
.Inserted
= FALSE
;
108 RemoveEntryList(&Timer
->TimerListEntry
);
109 //KiRemoveTimer(Timer);
112 /* Increment the Queue's active threads */
113 if (Thread
->Queue
) Thread
->Queue
->CurrentCount
++;
116 /* Must be called with the dispatcher lock held */
119 KiUnwaitThread(IN PKTHREAD Thread
,
120 IN NTSTATUS WaitStatus
,
121 IN KPRIORITY Increment
)
123 /* Unlink the thread */
124 KiUnlinkThread(Thread
, WaitStatus
);
126 /* Tell the scheduler do to the increment when it readies the thread */
127 ASSERT(Increment
>= 0);
128 Thread
->AdjustIncrement
= (SCHAR
)Increment
;
129 Thread
->AdjustReason
= AdjustUnwait
;
131 /* Reschedule the Thread */
132 KiReadyThread(Thread
);
137 KiAcquireFastMutex(IN PFAST_MUTEX FastMutex
)
139 /* Increase contention count */
140 FastMutex
->Contention
++;
142 /* Wait for the event */
143 KeWaitForSingleObject(&FastMutex
->Gate
,
151 // This routine exits the dispatcher after a compatible operation and
152 // swaps the context to the next scheduled thread on the current CPU if
155 // It does NOT attempt to scan for a new thread to schedule.
159 KiExitDispatcher(IN KIRQL OldIrql
)
161 PKPRCB Prcb
= KeGetCurrentPrcb();
162 PKTHREAD Thread
, NextThread
;
165 /* Make sure we're at synchronization level */
166 ASSERT_IRQL_EQUAL(SYNCH_LEVEL
);
168 /* Check if we have deferred threads */
169 KiCheckDeferredReadyList(Prcb
);
171 /* Check if we were called at dispatcher level or higher */
172 if (OldIrql
>= DISPATCH_LEVEL
)
174 /* Check if we have a thread to schedule, and that no DPC is active */
175 if ((Prcb
->NextThread
) && !(Prcb
->DpcRoutineActive
))
177 /* Request DPC interrupt */
178 HalRequestSoftwareInterrupt(DISPATCH_LEVEL
);
181 /* Lower IRQL and exit */
185 /* Make sure there's a new thread scheduled */
186 if (!Prcb
->NextThread
) goto Quickie
;
189 KiAcquirePrcbLock(Prcb
);
191 /* Get the next and current threads now */
192 NextThread
= Prcb
->NextThread
;
193 Thread
= Prcb
->CurrentThread
;
195 /* Set current thread's swap busy to true */
196 KiSetThreadSwapBusy(Thread
);
198 /* Switch threads in PRCB */
199 Prcb
->NextThread
= NULL
;
200 Prcb
->CurrentThread
= NextThread
;
202 /* Set thread to running */
203 NextThread
->State
= Running
;
205 /* Queue it on the ready lists */
206 KxQueueReadyThread(Thread
, Prcb
);
209 Thread
->WaitIrql
= OldIrql
;
211 /* Swap threads and check if APCs were pending */
212 PendingApc
= KiSwapContext(Thread
, NextThread
);
215 /* Lower only to APC */
216 KeLowerIrql(APC_LEVEL
);
219 KiDeliverApc(KernelMode
, NULL
, NULL
);
220 ASSERT(OldIrql
== PASSIVE_LEVEL
);
223 /* Lower IRQl back */
225 KeLowerIrql(OldIrql
);
228 /* PUBLIC FUNCTIONS **********************************************************/
235 KeDelayExecutionThread(IN KPROCESSOR_MODE WaitMode
,
236 IN BOOLEAN Alertable
,
237 IN PLARGE_INTEGER Interval OPTIONAL
)
240 PKWAIT_BLOCK TimerBlock
;
241 PKTHREAD Thread
= KeGetCurrentThread();
244 PLARGE_INTEGER OriginalDueTime
;
245 LARGE_INTEGER DueTime
, NewDueTime
, InterruptTime
;
247 /* If this is a user-mode wait of 0 seconds, yield execution */
248 if (!(Interval
->QuadPart
) && (WaitMode
!= KernelMode
))
250 /* Make sure the wait isn't alertable or interrupting an APC */
251 if (!(Alertable
) && !(Thread
->ApcState
.UserApcPending
))
253 /* Yield execution */
258 /* Setup the original time and timer/wait blocks */
259 OriginalDueTime
= Interval
;
260 Timer
= &Thread
->Timer
;
261 TimerBlock
= &Thread
->WaitBlock
[TIMER_WAIT_BLOCK
];
263 /* Check if the lock is already held */
264 if (!Thread
->WaitNext
) goto WaitStart
;
266 /* Otherwise, we already have the lock, so initialize the wait */
267 Thread
->WaitNext
= FALSE
;
270 /* Start wait loop */
273 /* Disable pre-emption */
274 Thread
->Preempted
= FALSE
;
276 /* Check if a kernel APC is pending and we're below APC_LEVEL */
277 if ((Thread
->ApcState
.KernelApcPending
) && !(Thread
->SpecialApcDisable
) &&
278 (Thread
->WaitIrql
< APC_LEVEL
))
280 /* Unlock the dispatcher */
281 KiReleaseDispatcherLock(Thread
->WaitIrql
);
285 /* Check if we have to bail out due to an alerted state */
286 WaitStatus
= KiCheckAlertability(Thread
, Alertable
, WaitMode
);
287 if (WaitStatus
!= STATUS_WAIT_0
) break;
289 /* Check if the timer expired */
290 InterruptTime
.QuadPart
= KeQueryInterruptTime();
291 if (InterruptTime
.QuadPart
>= Timer
->DueTime
.QuadPart
)
293 /* It did, so we don't need to wait */
297 /* It didn't, so activate it */
298 Timer
->Header
.Inserted
= TRUE
;
300 /* Handle Kernel Queues */
301 if (Thread
->Queue
) KiActivateWaiterQueue(Thread
->Queue
);
303 /* Setup the wait information */
304 Thread
->State
= Waiting
;
306 /* Add the thread to the wait list */
307 KiAddThreadToWaitList(Thread
, Swappable
);
309 /* Insert the timer and swap the thread */
310 ASSERT(Thread
->WaitIrql
<= DISPATCH_LEVEL
);
311 KiSetThreadSwapBusy(Thread
);
312 KiInsertWaitTimer(Timer
);
313 WaitStatus
= KiSwapThread(Thread
, KeGetCurrentPrcb());
315 /* Check if were swapped ok */
316 if (WaitStatus
!= STATUS_KERNEL_APC
)
318 /* This is a good thing */
319 if (WaitStatus
== STATUS_TIMEOUT
) WaitStatus
= STATUS_SUCCESS
;
325 /* Recalculate due times */
326 Interval
= KiRecalculateDueTime(OriginalDueTime
,
332 /* Setup a new wait */
333 Thread
->WaitIrql
= KeRaiseIrqlToSynchLevel();
335 KiAcquireDispatcherLockAtDpcLevel();
339 KiReleaseDispatcherLock(Thread
->WaitIrql
);
343 /* There was nothing to wait for. Did we have a wait interval? */
344 if (!Interval
->QuadPart
)
346 /* Unlock the dispatcher and do a yield */
347 KiReleaseDispatcherLock(Thread
->WaitIrql
);
348 return NtYieldExecution();
351 /* Unlock the dispatcher and adjust the quantum for a no-wait */
352 KiReleaseDispatcherLockFromDpcLevel();
353 KiAdjustQuantumThread(Thread
);
354 return STATUS_SUCCESS
;
362 KeWaitForSingleObject(IN PVOID Object
,
363 IN KWAIT_REASON WaitReason
,
364 IN KPROCESSOR_MODE WaitMode
,
365 IN BOOLEAN Alertable
,
366 IN PLARGE_INTEGER Timeout OPTIONAL
)
368 PKTHREAD Thread
= KeGetCurrentThread();
369 PKMUTANT CurrentObject
= (PKMUTANT
)Object
;
370 PKWAIT_BLOCK WaitBlock
= &Thread
->WaitBlock
[0];
371 PKWAIT_BLOCK TimerBlock
= &Thread
->WaitBlock
[TIMER_WAIT_BLOCK
];
372 PKTIMER Timer
= &Thread
->Timer
;
375 LARGE_INTEGER DueTime
, NewDueTime
, InterruptTime
;
376 PLARGE_INTEGER OriginalDueTime
= Timeout
;
378 /* Check if the lock is already held */
379 if (!Thread
->WaitNext
) goto WaitStart
;
381 /* Otherwise, we already have the lock, so initialize the wait */
382 Thread
->WaitNext
= FALSE
;
383 KxSingleThreadWait();
385 /* Start wait loop */
388 /* Disable pre-emption */
389 Thread
->Preempted
= FALSE
;
391 /* Check if a kernel APC is pending and we're below APC_LEVEL */
392 if ((Thread
->ApcState
.KernelApcPending
) && !(Thread
->SpecialApcDisable
) &&
393 (Thread
->WaitIrql
< APC_LEVEL
))
395 /* Unlock the dispatcher */
396 KiReleaseDispatcherLock(Thread
->WaitIrql
);
401 ASSERT(CurrentObject
->Header
.Type
!= QueueObject
);
403 /* Check if it's a mutant */
404 if (CurrentObject
->Header
.Type
== MutantObject
)
406 /* Check its signal state or if we own it */
407 if ((CurrentObject
->Header
.SignalState
> 0) ||
408 (Thread
== CurrentObject
->OwnerThread
))
410 /* Just unwait this guy and exit */
411 if (CurrentObject
->Header
.SignalState
!= (LONG
)MINLONG
)
413 /* It has a normal signal state. Unwait and return */
414 KiSatisfyMutantWait(CurrentObject
, Thread
);
415 WaitStatus
= Thread
->WaitStatus
;
420 /* Raise an exception */
421 KiReleaseDispatcherLock(Thread
->WaitIrql
);
422 ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED
);
426 else if (CurrentObject
->Header
.SignalState
> 0)
428 /* Another satisfied object */
429 KiSatisfyNonMutantWait(CurrentObject
, Thread
);
430 WaitStatus
= STATUS_WAIT_0
;
434 /* Make sure we can satisfy the Alertable request */
435 WaitStatus
= KiCheckAlertability(Thread
, Alertable
, WaitMode
);
436 if (WaitStatus
!= STATUS_WAIT_0
) break;
438 /* Enable the Timeout Timer if there was any specified */
441 /* Check if the timer expired */
442 InterruptTime
.QuadPart
= KeQueryInterruptTime();
443 if (InterruptTime
.QuadPart
>= Timer
->DueTime
.QuadPart
)
445 /* It did, so we don't need to wait */
446 WaitStatus
= STATUS_TIMEOUT
;
450 /* It didn't, so activate it */
451 Timer
->Header
.Inserted
= TRUE
;
454 /* Link the Object to this Wait Block */
455 InsertTailList(&CurrentObject
->Header
.WaitListHead
,
456 &WaitBlock
->WaitListEntry
);
458 /* Handle Kernel Queues */
459 if (Thread
->Queue
) KiActivateWaiterQueue(Thread
->Queue
);
461 /* Setup the wait information */
462 Thread
->State
= Waiting
;
464 /* Add the thread to the wait list */
465 KiAddThreadToWaitList(Thread
, Swappable
);
467 /* Activate thread swap */
468 ASSERT(Thread
->WaitIrql
<= DISPATCH_LEVEL
);
469 KiSetThreadSwapBusy(Thread
);
471 /* Check if we have a timer */
475 KiInsertWaitTimer(Timer
);
479 /* Otherwise, unlock the dispatcher */
480 KiReleaseDispatcherLockFromDpcLevel();
483 /* Do the actual swap */
484 WaitStatus
= KiSwapThread(Thread
, KeGetCurrentPrcb());
486 /* Check if we were executing an APC */
487 if (WaitStatus
!= STATUS_KERNEL_APC
) return WaitStatus
;
489 /* Check if we had a timeout */
492 /* Recalculate due times */
493 Timeout
= KiRecalculateDueTime(OriginalDueTime
,
499 /* Setup a new wait */
500 Thread
->WaitIrql
= KeRaiseIrqlToSynchLevel();
501 KxSingleThreadWait();
502 KiAcquireDispatcherLockAtDpcLevel();
506 KiReleaseDispatcherLock(Thread
->WaitIrql
);
510 /* Release dispatcher lock but maintain high IRQL */
511 KiReleaseDispatcherLockFromDpcLevel();
513 /* Adjust the Quantum and return the wait status */
514 KiAdjustQuantumThread(Thread
);
523 KeWaitForMultipleObjects(IN ULONG Count
,
525 IN WAIT_TYPE WaitType
,
526 IN KWAIT_REASON WaitReason
,
527 IN KPROCESSOR_MODE WaitMode
,
528 IN BOOLEAN Alertable
,
529 IN PLARGE_INTEGER Timeout OPTIONAL
,
530 OUT PKWAIT_BLOCK WaitBlockArray OPTIONAL
)
532 PKMUTANT CurrentObject
;
533 PKWAIT_BLOCK WaitBlock
;
534 PKTHREAD Thread
= KeGetCurrentThread();
535 PKWAIT_BLOCK TimerBlock
= &Thread
->WaitBlock
[TIMER_WAIT_BLOCK
];
536 PKTIMER Timer
= &Thread
->Timer
;
537 NTSTATUS WaitStatus
= STATUS_SUCCESS
;
539 PLARGE_INTEGER OriginalDueTime
= Timeout
;
540 LARGE_INTEGER DueTime
, NewDueTime
, InterruptTime
;
543 /* Make sure the Wait Count is valid */
546 /* Check in regards to the Thread Object Limit */
547 if (Count
> THREAD_WAIT_OBJECTS
)
550 KEBUGCHECK(MAXIMUM_WAIT_OBJECTS_EXCEEDED
);
553 /* Use the Thread's Wait Block */
554 WaitBlockArray
= &Thread
->WaitBlock
[0];
558 /* Using our own Block Array, so check with the System Object Limit */
559 if (Count
> MAXIMUM_WAIT_OBJECTS
)
562 KEBUGCHECK(MAXIMUM_WAIT_OBJECTS_EXCEEDED
);
569 /* Check if the lock is already held */
570 if (!Thread
->WaitNext
) goto WaitStart
;
572 /* Otherwise, we already have the lock, so initialize the wait */
573 Thread
->WaitNext
= FALSE
;
576 /* Start wait loop */
579 /* Disable pre-emption */
580 Thread
->Preempted
= FALSE
;
582 /* Check if a kernel APC is pending and we're below APC_LEVEL */
583 if ((Thread
->ApcState
.KernelApcPending
) && !(Thread
->SpecialApcDisable
) &&
584 (Thread
->WaitIrql
< APC_LEVEL
))
586 /* Unlock the dispatcher */
587 KiReleaseDispatcherLock(Thread
->WaitIrql
);
591 /* Check what kind of wait this is */
593 if (WaitType
== WaitAny
)
598 /* Get the Current Object */
599 CurrentObject
= (PKMUTANT
)Object
[Index
];
600 ASSERT(CurrentObject
->Header
.Type
!= QueueObject
);
602 /* Check if the Object is a mutant */
603 if (CurrentObject
->Header
.Type
== MutantObject
)
605 /* Check if it's signaled */
606 if ((CurrentObject
->Header
.SignalState
> 0) ||
607 (Thread
== CurrentObject
->OwnerThread
))
609 /* This is a Wait Any, so unwait this and exit */
610 if (CurrentObject
->Header
.SignalState
!=
613 /* Normal signal state, unwait it and return */
614 KiSatisfyMutantWait(CurrentObject
, Thread
);
615 WaitStatus
= Thread
->WaitStatus
| Index
;
620 /* Raise an exception (see wasm.ru) */
621 KiReleaseDispatcherLock(Thread
->WaitIrql
);
622 ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED
);
626 else if (CurrentObject
->Header
.SignalState
> 0)
628 /* Another signaled object, unwait and return */
629 KiSatisfyNonMutantWait(CurrentObject
, Thread
);
634 /* Go to the next block */
636 } while (Index
< Count
);
643 /* Get the Current Object */
644 CurrentObject
= (PKMUTANT
)Object
[Index
];
645 ASSERT(CurrentObject
->Header
.Type
!= QueueObject
);
647 /* Check if we're dealing with a mutant again */
648 if (CurrentObject
->Header
.Type
== MutantObject
)
650 /* Check if it has an invalid count */
651 if ((Thread
== CurrentObject
->OwnerThread
) &&
652 (CurrentObject
->Header
.SignalState
== MINLONG
))
654 /* Raise an exception */
655 KiReleaseDispatcherLock(Thread
->WaitIrql
);
656 ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED
);
658 else if ((CurrentObject
->Header
.SignalState
<= 0) &&
659 (Thread
!= CurrentObject
->OwnerThread
))
661 /* We don't own it, can't satisfy the wait */
665 else if (CurrentObject
->Header
.SignalState
<= 0)
667 /* Not signaled, can't satisfy */
671 /* Go to the next block */
673 } while (Index
< Count
);
675 /* Check if we've went through all the objects */
678 /* Loop wait blocks */
679 WaitBlock
= WaitBlockArray
;
682 /* Get the object and satisfy it */
683 CurrentObject
= (PKMUTANT
)WaitBlock
->Object
;
684 KiSatisfyObjectWait(CurrentObject
, Thread
);
686 /* Go to the next block */
687 WaitBlock
= WaitBlock
->NextWaitBlock
;
688 } while(WaitBlock
!= WaitBlockArray
);
690 /* Set the wait status and get out */
691 WaitStatus
= Thread
->WaitStatus
;
696 /* Make sure we can satisfy the Alertable request */
697 WaitStatus
= KiCheckAlertability(Thread
, Alertable
, WaitMode
);
698 if (WaitStatus
!= STATUS_WAIT_0
) break;
700 /* Enable the Timeout Timer if there was any specified */
703 /* Check if the timer expired */
704 InterruptTime
.QuadPart
= KeQueryInterruptTime();
705 if (InterruptTime
.QuadPart
>= Timer
->DueTime
.QuadPart
)
707 /* It did, so we don't need to wait */
708 WaitStatus
= STATUS_TIMEOUT
;
712 /* It didn't, so activate it */
713 Timer
->Header
.Inserted
= TRUE
;
715 /* Link the wait blocks */
716 WaitBlock
->NextWaitBlock
= TimerBlock
;
719 /* Insert into Object's Wait List*/
720 WaitBlock
= WaitBlockArray
;
723 /* Get the Current Object */
724 CurrentObject
= WaitBlock
->Object
;
726 /* Link the Object to this Wait Block */
727 InsertTailList(&CurrentObject
->Header
.WaitListHead
,
728 &WaitBlock
->WaitListEntry
);
730 /* Move to the next Wait Block */
731 WaitBlock
= WaitBlock
->NextWaitBlock
;
732 } while (WaitBlock
!= WaitBlockArray
);
734 /* Handle Kernel Queues */
735 if (Thread
->Queue
) KiActivateWaiterQueue(Thread
->Queue
);
737 /* Setup the wait information */
738 Thread
->State
= Waiting
;
740 /* Add the thread to the wait list */
741 KiAddThreadToWaitList(Thread
, Swappable
);
743 /* Activate thread swap */
744 ASSERT(Thread
->WaitIrql
<= DISPATCH_LEVEL
);
745 KiSetThreadSwapBusy(Thread
);
747 /* Check if we have a timer */
751 KiInsertWaitTimer(Timer
);
755 /* Otherwise, unlock the dispatcher */
756 KiReleaseDispatcherLockFromDpcLevel();
759 /* Swap the thread */
760 WaitStatus
= KiSwapThread(Thread
, KeGetCurrentPrcb());
762 /* Check if we were executing an APC */
763 if (WaitStatus
!= STATUS_KERNEL_APC
) return WaitStatus
;
765 /* Check if we had a timeout */
768 /* Recalculate due times */
769 Timeout
= KiRecalculateDueTime(OriginalDueTime
,
776 /* Setup a new wait */
777 Thread
->WaitIrql
= KeRaiseIrqlToSynchLevel();
779 KiAcquireDispatcherLockAtDpcLevel();
783 KiReleaseDispatcherLock(Thread
->WaitIrql
);
787 /* Release dispatcher lock but maintain high IRQL */
788 KiReleaseDispatcherLockFromDpcLevel();
790 /* Adjust the Quantum and return the wait status */
791 KiAdjustQuantumThread(Thread
);
797 NtDelayExecution(IN BOOLEAN Alertable
,
798 IN PLARGE_INTEGER DelayInterval
)
800 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
801 LARGE_INTEGER SafeInterval
;
802 NTSTATUS Status
= STATUS_SUCCESS
;
804 /* Check the previous mode */
805 if(PreviousMode
!= KernelMode
)
807 /* Enter SEH for probing */
810 /* Probe and capture the time out */
811 SafeInterval
= ProbeForReadLargeInteger(DelayInterval
);
812 DelayInterval
= &SafeInterval
;
816 /* Get SEH exception */
817 Status
= _SEH_GetExceptionCode();
820 if (!NT_SUCCESS(Status
)) return Status
;
823 /* Call the Kernel Function */
824 Status
= KeDelayExecutionThread(PreviousMode
,