• Please review our updated Terms and Rules here

PC BIOS function int 21, 48

Mills32

Experienced Member
Joined
Sep 25, 2018
Messages
149
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?
 
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.
 
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.
 
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.
 
... 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.
 
... 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)
 
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.
 
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).
 
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.
 
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);
 
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).
 
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
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;
}
 
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 :)
 
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.
 
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;
}
 
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).
 
If someone is interested, I managed to replace all turbo c library functions with bios/dos int calls, (all file related functions, setvect getvect, sleep, exit etc...). Now I can use this 21h,48 function with no issues, so there was some bug in turbo c.

I still need the c0c.obj (compact memory model functions) to get the program to compile. It looks like it needs this to add the stack and the main entry correctly, but the exe is now "tiny" (60KB).
 
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;

Are you sure? Is that Turbo C specific?

I just tried this (Watcom - which is what I have readily available):

D:\scratch\xxx>wcl -ml -c foo.c

D:\scratch\xxx>type foo.c
char *tempdata;

void foo(void)
{
tempdata = (char *) 0x1000C180;
}

D:\scratch\xxx>wdis foo.obj

0008 36 C7 06 00 00 80 C1 mov word ptr ss:_tempdata,0xc180
000F 36 C7 06 02 00 00 10 mov word ptr ss:_tempdata+0x2,0x1000

which looks perfectly fine to me.

I do know of one difference between Turbo C and Watcom.

Even in huge memory model, pointers are far by default with Turbo C, not huge.

But that is not relevant to this, where only far is required (thus compact, large or huge are fine and don't need non-C90 keywords put into your source code - you just need to choose an appropriate memory model).

BFN. Paul.
 
Back
Top