From 4257b6ddcb82818ad1ac8a7ee9a4ed2563e10901 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sun, 1 Aug 2021 15:03:25 +0800 Subject: [PATCH] Tidy section: Preliminaries --- lkmpg.tex | 158 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 119 insertions(+), 39 deletions(-) diff --git a/lkmpg.tex b/lkmpg.tex index ebadf7d..27101bd 100644 --- a/lkmpg.tex +++ b/lkmpg.tex @@ -543,24 +543,38 @@ If you do not desire to actually compile the kernel, you can interrupt the build Now you can turn back to the directory of your module and compile it: It will be built exactly according to your current kernel settings, and it will load into it without any errors. \section{Preliminaries} -\label{sec:org8c45a1a} \subsection{How modules begin and end} -\label{sec:orgb317392} -A program usually begins with a \textbf{main()} function, executes a bunch of instructions and terminates upon completion of those instructions. Kernel modules work a bit differently. A module always begin with either the init\_module or the function you specify with module\_init call. This is the entry function for modules; it tells the kernel what functionality the module provides and sets up the kernel to run the module's functions when they're needed. Once it does this, entry function returns and the module does nothing until the kernel wants to do something with the code that the module provides. +\label{sec:module_init_exit} +A program usually begins with a \textbf{main()} function, executes a bunch of instructions and terminates upon completion of those instructions. +Kernel modules work a bit differently. A module always begin with either the init\_module or the function you specify with module\_init call. +This is the entry function for modules; it tells the kernel what functionality the module provides and sets up the kernel to run the module's functions when they are needed. +Once it does this, entry function returns and the module does nothing until the kernel wants to do something with the code that the module provides. -All modules end by calling either \textbf{cleanup\_module} or the function you specify with the \textbf{module\_exit} call. This is the exit function for modules; it undoes whatever entry function did. It unregisters the functionality that the entry function registered. +All modules end by calling either \textbf{cleanup\_module} or the function you specify with the \textbf{module\_exit} call. +This is the exit function for modules; it undoes whatever entry function did. +It unregisters the functionality that the entry function registered. -Every module must have an entry function and an exit function. Since there's more than one way to specify entry and exit functions, I'll try my best to use the terms `entry function' and `exit function', but if I slip and simply refer to them as init\_module and cleanup\_module, I think you'll know what I mean. +Every module must have an entry function and an exit function. +Since there's more than one way to specify entry and exit functions, I'll try my best to use the terms ``entry function'' and ``exit function'', but if I slip and simply refer to them as init\_module and cleanup\_module, I think you'll know what I mean. \subsection{Functions available to modules} -\label{sec:org9ebe27a} -Programmers use functions they don't define all the time. A prime example of this is \textbf{printf()}. You use these library functions which are provided by the standard C library, libc. The definitions for these functions don't actually enter your program until the linking stage, which insures that the code (for printf() for example) is available, and fixes the call instruction to point to that code. +\label{sec:avail_func} +Programmers use functions they do not define all the time. A prime example of this is \textbf{printf()}. +You use these library functions which are provided by the standard C library, libc. +The definitions for these functions do not actually enter your program until the linking stage, which insures that the code (for printf() for example) is available, and fixes the call instruction to point to that code. -Kernel modules are different here, too. In the hello world example, you might have noticed that we used a function, \textbf{pr\_info()} but didn't include a standard I/O library. That's because modules are object files whose symbols get resolved upon insmod'ing. The definition for the symbols comes from the kernel itself; the only external functions you can use are the ones provided by the kernel. If you're curious about what symbols have been exported by your kernel, take a look at \textbf{/proc/kallsyms}. +Kernel modules are different here, too. In the hello world example, you might have noticed that we used a function, \textbf{pr\_info()} but did not include a standard I/O library. +That is because modules are object files whose symbols get resolved upon insmod'ing. +The definition for the symbols comes from the kernel itself; the only external functions you can use are the ones provided by the kernel. +If you're curious about what symbols have been exported by your kernel, take a look at \textbf{/proc/kallsyms}. -One point to keep in mind is the difference between library functions and system calls. Library functions are higher level, run completely in user space and provide a more convenient interface for the programmer to the functions that do the real work --- system calls. System calls run in kernel mode on the user's behalf and are provided by the kernel itself. The library function printf() may look like a very general printing function, but all it really does is format the data into strings and write the string data using the low-level system call write(), which then sends the data to standard output. +One point to keep in mind is the difference between library functions and system calls. Library functions are higher level, run completely in user space and provide a more convenient interface for the programmer to the functions that do the real work --- system calls. +System calls run in kernel mode on the user's behalf and are provided by the kernel itself. +The library function printf() may look like a very general printing function, but all it really does is format the data into strings and write the string data using the low-level system call write(), which then sends the data to standard output. -Would you like to see what system calls are made by printf()? It's easy! Compile the following program: +Would you like to see what system calls are made by printf()? +It is easy! +Compile the following program: \begin{code} #include @@ -572,43 +586,85 @@ int main(void) } \end{code} -with \textbf{gcc -Wall -o hello hello.c}. Run the exectable with \textbf{strace ./hello}. Are you impressed? Every line you see corresponds to a system call. \href{https://strace.io/}{strace} is a handy program that gives you details about what system calls a program is making, including which call is made, what its arguments are and what it returns. It's an invaluable tool for figuring out things like what files a program is trying to access. Towards the end, you'll see a line which looks like write (1, "hello", 5hello). There it is. The face behind the printf() mask. You may not be familiar with write, since most people use library functions for file I/O (like fopen, fputs, fclose). If that's the case, try looking at man 2 write. The 2nd man section is devoted to system calls (like kill() and read()). The 3rd man section is devoted to library calls, which you would probably be more familiar with (like cosh() and random()). +with \textbf{gcc -Wall -o hello hello.c}. +Run the exectable with \textbf{strace ./hello}. +Are you impressed? +Every line you see corresponds to a system call. +\href{https://strace.io/}{strace} is a handy program that gives you details about what system calls a program is making, including which call is made, what its arguments are and what it returns. +It is an invaluable tool for figuring out things like what files a program is trying to access. +Towards the end, you will see a line which looks like \verb|write(1, "hello", 5hello)|. +There it is. +The face behind the printf() mask. +You may not be familiar with write, since most people use library functions for file I/O (like fopen, fputs, fclose). +If that is the case, try looking at man 2 write. +The 2nd man section is devoted to system calls (like kill() and read()). +The 3rd man section is devoted to library calls, which you would probably be more familiar with (like cosh() and random()). -You can even write modules to replace the kernel's system calls, which we'll do shortly. Crackers often make use of this sort of thing for backdoors or trojans, but you can write your own modules to do more benign things, like have the kernel write Tee hee, that tickles! everytime someone tries to delete a file on your system. +You can even write modules to replace the kernel's system calls, which we will do shortly. +Crackers often make use of this sort of thing for backdoors or trojans, but you can write your own modules to do more benign things, like have the kernel write Tee hee, that tickles! everytime someone tries to delete a file on your system. \subsection{User Space vs Kernel Space} -\label{sec:org547d7b8} -A kernel is all about access to resources, whether the resource in question happens to be a video card, a hard drive or even memory. Programs often compete for the same resource. As I just saved this document, updatedb started updating the locate database. My vim session and updatedb are both using the hard drive concurrently. The kernel needs to keep things orderly, and not give users access to resources whenever they feel like it. To this end, a CPU can run in different modes. Each mode gives a different level of freedom to do what you want on the system. The Intel 80386 architecture had 4 of these modes, which were called rings. Unix uses only two rings; the highest ring (ring 0, also known as `supervisor mode' where everything is allowed to happen) and the lowest ring, which is called `user mode'. +\label{sec:user_kernl_space} +A kernel is all about access to resources, whether the resource in question happens to be a video card, a hard drive or even memory. +Programs often compete for the same resource. As I just saved this document, updatedb started updating the locate database. +My vim session and updatedb are both using the hard drive concurrently. +The kernel needs to keep things orderly, and not give users access to resources whenever they feel like it. +To this end, a CPU can run in different modes. +Each mode gives a different level of freedom to do what you want on the system. +The Intel 80386 architecture had 4 of these modes, which were called rings. Unix uses only two rings; the highest ring (ring 0, also known as ``supervisor mode'' where everything is allowed to happen) and the lowest ring, which is called ``user mode''. -Recall the discussion about library functions vs system calls. Typically, you use a library function in user mode. The library function calls one or more system calls, and these system calls execute on the library function's behalf, but do so in supervisor mode since they are part of the kernel itself. Once the system call completes its task, it returns and execution gets transfered back to user mode. +Recall the discussion about library functions vs system calls. +Typically, you use a library function in user mode. +The library function calls one or more system calls, and these system calls execute on the library function's behalf, but do so in supervisor mode since they are part of the kernel itself. +Once the system call completes its task, it returns and execution gets transfered back to user mode. \subsection{Name Space} -\label{sec:org81b5bd4} -When you write a small C program, you use variables which are convenient and make sense to the reader. If, on the other hand, you're writing routines which will be part of a bigger problem, any global variables you have are part of a community of other peoples' global variables; some of the variable names can clash. When a program has lots of global variables which aren't meaningful enough to be distinguished, you get namespace pollution. In large projects, effort must be made to remember reserved names, and to find ways to develop a scheme for naming unique variable names and symbols. +\label{sec:namespace} +When you write a small C program, you use variables which are convenient and make sense to the reader. +If, on the other hand, you are writing routines which will be part of a bigger problem, any global variables you have are part of a community of other peoples' global variables; some of the variable names can clash. +When a program has lots of global variables which aren't meaningful enough to be distinguished, you get namespace pollution. +In large projects, effort must be made to remember reserved names, and to find ways to develop a scheme for naming unique variable names and symbols. -When writing kernel code, even the smallest module will be linked against the entire kernel, so this is definitely an issue. The best way to deal with this is to declare all your variables as static and to use a well-defined prefix for your symbols. By convention, all kernel prefixes are lowercase. If you don't want to declare everything as static, another option is to declare a symbol table and register it with a kernel. We'll get to this later. +When writing kernel code, even the smallest module will be linked against the entire kernel, so this is definitely an issue. +The best way to deal with this is to declare all your variables as static and to use a well-defined prefix for your symbols. +By convention, all kernel prefixes are lowercase. If you do not want to declare everything as static, another option is to declare a symbol table and register it with a kernel. +We will get to this later. The file \textbf{/proc/kallsyms} holds all the symbols that the kernel knows about and which are therefore accessible to your modules since they share the kernel's codespace. \subsection{Code space} -\label{sec:orgc8229f4} -Memory management is a very complicated subject and the majority of O'Reilly's "\emph{Understanding The Linux Kernel}" exclusively covers memory management! We're not setting out to be experts on memory managements, but we do need to know a couple of facts to even begin worrying about writing real modules. +\label{sec:codespace} +Memory management is a very complicated subject and the majority of O'Reilly's "\emph{Understanding The Linux Kernel}" exclusively covers memory management! +We are not setting out to be experts on memory managements, but we do need to know a couple of facts to even begin worrying about writing real modules. -If you haven't thought about what a segfault really means, you may be surprised to hear that pointers don't actually point to memory locations. Not real ones, anyway. When a process is created, the kernel sets aside a portion of real physical memory and hands it to the process to use for its executing code, variables, stack, heap and other things which a computer scientist would know about. This memory begins with 0x00000000 and extends up to whatever it needs to be. Since the memory space for any two processes don't overlap, every process that can access a memory address, say 0xbffff978, would be accessing a different location in real physical memory! The processes would be accessing an index named 0xbffff978 which points to some kind of offset into the region of memory set aside for that particular process. For the most part, a process like our Hello, World program can't access the space of another process, although there are ways which we'll talk about later. +If you have not thought about what a segfault really means, you may be surprised to hear that pointers do not actually point to memory locations. +Not real ones, anyway. +When a process is created, the kernel sets aside a portion of real physical memory and hands it to the process to use for its executing code, variables, stack, heap and other things which a computer scientist would know about. +This memory begins with 0x00000000 and extends up to whatever it needs to be. +Since the memory space for any two processes do not overlap, every process that can access a memory address, say 0xbffff978, would be accessing a different location in real physical memory! The processes would be accessing an index named 0xbffff978 which points to some kind of offset into the region of memory set aside for that particular process. +For the most part, a process like our Hello, World program can't access the space of another process, although there are ways which we will talk about later. -The kernel has its own space of memory as well. Since a module is code which can be dynamically inserted and removed in the kernel (as opposed to a semi-autonomous object), it shares the kernel's codespace rather than having its own. Therefore, if your module segfaults, the kernel segfaults. And if you start writing over data because of an off-by-one error, then you're trampling on kernel data (or code). This is even worse than it sounds, so try your best to be careful. +The kernel has its own space of memory as well. Since a module is code which can be dynamically inserted and removed in the kernel (as opposed to a semi-autonomous object), it shares the kernel's codespace rather than having its own. +Therefore, if your module segfaults, the kernel segfaults. +And if you start writing over data because of an off-by-one error, then you're trampling on kernel data (or code). +This is even worse than it sounds, so try your best to be careful. -By the way, I would like to point out that the above discussion is true for any operating system which uses a monolithic kernel. This isn't quite the same thing as \emph{"building all your modules into the kernel"}, although the idea is the same. There are things called microkernels which have modules which get their own codespace. The GNU Hurd and the Zircon kernel of Google Fuchsia are two examples of a microkernel. +By the way, I would like to point out that the above discussion is true for any operating system which uses a monolithic kernel. +This is not quite the same thing as \emph{"building all your modules into the kernel"}, although the idea is the same. +There are things called microkernels which have modules which get their own codespace. +The GNU Hurd and the Zircon kernel of Google Fuchsia are two examples of a microkernel. \subsection{Device Drivers} -\label{sec:org578c01b} -One class of module is the device driver, which provides functionality for hardware like a serial port. On Unix, each piece of hardware is represented by a file located in /dev named a device file which provides the means to communicate with the hardware. The device driver provides the communication on behalf of a user program. So the es1370.o sound card device driver might connect the /dev/sound device file to the Ensoniq IS1370 sound card. A userspace program like mp3blaster can use /dev/sound without ever knowing what kind of sound card is installed. +\label{sec:device_drivers} +One class of module is the device driver, which provides functionality for hardware like a serial port. +On Unix, each piece of hardware is represented by a file located in /dev named a device file which provides the means to communicate with the hardware. +The device driver provides the communication on behalf of a user program. +So the es1370.ko sound card device driver might connect the /dev/sound device file to the Ensoniq IS1370 sound card. +A userspace program like mp3blaster can use /dev/sound without ever knowing what kind of sound card is installed. -\begin{enumerate} -\item Major and Minor Numbers -\label{sec:org63bb013} - -Let's look at some device files. Here are device files which represent the first three partitions on the primary master IDE hard drive: +Let's look at some device files. +% FIXME: use popular devices +Here are device files which represent the first three partitions on the primary master IDE hard drive: \begin{verbatim} $ ls -l /dev/hda[1-3] @@ -617,11 +673,23 @@ brw-rw---- 1 root disk 3, 2 Jul 5 2000 /dev/hda2 brw-rw---- 1 root disk 3, 3 Jul 5 2000 /dev/hda3 \end{verbatim} -Notice the column of numbers separated by a comma. The first number is called the device's major number. The second number is the minor number. The major number tells you which driver is used to access the hardware. Each driver is assigned a unique major number; all device files with the same major number are controlled by the same driver. All the above major numbers are 3, because they're all controlled by the same driver. +Notice the column of numbers separated by a comma. +The first number is called the device's major number. +The second number is the minor number. The major number tells you which driver is used to access the hardware. +Each driver is assigned a unique major number; all device files with the same major number are controlled by the same driver. +All the above major numbers are 3, because they're all controlled by the same driver. -The minor number is used by the driver to distinguish between the various hardware it controls. Returning to the example above, although all three devices are handled by the same driver they have unique minor numbers because the driver sees them as being different pieces of hardware. +The minor number is used by the driver to distinguish between the various hardware it controls. +Returning to the example above, although all three devices are handled by the same driver they have unique minor numbers because the driver sees them as being different pieces of hardware. -Devices are divided into two types: character devices and block devices. The difference is that block devices have a buffer for requests, so they can choose the best order in which to respond to the requests. This is important in the case of storage devices, where it's faster to read or write sectors which are close to each other, rather than those which are further apart. Another difference is that block devices can only accept input and return output in blocks (whose size can vary according to the device), whereas character devices are allowed to use as many or as few bytes as they like. Most devices in the world are character, because they don't need this type of buffering, and they don't operate with a fixed block size. You can tell whether a device file is for a block device or a character device by looking at the first character in the output of ls -l. If it's `b' then it's a block device, and if it's `c' then it's a character device. The devices you see above are block devices. Here are some character devices (the serial ports): +Devices are divided into two types: character devices and block devices. +The difference is that block devices have a buffer for requests, so they can choose the best order in which to respond to the requests. +This is important in the case of storage devices, where it is faster to read or write sectors which are close to each other, rather than those which are further apart. +Another difference is that block devices can only accept input and return output in blocks (whose size can vary according to the device), whereas character devices are allowed to use as many or as few bytes as they like. +Most devices in the world are character, because they don't need this type of buffering, and they don't operate with a fixed block size. +You can tell whether a device file is for a block device or a character device by looking at the first character in the output of ls -l. +If it is `b' then it is a block device, and if it is `c' then it is a character device. +The devices you see above are block devices. Here are some character devices (the serial ports): \begin{verbatim} crw-rw---- 1 root dial 4, 64 Feb 18 23:34 /dev/ttyS0 @@ -630,13 +698,24 @@ crw-rw---- 1 root dial 4, 66 Jul 5 2000 /dev/ttyS2 crw-rw---- 1 root dial 4, 67 Jul 5 2000 /dev/ttyS3 \end{verbatim} -If you want to see which major numbers have been assigned, you can look at /usr/src/linux/Documentation/devices.txt. +% FIXME: use the appropriate entries for device drivers +If you want to see which major numbers have been assigned, you can look at \verb|Documentation/devices.txt|. -When the system was installed, all of those device files were created by the mknod command. To create a new char device named `coffee' with major/minor number 12 and 2, simply do mknod /dev/coffee c 12 2. You don't have to put your device files into /dev, but it's done by convention. Linus put his device files in /dev, and so should you. However, when creating a device file for testing purposes, it's probably OK to place it in your working directory where you compile the kernel module. Just be sure to put it in the right place when you're done writing the device driver. +When the system was installed, all of those device files were created by the mknod command. +To create a new char device named `coffee' with major/minor number 12 and 2, simply do mknod /dev/coffee c 12 2. +You do not have to put your device files into /dev, but it is done by convention. +Linus put his device files in /dev, and so should you. +However, when creating a device file for testing purposes, it is probably OK to place it in your working directory where you compile the kernel module. +Just be sure to put it in the right place when you're done writing the device driver. -I would like to make a few last points which are implicit from the above discussion, but I'd like to make them explicit just in case. When a device file is accessed, the kernel uses the major number of the file to determine which driver should be used to handle the access. This means that the kernel doesn't really need to use or even know about the minor number. The driver itself is the only thing that cares about the minor number. It uses the minor number to distinguish between different pieces of hardware. +I would like to make a few last points which are implicit from the above discussion, but I would like to make them explicit just in case. +When a device file is accessed, the kernel uses the major number of the file to determine which driver should be used to handle the access. +This means that the kernel doesn't really need to use or even know about the minor number. +The driver itself is the only thing that cares about the minor number. +It uses the minor number to distinguish between different pieces of hardware. -By the way, when I say \emph{"hardware"}, I mean something a bit more abstract than a PCI card that you can hold in your hand. Look at these two device files: +By the way, when I say \emph{"hardware"}, I mean something a bit more abstract than a PCI card that you can hold in your hand. +Look at these two device files: \begin{verbatim} $ ls -l /dev/sda /dev/sdb @@ -644,8 +723,9 @@ brw-rw---- 1 root disk 8, 0 Jan 3 09:02 /dev/sda brw-rw---- 1 root disk 8, 16 Jan 3 09:02 /dev/sdb \end{verbatim} -By now you can look at these two device files and know instantly that they are block devices and are handled by same driver (block major 8). Sometimes two device files with the same major but different minor number can actually represent the same piece of physical hardware. So just be aware that the word "hardware" in our discussion can mean something very abstract. -\end{enumerate} +By now you can look at these two device files and know instantly that they are block devices and are handled by same driver (block major 8). +Sometimes two device files with the same major but different minor number can actually represent the same piece of physical hardware. +So just be aware that the word ``hardware'' in our discussion can mean something very abstract. \section{Character Device drivers} \label{sec:orgd3ad4b9}