Some Tricks used by the Linux kernel

Linux kernel uses a number of advanced keywords, macros, and extensions to C language. Here is a description of some of these keywords, macros and extensions that I found troublesome to understand and had to investigate a little bit in order to understand them.

Note: I have used kernel versions 2.4.18 or 2.6.0

asmlinkage:

asmlinkage tells the compiler to pass parameters on the local stack. This is related to the FASTCALL macro, which resolves to tell the (architecture-specific) compiler to pass parameters in the general-purpose registers. Here are the macros from include/asm-i386/linkage.h (line 4):

4: #define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0))) 5: #define FASTCALL(x) x __attribute__((regparm(3)))

An example of asmlinkage is as follows:

asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz)

UL:

UL is commonly appended to the end of a numerical constant to mark an unsigned long. UL (or L for long) is necessary because it tells the compiler to treat the value as a long value. This prevents certain architectures from overflowing the bounds of their data types.

Example: include/linux/kernel.h

21: #define LONG_MAX ((long)(~0UL>>1)) 23: #define ULONG_MAX (~0UL)

inline:

The inline keyword is intended to optimize the execution of functions by embeding the code of the function into the code of its callers. The Linux kernel uses mainly inline functions that are also declared as static. A static inline function results in the compiler attempting to incorporate the function's code into all its callers and, if possible, it discards the assembly code of the function.

volatile:

The volatile keyword marks variables that could change without warning. volatile informs the compiler that it needs to reload the marked variable every time it encounters it rather than storing and accessing a copy. Some good examples of variables that should be marked as volatile are ones that deal with interrupts, hardware registers, or variables that are shared between concurrent processes.

__init:

The __init macro tells the compiler that the associate function or variable is used only upon initialization. The compiler places all code marked with __init into a special memory section that is freed after the initialization phase ends: Example (init/main.c):

386: asmlinkage void __init start_kernel(void)

likely() and unlikely():

likely() and unlikely() are macros that Linux kernel developers use to give hints to the compiler and chipset. Modern CPUs have extensive branch-prediction heuristics that attempt to predict incoming commands in order to optimize speed. The likely() and unlikely() macros allow the developer to tell the CPU, through the compiler, that certain sections of code are likely, and thus should be predicted, or unlikely, so they shouldn't be predicted. They are defined in include/linux/compiler.h:

37: #define likely(x) __builtin_expect(!!(x), 1) 38: #define unlikely(x) __builtin_expect(!!(x), 0)

IS_ERR and PTR_ERR:

The IS_ERR macro encodes a negative error number into a pointer, while the PTR_ERR macro retrieves the error number from the pointer. Both macros are defined in include/linux/err.h (line 19 27):

19: static inline long PTR_ERR(const void *ptr) 20: { 21: return (long) ptr; 22: } 23: 24: static inline long IS_ERR(const void *ptr) 25: { 26: return (unsigned long)ptr > (unsigned long)-1000L; 27: }

Did this tutorial help a little? How about buy me a cup of coffee?

Buy me a coffee at ko-fi.com

Please feel free to use the comments form below if you have any questions or need more explanation on anything. I recommend thoroughy testing on a production-like test system first before moving to production.