Abstraction and function pointers

To a programmer, reading data from the keyboard or the CDROM is essentially the same thing. All the programmer cares about is that the bits of data come from somewhere. It is one of the fundamental roles of the operating system to provide this abstraction for the programmer.

Figure 1-1. Abstraction

Application Programming Interfaces

Abstraction is implemented by an Application Programming Interface. The programmer designs a set of functions and variables as the API which other programmers will use. A common method used in the Linux Kernel is function pointers.

Example 1-1. Abstraction with function pointers

    #include <stdio.h>
    
    /* The API to implement */
    struct greet_api
  5 {
    	int (*say_hello)(char *name);
    	int (*say_goodbye)(void);
    };
    
 10 /* Our implementation of the hello function */
    int say_hello_fn(char *name)
    {
    	printf("Hello %s\n", name);
    	return 0;
 15 }
    
    /* Our implementation of the goodbye function */
    int say_goodbye_fn(void)
    {
 20 	printf("Goodbye\n");
    	return 0;
    }
    
    /* A struct implementing the API */
 25 struct greet_api greet_api =
    {
    	.say_hello = say_hello_fn,
    	.say_goodbye = say_goodbye_fn
    };
 30 
    /* main() doesn't need to know anything about how the
     * say_hello/goodbye works, it just knows that it does */
    int main(int argc, char *argv[])
    {
 35 	greet_api.say_hello(argv[1]);
    	greet_api.say_goodbye();
    
    	printf("%p, %p, %p\n", greet_api.say_hello, say_hello_fn, &say_hello_fn);
    
 40 	exit(0);
    };

Code such as the above is the simplest example of constructs used repeatedly through the Linux Kernel (and indeed many other projects). Lets have a look at some specific elements.

We start out with a structure that defines the API. The functions whose names are encased in parenthesis with a pointer marker describe a function pointer[1]. The function pointer describes the prototype of function it must point to; pointing it at a function without the correct return type or parameters will generate a compiler warning, and will probably lead to incorrect operation or crashes.

We then have our implementation of the API. Often for more complex functionality you will see an idiom where API implementation functions will only be a wrapper around another function that is conventionally prepended with or or two underscores (i.e. say_hello_fn() would call another function _say_hello_function()). This avoids "namespace pollution" and often enables significant changes in the internal workings whilst leaving the API constant. Our implementation is very simple however, and doesn't even need it's own support functions.

Second to last, we fill out the function pointers in struct greet_api greet_api. The name of the function is a pointer, therefore there is no need to take the address of the function (i.e. &say_hello_fn).

Finally we can call the API functions through the structure in main.

Libraries

Libraries have two roles which illustrate abstraction.

For example, a library implementing access to the raw data in JPEG files has both the advantage that the many programs who wish to access image files can all use the same library and the programmers building these programs do not need to worry about the exact details of the JPEG file format, but can concentrate their efforts on what their program wants to do with the image.

Summary

The essential points to note are

Exercises

Notes

[1]

Often you will see that the names of the parameters are omitted, and only the type of the parameter is specified. This allows the implementer to specify their own parameter names avoiding warnings from the compiler.