1. Kernel Tools and Helper Functions
    1. container_of Macro
    2. Linked list
      1. Creating and initializing a linked list
      2. Creating a linked list node
      3. Adding a linked list node
      4. Deleting a linked list node
      5. Linked list traversal
    3. Kernel sleep mechanism
      1. Wait queue
        1. Definition
        2. Initialize wait queue
        3. wait queue entry API
        4. Sleep wait API
        5. Wake-up API
    4. Delays and timer management
      1. Linux kernel standard timers
        1. Definition
        2. DEFINE_TIMER
        3. timer_setup()
        4. timer_setup_on_stack()
        5. destroy_timer_on_stack()
        6. add_timer()
        7. del_timer()
        8. timer_pending()
        9. mod_timer()
        10. Global variable jiffies
        11. jiffies and time unit conversion functions
        12. Example
      2. Linux High Resolution Timer HRT
        1. Check if HRT is available on the system.
        2. struct hrtimer
        3. hrtimer_init()
        4. hrtimer_start()
        5. hrtimer_cancel()
        6. hrtimer_try_to_cancel()
        7. hrtimer_callback_running()
        8. hrtimer_forward_now()
        9. Callback return value
        10. Example
      3. Dynamic Tick/Tickless Kernel
      4. Delays and Sleep in the Kernel
        1. Atomic context
        2. Non-atomic context
      5. Calling user-space programs from the kernel
  2. IO Models
    1. IO Operation Process
    2. Classification of IO Models
      1. Synchronous blocking IO
      2. Synchronous Non-blocking IO
      3. IO Multiplexing
      4. Signal-driven IO
      5. Asynchronous I/O
    3. Wait queue implements blocking I/O
    4. Non-blocking access
    5. Implementation of IO multiplexing
      1. Linux application layer poll
      2. Driver-level poll
      3. Implementation in the driver
        1. poll_wait()
        2. Steps
        3. Example
    6. Signal-driven I/O
      1. Driver implementation
      2. Example
    7. Asynchronous I/O
  3. Linux Kernel Printing
    1. dmesg
    2. kmsg file
    3. Adjust kernel print level
  4. llseek positioning device driver
    1. Application layer lseek()
    2. Driver layer llseek()
      1. Usage steps
    3. Example
  5. ioctl device operation
    1. Application-layer ioctl()
    2. driver layer ioctl()
    3. example
  6. Encapsulate Driver-Provided API Functions
  7. Optimize driver stability and efficiency
    1. Detect ioctl commands
    2. Check whether the passed address is reasonable
      1. access_ok()
    3. Branch prediction optimization
      1. likely and unlikely macros
  8. Driver Debugging
    1. debugfs
      1. Example
    2. Debug Printing
      1. dump_stack()
      2. WARN_ON(condition)
      3. BUG() and BUG_ON(condition)
      4. panic (fmt…)
Cover image for Advanced Linux Character Device Development

Advanced Linux Character Device Development


Timeline

Timeline

2025-11-13

init

This article introduces the core kernel tools and mechanisms in advanced Linux character device driver development, discussing in detail the principle of the container_of macro, the creation and operation methods of kernel circular doubly linked lists, as well as the process sleep mechanism and the initialization and implementation of wait queues.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Race Conditions
5. Advanced Character Device Progression
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hot Plug
11. pinctrl Subsystem
12. GPIO Subsystem
13. Input Subsystem
14. Single Bus
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network Device
23. ADC
24. IIO
25. USB
26. LCD

Kernel Tools and Helper Functions

container_of Macro

1
2
3
4
5
6
7
// include/linux/kernel.h
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
  • ptrPointer to a member within the structure.

  • typeTarget structure type.

  • memberName of the member in the structure.

  • BUILD_BUG_ON_MSG(...)Compile-time type check to ensure ptr matches the member type, preventing type errors.

  • __mptr - offsetof(type, member)Core formula: Subtract the member’s offset within the structure from the member pointer to get the structure’s starting address.

  • offsetof(type, member)Standard macro to calculate the byte offset of a member within a structure.

container_ofConsider the offset of name from the start of the structure to obtain the correct pointer position. From the pointer__mptrsubtract the offset of the field name to get the correct position.

Linked list

A driver manages multiple devices. Assuming there are 5 devices, it may be necessary to track each device in the driver, which requires a linked list.

Kernel developers only implemented circular doubly linked lists because this structure can implement FIFO and LIFO, and kernel developers aim to keep code minimal. The header file to add in the code to support linked lists is<linux/list.h>. The core data structure for linked list implementation in the kernel isstruct list_head, defined as follows:

1
2
3
struct list_head {
struct list_head *next, *prev;
};

struct list_headUsed in the list head and each node. In the kernel, before representing a data structure as a linked list, the structure must embed thestruct list_headfield.

Example

Let’s create a car linked list:

1
2
3
4
5
struct car {
int door_number;
char *color;
char *model;
};

Before creating the car linked list, its structure must be modified to embed thestruct list_headfield. The structure becomes as follows:

1
2
3
4
5
6
struct car {
int door_number;
char *color;
char *model;
struct list_head list; /*Kernel's table structure */
}

Createstruct list_heada variable that always points to the head (first element) of the linked list. This instance of list_head is not associated with any car; it is a special instance:

1
static LIST_HEAD(carlist);

Now you can create cars and add them to the linked list carlist:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <linux/list.h>

struct car *redcar = kmalloc(sizeof(*car), GFP_KERNEL);
struct car *bluecar = kmalloc(sizeof(*car), GFP_KERNEL);

/* Initialize the list entry of each node*/
INIT_LIST_HEAD(&bluecar->list);
INIT_LIST_HEAD(&redcar->list);

/* Allocate memory for the color and model fields, and populate each field */
[...]

list_add(&redcar->list, &carlist) ;
list_add(&bluecar->list, &carlist) ;

Creating and initializing a linked list

There are two methods to create and initialize a linked list.

  1. Dynamic method

The dynamic method consists ofstruct list_headinitialized with theINIT_LIST_HEADmacro: ·

1
2
struct list_head mylist;
INIT_LIST_HEAD(&mylist);

INIT_LIST_HEAD is defined as follows

1
2
3
4
5
6
7
8
9
10
11
12
/**
* INIT_LIST_HEAD - Initialize a list_head structure
* @list: list_head structure to be initialized.
*
* Initializes the list_head to point to itself. If it is a list header,
* the result is an empty list.
*/
static inline void INIT_LIST_HEAD(struct list_head *list)
{
WRITE_ONCE(list->next, list);
list->prev = list;
}
  1. Static method

Static allocation is done via the LIST_HEAD macro:

1
LIST_HEAD(mylist)

LIST_HEAD is defined as follows

1
2
3
4
#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)

This assigns each pointer (prev and next) within the name field to point to name itself (just like INIT_LIST_HEAD does)

Creating a linked list node

To create a new node, simply create an instance of the data structure and initialize the embedded list_head field. Taking a car as an example, the code is as follows:

1
2
3
4
struct car *blackcar = kzalloc(sizeof(struct car), GFP_KERNEL);

/* Non-static initialization because it is an embedded list field*/
INIT_LIST_HEAD(&blackcar->list);

Adding a linked list node

Provided by the kernellist_add()Used to add a new item to the linked list, it is an internal function__list_addwrapper of:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
if (!__list_add_valid(new, prev, next))
return;

next->prev = new;
new->next = next;
new->prev = prev;
WRITE_ONCE(prev->next, new);
}

/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}

The following example adds two cars to the linked list:

1
2
list_add(&redcar->list, &carlist);
list_add(&blue->list, &carlist);

This pattern can be used to implement a stack. Another function for adding a node to the linked list, the code is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}

This will insert the specified new item at the end of the linked list. For the previous example, the following code can be used:

1
2
list_add_tail(&redcar->list, &carlist);
list_add_tail(&blue->list, &carlist);

This pattern can be used to implement a queue

Deleting a linked list node

Linked list handling in kernel code is a simple task. Deleting a node is straightforward:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty() on entry does not return true after this, the entry is
* in an undefined state.
*/
static inline void list_del(struct list_head *entry)
{
__list_del_entry(entry);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}

Delete the red car:

1
list_del(&redcar->list);

list_del disconnects the prev and next pointers of the specified node, removing it. The memory allocated to this node must be manually freed using kfree.

Linked list traversal

Using the macrolist_for_each_entry(pos, head, member)for linked list traversal.

  • pos: Used for iteration.

  • head: The head node of the linked list.

  • member: The data structure (in our case, it is thestruct carlist defined in ) of the linked liststruct list_headname.

1
2
3
4
5
6
7
8
9
struct car *acar; /* Loop counter*/

int blue_car_num = 0;

/* list is the name of the list_head structure in the data structure */
list_for_each_entry(acar, carlist, list){
if(acar->color == "blue")
blue_car_num++;
}

list_for_each_entrydefined as follows:

1
2
3
4
5
6
7
8
9
10
11
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member); \
!list_entry_is_head(pos, head, member); \
pos = list_next_entry(pos, member))

Kernel sleep mechanism

A process releases the processor through the sleep mechanism, allowing it to handle other processes. The processor may sleep due to sensing data availability or waiting for resource release.

The kernel scheduler manages the list of tasks to be run, which is called the run queue. Sleeping processes are no longer scheduled because they have been removed from the run queue. A sleeping process will never be executed unless its state changes (wake-up). Once a process enters a waiting state, it can release the processor, but it must be ensured that there is a condition or another process to wake it up.

The Linux kernel simplifies the implementation of the sleep mechanism by providing a set of functions and data structures.

Wait queue

Wait queues are actually used to handle blocked I/O, waiting for a specific condition to be met and sensing data or resource availability.

The wait queue is a kernel mechanism for implementing blocking and wake-up,The wait queue is based on a doubly circular linked list structure, where the list head and list node represent the wait queue head and wait queue element respectively.

Wait queue
Wait queue

Definition

include/linux/wait.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct wait_queue_entry wait_queue_entry_t;

struct wait_queue_entry {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head entry;
};

struct wait_queue_head {
spinlock_t lock;
struct list_head head;
};
typedef struct wait_queue_head wait_queue_head_t;

struct task_struct;

Initialize wait queue

APIPurposeDescription / ParametersNotes
DECLARE_WAIT_QUEUE_HEAD(name)Static definition of wait queue headGenerate a wait queue head namednameglobal/static wait queue headmost common method
init_waitqueue_head(wq)dynamically initialize wait queue headsuitable for wait queue head in dynamically allocated structuresmemory must be allocated first

wait queue entry API

these APIs are mostly not needed; task is usually current

APIpurposedescription / parametersnotes
DECLARE_WAITQUEUE(name, task)statically create a wait queue entrytaskusually fill incurrentglobal or static scenario
init_waitqueue_entry(&entry, task)dynamically initialize queue entrytask is usuallycurrentDynamically create item
add_wait_queue(head, entry)Add waiting item to wait queueNon-exclusive waitMostly used for low-level calls (not recommended for direct use)
add_wait_queue_exclusive(head, entry)Exclusive waitOnly wake up 1 exclusive process when wakingUsed for blocking queues such as write operations
remove_wait_queue(head, entry)Remove item from wait queueand add_wait_queue pairing

Sleep wait API

APIPurposeDescriptionReturn value / Notes
wait_event(wq, condition)Condition not met → sleep (non-interruptible)Return if condition is trueCannot be interrupted by signals
wait_event_timeout(wq, condition, timeout)Uninterruptible sleep + timeoutTimeout is in jiffiesReturns remaining jiffies or 0 on timeout
wait_event_interruptible(wq, condition)Interruptible sleepCan be interrupted by signalsReturns when interrupted-ERESTARTSYS
wait_event_interruptible_timeout(wq, condition, timeout)Interruptible + timeoutReturns: >0 remaining time, 0 timeout, <0 interrupted by signal
wait_event_killable(wq, condition)Only interrupted by fatal signalSafer than interruptibleInterrupted by kill signal
wait_event_killable_timeout(wq, condition, timeout)killable + timeoutSame as above

wait_event_interruptibleDoes not continuously poll, but only evaluates the condition when called. If the condition is false, the process will enterTASK_INTERRUPTIBLEstatus and remove it from the run queue.

After that, each time when calling in the wait queuewake_up_interruptible, the condition is rechecked. Ifwake_up_interruptiblethe runtime finds the condition true, the process in the wait queue will be awakened and its status set toTASK_RUNNING. Processes are awakened in the order they went to sleep.

To wake up all processes waiting in the queue, you should usewake_up_interruptible_all

Ifwake_uporwake_up_interruptibleis called and the condition is still FALSE, nothing happens. Ifwake_up(orwake_up_interuptible) is not called, the process will never be awakened.

wait_event、wake_up and wake_up_all. They handle processes in the queue with exclusive (uninterruptible) waiting because they cannot be interrupted by signals. They should only be used for critical tasks.

Interruptible functions are optional (but recommended). Since they can be interrupted by signals, their return value should be checked. A non-zero value means the sleep was interrupted by some signal, and the driver should returnERESTARTSYS

Wake-up API

APIUsageDescription / ParametersWake-up Rules
wake_up(&wq)Wake up all non-exclusive waiters in the queueDo not modify task stateSuitable for many readers, no restriction on wake-up
wake_up_all(&wq)Wake up all waitersInclude exclusiveForce wake up all tasks
wake_up_interruptible(&wq)Wake upTASK_INTERRUPTIBLEState taskAdapt for interruptible wait
wake_up_interruptible_all(&wq)Wake up all interruptible waiters
wake_up_nr(&wq, nr)Wake up nr exclusive waitersMost commonly used:wake_up(&wq)exclusive: wake up only one

There are two types of waiters in the wait queue:

TypeHow it comesTypical scenario
Non-exclusiveadd_wait_queue()Multiple readers (read)
exclusiveadd_wait_queue_exclusive()Writer, resource contention

The design goal of exclusive is:

Avoid the ‘thundering herd’

Delays and timer management

Time is one of the most commonly used resources after memory. It is used to perform almost everything: delaying work, sleeping, scheduling, timeouts, and many other tasks.

There are two types of time. The kernel uses absolute time to know the specific time, that is, the date and time of the day, while relative time is used by the kernel scheduler.

  • For absolute time, there is a hardware chip called the Real-Time Clock (RTC).
  • To handle relative time, the kernel relies on a CPU feature (peripheral) called a timer, which from the kernel’s perspective is referred to as a kernel timer.

Kernel timers are divided into two distinct parts.

  • Standard timers or system timers.
  • High-resolution timers.

Linux kernel standard timers

Standard timers are kernel timers that operate with Jiffy granularity.

The hardware provides the kernel with a system timer to measure elapsed time (i.e., a timing method based on future time points, starting from the current moment and ending at a future moment).The kernel can only calculate and manage time with the help of the system timer, butthe precision of kernel timers is not high, so they cannot be used as high-resolution timers.

Moreover, kernel timers do not run periodically; they automatically shut down upon reaching the timing endpoint. To achieve periodic timing, the timer must be restarted in the timer handler function.

Definition

In the Linux kernel, the timer_list structure is used to represent kernel timers

include/linux/timer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct hlist_node entry;
unsigned long expires;
void (*function)(struct timer_list *);
u32 flags;

#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};

DEFINE_TIMER

1
2
3
4
5
6
7
8
9
10
11
12
// include/linux/timer.h
#define __TIMER_INITIALIZER(_function, _flags) { \
.entry = { .next = TIMER_ENTRY_STATIC }, \
.function = (_function), \
.flags = (_flags), \
__TIMER_LOCKDEP_MAP_INITIALIZER( \
__FILE__ ":" __stringify(__LINE__)) \
}

#define DEFINE_TIMER(_name, _function) \
struct timer_list _name = \
__TIMER_INITIALIZER(_function, 0)

The following code can be used to define a timer and its corresponding timer handler function

1
DEFINE_TIMER(timer_test,function_test);//Define a timer

timer_setup()

ItemDescription
Function Definitionvoid timer_setup(struct timer_list *timer, void (*callback)(struct timer_list *), unsigned int flags);
Header File#include <linux/timer.h>
Parameter timerThe timer object to be initializedstruct timer_listTimer object
Parameter callbackCallback function executed after timer expiration, type isvoid (*)(struct timer_list *)
Parameter flagsTimer flags (e.g.,TIMER_DEFERRABLETIMER_PINNEDetc.)
FunctionInitialize a timer object, prepare it for first use, set callback function and flags
Return ValueNo return value (void)
DescriptionOnly completes initialization, does not start the timer; must be used withadd_timer()ormod_timer()to start timing

timer_setup_on_stack()

ItemDescription
Function Definitionvoid timer_setup_on_stack(struct timer_list *timer, void (*callback)(struct timer_list *), unsigned int flags);
Header File#include <linux/timer.h>
Parameter timerTimer object located on the stackstruct timer_listTimer object
Parameter callbackCallback function executed when the timer expires
Parameter flagsTimer Flag
FunctionInitialize a timer object allocated on the stack
Return ValueNo return value (void)
NotesMust be used withdestroy_timer_on_stack()in pairs, otherwise it may cause kernel debug warnings or resource issues

destroy_timer_on_stack()

ItemDescription
Function Definitionstatic inline void destroy_timer_on_stack(struct timer_list *timer);
Header File#include <linux/timer.h>
Parameter timeron the stackstruct timer_listtimer object
FunctionDestroy a timer created viatimer_setup_on_stack()initialized on the stack
Return ValueNo return value (void)
Usage ScenarioOnly for timers allocated on the stack (not heap/global variables)
NotesMust be used withtimer_setup_on_stack()in pairs

add_timer()

ItemDescription
Function Definitionvoid add_timer(struct timer_list *timer);
Header File#include <linux/timer.h>
Parameter timerAn initialized timer object, must be setexpires(expiry time) andfunction(callback function)
FunctionRegister the timer with the kernel to start timing. Whenexpiresexpires, the callback function is triggered
Return valueNo return value (void)

del_timer()

ItemDescription
Function definitionint del_timer(struct timer_list *timer);
Header file#include <linux/timer.h>
Parameter timerThe timer object to be deleted
FunctionDelete the timer from the kernel so it no longer fires. Does not wait for the callback to finish executing (if waiting is needed, usedel_timer_sync()
Return valueSuccessfully deleted:1(timer was active) no longer in the active queue:0

Note: Delete the timer from the kernel so it no longer fires. To wait for the callback to finish executing, usedel_timer_sync(), wait for the handler (even if executing on another CPU) to complete. Do not hold locks that prevent the handler from finishing, as this will cause a deadlock.

The timer should be released in the module cleanup routine. You can calltimer_pending()function to independently check if the timer is running

timer_pending()

This function checks if there is a pending triggered timer callback.

ItemDescription
Function definitionstatic inline int timer_pending(const struct timer_list *timer);
Header file#include <linux/timer.h>
Parameter timerThe timer whose status is to be checkedstruct timer_listTimer Object
FunctionDetermine whether the timer is currently in a pending state
Return ValueIf the timer has been added to the kernel timer queue (is timing), return1; otherwise, return0
NotesThe caller must ensure that access to the timer is concurrency-protected (e.g., by locking or within the same context)

mod_timer()

ItemDescription
Function Definitionint mod_timer(struct timer_list *timer, unsigned long expires);
Header File#include <linux/timer.h>
Parameter timerThe timer object to be modified
Parameter expiresNew expiration time (in jiffies)
FunctionModify the expiration time of the timer. If the timer is not active, it willautomatically activate. If it is active, it willrestart the timer
Return valueTimer was previously active:1
Timer was previously inactive:0

Before using the add_timer() function to register a timer with the Linux kernel, you also need to set the timeout period, which is determined by the timer_The expires parameter in the list structure, in units of ticks. The system tick frequency can be set through the Linux kernel configuration graphical interface menuconfig during compilation, with the specific path as follows:

1
2
-> Kernel Features
-> Timer frequency (<choice> [=y])

The current kernel version supports optional system tick rates of 100Hz, 250Hz, 300Hz, and 1000Hz, with 300Hz selected by default.

Global variable jiffies

The global variable jiffies records the total number of ticks since system boot. jiffies_64 is used for 64-bit systems, while jiffies is used for 32-bit systems. At boot, the kernel initializes this variable to 0. Thereafter, each clock interrupt handler increments its value. The increase in jiffies per second equals the configured system tick rate. This variable is defined ininclude/linux/jiffies.hIn the file (already included in timer.h, no need to include again), the specific definition is as follows:

1
2
extern u64 __cacheline_aligned_in_smp jiffies_64;
extern unsigned long volatile __cacheline_aligned_in_smp __jiffy_arch_data jiffies;

jiffies and time unit conversion functions

Function definitionPurpose
int jiffies_to_msecs(const unsigned long j)Convertjiffiesparameter of typejto the corresponding milliseconds
int jiffies_to_usecs(const unsigned long j)Convertjiffiesparameter of typejto the corresponding microseconds
u64 jiffies_to_nsecs(const unsigned long j)Convertjiffiesparameter of typejto the corresponding nanoseconds
long msecs_to_jiffies(const unsigned int m)Convert milliseconds tojiffiestype
long usecs_to_jiffies(const unsigned int u)Convert microseconds tojiffiestype
unsigned long nsecs_to_jiffies(u64 n)Convert nanoseconds tojiffiestype

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/uaccess.h>

struct drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
};

static struct drv_data *drv_dat;

static void timer_irq_func(struct timer_list *t);

DEFINE_TIMER(timer_test, timer_irq_func);

int timer_mod_test_open(struct inode *inode, struct file *file)
{
file->private_data = drv_dat;
pr_info("open is called by pid: %d\n", task_pid_nr(current));
return 0;
}

ssize_t timer_mod_test_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
size_t len = min(sizeof(u64) + 1, size);
char kbuf[72];

snprintf(kbuf, sizeof(kbuf), "%llu", jiffies64_to_msecs(jiffies_64));

if (copy_to_user(buf, kbuf, len) != 0)
return -EFAULT;

pr_info("read is called by pid: %d\n", task_pid_nr(current));
return len;
}

int timer_mod_test_release(struct inode *inode, struct file *file)
{
pr_info("release is called by pid: %d\n", task_pid_nr(current));
return 0;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = timer_mod_test_open,
.read = timer_mod_test_read,
.release = timer_mod_test_release,
};

static void timer_irq_func(struct timer_list *t)
{
pr_info("timer_irq_func is called\n");
mod_timer(&timer_test, jiffies_64 + msecs_to_jiffies(3000));
}

static int __init timer_mod_test_init(void)
{
int ret;
drv_dat = kzalloc(sizeof(struct drv_data), GFP_KERNEL);
if (drv_dat == NULL) {
ret = -ENOMEM;
goto kzalloc_fail;
}
ret = alloc_chrdev_region(&drv_dat->dev_num, 1, 0, "timer_mod_test_chrdev_region");
if (ret < 0)
goto alloc_chrdev_region_fail;

cdev_init(&drv_dat->cdev, &fops);
drv_dat->cdev.owner = THIS_MODULE;
ret = cdev_add(&drv_dat->cdev, drv_dat->dev_num, 1);
if (ret < 0)
goto cdev_add_fail;

drv_dat->class = class_create(THIS_MODULE, "chrdev");
if (IS_ERR(drv_dat->class)) {
ret = PTR_ERR(drv_dat->class);
goto class_create_fail;
}
drv_dat->dev =
device_create(drv_dat->class, NULL, drv_dat->dev_num, NULL, "timer_test%d", 0);
if (IS_ERR(drv_dat->dev)) {
ret = PTR_ERR(drv_dat->dev);
goto device_create_fail;
}
// Set timer_test to 5 seconds later
timer_test.expires = jiffies_64 + msecs_to_jiffies(3000);
// Add a timer
add_timer(&timer_test);

return 0;
device_create_fail:
class_destroy(drv_dat->class);
class_create_fail:
cdev_del(&drv_dat->cdev);
cdev_add_fail:
unregister_chrdev_region(drv_dat->dev_num, 1);
alloc_chrdev_region_fail:
kfree(drv_dat);
kzalloc_fail:
return ret;
}

static void __exit timer_mod_test_exit(void)
{
// Delete timer
del_timer(&timer_test);
device_destroy(drv_dat->class, drv_dat->dev_num);
class_destroy(drv_dat->class);

cdev_del(&drv_dat->cdev);

unregister_chrdev_region(drv_dat->dev_num, 1);

kfree(drv_dat);
}

module_init(timer_mod_test_init);
module_exit(timer_mod_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is a test sample for linux timer");

You can use atomic variables and timer to create a timer, example as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/timer.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/atomic.h>
#include <linux/string.h>

struct timer_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
atomic64_t sec;
};

static struct timer_drv_data *timer_drv_data;

static void timer_sec_func(struct timer_list *t);

DEFINE_TIMER(timer_sec, timer_sec_func);

int timer_sec_test_open(struct inode *inode, struct file *file)
{
file->private_data = timer_drv_data;
// Initialize to 0
atomic64_set(&timer_drv_data->sec, 0);
add_timer(&timer_sec);

pr_info("open is called by pid: %d\n", task_pid_nr(current));
return 0;
}

ssize_t timer_sec_test_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
struct timer_drv_data *dat = file->private_data;
size_t len;

char kbuf[128];
snprintf(kbuf, sizeof(kbuf), "current sec: %llu\n", atomic64_read(&dat->sec));

len = min(size, strlen(kbuf) + 1);
if (copy_to_user(buf, kbuf, len) != 0)
return -EFAULT;

return len;
}

int timer_sec_test_release(struct inode *inode, struct file *file)
{
del_timer(&timer_sec);
pr_info("release is called by pid: %d\n", task_pid_nr(current));
return 0;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = timer_sec_test_open,
.read = timer_sec_test_read,
.release = timer_sec_test_release,
};

static void timer_sec_func(struct timer_list *t)
{
atomic64_inc(&timer_drv_data->sec);
mod_timer(&timer_sec, get_jiffies_64() + msecs_to_jiffies(1000));
}

static int __init timer_sec_init(void)
{
int ret;
timer_drv_data = kzalloc(sizeof(struct timer_drv_data), GFP_KERNEL);
if (timer_drv_data == NULL) {
ret = -ENOMEM;
goto kzalloc_fail;
}

ret = alloc_chrdev_region(&timer_drv_data->dev_num, 0, 1, "test_chrdev_region");
if (ret < 0)
goto alloc_chrdev_region_fail;

cdev_init(&timer_drv_data->cdev, &fops);
timer_drv_data->cdev.owner = THIS_MODULE;
ret = cdev_add(&timer_drv_data->cdev, timer_drv_data->dev_num, 1);
if (ret < 0)
goto cdev_add_fail;

timer_drv_data->class = class_create(THIS_MODULE, "chrdev");
if (IS_ERR(timer_drv_data->class)) {
ret = PTR_ERR(timer_drv_data->class);
goto class_create_fail;
}

timer_drv_data->dev = device_create(timer_drv_data->class, NULL, timer_drv_data->dev_num,
NULL, "timer_sec%d", 0);
if (IS_ERR(timer_drv_data->dev)) {
ret = PTR_ERR(timer_drv_data->dev);
goto device_create_fail;
}

return 0;
device_create_fail:
class_destroy(timer_drv_data->class);
class_create_fail:
cdev_del(&timer_drv_data->cdev);
cdev_add_fail:
unregister_chrdev_region(timer_drv_data->dev_num, 1);
alloc_chrdev_region_fail:
kfree(timer_drv_data);
kzalloc_fail:
return ret;
}

static void __exit timer_sec_exit(void)
{
class_destroy(timer_drv_data->class);

cdev_del(&timer_drv_data->cdev);

unregister_chrdev_region(timer_drv_data->dev_num, 1);

kfree(timer_drv_data);
}

module_init(timer_sec_init);
module_exit(timer_sec_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is a sec timer");

Linux High Resolution Timer HRT

hrtimer= High Resolution TimerEnabled by the kernel configuration optionCONFIG_HIGH_RES_TIMERS, features as follows:

  • Nanosecond precision (based on ktime)
  • Suitable for high-precision periodic tasks
  • Runs in softirq context
  • Can be triggered periodically or once

Compared with traditionaltimer_listdifferences:

Itemtimer_listhrtimer
Precisionjiffies levelnanosecond level
Precision depends onHZhardware high-precision clock
Suitable forordinary delayshigh-precision periodic tasks
UnitSymbolEquals how many secondsApplication scenario
Millisecondms10⁻³ s = 0.001 sComputer response, audio
Microsecondμs10⁻⁶ sElectronics, network latency
Nanosecondns10⁻⁹ sCPU clock cycle, light propagation
Picosecondps10⁻¹² sLaser, quantum physics
Femtosecondfs10⁻¹⁵ sChemical reaction kinetics
Attosecondas10⁻¹⁸ sElectron motion inside atoms (related to 2023 Nobel Prize in Physics)

Header file:

1
#include <linux/hrtimer.h>

Check if HRT is available on the system.

  1. Check the kernel configuration file, which should contain something like:CONFIG_ HIGH_RES_TIMERS=y
  2. Checkcat /proc/timer_listorcat /proc/timer_list | grep resolutionresults..resolutionitem must show1 nsecs, event
    handler must showhrtimer_interrupts
  3. Useclock_getressystem call.
  4. In kernel code, use#ifdef CONFIG_HIGH_RES_TIMERS

When the system enables HRT, the precision of sleep and timer system calls no longer depends on jiffies, but they become as precise as HRT. This is why some systems do not supportnanosleep()and similar reasons.

struct hrtimer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* struct hrtimer - the basic hrtimer structure
* @node: timerqueue node, which also manages node.expires,
* the absolute expiry time in the hrtimers internal
* representation. The time is related to the clock on
* which the timer is based. Is setup by adding
* slack to the _softexpires value. For non range timers
* identical to _softexpires.
* @_softexpires: the absolute earliest expiry time of the hrtimer.
* The time which was given as expiry time when the timer
* was armed.
* @function: timer expiry callback function
* @base: pointer to the timer base (per cpu and per clock)
* @state: state information (See bit values above)
* @is_rel: Set if the timer was armed relative
* @is_soft: Set if hrtimer will be expired in soft interrupt context.
* @is_hard: Set if hrtimer will be expired in hard interrupt context
* even on RT.
*
* The hrtimer structure must be initialized by hrtimer_init()
*/
struct hrtimer {
struct timerqueue_node node;
ktime_t _softexpires;
enum hrtimer_restart (*function)(struct hrtimer *);
struct hrtimer_clock_base *base;
u8 state;
u8 is_rel;
u8 is_soft;
u8 is_hard;
};

hrtimer_init()

Initialize hrtimer. Before initializing hrtimer, you need to set ktime, which represents the duration.

ItemDescription
Prototypevoid hrtimer_init(struct hrtimer *timer, clockid_t which_clock, enum hrtimer_mode mode);
FunctionInitialize hrtimer
Parameterstimer: timer
which_clock: clock source
mode: mode
Return valueNone

Common clock:

  • CLOCK_MONOTONIC
  • CLOCK_REALTIME

Common mode:

  • HRTIMER_MODE_REL (relative time, relative to the current time value)
  • HRTIMER_MODE_ABS (absolute time)

hrtimer_start()

ItemDescription
Prototypeint hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode);
FunctionStart timer
Parameterstimer: timer
tim: time
mode: mode
Return Value0 or 1

hrtimer_cancel()

ItemDescription
Prototypeint hrtimer_cancel(struct hrtimer *timer);
FunctionCancel Timer
Return Value1 = Successfully canceled;
0 = Not running

hrtimer_try_to_cancel()

ItemDescription
Function Definitionextern int hrtimer_try_to_cancel(struct hrtimer *timer);
Header File#include <linux/hrtimer.h>
Parameter timerTo be canceledstruct hrtimerHigh-resolution timer object
FunctionAttempt to cancel a high-resolution timer (non-blocking mode)
Return value>0: Successfully canceled (timer is active)
0: Timer is not active (expired or not started)
-1: Timer callback function is executing, cannot cancel
Blocking or not❌ Does not wait for callback to complete (non-blocking), can be called in interrupt context

hrtimer_try_to_cancel internally calls hrtimer_callback_running

hrtimer_callback_running()

Independently check if the hrtimer callback function is still running:

ItemDescription
Function definitionstatic inline int hrtimer_callback_running(struct hrtimer *timer);
Header file#include <linux/hrtimer.h>
Parameter timerThe timer to check the status ofstruct hrtimerHigh-resolution timer object
FunctionDetermine whether the callback function of this hrtimer is currently executing
Return valueReturnsnon-zero (true); otherwise returns0 (false)
Blocking❌ Non-blocking, only performs status check
Use caseUsed to detect callback execution status, often used withhrtimer_try_to_cancel()Use
1
2
3
4
5
6
7
8
/*
* Helper function to check, whether the timer is running the callback
* function
*/
static inline int hrtimer_callback_running(struct hrtimer *timer)
{
return timer->base->running == timer;
}

hrtimer_forward_now()

ProjectDescription
Prototypeu64 hrtimer_forward_now(struct hrtimer *timer, ktime_t interval);
FunctionUsed for periodic timing
Return valueForwarding count

Callback return value

1
2
3
4
enum hrtimer_restart {
HRTIMER_NORESTART, // Execute only once
HRTIMER_RESTART, // Continue execution
};

To prevent the timer from automatically restarting, the hrtimer callback function must return HRTIMER_NORESTART

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <linux/module.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>

static struct hrtimer my_timer;
static ktime_t period;

static enum hrtimer_restart my_timer_callback(struct hrtimer *timer)
{
printk("hrtimer fired\n");

// Periodic timing
hrtimer_forward_now(timer, period);

return HRTIMER_RESTART;
}

static int __init my_init(void)
{
printk("hrtimer init\n");

period = ktime_set(1, 0); // 1 second

hrtimer_init(&my_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
my_timer.function = my_timer_callback;

hrtimer_start(&my_timer, period, HRTIMER_MODE_REL);

return 0;
}

static void __exit my_exit(void)
{
hrtimer_cancel(&my_timer);
printk("hrtimer exit\n");
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");

Dynamic Tick/Tickless Kernel

With the previous HZ option, even when idle, the kernel would interrupt HZ times per second to reschedule tasks. If HZ is set to 1000, there are 1000 kernel interrupts per second, preventing the CPU from staying idle for long periods, thus affecting CPU power consumption.

Now let’s take a look atkernels without fixed or predefined ticks, in these kernels,the tick is disabled until some task needs to be executed. Such kernels are called Tickless kernels.

In fact, tick activation is scheduled based on the next operation, so its correct name should be dynamic tick kernel. The kernel is responsible for task scheduling in the system and maintains a list of runnable tasks (run queue). When there are no tasks to schedule, the scheduler switches to the idle thread, which enables dynamic tick bydisabling the periodic tick until the next timer expires (when a new task is queued for processing)

The kernel also maintains a list of task timeouts internally (it knows when to sleep and for how long).

  • In the idle state, if the next tick is farther than the smallest timeout in the task timeout list, the kernel programs the timer with that timeout value.
  • When the timer expires, the kernel re-enables the periodic tick and calls the scheduler, which schedules the task associated with the timeout.

In this way, the Tickless kernel removes the periodic tick, saving power.

Delays and Sleep in the Kernel

There are two types of delays, depending on the context in which the code runs:Atomic or non-atomic. The header file required for handling kernel delays is#include <linux/delay.h>

Atomic context

Tasks in atomic context (such as ISRs) cannot enter sleep state, and scheduling is not possible. This is whydelays in atomic context must use busy-wait loops. The kernel provides the Xdelay family of functions, which consume enough time in a busy loop (based on jiffies) to achieve the required delay.

  • ndelay(unsigned long nsecs)
  • udelay(unsigned long usecs)
  • mdelay(unsigned long msecs)

You should always useudelay(), becausendelay()the precision of depends on the precision of the hardware timer (which may not be the case for embedded SoCs). It is not recommended to usemdelay()

Timer handlers (callbacks) execute in atomic context, meaning sleep is strictly prohibited. This refers to all functions that could cause the calling program to sleep, such as allocating memory, locking mutexes, or explicitly callingsleep()functions, etc.

Non-atomic context

In non-atomic context, the kernel providessleep[_range]family of functions, and which function to use depends on how long the delay needs to be.

  • udelay(unsigned long usecs): Based on a busy-wait loop. This function should be used if you need to sleep for a few microseconds (approximately ≤10 us).
  • usleep_range(unsigned long min, unsigned long max): Depends on hrtimer, sleeping for a few microseconds to a few milliseconds (10 us~20
    ms) is recommended to use it, avoiding theudelay()busy-wait loop.
  • msleep(unsigned long msecs): Supported by jiffies/traditional timers. Use this function for long sleeps of several milliseconds or more (10 ms+).

Kernel source documentationDocumentation/timers/timers-howto.rstexplains sleep and delay-related topics in detail.

Calling user-space programs from the kernel

Consider the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h> /* Work queue */
#include <linux/kmod.h>
static struct delayed_work initiate_shutdown_work;

static void delayed_shutdown( void )
{
char *cmd = "/sbin/shutdown";
char *argv[] = {
cmd,
"-h",
"now",
NULL,
};

char *envp[] = {
"HOME=/",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin",
NULL,
};

call_usermodehelper(cmd, argv, envp, 0);
}

static int __init my_shutdown_init( void )
{
schedule_delayed_work(&delayed_shutdown,
msecs_to_jiffies(200));
return 0;
}

static void __exit my_shutdown_exit( void )
{
return;
}

module_init( my_shutdown_init );
module_exit( my_shutdown_exit );

MODULE_LICENSE("GPL");
MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");
MODULE_DESCRIPTION("Simple module that trigger a delayed shut down");

In the previous example, the API used (call_usermodehelper) is part of the Usermode-helper API, and all functions are defined inkernel/kmod.cIts usage is very simple. It is used by the kernel, for example, for module loading/unloading and Cgroup management.

IO Models

IO Operation Process

A complete IO flow typically includes the following key steps:

  • IO Call: The application initiates an IO request (e.g., reading a file) to the kernel through the system call interface.
  • IO Execution: After receiving the request, the kernel completes the specific IO (e.g., reading data from disk) by operating the hardware through the driver, and returns the final result to user space.

A complete IO process needs to include the following three steps:

  1. The application in user space initiates an IO call request (system call) to the kernel.
  2. The kernel operating system prepares the data, loading the data from the IO device into the kernel buffer.
  3. The operating system copies the data, transferring the data from the kernel buffer to the user process buffer.

Classification of IO Models

In actual development, IO operations often become a key factor affecting program performance. Suppose there is a scenario: reading 100MB of data from disk and processing it, where reading the data takes 20 seconds and processing the data also takes 20 seconds. If the most traditional sequential flow is adopted—reading first and then processing—the entire process takes about 40 seconds, which is clearly inefficient. So, can we process the data while waiting for it? Of course! This is where the IO programming model comes into play.

Under the POSIX/Linux definition: IO models include blocking IO, non-blocking IO, signal-driven IO, IO multiplexing, and asynchronous IO. The first four are called synchronous IO.

  • Synchronous IO
    • Blocking IO
    • Non-blocking IO
    • IO Multiplexing
    • Signal-driven IO
  • Asynchronous IO

The difference between synchronous and asynchronous iswhether to wait for the IO execution result, or who completes the data copy to user space?。

**Synchronous IO:**The final action of copying data from kernel to user space is done by the user thread when callingread()

Asynchronous IO:

  • User mode initiates IO request → returns immediately
  • Kernel completes IO in the background
  • Data has been copied to user space
  • Kernel notifies the process: IO complete

Synchronous blocking IO

When a process performs an IO operation (such as a read operation), it first initiates a system call, thus switching to kernel space for processing,When the data in kernel space is not ready, the process will be blocked and will not continue execution, until the data in kernel space is ready, the data is copied from kernel space to user space, and finally returned to the application process for data processing in user space.

Blocking IO model
Blocking IO model

Blocking IO can obtain results in a timely manner and immediately process the obtained results. However, before obtaining the results, it cannot handle other tasks and must constantly monitor the results. For example,the scanf function in C language

Synchronous Non-blocking IO

Unlike the blocking IO model, when non-blocking IO performs an IO operation,if the kernel data is not ready, the kernel immediately returns an error to the process, without blocking; if the kernel space data is ready, the kernel immediately returns the data to the user-space process.

Synchronous Non-blocking IO
Synchronous Non-blocking IO

The advantage of non-blocking IO is high efficiency, allowing other tasks to be done in the same time. However, the disadvantage is also obvious: frequent checking of data readiness can lead to high CPU usage. To solve this problem, non-blocking IO is often used in conjunction with IO multiplexing technology.

IO Multiplexing

The select(), poll(), and epoll() functions are mechanisms for implementing IO multiplexing.

IO multiplexing allowsa single process to monitor multiple descriptors. When a descriptor is found to be ready, it notifies the program to perform the corresponding read or write operation.

Taking the select() function as an example, as shown in the figure. When using it, you need to pass the set of file descriptors to be monitored and a timeout value to select().

When select() is executed, the system triggers a system call,and the kernel will traverse and check whether these descriptors have triggered the target event.(e.g., readable or writable). If an event is detected, it returns immediately; ifno event is detected, the process enters a blocked state and sleepsuntil any descriptor is ready or a timeout occurs

After select() returns, user space must traverse all descriptors to confirm which one triggered the event, thereby achieving the effect of a single thread managing multiple IO operations simultaneously.

IO multiplexing
IO multiplexing

The advantage of IO multiplexing is that a single process/thread can monitor and handle multiple IO streams simultaneously, greatly improving efficiency. However, IO multiplexing is not a panacea,although IO multiplexing can monitor multiple IO streams, the actual processing of results can only be done sequentially, making it moresuitable for IO-intensive scenarios where each IO stream has a small amount of data and arrival times are scattered(e.g., online chat).

Additionally, select has an upper limit on the number of descriptors it can monitor (typically no more than 1024), andit needs to traverse to determine which IO generated data. Therefore, when there are many IO streams, efficiency is low (this issue is resolved by epoll).

Signal-driven IO

Signal-driven IO refers to**The process will inform the kernel in advance that when an event occurs on a certain descriptor, the kernel should send a SIGIO signal to notify the process.**The process can then handle the event within the signal handler function.

For example, in a Linux system, when a user presses Ctrl+C to terminate a running task, the system actually sends a SIGINT signal to the process, and the default handler for this signal is to exit the current program.

Specifically for the I/O model, the process needs to first register a corresponding signal handler for the SIGIO signal and enable signal-driven I/O for the relevant descriptor. When data is ready, the process receives a SIGIO signal and can call I/O operation functions within the signal handler to process the data.

Signal-driven I/O
Signal-driven I/O

Asynchronous I/O

aio_read()The function is often used for asynchronous I/O. When a process usesaio_read()to read data,it returns immediately if the data is not yet ready, without blocking

If the data is ready, it copies the data from kernel space to the user-space buffer, and thenexecutes the defined callback function to process the received data

Asynchronous I/O
Asynchronous I/O

However, for Linux AIO, Linux AIOonly supports direct I/Omodestorage file(storage file), and is mainly used inthe database subfield

whileio_uringsupports storage files and network files (network sockets), and also supports more asynchronous system calls (accept/openat/stat/...), rather than being limited toread/writesystem calls.

Wait queue implements blocking I/O

In Linux drivers,blocking processes can be implemented using wait queues

Step 1: Initialize the wait queue head and set the condition to false (condition=0).

Step 2: Callwait_event()at the point where blocking is needed, causing the process to enter a sleep state

Step 3: When the condition is met, the sleep needs to be lifted. First set the condition (condition=1), then callwake_up()function to wake up the sleeping processes in the wait queue.

Driver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>

#define KBUF_CAPACITY 32
struct waitqueue_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
char kbuf[KBUF_CAPACITY];
bool kbuf_ready;
wait_queue_head_t waitque;
};

static struct waitqueue_drv_data *drv_dat;



// DECLARE_WAIT_QUEUE_HEAD(waitque);

int waitqueue_test_open(struct inode *inode, struct file *file)
{
file->private_data = drv_dat;

pr_info("waitqueue_test_open is called\n");
return 0;
}

ssize_t waitqueue_test_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
struct waitqueue_drv_data *dat = file->private_data;
size_t len;
int ret;

// If opened in non-blocking mode
if (file->f_flags & O_NONBLOCK) {
if (!dat->kbuf_ready)
return -EAGAIN;
} else { // Opened in blocking mode
ret = wait_event_interruptible(dat->waitque, dat->kbuf_ready == true);
if (ret < 0) {
pr_err("waitque_test_read is interrupted\n");
return ret;
}
}

len = min(size, (size_t)strlen(dat->kbuf));

if (copy_to_user(buf, dat->kbuf + *offset, len) != 0)
return -EFAULT;

dat->kbuf_ready = false;

return len;
}

ssize_t waitqueue_test_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
struct waitqueue_drv_data *dat = file->private_data;
size_t len = min(size, (size_t)(KBUF_CAPACITY - 1));

if (copy_from_user(dat->kbuf, buf, len) != 0)
return -EFAULT;

dat->kbuf[len] = '\0';

dat->kbuf_ready = true;
wake_up_interruptible(&dat->waitque);

return len;
}

int waitqueue_test_release(struct inode *inode, struct file *file)
{
pr_info("waitqueue_test_release is called\n");
return 0;
}

static struct file_operations fops = {
.owner = THIS_MODULE,
.open = waitqueue_test_open,
.read = waitqueue_test_read,
.write = waitqueue_test_write,
.release = waitqueue_test_release,
};

static int __init waitqueue_test_init(void)
{
int err;

drv_dat =
(struct waitqueue_drv_data *)kzalloc(sizeof(struct waitqueue_drv_data), GFP_KERNEL);
if (drv_dat == NULL)
goto kzalloc_fail;
err = alloc_chrdev_region(&drv_dat->dev_num, 0, 1, "waitque_drv_chrdev_region");
if (err < 0)
goto alloc_chrdev_region_fail;

cdev_init(&drv_dat->cdev, &fops);
drv_dat->cdev.owner = THIS_MODULE;
err = cdev_add(&drv_dat->cdev, drv_dat->dev_num, 1);
if (err < 0)
goto cdev_add_fail;

drv_dat->class = class_create(THIS_MODULE, "chrdev");
if (IS_ERR(drv_dat->class)) {
err = PTR_ERR(drv_dat->class);
goto class_create_fail;
}
drv_dat->dev =
device_create(drv_dat->class, NULL, drv_dat->dev_num, NULL, "waitqueue_test%d", 0);
if (IS_ERR(drv_dat->dev)) {
err = PTR_ERR(drv_dat->dev);
goto device_create_fail;
}
// Initialize wait queue
init_waitqueue_head(&drv_dat->waitque);

return 0;
device_create_fail:
class_destroy(drv_dat->class);
class_create_fail:
cdev_del(&drv_dat->cdev);
cdev_add_fail:
unregister_chrdev_region(drv_dat->dev_num, 1);
alloc_chrdev_region_fail:
kfree(drv_dat);
kzalloc_fail:
return err;
}

static void __exit waitqueue_test_exit(void)
{
device_destroy(drv_dat->class, drv_dat->dev_num);
class_destroy(drv_dat->class);
cdev_del(&drv_dat->cdev);
unregister_chrdev_region(drv_dat->dev_num, 1);
kfree(drv_dat);
}

module_init(waitqueue_test_init);
module_exit(waitqueue_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@163.com>");
MODULE_DESCRIPTION("waitqueue sample");

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cat /dev/waitqueue_test0 &
[ 18.186918] waitqueue_test_open is called


$ echo "Hello World" > /dev/waitqueue_test0
[ 33.059018] waitqueue_test_open is called
[ 33.060401] waitqueue_test_release is called
Hello World


$ echo "Hello Linux" > /dev/waitqueue_test0
[ 43.618554] waitqueue_test_open is called
[ 43.619516] waitqueue_test_release is called
Hello Linux

Non-blocking access

The application can use the following example code to implement blocking access:

1
2
3
4
5
int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR);/* Blocking mode open */
ret = read(fd, &data, sizeof(data));/* Read data */

It can be seen that forthe default read mode of the device driver file is blocking, so previous experimental routine tests all used blocking I/O.
If the application wants to access the driver device file in non-blocking mode, it can use the following code:

1
2
3
4
int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /*Non-blocking mode open */
ret = read(fd, &data, sizeof(data)); /* Read data */

When using the open function to open “/dev/xxx_dev” device file, the parameter “O_NONBLOCK” is added, indicating that the device is opened in non-blocking mode, so reading data from the device is also non-blocking.

Driver

Through the f field in the file structure_in flags, it checks whether the application opened the device via O_NONBLOCK mode

1
2
3
4
5
6
7
8
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
struct device_test *test_dev=(struct device_test *)file->private_data;
if(file->f_flags & O_NONBLOCK ){
if (test_dev->flag !=1)
return -EAGAIN;
}
...

Implementation of IO multiplexing

IO multiplexing is a synchronous IO model. IO multiplexing canenable a process to monitor multiple file descriptors. Once a file descriptor is ready, it notifies the application to perform the corresponding read or write operation. When no file descriptor is ready, it blocks the application, thereby freeing CPU resources.

At the application layer, Linux provides three models for implementing IO multiplexing: select, poll, and epoll.

  • poll and select are basically the same; both can monitor multiple file descriptors and obtain ready file descriptors by polling them.
  • epoll changes active polling to passive notification, where it passively receives notifications when events occur.

Linux application layer poll

ItemContent
FunctionMonitor read/write events or exceptional events on multiple file descriptors
Prototypeint poll(struct pollfd *fds, nfds_t nfds, int timeout);
Parameters-fds: Array of struct pollfd, describing the file descriptors and events to be monitored
-nfds: Number of file descriptors to be monitored
-timeout: Timeout (ms)
>0: Wait for the specified time;
= 0: Return immediately;
-1: Block indefinitely until an event occurs
Return Value>0: Returns the number ofrevents ≠ 0file descriptors
=0: timeout
-1: failure

struct pollfd

1
2
3
4
5
struct pollfd {
int fd; // monitored file descriptor
short events; // events to monitor
short revents; // events returned by kernel
};

events and revents of pollfd

event typeconstant valueas value for eventsas value for reventsdescription
read eventPOLLINnormal data readable
read eventPOLLRDNORMreadable (normal data)
Read eventPOLLRDBANDReadable (out-of-band data)
Read eventPOLLPRIReadable (high-priority data)
Write eventPOLLOUTWritable
Write eventPOLLWRNORMWritable (normal data)
Write eventPOLLWRBANDWritable (out-of-band data)
Error eventPOLLERRError occurred
Error eventPOLLHUPHang-up occurred
Error eventPOLLNVALDescriptor is not an open file

Driver-level poll

ItemContent
Prototypeunsigned int (*poll)(struct file *filp, struct poll_table_struct *wait);
FunctionTells the kernel whether the current device can be accessed in a non-blocking manner (readable/writable)
Parameters-filp: File structure pointer
-wait: poll_table passed by the kernel, used to register the wait queue
Return valueReturns a status bitmask (same as POLLIN/POLLOUT events)

If passive waiting is needed (to avoid wasting CPU cycles when sensing a character device), it must implementpoll()function, whenever a user-space program executes a system call on the file associated with the deviceselect()orpoll()will be called every timepoll()function.

The core kernel function of this method ispoll_wait(), which is defined in<linux/poll.h>. This header file should be included in the driver code.

Implementation in the driver

poll_wait()

ItemContent
PurposeAdd the driver’s wait queue to the poll_table for select/poll/epoll
Prototypevoid poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *wait);
Header file#include <linux/poll.h>
Parameters-filp: file
-queue: wait queue head (wait_queue_head_t)
-wait: poll_table (from application layer)
Return valueNone
Features**Does not block!**Only registers the wait queue

poll_wait()Based on the events registered in thestruct poll_tablestructure (passed as the third parameter), associate the device linked to thestruct filepstructure (as the first parameter) to the list of devices that can wake up the process (specified by the second parameterstruct wait_queue_head_tstructure, in which the process is sleeping).

The user process can runpoll()select()orepoll()system call to add a set of files to wait on to the wait queue, to check if any associated devices are ready.

Afterwards, the kernel will call the poll entry of the driver associated with each device file. Each driver’s poll method then calls poll_wait to register events for processes that need to receive kernel notifications, putting the processes to sleep until these events occur, and registering the driver as one that can wake up the processes.

Steps

  1. Declare a wait queue for each event type (read, write, exception) that requires passive waiting. When there is no data to read or the device is not writable, put the task into that queue:
1
2
static DECLARE_WAIT_QUEUE_HEAD(my_wq);
static DECLARE_WAIT_QUEUE_HEAD(my_rq);
  1. Implement the poll function like this
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <linux/poll.h>
static unsigned int eep_poll(struct file *file, poll_table *wait)
{
unsigned int reval_mask = 0;
poll_wait(file, &my_wq, wait);
poll_wait(file, &my_rq, wait);

if (new-data-is-ready)
reval_mask |= (POLLIN | POLLRDNORM);
if (ready_to_be_written)
reval_mask |= (POLLOUT | POLLWRNORM);
return reval_mask;
}
  1. When there is new data or the device becomes writable, notify the wait queue:
1
2
wake_up_interruptible(&my_rq); /* Ready to read */
wake_up_interruptible(&my_wq); /* Ready to write */

The following two methods can be used to notify a readable event:

  • Notify in the driver’s write() method, meaning the written data can be read back;
  • Notify in the IRQ handler, meaning data sent by the external device can be read back.

The following two methods can be used to notify a writable event:

  • Notify in the driver’s read() method, meaning the buffer is empty and can be refilled;
  • Notify in the IRQ handler, meaning the device has finished sending data and is ready to receive data again.

Example

app read.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <poll.h>

int main(int argc, char **argv)
{
pid_t pid;
int fd, ret;
char prefix[32];
int ops_nr = 10;

pid = fork();

if (pid < 0) {
perror("[fork error]");
exit(EXIT_FAILURE);
} else if (pid == 0) { //child
char buf[32];
int i;
sprintf(prefix, "[child, pid:%d]:", getpid());

fd = open("/dev/poll_test0", O_RDWR);
if (fd < 0) {
fprintf(stderr, "%s Error: %s(errno:%d)\n", prefix, strerror(errno), errno);
exit(EXIT_FAILURE);
}
// child write for about 10 secs
for (i = 0; i < ops_nr; i++) {
sprintf(buf, "%d", i);
ret = write(fd, buf, strlen(buf) + 1);
if (ret < 0) {
close(fd);
fprintf(stderr, "%s Error: %s(errno:%d)\n", prefix, strerror(errno),
errno);
exit(EXIT_FAILURE);
}
sleep(1);
}
close(fd);

} else { // parent
char buf[32];
struct pollfd poll_fds[1];

sprintf(prefix, "[parent, pid:%d]:", getpid());

fd = open("/dev/poll_test0", O_RDWR);
if (fd < 0) {
fprintf(stderr, "%s Error: %s(errno:%d)\n", prefix, strerror(errno), errno);
exit(EXIT_FAILURE);
}
poll_fds[0].fd = fd;
poll_fds[0].events = POLLIN;

for (;;) {
ret = poll(poll_fds, sizeof(poll_fds) / sizeof(struct pollfd), 3000);
if (ret == 0) {
printf("timeout\n");
} else if (ret < 0) {
close(fd);
fprintf(stderr, "%s Error: %s(errno:%d)\n", prefix, strerror(errno),
errno);
exit(EXIT_FAILURE);
} else {
if (poll_fds[0].revents & POLLIN) {
ret = read(fd, buf, sizeof(buf));
if (ret < 0) {
close(fd);
fprintf(stderr, "%s Error: %s(errno:%d)\n", prefix,
strerror(errno), errno);
exit(EXIT_FAILURE);
}
printf("%s read: %s, read ret: %d\n", prefix, buf, ret);
ops_nr--;
if (ops_nr == 0) {
break;
}
} else {
printf("%s poll_fds[0].revents is %d\n", prefix,
poll_fds[0].revents);
}
}
}

close(fd);
}

return 0;
}

Driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/mutex.h>

#define KBUF_SIZE 32
struct poll_test_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
char kbuf[KBUF_SIZE];
wait_queue_head_t waitque;
struct mutex lock;
};

static struct poll_test_drv_data *drv_dat;

int poll_test_open(struct inode *inode, struct file *file)
{
file->private_data = drv_dat;
pr_info("open is called by pid: %d\n", current->pid);
return 0;
}
ssize_t poll_test_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int ret;
size_t len;
struct poll_test_drv_data *dat = file->private_data;

pr_info("read is called by pid: %d\n", current->pid);

if (file->f_flags & O_NONBLOCK) {
mutex_lock(&dat->lock);
if (dat->kbuf[0] == '\0') {
mutex_unlock(&dat->lock);
return -EAGAIN;
}
mutex_unlock(&dat->lock);
} else {
/* The wait_event condition must be "reentrant and re-checkable" */
ret = wait_event_interruptible(dat->waitque, ({
int ready;
mutex_lock(&dat->lock);
ready = (dat->kbuf[0] != '\0');
mutex_unlock(&dat->lock);
ready;
}));

if (ret < 0) {
pr_info("pid: %d read is interrupted while waiting\n", current->pid);
return ret;
}
}

mutex_lock(&dat->lock);

len = min(size, strlen(dat->kbuf) + 1);

if (copy_to_user(buf, dat->kbuf, len)) {
mutex_unlock(&dat->lock);
return -EFAULT;
}

dat->kbuf[0] = '\0'; // reset kbuf string

mutex_unlock(&dat->lock);

return len;
}
ssize_t poll_test_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
size_t len;
struct poll_test_drv_data *dat = file->private_data;

pr_info("write is called by pid: %d\n", current->pid);

len = min(size, (size_t)(KBUF_SIZE - 1));

mutex_lock(&dat->lock);

if (copy_from_user(dat->kbuf, buf, len)) {
mutex_unlock(&dat->lock);
return -EFAULT;
}

dat->kbuf[len] = '\0'; // make sure kbuf a valid string(end with '\0')

mutex_unlock(&dat->lock);

/* Wake up after unlocking */
wake_up_interruptible(&dat->waitque);

return len;
}

__poll_t poll_test_poll(struct file *file, struct poll_table_struct *p)
{
struct poll_test_drv_data *dat = file->private_data;
__poll_t mask = 0;


pr_info("poll is called by pid: %d\n", current->pid);
poll_wait(file, &dat->waitque, p);

mutex_lock(&dat->lock);
if (dat->kbuf[0] != '\0')
mask |= POLLIN | POLLRDNORM;
mutex_unlock(&dat->lock);

return mask;
}

int poll_test_release(struct inode *inode, struct file *file)
{
pr_info("release is called by pid: %d\n", current->pid);
return 0;
}

static struct file_operations fops = {
.owner = THIS_MODULE,
.open = poll_test_open,
.read = poll_test_read,
.write = poll_test_write,
.poll = poll_test_poll,
.release = poll_test_release,
};

static int __init poll_test_init(void)
{
int ret;
drv_dat =
(struct poll_test_drv_data *)kzalloc(sizeof(struct poll_test_drv_data), GFP_KERNEL);
if (drv_dat == NULL) {
ret = -ENOMEM;
goto kzalloc_fail;
}
ret = alloc_chrdev_region(&drv_dat->dev_num, 0, 1, "test_chrdev_region");
if (ret < 0)
goto alloc_chrdev_region_fail;

cdev_init(&drv_dat->cdev, &fops);
drv_dat->cdev.owner = THIS_MODULE;
ret = cdev_add(&drv_dat->cdev, drv_dat->dev_num, 1);
if (ret < 0)
goto cdev_add_fail;

drv_dat->class = class_create(THIS_MODULE, "chrdev");
if (IS_ERR(drv_dat->class)) {
ret = PTR_ERR(drv_dat->class);
goto class_create_fail;
}

drv_dat->dev =
device_create(drv_dat->class, NULL, drv_dat->dev_num, NULL, "poll_test%d", 0);
if (IS_ERR(drv_dat->dev)) {
ret = PTR_ERR(drv_dat->dev);
goto device_create_fail;
}
drv_dat->kbuf[0] = '\0'; // always keep kbuf a valid string
mutex_init(&drv_dat->lock);
// Initialize wait queue
init_waitqueue_head(&drv_dat->waitque);

return 0;

device_create_fail:
class_destroy(drv_dat->class);
class_create_fail:
cdev_del(&drv_dat->cdev);
cdev_add_fail:
unregister_chrdev_region(drv_dat->dev_num, 1);
alloc_chrdev_region_fail:
kfree(drv_dat);
kzalloc_fail:
return ret;
}

static void __exit poll_test_exit(void)
{
device_destroy(drv_dat->class, drv_dat->dev_num);
class_destroy(drv_dat->class);
cdev_del(&drv_dat->cdev);
unregister_chrdev_region(drv_dat->dev_num, 1);
kfree(drv_dat);
}

module_init(poll_test_init);
module_exit(poll_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@163.com>");
MODULE_DESCRIPTION("This is a test sample for poll_test");

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
$ insmod poll_test.ko
[ 12.171314] poll_test: loading out-of-tree module taints kernel.
$ ./test_poll.o
[ 15.908771] open is called by pid: 101
[ 15.908795] open is called by pid: 102
[ 15.909346] write is called by pid: 102
[ 15.909902] poll is called by pid: 101
[ 15.910627] read is called by pid: 101
[parent, pid:101]: read: 0, read ret: 2
[ 15.915991] poll is called by pid: 101
[ 16.914086] write is called by pid: 102
[ 16.917324] poll is called by pid: 101
[ 16.917645] read is called by pid: 101
[parent, pid:101]: read: 1, read ret: 2
[ 16.918526] poll is called by pid: 101
[ 17.917861] write is called by pid: 102
[ 17.919219] poll is called by pid: 101
[ 17.919745] read is called by pid: 101
[parent, pid:101]: read: 2, read ret: 2
[ 17.922355] poll is called by pid: 101
[ 18.920467] write is called by pid: 102
[ 18.921819] poll is called by pid: 101
[ 18.923714] read is called by pid: 101
[parent, pid:101]: read: 3, read ret: 2
[ 18.924807] poll is called by pid: 101
[ 19.923667] write is called by pid: 102
[ 19.925368] poll is called by pid: 101
[ 19.926362] read is called by pid: 101
[parent, pid:101]: read: 4, read ret: 2
[ 19.927909] poll is called by pid: 101
[ 20.926284] write is called by pid: 102
[ 20.927049] poll is called by pid: 101
[ 20.927249] read is called by pid: 101
[parent, pid:101]: read: 5, read ret: 2
[ 20.927689] poll is called by pid: 101
[ 21.927490] write is called by pid: 102
[ 21.928342] poll is called by pid: 101
[ 21.928747] read is called by pid: 101
[parent, pid:101]: read: 6, read ret: 2
[ 21.929097] poll is called by pid: 101
[ 22.929789] write is called by pid: 102
[ 22.931600] poll is called by pid: 101
[ 22.932469] read is called by pid: 101
[parent, pid:101]: read: 7, read ret: 2
[ 22.934791] poll is called by pid: 101
[ 23.932151] write is called by pid: 102
[ 23.933917] poll is called by pid: 101
[ 23.936726] read is called by pid: 101
[parent, pid:101]: read: 8, read ret: 2
[ 23.938596] poll is called by pid: 101
[ 24.933805] write is called by pid: 102
[ 24.935854] poll is called by pid: 101
[ 24.936546] read is called by pid: 101
[parent, pid:101]: read: 9, read ret: 2
[ 24.937991] release is called by pid: 101
[ 25.937143] release is called by pid: 102

Signal-driven I/O

Signal-driven I/O does not require the application to poll the device status. Once the device is ready, it triggers the SIGIO signal, which then calls the registered handler function.

To implement signal-driven I/O, the application and driver must cooperate. The application uses signal-driven I/O in three steps:

  • Step 1: Register the signal handler. The application uses the signal function to register the handler for the SIGIO signal.
  • Step 2: Set the process that can receive this signal (using the fcntl function)
  • Step 3: Enable signal-driven I/O. Typically, use the F_SETFL command of the fcntl function to set the FASYNC flag.

In user space, either O_ASYNC or FASYNC can be used.

1
2
3
4
5
6
7
8
9
10
// /usr/aarch64-linux-gnu/include/bits/fcntl-linux.h
/* Define some more compatibility macros to be backward compatible with
BSD systems which did not managed to hide these kernel macros. */
#ifdef __USE_MISC
# define FAPPEND O_APPEND
# define FFSYNC O_FSYNC
# define FASYNC O_ASYNC
# define FNONBLOCK O_NONBLOCK
# define FNDELAY O_NDELAY
#endif /* Use misc. */

Driver implementation

When the application enables signal-driven I/O, it triggers the fasync function in the driver. Therefore, first implement the fasync function in the file_operations structure, with the function prototype as follows:

1
int (*fasync) (int fd,struct file *filp,int on)

In the driver, the fasync function calls the fasync_helper function to manipulate the fasync_struct, the fasync_helper function prototype is as follows:

1
int fasync_helper(int fd,struct file *filp,int on,struct fasync_struct **fapp)

Wherestruct fasync_structDefined as follows:

1
2
3
4
5
6
7
8
struct fasync_struct {
rwlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
struct rcu_head fa_rcu;
};

struct fasync_structis a linked list node used by the kernel to manage which processes wish to receive SIGIO/SIGURG signals for a certain file.

  • Eachfasync_struct= one subscriber

  • The driver stores thelinked list head pointer

  • kill_fasync()and traverses this linked list to send signals

When the device is ready,the driver needs to call the kill_fasync function to notify the application, at which point the application’s SIGIO signal handler will be executed. kill_fasync is responsible for sending the specified signal, and its function prototype is as follows:

1
void kill_fasync(struct fasync_struct **fp,int sig,int band)
  • Function parameters
    • fp: Thefasync_struct
    • sig: The signal to send
    • band: Set to POLLIN when readable, and POLLOUT when writable

Example

app

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <signal.h>

static int fd;
static char buf[32];
static char prefix[32];

static volatile sig_atomic_t data_ready = 0;

void handle_sigio(int sig)
{
data_ready = 1;
}

int main(int argc, char **argv)
{
pid_t pid;
int ret;

pid = fork();
if (pid < 0) {
perror("[fork error]");
exit(EXIT_FAILURE);
} else if (pid == 0) { // child write
int i;
sprintf(prefix, "[child pid:%d]", getpid());
fd = open("/dev/signal_io_test0", O_RDWR);
if (fd < 0) {
perror(prefix);
exit(EXIT_FAILURE);
}

for (i = 0; i < INT_MAX; i++) {
sprintf(buf, "Hello num %d", i);
ret = write(fd, buf, strlen(buf) + 1);
if (ret < 0) {
perror(prefix);
close(fd);
exit(EXIT_FAILURE);
}
printf("%s: write: %s\n", prefix, buf);
sleep(1);
}
close(fd);

} else { // parent read by signal
int flags;
sprintf(prefix, "[parent pid:%d]", getpid());
fd = open("/dev/signal_io_test0", O_RDWR);
if (fd < 0) {
perror(prefix);
exit(EXIT_FAILURE);
}
sprintf(prefix, "[parent pid:%d]", getpid());

// 1. Register the signal handler for the SIGIO signal

// signal(SIGIO, handle_sigio); // better not use signal

// use sigaction instead
struct sigaction act;
act.sa_handler = handle_sigio;
sigemptyset(&act.sa_mask); //Which signals should be temporarily blocked while this signal handler is executing
act.sa_flags = 0; // Do not enable any special behavior
sigaction(SIGIO, &act, NULL);

// 2. Set the process that can receive this signal
fcntl(fd, F_SETOWN, getpid());
// 3. Enable signal-driven I/O
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_ASYNC);
for (;;) {
pause(); // wait for signal
if (data_ready) {
data_ready = 0;
ret = read(fd, buf, sizeof(buf));
printf("%s: read: %s\n", prefix, buf);
}
}
close(fd);
}

return 0;
}

Driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <linux/signal.h>

#define KBUF_SIZE 64

struct drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
wait_queue_head_t waitq;
char kbuf[KBUF_SIZE];
struct mutex lock;
struct fasync_struct *fa;
};

static struct drv_data *drv_dat;

int signal_io_open(struct inode *inode, struct file *file)
{
file->private_data = drv_dat;
pr_info("signal_io_open is called by pid: %d\n", task_pid_nr(current));
return 0;
}

ssize_t signal_io_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
struct drv_data *dat = file->private_data;
size_t len;
int ret;

if (file->f_flags & O_NONBLOCK) { // Non-blocking mode
mutex_lock(&drv_dat->lock);
if (dat->kbuf[0] == '\0') {
mutex_unlock(&drv_dat->lock);
return -EAGAIN;
}
mutex_unlock(&drv_dat->lock);
} else { // Blocking mode
ret = wait_event_interruptible(drv_dat->waitq, ({
bool status;
mutex_lock(&drv_dat->lock);
status = (dat->kbuf[0] != '\0');
mutex_unlock(&drv_dat->lock);
status;
}));
if(ret < 0){
pr_info("signal_io_read called by pid: %d is interrupted\n", task_pid_nr(current));
return ret;
}
}

mutex_lock(&dat->lock);
len = min(size, strlen(dat->kbuf) + 1);

if (copy_to_user(buf, dat->kbuf, len) != 0) {
mutex_unlock(&dat->lock);
return -EFAULT;
}

dat->kbuf[0] = '\0'; // clear kbuf

mutex_unlock(&dat->lock);

pr_info("signal_io_read is called by pid: %d\n", task_pid_nr(current));

return len;
}
ssize_t signal_io_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
struct drv_data *dat = file->private_data;
int len = min(size, (size_t)(KBUF_SIZE - 1));

mutex_lock(&dat->lock);

if (copy_from_user(drv_dat->kbuf, buf, len) != 0) {
mutex_unlock(&dat->lock);
return -EFAULT;
}
dat->kbuf[len] = '\0';
mutex_unlock(&dat->lock);

wake_up_interruptible(&dat->waitq);
kill_fasync(&dat->fa, SIGIO, POLLIN);

pr_info("signal_io_write is called by pid: %d\n", task_pid_nr(current));
return len;
}
__poll_t signal_io_poll(struct file *file, struct poll_table_struct *p)
{
struct drv_data *dat = file->private_data;
__poll_t mask = 0;
pr_info("signal_io_poll is called by pid: %d\n", task_pid_nr(current));

poll_wait(file, &dat->waitq, p);

mutex_lock(&dat->lock);

if (dat->kbuf[0] != '\0') {
mask |= POLLIN | POLLRDNORM;
}

mutex_unlock(&dat->lock);

return mask;
}

int signal_io_fasync(int fd, struct file *file, int on)
{
struct drv_data *dat = file->private_data;
pr_info("signal_io_fasync is called by pid: %d\n", task_pid_nr(current));
return fasync_helper(fd, file, on, &dat->fa);
}

int signal_io_release(struct inode *inode, struct file *file)
{
pr_info("signal_io_release is called by pid: %d\n", task_pid_nr(current));

/* Force removal from the asynchronous queue */
signal_io_fasync(-1, file, 0);

return 0;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = signal_io_open,
.read = signal_io_read,
.write = signal_io_write,
.poll = signal_io_poll,
.fasync = signal_io_fasync,
.release = signal_io_release,
};

static int __init signal_io_init(void)
{
int ret;

drv_dat = (struct drv_data *)kzalloc(sizeof(struct drv_data), GFP_KERNEL);
if (drv_dat == NULL) {
ret = -ENOMEM;
goto kzalloc_fail;
}

ret = alloc_chrdev_region(&drv_dat->dev_num, 0, 1, "chrdev_test_region");
if (ret < 0)
goto alloc_chrdev_region_fail;

cdev_init(&drv_dat->cdev, &fops);
drv_dat->cdev.owner = THIS_MODULE;
ret = cdev_add(&drv_dat->cdev, drv_dat->dev_num, 1);
if (ret < 0)
goto cdev_add_fail;

drv_dat->class = class_create(THIS_MODULE, "chrdev_test");
if (IS_ERR(drv_dat->class)) {
ret = PTR_ERR(drv_dat->class);
goto class_create_fail;
}
drv_dat->dev =
device_create(drv_dat->class, NULL, drv_dat->dev_num, NULL, "signal_io_test%d", 0);
if (IS_ERR(drv_dat->dev)) {
ret = PTR_ERR(drv_dat->dev);
goto device_create_fail;
}
drv_dat->kbuf[0] = '\0';
// Initialize the wait queue
init_waitqueue_head(&drv_dat->waitq);
// Initialize the mutex lock
mutex_init(&drv_dat->lock);

return 0;
device_create_fail:
class_destroy(drv_dat->class);
class_create_fail:
cdev_del(&drv_dat->cdev);
cdev_add_fail:
unregister_chrdev_region(drv_dat->dev_num, 1);
alloc_chrdev_region_fail:
kfree(drv_dat);
kzalloc_fail:
return ret;
}

static void __exit signal_io_exit(void)
{
device_destroy(drv_dat->class, drv_dat->dev_num);
class_destroy(drv_dat->class);
cdev_del(&drv_dat->cdev);
unregister_chrdev_region(drv_dat->dev_num, 1);
kfree(drv_dat);
}

module_init(signal_io_init);
module_exit(signal_io_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@163.com>");
MODULE_DESCRIPTION("test sample for signal io");

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
$ insmod signal_io.ko
[ 13.310579] signal_io: loading out-of-tree module taints kernel.
$ ./test_signal.o
[ 16.052738] signal_io_open is called by pid: 102
[ 16.052761] signal_io_open is called by pid: 101
[ 16.053719] signal_io_write is called by pid: 102
[ 16.053816] signal_io_fasync is called by pid: 101
[child pid:102]: write: Hello num 0
[ 17.062149] signal_io_write is called by pid: 102
[child pid:102]: write: Hello num 1
[ 17.063588] signal_io_read is called by pid: 101
[parent pid:101]: read: Hello num 1
[ 18.066214] signal_io_write is called by pid: 102
[ 18.068176] signal_io_read is called by pid: 101
[child pid:102]: write: Hello num 2
[parent pid:101]: read: Hello num 2
[ 19.072969] signal_io_write is called by pid: 102
[child pid:102]: write: Hello num 3
[ 19.078051] signal_io_read is called by pid: 101
[parent pid:101]: read: Hello num 3
[ 20.078613] signal_io_write is called by pid: 102
[ 20.079554] signal_io_read is called by pid: 101
[child pid:102]: write: Hello num 4
[parent pid:101]: read: Hello num 4
[ 21.081299] signal_io_write is called by pid: 102
[child pid:102]: write: Hello num 5
[ 21.082047] signal_io_read is called by pid: 101
[parent pid:101]: read: Hello num 5
[ 22.082641] signal_io_write is called by pid: 102
[child pid:102]: write: Hello num 6
[ 22.083896] signal_io_read is called by pid: 101
[parent pid:101]: read: Hello num 6
[ 23.085311] signal_io_write is called by pid: 102
[child pid:102]: write: Hello num 7
[ 23.086379] signal_io_read is called by pid: 101
[parent pid:101]: read: Hello num 7
[ 24.088591] signal_io_write is called by pid: 102
[child pid:102]: write: Hello num 8
[ 24.091260] signal_io_read is called by pid: 101
[parent pid:101]: read: Hello num 8
[ 25.094550] signal_io_write is called by pid: 102
[ 25.095438] signal_io_read is called by pid: 101
[child pid:102]: write: Hello num 9
[parent pid:101]: read: Hello num 9
[ 26.098043] signal_io_write is called by pid: 102
[ 26.100002] signal_io_read is called by pid: 101
[child pid:102]: write: Hello num 10
[parent pid:101]: read: Hello num 10
[ 27.103532] signal_io_write is called by pid: 102
[child pid:102]: write: Hello num 11
[ 27.105751] signal_io_read is called by pid: 101
[parent pid:101]: read: Hello num 11
[ 28.112041] signal_io_write is called by pid: 102
[child pid:102]: write: Hello num 12
[ 28.114740] signal_io_read is called by pid: 101
[parent pid:101]: read: Hello num 12
[ 29.118790] signal_io_write is called by pid: 102
[ 29.121123] signal_io_read is called by pid: 101
[child pid:102]: write: Hello num 13
[parent pid:101]: read: Hello num 13
^C[ 29.356357] signal_io_fasync is called by pid: 101
[ 29.356383] signal_io_release is called by pid: 102
[ 29.356413] signal_io_fasync is called by pid: 102
[ 29.356999] signal_io_release is called by pid: 101
[ 29.357737] signal_io_fasync is called by pid: 101

Asynchronous I/O

Asynchronous I/O relies on the implementation in the application-layer glibc and can be independent of the Linux kernel

Reference:

Forio_uringYou can refer to this article on our site

Linux Kernel Printing

dmesg

You can obtain kernel print information using the dmesg command in the terminal. The specific usage of this command is as follows:
dmesgCommand

  • Full English name: display message
  • Function: The kernel stores print information in the ring buffer. You can use the dmesg command to view kernel print information.
  • Common parameters:
    • -C, --clear Clear the kernel ring buffer
    • -c, --read-clear Read and clear all messages
    • -T, --show-timestamp Display timestamps

The dmesg command can also be used in combination with the grep command. For example, to find print information containing the keyword ‘usb’, you can use the following command:

1
dmesg | grep usb

Combined with the tail command to view the last 100 lines

1
dmesg | tail -n 100

kmsg file

All kernel print information is output to the circular buffer ‘log_buf’. To facilitate reading kernel print information in user space, the Linux kernel driver maps this circular buffer to the file node kmsg under the /proc directory.

When reading the Log Buffer via cat or other applications, it can continuously wait for new logs, soaccessing /proc/kmsg is suitable for long-term log reading, and once there is a new log, it can be printed out.

First, use the following command to read the kmsg file, which will block when there is no new kernel print information

1
cat /proc/kmsg

Adjust kernel print level

Kernel log printing is controlled by the corresponding print level. When printk is called, the kernel compares the message log level with the current console log level; if the former is
higher (lower value) than the latter, the message is immediately printed to the console.

You can check the log level parameters like this: The output of print logs can be controlled by adjusting the kernel print level. Use the following command to view the current default print level

1
cat /proc/sys/kernel/printk

It can be seen that the kernel print level is determined by four numbers, ‘7 4 1 7’ corresponding to console_loglevel、default_message_loglevel、minimum_console_loglevel、default_console_loglevel, with specific type descriptions as follows:

  1. console_loglevel
    • Description: Controls which levels of messages can be output tothe terminal(console)。
    • Rule: Only when the log priority of the messageis higher than console_loglevelwill it be displayed on the terminal.
    • Example: “7” means allowing messages of all levels (0~7) to be output to the terminal.
  2. default_message_loglevel
    • Description:printkThe default log level (priority) when the function prints messages.
    • Example: “4” means the default message level iswarning
  3. minimum_console_loglevel
    • Description: The minimum value that console_loglevel can be set to.
    • Example: “1” means console_loglevel can be set to a minimum of 1, which isemerg
  4. default_console_loglevel
    • Description: The default value of console_loglevel when the kernel starts.
    • Example: “7” means by default,all levelsare allowed to output to the terminal.

include/linux/printk.c

1
2
3
4
5
6
7
int console_printk[4] = {
CONSOLE_LOGLEVEL_DEFAULT, /* console_loglevel */
MESSAGE_LOGLEVEL_DEFAULT, /* default_message_loglevel */
CONSOLE_LOGLEVEL_MIN, /* minimum_console_loglevel */
CONSOLE_LOGLEVEL_DEFAULT, /* default_console_loglevel */
};
EXPORT_SYMBOL_GPL(console_printk);

include/linux/kern_levels.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __KERN_LEVELS_H__
#define __KERN_LEVELS_H__

#define KERN_SOH "\001" /* ASCII Start Of Header */
#define KERN_SOH_ASCII '\001'

#define KERN_EMERG KERN_SOH "0" /* system is unusable */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
#define KERN_CRIT KERN_SOH "2" /* critical conditions */
#define KERN_ERR KERN_SOH "3" /* error conditions */
#define KERN_WARNING KERN_SOH "4" /* warning conditions */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */
#define KERN_INFO KERN_SOH "6" /* informational */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */

#define KERN_DEFAULT "" /* the default kernel loglevel */

/*
* Annotation for a "continued" line of log printout (only done after a
* line that had no enclosing \n). Only to be used by core/arch code
* during early bootup (a continued line is not SMP-safe otherwise).
*/
#define KERN_CONT KERN_SOH "c"

/* integer equivalents of KERN_<LEVEL> */
#define LOGLEVEL_SCHED -2 /* Deferred messages from sched code
* are set to this special level */
#define LOGLEVEL_DEFAULT -1 /* default (or last) loglevel */
#define LOGLEVEL_EMERG 0 /* system is unusable */
#define LOGLEVEL_ALERT 1 /* action must be taken immediately */
#define LOGLEVEL_CRIT 2 /* critical conditions */
#define LOGLEVEL_ERR 3 /* error conditions */
#define LOGLEVEL_WARNING 4 /* warning conditions */
#define LOGLEVEL_NOTICE 5 /* normal but significant condition */
#define LOGLEVEL_INFO 6 /* informational */
#define LOGLEVEL_DEBUG 7 /* debug-level messages */

#endif
NumberLevel NameDescription
0KERN_EMERGEmergency, system unavailable
1KERN_ALERTAlert, needs immediate action
2KERN_CRITCritical error
3KERN_ERRGeneral error
4KERN_WARNINGWarning
5KERN_NOTICENormal but important information
6KERN_INFOInformational message
7KERN_DEBUGDebug message

Modify kernel print level:

1
2
3
echo 0 4 1 7 > /proc/sys/kernel/printk
# If prompted permission denied, use the following command
echo "7 4 1 7" | sudo tee /proc/sys/kernel/printk

Before printing information, printk can include the corresponding print level macro definition, the specific format is as follows:

1
printk(打印等级 "打印信息")

For example:

1
printk(KERN_ERR "This is an error\n");

If the debug level is omitted,printk("This is anerror\n")the kernel will provide a debug level to this function based on theCONFIG_DEFAULT_MESSAGE_LOGLEVELconfiguration option (which is the default
kernel log level).

In practice, the following macros with more meaningful names can be used; they are wrappers for the previously defined pr_emerg、pr_alert、pr_crit、pr_err、pr_warning、pr_notice、pr_info and pr_debug:

1
pr_err("This is the same error\n");

llseek positioning device driver

Application layer lseek()

All open files have acurrent file offset, hereinafter referred to as cfo. cfo is usually anon-negative integer, indicating the number of bytes from the beginning of the file to the current position. Read and write operations typically start at cfo and increase cfo by the number of bytes read or written. When a file is opened, cfo is initialized to 0, unless O_APPEND is used. The cfo of a file can be changed using the lseek function.

ItemDescription
Function definitionoff_t lseek(int fd, off_t offset, int whence);
Header file#include <sys/types.h>
#include <unistd.h>
ParametersfdFile descriptor
Parametersoff_t offsetOffset,in bytesPositive and negative indicate forward and backward movement respectively
ParameterswhencePosition base, optionalSEEK_SET(beginning of file),SEEK_CUR(current pointer position),SEEK_END(end of file)
FunctionMove file read/write pointer; get file length; extend file space
Return valueSuccessReturns the current offset (SEEK_CUR), returns -1 on failure

Example

  • Set the file position pointer to 100 (beginning + 100 bytes)

    1
    lseek(fd,100,SEEK_SET);
  • Set the file position to the end of the file

    1
    lseek(fd,0,SEEK_END);
  • Determine the current file position

    1
    lseek(fd,0,SEEK_CUR);

Driver layer llseek()

1
loff_t (*llseek)(struct file *file, loff_t offset, int whence);
  • Purpose: File pointer offset operation (similar to lseek system call).
  • Parameters
    • file: File object.
    • offset: Offset relative to the current file position, defining how much the current position will change.
    • whence: Defines where to start seeking, possible values are as follows:
      • SEEK_SET: relative to the beginning of the file
      • SEEK_CUR: relative to the current file pointer
      • SEEK_END: relative to the end of the file
  • Return value
    • New file pointer position (loff_t
    • Returns a negative value on error (e.g.,-EINVAL

Usage steps

  1. Use a switch statement to check each whence case; since its values are limited, adjust newpos accordingly:
1
2
3
4
5
6
7
8
9
10
11
12
13
switch( whence ) {
case SEEK_SET:/* Position relative to the beginning of the file */
newpos = offset; /* The offset becomes the new position*/
break;
case SEEK_CUR: /* Position relative to the current file position */
newpos = file->f_pos + offset; /* Simply add the offset to the current position */
break;
case SEEK_END: /* Position relative to the end of the file*/
newpos = filesize + offset;
break;
default:
return -EINVAL;
}
  1. Check if newpos is valid:
1
2
if ( newpos < 0 )
return -EINVAL;
  1. Update f_pos with the new position
1
filp->f_pos = newpos;
  1. Return the new file pointer position
1
return newpos;

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#include <linux/init.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/mutex.h>

#define KMEM_SIZE 32
struct test_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
struct mutex lock;
char kmem[KMEM_SIZE];
};

static struct test_drv_data *drv_dat;

int test_open(struct inode *inode, struct file *file)
{
file->private_data = drv_dat;
return 0;
}

ssize_t test_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int ret;
struct test_drv_data *dat = file->private_data;
size_t len;

if (*offset >= KMEM_SIZE)
return 0; // EOF
len = min(size, (size_t)(KMEM_SIZE - *offset));

ret = mutex_lock_interruptible(&dat->lock);
if (ret < 0) {
pr_info("test_read is interrupted while acquiring the mutex\n");
return ret;
}
if (copy_to_user(buf, dat->kmem + *offset, len) != 0) {
mutex_unlock(&dat->lock);
return -EFAULT;
}

mutex_unlock(&dat->lock);

*offset += len;
return len;
}

ssize_t test_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int ret;
struct test_drv_data *dat = file->private_data;
size_t len = min(size, (size_t)(KMEM_SIZE - *offset));

if (*offset > KMEM_SIZE)
return 0; // EOF

ret = mutex_lock_interruptible(&dat->lock);
if (ret < 0) {
pr_info("test_read is interrupted while acquiring the mutex\n");
return ret;
}
if (copy_from_user(dat->kmem + *offset, buf, len) != 0) {
mutex_unlock(&dat->lock);
return -EFAULT;
}

mutex_unlock(&dat->lock);
*offset += len;
return len;
}

loff_t test_llseek(struct file *file, loff_t offset, int whence)
{
loff_t new_offset;

switch (whence) {
case SEEK_SET:
new_offset = offset;
break;
case SEEK_CUR:
new_offset = file->f_pos + offset;
break;
case SEEK_END:
new_offset = KMEM_SIZE + offset;
break;
default:
return -EINVAL;
}
if (new_offset < 0 || new_offset > KMEM_SIZE)
return -EINVAL;

file->f_pos = new_offset;

return new_offset;
// return fixed_size_llseek(file, offset, whence, KMEM_SIZE);
}

int test_release(struct inode *inode, struct file *file)
{
return 0;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_open,
.read = test_read,
.write = test_write,
.llseek = test_llseek,
.release = test_release,
};

static int __init llseek_test_init(void)
{
int ret;
drv_dat = kzalloc(sizeof(struct test_drv_data), GFP_KERNEL);
if (drv_dat == NULL) {
ret = -ENOMEM;
goto kzalloc_fail;
}
ret = alloc_chrdev_region(&drv_dat->dev_num, 0, 1, "test_chrdev_region");
if (ret < 0)
goto alloc_chrdev_region;
cdev_init(&drv_dat->cdev, &fops);
drv_dat->cdev.owner = THIS_MODULE;
ret = cdev_add(&drv_dat->cdev, drv_dat->dev_num, 1);
if (ret < 0)
goto cdev_add_fail;

drv_dat->class = class_create(THIS_MODULE, "chrdev");
if (IS_ERR(drv_dat->class)) {
ret = PTR_ERR(drv_dat->class);
goto class_create_fail;
}
drv_dat->dev =
device_create(drv_dat->class, NULL, drv_dat->dev_num, NULL, "llseek_test%d", 0);
if (IS_ERR(drv_dat->dev)) {
ret = PTR_ERR(drv_dat->dev);
goto device_create_fail;
}
mutex_init(&drv_dat->lock);

return 0;
device_create_fail:
class_destroy(drv_dat->class);
class_create_fail:
cdev_del(&drv_dat->cdev);
cdev_add_fail:
unregister_chrdev_region(drv_dat->dev_num, 1);
alloc_chrdev_region:
kfree(drv_dat);
kzalloc_fail:
return ret;
}

static void __exit llseek_test_exit(void)
{
device_destroy(drv_dat->class, drv_dat->dev_num);
class_destroy(drv_dat->class);
cdev_del(&drv_dat->cdev);
unregister_chrdev_region(drv_dat->dev_num, 1);
kfree(drv_dat);
}

module_init(llseek_test_init);
module_exit(llseek_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is a test description for llseek");

For devices with fixed buffer sizes, directly return the kernel’s already implementedfixed_size_llseek(file, offset, whence, KMEM_SIZE);

Test case:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#define DEV_PATH "/dev/llseek_test0"
#define BUF_SIZE 64

static void dump_buf(const char *tag, const char *buf, ssize_t len)
{
printf("%s (%zd bytes): \"", tag, len);
for (ssize_t i = 0; i < len; i++) {
if (buf[i] >= 32 && buf[i] <= 126)
putchar(buf[i]);
else
printf("\\x%02x", (unsigned char)buf[i]);
}
printf("\"\n");
}

int main(void)
{
int fd;
char buf[BUF_SIZE];
ssize_t ret;
off_t off;

printf("open %s\n", DEV_PATH);
fd = open(DEV_PATH, O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}

/* ================= write ================= */
const char *msg = "Hello llseek test!";
printf("\n[TEST] write \"%s\"\n", msg);

ret = write(fd, msg, strlen(msg));
if (ret < 0) {
perror("write");
goto out;
}
printf("write ret = %zd\n", ret);

/* ================= SEEK_SET ================= */
printf("\n[TEST] lseek SEEK_SET 0\n");
off = lseek(fd, 0, SEEK_SET);
if (off < 0) {
perror("lseek SEEK_SET");
goto out;
}
printf("current offset = %ld\n", off);

memset(buf, 0, sizeof(buf));
ret = read(fd, buf, sizeof(buf));
if (ret < 0) {
perror("read");
goto out;
}
dump_buf("read", buf, ret);

/* ================= SEEK_CUR ================= */
printf("\n[TEST] lseek SEEK_CUR -6\n");
off = lseek(fd, -6, SEEK_CUR);
if (off < 0) {
perror("lseek SEEK_CUR");
} else {
printf("current offset = %ld\n", off);
}

memset(buf, 0, sizeof(buf));
ret = read(fd, buf, 6);
dump_buf("read", buf, ret);

/* ================= SEEK_END ================= */
printf("\n[TEST] lseek SEEK_END -5\n");
off = lseek(fd, -5, SEEK_END);
if (off < 0) {
perror("lseek SEEK_END");
} else {
printf("current offset = %ld\n", off);
}

memset(buf, 0, sizeof(buf));
ret = read(fd, buf, 5);
dump_buf("read", buf, ret);

/* ================= EOF ================= */
printf("\n[TEST] read until EOF\n");
off = lseek(fd, 0, SEEK_SET);
printf("seek to %ld\n", off);

while (1) {
ret = read(fd, buf, 8);
if (ret == 0) {
printf("EOF reached\n");
break;
}
if (ret < 0) {
perror("read");
break;
}
dump_buf("chunk", buf, ret);
}

/* ================= invalid lseek ================= */
printf("\n[TEST] invalid lseek (beyond end)\n");
off = lseek(fd, 100, SEEK_SET);
if (off < 0)
printf("expected error: %s\n", strerror(errno));
else
printf("unexpected success, off=%ld\n", off);

out:
close(fd);
return 0;
}

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ ./test_llseek.o
open /dev/llseek_test0

[TEST] write "Hello llseek test!"
write ret = 18

[TEST] lseek SEEK_SET 0
current offset = 0
read (32 bytes): "Hello llseek test!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

[TEST] lseek SEEK_CUR -6
current offset = 26
read (6 bytes): "\x00\x00\x00\x00\x00\x00"

[TEST] lseek SEEK_END -5
current offset = 27
read (5 bytes): "\x00\x00\x00\x00\x00"

[TEST] read until EOF
seek to 0
chunk (8 bytes): "Hello ll"
chunk (8 bytes): "seek tes"
chunk (8 bytes): "t!\x00\x00\x00\x00\x00\x00"
chunk (8 bytes): "\x00\x00\x00\x00\x00\x00\x00\x00"
EOF reached

[TEST] invalid lseek (beyond end)
expected error: Invalid argument

ioctl device operation

Application-layer ioctl()

ItemDescription
Function definitionint ioctl(int fd, unsigned long op, … /* arg */ );
Header file#include <sys/ioctl.h>(May require device-specific header files, such aslinux/ioctl.h
Parameter fdOpened device file descriptor (e.g.,/dev/...
Parameter opIO control commands (usually via_IO,_IOR,_IOW,_IOWRmacros, etc.)
parameter arg(optional)Data associated with the request command, can beint*,void*,struct *etc.
FunctionExecute control commands on the device driver (non-data read/write type), used for configuring hardware, obtaining status, sending control instructions, etc.
Return valueSuccess: usually0(may also return other positive values, depending on the request)
Failure: returns**-1**, and sets errno

Among the above three parameters, the most important is the second op parameter, which is of type unsigned int. To efficiently use the op parameter to convey more control information, an unsigned int op is split into 4 segments, each with its own meaning. The bit field breakdown of unsigned int cmd is as follows:

1
2
| 31 30 | 29 ................ 16 | 15 ...... 8 | 7 .......... 0 |
| dir | size | type | nr |
  • op[31:30]dirData (args) transfer direction (read/write)
  • op[29:16]sizeSize of data (args)
  • op[15:8]typeCommand type, which can be understood as the command’s key, generally an ASCII code (a character from 0-255, some characters are already occupied, and the sequence range of each character may be partially occupied)
  • op[7:0]nrCommand sequence number, an 8-bit number (sequence number, between 0-255)

The op parameter is defined byioctl synthesis macrosand is obtained through definition. The four synthesis macros are defined as follows:

  1. Define a command that requires no parameters:
1
2
#define _IO(type,nr)
_IOC(_IOC_NONE,(type),(nr),0)
  1. Define a command where the application reads parameters from the driver:
1
2
#define _IOR(type,nr,size)
_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
  1. Define a command where the application writes parameters to the driver:
1
2
#define _IOW(type,nr,size)
_IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
  1. Define a command where parameters are passed bidirectionally (write first, then read):
1
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

The macro definition parameter descriptions are as follows:

  • typeCommand type, generally an ASCII code value called the magic number; a driver typically uses one type
  • nr: The sequence number under this command. A driver has multiple commands, and generally their type and sequence numbers differ
  • size: the type of args

For example, the following code can be used to define three macros: one that takes no parameters, one that writes parameters to the driver, and one that reads parameters from the driver:

1
2
3
#define CMD_TEST0 _IO('L',0)
#define CMD_TEST1 _IOW('L',1,int)
#define CMD_TEST2 _IOR('L',2,int)

In the Linux kernel’s ioctl interface, each ioctl command can only pass one user-space pointer (i.e., one parameter). But this does not mean only an integer can be passed; it can be done bypassing a structure pointer to achieve ‘multiple parameters’

SIZE = arg 参数的数据结构大小(字节数)It is automatically encoded into the ioctl command code when the driver creates it.

Example:

1
_IOR('M', 1, int)

It will automatically encode:

1
sizeof(int) = 4

into op. If it is:

1
_IOW('M', 2, struct task_info)

then:

1
sizeof(struct task_info)

User calls

1
2
struct task_info info;
ioctl(fd, MY_WRITE_TASK, &info);

After the kernel receives it:

1
copy_from_user(&kernel_buffer, (void __user *)arg, sizeof(struct task_info));

The kernel source code’s Documentation/driver-api/ioctl.rstwill provide detailed explanation.

Exampleeep_ioctl.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

#ifndef PACKT_IOCTL_H
#define PACKT_IOCTL_H

/*
* Need to choose a number for the driver,and the sequence number of each command
*/

#define EEP_MAGIC 'E'
#define ERASE_SEQ_NO 0x01
#define RENAME_SEQ_NO 0x02
#define ClEAR_BYTE_SEQ_NO 0x03
#define GET_SIZE 0x04

/*
* the partition name must be the maximum32byte
*/
#define MAX_PART_NAME 32

/*
* definitionioctlnumber
*/
#define EEP_ERASE _IO(EEP_MAGIC, ERASE_SEQ_NO)
#define EEP_RENAME_PART _IOW(EEP_MAGIC, RENAME_SEQ_NO, unsigned long)
#define EEP_GET_SIZE _IOR(EEP_MAGIC, GET_SIZE,int *)

#endif

driver layer ioctl()

1
2
long unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
long compat_ioctl (struct file *file, unsigned int cmd, unsigned long arg);

unlocked_ioctlfor user programs with native bit width

compat_ioctlfor 32-bit user programs running on 64-bit kernel

calling undefinedioctlreturns when command-ENOTTYerror

the kernel will useioctl decomposition macroto parse cmd:

  • _IOC_DIR(cmd)— data direction
  • _IOC_TYPE(cmd)—— device magic
  • _IOC_NR(cmd)—— command number
  • _IOC_SIZE(cmd)—— transfer data size

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/timer.h>

#define TIMER_OPEN _IO('L',0)
#define TIMER_CLOSE _IO('L',1)
#define TIMER_SET _IOW('L',2,int)

struct device_test{

dev_t dev_num; //device number
int major ; //major device number
int minor ; //minor device number
struct cdev cdev_test; // cdev
struct class *class; //class
struct device *device; //device
int counter;
};
static struct device_test dev1;
static void fnction_test(struct timer_list *t);//define the function_test timing function
DEFINE_TIMER(timer_test,fnction_test);//define a timer

void fnction_test(struct timer_list *t)
{
printk("this is fnction_test\n");
mod_timer(&timer_test,jiffies_64 + msecs_to_jiffies(dev1.counter));//use the mod_timer function to reset the timer time
}
static int cdev_test_open(struct inode *inode, struct file *file)
{
file->private_data=&dev1;//set private data
return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
file->private_data=&dev1;//set private data
return 0;
}

static long cdev_test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct device_test *test_dev = (struct device_test *)file->private_data;//set private data
switch(cmd){
case TIMER_OPEN:
add_timer(&timer_test);//Add a timer
break;
case TIMER_CLOSE:
del_timer(&timer_test);//Delete a timer
break;
case TIMER_SET:
test_dev->counter = arg;
timer_test.expires = jiffies_64 + msecs_to_jiffies(test_dev->counter);//Set timer duration
break;
default:
break;
}
return 0;
}
/*Device operation functions*/
struct file_operations cdev_test_fops = {
.owner = THIS_MODULE, //Pointing the owner field to this module prevents unloading the module while its operations are in use
.open = cdev_test_open,
.release = cdev_test_release,
.unlocked_ioctl = cdev_test_ioctl,
};
static int __init timer_dev_init(void) //Driver entry function
{
/*Register character device driver*/
int ret;
/*1 Create device number*/
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name"); //Dynamically allocate device number
if (ret < 0)
{
goto err_chrdev;
}
printk("alloc_chrdev_region is ok\n");

dev1.major = MAJOR(dev1.dev_num); //Get major device number
dev1.minor = MINOR(dev1.dev_num); //Get minor device number

printk("major is %d \n", dev1.major); //Print major device number
printk("minor is %d \n", dev1.minor); //Print minor device number
/*2 Initialize cdev*/
dev1.cdev_test.owner = THIS_MODULE;
cdev_init(&dev1.cdev_test, &cdev_test_fops);

/*3 Add a cdev to complete character device registration into the kernel*/
ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
if(ret<0)
{
goto err_chr_add;
}
/*4 Create Class*/
dev1. class = class_create(THIS_MODULE, "test");
if(IS_ERR(dev1.class))
{
ret=PTR_ERR(dev1.class);
goto err_class_create;
}
/*5 Create Device*/
dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
if(IS_ERR(dev1.device))
{
ret=PTR_ERR(dev1.device);
goto err_device_create;
}

return 0;

err_device_create:
class_destroy(dev1.class); //Delete Class

err_class_create:
cdev_del(&dev1.cdev_test); //Delete cdev

err_chr_add:
unregister_chrdev_region(dev1.dev_num, 1); //Unregister Device Number

err_chrdev:
return ret;
}

static void __exit timer_dev_exit(void) //Driver Exit Function
{
/*Unregister Character Device*/
unregister_chrdev_region(dev1.dev_num, 1); //Unregister Device Number
cdev_del(&dev1.cdev_test); //Delete cdev
device_destroy(dev1.class, dev1.dev_num); //Delete Device
class_destroy(dev1.class); //Delete Class
}
module_init(timer_dev_init);
module_exit(timer_dev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");

Encapsulate Driver-Provided API Functions

As driver engineers, we can certainly understand the function of each line of code. However, in general, applications are written by professional application engineers. The above code writing style is not conducive to application engineers’ understanding and program portability, so encapsulating the application API is an inevitable task.

Compile into a library file,One .c file per function, each compiled into a library file

Optimize driver stability and efficiency

Detect ioctl commands

The cmd command of ioctl is obtained through composition macros, and there are corresponding decomposition macros to obtain each parameter. The four decomposition macros are as follows:

  • Decompose the cmd command to obtain the command type:
1
_IOC_TYPE(cmd)
  • Decompose the cmd command to obtain the data (args) transfer direction:
1
_IOC_DIR(cmd)
  • Decompose the cmd command to obtain the command number:
1
_IOC_NR(cmd)
  • Decompose the cmd command to obtain the data (args) size:
1
_IOC_SIZE(cmd)

In the driver, the above decomposition macros can be used to judge the parameters such as the type of the incoming ioctl command, thereby determining whether the incoming parameters are correct, thus optimizing the stability of the driver.

Check whether the passed address is reasonable

access_ok()

ItemDescription
Function prototypeint access_ok(const void __user *addr, unsigned long size);
Header file#include <linux/uaccess.h>
Parameter addrPointer variable in user space, pointing to the starting address of the memory block to check
Parameter sizeSize of the memory block to check (in bytes)
FunctionCheck whether the specified user-space memory block is accessible (read/write), used to protect kernel access to user-space data security
Return valueSuccess:1(accessible) Failure:0(inaccessible)

Example:

1
2
3
4
len = sizeof(struct args);
if(!access_ok(arg,len)){
return -1;
}

Branch prediction optimization

Modern CPUs have ICache and pipeline mechanisms. That is, when executing the current instruction, the ICache pre-fetches subsequent instructions to improve efficiency. However, if the result of a conditional branch jumps to another instruction, pre-fetching the next instruction wastes time.

likely and unlikely macros

The ones we will use arelikely and unlikely macros, which will always make the compiler place the code with a high probability of execution in a forward position, thereby improving the efficiency of the driver.

The likely and unlikely macros are defined in the kernel source file include/linux/compiler.h, with the specific definitions as follows:

1
2
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

__builtin_The role of expect is to inform the compiler that the expression exp is more likely to equal c, allowing the compiler to better optimize the code based on this factor. Therefore, the role of likely and unlikely is to express that x is more likely (likely) or less likely (unlikely) to be true.

Example:

1
2
3
if(unlikely(copy_from_user(&test,(int *)arg,sizeof(test)) != 0)){
printk("copy_from_user error\n");
}

Driver Debugging

debugfs

Example

Kernel Examplearch/arm/mm/ptdump_debugfs.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// SPDX-License-Identifier: GPL-2.0
#include <linux/debugfs.h>
#include <linux/seq_file.h>

#include <asm/ptdump.h>

static int ptdump_show(struct seq_file *m, void *v)
{
struct ptdump_info *info = m->private;

ptdump_walk_pgd(m, info);
return 0;
}

static int ptdump_open(struct inode *inode, struct file *file)
{
return single_open(file, ptdump_show, inode->i_private);
}

static const struct file_operations ptdump_fops = {
.open = ptdump_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};

void ptdump_debugfs_register(struct ptdump_info *info, const char *name)
{
debugfs_create_file(name, 0400, NULL, info, &ptdump_fops);
}

Only need debugfs.h and seq_file.h these two header files, using seq_file makes printing simpler.

Debug Printing

dump_stack()

dump_stack()The role of is:

Print the call trace of the current CPU to the kernel log.

Calling it in kernel code is equivalent to executing in user spacebacktrace()or the C library’sprintf("%pS", __builtin_return_address())functions like that.

Example:

1
2
3
4
5
6
7
8
#include <linux/module.h>
#include <linux/kernel.h>
static int __init helloworld_init(void)
{
printk(KERN_EMERG "helloworld_init\n");
dump_stack();
return 0;
}

WARN_ON(condition)

Function of WARN_ON(condition):When the condition in parentheses is true, the kernel will throw a stack traceback and print the function call relationship.

It is usually used for the kernel to throw a warning, implying that something unreasonable has occurred.

WARN_ON actually also calls dump_stack, but with an additional parameter condition to determine whether the condition is true., for example, WARN_ON(1) means the condition check succeeds, and the function will execute successfully.

Example:

1
2
3
4
5
6
7
8
#include <linux/module.h>
#include <linux/kernel.h>
static int __init helloworld_init(void)
{
printk(KERN_EMERG "helloworld_init\n");
WARN_ON(1);
return 0;
}

BUG() and BUG_ON(condition)

There are many places in the kernel that call statements like BUG_ON(), which is very much like a runtime assertion in the kernel, meaning that execution should not have reached BUG_ON() statement,Once BUG_ON() executes, the kernel will immediately throw an oops(an oops is thrown only when a fatal error occurs in Linux), causing stack traceback and error message printing.

Most architectures define BUG() and BUG_ON() as some kind of illegal operation, which naturally produces the required oops. The application layer can see that a segmentation fault has occurred.

The parameter condition determines whether the condition holds. For example, BUG_ON(1) means the condition is true, and the function will execute successfully.

1
2
3
4
5
6
7
8
9
#include <linux/module.h>
#include <linux/kernel.h>

static int __init helloworld_init(void)
{
printk(KERN_EMERG "helloworld_init\n");
BUGON(1);
return 0;
}

panic (fmt…)

panic(fmt…) function:The output print will cause the system to crash and will print out the function call chain and register values.

1
2
3
4
5
6
7
8
#include <linux/module.h>
#include <linux/kernel.h>
static int __init helloworld_init(void)
{
printk(KERN_EMERG "helloworld_init\n");
panic("!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
return 0;
}