4018f60d4dea88c9032aee674e2d241e3a8cd1e1
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: subsystems/mvdm/ntvdm/dos/dos32krnl/memory.c
5 * PURPOSE: DOS32 Memory Manager
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
10 /* INCLUDES *******************************************************************/
17 #include "bios/umamgr.h" // HACK until we correctly call XMS services for UMBs.
25 // FIXME: Should be dynamically initialized!
26 #define FIRST_MCB_SEGMENT (SYSTEM_ENV_BLOCK + 0x200) // old value: 0x1000
27 #define USER_MEMORY_SIZE (0x9FFE - FIRST_MCB_SEGMENT)
30 * Activate this line if you want run-time DOS memory arena integrity validation
31 * (useful to know whether this is an application, or DOS kernel itself, which
32 * messes up the DOS memory arena).
36 /* PRIVATE VARIABLES **********************************************************/
38 /* PUBLIC VARIABLES ***********************************************************/
40 /* PRIVATE FUNCTIONS **********************************************************/
42 static inline BOOLEAN
ValidateMcb(PDOS_MCB Mcb
)
44 return (Mcb
->BlockType
== 'M' || Mcb
->BlockType
== 'Z');
48 * This is a helper function to help us detecting
49 * when the DOS arena starts to become corrupted.
52 static VOID
DosMemValidate(VOID
)
54 WORD PrevSegment
, Segment
= SysVars
->FirstMcb
;
57 PrevSegment
= Segment
;
60 /* Get a pointer to the MCB */
61 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
63 /* Make sure it's valid */
64 if (!ValidateMcb(CurrentMcb
))
66 DPRINT1("The DOS memory arena is corrupted! (CurrentMcb = 0x%04X; PreviousMcb = 0x%04X)\n", Segment
, PrevSegment
);
70 PrevSegment
= Segment
;
72 /* If this was the last MCB in the chain, quit */
73 if (CurrentMcb
->BlockType
== 'Z') return;
75 /* Otherwise, update the segment and continue */
76 Segment
+= CurrentMcb
->Size
+ 1;
80 #define DosMemValidate()
83 static VOID
DosCombineFreeBlocks(WORD StartBlock
)
85 /* NOTE: This function is always called with valid MCB blocks */
87 PDOS_MCB CurrentMcb
= SEGMENT_TO_MCB(StartBlock
), NextMcb
;
89 /* If the block is not free, quit */
90 if (CurrentMcb
->OwnerPsp
!= 0) return;
93 * Loop while the current block is not the last one. It can happen
94 * that the block is not the last one at the beginning, but becomes
95 * the last one at the end of the process. This happens in the case
96 * where its next following blocks are free but not combined yet,
97 * and they are terminated by a free last block. During the process
98 * all the blocks are combined together and we end up in the situation
99 * where the current (free) block is followed by the last (free) block.
100 * At the last step of the algorithm the current block becomes the
103 while (CurrentMcb
->BlockType
!= 'Z')
105 /* Get a pointer to the next MCB */
106 NextMcb
= SEGMENT_TO_MCB(StartBlock
+ CurrentMcb
->Size
+ 1);
108 /* Make sure it's valid */
109 if (!ValidateMcb(NextMcb
))
111 DPRINT1("The DOS memory arena is corrupted!\n");
112 Sda
->LastErrorCode
= ERROR_ARENA_TRASHED
;
116 /* Check if the next MCB is free */
117 if (NextMcb
->OwnerPsp
== 0)
120 CurrentMcb
->Size
+= NextMcb
->Size
+ 1;
121 CurrentMcb
->BlockType
= NextMcb
->BlockType
;
122 NextMcb
->BlockType
= 'I';
126 /* No more adjoining free blocks */
132 /* PUBLIC FUNCTIONS ***********************************************************/
134 WORD
DosAllocateMemory(WORD Size
, WORD
*MaxAvailable
)
136 WORD Result
= 0, Segment
= SysVars
->FirstMcb
, MaxSize
= 0;
138 BOOLEAN SearchUmb
= FALSE
;
140 DPRINT("DosAllocateMemory: Size 0x%04X\n", Size
);
144 if (SysVars
->UmbLinked
&& SysVars
->UmbChainStart
!= 0xFFFF &&
145 (Sda
->AllocStrategy
& (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
)))
147 /* Search UMB first */
148 Segment
= SysVars
->UmbChainStart
;
154 /* Get a pointer to the MCB */
155 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
157 /* Make sure it's valid */
158 if (!ValidateMcb(CurrentMcb
))
160 DPRINT1("The DOS memory arena is corrupted!\n");
161 Sda
->LastErrorCode
= ERROR_ARENA_TRASHED
;
165 /* Only check free blocks */
166 if (CurrentMcb
->OwnerPsp
!= 0) goto Next
;
168 /* Combine this free block with adjoining free blocks */
169 DosCombineFreeBlocks(Segment
);
171 /* Update the maximum block size */
172 if (CurrentMcb
->Size
> MaxSize
) MaxSize
= CurrentMcb
->Size
;
174 /* Check if this block is big enough */
175 if (CurrentMcb
->Size
< Size
) goto Next
;
177 switch (Sda
->AllocStrategy
& ~(DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
179 case DOS_ALLOC_FIRST_FIT
:
181 /* For first fit, stop immediately */
186 case DOS_ALLOC_BEST_FIT
:
188 /* For best fit, update the smallest block found so far */
189 if ((Result
== 0) || (CurrentMcb
->Size
< SEGMENT_TO_MCB(Result
)->Size
))
197 case DOS_ALLOC_LAST_FIT
:
199 /* For last fit, make the current block the result, but keep searching */
206 /* If this was the last MCB in the chain, quit */
207 if (CurrentMcb
->BlockType
== 'Z')
209 /* Check if nothing was found while searching through UMBs */
210 if ((Result
== 0) && SearchUmb
&& (Sda
->AllocStrategy
& DOS_ALLOC_HIGH_LOW
))
212 /* Search low memory */
213 Segment
= SysVars
->FirstMcb
;
221 /* Otherwise, update the segment and continue */
222 Segment
+= CurrentMcb
->Size
+ 1;
228 /* If we didn't find a free block, bail out */
231 DPRINT("DosAllocateMemory FAILED. Maximum available: 0x%04X\n", MaxSize
);
232 Sda
->LastErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
233 if (MaxAvailable
) *MaxAvailable
= MaxSize
;
237 /* Get a pointer to the MCB */
238 CurrentMcb
= SEGMENT_TO_MCB(Result
);
240 /* Check if the block is larger than requested */
241 if (CurrentMcb
->Size
> Size
)
243 /* It is, split it into two blocks */
244 if ((Sda
->AllocStrategy
& ~(DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
)) != DOS_ALLOC_LAST_FIT
)
246 PDOS_MCB NextMcb
= SEGMENT_TO_MCB(Result
+ Size
+ 1);
248 /* Initialize the new MCB structure */
249 NextMcb
->BlockType
= CurrentMcb
->BlockType
;
250 NextMcb
->Size
= CurrentMcb
->Size
- Size
- 1;
251 NextMcb
->OwnerPsp
= 0;
253 /* Update the current block */
254 CurrentMcb
->BlockType
= 'M';
255 CurrentMcb
->Size
= Size
;
259 /* Save the location of the current MCB */
260 PDOS_MCB PreviousMcb
= CurrentMcb
;
262 /* Move the current MCB higher */
263 Result
+= CurrentMcb
->Size
- Size
;
264 CurrentMcb
= SEGMENT_TO_MCB(Result
);
266 /* Initialize the new MCB structure */
267 CurrentMcb
->BlockType
= PreviousMcb
->BlockType
;
268 CurrentMcb
->Size
= Size
;
269 CurrentMcb
->OwnerPsp
= 0;
271 /* Update the previous block */
272 PreviousMcb
->BlockType
= 'M';
273 PreviousMcb
->Size
-= Size
+ 1;
277 /* Take ownership of the block */
278 CurrentMcb
->OwnerPsp
= Sda
->CurrentPsp
;
279 RtlCopyMemory(CurrentMcb
->Name
, SEGMENT_TO_MCB(Sda
->CurrentPsp
- 1)->Name
, sizeof(CurrentMcb
->Name
));
283 /* Return the segment of the data portion of the block */
287 BOOLEAN
DosResizeMemory(WORD BlockData
, WORD NewSize
, WORD
*MaxAvailable
)
289 BOOLEAN Success
= TRUE
;
290 WORD Segment
= BlockData
- 1, ReturnSize
= 0, NextSegment
;
291 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
), NextMcb
;
293 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
298 /* Make sure this is a valid and allocated block */
299 if (BlockData
== 0 || !ValidateMcb(Mcb
) || Mcb
->OwnerPsp
== 0)
301 Sda
->LastErrorCode
= ERROR_INVALID_BLOCK
;
306 ReturnSize
= Mcb
->Size
;
308 /* Check if we need to expand or contract the block */
309 if (NewSize
> Mcb
->Size
)
311 /* We can't expand the last block */
312 if (Mcb
->BlockType
== 'Z')
314 DPRINT("Cannot expand memory block 0x%04X: this is the last block (size 0x%04X)!\n", Segment
, Mcb
->Size
);
315 Sda
->LastErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
320 /* Get the pointer and segment of the next MCB */
321 NextSegment
= Segment
+ Mcb
->Size
+ 1;
322 NextMcb
= SEGMENT_TO_MCB(NextSegment
);
324 /* Make sure it's valid */
325 if (!ValidateMcb(NextMcb
))
327 DPRINT1("The DOS memory arena is corrupted!\n");
328 Sda
->LastErrorCode
= ERROR_ARENA_TRASHED
;
332 /* Make sure the next segment is free */
333 if (NextMcb
->OwnerPsp
!= 0)
335 DPRINT("Cannot expand memory block 0x%04X: next segment is not free!\n", Segment
);
336 Sda
->LastErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
341 /* Combine this free block with adjoining free blocks */
342 DosCombineFreeBlocks(NextSegment
);
344 /* Set the maximum possible size of the block */
345 ReturnSize
+= NextMcb
->Size
+ 1;
347 if (ReturnSize
< NewSize
)
349 DPRINT("Cannot expand memory block 0x%04X: insufficient free segments available!\n", Segment
);
350 Sda
->LastErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
355 /* Maximize the current block */
356 Mcb
->Size
= ReturnSize
;
357 Mcb
->BlockType
= NextMcb
->BlockType
;
359 /* Invalidate the next block */
360 NextMcb
->BlockType
= 'I';
362 /* Check if the block is larger than requested */
363 if (Mcb
->Size
> NewSize
)
365 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
368 /* It is, split it into two blocks */
369 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
371 /* Initialize the new MCB structure */
372 NextMcb
->BlockType
= Mcb
->BlockType
;
373 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
374 NextMcb
->OwnerPsp
= 0;
376 /* Update the current block */
377 Mcb
->BlockType
= 'M';
381 else if (NewSize
< Mcb
->Size
)
383 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
386 /* Just split the block */
387 NextSegment
= Segment
+ NewSize
+ 1;
388 NextMcb
= SEGMENT_TO_MCB(NextSegment
);
389 NextMcb
->BlockType
= Mcb
->BlockType
;
390 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
391 NextMcb
->OwnerPsp
= 0;
394 Mcb
->BlockType
= 'M';
397 /* Combine this free block with adjoining free blocks */
398 DosCombineFreeBlocks(NextSegment
);
402 /* Check if the operation failed */
405 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n", ReturnSize
);
407 /* Return the maximum possible size */
408 if (MaxAvailable
) *MaxAvailable
= ReturnSize
;
416 BOOLEAN
DosFreeMemory(WORD BlockData
)
418 PDOS_MCB Mcb
= SEGMENT_TO_MCB(BlockData
- 1);
420 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData
);
424 Sda
->LastErrorCode
= ERROR_INVALID_BLOCK
;
430 /* Make sure the MCB is valid */
431 if (!ValidateMcb(Mcb
))
433 DPRINT("MCB block type '%c' not valid!\n", Mcb
->BlockType
);
434 Sda
->LastErrorCode
= ERROR_INVALID_BLOCK
;
438 /* Mark the block as free */
444 BOOLEAN
DosLinkUmb(VOID
)
446 DWORD Segment
= SysVars
->FirstMcb
;
447 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
449 DPRINT("Linking UMB\n");
451 /* Check if UMBs are initialized and already linked */
452 if (SysVars
->UmbChainStart
== 0xFFFF) return FALSE
;
453 if (SysVars
->UmbLinked
) return TRUE
;
457 /* Find the last block before the start of the UMB chain */
458 while (Segment
< SysVars
->UmbChainStart
)
460 /* Get a pointer to the MCB */
461 Mcb
= SEGMENT_TO_MCB(Segment
);
463 /* Make sure it's valid */
464 if (!ValidateMcb(Mcb
))
466 DPRINT1("The DOS memory arena is corrupted!\n");
467 Sda
->LastErrorCode
= ERROR_ARENA_TRASHED
;
471 /* If this was the last MCB in the chain, quit */
472 if (Mcb
->BlockType
== 'Z') break;
474 /* Otherwise, update the segment and continue */
475 Segment
+= Mcb
->Size
+ 1;
478 /* Make sure it's valid */
479 if (Mcb
->BlockType
!= 'Z') return FALSE
;
481 /* Connect the MCB with the UMB chain */
482 Mcb
->BlockType
= 'M';
486 SysVars
->UmbLinked
= TRUE
;
490 BOOLEAN
DosUnlinkUmb(VOID
)
492 DWORD Segment
= SysVars
->FirstMcb
;
493 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
495 DPRINT("Unlinking UMB\n");
497 /* Check if UMBs are initialized and already unlinked */
498 if (SysVars
->UmbChainStart
== 0xFFFF) return FALSE
;
499 if (!SysVars
->UmbLinked
) return TRUE
;
503 /* Find the last block before the start of the UMB chain */
504 while (Segment
< SysVars
->UmbChainStart
)
506 /* Get a pointer to the MCB */
507 Mcb
= SEGMENT_TO_MCB(Segment
);
509 /* Make sure it's valid */
510 if (!ValidateMcb(Mcb
))
512 DPRINT1("The DOS memory arena is corrupted!\n");
513 Sda
->LastErrorCode
= ERROR_ARENA_TRASHED
;
517 /* Advance to the next MCB */
518 Segment
+= Mcb
->Size
+ 1;
521 /* Mark the MCB as the last MCB */
522 Mcb
->BlockType
= 'Z';
526 SysVars
->UmbLinked
= FALSE
;
530 VOID
DosChangeMemoryOwner(WORD Segment
, WORD NewOwner
)
532 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
- 1);
533 Mcb
->OwnerPsp
= NewOwner
;
537 * Some information about DOS UMBs:
538 * http://textfiles.com/virus/datut010.txt
539 * http://www.asmcommunity.net/forums/topic/?id=30884
542 WORD
DosGetPreviousUmb(WORD UmbSegment
)
545 WORD Segment
, PrevSegment
= 0; // FIXME: or use UmbChainStart ??
547 if (SysVars
->UmbChainStart
== 0xFFFF)
550 /* Start scanning the UMB chain */
551 Segment
= SysVars
->UmbChainStart
;
554 /* Get a pointer to the MCB */
555 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
557 /* Make sure it's valid */
558 if (!ValidateMcb(CurrentMcb
))
560 DPRINT1("The UMB DOS memory arena is corrupted!\n");
561 Sda
->LastErrorCode
= ERROR_ARENA_TRASHED
;
565 /* We went over the UMB segment, quit */
566 if (Segment
>= UmbSegment
) break;
568 PrevSegment
= Segment
;
570 /* If this was the last MCB in the chain, quit */
571 if (CurrentMcb
->BlockType
== 'Z') break;
573 /* Otherwise, update the segment and continue */
574 Segment
+= CurrentMcb
->Size
+ 1;
580 VOID
DosInitializeUmb(VOID
)
583 USHORT UmbSegment
= 0x0000, PrevSegment
;
585 PDOS_MCB Mcb
, PrevMcb
;
587 ASSERT(SysVars
->UmbChainStart
== 0xFFFF);
589 // SysVars->UmbLinked = FALSE;
591 /* Try to allocate all the UMBs */
594 /* Find the maximum amount of memory that can be allocated */
596 Result
= UmaDescReserve(&UmbSegment
, &Size
);
598 /* If we are out of UMBs, bail out */
599 if (!Result
&& Size
== 0) // XMS_STATUS_OUT_OF_UMBS
602 /* We should not have succeeded! */
605 /* 'Size' now contains the size of the biggest UMB block. Request it. */
606 Result
= UmaDescReserve(&UmbSegment
, &Size
);
607 ASSERT(Result
); // XMS_STATUS_SUCCESS
609 /* If this is our first UMB block, initialize the UMB chain */
610 if (SysVars
->UmbChainStart
== 0xFFFF)
612 /* Initialize the link MCB to the UMB area */
613 // NOTE: We use the fact that UmbChainStart is still == 0xFFFF
614 // so that we initialize this block from 9FFF:0000 up to FFFF:000F.
615 // It will be splitted as needed just below.
616 Mcb
= SEGMENT_TO_MCB(SysVars
->FirstMcb
+ USER_MEMORY_SIZE
+ 1); // '+1': Readjust the fact that USER_MEMORY_SIZE is based using 0x9FFE instead of 0x9FFF
617 Mcb
->BlockType
= 'Z'; // At the moment it is really the last block
618 Mcb
->Size
= (SysVars
->UmbChainStart
/* UmbSegment */ - SysVars
->FirstMcb
- USER_MEMORY_SIZE
- 2) + 1;
619 Mcb
->OwnerPsp
= SYSTEM_PSP
;
620 RtlCopyMemory(Mcb
->Name
, "SC ", sizeof("SC ")-1);
622 #if 0 // Keep here for reference; this will be deleted as soon as it becomes unneeded.
623 /* Initialize the UMB area */
624 Mcb
= SEGMENT_TO_MCB(SysVars
->UmbChainStart
);
625 Mcb
->Size
= UMB_END_SEGMENT
- SysVars
->UmbChainStart
;
628 // FIXME: We should adjust the size of the previous block!!
630 /* Initialize the start of the UMB chain */
631 SysVars
->UmbChainStart
= SysVars
->FirstMcb
+ USER_MEMORY_SIZE
+ 1;
634 /* Split the block */
636 /* Get the previous block */
637 PrevSegment
= DosGetPreviousUmb(UmbSegment
);
638 PrevMcb
= SEGMENT_TO_MCB(PrevSegment
);
640 /* Initialize the next block */
641 Mcb
= SEGMENT_TO_MCB(UmbSegment
+ /*Mcb->Size*/(Size
- 1) + 0);
642 // Mcb->BlockType = 'Z'; // FIXME: What if this block happens to be the last one??
643 Mcb
->BlockType
= PrevMcb
->BlockType
;
644 Mcb
->Size
= PrevMcb
->Size
- (UmbSegment
+ Size
- PrevSegment
) + 1;
645 Mcb
->OwnerPsp
= PrevMcb
->OwnerPsp
;
646 RtlCopyMemory(Mcb
->Name
, PrevMcb
->Name
, sizeof(PrevMcb
->Name
));
648 /* The previous block is not the latest one anymore */
649 PrevMcb
->BlockType
= 'M';
650 PrevMcb
->Size
= UmbSegment
- PrevSegment
- 1;
652 /* Initialize the new UMB block */
653 Mcb
= SEGMENT_TO_MCB(UmbSegment
);
654 Mcb
->BlockType
= 'M'; // 'Z' // FIXME: What if this block happens to be the last one??
655 Mcb
->Size
= Size
- 1 - 1; // minus 2 because we need to have one arena at the beginning and one at the end.
657 // FIXME: Which MCB name should we use? I need to explore more the docs!
658 RtlCopyMemory(Mcb
->Name
, "UMB ", sizeof("UMB ")-1);
659 // RtlCopyMemory(Mcb->Name, "SM ", sizeof("SM ")-1);
663 VOID
DosInitializeMemory(VOID
)
667 /* Set the initial allocation strategy to "best fit" */
668 Sda
->AllocStrategy
= DOS_ALLOC_BEST_FIT
;
670 /* Initialize conventional memory; we don't have UMBs yet */
671 SysVars
->FirstMcb
= FIRST_MCB_SEGMENT
; // The Arena Head
672 SysVars
->UmbLinked
= FALSE
;
673 SysVars
->UmbChainStart
= 0xFFFF;
675 Mcb
= SEGMENT_TO_MCB(SysVars
->FirstMcb
);
677 /* Initialize the MCB */
678 Mcb
->BlockType
= 'Z';
679 Mcb
->Size
= USER_MEMORY_SIZE
;