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)
15 // FIXME: Should be dynamically initialized!
16 #define FIRST_MCB_SEGMENT (SYSTEM_ENV_BLOCK + 0x200) // old value: 0x1000
17 #define USER_MEMORY_SIZE (0x9FFE - FIRST_MCB_SEGMENT)
20 * Activate this line if you want run-time DOS memory arena integrity validation
21 * (useful to know whether this is an application, or DOS kernel itself, which
22 * messes up the DOS memory arena).
26 /* PRIVATE VARIABLES **********************************************************/
28 /* PUBLIC VARIABLES ***********************************************************/
30 /* PRIVATE FUNCTIONS **********************************************************/
32 static inline BOOLEAN
ValidateMcb(PDOS_MCB Mcb
)
34 return (Mcb
->BlockType
== 'M' || Mcb
->BlockType
== 'Z');
38 * This is a helper function to help us detecting
39 * when the DOS arena starts to become corrupted.
42 static VOID
DosMemValidate(VOID
)
44 WORD PrevSegment
, Segment
= SysVars
->FirstMcb
;
47 PrevSegment
= Segment
;
50 /* Get a pointer to the MCB */
51 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
53 /* Make sure it's valid */
54 if (!ValidateMcb(CurrentMcb
))
56 DPRINT1("The DOS memory arena is corrupted! (CurrentMcb = 0x%04X; PreviousMcb = 0x%04X)\n", Segment
, PrevSegment
);
60 PrevSegment
= Segment
;
62 /* If this was the last MCB in the chain, quit */
63 if (CurrentMcb
->BlockType
== 'Z') return;
65 /* Otherwise, update the segment and continue */
66 Segment
+= CurrentMcb
->Size
+ 1;
70 #define DosMemValidate()
73 static VOID
DosCombineFreeBlocks(WORD StartBlock
)
75 /* NOTE: This function is always called with valid MCB blocks */
77 PDOS_MCB CurrentMcb
= SEGMENT_TO_MCB(StartBlock
), NextMcb
;
79 /* If the block is not free, quit */
80 if (CurrentMcb
->OwnerPsp
!= 0) return;
83 * Loop while the current block is not the last one. It can happen
84 * that the block is not the last one at the beginning, but becomes
85 * the last one at the end of the process. This happens in the case
86 * where its next following blocks are free but not combined yet,
87 * and they are terminated by a free last block. During the process
88 * all the blocks are combined together and we end up in the situation
89 * where the current (free) block is followed by the last (free) block.
90 * At the last step of the algorithm the current block becomes the
93 while (CurrentMcb
->BlockType
!= 'Z')
95 /* Get a pointer to the next MCB */
96 NextMcb
= SEGMENT_TO_MCB(StartBlock
+ CurrentMcb
->Size
+ 1);
98 /* Make sure it's valid */
99 if (!ValidateMcb(NextMcb
))
101 DPRINT1("The DOS memory arena is corrupted!\n");
102 Sda
->LastErrorCode
= ERROR_ARENA_TRASHED
;
106 /* Check if the next MCB is free */
107 if (NextMcb
->OwnerPsp
== 0)
110 CurrentMcb
->Size
+= NextMcb
->Size
+ 1;
111 CurrentMcb
->BlockType
= NextMcb
->BlockType
;
112 NextMcb
->BlockType
= 'I';
116 /* No more adjoining free blocks */
122 /* PUBLIC FUNCTIONS ***********************************************************/
124 WORD
DosAllocateMemory(WORD Size
, WORD
*MaxAvailable
)
126 WORD Result
= 0, Segment
= SysVars
->FirstMcb
, MaxSize
= 0;
128 BOOLEAN SearchUmb
= FALSE
;
130 DPRINT("DosAllocateMemory: Size 0x%04X\n", Size
);
134 if (SysVars
->UmbLinked
&& SysVars
->UmbChainStart
!= 0xFFFF &&
135 (Sda
->AllocStrategy
& (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
)))
137 /* Search UMB first */
138 Segment
= SysVars
->UmbChainStart
;
144 /* Get a pointer to the MCB */
145 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
147 /* Make sure it's valid */
148 if (!ValidateMcb(CurrentMcb
))
150 DPRINT1("The DOS memory arena is corrupted!\n");
151 Sda
->LastErrorCode
= ERROR_ARENA_TRASHED
;
155 /* Only check free blocks */
156 if (CurrentMcb
->OwnerPsp
!= 0) goto Next
;
158 /* Combine this free block with adjoining free blocks */
159 DosCombineFreeBlocks(Segment
);
161 /* Update the maximum block size */
162 if (CurrentMcb
->Size
> MaxSize
) MaxSize
= CurrentMcb
->Size
;
164 /* Check if this block is big enough */
165 if (CurrentMcb
->Size
< Size
) goto Next
;
167 switch (Sda
->AllocStrategy
& ~(DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
169 case DOS_ALLOC_FIRST_FIT
:
171 /* For first fit, stop immediately */
176 case DOS_ALLOC_BEST_FIT
:
178 /* For best fit, update the smallest block found so far */
179 if ((Result
== 0) || (CurrentMcb
->Size
< SEGMENT_TO_MCB(Result
)->Size
))
187 case DOS_ALLOC_LAST_FIT
:
189 /* For last fit, make the current block the result, but keep searching */
196 /* If this was the last MCB in the chain, quit */
197 if (CurrentMcb
->BlockType
== 'Z')
199 /* Check if nothing was found while searching through UMBs */
200 if ((Result
== 0) && SearchUmb
&& (Sda
->AllocStrategy
& DOS_ALLOC_HIGH_LOW
))
202 /* Search low memory */
203 Segment
= SysVars
->FirstMcb
;
211 /* Otherwise, update the segment and continue */
212 Segment
+= CurrentMcb
->Size
+ 1;
218 /* If we didn't find a free block, bail out */
221 DPRINT("DosAllocateMemory FAILED. Maximum available: 0x%04X\n", MaxSize
);
222 Sda
->LastErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
223 if (MaxAvailable
) *MaxAvailable
= MaxSize
;
227 /* Get a pointer to the MCB */
228 CurrentMcb
= SEGMENT_TO_MCB(Result
);
230 /* Check if the block is larger than requested */
231 if (CurrentMcb
->Size
> Size
)
233 /* It is, split it into two blocks */
234 if ((Sda
->AllocStrategy
& ~(DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
)) != DOS_ALLOC_LAST_FIT
)
236 PDOS_MCB NextMcb
= SEGMENT_TO_MCB(Result
+ Size
+ 1);
238 /* Initialize the new MCB structure */
239 NextMcb
->BlockType
= CurrentMcb
->BlockType
;
240 NextMcb
->Size
= CurrentMcb
->Size
- Size
- 1;
241 NextMcb
->OwnerPsp
= 0;
243 /* Update the current block */
244 CurrentMcb
->BlockType
= 'M';
245 CurrentMcb
->Size
= Size
;
249 /* Save the location of the current MCB */
250 PDOS_MCB PreviousMcb
= CurrentMcb
;
252 /* Move the current MCB higher */
253 Result
+= CurrentMcb
->Size
- Size
;
254 CurrentMcb
= SEGMENT_TO_MCB(Result
);
256 /* Initialize the new MCB structure */
257 CurrentMcb
->BlockType
= PreviousMcb
->BlockType
;
258 CurrentMcb
->Size
= Size
;
259 CurrentMcb
->OwnerPsp
= 0;
261 /* Update the previous block */
262 PreviousMcb
->BlockType
= 'M';
263 PreviousMcb
->Size
-= Size
+ 1;
267 /* Take ownership of the block */
268 CurrentMcb
->OwnerPsp
= Sda
->CurrentPsp
;
269 RtlCopyMemory(CurrentMcb
->Name
, SEGMENT_TO_MCB(Sda
->CurrentPsp
- 1)->Name
, sizeof(CurrentMcb
->Name
));
273 /* Return the segment of the data portion of the block */
277 BOOLEAN
DosResizeMemory(WORD BlockData
, WORD NewSize
, WORD
*MaxAvailable
)
279 BOOLEAN Success
= TRUE
;
280 WORD Segment
= BlockData
- 1, ReturnSize
= 0, NextSegment
;
281 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
), NextMcb
;
283 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
288 /* Make sure this is a valid and allocated block */
289 if (BlockData
== 0 || !ValidateMcb(Mcb
) || Mcb
->OwnerPsp
== 0)
291 Sda
->LastErrorCode
= ERROR_INVALID_BLOCK
;
296 ReturnSize
= Mcb
->Size
;
298 /* Check if we need to expand or contract the block */
299 if (NewSize
> Mcb
->Size
)
301 /* We can't expand the last block */
302 if (Mcb
->BlockType
== 'Z')
304 DPRINT("Cannot expand memory block 0x%04X: this is the last block (size 0x%04X)!\n", Segment
, Mcb
->Size
);
305 Sda
->LastErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
310 /* Get the pointer and segment of the next MCB */
311 NextSegment
= Segment
+ Mcb
->Size
+ 1;
312 NextMcb
= SEGMENT_TO_MCB(NextSegment
);
314 /* Make sure it's valid */
315 if (!ValidateMcb(NextMcb
))
317 DPRINT1("The DOS memory arena is corrupted!\n");
318 Sda
->LastErrorCode
= ERROR_ARENA_TRASHED
;
322 /* Make sure the next segment is free */
323 if (NextMcb
->OwnerPsp
!= 0)
325 DPRINT("Cannot expand memory block 0x%04X: next segment is not free!\n", Segment
);
326 Sda
->LastErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
331 /* Combine this free block with adjoining free blocks */
332 DosCombineFreeBlocks(NextSegment
);
334 /* Set the maximum possible size of the block */
335 ReturnSize
+= NextMcb
->Size
+ 1;
337 if (ReturnSize
< NewSize
)
339 DPRINT("Cannot expand memory block 0x%04X: insufficient free segments available!\n", Segment
);
340 Sda
->LastErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
345 /* Maximize the current block */
346 Mcb
->Size
= ReturnSize
;
347 Mcb
->BlockType
= NextMcb
->BlockType
;
349 /* Invalidate the next block */
350 NextMcb
->BlockType
= 'I';
352 /* Check if the block is larger than requested */
353 if (Mcb
->Size
> NewSize
)
355 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
358 /* It is, split it into two blocks */
359 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
361 /* Initialize the new MCB structure */
362 NextMcb
->BlockType
= Mcb
->BlockType
;
363 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
364 NextMcb
->OwnerPsp
= 0;
366 /* Update the current block */
367 Mcb
->BlockType
= 'M';
371 else if (NewSize
< Mcb
->Size
)
373 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
376 /* Just split the block */
377 NextSegment
= Segment
+ NewSize
+ 1;
378 NextMcb
= SEGMENT_TO_MCB(NextSegment
);
379 NextMcb
->BlockType
= Mcb
->BlockType
;
380 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
381 NextMcb
->OwnerPsp
= 0;
384 Mcb
->BlockType
= 'M';
387 /* Combine this free block with adjoining free blocks */
388 DosCombineFreeBlocks(NextSegment
);
392 /* Check if the operation failed */
395 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n", ReturnSize
);
397 /* Return the maximum possible size */
398 if (MaxAvailable
) *MaxAvailable
= ReturnSize
;
406 BOOLEAN
DosFreeMemory(WORD BlockData
)
408 PDOS_MCB Mcb
= SEGMENT_TO_MCB(BlockData
- 1);
410 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData
);
414 Sda
->LastErrorCode
= ERROR_INVALID_BLOCK
;
420 /* Make sure the MCB is valid */
421 if (!ValidateMcb(Mcb
))
423 DPRINT("MCB block type '%c' not valid!\n", Mcb
->BlockType
);
424 Sda
->LastErrorCode
= ERROR_INVALID_BLOCK
;
428 /* Mark the block as free */
434 BOOLEAN
DosLinkUmb(VOID
)
436 DWORD Segment
= SysVars
->FirstMcb
;
437 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
439 DPRINT("Linking UMB\n");
441 /* Check if UMBs are initialized and already linked */
442 if (SysVars
->UmbChainStart
== 0xFFFF) return FALSE
;
443 if (SysVars
->UmbLinked
) return TRUE
;
447 /* Find the last block before the start of the UMB chain */
448 while (Segment
< SysVars
->UmbChainStart
)
450 /* Get a pointer to the MCB */
451 Mcb
= SEGMENT_TO_MCB(Segment
);
453 /* Make sure it's valid */
454 if (!ValidateMcb(Mcb
))
456 DPRINT1("The DOS memory arena is corrupted!\n");
457 Sda
->LastErrorCode
= ERROR_ARENA_TRASHED
;
461 /* If this was the last MCB in the chain, quit */
462 if (Mcb
->BlockType
== 'Z') break;
464 /* Otherwise, update the segment and continue */
465 Segment
+= Mcb
->Size
+ 1;
468 /* Make sure it's valid */
469 if (Mcb
->BlockType
!= 'Z') return FALSE
;
471 /* Connect the MCB with the UMB chain */
472 Mcb
->BlockType
= 'M';
476 SysVars
->UmbLinked
= TRUE
;
480 BOOLEAN
DosUnlinkUmb(VOID
)
482 DWORD Segment
= SysVars
->FirstMcb
;
483 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
485 DPRINT("Unlinking UMB\n");
487 /* Check if UMBs are initialized and already unlinked */
488 if (SysVars
->UmbChainStart
== 0xFFFF) return FALSE
;
489 if (!SysVars
->UmbLinked
) return TRUE
;
493 /* Find the last block before the start of the UMB chain */
494 while (Segment
< SysVars
->UmbChainStart
)
496 /* Get a pointer to the MCB */
497 Mcb
= SEGMENT_TO_MCB(Segment
);
499 /* Make sure it's valid */
500 if (!ValidateMcb(Mcb
))
502 DPRINT1("The DOS memory arena is corrupted!\n");
503 Sda
->LastErrorCode
= ERROR_ARENA_TRASHED
;
507 /* Advance to the next MCB */
508 Segment
+= Mcb
->Size
+ 1;
511 /* Mark the MCB as the last MCB */
512 Mcb
->BlockType
= 'Z';
516 SysVars
->UmbLinked
= FALSE
;
520 VOID
DosChangeMemoryOwner(WORD Segment
, WORD NewOwner
)
522 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
- 1);
523 Mcb
->OwnerPsp
= NewOwner
;
527 * Some information about DOS UMBs:
528 * http://textfiles.com/virus/datut010.txt
529 * http://www.asmcommunity.net/forums/topic/?id=30884
532 WORD
DosGetPreviousUmb(WORD UmbSegment
)
535 WORD Segment
, PrevSegment
= 0; // FIXME: or use UmbChainStart ??
537 if (SysVars
->UmbChainStart
== 0xFFFF)
540 /* Start scanning the UMB chain */
541 Segment
= SysVars
->UmbChainStart
;
544 /* Get a pointer to the MCB */
545 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
547 /* Make sure it's valid */
548 if (!ValidateMcb(CurrentMcb
))
550 DPRINT1("The UMB DOS memory arena is corrupted!\n");
551 Sda
->LastErrorCode
= ERROR_ARENA_TRASHED
;
555 /* We went over the UMB segment, quit */
556 if (Segment
>= UmbSegment
) break;
558 PrevSegment
= Segment
;
560 /* If this was the last MCB in the chain, quit */
561 if (CurrentMcb
->BlockType
== 'Z') break;
563 /* Otherwise, update the segment and continue */
564 Segment
+= CurrentMcb
->Size
+ 1;
570 VOID
DosInitializeUmb(VOID
)
573 USHORT UmbSegment
= 0x0000, PrevSegment
;
575 PDOS_MCB Mcb
, PrevMcb
;
577 ASSERT(SysVars
->UmbChainStart
== 0xFFFF);
579 // SysVars->UmbLinked = FALSE;
581 /* Try to allocate all the UMBs */
584 /* Find the maximum amount of memory that can be allocated */
586 Result
= UmaDescReserve(&UmbSegment
, &Size
);
588 /* If we are out of UMBs, bail out */
589 if (!Result
&& Size
== 0) // XMS_STATUS_OUT_OF_UMBS
592 /* We should not have succeeded! */
595 /* 'Size' now contains the size of the biggest UMB block. Request it. */
596 Result
= UmaDescReserve(&UmbSegment
, &Size
);
597 ASSERT(Result
); // XMS_STATUS_SUCCESS
599 /* If this is our first UMB block, initialize the UMB chain */
600 if (SysVars
->UmbChainStart
== 0xFFFF)
602 /* Initialize the link MCB to the UMB area */
603 // NOTE: We use the fact that UmbChainStart is still == 0xFFFF
604 // so that we initialize this block from 9FFF:0000 up to FFFF:000F.
605 // It will be splitted as needed just below.
606 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
607 Mcb
->BlockType
= 'Z'; // At the moment it is really the last block
608 Mcb
->Size
= (SysVars
->UmbChainStart
/* UmbSegment */ - SysVars
->FirstMcb
- USER_MEMORY_SIZE
- 2) + 1;
609 Mcb
->OwnerPsp
= SYSTEM_PSP
;
610 RtlCopyMemory(Mcb
->Name
, "SC ", sizeof("SC ")-1);
612 #if 0 // Keep here for reference; this will be deleted as soon as it becomes unneeded.
613 /* Initialize the UMB area */
614 Mcb
= SEGMENT_TO_MCB(SysVars
->UmbChainStart
);
615 Mcb
->Size
= UMB_END_SEGMENT
- SysVars
->UmbChainStart
;
618 // FIXME: We should adjust the size of the previous block!!
620 /* Initialize the start of the UMB chain */
621 SysVars
->UmbChainStart
= SysVars
->FirstMcb
+ USER_MEMORY_SIZE
+ 1;
624 /* Split the block */
626 /* Get the previous block */
627 PrevSegment
= DosGetPreviousUmb(UmbSegment
);
628 PrevMcb
= SEGMENT_TO_MCB(PrevSegment
);
630 /* Initialize the next block */
631 Mcb
= SEGMENT_TO_MCB(UmbSegment
+ /*Mcb->Size*/(Size
- 1) + 0);
632 // Mcb->BlockType = 'Z'; // FIXME: What if this block happens to be the last one??
633 Mcb
->BlockType
= PrevMcb
->BlockType
;
634 Mcb
->Size
= PrevMcb
->Size
- (UmbSegment
+ Size
- PrevSegment
) + 1;
635 Mcb
->OwnerPsp
= PrevMcb
->OwnerPsp
;
636 RtlCopyMemory(Mcb
->Name
, PrevMcb
->Name
, sizeof(PrevMcb
->Name
));
638 /* The previous block is not the latest one anymore */
639 PrevMcb
->BlockType
= 'M';
640 PrevMcb
->Size
= UmbSegment
- PrevSegment
- 1;
642 /* Initialize the new UMB block */
643 Mcb
= SEGMENT_TO_MCB(UmbSegment
);
644 Mcb
->BlockType
= 'M'; // 'Z' // FIXME: What if this block happens to be the last one??
645 Mcb
->Size
= Size
- 1 - 1; // minus 2 because we need to have one arena at the beginning and one at the end.
647 // FIXME: Which MCB name should we use? I need to explore more the docs!
648 RtlCopyMemory(Mcb
->Name
, "UMB ", sizeof("UMB ")-1);
649 // RtlCopyMemory(Mcb->Name, "SM ", sizeof("SM ")-1);
653 VOID
DosInitializeMemory(VOID
)
657 /* Set the initial allocation strategy to "best fit" */
658 Sda
->AllocStrategy
= DOS_ALLOC_BEST_FIT
;
660 /* Initialize conventional memory; we don't have UMBs yet */
661 SysVars
->FirstMcb
= FIRST_MCB_SEGMENT
; // The Arena Head
662 SysVars
->UmbLinked
= FALSE
;
663 SysVars
->UmbChainStart
= 0xFFFF;
665 Mcb
= SEGMENT_TO_MCB(SysVars
->FirstMcb
);
667 /* Initialize the MCB */
668 Mcb
->BlockType
= 'Z';
669 Mcb
->Size
= USER_MEMORY_SIZE
;