Software interrupts

DOS, BIOS or any operating system provides system services via system calls. System calls are software interrupts. These are the interrupts generated by CPU with the execution of INT instruction.

Software interrupt control flow

System calls are kernel services to be used by users. User context runs in higher privilege level with protection and thus user context cannot jump or call to kernel functions directly. Operating system reserves a software interrupt vector for this purpose. This is a vector number which should be invoked with INT <vector>. For MSdos this number is 0x21. BIOS also provides a number of services via software interrupts like 0x10 for display, 0x16 for keyboard, 0x13 for disk storage service.

Now calling these system interrupts are tricky. These are not like function calls where compiler pushes argument one by one and calls the function. Here every arguments has to be in CPU registers. Then arguments are set to the registers using MOV. Last is the INT 0xNN instruction. After INT instruction context will jump to kernel interrupt handler. Kernel saves user context and switch to kernel context. Context is a snapshot of CPU registers. Kernel reads all arguments and does all the operations for this service and moves return value to AX and executes an IRET instruction. IRET woul switch context back to user mode and user will get return values and all output arguments in user context.

system interrupt control flow

System calls like file handling open/close,read/writes are system calls. We do not require to write inline assembly or int86 call as these are already implemented by C runtime library. However some DOS and BIOS system calls are not implemented by C runtime. We can call these system interrupts via inline assembly or via int86 routine.

Software interrupt with Inline Assembly

'INT' instruction of x86 microprocessor is used to generate a software interrupt to the CPU. This instruction takes an argument of a number which is the interrupt vector.

/* our own getch() function using inline assembly and INT 16 service */
/* INT 16,0 - Wait for Keypress and Read Character */
/* AH = 00 */
/* on return: */
/* AH = keyboard scan code */
/* AL = ASCII character or zero if special function key */
/* - halts program until key with a scancode is pressed */

char getkey(void)
{
  char ret;
  _asm{
    MOV ah, 0
    INT 0x16
    MOV ret, AL
    }
  return ret;
}

INT86

Int86 is a C library function facilitates access to bare bone DOS and BIOS service interrupts. It is a wrapper over inline assembly interrupt call. It takes CPU register values with object to a structure where member variables are equivalent to CPU registers.

Union REGS in c

REGS is a data structure primarily used as the arguments of int86 and other similar functions. REGS is of type union. It represents the individual CPU registers present in 8086 microprocessor. As we know 8086 is 16 bit microprocessor and CPU registers can be assessed by 16 bit mode as well as by 8bit mode. To facilitates this REGS has WORDREGS (16bit) and BYTEREGS (8 bit) structures as union. structure WORDREGS access the members as 2 bytes or 16 bit mode. Registers are like AX, BX, CX, DX etc. structure BYTEREGS access the members as 1 bytes or 8 bit mode. These can access upper 8bit and lower 8 bit of the same WORD registers. Register AX (16 bit) can be accessed by lower 8 bit with AL and upper 8 bit with AH.

struct WORDREGS {
    unsigned int    ax, bx, cx, dx, si, di, cflag, flags;
};

struct BYTEREGS {
    unsigned char   al, ah, bl, bh, cl, ch, dl, dh;
};

union   REGS    {
    struct  WORDREGS x;
    struct  BYTEREGS h;
};

struct  SREGS   {
    unsigned int    es;
    unsigned int    cs;
    unsigned int    ss;
    unsigned int    ds;
};

Int86 function in C

int86() Execute x86 software Interrupt

int int86 (
  int inter_no,
  union REGS *input_regs,
  union REGS *output_regs
);

Int86 arguments

  • inter_no - Software interrupt number
  • input_regs - Register values going into call
  • output_regs - Register values on return

int86() executes the x86 software interrupt vector given in 'inter_no'. Caller should provide values of the registers in 'input_regs' before calling this function. 'output_regs' is set to the value of the registers after returning from the function or rather returning from the interrupt is execution. The 'cflag' field of 'output_regs' is set to the status of the system carry flag.

Int86 return values

AX register generally have the return value of the system call. To indicate if the system interrupt is a success or failure, most of the system call sets AL as zero on success. System interrupt interface reference guide should be checked for proper interpretation. If the 'cflag' field in 'output_regs' is non-zero, an error has occurred and '_doserrno' (defined in ) is set to the error code.

Int86 usage

DOS software interrupt vector is 0x21. intdos function call can be done using int86(0x21) i.e. calling int86 with vector number as 0x21. int86 function is limited to primary registers of CPUs. Segment registers are needed for pointer types of 'far' or 'huge' addresses. int86x should be used instead of int86().

Int86 example

We have an example using int86 function in C to interface with Video modes interrupts. This invokes get VESA information service of the graphics card in BIOS. We provided a FAR buffer of 256 bytes to this service using int86x. This VGA BIOS interrupt populates the graphics card related information in the buffer. We have offset [0-3] as string of VESA signature. This can have a value of 'VESA' or 'VBE2'. Buffer offset [4-5] is a WORD containing the VESA version is BCD format. Buffer [6-9] is a DWORD which is a FAR pointer to video card's OEM vendor string.

#include <stdio.h>
#include <dos.h>

#define GET_VIDEO_MODE 0xF
#define SET_VIDEO_MODE 0x0
#define VIDEO_MODE_VGA 0x12
int main()
{
    union REGS input_regs, output_regs;
    int prev_mode, prev_txtcol;

    input_regs.h.ah = GET_VIDEO_MODE;
    int86(0x10, &input_regs, &output_regs);
    prev_mode = output_regs.h.al;
    prev_txtcol = output_regs.h.ah;
      
    
    input_regs.h.ah = SET_VIDEO_MODE;
    input_regs.h.al = VIDEO_MODE_VGA;
    int86(0x10, &input_regs, &output_regs);
    printf("Current Video mode is 0x12 640x480 16 colors\n");
    printf("Previous Video mode was 0x%X text width %d\n", prev_mode, prev_txtcol);

    return 0;
}

Int86 output

int86 calling int10 vga get mode set mode

Int86x function in C

Set x86 Segment Registers and Execute Software Interrupt

int int86x (
  int inter_no,
  union REGS *input_regs,
  union REGS *output_regs,
  struct SREGS  *seg_regs
);

Int86x arguments

  • inter_no - Software interrupt number
  • input_regs - Register values going into call
  • output_regs - Register values on return
  • seg_regs - Segment-register values on call

int86x() executes the 8086 software interrupt whose vector is 'inter_no'. Tt is similar to int86() but it has the added advantage of giving segment registers to the interrupt call. DS and ES segment registers can be set and 20bit real mode pointers can be passed to the routine. The 'input_regs' and 'output_regs' parameters are same as of int86 function. The parameter 'segregs' is the addition where caller can set DS and ES segment values.

Int86x usage

Return values are same as discussed in int86.

This should be called when DS and ES segment registers are needed in interrupt. int86() should be used if segment registers are not needed.. The values for the segment registers can be obtained with the segread() function or the FP_SEG macro. This function can be called with vector 0x21 to simulate the call of intdos() and intdosx(). The DS register is restored upon completion of the int86x() call.

Int86x example

Below example is an interesting example using int86x function in C. This invokes get VESA information service of the graphics card in BIOS. We provided a FAR buffer of 256 bytes to this service using int86x. This VGA BIOS interrupt populates the graphics card related information in the buffer. We have offset [0-3] as string of VESA signature. This can have a value of 'VESA' or 'VBE2'. Buffer offset [4-5] is a WORD containing the VESA version is BCD format. Buffer [6-9] is a DWORD which is a FAR pointer to video card's OEM vendor string.

#include<stdio.h>
#include<dos.h>
#include<string.h>

#define GET_VESA_INFO 0x4F00
unsigned char vga_info_buf [256];
char vesa_sig[5] = {0};
char vesa_oem_str[50] = {0};

int main()
{
    union REGS input_regs, output_regs;
    struct SREGS seg_regs;
    char far *vesa_oem;
    int i=0;
    
    segread(&seg_regs);
    seg_regs.es = FP_SEG((char far *)vga_info_buf);
    input_regs.x.di = FP_OFF((char far *)vga_info_buf);
              
    input_regs.x.ax = GET_VESA_INFO;
    int86x(0x10, &input_regs, &output_regs, &seg_regs);
    
    /* VESA signature */
    memcpy(vesa_sig, vga_info_buf, 4);
    
    /* Video card OEM string */
    vesa_oem = MK_FP((*(short*)&vga_info_buf[8]),
                     (*(short*)&vga_info_buf[6]));
    while(*vesa_oem)
    {
      vesa_oem_str[i++]=*vesa_oem++;
    }
    vesa_oem_str[i] = 0;
    printf("VGA signature %s\nVersion %d.%d\nVideo card '%s'\n",
    vesa_sig, *(char*)&vga_info_buf[5], *(char*)&vga_info_buf[4], vesa_oem_str);

    return 0;
}

Int86x output

int86x calling vesa vga software interrupt

Intdos() function in C

intdos() Call DOS system interrupt (vector 0x21)

int intdos (
  union REGS *input_regs,
  union REGS *output_regs
);

Intdos arguments

  • input_regs - Register values going into call
  • output_regs - Register values on return

intdos() invokes a DOS system call (Interrupt 21h) after setting the registers to the values in 'input_regs'. 'output_regs' is set to the value of the registers after the system call. The 'cflag' field of 'output_regs' is set to the status of the system carry flag; a non-zero value indicates a DOS error. 'input_regs.h.ah' is the DOS function number. (The structure REGS is defined in dos.h.)

Intdos usage

Return values are same as discussed in int86.

intdos() is intended to invoke DOS system calls that expect arguments in registers other than DX or AL, or that indicate errors by setting the carry flag. Services that use only DX and AL and don't use the carry flag, can be invoked via the bdos() function. If 'far' or 'huge' addresses are passed to DOS, you may need to use intdosx() instead of intdos().

Intdos example

Below example shows on how to call intdos function in C. We are calling get system date (2A) service using DOS interrupt. This service returns the system current date values in output registers. AL register should contain the day of week value, DL contains the day of the month, DH has the value of the month, CX has the value of the year.

#include<stdio.h>
#include<dos.h>

#define GET_SYSTEM_DATE 0x2A

char * days_of_week [] = 
{ "Sun", "Mon", "Tue",
  "Wed","Thu","Fri","Sat"};
    
char * months [] =
{"Jan", "Feb", "Mar","Apr","May","Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

int main()
{
    union REGS input_regs, output_regs;

    input_regs.h.ah = GET_SYSTEM_DATE;
    intdos(&input_regs, &output_regs);

    printf("Date: %s Day %d, Month %s Year %d\n",
           days_of_week[output_regs.h.al], 
           output_regs.h.dl,
           months[output_regs.h.dh-1],
           output_regs.x.cx);

    return 0;
}

Intdos output

intdos calling DOS int21 get system date

Intdosx() function in C

intdosx() Set segment registers and call DOS system interrupt (vector 0x21)

int intdosx (
  union REGS *input_regs,
  union REGS *output_regs,
  struct SREGS  *seg_regs
);

Intdosx arguments

  • input_regs - Register values going into call
  • output_regs - Register values on return
  • seg_regs - ES and DS values going into call

intdosx() invokes a DOS system interrupt 21h. Caller should set the registers to the values in 'input_regs' and setting the DS and ES registers to the values in 'seg_regs' before calling this function. 'output_regs' is set to the value of the registers after the system interrupt. The 'cflag' field of 'output_regs' is set to the status of the system carry flag; a non-zero value indicates a DOS error. 'input_regs.h.ah' is the DOS function number.

Intdosx usage

Return values are same as discussed in int86.

The values for segment registers can be obtained with the segread() function or the FP_SEG() macro. If the DS and ES registers don't need to be set, intdos() can be used. DOS functions that use only the DX and AL registers and that don't return error information in the carry flag can be invoked via the bdos() function.

Intdosx example

This is a small source code to demo the intdosx function. It invokes DOS create file 0x3C interrupt. The filename is a FAR pointer of string. This string is passed to the function using segment:offset DS:DX. We also close the file using intdos function 0x3E. We have used DIR DOS command in the command shell to show that file has been created.

#include<stdio.h>
#include<dos.h>
#include<string.h>

char *file_name = "intdosx.txt";

#define DOS_CREATE_FILE  0x3c
#define ATTR_CREATE_FILE 0x0
#define DOS_CLOSE_FILE   0x3e

int main()
{
    union REGS input_regs, output_regs;
    struct SREGS seg_regs;
    int handle;
    segread(&seg_regs);
    seg_regs.ds = FP_SEG((char far *)file_name);
    input_regs.x.dx = FP_OFF((char far *)file_name);
              
    input_regs.h.ah = DOS_CREATE_FILE;
    input_regs.x.cx = ATTR_CREATE_FILE;
    intdosx(&input_regs, &output_regs, &seg_regs);
    if(output_regs.x.cflag == 0)
    {
      printf("intdosx.txt has been created.\n");
      handle = output_regs.x.ax;
      input_regs.h.ah = DOS_CLOSE_FILE;
      input_regs.x.cx = handle;
      intdos(&input_regs, &output_regs);
      
    } else {
      printf("intdosx.txt cannot be created.\n");
    }
    return 0;
}

Intdosx output

intdosx calls DOS int21 create file service

About our authors: Team EQA

You have viewed 1 page out of 252. Your C learning is 0.00% complete. Login to check your learning progress.

#