• Please review our updated Terms and Rules here

PC BIOS function int 21, 48

Mills32

Experienced Member
Joined
Sep 25, 2018
Messages
147
Location
Spain
I tried using my own simple malloc by calling this bios function. Again in MS-DOS real mode.

This bios function returns a 16 bit number and I don't really know if my code is correct to convert it to the actual address:

Code:
word _malloc(word para){
    word addr = 0;
    asm mov ah,48h
    asm mov bx,para
    asm int 21h
    asm jc    _error
    asm mov addr,ax
    return addr;
    _error:
    return 0;
}

I call this function once (allocating 4096 paragraphs=64K), it returns 0x1C18 (using dosbox for example). That is the number of 16 byte paragraphs, so the real address is 0x1C180.
I assumed the first digit should be the memory segment, and the rest are the offset, so 0x1C18 = 0x1000:C180.
Assuming it is, I tested allocating 64KB arrays:

Code:
byte *tempdata  = (byte*)0x1000C180;

There was no problem, I even could use default "free" functions with that pointer, and everything seems ok.

Calling the function a second time, returns 0x2C19 = 2000:C190.

Is this correct?
 

krebizfan

Veteran Member
Joined
May 23, 2009
Messages
6,198
Location
Connecticut
No. I think the entire returned value in AX is the start of the segment. If the carry flag isn't set, you would have access to memory in the range 0x1C18:0000 to 0x1C18:FFFF. The two allocations you have made indicate a gap of 64K between them which is what should happen if 64K was allocated.
 

Mills32

Experienced Member
Joined
Sep 25, 2018
Messages
147
Location
Spain
No. I think the entire returned value in AX is the start of the segment. If the carry flag isn't set, you would have access to memory in the range 0x1C18:0000 to 0x1C18:FFFF. The two allocations you have made indicate a gap of 64K between them which is what should happen if 64K was allocated.
Thanks, I understand now. My code was working because the program was doing nothing else.
 

Plasma

Veteran Member
Joined
Nov 7, 2005
Messages
1,978
Segments are 16 bytes in real mode. So 1C18:0000 is the same address as 1000:C180. However it's probably easier to use offset 0 so you don't have to worry about wrapping.
 

Martin Hepperle

Experienced Member
Joined
Nov 10, 2014
Messages
130
... and it is a MS-DOS function, not a BIOS function ;-)
Your code will also run on non IBM-PC compatibles with the proper MS-DOS version.
 

Mills32

Experienced Member
Joined
Sep 25, 2018
Messages
147
Location
Spain
... and it is a MS-DOS function, not a BIOS function ;-)
Your code will also run on non IBM-PC compatibles with the proper MS-DOS version.
Thanks, I'm using all bios/dos functions so I don't have to use libraries (from turbo c at the moment)
 

Mills32

Experienced Member
Joined
Sep 25, 2018
Messages
147
Location
Spain
Well this function causes a lot of trouble, I looked at the turbo c malloc source, and it is a huge piece of code which does not use this int 21 function, or it uses it in a way I could not understand.

I tried resizing the MCB (the block the exe file allocates) but that was not working at all.
 

Chuck(G)

25k Member
Joined
Jan 11, 2007
Messages
43,939
Location
Pacific Northwest, USA
I'm guessing that it depends on your memory model for Turbo C. If you use the small or medium memory model, the library routines will allocate from the current data segment for malloc(). If you specify the huge, large or compact models, malloc returns a far address and will probably use the MSDOS call for memory allocation. Note also, that the MSDOS int 21h call will only get you available memory from within the 1MB memory base. If you need lots of memory, then the EMS/XMS functions should be used.

It's probably wiser to use the _dos_xxxx calls, IIRC, to handle a lot of the MSDOS interface issues (cf. dos.h; e.g. _dos_allocmem).
 

Mills32

Experienced Member
Joined
Sep 25, 2018
Messages
147
Location
Spain
I'm guessing that it depends on your memory model for Turbo C. If you use the small or medium memory model, the library routines will allocate from the current data segment for malloc(). If you specify the huge, large or compact models, malloc returns a far address and will probably use the MSDOS call for memory allocation. Note also, that the MSDOS int 21h call will only get you available memory from within the 1MB memory base. If you need lots of memory, then the EMS/XMS functions should be used.

It's probably wiser to use the _dos_xxxx calls, IIRC, to handle a lot of the MSDOS interface issues (cf. dos.h; e.g. _dos_allocmem).
Thanks.
I only need conventional memory, and not a lot of it, (around 192K + program code + variables), this is a program for 8088 cpus.
 

BloodyCactus

Experienced Member
Joined
Oct 18, 2015
Messages
257
Location
Lexington VA
Code:
byte *tempdata  = (byte*)0x1000C180;

I think this is your problem, you need to declare it as a far pointer, regardless of the memory model your using. you will probably run into issues if you use things like memmove because they expect pointers in their native memory model, unless its smart enough to start doing conversions.

byte far *ptr = (byte far *)0x1c180:0;

or use MK_FP

byte far *ptr = MK_FP(allocated_segment, 0);
 

Mills32

Experienced Member
Joined
Sep 25, 2018
Messages
147
Location
Spain
I think this is your problem, you need to declare it as a far pointer, regardless of the memory model your using. you will probably run into issues if you use things like memmove because they expect pointers in their native memory model, unless its smart enough to start doing conversions.

byte far *ptr = (byte far *)0x1c180:0;

or use MK_FP

byte far *ptr = MK_FP(allocated_segment, 0);
Thanks for the info, I tested that and it does not solve the problem.

Reading some old posts about using this dos int, I found why it is failing in my code.
If you use this int 21,48 function, you can't use any malloc, calloc or any other related function which uses malloc internaly in the code, because most malloc implementations will delete (or not see ) the blocks allocated with this function.

I tested a small program, and fopen (for example) will crash my program, because it uses malloc and it deletes the memory control block created by in 21,48.

So to use this, I have to create my own fopen, fread etc (probably print and fprintf). Fortunately, I could replicate these functions with other dos int 21 ones (I tested some of them and there was no problem).
 

Chuck(G)

25k Member
Joined
Jan 11, 2007
Messages
43,939
Location
Pacific Northwest, USA
When I was using that call for memory allocation, I simply wrote my own version of the C near memory management functions (handled in the startup code). I was using 16-bit MS C at the time. Maybe this code may help you.
 

Attachments

  • startup.zip
    3.5 KB · Views: 3

BloodyCactus

Experienced Member
Joined
Oct 18, 2015
Messages
257
Location
Lexington VA
Thanks for the info, I tested that and it does not solve the problem.

Reading some old posts about using this dos int, I found why it is failing in my code.
If you use this int 21,48 function, you can't use any malloc, calloc or any other related function which uses malloc internaly in the code, because most malloc implementations will delete (or not see ) the blocks allocated with this function.

I tested a small program, and fopen (for example) will crash my program, because it uses malloc and it deletes the memory control block created by in 21,48.

So to use this, I have to create my own fopen, fread etc (probably print and fprintf). Fortunately, I could replicate these functions with other dos int 21 ones (I tested some of them and there was no problem).

hmmm that does not sound correct at all. I do it all the time with watcomc. its been a long time since I used turboc, but should not make any difference.
I just did a test (turboc 2.01), works perfectly fine for me; compile with "tcc -ml"

C:
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>

typedef unsigned short word;

word _malloc(word para){
    word addr = 0;
    asm mov ah,48h
    asm mov bx,para
    asm int 21h
    asm jc    _error
    asm mov addr,ax
    return addr;
    _error:
    return 0;
}

int main(const int argc, const char *argv[])
{
    FILE *fp;
    word seg = _malloc(4096);
    unsigned char far *ptr = MK_FP(seg, 0);

    fp = fopen(argv[0], "rb");
    fseek(fp, 0x0L, SEEK_END);
    printf("%s - %lu, _malloc seg=%04X, ptr=%p\n", argv[0], ftell(fp), seg, ptr);
    fclose(fp);

    return 0;
}
 

Mills32

Experienced Member
Joined
Sep 25, 2018
Messages
147
Location
Spain
hmmm that does not sound correct at all. I do it all the time with watcomc. its been a long time since I used turboc, but should not make any difference.
I just did a test (turboc 2.01), works perfectly fine for me; compile with "tcc -ml"

C:
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>

typedef unsigned short word;

word _malloc(word para){
    word addr = 0;
    asm mov ah,48h
    asm mov bx,para
    asm int 21h
    asm jc    _error
    asm mov addr,ax
    return addr;
    _error:
    return 0;
}

int main(const int argc, const char *argv[])
{
    FILE *fp;
    word seg = _malloc(4096);
    unsigned char far *ptr = MK_FP(seg, 0);

    fp = fopen(argv[0], "rb");
    fseek(fp, 0x0L, SEEK_END);
    printf("%s - %lu, _malloc seg=%04X, ptr=%p\n", argv[0], ftell(fp), seg, ptr);
    fclose(fp);

    return 0;
}
I surely forgot to add a "far" somewhere in my code, your sample works well :)
 

Mills32

Experienced Member
Joined
Sep 25, 2018
Messages
147
Location
Spain
hmmm that does not sound correct at all. I do it all the time with watcomc. its been a long time since I used turboc, but should not make any difference.
I just did a test (turboc 2.01), works perfectly fine for me; compile with "tcc -ml"

C:
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>

typedef unsigned short word;

word _malloc(word para){
    word addr = 0;
    asm mov ah,48h
    asm mov bx,para
    asm int 21h
    asm jc    _error
    asm mov addr,ax
    return addr;
    _error:
    return 0;
}

int main(const int argc, const char *argv[])
{
    FILE *fp;
    word seg = _malloc(4096);
    unsigned char far *ptr = MK_FP(seg, 0);

    fp = fopen(argv[0], "rb");
    fseek(fp, 0x0L, SEEK_END);
    printf("%s - %lu, _malloc seg=%04X, ptr=%p\n", argv[0], ftell(fp), seg, ptr);
    fclose(fp);

    return 0;
}

I managed to create a minimal sample which replicated the problems of my program:

Code:
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>

typedef unsigned short word;

void far *_malloc(word para){
    word addr = 0;
    asm mov ah,48h
    asm mov bx,para
    asm int 21h
    asm jc    _error
    asm mov addr,ax
    return (void far *) MK_FP(addr,0);
    _error:
    return 0;
}


int main(const int argc, const char *argv[]){
    FILE *fp;
    unsigned char far *ptr;
    unsigned char far *ptr1;
 
    fp = fopen(argv[0],"rb");
    if (!fp) printf("error1\n");
    fclose(fp);
  
    ptr = _malloc(64000>>4);
    ptr1 = malloc(64000);
  
    fp = fopen(argv[0],"rb");
    if (!fp) printf("error2\n");
    fclose(fp);
  
    printf("_malloc seg=%04X malloc seg=%04X \n",FP_SEG(ptr),FP_SEG(ptr1));

    return 0;
}

ptr1 fails (NULL), second fopen fails (error2). If fopen uses malloc, that can confirm what I read. But maybe there are other reasons to make this fail.
 

BloodyCactus

Experienced Member
Joined
Oct 18, 2015
Messages
257
Location
Lexington VA
I took your dos call out and just used the library function farmalloc. I had it allocate 400KB and then the 64k.. no problems. is there a reason you want to use dos 48?
I suspect there is an issue with turbo c's heap, i ran debugger over it and it was doing something weird in its internal call. I suspect its messing with the MCB header because it kept giving me offset 0x0000:0x0008 doing some trickery assuming the memory pool after calling 0x4A is static, which is pretty stupid but also shows turbo c's age (well I have 2.01 which is quite old!)

C:
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
#include <dos.h>

int main(const int argc, const char *argv[]){
    FILE *fp;
    unsigned char far *ptr;
    unsigned char far *ptr1;

    fp = fopen(argv[0],"rb");
    if (!fp) printf("error1\n");
    fclose(fp);

    ptr = farmalloc(1024L * 400);
    ptr1 = malloc(64000);

    fp = fopen(argv[0],"rb");
    if (!fp) printf("error2\n");
    fclose(fp);

    printf("farmalloc seg=%04X malloc seg=%04X \n",FP_SEG(ptr),FP_SEG(ptr1));

    return 0;
}
 

Mills32

Experienced Member
Joined
Sep 25, 2018
Messages
147
Location
Spain
I took your dos call out and just used the library function farmalloc. I had it allocate 400KB and then the 64k.. no problems. is there a reason you want to use dos 48?
I suspect there is an issue with turbo c's heap, i ran debugger over it and it was doing something weird in its internal call. I suspect its messing with the MCB header because it kept giving me offset 0x0000:0x0008 doing some trickery assuming the memory pool after calling 0x4A is static, which is pretty stupid but also shows turbo c's age (well I have 2.01 which is quite old!)
Thanks for testing.

I just want to make my game engine library (and the final exe) as small as possible, by being independent of any turbo c or watcom library, using all dos (I think dos 4.0 will be the minimum required) and bios functions.

I don't really need to do this, it's just for the fun of making my own functions, and if I get the exe down to 64kb or less (it already is <64kb without the turbo c library), the compact memory model might be a bit faster (for slow 8088's I think).
 
Top