I created this method for testing/experimenting with a kernel on a 32 bit Intel/AMD system. The programs can easily be port to a 64 bit Intel/AMD processor with some reference to the Intel/AMD manuals..I actually have it here somewhere if you really need it.

Please Note - This is a hack that will only work on a 32 bit Intel/AMD system, paying special attention to the lines "don't need this line for some compilers/systems - i.e slackware 13.0"

So what does the module and program do?

The module basically sets up an interrupt "0x81" and a simple handler so that the programmer can interact with the kernel in a free and controlled way by calling the interrupt and executing the handler.

I know, not much of an example, its basic allowing the programmer to pass the address of a variable into the kernel an then set it to 99999 but this implementation can be expanded to cover a whole host of interests.

test1.c - The Kernel Module
Code:
#include <linux/kernel.h>
#include <linux/module.h>

struct id//interrupt description table
{
	unsigned short limit;
	void *base;
}__attribute__((packed)) myidtr;

struct intgate//interrupt gate struct
{
	unsigned short off1;
	unsigned short sel;
	unsigned short none;
	unsigned short off2;
}__attribute__((packed)) *idttr1, *idttr2, orig, orig2;

unsigned int myint = 99999;//value to set user variable to

void myfunc(void)//our simple handler
{
	__asm__
	(
		"movl	%ebp, %esp\n\t"//don't need this line for some compilers/systems - i.e slackware 13.0
		"popl	%ebp\n\t"//don't need this line for some compilers/systems - i.e slackware 13.0

		"pushl	%eax\n\t"
		"pushl	%ebx\n\t"

		"movl	myint, %ebx\n\t"
		"movl	%ebx, (%eax)\n\t"

		"popl	%ebx\n\t"
		"popl	%eax\n\t"

		"iret\n\t"
	);
}

union myaddr
{
	void *addr;
	struct
	{
		unsigned short a;
		unsigned short b;
	}__attribute__((packed)) iaddr;
}__attribute__((packed)) theaddr;

int init_module()
{	__asm__	("sidt	myidtr\n\t");

	idttr1 = (struct intgate*)(myidtr.base + (128 * 8));//interrupt 0x80
	idttr2 = (struct intgate*)(myidtr.base + (129 * 8));//interrupt 0x81
	orig2 = *idttr2;

	*idttr2 = *idttr1;//short cut - set 0x81 interrupt equal to interrupt 0x08

	theaddr.addr = (void*)myfunc;//interrupt handler

	idttr2->off1 = theaddr.iaddr.a;//set myfunc to 0x81 handler
	idttr2->off2 = theaddr.iaddr.b;//set myfunc to 0x81 handler
	return 0;
}

void cleanup_module()
{
	*idttr2 = orig2;
	printk("setting everything back...we're out of here!\n");
}
testit.c - The test user space executable
Code:
#include <stdio.h>
#include <stdlib.h>

unsigned int testval = 0;

int main(int argc, char**argv)
{
	fprintf(stdout, "initial testval->%d\n", testval);

	__asm__
	(
		"movl	$testval, %eax\n\t"
	       	"int	$0x81\n\t"	
	);

	fprintf(stdout, "final testval->%d\n", testval);
	exit(EXIT_SUCCESS);
}
Note - this is from months of reading the AMD/Intel manuals...I thought I would save you the pleasure of that task..