ioctl
bietet einen gerätespezifischen Eingangspunkt für den Treiber, um Kommandos zu behandeln. Es erlaubt Applikationen, auf Features zuzugreifen, die der Hardware eigen sind, z.B. Konfiguration.read
und write
zugegriffen werden. ioctl
im User-Space aufgerufen, gilt folgender Prototyp:int ioctl(int fd,int cmd,...);
char *argp
realisiert. Dieser Parameter hängt vom angegebenen Kontrollkommando (zweiter Parameter) ab.int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);
<linux/ioctl.h>
definiert sind.switch
.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#define IOCTL_GETVALUE 0x0001
int main( int argc, char **argv )
{
int i, fd;
char buffer[128];
fd=open(''mydevice'', O_RDWR);
i=ioctl( fd, IOCTL_GETVALUE, buffer );
printf(''%p - %s\n'', buffer, buffer );
}
/* vim: set ts=4: */
/* ioctltreiber */
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#define IOCTL_MAJOR 240
#define IOCTL_GETVALUE 0x0001
static char *Version =
''$Id: treiber4.htm,v 1.1 2001/11/15 16:41:34 si Exp $'';
static int MyIOctl( struct inode *Inode, struct file *File,unsigned int cmd, unsigned long arg );
static int MyIOctl( struct inode *Inode, struct file *File,unsigned int cmd, unsigned long arg )
{
printk(''ioctl called 0x%4.4x %p\n'', cmd, (void *)arg );
switch( cmd ) {
case IOCTL_GETVALUE:
copy_to_user( (void *)arg, ''Hollahop\n'', 10 );
break;
default:
printk(''unknown IOCTL 0x%x\n'', cmd);
break;
}
return( 0 );
}
static struct file_operations ioctlFops = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
THIS_MODULE,
#endif
NULL, /* llseek */
NULL, /* read */
NULL, /* write */
NULL, /* readdir */
NULL, /* poll */
MyIOctl, /* ioctl */
NULL, /* mmap */
NULL, /* open */
NULL, /* flush */
NULL, /* release */
NULL, /* fsync */
NULL, /* fasync */
NULL, /* lock */
};
static int __init IoctlInit(void)
{
if(register_chrdev(IOCTL_MAJOR, ''ioctl-Driver'', &ioctlFops) == 0) {
printk(''%s\n'', Version );
return 0;
};
printk(''ioctl: unable to get major %d\n'',IOCTL_MAJOR);
return( -EIO );
}
static void __exit IoctlExit(void)
{
printk(''cleanup_module called\n'');
unregister_chrdev(IOCTL_MAJOR,''ioctl-Driver'');
}
module_init( IoctlInit );
module_exit( IoctlExit );
fcntl
eingestellt. wait_queue_head_t
arbeiten. wait_queue_head_t my_queue;
init_waitqueue_head (&my_queue);
sleep_on(wait_queue_head_t *queue);
legt einen Prozess schlafen. Wenn das erwartete Ereignis niemals eintritt, wacht der Prozess auch nicht wieder auf.interruptible_sleep_on(wait_queue_head_t *queue);
arbeitet genauso, kann aber durch ein Signal unterbrochen werden. Damit wurde lange gearbeitet, bis int wait_event_interruptible(wait_queue_head_t queue,int condition);
erschien. Dieses Makro wartet auf ein Ereignis und testet auf sein Eintreten. Es expandiert zu einer while
-Schleife. wake_up(wait_queue_head_t *queue), wake_up_interruptible(wait_queue_head_t *queue), wake_up_sync(wait_queue_head_t *queue)
oder wake_up_interruptible_sync(wait_queue_head_t *queue)
kann man die Prozesse wieder aufwecken. poll
und select
select
wurde in BSD Unix vorgestellt, poll
war die Lösung im System V. poll
, weil es eine detailliertere Kontrolle ermöglicht.poll
sieht so aus:unsigned int (*poll)(struct file *,poll_table *wait)
.poll
als auch durch select
im Usermode aufgerufen.poll
-Funktion hat die Aufgabe festzustellen, ob Daten direkt gelesen und ob Daten direkt geschrieben werden können. Da die Applikation, die den Poll-Systemcall aufruft, durch das Betriebssystem eventuell schlafen gelegt wird (dann, wenn keines der spezifizierten Geräte Daten zum lesen bereit hält bzw. Daten zum Schreiben entgegennehmen kann), muß das Betriebssystem auch mitbekommen, wenn der Treiber eventuell wieder Daten zur Verfügung hat. Da das Schlafenlegen der Applikation im Regelfall über eine Waitqueue erfolgt, werden dem Betriebssystem alle diesbezüglich möglichen Waitqueues mitgeteilt.
unsigned int MyDevice_Poll( struct file *File, poll_table *wait ){
unsigned int mask = 0;
if( DataAvailableToRead ) {
mask |= POLLIN | POLLRDNORM;
}
if( DataCanBeWritten ) {
mask |= POLLOUT | POLLWRNORM;
}
poll_wait( &ReadQueue, wait );
poll_wait( &WriteQueue, wait );
return( mask );
}
tq_timer
: Die in dieser Queue eingehängten Funktionen werden bei jedem Timer-Tick aufgerufen.tq_immediate
: Entweder am Ende eines Systemcalls oder bei Aufruf des Schedulers werden die Funktionen in dieser Taskqueue aufgerufen. Damit eignet sich diese Queue insbesondere dazu, um einen Interrupt Service Thread (IST) einzuhängen.tq_disk
: Diese Task-Queue wird im Kontext von pluggable-Geräten genutzt. Die Task-Queue wird genutzt, um Aufträge direkt zu starten, nachdem ein Gerät eingehängt worden ist.tq_scheduler
gibt es unter Linux 2.4 nicht mehr. Da die aufgerufenen Funktionen nicht zwangsläufig im richtigen Kontext abgelaufen sind, wurde die Queue durch die Funktion schedule_task
ausgetauscht, die im richtigen Kontext praktisch das gleiche tut, nämlich eine Funktion aufrufen, sobald der Scheduler aufgerufen wird.void queue_task(struct tq_struct *task, task_queue *list);
in eine Queue eingehängt. Wird eine eingehängte Funktion aufgerufen, wird sie aus der Queue entfernt. Soll sie mehrfach aufgerufen werden können, muß sie sich immer wieder selber einhängen.tq_struct
neben der Funktionsadresse noch ein Parameter definiert, der der Funktion bei Aufruf übergeben wird.
struct tq_struct {
struct tq_struct *next; /* linked list of active bh's */
unsigned long sync; /* must be initialized to zero */
void (*routine)(void *); /* function to call */
void *data; /* argument to function */
};
Folgender Code hängt eine Funktion in die Queue:
tq.routine=IncCount;
tq.data=NULL; // no argument today
queue_task(&tq, &tq_timer);
IncCount
muß dann so definiert sein:
static void IncCount(void *arg){
timerticks++;
queue_task(&tq, &tq_timer);
return;
}
Es ist zu beachten, daß Funktionen, die über eine Task-Queue aufgerufen werden, nicht im Kontext eines Prozesses laufen. Der Zugriff auf die Struktur current
ist dann nicht möglich.