Writing ISRs (Interrupt Service Routines) - Interrupt Service Routines
Discussion by Aditya with 6 Replies.
Last Update: March 10, 2011, 7:34 am
It is assumed in this tutorial that you have knowledge of the C language. Although something about pointers is mentioned but this is NOT a C language tutorial. So here we go.
Many functions of the computer are performed by use of interrupts. These include the typing, printer, task switching etc to name some of them. Now how does these things happen? We are here to answer some of these questions.
Pointers in C language:
Pointers in C language are an amazing thing to work with and very dangerous also if you don’t know what you are doing with them. Now since you know C I assume that you must also have the basic knowledge of pointers. So we move on to the better stuff.
As you know pointers can be made to point to just about anything but we are interested here in pointers to functions, why is that you will know shortly.
Pointer to a function can be declared as shown below
<return_type_of_function> (*ptr)(<arguments_of _function>)
PS: The parenthesis around the ptr are necessary. Why this is necessary you will know when we describe the rule of reading what pointer declarations mean.
Let’s see an example of this. Consider that we have a function named myfunc with prototype as ( void myfunc() ), now to declare a pointer to this function from the above rule will be like this
This declares ptr to be a type of pointer which points to a function that takes no arguments and doesn’t return anything. Note that the function this pointer points to hasn’t been told yet so this pointer is dangerous to use.
Initializing the pointer is very easy just do ptr=myfunc; and we have the pointer ptr pointing to the function myfunc since the name of the function appears to give the address of the function where it is stored just like arrays where name gives the address of the array from where it starts.
Similarly if we had a function with prototype as (int myfunc2(int,int)) the pointer to this function will be declared as int (*ptr)(int,int). Actually this pointer can point to any function with a prototype as that of myfunc2. I think you got the idea.
Rule to read pointers declarations
Well this rule is not some kind of a standard but it almost never fails however you are very smart people I think you will find something where this rule doesn’t apply.
For any type of pointer declaration start from the name of the pointer variable then go to right and add up to the declaration meaning after that go to the left and add to whatever you just found about the declaration of pointer. You will be stopped whenever you find parenthesis or some terminate symbol like ‘=’. Let’s take a few examples to make this more clear
1.) int *ptr; The meaning of this declaration according to above rule. We start from name ptr then we go to right we find a ‘=’ so we move on to left and find a ‘*’ so the declaration upto this point is “ptr is a pointer…….” now since we didn’t find any terminators we continue moving left and find an ‘int’ so the declaration means “ptr is a pointer to an integer”.
2.) int (*ptr)(); Applying the above said rule we start from ptr then go to right and find a parenthesis. So we move to left and find a ‘*’ therefore till now “ptr is a pointer”. Now since we find a “(“ we stop moving left and move right and find “()”. Therefore the declaration till now becomes “ptr is a pointer to a function which has no arguments” we are stopped by ; so we move left again there we find an int so the declaration is “ptr is a pointer to a function that takes no arguments and return an integer value”.
3.) int *ptr: starting at ptr move right we find “” this means till now “ptr is an array of 5” we are stopped by “;” so we move left and find a “*” therefore decleration till now means “ptr is an array of 5 pointers” we continue moving left since there’s nothing to stop we find “int” so the full declaration becomes “ptr as an array of 5 pointers to integers”.I think you got what I’m saying. So how about you trying some examples …
CODEA) int* (*ptr)()
b[b][/b]) int* (*ptr)(int*,char)
c)int *(*ptr)(int *,char *)[/color]
Back to writing ISR s
With the pointer terminology clear now take a look what these pointers mean in the code. Well there are 2 kind of pointers
a ) which we know
b ) which our teachers never taught us.
Actually there are 3 types.
Near Pointers: we all know these so there’s nothing to talk about them really. These are the ones that our teachers dared to tell us (thanks 4 that). These pointers are 2 bytes long always i.e. any pointer that you declared normally they are 2 bytes long. This permits only 2^16 values which implies that you can access only the code which is in your data segment (since 1 segment is 64KB = 2^16). Any attempt to access data will result in segment protection error from the OS.
Far and Huge Pointers: We take both these two since they are common and differ only when some arithmetic is performed using these pointers. Yes I think you have guessed these type of pointers our teachers never dared to tell us so something must be special about them!
Far and Huge pointers are both 4 bytes long. This means they can they can handle about 2^32= 2GBof memory locations!! Now since we don’t have that much amount of memory when we are real mode (when computer starts up) we are safe since only 1MB is available (only 20 address lines available instead of 32 present address lines). We can access the whole of memory when we are in protected mode. This mode gives the whole 2GB memory as a flat i.e. no segments are available when in protected mode. But since we are in real mode our max memory limit is 1MB and our memory is fragmented into different segments of 64Kb each in size.
Ok. So now you will ask we have 20 address lines and since each segment is 64KB= 2^16 how do we put the correct address. Well this is already done for you in the processor. To access any memory location in the 1MB limit we first find the starting address of the segment then to this address (base address of segment) an offset of 16 bits is added. The way these addresses are added is a bit tricky. First the segment’s address is left shifted by 4 bits (padded with 4 zeros in the end) and then the offset is added to it. Now this is a full 20 bit address and processor use this address to locate a memory location in the 1MB limit. The shifting need not be done by us since processor does that for us we just need to supply the segment’s base address and an offset within that segment.
All segments starts at some multiple of 16 also called a paragraph boundary. This is because for each 16 bit segment address it is first left shifted by 4 bits ( 2^4=16 bytes).
One more thing where these pointers differ from each other is in the way they are stored. Far pointers are not normalized while huge pointers are normalized. Normalization means that they have most part of the address in the segment. This is not a problem if you will perform logical / arithmetic operations on such pointers but if you do then using huge will benefit you since the results will be same as expected while with far pointers you may get strange results. Huge pointer arithmetic is therefore more complex which requires macros and thus slows down the performance.
Declaring far /huge pointers
The Turbo C provides us the keywords far and huge to declare such pointers. Just add these keywords and declare these pointers like you normally do.
Example int far *ptr; (this ptr is a far pointer to an int)
int huge *ptr(this ptr is huge pointer to an int)
Why do we need these pointers while writing ISR s?
Well as I said before with normal / near pointer declaration we can’t access the code outside of our data segment. Since ISR s require us to go beyond our data segment we must use these pointers. Only far can be used huge may not be required since there’s not much in the data segment which we need to access. Using huge pointers we can access more than 1MBs of data in the data segment while 1MB when we use far.
The Booting process of PC
When we push the start button of the PC many things are done. First a POST is done which checks the computer for its resources after the check is performed the BIOS (Basic Input Output System) is given the control. The earlier versions of BIOS used to come with BASIC loaded on them so in case an OS is not found the BASIC used to run on the computer now this doesn’t happen and that’s why we see Disk Error message when no OS is found.
The purpose of the BIOS is to make computer ready to support the OS so that is can start up. Its purpose is to load the interrupt routines and start video output in text mode. The interrupt routines used by BIOS are very basic and they don’t interpret the data its up to the OS getting loading to make use of that data.
The interrupt service is loaded through an IVT (Interrupt Vector Table) this is done so that if any other routine is required for some interrupt instead of the one provided by BIOS we just need to change the values present in the vector table. This makes interrupt handling robust.
Interrupt Handling Process
Whenever an interrupt occurs the processor stops whatever it was doing and handles the interrupt. Each interrupt (256 in total supported by Intel’s processors) has an entry in the IVT which is made by BIOS. Each entry is 4 bytes long. Therefore whenever an interrupt occurs its number is multiplied by 4 to access the ISR location. These locations are located in the low memory area of the memory, thus to have access to code outside of our segment we need to use far /huge pointers.
An ISR is just a module which replaces the entry currently in the IVT to itself. Since these modules already present in the memory don’t have any names therefore the only way to access them is by use of pointers to functions.
TC provides the keyword interrupt for writing ISR s. This keyword may be different on different compilers and the code that we will compile is for REAL MODE only (16 bit code). For 32 bit code we have to switch into protected mode which I don’t know myself yet. Also the IVT thing works only in REAL MODE in protected mode this changes to IDT (Interrupt Descriptor Table) this is way 2 advanced to handle here.
Things that we need
TC 16 bit compiler (that IDE one)
Functions setvect() and getvect()
A big heart to handle the errors that XP will show you when you run these programs!
The functions setvect() and getvect() are both defined in dos.h. They provide a neat way to set the IVT for an interrupt to our ISR. A small program will demonstrate this.
char far *scr=(char far*) 0xb8000000l; // Address of VDU for //monochrome displays use 0xb0000000l
void interrupt our(); // define an ISR our
void interrupt (*prev)(); //define a pointer to the location in IVT
prev=getvect(9); // Save previous value in IVT in the pointer
setvect(9,our); // Set IVT to our ISR
getch(); // u know that!
void interrupt our()
scr[i]=i%16; //(I think only 16 colors are present in text mode)
(*prev)(); // necessary unless u know everything this interrupt does.
The above program creates an ISR for the keyboard interrupt( number 9). When we are inside an ISR we can’t generate another interrupt since we can handle only 1 interrupt at 1 time. Therefore we have written directly to the VDU in the our ISR. Since there are 80 cols and 25 rows => (80 X 25 =2000) memory locations. Each row/col entry comprises of a 2 byte value( that's why looping 4000 times). The even byte (1st byte) gives the value present at that location while the odd byte (2nd byte) gives its color and other attributes such as blinking, intensity etc.
For using the internal clock use interrupt number 8. Watch what happens then!
We have called (*prev)() from inside the our ISR this is necessary since at this point if time we don’t know what actually takes place on pressing the key. Therefore its best to call the previously stored ISR after we are done for smooth operation.
To compile as a CPP file use 3 periods(...) in the argument section of the functions declarations. However this is only useful when we are not interested in the values present on the stack.
When an interrupt occurs the processor responsibility is to save its state so that it can resume from that point when handling of interrupt is over. By default the processor just pushes 3 values on the current stack. These are the flags, IP(Instruction Pointer) and the CS(Code Segment) register. However to successfully execute the program we need that all registers state be saved. This is done by the compiler for us.
Use this command tcc –s <filename>. This will generate an ASM file with the same name as that of the filename given. Now if you take a look at the statement proc our far in this asm code you will see that all general purpose registers are pushed before any instruction in the ISR is executed. However there’s no mention of the CS,IP and flags but they are present on stack and their values can be changed. These changed values if any will be popped back into the registers when the ISR is over.
PS: Intel specifies that you can’t change the value in the IP register directly however through stack this can be done easily. If this gives you some bright ideas please meet me to discuss that I have thought on it but some problems are there!
Using Stack inside of ISR.
To use stack we need to pass some argument in the ISR so that these values are available in that argument and we can use them. The best way to do that is to declare a structure in the order in which the values appear in the ASM code while pushing. Also remember that to use completely Stack values the IP,CS and flag registers have also to be present in the structure. The structure is shown below
Now using this structure we can get values present in all the registers . I have developed a program to just that when the user presses the keyboard (i.e. we use interrupt number 9) to display the values.
struct INTERRUPT r1;
void interrupt (*prev)();
void interrupt myfunc(struct INTERRUPT r)
//cout<<"\nHELLO WORLD ONCE AGAIN";
/*cout<<"\nAddress of myfunc="<< (void*)(myfunc)<<"\n";
cout<<"\nAddress of myfunc2="<< (void*)(myfunc2)<<"\n";
printf("VALUES OF R1 ARE\n");
Another Program but it doesn’t work that well but shows the values of all register is shown below. Just press a key to get out of it If you can pls correct the malfunction that arises while printing values. Also if you see any negative values in the registers its just that the address is being interpreted or it has exceeded the maximum limit of the data type. No NEGATIVE addresses are there!!!
char far *scr=(char far*)0xB8000000l;
struct INTERRUPT r1;
// unsigned ip,fl,c;
void interrupt (*prev)();
void printToMemory(char *msg,int row,int col)
// else col++;
void printToMemory2(char *msg,int *row,int col)
void interrupt our(struct INTERRUPT r)
static int i=8;
if(counter==18 && busy==0)
itoa(r.cs,str2,16);//WHY NEGATIVE VALUES IN CS?? DECLARED IT AS UNSIGNED!!
//r1.ip=atoi(str); //Should hold value OF IP that's in str
printf("THE OUTPUT YOU SEE IS IN THE FORM CS:IP(or CODE_SEGMENT:PROGRAM COUNTER)");
printf("\n TO CHANGE THE OUTPUT FROM DECIMAL TYPE 16 in PLACE OF 10 in itoa()");
printf("\n Don't USE printf inside the ISR our.SINCE printf also uses an INTERRUPT");
printf("\nCS=%x \nDS=%d \nIP=%x",r1.cs,r1.ds,r1.ip);
That’s all for now try some programs by getting info on various interrupts. Note that all are not defined by the BIOS and are user defined interrupts like interrupt 0x21 this is defined by DOS not the bios. However you can use it and any other up until 255 (0-255 total interrupts).
Please give some remarks for this tutorial. So that I know people are reading the stuff I write.
Writing ISRs (Interrupt Service Routines)
Replying to Aditya Thanks for putting this together.
-reply by suk00n
Writing ISRs (Interrupt Service Routines)
Replying to Adityait is a nice and useful tutorial.
-reply by mohan
int main (void)
unsigned port =0;
value = output(port, 'c') ;
printf( "value %c sent to port number %dand",value,port);
-reply by kingsley
Very good tutorial, and it is helpful for me to understand how the interrupt service routine works.
I have a question: How can I test these code? Should I use DOS , win98, or XP?
Thank you very much.
|The Holywood Principle Don't Call Us, We'll Call You (0)||(21) [tutorial] Basics Of C Programming - Part 2|