Linux Device Driver Article 2: How to implement a simple hello world Driver
The previous article introduced the concept of the linux driver and the basic category of the device driver in linux and the basis and differences of each category, this article describes how to write a simple test driver similar to hello world. The only function of this driver is to output hello world.
Before writing a specific instance, let's take a look at an important function printk and several important concepts for debugging programs in the Linux kernel.
Printk is similar to the C language printf, and is a function that outputs print information in the kernel. In the future, the importance of driver debugging is self-evident. Here is a brief introduction.
Printk level
There are a total of eight log levels. The log levels of printk are defined as follows (in include/linux/kernel. h ):
# Define KERN_EMERG 0/* emergency message, which indicates that the system is unavailable */
# Define KERN_ALERT 1/* Report messages, indicating that actions must be taken immediately */
# Define KERN_CRIT 2/* critical conditions, usually involving serious hardware or software operation failures */
# Define KERN_ERR 3/* error conditions. Drivers often use KERN_ERR to report hardware errors */
# Define KERN_WARNING 4/* warning condition to warn of possible problems */
# Define KERN_NOTICE 5/* normal but important conditions for reminder */
# Define KERN_INFO 6/* prompts, such as printing hardware information when the driver starts */
# Define KERN_DEBUG 7/* debug-level messages */
The DEFAULT level of a printk statement with no log level specified is DEFAULT _ MESSAGE_LOGLEVEL (this DEFAULT level is generally <4>, that is, it is at the same level as KERN_WARNING ), it is defined in kernel/printk. c. To enable all log information during driver debugging, use echo 7>/proc/sys/kernel/printk, and disable echo 0>/proc/sys/kernel/printk for logs.
Next we will introduce several important concepts. These concepts can be understood first and will be mentioned in subsequent articles.
Kernel space and user space
Linux systems are divided into two levels. The kernel runs at the highest level and can perform all operations. While applications run at the lowest level, the processor controls direct access to hardware and unauthorized access to memory. Kernel space and user space not only have different priority levels, but also have different memory ing and their respective address spaces. For details, see memory management.
The application can only switch from the user space to the kernel space through system calls or interruptions. The system calls are soft interruptions (0x80 interrupted ). The system code that executes the system call runs in the process context. It indicates that the calling process executes the operation, so it can access all data in the process address space. The kernel code and processes that handle hardware interruptions are asynchronous and irrelevant to any specific process.
Concurrency in the kernel
The difference between kernel programming and common application programming lies in the processing of concurrency. Most applications, except multithreading, are generally executed in sequence and do not need to worry about changing the runtime environment due to other things. The kernel code is not like this. At the same time, multiple processes may access the same module.
The cause of concurrency issues should be considered in kernel programming: 1. linux is usually running multiple concurrent processes, and multiple processes may use our drivers at the same time. 2. Most devices can interrupt the processor while the interrupt handler is asynchronous and may be called when the driver is trying to process other tasks. 3. Some code similar to the kernel timer is run asynchronously. 4. Run on a symmetric multi-processor (SMP), with more than one cpu running the driver. 5. the kernel code can be preemptible.
Current Process
The kernel code can obtain the current process by accessing the global item current. The current Pointer Points to the currently running process. In the execution process of open, read, and other system calls, the current process refers to the process that calls these system calls. The kernel code can obtain information related to the current process through the current pointer.
Functions with "_" in the kernel:Kernel API functions with this name are usually the underlying functions of some interfaces and should be used with caution. In essence, the Double underline here is to tell the programmer: Be careful when calling, otherwise the consequences will be borne by yourself. Take _ init as an example. __init indicates that this function is only used during initialization. After the module is loaded, the module loader will discard the initialization function, which can release the memory occupied by the function and use it. Note: Do not Mark _ init and _ initdata on functions (or data structures) that are still used after initialization. Here is a summary of the online section, as follows.
_ Init, _ initdata and other attribute flags need to put the code of this attribute into the target file. init. text section, data. init. data Section-this process provides xxx for the target platform by compiling the kernel. the lds link script guides ld to complete.
For code and data compiled into a module, when the module is loaded, the __init function is executed;
For code and data statically compiled into the kernel, The do_basic_setup () function calls the do_initcils () function during kernel boot, and the latter is responsible for executing all. init functions.
After initialization, the memory occupied by the functions or data identified by these keywords will be released.
1) All functions marked as _ init are stored in the link. init. in the text section, the placement order of functions in this section is related to the order of links and is uncertain.
2) All the _ init functions are in the Section. initcall. init also saves a function pointer. during initialization, the kernel will call these _ init function pointers through these function pointers. After the initialization is complete, release the entire init section (including. init. text ,. initcall. init, etc.). Note that the calling sequence of these functions during kernel Initialization is only related to the order of the function pointers here, and 1) the functions described in. init. the order in the text section is irrelevant.
Let's take a look at how the hello world Program of a driver is implemented:
#include <linux/init.h>#include <linux/module.h>MODULE_LICENSE("Dual BSD/GPL");static int hello_init(void){ printk(KERN_ALERT "Hello, world\n"); return 0;}static void hello_exit(void){ printk(KERN_ALERT "Goodbye, cruel world\n");}module_init(hello_init);module_exit(hello_exit);
The compilation of the kernel module is different from that of the application. The compilation command of the hello world module is as follows:
Make-C/xxx/kernel_src/M = $ (PWD) modules
/Xxx/kernel_src/indicates the path of the compiled kernel source code. ubuntu is generally located in the/lib/modules/$ (shell uname-r)/build directory.
This function only has two functions: hello_init, which is executed during insmod, the initialization function of the module, and hello_exit, which is executed during rmmod, is the function to be executed when the module is detached. The only function of this module is to output Hello and world in insmod, and Goodbye and cruel world in rmmod.
When writing an application, we generally consist of multiple source files. At this time, compilation cannot continue to use command line compilation, and Makefile must be used. Similarly, makefile is also required for driver module compilation. Below is a Makefile file that can be referenced when compiling a driver module that contains multiple source code files.
ifndef CROSS_COMPILEexport CROSS_COMPILE ?=arm-none-linux-gnueabi-endifARCH ?= armSRC_DIR := /home/XXX/XXXOBJ_DIR := $(SRC_DIR)/objPWD := $(shell pwd)LINUX_SRC ?= /home/XXX/kernelCFG_INC = -I$(SRC_DIR) \-I$(DIR_A) \-I$(DIR_B)CFG_FLAGS += -O2EXTRA_CFLAGS += $(C_FLAGS) $(CFG_INC) $(CFG_INC)obj-m := mymodule.omymodule-objs := a.omymodule-objs += b.omymodule-objs += c.omodules:@make ARCH=$(ARCH) -C $(LINUX_SRC) M=$(PWD) modulesclean:@echo "cleaning..."rm -f mymodule.ko mymodule.o mymodule.mod.* modules.order Module.symversrm -f $(mymodule-objs)
Get a blog update reminder and share more technical information as soon as possible. Welcome to the personal public platform: coder_online)
1. Help you directly answer questions about linux Device Drivers
2. Get technical articles from more than 10 fields in the industry in the first time
3. Ask questions in the article, reply to you immediately, and help you answer questions patiently
4. Let you and the original author become good friends and expand their own network resources
Scan the QR code below or search for the coder_online code. We can contact you online.