Section 10.2. Writing the Code


10.2. Writing the Code

10.2.1. Device Basics

When you create a device driver, it is tied to the operating system through an entry in the filesystem. This entry has a major number that indicates to the kernel which driver to use when the file is referenced as well as a minor number that the driver itself can use for greater granularity. When the device driver is loaded, it registers its major number. This registration can be viewed by examining /proc/devices:

 ----------------------------------------------------------------------- lkp# less /proc/devices Character devices:  1 mem  2 pty  3 ttyp  4 ttyS  5 cua  6 lp  7 vcs  10 misc  29 fb 128 ptm 136 pts Block devices:  1 ramdisk  2 fd  3 ide0  7 loop  22 ide1 ----------------------------------------------------------------------- 

This number is entered in /proc/devices when the device driver registers itself with the kernel; for character devices, it calls the function register_chrdev().

 ----------------------------------------------------------------------- include/linux/fs.h  1: int register_chrdev(unsigned int major, const char *name,  2:      struct file_operations *fops) ----------------------------------------------------------------------- 

  • major. The major number of the device being registered. If major is 0, the kernel dynamically assigns it a major number that doesn't conflict with any other module currently loaded.

  • name. The string representation of the device in the /dev tree of the filesystem.

  • fops. A pointer to file-operations structure that defines what operations can be performed on the device being registered.

Using 0 as the major number is the preferred method for creating a device number for those devices that do not have set major numbers (IDE drivers always use 3; SCSI, 8; floppy, 2). By dynamically assigning a device's major number, we avoid the problem of choosing a major number that some other device driver might have chosen.[8] The consequence is that creating the filesystem node is slightly more complicated because after module loading, we must check what major number was assigned to the device. For example, while testing a device, you might need to do the following:

[8] The register_chrdev() function returns the major number assigned. It might be useful to capture this information when dynamically assigning major numbers.

 ----------------------------------------------------------------------- lkp@lkp# insmod my_module.o lkp@lkp# less /proc/devices 1 mem ... 233 my_module lkp@lkp# mknod c /dev/my_module0 233 0 lkp@lkp# mknod c /dev/my_module1 233 1 ----------------------------------------------------------------------- 

This code shows how we can insert our module using the command insmod. insmod installs a loadable module in the running kernel. Our module code contains these lines:

 ----------------------------------------------------------------------- static int my_module_major=0; ... module_param(my_module_major, int, 0); ... result = register_chrdev(my_module_major, "my_module", &my_module_fops); ----------------------------------------------------------------------- 

The first two lines show how we create a default major number of 0 for dynamic assignment but allow the user to override that assignment by using the my_module_major variable as a module parameter:

 ----------------------------------------------------------------------- include/linux/moduleparam.h  1: /* This is the fundamental function for registering boot/module  parameters. perm sets the visibility in driverfs: 000 means it's  not there, read bits mean it's readable, write bits mean it's  writable. */ ... /* Helper functions: type is byte, short, ushort, int, uint, long,  ulong, charp, bool or invbool, or XXX if you define param_get_XXX,  param_set_XXX and param_check_XXX. */ ...  2: #define module_param(name, type, perm) ----------------------------------------------------------------------- 

In previous versions of Linux, the module_param macro was MODULE_PARM; this is deprecated in version 2.6 and module_param must be used.

  • name. A string that is used to access the value of the parameter.

  • type. The type of value that is stored in the parameter name.

  • perm. The visibility of the module parameter name in sysfs. If you don't know what sysfs is, use a value of 0, which means the parameter is not accessible via sysfs.

Recall that we pass into register_chrdev() a pointer to a fops structure. This tells the kernel what functions the driver handles. We declare only those functions that the module handles. To declare that read, write, ioctl, and open are valid operations upon the device that we are registering, we add code like the following:

 ----------------------------------------------------------------------- struct file_operations my_mod_fops = {  .read = my_mod_read,  .write = my_mod_write,  .ioctl = my_mod_ioctl,  .open = my_mod_open, }; ----------------------------------------------------------------------- 

10.2.2. Symbol Exporting

In the course of writing a complex device driver, there might be reasons to export some of the symbols defined in the driver for use by other kernel modules. This is commonly used in low-level drivers that expect higher-level drivers to build upon their basic functionality.

When a device driver is loaded, any exported symbol is placed into the kernel symbol table. Drivers that are loaded subsequently can use any symbols exported by prior drivers. When modules depend on each other, the order in which they are loaded becomes important; insmod fails if the symbols that a high-level module depend on aren't present.

In the 2.6 Linux kernel, two macros are available to a device programmer to export symbols:

 ----------------------------------------------------------------------- include/linux/module.h 187 #define EXPORT_SYMBOL(sym)          \ 188   __EXPORT_SYMBOL(sym, "") 189  190 #define EXPORT_SYMBOL_GPL(sym)         \ 191   __EXPORT_SYMBOL(sym, "_gpl") ----------------------------------------------------------------------- 

The EXPORT_SYMBOL macro allows the given symbol to be seen by other pieces of the kernel by placing it into the kernel's symbol table. EXPORT_SYMBOL_GPL allows only modules that have defined a GPL-compatible license in their MODULE_LICENSE attribute. (See include/linux/module.h for a complete list of licenses.)

10.2.3. IOCTL

Until now, we have primarily dealt with device drivers that take actions of their own accord or read and write data to their device. What happens when you have a device that can do more than just read and write? Or you have a device that can do different kinds of reads and writes? Or your device requires some kind of hardware control interface? In Linux, device drivers typically use the ioctl method to solve these problems.

ioctl is a system call that allows the device driver to handle specific commands that can be used to control the I/O channel. A device driver's ioctl call must follow the declaration inside of the file_operations structure:

 ----------------------------------------------------------------------- include/linux/fs.h 863 struct file_operations {  ... 872 int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); ----------------------------------------------------------------------- 

From user space, the ioctl function call is defined as follows:

 int ioctl (int d, int request, ...); 

The third argument in the user space definition is an untyped pointer to memory. This is how data passes from user space to the device driver's ioctl implementation. It might sound complex, but to actually use ioctl within a driver is fairly simple.

First, we want to declare what IOCTL numbers are valid for our device. We should consult the file Documentation/ioctl-number.txt and choose a code that the machine won't use. By consulting the current 2.6 file, we see that the ioctl code of 'g' is not currently in use. In our driver, we claim it with the following code:

 #define MYDRIVER_IOC_MAGIC 'g' 

For each distinct control message the driver receives, we need to declare a unique ioctl number. This is based off of the magic number just defined:

 ----------------------------------------------------------------------- #define MYDRIVER_IOC_OP1 _IO(MYDRIVER_IOC_MAGIC, 0) #define MYDRIVER_IOC_OP2 _IOW(MYDRIVER_IOC_MAGIC, 1) #define MYDRIVER_IOC_OP3 _IOW(MYDRIVER_IOC_MAGIC, 2) #define MYDRIVER_IOC_OP4 _IORW(MYDRIVER_IOC_MAGIC, 3) ----------------------------------------------------------------------- 

The four operations just listed ( op1, op2, op3, and op4) have been given unique ioctl numbers using the macros defined in include/asm/ioctl.h using MYDRIVER_IOC_MAGIC, which is our ioctl magic number. The documentation file is eloquent on what everything means:

 ----------------------------------------------------------------------- Documentation/lioctl-number.txt 6 If you are adding new ioctls to the kernel, you should use the _IO 7 macros defined in <linux/ioctl.h>: 8 9  _IO an ioctl with no parameters 10  _IOW an ioctl with write parameters (copy_from_user) 11  _IOR an ioctl with read parameters (copy_to_user) 12  _IOWR an ioctl with both write and read parameters. 13 14 'Write' and 'read' are from the user's point of view, just like the 15 system calls 'write' and 'read'. For example, a SET_FOO ioctl would 16 be _IOW, although the kernel would actually read data from user space; 17 a GET_FOO ioctl would be _IOR, although the kernel would actually write 18 data to user space. ----------------------------------------------------------------------- 

From user space, we could call the ioctl commands like this:

 ----------------------------------------------------------------------- ioctl(fd, MYDRIVER_IOC_OP1, NULL); ioctl(fd, MYDRIVER_IOC_OP2, &mydata); ioctl(fd, MYDRIVER_IOC_OP3, mydata); ioctl(fd, MYDRIVER_IOC_OP4, &mystruct); ----------------------------------------------------------------------- 

The user space program needs to know what the ioctl commands are (in this case, MYDRIVER_IOC_OP1MY_DRIVER_IOC_OP4) and the type of arguments the commands expect. We could return a value by using the return code of the ioctl system call or we could interpret the parameter as a pointer to be set or read. In the latter case, remember that the pointer references a section of user space memory that must be copied into, or out of, the kernel.

The cleanest way to move memory between user space and kernel space in an ioctl function is by using the routines put_user() and get_user(), which are defined here:

 ----------------------------------------------------------------------- Include/asm-i386/uaccess.h * get_user: - Get a simple variable from user space. * @x: Variable to store result. * @ptr: Source address, in user space.  ... * put_user: - Write a simple value into user space. * @x: Value to copy to user space. * @ptr: Destination address, in user space. ----------------------------------------------------------------------- 

put_user() and get_user() ensure that the user space memory being read or written to is in memory at the time of the call.

There is an additional constraint that you might want to add to the ioctl functions of your device driver: authentication.

One way to test whether the process calling your ioctl function is authorized to call ioctl is by using capabilities. A common capability used in driver authentication is CAP_SYS_ADMIN:

 ----------------------------------------------------------------------- include/linux/capability.h 202 /* Allow configuration of the secure attention key */  203 /* Allow administration of the random device */  204 /* Allow examination and configuration of disk quotas */  205 /* Allow configuring the kernel's syslog (printk behavior) */  206 /* Allow setting the domainname */  207 /* Allow setting the hostname */  208 /* Allow calling bdflush() */  209 /* Allow mount() and umount(), setting up new smb connection */  210 /* Allow some autofs root ioctls */  211 /* Allow nfsservctl */  212 /* Allow VM86_REQUEST_IRQ */  213 /* Allow to read/write pci config on alpha */  214 /* Allow irix_prctl on mips (setstacksize) */  215 /* Allow flushing all cache on m68k (sys_cacheflush) */  216 /* Allow removing semaphores */  217 /* Used instead of CAP_CHOWN to "chown" IPC message queues, semaphores  218 and shared memory */  219 /* Allow locking/unlocking of shared memory segment */  220 /* Allow turning swap on/off */  221 /* Allow forged pids on socket credentials passing */  222 /* Allow setting readahead and flushing buffers on block devices */  223 /* Allow setting geometry in floppy driver */  224 /* Allow turning DMA on/off in xd driver */  225 /* Allow administration of md devices (mostly the above, but some  226 extra ioctls) */  227 /* Allow tuning the ide driver */  228 /* Allow access to the nvram device */  229 /* Allow administration of apm_bios, serial and bttv (TV) device */  230 /* Allow manufacturer commands in isdn CAPI support driver */  231 /* Allow reading non-standardized portions of pci configuration space */  232 /* Allow DDI debug ioctl on sbpcd driver */  233 /* Allow setting up serial ports */  234 /* Allow sending raw qic-117 commands */  235 /* Allow enabling/disabling tagged queuing on SCSI controllers and sending  236 arbitrary SCSI commands */  237 /* Allow setting encryption key on loopback filesystem */  238 239 #define CAP_SYS_ADMIN 21 ----------------------------------------------------------------------- 

Many other more specific capabilities in include/linux/capability.h might be more appropriate for a more restricted device driver, but CAP_SYS_ADMIN is a good catch-all.

To check the capability of the calling process within your driver, add something similar to the following code:

 if (! capable(CAP_SYS_ADMIN)) {  return EPERM; } 

10.2.4. Polling and Interrupts

When a device driver sends a command to the device it is controlling, there are two ways it can determine whether the command was successful: It can poll the device or it can use device interrupts.

When a device is polled, the device driver periodically checks the device to ensure that the command it delivered succeeded. Because device drivers are part of the kernel, if they were to poll directly, they risk causing the kernel to wait until the device completes the poll operation. The way device drivers that poll get around this is by using system timers. When the device driver wants to poll a device, it schedules the kernel to call a routine within the device driver at a later time. This routine performs the device check without pausing the kernel.

Before we get further into the details of how kernel interrupts work, we must explain the main method of locking access to critical sections of code in the kernel: spinlocks. Spinlocks work by setting a special flag to a certain value before it enters the critical section of code and resetting the value after it leaves the critical section. Spinlocks should be used when the task context cannot block, which is precisely the case in kernel code. Let's look at the spinlock code for x86 and PPC architectures:

 ----------------------------------------------------------------------- include/asm-i386/spinlock.h 32 #define SPIN_LOCK_UNLOCKED (spinlock_t) { 1 SPINLOCK_MAGIC_INIT } 33 34 #define spin_lock_init(x)  do { *(x) = SPIN_LOCK_UNLOCKED; } while(0) ... 43 #define spin_is_locked(x)  (*(volatile signed char *)(&(x)->lock) <= 0) 44 #define spin_unlock_wait(x)  do { barrier(); } while(spin_is_locked(x)) include/asm-ppc/spinlock.h 25 #define SPIN_LOCK_UNLOCKED  (spinlock_t) { 0 SPINLOCK_DEBUG_INIT } 26 27 #define spin_lock_init(x)  do { *(x) = SPIN_LOCK_UNLOCKED; } while(0) 28 #define spin_is_locked(x)  ((x)->lock != 0) while(spin_is_locked(x)) 29 #define spin_unlock_wait(x)  do { barrier(); } while(spin_is_locked(x)) ----------------------------------------------------------------------- 

In the x86 architecture, the actual spinlock's flag value is 1 if unlocked whereas on the PPC, it's 0. This illustrates that in writing a driver, you need to use the supplied macros instead of raw values to ensure cross-platform compatibility.

Tasks that want to gain the lock will, in a tight loop, continuously check the value of the special flag until it is less than 0; hence, waiting tasks spin. (See spin_unlock_wait() in the two code blocks.)

Spinlocks for drivers are normally used during interrupt handling when the kernel code needs to execute a critical section without being interrupted by other interrupts. In prior versions of the Linux kernel, the functions cli() and sti() were used to disable and enable interrupts. As of 2.5.28, cli() and sti() are being phased out and replaced with spinlocks. The new way to execute a section of kernel code that cannot be interrupted is by the following:

 ----------------------------------------------------------------------- Documentation/cli-sti-removal.txt  1: spinlock_t driver_lock = SPIN_LOCK_UNLOCKED;  2: struct driver_data;  3:  4: irq_handler (...)  5: {  6: unsigned long flags;  7: ....  8: spin_lock_irqsave(&driver_lock, flags);  9: .... 10: driver_data.finish = 1; 11: driver_data.new_work = 0; 12: .... 13: spin_unlock_irqrestore(&driver_lock, flags); 14: .... 15: } 16: 17: ... 18: 19: ioctl_func (...) 20: { 21: ... 22: spin_lock_irq(&driver_lock); 23: ... 24: driver_data.finish = 0; 25: driver_data.new_work = 2; 26: ... 27: spin_unlock_irq(&driver_lock); 28: ... 29: } ----------------------------------------------------------------------- 

Line 8

Before starting the critical section of code, save the interrupts in flags and lock driver_lock.

Lines 912

This critical section of code can only be executed one task at a time.

Line 27

This line finishes the critical section of code. Restore the state of the interrupts and unlock driver_lock.

By using spin_lock_irq_save() (and spin_lock_irq_restore()), we ensure that interrupts that were disabled before the interrupt handler ran remain disabled after it finishes.

When ioctl_func() has locked driver_lock, other calls of irq_handler() will spin. Thus, we need to ensure the critical section in ioctl_func() finishes as fast as possible to guarantee the irq_handler(), which is our top-half interrupt handler, waits for an extremely short time.

Let's examine the sequence of creating an interrupt handler and its top-half handler (see Section 10.2.5 for the bottom half, which uses a work queue):

 ----------------------------------------------------------------------- #define mod_num_tries 3 static int irq = 0; ... int count = 0; unsigned int irqs = 0; while ((count < mod_num_tries) && (irq <= 0)) {  irqs = probe_irq_on();  /* Cause device to trigger an interrupt.   Some delay may be required to ensure receipt    of the interrupt */  irq = probe_irq_off(irqs);  /* If irq < 0 multiple interrupts were received.   If irq == 0 no interrupts were received. */  count++; } if ((count == mod_num_tries) && (irq <=0)) {  printk("Couldn't determine interrupt for %s\n",    MODULE_NAME); } ----------------------------------------------------------------------- 

This code would be part of the initialization section of the device driver and would likely fail if no interrupts could be found. Now that we have an interrupt, we can register that interrupt and our top-half interrupt handler with the kernel:

 ----------------------------------------------------------------------- retval = request_irq(irq, irq_handler, SA_INTERRUPT,       DEVICE_NAME, NULL); if (retval < 0) {  printk("Request of IRQ %n failed for %s\n",     irq, MODULE_NAME);  return retval; } ----------------------------------------------------------------------- 

request_irq() has the following prototype:

 ----------------------------------------------------------------------- arch/ i386/kernel/irq.c 590 /**  591 *  request_irq - allocate an interrupt line  592 *  @irq: Interrupt line to allocate  593 *  @handler: Function to be called when the IRQ occurs  594 *  @irqflags: Interrupt type flags  595 *  @devname: An ascii name for the claiming device  596 *  @dev_id: A cookie passed back to the handler function ... 622 int request_irq(unsigned int irq,  623     irqreturn_t (*handler)(int, void *, struct pt_regs *),  624     unsigned long irqflags,  625     const char * devname, 626     void *dev_id) ----------------------------------------------------------------------- 

The irqflags parameter can be the ord value of the following macros:

  • SA_SHIRQ for a shared interrupt

  • SA_INTERRUPT to disable local interrupts while running handler

  • SA_SAMPLE_RANDOM if the interrupt is a source of entropy

dev_id must be NULL if the interrupt is not shared and, if shared, is usually the address of the device data structure because handler receives this value.

At this point, it is useful to remember that every requested interrupt needs to be freed when the module exits by using free_irq():

 ----------------------------------------------------------------------- arch/ i386/kernel/irq.c 669 /** 670 *  free_irq - free an interrupt 671 *  @irq: Interrupt line to free 672 *  @dev_id: Device identity to free ... 682 */ 683  684 void free_irq(unsigned int irq, void *dev_id) ----------------------------------------------------------------------- 

If dev_id is a shared irq, the module should ensure that interrupts are disabled before calling this function. In addition, free_irq() should never be called from interrupt context. Calling free_irq() in the module cleanup routine is standard. (See spin_lock_irq() and spin_unlock_irq.)

At this point, we have registered our interrupt handler and the irq it is linked to. Now, we have to write the actual top-half handler, what we defined as irq_handler():

 ----------------------------------------------------------------------- void irq_handler(int irq, void *dev_id, struct pt_regs *regs) {  /* See above for spin lock code */  /* Copy interrupt data to work queue data for handling in   bottom-half */  schedule_work( WORK_QUEUE );  /* Release spin_lock */ } ----------------------------------------------------------------------- 

If you just need a fast interrupt handler, you can use a tasklet instead of a work queue:

 ----------------------------------------------------------------------- void irq_handler(int irq, void *dev_id, struct pt_regs *regs) {  /* See above for spin lock code */  /* Copy interrupt data to tasklet data */  tasklet_schedule( TASKLET_QUEUE );  /* Release spin_lock */ } ----------------------------------------------------------------------- 

10.2.5. Work Queues and Tasklets

The bulk of the work in an interrupt handler is usually done in a work queue. In the last section, we've seen that the top half of the interrupt handler copies pertinent data from the interrupt to a data structure and then calls schedule_work().

To have tasks run from a work queue, they must be packaged in a work_struct. To declare a work structure at compile time, use the DECLARE_WORK() macro. For example, the following code could be placed in our module to initialize a work structure with an associated function and data:

 ----------------------------------------------------------------------- ... struct bh_data_struct {  int data_one;  int *data_array;  char *data_text; } ... static bh_data_struct bh_data; ... static DECLARE_WORK(my_mod_work, my_mod_bh, &bh_data); ... static void my_mod_bh(void *data) {  struct bh_data_struct *bh_data = data;  /* all the wonderful bottom half code */ } ----------------------------------------------------------------------- 

The top-half handler would set all the data required by my_mod_bh in bh_data and then call schedule_work(my_mod_work).

schedule_work() is a function that is available to any module; however, this means that the work schedule is put on the generic work queue "events." Some modules might want to make their own work queues, but the functions required to do so are only exported to GPL-compatible modules. Thus, if you want to keep your module proprietary, you must use the generic work queue.

A work queue is created by using the create_workqueue() macro, which calls __create_workqueue() with a second parameter of 0:

 ----------------------------------------------------------------------- kernel/workqueue.c 304 struct workqueue_struct *__create_workqueue(const char *name,  305            int singlethread) ----------------------------------------------------------------------- 

name can be up to 10 characters long.

If singlethread is 0, the kernel creates a workqueue thread per CPU; if singlethread is 1, the kernel creates a single workqueue thread for the entire system.

Work structures are created in the same way as what's been previously described, but they are placed on your custom work queue using queue_work() instead of schedule_work().

 ----------------------------------------------------------------------- kernel/workqueue.c 97 int fastcall queue_work(struct workqueue_struct *wq, struct work_struct *work) 98 { ----------------------------------------------------------------------- 

wq is the custom work queue created with create_workqueue().

work is the work structure to be placed on wq.

Other work queue functions, found in kernel/workqueue.c, include the following:

  • queue_work_delayed(). Ensures the work structure function is not called until a specified number of jiffies has passed.

  • flush_workqueue(). Causes the caller to wait until all scheduled work on the queue has finished. This is commonly used when a device driver exits.

  • destroy_workqueue(). Flushes and then frees the work queue.

Similar functions, schedule_work_delayed() and flush_scheduled_work(), exist for the generic work queue.

10.2.6. Adding Code for a System Call

We could edit the Makefile in /kernel to include a file with our function, but an easier method is to include our function code in an already existing file in the source tree. The file /kernel/sys.c contains the kernel functions for the system calls and the file arch/i386/kernel/sys_i386.c contains x86 system calls with a nonstandard calling sequence. The former is where we add the source code for our syscall function written in C. This code runs in kernel mode and does all the work. Everything else in this procedure is in support of getting us to this function. It is dispatched through the x86 exception handler:

 ----------------------------------------------------------------------- kernel/sys.c  1: ...  2: /* somewhere after last function */  3:  4: /* simple function to demonstrate a syscall. */  5: /* take in a number, print it out, return the number+1 */  6:   7: asmlinkage long sys_ourcall(long num)  8: {  9: printk("Inside our syscall num =%d \n", num); 10: return(num+1); 11: } ----------------------------------------------------------------------- 

When the exception handler processes the int 0x80, it indexes into the system call table. The file /arch/i386/kernel/entry.S contains low-level interrupt handling routines and the system call table sys_call_tabl. The table is an assembly code implementation of an array in C with each element being 4 bytes. Each element or entry in this table is initialized to the address of a function. By convention, we must prepend the name of our function with sys_. Because the position in the table determines the syscall number, we must add the name of our function to the end of the list. See the following code for the table changes:

 ----------------------------------------------------------------------- arch/i386/kernel/entry.S  : .data 608: ENTRY(sys_call_table)   .long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting*/ ...   .long sys_tgkill  /* 270 */   .long sys_utimes   .long sys_fadvise64_64   .long sys_ni_syscall /* sys_vserver */   .long sys_ourcall  /* our syscall will be 274 */ 884: nr_syscalls=(.-sys_call_table)/4 ----------------------------------------------------------------------- 

The file include/asm/unistd.h associates the system calls with their positional numbers in the sys_call_table. Also in this file are macro routines to assist the user program (written in C) in loading the registers with parameters. Here are the changes to unistd.h to insert our system call:

 ----------------------------------------------------------------------- include/asm/unistd.h  1: /*  2: * This file contains the system call numbers.  3: */  4:  5: #define __NR_restart_syscall 0  6: #define __NR_exit    1  7: #define __NR_fork    2  8: ...   9: #define __NR_utimes   271 10: #define __NR_fadvise64_64  272 11: #define __NR_vserver   273 12: #define __NR_ourcall   274 13: 14: /* #define NR_syscalls 274 this is the old value before our syscall */ 15: #define NR_syscalls   275 ----------------------------------------------------------------------- 

Finally, we want to create a user program to test the new syscall. As previously mentioned in this section, a set of macros exists to assist the kernel programmer in loading the parameters from C code into the x86 registers. In /usr/include/asm/unistd.h, there are seven macros: _syscallx(type, name,..), where x is the number of parameters. Each macro is dedicated to loading the proper number of parameters from 0 to 5 and syscall6(...) allows for passing a pointer to more parameters. The following example program takes in one parameter. For this example (on line 5), we use the _syscall1(type, name,type1,name1) macro from /unistd.h, which resolves to a call to int 0x80 with the proper parameters:

 ----------------------------------------------------------------------- mytest.c  1: #include <stdio.h>  2: #include <stdlib.h>  3: #include "/usr/include/asm/unistd.h"  4:  5: _syscall(long,ourcall,long, num);  6:  7: main()  8: {  9: printf("our syscall --> num in=5, num out = %d\n", ourcall(5)); 10: } ----------------------------------------------------------------------- 




The Linux Kernel Primer. A Top-Down Approach for x86 and PowerPC Architectures
The Linux Kernel Primer. A Top-Down Approach for x86 and PowerPC Architectures
ISBN: 131181637
EAN: N/A
Year: 2005
Pages: 134

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net