Internet Windows Android

Configuring and compiling the Linux kernel. Category Archives: Driver Programming Explicitly Defining Finalization and Initialization Functions

Why bother compiling the kernel yourself?
Perhaps the main question that is asked about compiling a kernel is, "Why should I do this?"
Many consider it a pointless waste of time in order to prove themselves to be an intelligent and advanced "Linuxoid". In fact, compiling the kernel is very important. Let's say you bought a new laptop and your webcam doesn't work. Your actions? You look in a search engine and look for a solution to a problem on this matter. Quite often it may turn out that your webcam is running on a newer kernel than yours. If you do not know what version you have, enter uname -r in the terminal, as a result you will get the kernel version (for example, linux-2.6.31-10). Kernel compilation is also widely used to increase performance: the fact is that by default in distributions kernels are compiled "for everyone", which is why it includes a huge number of drivers that you may not need. So if you know the hardware used well, you can disable unnecessary drivers at the configuration stage. It is also possible to enable support for more than 4 Gigabytes of RAM without changing the system's bit depth. So, if you still need to have your own kernel, let's start compiling!

Obtaining the kernel source.
The first thing to do is get the source code for the correct kernel version. You usually need to get the latest stable version. All official versions of the kernel are available at kernel.org. If you already have an X server installed (home computer), then you can go to the site in your favorite browser and download the required version in tar.gz archive (compressed gzip). If you are working in the console (for example, you have not yet installed the X server or are configuring the server), you can use a text browser (for example, elinks). You can also use the standard wget download manager:
wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.33.1.tar.gz
Please note, however, that you must know the exact version number you want.

Unpacking the source archive.
After you have received the archive with the source code, you need to unpack the archive into a folder. This can be done from graphical file managers (dolphin, nautilus, etc.) or via mc. Alternatively, use the traditional tar command:
tar -zxvf path_to_archive
Now you have a folder and source code, navigate to it using the command cd kernel_source_directory(use the ls command to list the directories in a folder).

Kernel configuration.
Once you have navigated to the kernel source directory, you need to perform a "20 minute" kernel configuration. Its purpose is to leave only the necessary drivers and functions. All commands already need to be executed on behalf of the superuser.

make config - console mode of the configurator.

make menuconfig - console mode as a list.

make xconfig - graphical mode.

After making the necessary changes, save the settings and exit the configurator.

Compilation.
The time has come for the final stage of the assembly - compilation. This is done with two commands:
make && make install
The first command will compile all files to machine code, and the second will install the new kernel on your system.
We are waiting from 20 minutes to several hours (depending on the power of the computer). The kernel is installed. To make it appear in the grub (2) list, type (as superuser)
update-grub
Now after rebooting press "Escape" and you will see the new kernel listed. If the kernel does not turn on, then just boot with the old kernel and configure more carefully.

KernelCheck - compiling the kernel without going into the console.
allows you to build the kernel in a fully graphical mode for Debian and distributions based on it... After launching, KernelCheck will offer the latest versions of the kernel and patches, and after your consent, it will download the source code, launch the graphical configurator. The program will compile the kernel into .deb packages and install them. You just have to reboot.

About: "Based on the translation" Linux Device Driver 2nd edition. Translation: Knyazev Alexey [email protected] Date of last change: 03.08.2004 Placement: http://lug.kmv.ru/index.php?page=knz_ldd2

Now let's start programming! This chapter provides an overview of modules and kernel programming.
Here we will build and run a full-fledged module, the structure of which corresponds to any real module driver.
At the same time, we will concentrate on the main positions without taking into account the specifics of real devices.

All parts of the core like functions, variables, headers and macros that are mentioned here will
are detailed at the end of the chapter.

Hello world!

While reading the original material written by Alessndro Rubini & Jonathan Corbet, I found it a somewhat unfortunate example given as Hello world! Therefore, I want to provide the reader with, in my opinion, a better version of the first module. I hope that there will be no problems with compiling and installing it under the 2.4.x kernel. The proposed module and the way it is compiled allows it to be used in kernels, both supporting and not supporting version control. Later you will get acquainted with all the details and terminology, I now open vim and start working!

================================================== // hello_knz.c file #include #include <1>Hello, world \ n "); return 0;); void cleanup_module (void) (printk ("<1>Good bye cruel world \ n ");) MODULE_LICENSE (“ GPL ”); =============================== =================

The following Makefile can be used to compile such a module. Remember to put a tab character in front of the line starting with $ (CC)….

================================================== FLAGS = -c -Wall -D__KERNEL__ -DMODULE PARAM = -I / lib / modules / $ (shell uname -r) / build / include hello_knz.o: hello_knz.c $ (CC) $ (FLAGS) $ (PARAM) - o [email protected] $^ =================================================

Two features are used here, compared to the original Hello world code proposed by Rubini & Corbet. First, the module will have the same version as the kernel. This is achieved by setting the PARAM variable in the compilation script. Secondly, now the module will be licensed under the GPL (using the MODULE_LICENSE () macro). If this is not done, then when installing the module into the kernel, you may see, approximately, the following warning:

# insmod hello_knz.o Warning: loading hello_knz.o will taint the kernel: no license See http://www.tux.org/lkml/#export-tainted for information about tainted modules Module hello_knz loaded, with warnings

Let us now explain the module compilation options (macros will be explained later):

-with- if this option is present, the gcc compiler will stop compiling the file immediately after creating the object file, without trying to create an executable binary.

-Wall- the maximum level for displaying warnings while gcc is running.

-D- definitions of macro symbols. The same as the #define directive in the compiled file. It makes no difference how you define the macros used in this module using #define in the source file or using the -D option for the compiler.

-I- additional search paths for include-files. Note the use of the “uname -r” substitution, which will determine the exact name of the currently used kernel version.

The next section provides another example of a module. It also explains in detail how to install and unload it from the kernel.

The original Hello world!

Now here's the original code for a simple "Hello, World" module offered by Rubini & Corbet. This code can be compiled under kernels 2.0 through 2.4. This example, like the others in the book, is available on the O'Reilly FTP site (see Chapter 1).

// hello.c file #define MODULE #include int init_module (void) (printk ("<1>Hello, world \ n "); return 0;) void cleanup_module (void) (printk ("<1>Goodbye cruel world \ n ");)

Function printk () defined in the Linux kernel and works like a standard library function printf () in the C language. The kernel needs its own, preferably small in size, output function contained directly in the kernel, not in user-level libraries. A module can call a function printk () because after loading the module with the command insmod the module communicates with the kernel and has access to published (exported) kernel functions and variables.

String parameter “<1>”Passed to the printk () function is the priority of the message. The original English sources use the term loglevel, meaning the level of logging of messages. Here, we will use the term priority instead of the original “loglevel”. In this example, we use high priority for the message, which corresponds to the low number. The high priority of the message is set deliberately, because the message with the default priority may not be displayed in the console from which the module was installed. The direction of output of kernel messages with a default priority depends on the version of the running kernel, the version of the daemon klogd, and your configuration. In more detail, working with a function printk () we'll explain in Chapter 4, "Debugging Techniques".

You can test the module using the command insmod to install a module into the kernel and commands rmmod to remove a module from the kernel. Below we will show you how this can be done. The init_module () entry point is executed when a module is installed into the kernel, and cleanup_module () is executed when it is retrieved from the kernel. Remember that only a root user can load and unload modules.

The example module above can only be used with a kernel that was built with the “module version support” flag turned off. Unfortunately, most distributions use versioned kernels (discussed in the section "Version Control in Modules" in Chapter 11, "kmod and Advanced Modularization"). Although older versions of the package modutils allow loading such modules into kernels compiled with version control, now this is not possible. Recall that the modutils package contains a set of programs that include the insmod and rmmod programs.

Task: Determine the version number and contents of the modutils package from your distribution.

When you try to insert such a module into a kernel that supports version control, you may see an error message similar to the following:

# insmod hello.o hello.o: kernel-module version mismatch hello.o was compiled for kernel version 2.4.20 while this kernel is version 2.4.20-9asp.

In the catalog misc-modules For examples from ftp.oreilly.com you will find the original hello.c example program, which is slightly more lines long and can be installed in kernels with or without version control. However, we strongly recommend that you build your own kernel without version control support. At the same time, it is recommended to take the original kernel sources on the site www.kernel.org

If you are new to building kernels, then try reading the article that Alessandro Rubini (one of the authors of the original book) posted at http://www.linux.it/kerneldocs/kconf, which should help you master the process.

Run the following commands in a text console to compile and test the original example module above.

Root # gcc -c hello.c root # insmod ./hello.o Hello, world root # rmmod hello Goodbye cruel world root #

Depending on the mechanism that your system uses for sending message strings, the direction of output of messages sent by the function printk () may differ. In the given example of compiling and testing the module, the messages passed from the printk () function were displayed in the same console, from where the commands to install and run the modules were given. This example was pulled from a text console. If you run the commands insmod and rmmod from under the program xterm then most likely you will not see anything on your terminal. Instead, the message may end up in one of the system logs, for example / var / log / messages. The exact name of the file depends on the distribution. Look at the modification time of the log files. The mechanism used to pass messages from the printk () function is described in the "How Messages Get Logged" section in Chapter 4, "Technique
debug ".

To view module messages in the system log file / val / log / messages, it is convenient to use the system tail utility, which, by default, displays the last 10 lines of the file transferred to it. An interesting option of this utility is the -f option, which starts the utility in the mode of tracking the last lines of the file, i.e. when new lines appear in the file, they will be automatically displayed. To stop the execution of the command in this case, press Ctrl + C. Thus, to view the last ten lines of the system log file, enter the following at the command line:

Root # tail / var / log / messages

As you can see, writing a module is not as difficult as it might seem. The hardest part is understanding how your device works and how to increase the module's performance. Throughout this chapter, we will learn more about writing simple modules, leaving the device specifics for the next chapters.

Differences between kernel modules and applications

An application has one entry point, which starts executing immediately after the launched application is placed in the computer's RAM. This entry point is described in C as the main () function. The termination of the main () function means the termination of the application. The module has several entry points that are executed when installing and removing the module from the kernel, as well as when processing incoming requests from the user. For example, the init_module () entry point is executed when a module is loaded into the kernel. The cleanup_module () function is executed when the module is unloaded. In the future, we will get acquainted with other points of entry into the module, which are executed when making various requests to the module.

The ability to load and unload modules are two pillars of the modularization mechanism. They can be rated in different keys. For a developer, this means, first of all, a reduction in development time, since you can test the driver functions without a lengthy reboot process.

As a programmer, you know that an application can call a function that has not been declared in the application. At the stages of static or dynamic linking, the addresses of such functions from the corresponding libraries are determined. Function printf () one of these callable functions, which is defined in the library libc... A module, on the other hand, is only associated with the kernel and can only call functions that are exported by the kernel. Kernel executable code cannot use external libraries. So, for example, the function printk () which was used in the example hello.c, is an analogue of the well-known function printf () available in user-level applications. Function printk () is located in the kernel and should be as small as possible. Therefore, unlike printf (), it has very limited support for data types, and, for example, does not support floating point numbers at all.

Kernel 2.0 and 2.2 implementations did not support type specifiers L and Z... They were only introduced in the 2.4 kernel.

Figure 2-1 shows the implementation of the mechanism for calling functions that are entry points into a module. Also, this figure shows the mechanism of interaction of an installed or installed module with the kernel.

Rice. 2-1. Linking the module to the kernel

One of the peculiarities of Unix / Linux operating systems is the absence of libraries that can be linked to kernel modules. As you already know, modules, when they are loaded, are linked into the kernel, therefore all functions external to your module must be declared in the kernel header files and be present in the kernel. Sources of modules never should not include regular header files from user-space libraries. In kernel modules, you can only use functions that are actually part of the kernel.

The entire kernel interface is described in the header files located in the directories include / linux and include / asm inside the kernel sources (usually located in /usr/src/linux-x.y.z(x.y.z is your kernel version)). Older distributions (based on libc versions 5 or less) used symbolic links / usr / include / linux and / usr / include / asm to the corresponding directories in the kernel sources. These symbolic links provide the ability to use the kernel interfaces in custom applications as needed.

Although the user-space library interface is now separate from the kernel interface, sometimes it becomes necessary for user processes to use the kernel interfaces. However, many of the links in the kernel headers are specific to the kernel itself and should not be accessible to user applications. Therefore, these ads are protected. #ifdef __KERNEL__ blocks. This is why your driver, like other kernel code, must be compiled with the declared macro symbol __KERNEL__.

The role of the individual kernel headers will be discussed throughout the book as needed.

Developers working on any large software project (such as the kernel) should consider and avoid "namespace pollution"... This problem occurs when there are a large number of functions and global variables whose names are not expressive enough (distinguishable). A programmer who subsequently has to deal with such applications is forced to spend much more time memorizing "reserved" names and coming up with unique names for new elements. Name collisions (ambiguities) can create a wide range of problems, from errors when loading a module, to unstable or unexplained program behavior that can occur to users using a kernel built in a different configuration.

Developers cannot afford to make such mistakes when writing kernel code, because even the smallest module will be linked to the entire kernel. The best solution for avoiding name collisions is, firstly, to declare your program objects as static and, secondly, the use of a unique system-wide prefix for naming global objects. In addition, as a module developer, you can control the scopes of objects in your code, as described later in the "Kernel Linking Table" section.

Most (but not all) versions of the command insmod export all module objects that are not declared as static, by default, i.e. unless specific instructions are defined in the module. Therefore, it makes perfect sense to declare module objects that you are not going to export as static.

Using a unique prefix for local objects within a module can be good practice as it makes debugging easier. While testing your driver, you may need to export additional objects to the kernel. By using a unique prefix to denote names, you don't run the risk of introducing collisions into the kernel namespace. Kernel prefixes use lowercase characters by convention, and we'll stick to that convention.

Another significant difference between the kernel and user processes is the error handling mechanism. The kernel controls the execution of the user process, so an error in the user process leads to a message, harmless to the system: segmentation fault. In this case, a debugger can always be used to track an error in the source code of a custom application. Errors occurring in the kernel are fatal - if not for the entire system, then at least for the current process. In the “Debugging System Errors” section in Chapter 4, “Debugging Techniques,” we'll look at ways to track kernel errors.

User space and kernel space

The module is executed in the so-called kernel space while apps run in. This concept is the foundation of the theory of operating systems.

One of the main purposes of the operating system is to provide the user and user programs with computer resources, most of which are represented by external devices. The operating system must not only provide access to resources, but also control their allocation and use, preventing collisions and unauthorized access. In addition to this, the operating system can create independent operations for programs and protect against unauthorized access to resources. The solution to this non-trivial task is possible only if the processor protects system programs from user applications.

Almost every modern processor is able to provide this separation by implementing different levels of privileges for the executable code (at least two levels are required). For example, I32 processors have four privilege levels from 0 to 3. Moreover, level 0 has the highest privileges. For such processors, there is a class of privileged instructions that can only be executed at privileged levels. Unix systems use two levels of processor privilege. If the processor has more than two privilege levels, the lowest and highest are used. The Unix kernel operates at the highest privilege level, providing control over the user's hardware and processes.

When we talk about kernel space and user process space I mean not only different levels of privileges of the executable code, but also different address spaces.

Unix transfers execution from user process space to kernel space on two occasions. Firstly, when the user application makes a call to the kernel (system call), and, secondly, while servicing hardware interrupts. Kernel code executed on a system call runs in the context of a process, i.e. working in the interests of the calling process from has access to the data of the address space of the process. On the other hand, the code executed when servicing the hardware interrupt is asynchronous with respect to the process, and does not belong to any special process.

The purpose of the modules is to extend the functionality of the kernel. Module code is executed in kernel space. Typically, a module performs both of the tasks noted earlier: some of the module's functions are executed as part of system calls, and some are responsible for managing interrupts.

Parallelization in the kernel

When programming device drivers, in contrast to application programming, the issue of parallelizing executable code is especially acute. Typically, an application runs from start to finish sequentially without worrying about changing its environment. The kernel code should work with the assumption that it can be accessed several times at the same time.

There are many reasons for parallelizing kernel code. There are usually many processes running in Linux, and some of them may try to access your module code at the same time. Many devices can trigger hardware interrupts on the processor. Interrupt handlers are called asynchronously and can be called while your driver is busy executing another request. Some software abstractions (such as kernel timers, explained in Chapter 6, “Flow of Time,”) also run asynchronously. In addition, Linux can run on a symmetric multiprocessor (SMP) system, whereby your driver code can run in parallel on multiple processors at the same time.

For these reasons, Linux kernel code, including driver codes, must be reentrant, i.e. must be able to work with more than one data context at the same time. Data structures should be designed with multiple threads running in parallel. In turn, the kernel code must be able to handle multiple parallel data streams without damaging them. Writing code that can execute in parallel and avoid situations in which a different sequence of execution could lead to undesirable system behavior is time-consuming and possibly tricky. Each driver example in this book has been written with concurrency in mind. If necessary, we will explain the features of the technique of writing such a code.

The most common mistake programmers make is that they assume that concurrency is not a problem because some code segments cannot sleep. Indeed, the Linux kernel is non-paged, with an important exception regarding interrupt handlers, which cannot be received by the processor while critical kernel code is executing. Recently, non-paging has been sufficient to prevent unwanted parallelism in most cases. On SMP systems, however, code unloading is not required due to parallel computation.

If your code assumes that it will not be dumped, then it will not work correctly on SMP systems. Even if you don't have such a system, someone else might have it using your code. It is also possible that the kernel will use paging in the future, so even uniprocessor systems will have to deal with parallelism all over the place. There are already options for implementing such kernels. Thus, a prudent programmer would write kernel code on the assumption that it would run on an SMP system.

Approx. translator: Sorry, but the last two paragraphs are not clear to me. Perhaps this is the result of a mistranslation. Therefore, I am giving the original text.

A common mistake made by driver programmers is to assume that concurrency is not a problem as long as a particular segment of code
does not go to sleep (or "block"). It is true that the Linux kernel is nonpreemptive; with the important exception of
servicing interrupts, it will not take the processor away from kernel code that does not yield willingly. In past times, this nonpreemptive
behavior was enough to prevent unwanted concurrency most of the time. On SMP systems, however, preemption is not required to cause
concurrent execution.

If your code assumes that it will not be preempted, it will not run properly on SMP systems. Even if you do not have such a system,
others who run your code may have one. In the future, it is also possible that the kernel will move to a preemptive mode of operation,
at which point even uniprocessor systems will have to deal with concurrency everywhere (some variants of the kernel already implement
it).

Information about the current process

Although kernel module code is not executed sequentially like applications, most calls to the kernel are made relative to the calling process. The kernel code can identify the process that called it by referring to the global pointer that points to the structure struct task_struct defined for 2.4 kernels in the file included in ... Pointer current indicates the currently running user process. When executing system calls such as open () or close (), there must be a process that caused them. Kernel code, if necessary, can call specific information on the calling process through a pointer current... For examples of using this pointer, see “Controlling Device File Access” in Chapter 5, “Enhanced Char Driver Operations”.

To date, pointer current is not a more global variable as in earlier versions of the kernel. The developers have optimized access to the structure describing the current process by transferring it to the stack page. You can look at the implementation details of current in the file ... The code that you will see there may not seem simple to you. Keep in mind that Linux is an SMP oriented system and a global variable simply won't work when you are dealing with multiple CPUs. The implementation details remain hidden to other kernel subsystems and the device driver can access the pointer current only through the interface .

From a module point of view, current looks like an external link printk ()... The module can use current wherever you need it. For example, the following piece of code prints the identifier (process ID - PID) and the command name of the process that called the module, getting them through the corresponding fields of the structure struct task_struct:

Printk ("The process is \"% s \ "(pid% i) \ n", current-> comm, current-> pid);

The current-> comm field is the filename of the command that spawned the current process.

Compiling and loading modules

The rest of this chapter is devoted to writing a complete, albeit atypical, module. Those. the module does not belong to any of the classes described in the Device and Module Classes section in Chapter 1, Introduction to Device Drivers. The example driver shown in this chapter will be called skull (Simple Kernel Utility for Loading Localities). You can use the scull module as a template for writing your own local code.

We use the term “local code” to emphasize your personal code changes, in the good old Unix tradition (/ usr / local).

However, before we fill in the init_module () and cleanup_module () functions, we will write a Makefile script that make will use to build the module object code.

Before the preprocessor processes the inclusion of any header file, the macro symbol __KERNEL__ must be defined by the #define directive. As mentioned earlier, a kernel-specific context can be defined in the kernel interface files, visible only if the __KERNEL__ symbol is predefined in preprocessing stage.

Another important symbol defined by the #define directive is the MODULE symbol. From must be defined to enable the interface (excluding those drivers that will be compiled with the kernel). The drivers compiled into the kernel will not be described in this book, so the MODULE symbol will be present in all our examples.

If you are building a module for an SMP system, you also need to define the __SMP__ macro before enabling the kernel interfaces. In version 2.2 of the kernel, the choice between a uniprocessor and a multiprocessor system was introduced as a separate item in the kernel configuration. Therefore, including the following lines as the very first lines of your module will result in multiprocessor support.

#include #ifdef CONFIG_SMP # define __SMP__ #endif

Module developers should also define the -O optimization flag for the compiler, because many functions are declared inline in the kernel headers. The gcc compiler does not perform inline expansion on functions until optimizations are enabled. Enabling the extension of inline substitutions with the -g and -O options will allow you to later debug code using inline functions in the debugger. Since the kernel makes extensive use of inline functions, it is very important that they are extended correctly.

Note, however, that any optimization above the -O2 level is risky because the compiler may extend functions that are not described as inline. This can lead to problems. some function code expects to find its standard call stack. Inline expansion is understood as the insertion of the function code at the point of its invocation instead of the corresponding function call instruction. Accordingly, in this case, since there is no function call, then there is no call stack.

You may need to check that you are using the same compiler to compile the modules that was used to build the kernel where the module is supposed to be installed. See the original document for details. Documentation / Changes located in the kernel sources directory. Kernel and compiler developments are generally synchronized between development teams. There may be times when updating one of these elements exposes errors in the other. Some distribution makers ship ultra-new versions of the compiler that do not match the kernel they are using. In this case, they usually provide a separate package (often called kgcc) with a compiler specifically designed for
compiling the kernel.

Finally, in order to prevent annoying errors, we suggest you use the compile option -Wall(all warning - all warnings). You may need to change your usual programming style to meet all of these warnings. When writing kernel code, it is preferable to use the coding style suggested by Linus Torvalds. So, document Documentation / CodingStyle, from the kernel source directory, is interesting enough and recommended to all those interested in kernel-level programming.

It is recommended to place the set of module compilation flags, which we have seen recently, in the variable CFLAGS your Makefile. For the make utility, this is a special variable, the use of which will become clear from the following description.

Apart from the flags in the variable CFLAGS, your Makefile may need a target to concatenate different object files. This goal is only necessary when the module code is split into multiple source files, which is not uncommon in general. Object files are combined with the command ld -r, which is not a linking operation in the conventional sense, despite the use of the linker ( ld). The result of command execution ld -r is another object file that combines the object codes of the linker input files. Option -r means “ relocatable - relocation”, I.e. we move the output file of the command in the address space, since it does not yet have absolute function call addresses.

The following example shows the minimum Makefile required to compile a module with two source files. If your module consists of one source file, then from the given example you need to remove the target containing the command ld -r.

# The path to your kernel sources directory can be changed here, # or you can pass it as a parameter when calling “make” KERNELDIR = / usr / src / linux include $ (KERNELDIR) /. Config CFLAGS = -D__KERNEL__ -DMODULE -I $ (KERNELDIR) / include \ -O -Wall ifdef CONFIG_SMP CFLAGS + = -D__SMP__ -DSMP endif all: skull.o skull.o: skull_init.o skull_clean.o $ (LD) -r $ ^ -o [email protected] clean: rm -f * .o * ~ core

If you are new to the make utility, you might be surprised by the lack of rules for compiling * .c files into object * .o files. The definition of such rules is not necessary, since the make utility, if necessary, converts * .c files to * .o files itself using the default compiler or the compiler specified by the variable $ (CC)... In this case, the contents of the variable $ (CFLAGS) used to specify compilation flags.

The next step after building the module is loading it into the kernel. We have already said that for this we will use the insmod utility, which links all undefined symbols (function calls, etc.) of the module to the symbol table of the running kernel. However, unlike a linker (such as ld), it does not modify the module's disk file, but loads the module object linked to the kernel into RAM. The insmod utility accepts several command line options. Details can be viewed through man insmod... Using these options, you can, for example, assign specific integer and string variables in your module to specific values ​​before linking the module to the kernel. Thus, if a module is properly designed, it can be configured at boot time. This way of configuring the module gives the user more flexibility than configuring at compile time. Download-phase configuration is explained in the “Manual and Automatic Configuration” section later in this chapter.

Some readers may be interested in the details of how the insmod utility works. The insmod implementation is based on several of the system calls defined in kernel / module.c. The sys_create_module () function allocates the required amount of memory in the kernel address space to load a module. This memory is allocated using the vmalloc () function (see the section “vmalloc and Friends” in Chapter 7 “Getting Hold of Memory”). The get_kernel_sysms () system call returns the kernel symbol table that will be used to determine the real addresses of objects when linking. The sys_init_module () function copies the module object code into the kernel address space and calls the module's initialization function.

If you look at the sources of the kernel code, you will find there system call names that start with the sys_ prefix. This prefix is ​​only used for system calls. No other functions should be using it. Keep this in mind when processing kernel sources with grep.

Version dependencies

If you do not know anything more than what was said here, then most likely the modules you create will need to be recompiled for each version of the kernel into which they will be linked. Each module must define a symbol called __module_kernel_version whose value
compared to the version of the current kernel with insmod. This symbol is located in the section .modinfo ELF (Executable and Linking Format) files. This is explained in more detail in Chapter 11, “kmod and Advanced Modularization”. Please note that this method of revision control is only applicable for kernels 2.2 and 2.4. In the 2.0 kernel, this is done in a slightly different way.

The compiler will define this symbol wherever the header file is included ... Therefore, in the above example hello.c, we did not describe this symbol. This also means that if your module consists of many source files, you must include the file into your code only once. An exception is the case of using the definition __NO_VERSION__, which we will meet later.

Below is the definition of the described symbol from the file module.h extracted from the 2.4.25 kernel code.

Static const char __module_kernel_versio / PRE__attribute __ ((section (". Modinfo"))) = "kernel_version =" UTS_RELEASE;

If a module fails to load due to version mismatch, you can try to load this module by passing the key to the parameter string of the insmod utility -f(force). This way of loading a module is not safe and not always successful. It is difficult to explain the reasons for possible failures. It is possible that the module will not be loaded because the symbols are not resolvable when linking. In this case, you will receive a corresponding error message. The reasons for failure may also lie in changes in the work or structure of the kernel. In this case, loading the module can lead to severe runtime errors as well as system panic. The latter should be a good incentive to use a version control system. Version mismatch can be managed more elegantly by using version control in the kernel. We will talk about this in detail in the section “Version Control in Modules” in Chapter 11 “kmod and Advanced Modularization”.

If you want to compile your module for a particular kernel version, you must include the header files from that particular kernel version. In the above example Makefile, the variable was used to determine the directory where these files are located KERNELDIR... This custom compilation is not uncommon when kernel sources are available. Also, it is not uncommon for the presence of different versions of the kernel in the directory tree. All of the module examples in this book use the variable KERNELDIR to indicate the location of the source directory of the version of the kernel into which it is supposed to link the assembled module. You can use a system variable to specify this directory, or pass its location through command line options to make.

When a module is loaded, insmod uses its own search paths for the module's object files, looking at version-dependent directories starting from dot / lib / modules... Although older versions of the utility included the current directory in the search paths, this behavior is now considered unacceptable for security reasons (the same problems as using the system variable PATH). Thus, if you want to load a module from the current directory, you can specify it in the style ./module.o... Specifying the position of the module in this way will work for all versions of the insmod utility.

Sometimes you may come across kernel interfaces that differ between versions 2.0.x and 2.4.x. In this case, you will need to resort to using a macro that determines the current version of the kernel. This macro is located in the header file ... We will point out the cases of differences in interfaces when using those. This can be done either right along the way, or at the end of the section, in a special section devoted to version dependencies. Moving the details into a separate section, in some cases, will allow not to complicate the description according to the 2.4.x kernel version that is profiling for this book.

In the header file linux / version.h the following macros are defined related to the definition of the kernel version.

UTS_RELEASE A macro that expands into a string describing the kernel version of the current
source tree. For example, a macro can expand into this
line: "2.3.48" . LINUX_VERSION_CODE This macro expands into a binary representation of the kernel version, by
one byte for each part of the number. For example, the binary
the representation for version 2.3.48 will be 131888 (decimal
representation for hex 0x020330). Possibly binary
the view will seem more convenient to you than the string one. Notice what is
the view allows you to describe no more than 256 options in each
parts of the room. KERNEL_VERSION (major, minor, release) This macro allows you to build kernel_version_code
of the individual elements that make up the kernel version. For example,
next macro KERNEL_VERSION (2, 3, 48)
expands to 131888. This macro is very useful when
comparing the current version of the kernel with the required one. We will be repeatedly
use this macro definition throughout the book.

Let's give the contents of the file linux / version.h for kernel 2.4.25 (the text of the header file is given in full).

#define UTS_RELEASE "2.4.25" #define LINUX_VERSION_CODE 132121 #define KERNEL_VERSION (a, b, c) (((a)<< 16) + ((b) << 8) + (c))

The version.h header file is included in the module.h file, so you usually don't need to explicitly include version.h in your module code. Alternatively, you can prevent the header file version.h from being included in module.h by declaring a macro __NO_VERSION__... You will use __NO_VERSION__, for example, in the case when you need to enable into several source files, which will subsequently be linked into one module. Announcement __NO_VERSION__ before including the header file module.h prevents
automatic line description __module_kernel_version or its equivalent in the source files. You may need this to address linker complaints when ld -r who will not like the multiple descriptions of symbols in link tables. Usually, if the module code is split into multiple source files including the header file then ad __NO_VERSION__ is done in all but one of these files. At the end of the book, there is an example of a module using __NO_VERSION__.

Most of the dependencies associated with the kernel version can be handled using logic built on preprocessor directives using macros KERNEL_VERSION and LINUX_VERSION_CODE... However, checking version dependencies can greatly complicate the readability of the module code due to motley directives #ifdef... So perhaps the best solution is to put the dependency checker in a separate header file. This is why our example includes the header file sysdep.h used to contain all macros associated with version dependency checks.

The first version dependency we want to present is in the target declaration " make install"our driver compilation script. As you might expect, the installation directory, which changes according to the kernel version used, is selected based on viewing the version.h file. Here is a code snippet from the file Rules.make which is used by all kernel Makefiles.

VERSIONFILE = $ (INCLUDEDIR) /linux/version.h VREION = $ (shell awk -F \ "" / REL / (print $$ 2) "$ (VERSIONFILE)) INSTALLDIR = / lib / modules / $ (VERSION) / misc

Note that we are using the misc directory to install all of our drivers (the INSTALLDIR declaration in the above example Makefile). Starting with kernel 2.4, this directory is the recommended directory for placing custom drivers. In addition, both old and new versions of the modutils package contain a misc directory in their search paths.

Using the definition of INSTALLDIR given above, the install target in the Makefile might look like this:

Install: install -d $ (INSTALLDIR) install -c $ (OBJS) $ (INSTALLDIR)

Platform Dependency

Each computing platform has its own characteristics that must be taken into account by the kernel developers in order to achieve the highest performance.

Kernel developers have much more freedom in choice and decision making than / PCLASS = "western" and application developers. It is this freedom that allows you to optimize your code, squeezing the maximum out of each specific platform.

The module code must be compiled using the same compiler options that were used when compiling the kernel. This applies both to using the same processor register usage patterns and performing the same level of optimization. File Rules.make, located at the root of the kernel source tree, includes the platform-specific definitions that must be included in all compilation Makefiles. All platform-specific compilation scripts are named Makefile. platform and contain the values ​​of the variables for the make utility according to the current kernel configuration.

Another interesting feature of the Makefile is its support for cross-platform or just cross-compilation. This term is used when you need to compile code for a different platform. For example, using the i86 platform, you are going to generate code for the M68000 platform. If you are going to use cross compilation, then you need to replace your compilation tools ( gcc, ld, etc.) with another set of appropriate tools
(for example, m68k-linux-gcc, m68k-linux-ld). The prefix used can be specified by either the $ (CROSS_COMPILE) Makefile variable, a command line parameter to make, or a system environment variable.

The SPARC architecture is a special case that must be handled accordingly in the Makefile. User programs run on the SPARC64 (SPARC V9) platform are binaries, usually designed for the SPARC32 (SPARC V8) platform. Therefore, the default compiler on the SPARC64 platform (gcc) generates the object code for SPARC32. On the other hand, a kernel designed to run on SPARC V9 must contain the object code for SPARC V9, so even so, a cross compiler is required. All GNU / Linux distributions targeting SPARC64 include an appropriate cross compiler, which must be selected in the Makefile of the kernel compilation script.

Although the complete list of version and platform dependencies is a bit more complex than the one described here, it is still sufficient for cross-compilation. For more information, you can look at the Makefile compilation scripts and kernel source files.

Kernel 2.6 features

Time does not stand still. And now we are witnessing the emergence of a new generation of the 2.6 kernel. Unfortunately, the original of this book does not cover the new core, so the translator will take the liberty of adding new knowledge to the translation.

You can use IDEs such as TimeSys ’TimeStorm, which will correctly skeleton and compile your module depending on the required kernel version. If you are going to write all this yourself, then you will need some additional information about the main differences introduced by the new kernel.

One of the features of the 2.6 kernel is the need to use the module_init () and module_exit () macros to explicitly register the names of the initialization and termination functions.

The MODULE_LISENCE () macro, introduced in the 2.4 kernel, is still needed if you don't want to see the corresponding warnings when loading a module. You can select the following license strings for transfer to macro: “GPL”, “GPL v2”, “GPL and additional rights”, “Dual BSD / GPL” (choose between BSD or GPL licenses), “Dual MPL / GPL "(choose between Mozilla or GPL licenses) and
"Proprietary".

More important for the new kernel is a new scheme for compiling modules, which entails not only changes in the code of the module itself, but also in the Makefile of its compilation script.

Thus, the definition of the MODULE macro symbol is no longer required either in the module code or in the Makefile. If necessary, the new compilation scheme will itself define this macro symbol. Also, you do not need to explicitly define __KERNEL__ macros, or newer ones such as KBUILD_BASENAME and KBUILD_MODNAME.

Also, you should not specify the compile-time optimization level (-O2 or others), since your module will be compiled with all that set of flags, including the optimization flags with which all other modules in your kernel are compiled - the make utility will automatically use all the necessary set of flags.

For these reasons, the Makefile for compiling a module for the 2.6 kernel is much easier. So for the hello.c module, the Makefile will look like this:

Obj-m: = hello.o

However, in order to compile the module, you will need write access to the kernel source tree, where temporary files and directories will be created. Therefore, the command for compiling the module to the 2.6 kernel, given from the current directory containing the module's source code, should look like this:

# make -C /usr/src/linux-2.6.1 SUBDIRS = `pwd` modules

So, we have the source of the module hello-2.6.c, to compile in a 2.6 kernel:

//hello-2.6.c #include #include #include MODULE_LICENSE ("GPL"); static int __init my_init (void) (printk ("Hello world \ n"); return 0;); static void __exit my_cleanup (void) (printk ("Good bye \ n");); module_init (my_init); module_exit (my_cleanup);

Accordingly, we have a Makefile:

Obj-m: = hello-2.6.o

We call the make utility to process our Makefile with the following parameters:

# make -C / usr / src / linux-2.6.3 SUBDIRS = `pwd` modules

The normal compilation process will proceed with the following standard output:

Make: Enter directory `/usr/src/linux-2.6.3" *** Warning: Overriding SUBDIRS on the command line can cause *** inconsistencies make: `arch / i386 / kernel / asm-offsets.s" requires updating. CHK include / asm-i386 / asm_offsets.h CC [M] /home/knz/j.kernel/3/hello-2.6.o Building modules, stage 2. /usr/src/linux-2.6.3/scripts/Makefile .modpost: 17: *** Uh-oh, you have stale module entries. You messed with SUBDIRS, /usr/src/linux-2.6.3/scripts/Makefile.modpost:18: do not complain if something goes wrong. MODPOST CC /home/knz/j.kernel/3/hello-2.6.mod.o LD [M] /home/knz/j.kernel/3/hello-2.6.ko make: Exit directory `/ usr / src /linux-2.6.3 "

The end result of the compilation will be a module file hello-2.6.ko that can be installed into the kernel.

Note that in the 2.6 kernel module files are suffixed .ko, not .o as in the 2.4 kernel.

Kernel Symbol Table

We have already talked about how insmod uses the kernel's public symbol table when linking a module to the kernel. This table contains the addresses of the global kernel objects — functions and variables — that are required to implement modular driver variants. The kernel public symbol table can be read in text form from the / proc / ksyms file, provided your kernel supports the / proc filesystem.

In the 2.6 kernel, the / proc / ksyms file has been renamed to / proc / modules.

When a module is loaded, the symbols exported by the module become part of the kernel's symbol table, and you can view them from / proc / ksyms.

New modules can use symbols exported by your module. So, for example, the msdos module relies on the symbols exported by the fat module, and every USB device used in read mode uses the symbols from the usbcore and input modules. This relationship, implemented by sequentially loading modules, is called a module stack.

The module stack is useful when creating complex module projects. This abstraction is useful for separating device driver code into hardware-dependent and hardware-independent parts. For example, the video-for-linux driver set consists of a main module that exports symbols for a low-level driver that takes into account the specifics of the hardware being used. According to your configuration, you load the main video module and the module specific to your hardware. Likewise, support is provided for parallel ports and a wide range of plug-in devices such as USB devices. The parallel port system stack is shown in Fig. 2-2. The arrows indicate the interactions between modules and the kernel API. Interaction can occur both at the level of functions and at the level of data structures controlled by functions.

Figure 2-2. Parallel port module stack

When using stacking modules, it is convenient to use the modprobe utility. The functionality of the modprobe utility is in many ways similar to the insmod utility, but when a module is loaded, it checks its underlying dependencies, and, if necessary, loads the necessary modules until the required module stack is filled. Thus, a single modprobe command can result in multiple calls to the insmod command. You could say that modprobe is a smart wrapper over insmod. You can use modprobe instead of insmod everywhere, except when loading custom modules from the current directory. modprobe only looks at special module locations, and will not be able to satisfy possible dependencies.

Dividing modules into parts helps to reduce development time by simplifying the problem statement. This is similar to the separation between implementation mechanism and control policy discussed in Chapter 1, “An Introduction to Device Drivers.”

Usually a module implements its functionality without needing to export symbols at all. You will need to export symbols if other modules can benefit from it. You may need to include a special directive to prevent the export of non-static symbols, since most implementations of modutils export all of them by default.

Linux kernel headers provide a convenient way to control the visibility of your symbols, thus preventing pollution of the kernel symbol table namespace. The mechanism described in this chapter works in kernels since version 2.1.18. Kernel 2.0 had a completely different control mechanism
the visibility of symbols, which will be described at the end of the chapter.

If your module shouldn't export symbols at all, you can explicitly place the following macro call in the module source file:

EXPORT_NO_SYMBOLS;

This macro call, defined in the linux / module.h file, is expanded into an assembler directive and can be specified anywhere in the module. However, when creating code that is portable to different kernels, it is necessary to place this macro call in the initialization function of the module (init_module), because the version of this macro definition defined by us in our sysdep.h file for older versions of the kernel will only work here.

On the other hand, if you need to export a certain part of the symbols from your module, then you need to use the macro symbol
EXPORT_SYMTAB... This macro must be defined front including the header file module.h. It is generally accepted practice
the definition of this macro symbol through the flag -D in the Makefile.

If the macro character EXPORT_SYMTAB is defined, individual symbols can be exported using a couple of macros:

EXPORT_SYMBOL (name); EXPORT_SYMBOL_NOVERS (name);

Either of these two macros will make the given symbol available outside the module. The difference is that the macro EXPORT_SYMBOL_NOVERS exports a symbol without version information (see chapter 11 “kmod and Advanced Modularization”). For more details
check out the header file , although the above is quite enough for practical use
macros.

Initializing and Completing Modules

As mentioned, the init_module () function registers the functional components of a module in the kernel. After such registration, for the application using the module, the entry points to the module will be available through the interface provided by the kernel.

Modules can register many different components, which, when registered, are the names of the module's functions. A pointer to a data structure containing pointers to functions implementing the proposed functionality is passed to the core registration function.

Chapter 1, “Introduction to Device Drivers,” mentioned a classification of the main types of devices. You can register not only the types of devices mentioned there, but also any others, up to software abstractions, such as, for example, files of the / proc file system, etc. Anything that can work in the kernel through the driver API can be registered as a driver.

If you want to know more about the types of registered drivers using your kernel as an example, you can implement a search for the EXPORT_SYMBOL substring in the kernel sources and find the entry points offered by various drivers. As a rule, registration functions use the prefix in their name register_,
so another possible way to find them is to search for a substring register_ in the / proc / ksyms file using the grep utility. As mentioned, in the 2.6.x kernel the / proc / ksyms file is replaced by / proc / modules.

Error handling in init_module

If an error of any kind occurs during the initialization of the module, then you must undo the initialization already done before stopping the loading of the module. An error can occur, for example, due to insufficient memory in the system when allocating data structures. Unfortunately, this can happen, and good code should be able to handle these situations.

Anything that was registered or allocated before the error occurred in the init_module () initialization function must be canceled or released on its own, because the Linux kernel does not track initialization errors and does not undo the borrowing and provision of resources already executed by the module code. If you did not roll back, or could not roll back the registration, then the kernel will remain in an unstable state, and when the module is reloaded
you will not be able to re-register already registered items, and you will not be able to cancel a previously made registration, because in a new instance of the init_module () function, you will not have the correct value for the addresses of the registered functions. Restoring the system to its previous state will require various complex tricks, and more often this is done by a simple reboot of the system.

The implementation of restoring the previous state of the system in the event of module initialization errors is best implemented using the goto statement. Usually, this operator is treated extremely negatively, and even with hatred, but it is in this situation that he turns out to be very useful. Therefore, in the kernel, the goto statement is often used to handle module initialization errors.

The following simple code demonstrates this way of handling errors using the fictitious registration and cancellation functions as an example.

Int init_module (void) (int err; / * registration takes a pointer and a name * / err = register_this (ptr1, "skull"); if (err) goto fail_this; err = register_that (ptr2, "skull"); if (err) goto fail_that; err = register_those (ptr3, "skull"); if (err) goto fail_those; return 0; / * success * / fail_those: unregister_that (ptr2, "skull"); fail_that: unregister_this (ptr1, " skull "); fail_this: return err; / * propagate the error * /)

This example attempts to register three module components. The goto statement is used when a registration error occurs and causes the registered components to be unregistered before the module stops loading.

Another example of using the goto statement for easy-to-read code is the trick of “remembering” the successful registration operations of a module and calling cleanup_module () and passing this information when an error occurs. The cleanup_module () function is designed to rollback the executed initialization operations and is automatically called when the module is unloaded. The value returned by the init_module () function must
represent the module initialization error code. In the Linux kernel, the error code is a negative number from many definitions made in the header file. ... Include this header file in your module in order to use the symbolic mnemonics of reserved error codes such as -ENODEV, -ENOMEM, etc. It is considered good programming style to use such mnemonics. However, it should be noted that some versions of the modutils utilities do not correctly handle the returned error codes and display the “Device busy” message.
in response to a whole bunch of completely different errors returned by init_modules (). In the latest versions of the package, this
the annoying bug has been fixed.

The cleanup_module () function code for the above case could be, for example:

Void cleanup_module (void) (unregister_those (ptr3, "skull"); unregister_that (ptr2, "skull"); unregister_this (ptr1, "skull"); return;)

If your initialization and termination code is more complex than the one described here, then using the goto statement can lead to hard-to-read program code, because the termination code must be repeated in the init_module () function using multiple labels for goto jumps. For this reason, a more tricky technique is used using the call to the cleanup_module () function in the init_module () function, passing information about the amount of successful initialization when a module loading error occurs.

Below is an example of how init_module () and cleanup_module () are written in this way. This example uses globally defined pointers to provide information about the amount of successful initialization.

Struct something * item1; struct somethingelse * item2; int stuff_ok; void cleanup_module (void) (if (item1) release_thing (item1); if (item2) release_thing2 (item2); if (stuff_ok) unregister_stuff (); return;) int init_module (void) (int err = -ENOMEM; item1 = allocate_thing (arguments); item2 = allocate_thing2 (arguments2); if (! item2 ||! item2) goto fail; err = register_stuff (item1, item2); if (! err) stuff_ok = 1; else goto fail; return 0; / * success * / fail: cleanup_module (); return err;)

Depending on the complexity of the initialization operations of your module, you can use one of the methods described here to control module initialization errors.

Module usage counter

The system contains a usage counter for each module in order to determine if the module can be safely unloaded. The system needs this information, because the module cannot be unloaded if it is busy with someone or something - you cannot remove the file system driver if this file system is mounted, or you cannot unload the character device module if some process uses this device. Otherwise,
this can lead to system crash - segmentation fault or kernel panic.

In modern kernels, the system can provide you with an automatic counter for module usage using a mechanism that we will look at in the next chapter. Regardless of the kernel version, you can use manual control of this counter. So, the code that is supposed to be used in older versions of the kernel must use the module usage accounting model built on the following three macros:

MOD_INC_USE_COUNT Increases the usage counter of the current module MOD_DEC_USE_COUNT Decreases the usage counter of the current module MOD_IN_USE Returns true if the usage counter for this module is zero

These macros are defined in and they manipulate a special internal data structure that is not desirable to directly access. The fact is that the internal structure and way of managing this data can change from version to version, while the external interface for using these macros remains unchanged.

Note that you do not need to check MOD_IN_USE in the code for the cleanup_module () function, because this check is done automatically before calling cleanup_module () in the sys_delete_module () system call, which is defined in kernel / module.c.

Correctly managing the module usage counter is critical to system stability. Remember that the kernel may decide to unload an unused module automatically at any time. A common mistake in the programming of modules is the mismanagement of this counter. For example, in response to a request, the module code performs some actions and, when processing completes, increments the module usage counter. Those. Such a programmer assumes that this counter is intended to collect statistics on module usage, while in fact, it is, in fact, the counter of the module's current occupancy, i.e. keeps track of the number of processes currently using the module code. Thus, when processing a request to a module, you must call MOD_INC_USE_COUNT before taking any action, and MOD_DEC_USE_COUNT after completing them.

There are situations in which, for obvious reasons, you will not be able to unload the module if you lose control of the counter of its use. This situation is often encountered during the development phase of a module. For example, a process can interrupt when it tries to dereference a NULL pointer, and you cannot unload such a module until you reset its usage counter to zero. One of the possible solutions to such a problem at the stage of debugging a module is to completely abandon the management of the module usage counter by overriding MOD_INC_USE_COUNT and MOD_DEC_USE_COUNT into empty code. Another solution is to make an ioctl () call to force the module usage counter to zero. We'll cover this in the “Using the ioctl Argument” section in Chapter 5, “Enhanced Char Driver Operations”. Of course, in a ready-to-use driver, such fraudulent manipulations with the counter should be excluded, however, at the debugging stage, they save the developer's time and are quite acceptable.

You can find the current value of the system usage counter for each module in the third field of each entry in the / proc / modules file. This file contains information about the currently loaded modules - one line for each module. The first field of the line contains the name of the module, the second field - the size occupied by the module in memory, and the third field - the current value of the usage counter. This information, in formatted form,
can be obtained by calling the lsmod utility. Below is an example / proc / modules file:

Parport_pc 7604 1 (autoclean) lp 4800 0 (unused) parport 8084 1 lockd 33256 1 (autoclean) sunrpc 56612 1 (autoclean) ds 6252 1 i82365 22304 1 pcmcia_core 41280 0

Here we see several modules loaded into the system. In the flags field (the last field of the line), the module dependency stack is displayed in square brackets. Among other things, you may notice that the parallel port modules communicate through the module stack, as shown in Fig. 2-2. The (autoclean) flag marks modules managed by kmod or kerneld. This will be covered in Chapter 11, “kmod and Advanced Modularization”). The (unused) flag means that the module is not currently being used. In the 2.0 kernel, the size field displayed information not in bytes, but in pages, the size of which for most platforms is 4kBt.

Unloading a module

To unload a module, use the rmmod utility. Unloading a module is a simpler task than loading it, which dynamically links it to the kernel. When a module is unloaded, the delete_module () system call is executed, which either calls the cleanup_module () function of the unloaded module if its usage counter is zero, or terminates with an error.

As already mentioned, in the cleanup_module () function, the initialization operations performed when the module was loaded are rolled back by the cleanup_module () function. Also, the exported symbols of the module are automatically deleted.

Explicitly Defining Finalization and Initialization Functions

As already mentioned, when loading a module, the kernel calls the init_module () function, and when unloading, it calls cleanup_module (). However, in modern versions of the kernel, these functions often have different names. Starting with kernel 2.3.23, it became possible to explicitly define a name for the module load and unload function. It is now recommended programming style to explicitly name these functions.

Let's give an example. If you want to declare the my_init () function as the initialization function of your module, and the my_cleanup () function as the final one, instead of init_module () and cleanup_module (), respectively, then you will need to add the following two macros with the module text (they are usually inserted at the end
module source file):

Module_init (my_init); module_exit (my_cleanup);

Note that in order to use these macros you will need to include the header file in your module. .

The convenience of using this style is that each module initialization and completion function in the kernel can have its own unique name, which greatly helps in debugging. Moreover, the use of these functions simplifies debugging, regardless of whether you are implementing your driver code as a module, or are going to embed it directly into the kernel. Of course, you don't need to use the macros module_init and module_exit if your initialization and termination functions have reserved names, i.e. init_module () and cleanup_module () respectively.

If you familiarize yourself with the sources for kernels 2.2 or later, you may see a slightly different form of description for the initialization and completion function. For example:

Static int __init my_init (void) (....) static void __exit my_cleanup (void) (....)

Attribute usage __init will lead to the fact that after the completion of initialization, the initialization function will be unloaded from memory. However, this only works for built-in kernel drivers, and will be ignored for modules. Also, for drivers built into the kernel, the attribute __exit will cause the entire function marked with this attribute to be ignored. For modules, this flag will also be ignored.

Using attributes __init(and __initdata for describing data) can reduce the amount of memory used by the kernel. Flagging __init the initialization function of the module will do no benefit or harm. Controlling this way of initialization has not yet been implemented for modules, although it may be done in the future.

Summarizing

So, as a result of the presented material, we can present the following version of the "Hello world" module:

Module source file code ============================================= #include #include #include static int __init my_init_module (void) (EXPORT_NO_SYMBOLS; printk ("<1>Hello world \ n "); return 0;); static void __exit my_cleanup_module (void) (printk ("<1>Good bye \ n ");); module_init (my_init_module); module_exit (my_cleanup_module); MODULE_LICENSE (" GPL "); ======================== ====================== Makefile to compile the module ======================== ===================== CFLAGS = -Wall -D__KERNEL__ -DMODULE -I / lib / modules / $ (shell uname -r) / build / include hello.o: ===============================================================================

Note that when writing the Makefile, we used the convention that GNU make can independently determine how to generate an object file based on the CFLAGS variable and the compiler available on the system.

Resource usage

A module cannot complete its task without using system resources such as memory, I / O ports, I / O memory, interrupt lines, and DMA channels.

As a programmer, you should already be familiar with heap management. The heap management in the kernel is not fundamentally different. Your program can get memory using the function kmalloc () and free her with kfree ()... These functions are very similar to the familiar malloc () and free (), except that an additional priority argument is passed to kmalloc (). Usually the priority is GFP_KERNEL or GFP_USER. GFP is an acronym for “get free page” - take a free page. Kernel heap management is detailed in Chapter 7, “Getting Hold of Memory”.

A novice driver developer may be surprised at the need to explicitly allocate I / O ports, I / O memory, and interrupt lines. Only then can the kernel module get easy access to these resources. Although system memory can be allocated from anywhere, I / O memory, ports, and interrupt lines play a special role and are allocated differently. For example, a driver needs to allocate specific ports, not
everything, but those that he needs to control the device. But the driver cannot use these resources until it makes sure that they are not being used by someone else.

The area of ​​memory that belongs to a peripheral device is commonly referred to as I / O memory, in order to distinguish it from system RAM (RAM), simply referred to as memory.

I / O ports and memory

A typical driver's job mostly consists of reading and writing ports and I / O memory. Ports and I / O memory are collectively referred to as the I / O region (or area).

Unfortunately, not on every bus architecture it is possible to clearly define the I / O region belonging to each device, and it is possible that the driver will have to guess the location of the region belonging to it, or even try read / write operations of possible address spaces. This problem is especially
refers to the ISA bus, which is still used to install simple devices in a personal computer and is very popular in the industrial world in the implementation of PC / 104 (see the section “PC / 104 and PC / 104 +” in Chapter 15 “Overview of peripheral buses” ).

Whichever bus is used to connect a hardware device, the device driver must be guaranteed exclusive access to its I / O region to prevent collisions between drivers. If a module, referring to its device, writes to a device that does not belong to it, then this can lead to fatal consequences.

The Linux developers have implemented a request / release mechanism for I / O regions mainly to prevent collisions between different devices. This mechanism has long been used for I / O ports and has recently been generalized to a resource management mechanism in general. Note that this mechanism is a software abstraction and does not extend to hardware capabilities. For example, unauthorized access to I / O ports at the hardware level does not cause any error similar to a “segmentation fault”, since the hardware does not allocate and authorize its resources.

Information about registered resources is available in text form in the / proc / ioports and / proc / iomem files. This information has been provided in Linux since kernel 2.3. Recall that this book focuses primarily on the 2.4 kernel, and compatibility notes will be provided at the end of the chapter.

Ports

Below is the typical content of the / proc / ioports file:

0000-001f: dma1 0020-003f: pic1 0040-005f: timer 0060-006f: keyboard 0080-008f: dma page reg 00a0-00bf: pic2 00c0-00df: dma2 00f0-00ff: fpu 0170-0177: ide1 01f0-01f7 : ide0 02f8-02ff: serial (set) 0300-031f: NE2000 0376-0376: ide1 03c0-03df: vga + 03f6-03f6: ide0 03f8-03ff: serial (set) 1000-103f: Intel Corporation 82371AB PIIX4 ACPI 1000-1003 : acpi 1004-1005: acpi 1008-100b: acpi 100c-100f: acpi 1100-110f: Intel Corporation 82371AB PIIX4 IDE 1300-131f: pcnet_cs 1400-141f: Intel Corporation 82371AB PIIX4 ACPI 1800-18ff: PCI CardBus # 02 1c00- 1cff: PCI CardBus # 04 5800-581f: Intel Corporation 82371AB PIIX4 USB d000-dfff: PCI Bus # 01 d000-d0ff: ATI Technologies Inc 3D Rage LT Pro AGP-133

Each line of this file displays in hexadecimal the range of ports associated with a driver or device owner. In earlier versions of the kernel, the file has the same format, except that the ports hierarchy was not displayed.

The file can be used to avoid port collisions when adding a new device to the system. This is especially convenient when manually configuring the installed equipment by switching jumpers (jampers). In this case, the user can easily view the list of used ports and select a free range for the installed device. And although most modern devices do not use manual jumpers at all, they are nevertheless still used in the manufacture of small-scale components.

More importantly, there is a programmatically accessible data structure associated with the / proc / ioports file. Therefore, when the device driver initializes, it can find out the occupied range of I / O ports. This means that if it is necessary to scan ports in search of a new device, the driver is able to avoid the situation of writing to ports occupied by foreign devices.

Scanning the ISA bus is known to be a risky task. Therefore, some drivers distributed with the official Linux kernel avoid this scan when loading the module. Thus, they avoid the risk of damaging the running system by writing to ports used by other equipment. Fortunately, modern bus architectures are immune to these problems.

The programming interface used to access the I / O registers consists of the following three functions:

Int check_region (unsigned long start, unsigned long len); struct resource * request_region (unsigned long start, unsigned long len, char * name); void release_region (unsigned long start, unsigned long len);

Function check_region () can be called to check if the specified port range is busy. It returns a negative error code (such as -EBUSY or -EINVAL) if the answer is negative.

Function request_region () performs allocation of the specified range of addresses, returning a non-null pointer on success. The driver does not need to store or use the returned pointer. All you need to do is check it for NULL. Code that should only work with kernel 2.4 (or higher) does not need to call check_region () at all. There is no doubt the advantage of this distribution method, since
it is not known what might happen between the calls to check_region () and request_region (). If you want to maintain compatibility with older versions of the kernel, then calling check_region () before request_region () is necessary.

Function release_region () should be called when the driver releases previously used ports.

The actual pointer value returned by request_region () is only used by the resource allocator running in the kernel.

These three functions are actually macros defined in .

The following is an example of using the call sequence used to register ports. The example is taken from the skull driver code. (The code for the skull_probe_hw () function is not shown here, as it contains hardware-dependent code.)

#include #include static int skull_detect (unsigned int port, unsigned int range) (int err; if ((err = check_region (port, range))< 0) return err; /* busy */ if (skull_probe_hw(port,range) != 0) return -ENODEV; /* not found */ request_region(port,range,"skull"); /* "Can"t fail" */ return 0; }

This example first checks the availability of the required port range. If the ports are not available, then it is not possible to access the hardware.
The actual port locations of the device can be verified by scanning. The request_region () function should not, in this example,
will end in failure. The kernel cannot load more than one module at a time, so there are no port collisions.
must.

Any I / O ports allocated by the driver must subsequently be released. Our skull driver does this in the cleanup_module () function:

Static void skull_release (unsigned int port, unsigned int range) (release_region (port, range);)

The resource request / release mechanism is similar to the module registration / deregistration mechanism and is perfectly implemented based on the goto statement described above.

Memory

I / O memory information is available through the / proc / iomem file. Below is a typical example of such a file for a personal computer:

00000000-0009fbff: System RAM 0009fc00-0009ffff: reserved 000a0000-000bffff: Video RAM area 000c0000-000c7fff: Video ROM 000f0000-000fffff: System ROM 00100000-03feffff: System RAM 00100000-0022c557: Kernel code 0022c558-0024455f: Kernel data 20000000 2fffffff: Intel Corporation 440BX / ZX - 82443BX / ZX Host bridge 68000000-68000fff: Texas Instruments PCI1225 68001000-68001fff: Texas Instruments PCI1225 (# 2) e0000000-e3ffffff: PCI Bus # 01 e4000000-e7ffffff: PCI Bus # 01 e4000000-eff : ATI Technologies Inc 3D Rage LT Pro AGP-133 e6000000-e6000fff: ATI Technologies Inc 3D Rage LT Pro AGP-133 fffc0000-ffffffff: reserved

The values ​​for the address ranges are shown in hexadecimal notation. For each address range, its owner is shown.

Registering I / O memory access is similar to registering I / O ports and is built on the same mechanism in the kernel.

To obtain and release the required range of I / O memory addresses, the driver must use the following calls:

Int check_mem_region (unsigned long start, unsigned long len); int request_mem_region (unsigned long start, unsigned long len, char * name); int release_mem_region (unsigned long start, unsigned long len);

Usually, the driver knows the range of I / O memory addresses, so the allocation code for this resource can be reduced compared to the example for allocating the port range:

If (check_mem_region (mem_addr, mem_size)) (printk ("drivername: memory already in use \ n"); return -EBUSY;) request_mem_region (mem_addr, mem_size, "drivername");

Resource Allocation in Linux 2.4

The current resource allocation mechanism was introduced in the Linux 2.3.11 kernel and provides flexible access to manage system resources. This section briefly describes this mechanism. However, basic resource allocation functions (such as request_region (), etc.) are still implemented as macros and are used for backward compatibility with earlier versions of the kernel. In most cases, you don't need to know anything about the actual distribution mechanism, but it can be interesting when building more complex drivers.

The resource management system implemented in Linux can manage arbitrary resources in a uniform hierarchical manner. Global system resources (for example, I / O ports) can be subdivided into subsets - for example, those associated with a slot on the hardware bus. Certain drivers can also subdivide the captured resources based on their logical structure, if desired.

The range of allocated resources is described through the struct resource structure, which is declared in the header file :

Struct resource (const char * name; unsigned long start, end; unsigned long flags; struct resource * parent, * sibling, * child;);

The global (root) resource range is created at boot time. For example, a resource structure describing I / O ports is created as follows:

Struct resource ioport_resource = ("PCI IO", 0x0000, IO_SPACE_LIMIT, IORESOURCE_IO);

It describes a resource named PCI IO that covers the address range from zero to IO_SPACE_LIMIT. The value of this variable depends on the platform used and can be 0xFFFF (16-bit address space, for x86, IA-64, Alpha, M68k and MIPS architectures), 0xFFFFFFFF (32-bit space, for SPARC, PPC, SH) or 0xFFFFFFFFFFFFFFFF (64-bit, SPARC64).

Subranges of this resource can be created by calling allocate_resource (). For example, during the initialization of the PCI bus, a new resource is created for the region of addresses of this bus, which is assigned to a physical device. When the PCI bus manager kernel code processes the port and memory assignments, it creates a new resource for those regions only and allocates them using ioport_resource () or iomem_resource () calls.

The driver can then request a subset of a resource (usually part of a global resource) and mark it as busy. The resource is captured by calling request_region (), which returns either a pointer to a new struct resource that describes the requested resource, or NULL in case of an error. This structure is part of the global resource tree. As already mentioned, after obtaining the resource, the driver does not need the value of this pointer.

The interested reader may enjoy viewing the details of this resource management scheme in the kernel / resource.c file located in the kernel sources directory. However, for most developers, the already stated knowledge will be enough.

The layering mechanism of resource allocation brings double benefits. On the one hand, it provides a visual representation of the kernel data structures. Let's look at the example of the / proc / ioports file again:

E800-e8ff: Adaptec AHA-2940U2 / W / 7890 e800-e8be: aic7xxx

The e800-e8ff range is allocated to an Adaptec adapter that has identified itself as a PCI bus driver. Most of this range was requested by the aic7xxx driver.

Another advantage of such resource management is the division of the address space into sub-bands that reflect the actual interconnection of the equipment. The resource manager is unable to allocate overlapping address subranges, which can prevent the installation of a malfunctioning driver.

Automatic and manual configuration

Some of the parameters required by a driver may vary from system to system. For example, the driver needs to know about valid I / O addresses and memory ranges. This is not a problem for well-organized bus interfaces. However, sometimes you need to pass parameters to the driver to help it find its own device, or to enable / disable some of its functions.

These parameters, which affect the operation of the driver, are device dependent. For example, this could be the version number of an installed device. Of course, this information is necessary for the driver to work correctly with the device. The definition of such parameters (driver configuration) is sufficient
a tricky task performed when the driver is initialized.

Usually, there are two ways to get the correct values ​​for this parameter - either the user defines them explicitly, or the driver determines them independently, based on polling the equipment. Although auto-detecting a device is undoubtedly the best solution for configuring a driver,
custom configuration is much easier to implement. The driver developer should implement driver autoconfiguration wherever possible, but at the same time, he should provide the user with a manual configuration mechanism. Of course, manual configuration should take precedence over autoconfiguration. At the initial stages of development, as a rule, only manual transfer of parameters to the driver is implemented. Autoconfiguration is added later if possible.

Many drivers, among their configuration parameters, have parameters that control the driver's operations. For example, Integrated Device Electronics (IDE) interface drivers allow the user to control DMA operations. Thus, if your driver does a good job of auto-detecting hardware, you might want to give the user control over the functionality of the driver.

Parameter values ​​can be passed during module loading with the insmod or modprobe commands. Recently, it has become possible to read the value of parameters from a configuration file (usually /etc/modules.conf). Integer and string values ​​can be passed as parameters. Thus, if you need to pass an integer value for the skull_ival parameter and a string value for the skull_sval parameter, you can pass them during module loading with additional parameters of the insmod command:

Insmod skull skull_ival = 666 skull_sval = "the beast"

However, before insmod can change the values ​​of module parameters, the module must make those parameters available. Parameters are declared using the MODULE_PARM macro, which is defined in the module.h header file. The MODULE_PARM macro takes two parameters: a variable name and a string that defines its type. This macro definition should be placed outside of any functions and is usually located at the beginning of the file after the definition of variables. So, the two parameters mentioned above can be declared as follows:

Int skull_ival = 0; char * skull_sval; MODULE_PARM (skull_ival, "i"); MODULE_PARM (skull_sval, "s");

Currently, five types of module parameters are supported:

  • b - one-byte value;
  • h - (short) two-byte value;
  • i - whole;
  • l - long integer;
  • s - string (char *);

In the case of string parameters, a pointer (char *) must be declared in the module. The insmod command allocates memory for the passed string and initializes it with the required value. Using the macro MODULE_PARM, you can initialize parameter arrays. In this case, the integer preceding the type letter determines the length of the array. When specifying two integers separated by a dash, they define the minimum and maximum number of values ​​to be transmitted. For a more detailed understanding of the operation of this macro, refer to the header file .

For example, suppose the parameter array must be initialized with at least two and at least four integer values. Then it can be described as follows:

Int skull_array; MODULE_PARM (skull_array, "2-4i");

In addition, the programmer's toolkit contains the MODULE_PARM_DESC macro definition, which allows you to place comments on the transferred module parameters. These comments are saved in the module object file and can be viewed using, for example, the objdump utility, or using automated system administration tools. Here is an example of using this macro definition:

Int base_port = 0x300; MODULE_PARM (base_port, "i"); MODULE_PARM_DESC (base_port, "The base I / O port (default 0x300)");

It is desirable that all parameters of the module have default values. Changing these values ​​with insmod should only be required if necessary. The module can check the explicit setting of parameters by checking their current values ​​with the default values. Subsequently, you can implement an auto-configuration mechanism based on the following diagram. If the parameter values ​​have default values, then autoconfiguration is performed. Otherwise, the current values ​​are used. For this scheme to work, the default values ​​must not match any of the possible real-world system configurations. Then it can be assumed that such values ​​cannot be set by the user in manual configuration.

The following example shows how the skull driver autodetects the port address space on a device. In the given example, autodetection uses multiple device browsing, while manual configuration restricts the driver to one device. You have already seen the skull_detect function earlier in the section describing the I / O ports. The implementation of the skull_init_board () function is not shown, as it
conducts hardware-dependent initialization.

/ * * port ranges: the device can reside between * 0x280 and 0x300, in steps of 0x10. It uses 0x10 ports. * / #define SKULL_PORT_FLOOR 0x280 #define SKULL_PORT_CEIL 0x300 #define SKULL_PORT_RANGE 0x010 / * * the following function performs autodetection, unless a specific * value was assigned by insmod to "skull_port_base" * / static int skull_port_base = 0; / * 0 forces autodetection * / MODULE_PARM (skull_port_base, "i"); MODULE_PARM_DESC (skull_port_base, "Base I / O port for skull"); static int skull_find_hw (void) / * returns the # of devices * / (/ * base is either the load-time value or the first trial * / int base = skull_port_base? skull_port_base: SKULL_PORT_FLOOR; int result = 0; / * loop one time if value assigned; try them all if autodetecting * / do (if (skull_detect (base, SKULL_PORT_RANGE) == 0) (skull_init_board (base); result ++;) base + = SKULL_PORT_RANGE; / * prepare for next trial * /) while (skull_port_base == 0 && base< SKULL_PORT_CEIL); return result; }

If the configuration variables are used only inside the driver (that is, they are not published in the kernel symbol table), then the programmer can slightly simplify the user's life by not using prefixes in the variable names (in our case, the skull_ prefix). For the user, these prefixes mean little, and their absence makes it easier to type commands from the keyboard.

For the sake of completeness, we will provide a description of three more macros that allow you to place some comments in the object file.

MODULE_AUTHOR (name) Places a string with the name of the author in the object file. MODULE_DESCRIPTION (desc) Places a general description line for a module in an object file. MODULE_SUPPORTED_DEVICE (dev) Places a string describing the device supported by the module. V
Linux: The Complete Guide Kolisnichenko Denis Nikolaevich

28.2. Compiling a module

28.2. Compiling a module

We will compile the module.c file. This requires the gcc compiler, headers and kernel sources to be installed. If you have read the book before this chapter, then you should already have the packages installed:

1. cpp - cpp preprocessor;

2. binutils - a set of various utilities (as, gprof, ld);

3.glibc-kerheaders - kernel headers;

4. glibc-devel - auxiliary files for developing applications using the standard C library;

5.gcc is the gcc compiler.

It remains to install the kernel-source package - the sources of the Linux kernel. In addition, you need to make sure that your kernel supports dynamically loadable modules (section 20.3.2.3). If option Enable loadable module support is disabled, you need to enable it, save the kernel config file and recompile the kernel.

The gcc compiler needs to be called with many options, so to make our work easier, we will write a makefile (section 21.2):

Listing 28.5. Makefile to build the module

PATH = / usr / include /usr/src/linux-2.4/include

MODFLAGS: = -O3 -Wall -DLINUX -D__KERNEL__ -I $ (PATH)

module.o: module.c

$ (CC) $ (MODFLAGS) -c module.c

Compiler options mean the following:

O3: the third level of optimization will be used (what this is, you will find out in the help system gcc: man gcc);

Wall: turn on all warnings;

DLINUX: generating code for Linux;

I $ (PATH): define the search path for header files. By default, the compiler looks for header files in the / usr / include directory, but it may not be there. For example, for the ALT Linux distribution (kernel 2.4.21), the header files are located in the /usr/include/linux-2.4.21rel-std-up directory.

Place the makefile in the same directory as module.c and run make. After completing it, you will receive a file module.o, which will be located in the same directory.

# insmod module.o

You will see the message My module: Starting ... The same message will be written to the log file / var / log / messages.

From the C ++ book by Hill Murray

1.1.2 Compilation Where did the cout output stream and the code for the "" output operation come from? To get executable code, a program written in C ++ must be compiled. At its core, the compilation process is the same as for C, and most of the input

From Fedora 8 User's Guide the author

3.4.3. Compilation As a rule, the source codes of programs are distributed in the form of an archive with the "double extension" -.tar.gz. It is customary to unpack the source code into the / usr / src directory. Therefore, to unpack the archive, you need to run the following commands: sucd / usr / srcgunzip archive.tar.gztar xvf

From the Linux user book the author Kostromin Viktor Alekseevich

From The 200 Best Linux Software the author Yaremchuk Sergey Akimovich

17.5.6. Compiling modules If you configured some drivers as separate modules (you chose the "m" option in the configuration when answering some questions), then you must also run the make modules command, and then also the make modules_install command. In the Documentation / modules.txt file you can

From the book The C # 2005 Programming Language and the .NET 2.0 Platform. author Troelsen Andrew

Compiling Programs Even after packages appeared, which were already compiled programs, compilation remained for a long time and for some it remains the primary means of installation. Note The first precompiled kits appeared in

From the book Asterisk ™: The Future of Telephony, Second Edition the author Meggelen Jim Wan

Conditional Compilation Another package of preprocessor directives (#if, #elif, #else, #endif) allows you to conditionally compile a block of code based on predefined symbols. The classic use case for these directives is block identification

From the book Linux Networking author Smith Roderick W.

From the book The C Programming Language for the Personal Computer author Bochkov S.O.

Compiling libpri The libpri libraries do not use autoconf to set up the build environment or the build component selector as they are not needed; thus, installation is simplified. libpri is used by various hardware manufacturers

From the book Linux: The Complete Guide the author Kolisnichenko Denis Nikolaevich

Compiling Asterisk After compiling and installing the zaptel and libpri packages (if needed), you can move on to installing Asterisk. This section covers a standard installation and presents some alternative make arguments that can

From the book Linux Programming by Example the author Robbins Arnold

Compiling the Kernel After you have configured the kernel with make xconfig or the other command at the beginning of this chapter, you must compile the kernel and install its modules. To do this, run the following commands: # make dep # make bzImage # make modules # make

From the book C Language - A Beginner's Guide by Prata Stephen

Conditional Compilation This section describes the directives that control conditional compilation. These directives allow you to exclude any parts of the source file from the compilation process by checking the conditions (constant

From the book Linux through the eyes of a hacker the author Flenov Mikhail Evgenievich

20.5. Compiling the Kernel 20.5.1. Why update the kernel? Linux is growing faster than any other operating system. New versions of the kernel that implement new functions appear regularly. For example, the Fedora Core 4 distribution kit was barely released on the 2.6.11 kernel, and www.kernel.org already has a stable

From the book UNIX Operating System the author Robachevsky Andrey M.

15.2. Compiling for Debugging To use the source debugger, the executable being debugged must be compiled with the -g compiler option. This option forces the compiler to inject additional debug identifiers into the object code; that is

From the author's book

Why compile? BASIC readers may wonder why there are so many steps to complete a program. It seems that this way of compiling takes more time (and in some cases it may actually be). But since in

From the author's book

3.8.3. Compiling the kernel When installing from an RPM package, we get a modular kernel, in which device drivers can be either compiled into one piece with the kernel, or loaded separately. Such a kernel is slower in operation, but allows you to update drivers with a simple replacement

From the author's book

Compilation The procedure for creating most applications is general and is shown in Fig. 2.2. Rice. 2.2. The compilation scheme of the program The first phase is the compilation stage, when the source files of the program, including the header files, are processed by the compiler.